3using System.Collections.Immutable;
5using Microsoft.CodeAnalysis;
6using Microsoft.CodeAnalysis.CSharp.Syntax;
10 [Generator(LanguageNames.CSharp)]
14 private static readonly DiagnosticDescriptor debugGeneratedCommand =
new DiagnosticDescriptor(
16 title:
"Command Generated",
17 messageFormat:
"Generated command property '{0}' for method '{1}' in class '{2}'",
18 category:
"ObservableTaskCommandGenerator",
19 defaultSeverity: DiagnosticSeverity.Info,
20 isEnabledByDefault:
true);
22 private static readonly DiagnosticDescriptor unsupportedSignature =
new DiagnosticDescriptor(
24 title:
"Unsupported Method Signature",
25 messageFormat:
"Method '{0}' in class '{1}' has an unsupported signature for command generation",
26 category:
"ObservableTaskCommandGenerator",
27 defaultSeverity: DiagnosticSeverity.Error,
28 isEnabledByDefault:
true);
30 public void Initialize(IncrementalGeneratorInitializationContext context)
33 IncrementalValuesProvider<MethodDeclarationSyntax> MethodDeclarations =
34 context.SyntaxProvider.CreateSyntaxProvider(
35 predicate:
static (node,
_) =>
36 node is MethodDeclarationSyntax M && M.AttributeLists.Count > 0,
37 transform: static (ctx,
_) => (MethodDeclarationSyntax)ctx.Node)
38 .Where(method => method is not
null);
41 IncrementalValueProvider<(Compilation, ImmutableArray<MethodDeclarationSyntax>)> CompilationAndMethods =
42 context.CompilationProvider.Combine(MethodDeclarations.Collect());
45 context.RegisterSourceOutput(CompilationAndMethods, (spc, source) =>
47 (Compilation Compilation, ImmutableArray<MethodDeclarationSyntax> Methods) = source;
49 INamedTypeSymbol? AttributeSymbol = Compilation.GetTypeByMetadataName(
"NeuroAccessMaui.UI.MVVM.ObservableTaskCommandAttribute");
50 if (AttributeSymbol is
null)
53 spc.ReportDiagnostic(Diagnostic.Create(
54 new DiagnosticDescriptor(
56 title:
"Attribute Not Found",
57 messageFormat:
"The attribute 'ObservableTaskCommandAttribute' was not found.",
58 category:
"ObservableTaskCommandGenerator",
59 defaultSeverity: DiagnosticSeverity.Info,
60 isEnabledByDefault:
true),
65 foreach (MethodDeclarationSyntax MethodDeclaration
in Methods)
67 SemanticModel SemanticModel = Compilation.GetSemanticModel(MethodDeclaration.SyntaxTree);
68 if (SemanticModel.GetDeclaredSymbol(MethodDeclaration) is not IMethodSymbol MethodSymbol)
72 AttributeData? AttrData = MethodSymbol.GetAttributes()
73 .FirstOrDefault(attr => SymbolEqualityComparer.Default.Equals(attr.AttributeClass, AttributeSymbol));
78 INamedTypeSymbol? ContainingType = MethodSymbol.ContainingType;
79 string NamespaceName = ContainingType.ContainingNamespace.ToDisplayString();
80 string ClassName = ContainingType.Name;
81 string MethodName = MethodSymbol.Name;
84 string CommandName = MethodName.EndsWith(
"Async")
85 ? MethodName.Substring(0, MethodName.Length - 5) +
"Command"
86 : MethodName +
"Command";
90 bool ReturnsGenericTask =
false;
91 if (MethodSymbol.ReturnType is INamedTypeSymbol NamedType)
93 if (NamedType.ConstructedFrom.ToDisplayString() ==
"System.Threading.Tasks.Task<TResult>")
95 ReturnsGenericTask =
true;
96 ResultType = NamedType.TypeArguments[0].ToDisplayString();
98 else if (NamedType.ToDisplayString() ==
"System.Threading.Tasks.Task")
100 ResultType =
"object";
105 spc.ReportDiagnostic(Diagnostic.Create(unsupportedSignature, MethodDeclaration.GetLocation(), MethodName, ClassName));
111 spc.ReportDiagnostic(Diagnostic.Create(unsupportedSignature, MethodDeclaration.GetLocation(), MethodName, ClassName));
117 string ProgressType =
"object";
118 if (MethodSymbol.Parameters.Length == 1)
120 IParameterSymbol P0 = MethodSymbol.Parameters[0];
121 if (P0.Type is INamedTypeSymbol ContextSymbol &&
122 ContextSymbol is { Name:
"TaskContext", TypeArguments.Length: 1 })
124 ProgressType = ContextSymbol.TypeArguments[0].ToDisplayString();
128 spc.ReportDiagnostic(Diagnostic.Create(unsupportedSignature, MethodDeclaration.GetLocation(), MethodName, ClassName));
132 else if (MethodSymbol.Parameters.Length == 0)
135 ProgressType =
"object";
139 spc.ReportDiagnostic(Diagnostic.Create(unsupportedSignature, MethodDeclaration.GetLocation(), MethodName, ClassName));
145 string OptionsValue =
"ObservableTaskCommandOptions.None";
146 if (AttrData.ConstructorArguments.Length > 0 && AttrData.ConstructorArguments[0].Value is
int Value)
149 OptionsValue = $
"((ObservableTaskCommandOptions){Value})";
154 foreach (KeyValuePair<string, TypedConstant> NamedArg
in AttrData.NamedArguments)
156 if (NamedArg.Key ==
"Options" && NamedArg.Value.Value is
int N)
158 OptionsValue = $
"((ObservableTaskCommandOptions){N})";
165 string LambdaBody =
"";
166 if (MethodSymbol.Parameters.Length == 0)
169 LambdaBody = ReturnsGenericTask
170 ? $
"await {MethodName}();"
171 : $
"await {MethodName}();";
173 else if (MethodSymbol.Parameters.Length == 1)
176 LambdaBody = ReturnsGenericTask
177 ? $
"await {MethodName}(context);"
178 : $
"await {MethodName}(context);";
182 spc.ReportDiagnostic(Diagnostic.Create(unsupportedSignature, MethodDeclaration.GetLocation(), MethodName, ClassName));
187 string GeneratedSource = $
@"
189using System.Threading;
190using System.Threading.Tasks;
191using NeuroAccessMaui.UI.MVVM;
193namespace {NamespaceName}
195 public partial class {ClassName}
197 private ObservableTaskCommand<{ProgressType}>? _{CommandName};
198 public ObservableTaskCommand<{ProgressType}> {CommandName} => _{CommandName} ??= new ObservableTaskCommand<{ProgressType}>(
209 spc.AddSource($
"{ClassName}_{CommandName}_generated.cs", GeneratedSource);
211 spc.ReportDiagnostic(Diagnostic.Create(debugGeneratedCommand, MethodDeclaration.GetLocation(), CommandName, MethodName, ClassName));