Skip to content

Commit 76609ad

Browse files
committed
RegisterCommands
1 parent 40abe56 commit 76609ad

File tree

3 files changed

+100
-61
lines changed

3 files changed

+100
-61
lines changed

sandbox/GeneratorSandbox/Program.cs

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@
1515

1616
// var app = ConsoleApp.Create();
1717

18-
// app.Add<MyCommands>();
19-
// app.Add<MyCommands>();
18+
// app.Add<MyCommands>();
19+
app.Add<MyCommands>();
2020

21-
// app.Run(args);
21+
app.Run(args);
2222

2323

2424

@@ -33,6 +33,10 @@ public void Execute(int x)
3333

3434
public class MyCommands
3535
{
36+
/// <summary>
37+
///
38+
/// </summary>
39+
/// <param name="msg2">foobarbaz!</param>
3640
[Command("Error1")]
3741
public void Error1(string msg = @"\")
3842
{
@@ -95,15 +99,12 @@ internal static partial class ConsoleApp
9599
//}
96100
}
97101

98-
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
99-
internal sealed class RegisterCommandsAttribute : Attribute
100-
{
101-
public string CommandPath { get; set; } = "";
102-
}
102+
103103
}
104104

105105
namespace HogeHoge
106106
{
107+
107108
public class BatchAttribute : Attribute
108109
{
109110
}
@@ -112,4 +113,13 @@ public class BatchAttribute : Attribute
112113
public class Batch2Attribute : BatchAttribute
113114
{
114115
}
116+
117+
118+
[RegisterCommands, Batch]
119+
public class Takoyaki
120+
{
121+
public void Error12345()
122+
{
123+
}
124+
}
115125
}

src/ConsoleAppFramework/ConsoleAppGenerator.cs

Lines changed: 70 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
2020
return x.Display?.EndsWith("Microsoft.Extensions.DependencyInjection.Abstractions.dll") ?? false;
2121
})
2222
.Collect()
23-
.Select((x, ct) => x.Any());
23+
.Select((x, ct) => x.Length != 0);
2424

2525
context.RegisterSourceOutput(hasDependencyInjection, EmitConsoleAppCreateConfigure);
2626

@@ -99,16 +99,20 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
9999
.Select((x, ct) => new CollectBuilderContext(x, ct))
100100
.WithTrackingName("ConsoleApp.Builder.2_Collect");
101101

102-
// WIP
103-
//var registerCommands = context.SyntaxProvider.ForAttributeWithMetadataName("ConsoleAppFramework.RegisterCommandsAttribute",
104-
// (node, token) => true,
105-
// (ctx, token) => ctx)
106-
// .Collect()
107-
// .Select((x, token) => new CollectBuilderContext(default, token));
102+
var registerCommands = context.SyntaxProvider.ForAttributeWithMetadataName("ConsoleAppFramework.RegisterCommandsAttribute",
103+
(node, token) => true,
104+
(ctx, token) => ctx)
105+
.Collect();
108106

109-
//var lr = builderSource.Combine(registerCommands); // combine with registerCommands
107+
var combined = builderSource.Combine(registerCommands)
108+
.Select((tuple, token) =>
109+
{
110+
var (context, commands) = tuple;
111+
context.AddRegisterAttributes(commands);
112+
return context;
113+
});
110114

111-
context.RegisterSourceOutput(builderSource, EmitConsoleAppBuilder);
115+
context.RegisterSourceOutput(combined, EmitConsoleAppBuilder);
112116
}
113117

