예제 #1
0
        /// <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));
        }
예제 #2
0
        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);
                        }
                    }
                }
            }
        }
예제 #3
0
        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);
                    }
                }
            }
        }