public override void VisitQualifiedName(QualifiedNameSyntax node)
        {
            if (_ignoredSpans?.HasIntervalThatOverlapsWith(node.FullSpan.Start, node.FullSpan.Length) ?? false)
            {
                return;
            }

            if (node.IsKind(SyntaxKind.QualifiedName) && TrySimplify(node))
            {
                // found a match. report it and stop processing.
                return;
            }

            // descend further.
            DefaultVisit(node);
        }
Beispiel #2
0
        public async Task <Document> AddImportsAsync(
            Document document,
            IEnumerable <TextSpan> spans,
            Strategy strategy,
            OptionSet?options,
            CancellationToken cancellationToken)
        {
            options ??= await document.GetOptionsAsync(cancellationToken).ConfigureAwait(false);

            var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false);

            var addImportsService = document.GetRequiredLanguageService <IAddImportsService>();
            var codeGenerator     = document.GetRequiredLanguageService <ICodeGenerationService>();
            var generator         = document.GetRequiredLanguageService <SyntaxGenerator>();
            var preferences       = codeGenerator.GetPreferences(root.SyntaxTree.Options, options);

            // Create a simple interval tree for simplification spans.
            var spansTree = new SimpleIntervalTree <TextSpan, TextSpanIntervalIntrospector>(new TextSpanIntervalIntrospector(), spans);

            Func <SyntaxNode, bool> overlapsWithSpan = n => spansTree.HasIntervalThatOverlapsWith(n.FullSpan.Start, n.FullSpan.Length);

            // Only dive deeper into nodes that actually overlap with the span we care about.  And also only include
            // those child nodes that themselves overlap with the span.  i.e. if we have:
            //
            //                Parent
            //       /                    \
            //      A  [|   B     C   |]   D
            //
            // We'll dive under the parent because it overlaps with the span.  But we only want to include (and dive
            // into) B and C not A and D.
            var nodes = root.DescendantNodesAndSelf(overlapsWithSpan).Where(overlapsWithSpan);

            var allowInHiddenRegions = document.CanAddImportsInHiddenRegions();

            if (strategy == Strategy.AddImportsFromSymbolAnnotations)
            {
                return(await AddImportDirectivesFromSymbolAnnotationsAsync(document, preferences, nodes, addImportsService, generator, allowInHiddenRegions, cancellationToken).ConfigureAwait(false));
            }

            if (strategy == Strategy.AddImportsFromSyntaxes)
            {
                return(await AddImportDirectivesFromSyntaxesAsync(document, preferences, nodes, addImportsService, generator, allowInHiddenRegions, cancellationToken).ConfigureAwait(false));
            }

            throw ExceptionUtilities.UnexpectedValue(strategy);
        }
Beispiel #3
0
        public async Task <Document> AddImportsAsync(
            Document document, IEnumerable <TextSpan> spans,
            OptionSet options, CancellationToken cancellationToken)
        {
            options = options ?? await document.GetOptionsAsync(cancellationToken).ConfigureAwait(false);

            var model = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);

            var root = await model.SyntaxTree.GetRootAsync(cancellationToken).ConfigureAwait(false);

            // Create a simple interval tree for simplification spans.
            var spansTree = new SimpleIntervalTree <TextSpan>(TextSpanIntervalIntrospector.Instance, spans);

            bool isInSpan(SyntaxNodeOrToken nodeOrToken) =>
            spansTree.HasIntervalThatOverlapsWith(nodeOrToken.FullSpan.Start, nodeOrToken.FullSpan.Length);

            var nodesWithExplicitNamespaces = root.DescendantNodesAndSelf().Where(n => isInSpan(n) && GetExplicitNamespaceSymbol(n, model) != null).ToList();

            var namespacesToAdd = new HashSet <INamespaceSymbol>();

            namespacesToAdd.AddRange(nodesWithExplicitNamespaces.Select(
                                         n => GetExplicitNamespaceSymbol(n, model)));

            var generator = SyntaxGenerator.GetGenerator(document);
            var imports   = namespacesToAdd.Select(ns => generator.NamespaceImportDeclaration(ns.ToDisplayString()).WithAdditionalAnnotations(Simplifier.Annotation))
                            .ToArray();

            // annotate these nodes so they get simplified later
            var newRoot = root.ReplaceNodes(
                nodesWithExplicitNamespaces,
                (o, r) => r.WithAdditionalAnnotations(Simplifier.Annotation));

            var placeSystemNamespaceFirst = options.GetOption(GenerationOptions.PlaceSystemNamespaceFirst, document.Project.Language);
            var addImportsService         = document.GetLanguageService <IAddImportsService>();
            var finalRoot = addImportsService.AddImports(
                model.Compilation, newRoot, newRoot, imports, placeSystemNamespaceFirst);

            return(document.WithSyntaxRoot(finalRoot));
        }
