csharp/71/Ryder/Ryder.Lightweight/Program.cs

Program.cs
using System;
using System.IO;
using System.Linq;
using Microsoft.Codeastysis;
using Microsoft.Codeastysis.CSharp;
using Microsoft.Codeastysis.CSharp.Syntax;
using Microsoft.Codeastysis.Formatting;

namespace Ryder.Lightweight
{
    /// 
    ///   Program used to create the 'Ryder.Lightweight.cs' file.
    /// 
    internal static clast Program
    {
        private const string OUTPUT_FILENAME = "Ryder.Lightweight.cs";

        /// 
        ///   Entry point of the program: generates the 'Ryder.Lightweight.cs' file.
        /// 
        public static int Main(string[] args)
        {
            string ns = null;
            string dir = Directory.GetCurrentDirectory();
            string output = Path.Combine(Directory.GetCurrentDirectory(), OUTPUT_FILENAME);
            bool makePublic = false;

            for (int i = 0; i < args.Length; i++)
            {
                switch (args[i])
                {
                    case "--public":
                    case "-p":
                        makePublic = true;
                        break;
                    case "--namespace":
                    case "-n":
                        if (args.Length == i + 1)
                        {
                            Console.Error.WriteLine("No namespace given.");
                            return 1;
                        }

                        ns = args[++i];
                        break;
                    case "--directory":
                    case "-d":
                        if (args.Length == i + 1)
                        {
                            Console.Error.WriteLine("No directory given.");
                            return 1;
                        }

                        dir = args[++i];
                        break;
                    case "--output":
                    case "-o":
                        if (args.Length == i + 1)
                        {
                            Console.Error.WriteLine("No directory given.");
                            return 1;
                        }

                        output = args[++i];
                        break;
                    default:
                        Console.Error.WriteLine($"Unknown argument: '{args[i]}'.");
                        return 1;
                }
            }

            string methodRedirectionPath = Path.Combine(dir, "Redirection.Method.cs");
            string helpersPath = Path.Combine(dir, "Helpers.cs");

            if (!File.Exists(methodRedirectionPath) || !File.Exists(helpersPath))
            {
                Console.Error.WriteLine("Invalid directory given.");
                return 1;
            }

            try
            {
                // Read files
                string methodRedirectionContent = File.ReadAllText(methodRedirectionPath);
                string helpersContent = File.ReadAllText(helpersPath);

                // Parse content to trees, and get their root / clastes / usings
                SyntaxTree methodRedirectionTree = SyntaxFactory.ParseSyntaxTree(methodRedirectionContent, path: methodRedirectionPath);
                SyntaxTree helpersTree = SyntaxFactory.ParseSyntaxTree(helpersContent, path: helpersPath);

                CompilationUnitSyntax methodRedirection = methodRedirectionTree.GetCompilationUnitRoot();
                CompilationUnitSyntax helpers = helpersTree.GetCompilationUnitRoot();

                UsingDirectiveSyntax[] usings = methodRedirection.Usings.Select(x => x.Name.ToString())
                    .Concat(helpers.Usings.Select(x => x.Name.ToString()))
                    .Distinct()
                    .OrderBy(x => x)
                    .Select(x => SyntaxFactory.UsingDirective(SyntaxFactory.ParseName(x)))
                    .ToArray();

                ClastDeclarationSyntax methodRedirectionClast = methodRedirection.DescendantNodes()
                                                                                 .OfType()
                                                                                 .First();
                ClastDeclarationSyntax helpersClast = helpers.DescendantNodes()
                                                             .OfType()
                                                             .First();

                // Set visibility of main clast
                if (!makePublic)
                {
                    var modifiers = methodRedirectionClast.Modifiers;
                    var publicModifier = modifiers.First(x => x.Kind() == SyntaxKind.PublicKeyword);

                    methodRedirectionClast = methodRedirectionClast.WithModifiers(
                        modifiers.Replace(publicModifier, SyntaxFactory.Token(SyntaxKind.InternalKeyword))
                    );
                }

                // Set visibility of helpers clast
                helpersClast = helpersClast.WithModifiers(
                    helpersClast.Modifiers.Replace(
                        helpersClast.Modifiers.First(x => x.Kind() == SyntaxKind.InternalKeyword),
                        SyntaxFactory.Token(SyntaxKind.PrivateKeyword)
                    )
                );

                // Change helpers clast extension methods to normal methods
                var extMethods = helpersClast.DescendantNodes()
                                             .OfType()
                                             .Where(x => x.ParameterList.DescendantTokens().Any(tok => tok.Kind() == SyntaxKind.ThisKeyword));
                var extMethodsNames = extMethods.Select(x => x.Identifier.Text);

                helpersClast = helpersClast.ReplaceNodes(
                    helpersClast.DescendantNodes().OfType().Where(x => x.Modifiers.Any(SyntaxKind.ThisKeyword)),
                    (x,_) => x.WithModifiers(x.Modifiers.Remove(x.Modifiers.First(y => y.Kind() == SyntaxKind.ThisKeyword)))
                );

                // Disable overrides
                var members = methodRedirectionClast.Members;

                for (int i = 0; i < members.Count; i++)
                {
                    var member = members[i];

                    if (!(member is MethodDeclarationSyntax method))
                    {
                        if (member is ConstructorDeclarationSyntax ctor)
                            members = members.Replace(ctor, ctor.WithIdentifier(SyntaxFactory.Identifier("Redirection")));

                        continue;
                    }

                    var overrideModifier = method.Modifiers.FirstOrDefault(x => x.Kind() == SyntaxKind.OverrideKeyword);

                    if (overrideModifier == default(SyntaxToken))
                        continue;

                    method = method.WithModifiers(
                        method.Modifiers.Remove(overrideModifier)
                    );

                    members = members.Replace(member, method);
                }

                // Add missing field
                var field = SyntaxFactory.FieldDeclaration(
                    SyntaxFactory.VariableDeclaration(
                        SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.BoolKeyword)),
                        SyntaxFactory.SeparatedList(new[] {
                            SyntaxFactory.VariableDeclarator("isRedirecting")
                        })
                    )
                );

                const string DOCS = @"
    /// 
    ///   Provides the ability to redirect calls from one method to another.
    /// 
";

                var disposableType = SyntaxFactory.SimpleBaseType(SyntaxFactory.ParseTypeName(nameof(IDisposable)));

                methodRedirectionClast = methodRedirectionClast.WithMembers(members)
                    // Add docs
                    .WithLeadingTrivia(SyntaxFactory.Comment(DOCS))
                    // Rename to 'Redirection'
                    .WithIdentifier(SyntaxFactory.Identifier("Redirection"))
                    // Disable inheritance, but implement IDisposable
                    .WithBaseList(SyntaxFactory.BaseList().AddTypes(disposableType))
                    // Embed helpers, missing field
                    .AddMembers(field, helpersClast);

                // Generate namespace (or member, if no namespace is specified)
                MemberDeclarationSyntax @namespace = ns == null
                    ? (MemberDeclarationSyntax)methodRedirectionClast
                    : SyntaxFactory.NamespaceDeclaration(SyntaxFactory.ParseName(ns)).AddMembers(methodRedirectionClast);

                var extCalls = @namespace.DescendantNodes()
                                         .OfType()
                                         .Where(x => x.Expression is MemberAccessExpressionSyntax access && extMethodsNames.Contains(access.Name.Identifier.Text));
                var helpersAccess = SyntaxFactory.IdentifierName("Helpers");

                @namespace = @namespace.ReplaceNodes(
                    extCalls,
                    (x, _) => SyntaxFactory.InvocationExpression(((MemberAccessExpressionSyntax)x.Expression).WithExpression(helpersAccess)).WithArgumentList(x.ArgumentList.WithArguments(x.ArgumentList.Arguments.Insert(0, SyntaxFactory.Argument(((MemberAccessExpressionSyntax)x.Expression).Expression)))));

                // Generate syntax root
                CompilationUnitSyntax root = SyntaxFactory.CompilationUnit()
                                                          .AddUsings(usings)
                                                          .AddMembers(@namespace);

                // Print root to file
                using (FileStream fs = File.OpenWrite(output))
                using (TextWriter writer = new StreamWriter(fs))
                {
                    fs.SetLength(0);

                    Formatter.Format(root, new AdhocWorkspace()).WriteTo(writer);
                }
            }
            catch (Exception e)
            {
                Console.Error.WriteLine("Error encountered:");
                Console.Error.WriteLine(e.Message);
                return 1;
            }

            return 0;
        }
    }
}