mirror of
https://github.com/Tyrrrz/CliFx.git
synced 2025-10-25 15:19:17 +00:00
131 lines
4.7 KiB
C#
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;
|
|
}
|
|
}
|
|
"""
|
|
)
|
|
);
|
|
}
|
|
}
|