114118
public const string ConsoleAppBaseCode = """
@@ -175,6 +179,22 @@ public CommandAttribute(string command)
175179
}
176180
}
177181
182+
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
183+
internal sealed class RegisterCommandsAttribute : Attribute
184+
{
185+
public string CommandPath { get; }
186+
187+
public RegisterCommandsAttribute()
188+
{
189+
this.CommandPath = "";
190+
}
191+
192+
public RegisterCommandsAttribute(string commandPath)
193+
{
194+
this.CommandPath = commandPath;
195+
}
196+
}
197+
178198
internal static partial class ConsoleApp
179199
{
180200
public static IServiceProvider? ServiceProvider { get; set; }
@@ -746,12 +766,14 @@ public bool Equals(CommanContext other)
746766

747767
class CollectBuilderContext : IEquatable<CollectBuilderContext>
748768
{
749-
public Command[] Commands { get; } = [];
769+
public Command[] Commands { get; private set; } = [];
750770
public DiagnosticReporter DiagnosticReporter { get; }
751771
public CancellationToken CancellationToken { get; }
752772
public bool HasRun { get; }
753773
public bool HasRunAsync { get; }
754774

775+
FilterInfo[]? globalFilters { get; }
776+
755777
public CollectBuilderContext(ImmutableArray<BuilderContext> contexts, CancellationToken cancellationToken)
756778
{
757779
this.DiagnosticReporter = new DiagnosticReporter();
@@ -781,7 +803,7 @@ public CollectBuilderContext(ImmutableArray<BuilderContext> contexts, Cancellati
781803
return x.Name;
782804
});
783805

784-
var globalFilters = methodGroup["UseFilter"]
806+
globalFilters = methodGroup["UseFilter"]
785807
.OrderBy(x => x.Node.GetLocation().SourceSpan) // sort by line number
786808
.Select(x =>
787809
{
@@ -805,6 +827,7 @@ public CollectBuilderContext(ImmutableArray<BuilderContext> contexts, Cancellati
805827
// don't emit if exists failure
806828
if (DiagnosticReporter.HasDiagnostics)
807829
{
830+
globalFilters = null;
808831
return;
809832
}
810833

@@ -862,46 +885,46 @@ public CollectBuilderContext(ImmutableArray<BuilderContext> contexts, Cancellati
862885
// from ForAttributeWithMetadataName
863886
public void AddRegisterAttributes(ImmutableArray<GeneratorAttributeSyntaxContext> contexts)
864887
{
865-
if (contexts.Length == 0)
888+
if (contexts.Length == 0 || DiagnosticReporter.HasDiagnostics)
866889
{
867890
return;
868891
}
869892

870-
//var names = new HashSet<string>();
871-
872-
//var commands2 = methodGroup["Add<T>"]
873-
// .SelectMany(x =>
874-
// {
875-
// var wellKnownTypes = new WellKnownTypes(x.Model.Compilation);
876-
// var parser = new Parser(DiagnosticReporter, x.Node, x.Model, wellKnownTypes, DelegateBuildType.None, globalFilters);
877-
// var commands = parser.ParseAndValidateForBuilderClassRegistration();
878-
879-
// // validation command name duplicate
880-
// foreach (var command in commands)
881-
// {
882-
// if (command != null && !names.Add(command.Name))
883-
// {
884-
// DiagnosticReporter.ReportDiagnostic(DiagnosticDescriptors.DuplicateCommandName, x.Node.GetLocation(), command!.Name);
885-
// return [null];
886-
// }
887-
// }
888-
889-
// return commands;
890-
// });
891-
892-
//if (DiagnosticReporter.HasDiagnostics)
893-
//{
894-
// return;
895-
//}
896-
897-
//// set properties
898-
//this.Commands = commands1.Concat(commands2!).ToArray()!; // not null if no diagnostics
899-
//this.HasRun = methodGroup["Run"].Any();
900-
//this.HasRunAsync = methodGroup["RunAsync"].Any();
901-
902-
//var model = contexts[0].Model;
903-
//var serviceCollectionSymbol = model.Compilation.GetTypeByMetadataName("Microsoft.Extensions.DependencyInjection.ServiceCollection");
904-
//this.HasDependencyInjectionReference = serviceCollectionSymbol != null;
893+
var names = new HashSet<string>(Commands.Select(x => x.Name));
894+
895+
var list = new List<Command>();
896+
foreach (var ctx in contexts)
897+
{
898+
string? commandPath = null;
899+
var attrData = ctx.Attributes[0]; // AllowMultiple = false
900+
if (attrData.ConstructorArguments.Length != 0)
901+
{
902+
commandPath = attrData.ConstructorArguments[0].Value as string;
903+
}
904+
905+
var wellKnownTypes = new WellKnownTypes(ctx.SemanticModel.Compilation);
906+
var parser = new Parser(DiagnosticReporter, ctx.TargetNode, ctx.SemanticModel, wellKnownTypes, DelegateBuildType.None, globalFilters ?? []);
907+
908+
var commands = parser.CreateCommandsFromType((ITypeSymbol)ctx.TargetSymbol, commandPath);
909+
910+
foreach (var command in commands)
911+
{
912+
if (command != null)
913+
{
914+
if (!names.Add(command.Name))
915+
{
916+
DiagnosticReporter.ReportDiagnostic(DiagnosticDescriptors.DuplicateCommandName, ctx.TargetNode.GetLocation(), command!.Name);
917+
break;
918+
}
919+
else
920+
{
921+
list.Add(command);
922+
}
923+
}
924+
}
925+
}
926+
927+
Commands = Commands.Concat(list).ToArray();
905928
}
906929

907930
public bool Equals(CollectBuilderContext other)

src/ConsoleAppFramework/Parser.cs

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@
44

55
namespace ConsoleAppFramework;
66

7-
internal class Parser(DiagnosticReporter context, InvocationExpressionSyntax node, SemanticModel model, WellKnownTypes wellKnownTypes, DelegateBuildType delegateBuildType, FilterInfo[] globalFilters)
7+
internal class Parser(DiagnosticReporter context, SyntaxNode node, SemanticModel model, WellKnownTypes wellKnownTypes, DelegateBuildType delegateBuildType, FilterInfo[] globalFilters)
88
{
99
public Command? ParseAndValidateForRun() // for ConsoleApp.Run, lambda or method or &method
1010
{
11-
var args = node.ArgumentList.Arguments;
11+
var args = (node as InvocationExpressionSyntax)!.ArgumentList.Arguments;
1212
if (args.Count == 2) // 0 = args, 1 = lambda
1313
{
1414
var command = ExpressionToCommand(args[1].Expression, ""); // rootCommand(commandName = "")
@@ -26,7 +26,7 @@ internal class Parser(DiagnosticReporter context, InvocationExpressionSyntax nod
2626
public Command? ParseAndValidateForBuilderDelegateRegistration() // for ConsoleAppBuilder.Add
2727
{
2828
// Add(string commandName, Delgate command)
29-
var args = node.ArgumentList.Arguments;
29+
var args = (node as InvocationExpressionSyntax)!.ArgumentList.Arguments;
3030
if (args.Count == 2) // 0 = string command, 1 = lambda
3131
{
3232
var commandName = args[0];
@@ -51,14 +51,15 @@ internal class Parser(DiagnosticReporter context, InvocationExpressionSyntax nod
5151

5252
public Command?[] ParseAndValidateForBuilderClassRegistration()
5353
{
54+
var node2 = (node as InvocationExpressionSyntax)!;
5455
// Add<T>
55-
var genericName = (node.Expression as MemberAccessExpressionSyntax)?.Name as GenericNameSyntax;
56+
var genericName = (node2.Expression as MemberAccessExpressionSyntax)?.Name as GenericNameSyntax;
5657
var genericType = genericName!.TypeArgumentList.Arguments[0];
5758

5859
// Add<T>(string commandPath)
5960
string? commandPath = null;
60-
var args = node.ArgumentList.Arguments;
61-
if (node.ArgumentList.Arguments.Count == 1)
61+
var args = node2.ArgumentList.Arguments;
62+
if (node2.ArgumentList.Arguments.Count == 1)
6263
{
6364
var commandName = args[0];
6465
if (!commandName.Expression.IsKind(SyntaxKind.StringLiteralExpression))
@@ -73,6 +74,11 @@ internal class Parser(DiagnosticReporter context, InvocationExpressionSyntax nod
7374
// T
7475
var type = model.GetTypeInfo(genericType).Type!;
7576

77+
return CreateCommandsFromType(type, commandPath);
78+
}
79+
80+
public Command?[] CreateCommandsFromType(ITypeSymbol type, string? commandPath)
81+
{
7682
if (type.IsStatic || type.IsAbstract)
7783
{
7884
context.ReportDiagnostic(DiagnosticDescriptors.ClassIsStaticOrAbstract, node.GetLocation());

0 commit comments

Comments
 (0)