Beispiel #4
0
        public async Task <Document> AddImportsAsync(
            Document document,
            IEnumerable <TextSpan> spans,
            Strategy strategy,
            bool safe,
            OptionSet?options,
            CancellationToken cancellationToken)
        {
            options ??= await document.GetOptionsAsync(cancellationToken).ConfigureAwait(false);

            var model = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);

            Contract.ThrowIfNull(model);
            var root = await model.SyntaxTree.GetRootAsync(cancellationToken).ConfigureAwait(false);

            var addImportsService = document.GetRequiredLanguageService <IAddImportsService>();
            var generator         = document.GetRequiredLanguageService <SyntaxGenerator>();

            // Create a simple interval tree for simplification spans.
            var spansTree = new SimpleIntervalTree <TextSpan, TextSpanIntervalIntrospector>(new TextSpanIntervalIntrospector(), spans);

            var nodes = root.DescendantNodesAndSelf().Where(IsInSpan);

            var(importDirectivesToAdd, namespaceSymbols, context) = strategy switch
            {
                Strategy.AddImportsFromSymbolAnnotations
                => GetImportDirectivesFromAnnotatedNodes(nodes, root, model, addImportsService, generator, cancellationToken),
                Strategy.AddImportsFromSyntaxes
                => GetImportDirectivesFromSyntaxes(nodes, ref root, model, addImportsService, generator, cancellationToken),
                _ => throw new InvalidEnumArgumentException(nameof(strategy), (int)strategy, typeof(Strategy)),
            };

            if (importDirectivesToAdd.Length == 0)
            {
                return(document.WithSyntaxRoot(root)); //keep any added simplifier annotations
            }

            if (safe)
            {
                // Mark the context with an annotation.
                // This will allow us to find it after we have called MakeSafeToAddNamespaces.
                var annotation = new SyntaxAnnotation();
                RoslynDebug.Assert(context is object);
                document = document.WithSyntaxRoot(root.ReplaceNode(context, context.WithAdditionalAnnotations(annotation)));
                root     = (await document.GetSyntaxRootAsync().ConfigureAwait(false)) !;

                model = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);

                Contract.ThrowIfNull(model);

                // Make Safe to add namespaces
                document = document.WithSyntaxRoot(
                    MakeSafeToAddNamespaces(root, namespaceSymbols, model, document.Project.Solution.Workspace, cancellationToken));
                root = (await document.GetSyntaxRootAsync().ConfigureAwait(false)) !;

                model = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);

                Contract.ThrowIfNull(model);

                // Find the context. It might be null if we have removed the context in the process of complexifying the tree.
                context = root.DescendantNodesAndSelf().FirstOrDefault(x => x.HasAnnotation(annotation)) ?? root;
            }

            var placeSystemNamespaceFirst = options.GetOption(GenerationOptions.PlaceSystemNamespaceFirst, document.Project.Language);

            root = addImportsService.AddImports(model.Compilation, root, context, importDirectivesToAdd, generator, placeSystemNamespaceFirst, cancellationToken);

            return(document.WithSyntaxRoot(root));

            bool IsInSpan(SyntaxNode node) =>
            spansTree.HasIntervalThatOverlapsWith(node.FullSpan.Start, node.FullSpan.Length);
        }
        private async Task <Document> ReduceAsyncInternal(
            Document document,
            ImmutableArray <TextSpan> spans,
            OptionSet optionSet,
            ImmutableArray <AbstractReducer> reducers,
            CancellationToken cancellationToken)
        {
            // Create a simple interval tree for simplification spans.
            var spansTree = new SimpleIntervalTree <TextSpan>(TextSpanIntervalIntrospector.Instance, spans);

            bool isNodeOrTokenOutsideSimplifySpans(SyntaxNodeOrToken nodeOrToken) =>
            !spansTree.HasIntervalThatOverlapsWith(nodeOrToken.FullSpan.Start, nodeOrToken.FullSpan.Length);

            var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);

            var root = await semanticModel.SyntaxTree.GetRootAsync(cancellationToken).ConfigureAwait(false);

            // prep namespace imports marked for simplification
            var removeIfUnusedAnnotation = new SyntaxAnnotation();
            var originalRoot             = root;

            root = this.PrepareNamespaceImportsForRemovalIfUnused(document, root, removeIfUnusedAnnotation, isNodeOrTokenOutsideSimplifySpans);
            var hasImportsToSimplify = root != originalRoot;

            if (hasImportsToSimplify)
            {
                document      = document.WithSyntaxRoot(root);
                semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);

                root = await semanticModel.SyntaxTree.GetRootAsync(cancellationToken).ConfigureAwait(false);
            }

            // Get the list of syntax nodes and tokens that need to be reduced.
            var nodesAndTokensToReduce = this.GetNodesAndTokensToReduce(root, isNodeOrTokenOutsideSimplifySpans);

            if (nodesAndTokensToReduce.Any())
            {
                if (reducers.IsDefault)
                {
                    reducers = _reducers;
                }

                var reducedNodesMap  = new ConcurrentDictionary <SyntaxNode, SyntaxNode>();
                var reducedTokensMap = new ConcurrentDictionary <SyntaxToken, SyntaxToken>();

                // Reduce all the nodesAndTokensToReduce using the given reducers/rewriters and
                // store the reduced nodes and/or tokens in the reduced nodes/tokens maps.
                // Note that this method doesn't update the original syntax tree.
                await this.ReduceAsync(document, root, nodesAndTokensToReduce, reducers, optionSet, semanticModel, reducedNodesMap, reducedTokensMap, cancellationToken).ConfigureAwait(false);

                if (reducedNodesMap.Any() || reducedTokensMap.Any())
                {
                    // Update the syntax tree with reduced nodes/tokens.
                    root = root.ReplaceSyntax(
                        nodes: reducedNodesMap.Keys,
                        computeReplacementNode: (o, n) => TransformReducedNode(reducedNodesMap[o], n),
                        tokens: reducedTokensMap.Keys,
                        computeReplacementToken: (o, n) => reducedTokensMap[o],
                        trivia: SpecializedCollections.EmptyEnumerable <SyntaxTrivia>(),
                        computeReplacementTrivia: null);

                    document = document.WithSyntaxRoot(root);
                }
            }

            if (hasImportsToSimplify)
            {
                // remove any unused namespace imports that were marked for simplification
                document = await this.RemoveUnusedNamespaceImportsAsync(document, removeIfUnusedAnnotation, cancellationToken).ConfigureAwait(false);
            }

            return(document);
        }
