- Removed NadekoCommand and Aliases attribute from all commands

- All commands must be marked as partial
- Added [Cmd] Attribute to all commands
- Cmd Attribute comes from the source generator which adds [NadekoCommand] and [Aliases] Attribute to each command
- Should be updated in the future probably to be more performant and maybe add extra data to the commands
- Started reorganizing modules and submodules
This commit is contained in:
Kwoth
2021-12-31 16:04:12 +01:00
parent 6eee161b6b
commit 25eeffa163
107 changed files with 1620 additions and 3236 deletions

View File

@@ -0,0 +1,343 @@
using System.CodeDom.Compiler;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
namespace NadekoBot.Generators.Command;
[Generator]
public class CommandAttributesGenerator : IIncrementalGenerator
{
public const string ATTRIBUTE = @"// <AutoGenerated />
namespace NadekoBot.Common;
[System.AttributeUsage(System.AttributeTargets.Method)]
public class CmdAttribute : System.Attribute
{
}";
public class MethodModel
{
public string Namespace { get; set; }
public IReadOnlyCollection<string> Classes { get; set; }
public string ReturnType { get; set; }
public string MethodName { get; set; }
public IEnumerable<string> Params { get; set; }
}
public class FileModel
{
public string Namespace { get; set; }
public IReadOnlyCollection<string> ClassHierarchy { get; set; }
public IReadOnlyCollection<MethodModel> Methods { get; set; }
}
public void Initialize(IncrementalGeneratorInitializationContext context)
{
context.RegisterPostInitializationOutput(static ctx => ctx.AddSource(
"CmdAttribute.g.cs",
SourceText.From(ATTRIBUTE, Encoding.UTF8)));
var methods = context.SyntaxProvider
.CreateSyntaxProvider(
static (node, _) => node is MethodDeclarationSyntax { AttributeLists.Count: > 0 },
static (ctx, cancel) => Transform(ctx, cancel))
.Where(static m => m != default)
.Where(static m => m.ChildTokens().Any(static x => x.IsKind(SyntaxKind.PublicKeyword)));
var compilationMethods = context.CompilationProvider.Combine(methods.Collect());
context.RegisterSourceOutput(compilationMethods,
static (ctx, tuple) => RegisterAction(in ctx, tuple.Left, in tuple.Right));
}
private static void RegisterAction(in SourceProductionContext ctx,
Compilation comp,
in ImmutableArray<MethodDeclarationSyntax> methods)
{
if (methods.IsDefaultOrEmpty)
return;
var models = GetModels(comp, methods, ctx.CancellationToken);
foreach (var model in models)
{
var source = GetSourceText(model);
ctx.AddSource($"{model.Namespace}.{string.Join(".", model.ClassHierarchy)}.g.cs",
SourceText.From(source, Encoding.UTF8));
}
}
// private static void GenerateFileFromModel(in SourceProductionContext ctx, IGrouping<string, DataModel> @group)
// {
// using var sw = new StringWriter();
// using var tw = new IndentedTextWriter(sw);
//
// foreach (var model in group)
// {
// tw.WriteLine($"public partial {model.ReturnType} {model.MethodName}({string.Join(", ", model.Params)});");
// }
//
// GenerateFileFromModel(ctx, sw.ToString(), group.Key, group.AsEnumerable());
//
//
// }
// private static void GenerateFileFromModel(SourceProductionContext ctx, string innerContent, string groupName, IEnumerable<DataModel> modelsEnum)
// {
// using var sw = new StringWriter();
// using var tw = new IndentedTextWriter(sw);
//
// var models = modelsEnum.ToList();
// var referenceModel = models.First();
// tw.WriteLine($"namespace {referenceModel.Namespace};");
//
// foreach (var className in referenceModel.ClassNames.Reverse().ToList())
// {
// tw.WriteLine($"public partial class {className}");
// tw.WriteLine("{");
// tw.WriteLine();
// tw.Indent ++;
// }
//
// foreach (var model in models)
// {
// tw.WriteLine($"public partial {model.ReturnType} {model.MethodName}({string.Join(", ", model.Params)});");
// }
//
// foreach (var _ in referenceModel.ClassNames)
// {
// tw.Indent --;
// tw.WriteLine("}");
// }
//
// // tw.Indent--;
// // tw.WriteLine("}");
// tw.Flush();
//
// ctx.AddSource($"{groupName}.qwertyus.g.cs",
// SourceText.From(sw.ToString(), Encoding.UTF8));
// }
private static string GetSourceText(FileModel model)
{
using var sw = new StringWriter();
using var tw = new IndentedTextWriter(sw);
tw.WriteLine("// <AutoGenerated />");
tw.WriteLine("#pragma warning disable CS1066");
tw.WriteLine($"namespace {model.Namespace};");
foreach (var className in model.ClassHierarchy)
{
tw.WriteLine($"public partial class {className}");
tw.Write("{");
tw.Indent ++;
}
foreach (var method in model.Methods)
{
tw.WriteLine();
tw.WriteLine("[NadekoCommand]");
tw.WriteLine("[Aliases]");
tw.WriteLine($"public partial {method.ReturnType} {method.MethodName}({string.Join(", ", method.Params)});");
}
foreach (var _ in model.ClassHierarchy)
{
tw.Indent --;
tw.WriteLine("}");
}
tw.Flush();
return sw.ToString();
}
private static IReadOnlyCollection<FileModel> GetModels(Compilation compilation,
in ImmutableArray<MethodDeclarationSyntax> methods,
CancellationToken cancel)
{
var models = new List<FileModel>();
var methodModels = methods.Select(x => MethodDeclarationToMethodModel(compilation, x));
var groups = methodModels
.GroupBy(static x => $"{x.Namespace}.{string.Join(".", x.Classes)}");
foreach (var group in groups)
{
var elems = group.ToList();
var model = new FileModel()
{
Methods = elems,
Namespace = elems[0].Namespace,
ClassHierarchy = elems[0].Classes
};
models.Add(model);
}
return models;
}
private static MethodModel MethodDeclarationToMethodModel(Compilation comp, MethodDeclarationSyntax decl)
{
// SpinWait.SpinUntil(static () => Debugger.IsAttached);
var methodModel = new MethodModel();
var semanticModel = comp.GetSemanticModel(decl.SyntaxTree);
methodModel.Params = decl.ParameterList.Parameters
.Select(p =>
{
var prefix = p.Modifiers.Any(static x => x.IsKind(SyntaxKind.ParamsKeyword))
? "params "
: string.Empty;
var type = semanticModel
.GetTypeInfo(p.Type!)
.Type!
.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
var name = p.Identifier.Text;
var suffix = string.Empty;
if (p.Default is not null)
{
if (p.Default.Value is LiteralExpressionSyntax)
{
suffix = " = " + p.Default.Value;
}
else if (p.Default.Value is MemberAccessExpressionSyntax maes)
{
var maesSemModel = comp.GetSemanticModel(maes.SyntaxTree);
var sym = maesSemModel.GetSymbolInfo(maes.Name);
if (sym.Symbol is null)
{
suffix = " = " + p.Default.Value;
}
else
{
suffix = " = " + sym.Symbol.ToDisplayString();
}
}
}
return $"{prefix}{type} {name}{suffix}";
})
.ToList();
methodModel.MethodName = decl.Identifier.Text;
methodModel.ReturnType = decl.ReturnType.ToString();
methodModel.Namespace = GetNamespace(decl);
methodModel.Classes = GetClasses(decl);
return methodModel;
}
// private static IEnumerable<string> GetParams(Compilation compilation, MethodDeclarationSyntax method)
// {
// var semModel = compilation.GetSemanticModel(method.SyntaxTree);
// return method.ParameterList.Parameters.Select(p =>
// {
// var prefix = string.Empty;
//
// var typeSymbol = semModel.GetTypeInfo(p).Type;
//
// if (p.Modifiers.Any(static x => x.IsKind(SyntaxKind.ParamsKeyword)))
// prefix += "params ";
// return prefix + $"{typeSymbol?.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)} {p.Identifier.ToString()}";
// });
// // foreach (var param in method.ParameterList.Parameters)
// // {
// // yield return param.ToString();
// // }
// }
//https://github.com/andrewlock/NetEscapades.EnumGenerators/blob/main/src/NetEscapades.EnumGenerators/EnumGenerator.cs
static string GetNamespace(MethodDeclarationSyntax declarationSyntax)
{
// determine the namespace the class is declared in, if any
var nameSpace = string.Empty;
var parentOfInterest = declarationSyntax.Parent;
while (parentOfInterest != null)
{
parentOfInterest = parentOfInterest.Parent;
if (parentOfInterest is BaseNamespaceDeclarationSyntax ns)
{
nameSpace = ns.Name.ToString();
while (true)
{
if (ns.Parent is not NamespaceDeclarationSyntax parent)
{
break;
}
ns = parent;
nameSpace = $"{ns.Name}.{nameSpace}";
}
return nameSpace;
}
}
return default;
}
static IReadOnlyCollection<string> GetClasses(MethodDeclarationSyntax declarationSyntax)
{
// determine the namespace the class is declared in, if any
var classes = new LinkedList<string>();
var parentOfInterest = declarationSyntax.Parent;
while (parentOfInterest is not null)
{
if (parentOfInterest is ClassDeclarationSyntax cds)
{
classes.AddFirst(cds.Identifier.ToString());
}
parentOfInterest = parentOfInterest.Parent;
}
Debug.WriteLine($"Method {declarationSyntax.Identifier.Text} has {classes.Count} classes");
return classes;
}
private static MethodDeclarationSyntax Transform(GeneratorSyntaxContext ctx, CancellationToken cancel)
{
var methodDecl = (MethodDeclarationSyntax)ctx.Node;
foreach (var attListSyntax in methodDecl.AttributeLists)
{
foreach (var attSyntax in attListSyntax.Attributes)
{
if (cancel.IsCancellationRequested)
return default;
var symbol = ModelExtensions.GetSymbolInfo(ctx.SemanticModel, attSyntax).Symbol;
if (symbol is not IMethodSymbol attSymbol)
continue;
if (attSymbol.ContainingType.ToDisplayString() == "NadekoBot.Common.CmdAttribute")
return methodDecl;
}
}
return default;
}
}