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); }
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); }
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)); }
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); }
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()); }