You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
76 lines
2.8 KiB
76 lines
2.8 KiB
using System.Collections.Generic; |
|
using System.Linq; |
|
using gaemstone.SourceGen.Utility; |
|
using Microsoft.CodeAnalysis; |
|
using Microsoft.CodeAnalysis.CSharp; |
|
using Microsoft.CodeAnalysis.CSharp.Syntax; |
|
|
|
namespace gaemstone.SourceGen.Generators; |
|
|
|
[Generator] |
|
public class ModuleGenerator |
|
: ISourceGenerator |
|
{ |
|
private static readonly DiagnosticDescriptor ModuleMayNotBeNested = new( |
|
"gaem0001", "Module may not be nested", |
|
"Type {0} marked with [Module] may not be a nested type", |
|
nameof(ModuleGenerator), DiagnosticSeverity.Error, true); |
|
|
|
private static readonly DiagnosticDescriptor ModuleMustBePartial = new( |
|
"gaem0002", "Module must be partial", |
|
"Type {0} marked with [Module] must be a partial type", |
|
nameof(ModuleGenerator), DiagnosticSeverity.Error, true); |
|
|
|
private static readonly DiagnosticDescriptor ModuleBuiltInMustHavePath = new( |
|
"gaem0003", "Built-in module must have [Path]", |
|
"Type {0} marked with [Module] is a built-in module (static), and therefore must have [Path] set", |
|
nameof(ModuleGenerator), DiagnosticSeverity.Error, true); |
|
|
|
|
|
public void Initialize(GeneratorInitializationContext context) |
|
=> context.RegisterForSyntaxNotifications(() => new SyntaxReceiver()); |
|
|
|
private class SyntaxReceiver |
|
: ISyntaxContextReceiver |
|
{ |
|
public HashSet<INamedTypeSymbol> Symbols { get; } |
|
= new(SymbolEqualityComparer.Default); |
|
|
|
public void OnVisitSyntaxNode(GeneratorSyntaxContext context) |
|
{ |
|
if (context.Node is not AttributeSyntax attrNode) return; |
|
var model = context.SemanticModel; |
|
|
|
var attrType = model.GetTypeInfo(attrNode).Type!; |
|
if (attrType.GetFullName(true) != "gaemstone.ECS.ModuleAttribute") return; |
|
|
|
var memberNode = attrNode.Parent?.Parent!; |
|
var memberSymbol = model.GetDeclaredSymbol(memberNode) as INamedTypeSymbol; |
|
Symbols.Add(memberSymbol!); |
|
} |
|
} |
|
|
|
public void Execute(GeneratorExecutionContext context) |
|
{ |
|
if (context.SyntaxContextReceiver is not SyntaxReceiver receiver) return; |
|
|
|
foreach (var symbol in receiver.Symbols) { |
|
var isNested = (symbol.ContainingType != null); |
|
if (isNested) |
|
context.ReportDiagnostic(Diagnostic.Create(ModuleMayNotBeNested, |
|
symbol.Locations.FirstOrDefault(), symbol.GetFullName())); |
|
|
|
var isPartial = symbol.DeclaringSyntaxReferences |
|
.Any(r => (r.GetSyntax() as ClassDeclarationSyntax)?.Modifiers |
|
.Any(t => t.IsKind(SyntaxKind.PartialKeyword)) ?? false); |
|
if (!isPartial) |
|
context.ReportDiagnostic(Diagnostic.Create(ModuleMustBePartial, |
|
symbol.Locations.FirstOrDefault(), symbol.GetFullName())); |
|
|
|
if (symbol.IsStatic && (symbol.GetAttribute("gaemstone.ECS.PathAttribute")? |
|
.ConstructorArguments.FirstOrDefault().Value == null)) |
|
context.ReportDiagnostic(Diagnostic.Create(ModuleBuiltInMustHavePath, |
|
symbol.Locations.FirstOrDefault(), symbol.GetFullName())); |
|
} |
|
} |
|
}
|
|
|