Beispiel #6
0
        private ImmutableArray <Diagnostic> AnalyzeSemanticModel(SemanticModelAnalysisContext context, int positionOfFirstReducingNullableDirective, SimpleIntervalTree <TextSpan, TextSpanIntervalIntrospector>?codeBlockIntervalTree, SimpleIntervalTree <TextSpan, TextSpanIntervalIntrospector>?possibleNullableImpactIntervalTree)
        {
            var root = context.SemanticModel.SyntaxTree.GetCompilationUnitRoot(context.CancellationToken);

            using (var simplifier = new NullableImpactingSpanWalker(context.SemanticModel, positionOfFirstReducingNullableDirective, ignoredSpans: codeBlockIntervalTree, context.CancellationToken))
            {
                simplifier.Visit(root);
                possibleNullableImpactIntervalTree ??= new SimpleIntervalTree <TextSpan, TextSpanIntervalIntrospector>(new TextSpanIntervalIntrospector(), values: null);
                foreach (var interval in simplifier.Spans)
                {
                    possibleNullableImpactIntervalTree.AddIntervalInPlace(interval);
                }
            }

            using var diagnostics = TemporaryArray <Diagnostic> .Empty;

            var compilationOptions = ((CSharpCompilationOptions)context.SemanticModel.Compilation.Options).NullableContextOptions;

            DirectiveTriviaSyntax? previousRetainedDirective = null;
            NullableContextOptions?retainedOptions           = compilationOptions;

            DirectiveTriviaSyntax?currentOptionsDirective = null;
            var currentOptions = retainedOptions;

            for (var directive = root.GetFirstDirective(); directive is not null; directive = directive.GetNextDirective())
            {
                context.CancellationToken.ThrowIfCancellationRequested();

                if (directive.IsKind(SyntaxKind.NullableDirectiveTrivia, out NullableDirectiveTriviaSyntax? nullableDirectiveTrivia))
                {
                    // Once we reach a new directive, check to see if we can remove the previous directive
                    var removedCurrent = false;
                    if (IsReducing(retainedOptions, currentOptions))
                    {
                        // We can't have found a reducing directive and not know which directive it was
                        Contract.ThrowIfNull(currentOptionsDirective);

                        if (possibleNullableImpactIntervalTree is null ||
                            !possibleNullableImpactIntervalTree.HasIntervalThatOverlapsWith(currentOptionsDirective.Span.End, nullableDirectiveTrivia.SpanStart - currentOptionsDirective.Span.End))
                        {
                            diagnostics.Add(Diagnostic.Create(Descriptor, currentOptionsDirective.GetLocation()));
                        }
                    }

                    if (!removedCurrent)
                    {
                        previousRetainedDirective = currentOptionsDirective;
                        retainedOptions           = currentOptions;
                    }

                    currentOptionsDirective = nullableDirectiveTrivia;
                    currentOptions          = CSharpRemoveRedundantNullableDirectiveDiagnosticAnalyzer.GetNullableContextOptions(compilationOptions, currentOptions, nullableDirectiveTrivia);
                }
                else if (directive.IsKind(SyntaxKind.IfDirectiveTrivia, SyntaxKind.ElifDirectiveTrivia, SyntaxKind.ElseDirectiveTrivia))
                {
                    possibleNullableImpactIntervalTree ??= new SimpleIntervalTree <TextSpan, TextSpanIntervalIntrospector>(new TextSpanIntervalIntrospector(), values: null);
                    possibleNullableImpactIntervalTree.AddIntervalInPlace(directive.Span);
                }
            }

            // Once we reach the end of the file, check to see if we can remove the last directive
            if (IsReducing(retainedOptions, currentOptions))
            {
                // We can't have found a reducing directive and not know which directive it was
                Contract.ThrowIfNull(currentOptionsDirective);

                if (possibleNullableImpactIntervalTree is null ||
                    !possibleNullableImpactIntervalTree.HasIntervalThatOverlapsWith(currentOptionsDirective.Span.End, root.Span.End - currentOptionsDirective.Span.End))
                {
                    diagnostics.Add(Diagnostic.Create(Descriptor, currentOptionsDirective.GetLocation()));
                }
            }

            return(diagnostics.ToImmutableAndClear());
        }