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;
}
}
}