Skip to content

Commit ddba46c

Browse files
authored
Add analyzer warnings for command name issues (#56)
1 parent 0a31f40 commit ddba46c

File tree

4 files changed

+92
-3
lines changed

4 files changed

+92
-3
lines changed

src/DemoApp/Services/IntermediateCaGenerator.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ namespace DemoApp.Services;
66

77
public class IntermediateCaGenerator
88
{
9-
ISerialNumberProvider _serialNumberProvider;
9+
readonly ISerialNumberProvider _serialNumberProvider;
1010
public IntermediateCaGenerator(ISerialNumberProvider serialNumberProvider)
1111
{
1212
_serialNumberProvider = serialNumberProvider;

src/System.CommandLine.Minimal.SourceGenerator/CommandSourceGenerator.cs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
using Microsoft.CodeAnalysis;
22
using Microsoft.CodeAnalysis.CSharp.Syntax;
3+
using System.Collections.Generic;
34
using System.Collections.Immutable;
5+
using System.CommandLine.Minimal.SourceGenerator;
46
using System.Threading;
57

68
namespace System.CommandLine.Minimal.SourceGeneration;
@@ -22,17 +24,32 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
2224
).Collect();
2325

2426
context.RegisterSourceOutput(bindersProvider, (spc, binders) => {
25-
27+
HashSet<string> commandNames = new();
28+
2629
// first generate and create all of the CommandOptions classes
2730
foreach (GeneratingCommandBinder? binder in binders)
2831
{
32+
if(binder.CommandName is null || string.IsNullOrWhiteSpace(binder.CommandName))
33+
{
34+
spc.ReportCommandNameEmptyError(binder.CommandNameLocation);
35+
continue;
36+
}
37+
if(commandNames.Contains(binder.CommandName))
38+
{
39+
spc.ReportCommandNameConflict(binder.CommandNameLocation, binder.CommandName);
40+
continue;
41+
}
42+
43+
// command name is validated so add to the hashset
44+
commandNames.Add(binder.CommandName);
45+
2946
string? code = CommandOptionsWriter.GenerateOptions(binder);
3047
if(code is not null)
3148
{
3249
spc.AddSource($"{binder.ClassName}_{binder.MethodName}_Command.g.cs", code);
3350
}
3451
}
35-
52+
3653
// Emit the aggregated Register method
3754
string? registryCode = MapAllCommandsExtensionWriter.GenerateMapAllCommandsExt(binders);
3855
if(registryCode is not null)
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
using Microsoft.CodeAnalysis;
2+
3+
namespace System.CommandLine.Minimal.SourceGenerator
4+
{
5+
internal static class DiagnosticErrors
6+
{
7+
const string CommandNameCategory = "Command Name";
8+
static readonly DiagnosticDescriptor CommandNameConflict =
9+
new DiagnosticDescriptor(
10+
"MIN0001",
11+
"Command name conflict",
12+
"Another Handler attribute already has this command name \"{0}\"",
13+
CommandNameCategory,
14+
DiagnosticSeverity.Error,
15+
true
16+
);
17+
static readonly DiagnosticDescriptor CommandNameEmpty =
18+
new DiagnosticDescriptor(
19+
"MIN0002",
20+
"Command name empty",
21+
"A command name is null or empty",
22+
CommandNameCategory,
23+
DiagnosticSeverity.Error,
24+
true
25+
);
26+
27+
/// <summary>
28+
/// MIN0002 empty command name.
29+
/// </summary>
30+
internal static void ReportCommandNameEmptyError(
31+
this SourceProductionContext ctx,
32+
Location? location)
33+
=> ctx.ReportDiagnostic(Diagnostic.Create(CommandNameEmpty, location));
34+
35+
/// <summary>
36+
/// MIN0001 duplicate command name.
37+
/// </summary>
38+
internal static void ReportCommandNameConflict(
39+
this SourceProductionContext ctx,
40+
Location? location,
41+
string commandName)
42+
=> ctx.ReportDiagnostic(Diagnostic.Create(CommandNameConflict, location, commandName));
43+
44+
}
45+
}

src/System.CommandLine.Minimal.SourceGenerator/GeneratorBindingsProvider.cs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,31 @@ public static GeneratingCommandBinder Transform(GeneratorAttributeSyntaxContext
2525
&& a.AttributeClass.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) == "global::System.CommandLine.Minimal.HandlerAttribute");
2626
string? commandName = handlerAttribute?.ConstructorArguments.FirstOrDefault().Value as string;
2727

28+
Location? argumentLocation = null;
29+
if (ctx.TargetNode is MethodDeclarationSyntax methodDecl)
30+
{
31+
AttributeSyntax? handlerAttrSyntax = methodDecl.AttributeLists
32+
.SelectMany(attrs => attrs.Attributes)
33+
.FirstOrDefault(attr => ctx.SemanticModel
34+
.GetTypeInfo(attr)
35+
.Type?
36+
.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) == "global::System.CommandLine.Minimal.HandlerAttribute"
37+
);
38+
argumentLocation = handlerAttrSyntax?.ArgumentList?.Arguments.FirstOrDefault()?.GetLocation();
39+
}
40+
//// Find the AttributeSyntax node for the HandlerAttribute
41+
//AttributeSyntax? handlerAttributeSyntax = ctx.TargetNode switch
42+
//{
43+
// MethodDeclarationSyntax methodDecl => methodDecl.AttributeLists
44+
// .SelectMany(list => list.Attributes)
45+
// .FirstOrDefault(attr =>
46+
// ctx.SemanticModel.GetTypeInfo(attr).Type?.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)
47+
// == "global::System.CommandLine.Minimal.HandlerAttribute"),
48+
// _ => null
49+
//};
50+
//Location? argumentLocation = handlerAttributeSyntax?.ArgumentList?.Arguments.FirstOrDefault()?.GetLocation();
51+
52+
2853
string classNamespace = methodSymbol.ContainingNamespace.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
2954
string className = methodSymbol.ContainingType.Name;
3055
string methodName = methodSymbol.Name;
@@ -73,6 +98,7 @@ public static GeneratingCommandBinder Transform(GeneratorAttributeSyntaxContext
7398
MethodName: methodName,
7499
MethodReturnType: methodReturnType,
75100
MethodIsStatic: methodIsStatic,
101+
CommandNameLocation: argumentLocation,
76102
Bindings: bindings.ToImmutableArray());
77103
}
78104

@@ -221,6 +247,7 @@ internal record GeneratingCommandBinder(
221247
string MethodName,
222248
string MethodReturnType,
223249
bool MethodIsStatic,
250+
Location? CommandNameLocation,
224251
ImmutableArray<ParameterBinding>? Bindings
225252
)
226253
{

0 commit comments

Comments
 (0)