Files
CliFx/CliFx.SourceGeneration/CommandSchemaGenerator.cs
Tyrrrz 40beb283d5 asd
2024-09-21 21:03:05 +03:00

131 lines
4.7 KiB
C#

using System.Linq;
using CliFx.SourceGeneration.SemanticModel;
using CliFx.SourceGeneration.Utils.Extensions;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace CliFx.SourceGeneration;
[Generator]
public class CommandSchemaGenerator : IIncrementalGenerator
{
public void Initialize(IncrementalGeneratorInitializationContext context)
{
var values = context.SyntaxProvider.ForAttributeWithMetadataName<(
CommandSymbol?,
Diagnostic?
)>(
KnownSymbolNames.CliFxCommandAttribute,
(n, _) => n is TypeDeclarationSyntax,
(x, _) =>
{
// Predicate above ensures that these casts are safe
var commandTypeSyntax = (TypeDeclarationSyntax)x.TargetNode;
var commandTypeSymbol = (INamedTypeSymbol)x.TargetSymbol;
// Check if the target type and all its containing types are partial
if (
commandTypeSyntax
.AncestorsAndSelf()
.Any(a =>
a is TypeDeclarationSyntax t
&& !t.Modifiers.Any(m => m.IsKind(SyntaxKind.PartialKeyword))
)
)
{
return (
null,
Diagnostic.Create(
DiagnosticDescriptors.CommandMustBePartial,
commandTypeSyntax.Identifier.GetLocation()
)
);
}
// Check if the target type implements ICommand
var hasCommandInterface = commandTypeSymbol.AllInterfaces.Any(i =>
i.DisplayNameMatches(KnownSymbolNames.CliFxCommandInterface)
);
if (!hasCommandInterface)
{
return (
null,
Diagnostic.Create(
DiagnosticDescriptors.CommandMustImplementInterface,
commandTypeSymbol.Locations.First()
)
);
}
// Resolve the command
var commandAttribute = x.Attributes.First(a =>
a.AttributeClass?.DisplayNameMatches(KnownSymbolNames.CliFxCommandAttribute)
== true
);
var command = CommandSymbol.FromSymbol(commandTypeSymbol, commandAttribute);
// TODO: validate command
return (command, null);
}
);
// Report diagnostics
var diagnostics = values.Select((v, _) => v.Item2).WhereNotNull();
context.RegisterSourceOutput(diagnostics, (x, d) => x.ReportDiagnostic(d));
// Generate command schemas
var symbols = values.Select((v, _) => v.Item1).WhereNotNull();
context.RegisterSourceOutput(
symbols,
(x, c) =>
x.AddSource(
$"{c.Type.FullyQualifiedName}.CommandSchema.Generated.cs",
// lang=csharp
$$"""
namespace {{c.Type.Namespace}};
partial class {{c.Type.Name}}
{
public static CliFx.Schema.CommandSchema<{{c.Type.FullyQualifiedName}}> Schema { get; } = {{c.GenerateSchemaInitializationCode()}};
}
"""
)
);
// Generate extension methods
var symbolsCollected = symbols.Collect();
context.RegisterSourceOutput(
symbolsCollected,
(x, cs) =>
x.AddSource(
"CommandSchemaExtensions.Generated.cs",
// lang=csharp
$$"""
namespace CliFx;
static partial class GeneratedExtensions
{
public static CliFx.CliApplicationBuilder AddCommandsFromThisAssembly(this CliFx.CliApplicationBuilder builder)
{
{{
cs.Select(c => c.Type.FullyQualifiedName)
.Select(t =>
// lang=csharp
$"builder.AddCommand({t}.Schema);"
)
.JoinToString("\n")
}}
return builder;
}
}
"""
)
);
}
}