/// <inheritdoc /> protected override async Task <Document> GetChangedDocumentAsync(CancellationToken cancellationToken) { var root = await document.GetSyntaxRootAsync(cancellationToken); if (root == null) { return(document); } var semanticModel = await document.GetSemanticModelAsync(cancellationToken); if (semanticModel == null) { return(document); } var compilation = await document.Project.GetCompilationAsync(cancellationToken); var symbols = diagnostic.Properties["Symbols"] .Split('|') .Select(x => (INamedTypeSymbol)compilation.GetTypeByFullName(x)) .Where(t => t != null) .ToArray(); var name = naming.GetFullName(symbols); // Check if the compilation already contains the type, which will be // the case in batch fixing if (compilation.GetTypeByMetadataName(name) != null) { return(document); } var generator = SyntaxGenerator.GetGenerator(document.Project); var stunts = CreateGenerator(naming); return(await CreateStunt(symbols, generator, stunts, cancellationToken)); }
void AnalyzeSyntaxNode(SyntaxNodeAnalysisContext context) { // Get the matching symbol for the given generator attribute from the current compilation. var generator = context.Compilation.GetTypeByMetadataName(generatorAttribute.FullName); if (generator == null) { // This may be an extender authoring error, but another analyzer should ensure proper // metadata references exist. Typically, the same NuGet package that adds this analyzer // also adds the required assembly references, so this should never happen anyway. return; } var symbol = context.Compilation.GetSemanticModel(context.Node.SyntaxTree).GetSymbolInfo(context.Node); if (symbol.Symbol?.Kind == SymbolKind.Method) { var method = (IMethodSymbol)symbol.Symbol; if (method.GetAttributes().Any(x => x.AttributeClass == generator) && // We don't generate anything if generator is applied to a non-generic method. !method.TypeArguments.IsDefaultOrEmpty && method.TypeArguments.TryValidateGeneratorTypes(out _)) { var name = naming.GetFullName(method.TypeArguments.OfType <INamedTypeSymbol>()); var compilationErrors = new Lazy <Diagnostic[]>(() => context.Compilation.GetCompilationErrors()); HashSet <string> recursiveSymbols; if (recursive) { // Collect recursive symbols to generate/update as needed. recursiveSymbols = new HashSet <string>(method.TypeArguments.OfType <INamedTypeSymbol>().InterceptableRecursively() .Where(x => { var candidate = context.Compilation.GetTypeByMetadataName(naming.GetFullName(new[] { x })); return(candidate == null || candidate.HasDiagnostic(compilationErrors.Value)); }) .Select(x => x.ToFullMetadataName())); } else { recursiveSymbols = new HashSet <string>(); } // See if the stunt already exists var stunt = context.Compilation.GetTypeByMetadataName(name); if (stunt == null) { var diagnostic = Diagnostic.Create( MissingDiagnostic, context.Node.GetLocation(), new Dictionary <string, string> { { "TargetFullName", name }, { "Symbols", string.Join("|", method.TypeArguments .OfType <INamedTypeSymbol>().Select(x => x.ToFullMetadataName())) }, // By passing the detected recursive symbols to update/generate, // we avoid doing all the work we already did during analysis. // The code action can therefore simply act on them, without // further inquiries to the compilation. { "RecursiveSymbols", string.Join("|", recursiveSymbols) }, }.ToImmutableDictionary(), name); context.ReportDiagnostic(diagnostic); } else { // See if the symbol has any compilation error diagnostics associated if (stunt.HasDiagnostic(compilationErrors.Value) || (recursive && recursiveSymbols.Any())) { // If there are compilation errors, we should update the proxy. var diagnostic = Diagnostic.Create( OutdatedDiagnostic, context.Node.GetLocation(), new Dictionary <string, string> { { "TargetFullName", name }, { "Symbols", string.Join("|", method.TypeArguments .OfType <INamedTypeSymbol>().Select(x => x.ToFullMetadataName())) }, // We pass the same recursive symbols in either case. The // Different diagnostics exist only to customize the message // displayed to the user. { "RecursiveSymbols", string.Join("|", recursiveSymbols) }, }.ToImmutableDictionary(), name); context.ReportDiagnostic(diagnostic); } } } } }
private void AnalyzeSyntaxNode(SyntaxNodeAnalysisContext context) { // Get the matching symbol for the given generator attribute from the current compilation. var generator = context.Compilation.GetTypeByMetadataName(generatorAttribute.FullName); if (generator == null) { // This may be an extender authoring error, but another analyzer should ensure proper // metadata references exist. Typically, the same NuGet package that adds this analyzer // also adds the required assembly references, so this should never happen anyway. return; } var symbol = context.Compilation.GetSemanticModel(context.Node.SyntaxTree).GetSymbolInfo(context.Node); if (symbol.Symbol?.Kind != SymbolKind.Method) { return; } var method = (IMethodSymbol)symbol.Symbol; if (method.GetAttributes().Any(x => x.AttributeClass == generator) && // We don't generate anything if generator is applied to a non-generic method. !method.TypeArguments.IsDefaultOrEmpty && method.TypeArguments.TryValidateGeneratorTypes(out _)) { var name = naming.GetFullName(method.TypeArguments.OfType <INamedTypeSymbol>()); var compilationErrors = new Lazy <Diagnostic[]>(() => context.Compilation.GetCompilationErrors()); HashSet <string> recursiveSymbols; if (recursive) { // Collect recursive symbols to generate/update as needed. recursiveSymbols = new HashSet <string>(method.TypeArguments.OfType <INamedTypeSymbol>().InterceptableRecursively() .Where(x => { var candidate = context.Compilation.GetTypeByMetadataName(naming.GetFullName(new[] { x })); return(candidate == null || candidate.HasDiagnostic(compilationErrors.Value)); }) .Select(x => x.ToFullMetadataName())); } else { recursiveSymbols = new HashSet <string>(); } // See if the stunt already exists var stunt = context.Compilation.GetTypeByMetadataName(name); if (stunt == null) { var diagnostic = Diagnostic.Create( MissingDiagnostic, context.Node.GetLocation(), new Dictionary <string, string> { { "TargetFullName", name }, { "Symbols", string.Join("|", method.TypeArguments .OfType <INamedTypeSymbol>().Select(x => x.ToFullMetadataName())) }, // By passing the detected recursive symbols to update/generate, // we avoid doing all the work we already did during analysis. // The code action can therefore simply act on them, without // further inquiries to the compilation. { "RecursiveSymbols", string.Join("|", recursiveSymbols) }, }.ToImmutableDictionary(), name); // Flag AutoCodeFix that the next build should generate the type. if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("AutoCodeFix")) && // We only flag if we're not being run by AutoCodeFix itself context.Options.GetCodeFixSettings().TryGetValue("IntermediateOutputPath", out var intermediateOutputPath)) { File.WriteAllText(Path.Combine(intermediateOutputPath, "AutoCodeFixBeforeCompile.flag"), ""); } context.ReportDiagnostic(diagnostic); } else { // See if the symbol has any compilation error diagnostics associated if (stunt.HasDiagnostic(compilationErrors.Value) || (recursive && recursiveSymbols.Any())) { var location = stunt.Locations.Length == 1 ? stunt.Locations[0].GetLineSpan().Path : stunt.Locations.FirstOrDefault(loc => loc.GetLineSpan().Path.EndsWith(".g.cs", StringComparison.OrdinalIgnoreCase) || loc.GetLineSpan().Path.EndsWith(".g.vb", StringComparison.OrdinalIgnoreCase)) ?.GetLineSpan().Path ?? ""; // Any outdated files should be automatically updated on the next build, // so signal this to the build targets with a text file that the targets // can use to determine a pre-compile analysis and codefix phase is needed. if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("AutoCodeFix")) && // We only flag if we're not being run by AutoCodeFix itself context.Options.GetCodeFixSettings().TryGetValue("IntermediateOutputPath", out var intermediateOutputPath)) { File.WriteAllText(Path.Combine(intermediateOutputPath, "AutoCodeFixBeforeCompile.flag"), ""); } // If there are compilation errors, we should update the proxy. var diagnostic = Diagnostic.Create( OutdatedDiagnostic, context.Node.GetLocation(), new Dictionary <string, string> { { "TargetFullName", name }, { "Location", location }, { "Symbols", string.Join("|", method.TypeArguments .OfType <INamedTypeSymbol>().Select(x => x.ToFullMetadataName())) }, // We pass the same recursive symbols in either case. The // Different diagnostics exist only to customize the message // displayed to the user. { "RecursiveSymbols", string.Join("|", recursiveSymbols) }, }.ToImmutableDictionary(), name); context.ReportDiagnostic(diagnostic); } } } }