private async Task <Document> AddImportDirectivesFromSymbolAnnotationsAsync( Document document, IEnumerable <SyntaxNode> syntaxNodes, IAddImportsService addImportsService, SyntaxGenerator generator, bool placeSystemNamespaceFirst, bool allowInHiddenRegions, CancellationToken cancellationToken) { using var _ = PooledDictionary <INamespaceSymbol, SyntaxNode> .GetInstance(out var importToSyntax); var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var model = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); SyntaxNode?first = null, last = null; var annotatedNodes = syntaxNodes.Where(x => x.HasAnnotations(SymbolAnnotation.Kind)); foreach (var annotatedNode in annotatedNodes) { cancellationToken.ThrowIfCancellationRequested(); if (annotatedNode.GetAnnotations(DoNotAddImportsAnnotation.Kind).Any()) { continue; } var annotations = annotatedNode.GetAnnotations(SymbolAnnotation.Kind); foreach (var annotation in annotations) { cancellationToken.ThrowIfCancellationRequested(); foreach (var namedType in SymbolAnnotation.GetSymbols(annotation, model.Compilation).OfType <INamedTypeSymbol>()) { cancellationToken.ThrowIfCancellationRequested(); if (namedType.OriginalDefinition.IsSpecialType() || namedType.IsNullable() || namedType.IsTupleType) { continue; } var namespaceSymbol = namedType.ContainingNamespace; if (namespaceSymbol is null || namespaceSymbol.IsGlobalNamespace) { continue; } first ??= annotatedNode; last = annotatedNode; if (importToSyntax.ContainsKey(namespaceSymbol)) { continue; } var namespaceSyntax = GenerateNamespaceImportDeclaration(namespaceSymbol, generator); if (addImportsService.HasExistingImport(model.Compilation, root, annotatedNode, namespaceSyntax, generator)) { continue; } if (IsInsideNamespace(annotatedNode, namespaceSymbol, model, cancellationToken)) { continue; } importToSyntax[namespaceSymbol] = namespaceSyntax; } } } if (first == null || last == null || importToSyntax.Count == 0) { return(document); } var context = first.GetCommonRoot(last); // Find the namespace/compilation-unit we'll be adding all these imports to. var importContainer = addImportsService.GetImportContainer(root, context, importToSyntax.First().Value); // Now remove any imports we think can cause conflicts in that container. var safeImportsToAdd = GetSafeToAddImports(importToSyntax.Keys.ToImmutableArray(), importContainer, model, cancellationToken); var importsToAdd = importToSyntax.Where(kvp => safeImportsToAdd.Contains(kvp.Key)).Select(kvp => kvp.Value).ToImmutableArray(); if (importsToAdd.Length == 0) { return(document); } root = addImportsService.AddImports( model.Compilation, root, context, importsToAdd, generator, placeSystemNamespaceFirst, allowInHiddenRegions, cancellationToken); return(document.WithSyntaxRoot(root)); }
/// <summary> /// Fix each reference and return a collection of proper containers (innermost container /// with imports) that new import should be added to based on reference locations. /// If <paramref name="namespaceParts"/> is specified (not default), the fix would be: /// 1. qualify the reference with new namespace and mark it for simplification, or /// 2. find and mark the qualified reference for simplification. /// Otherwise, there would be no namespace replacement. /// </summary> private async Task <(Document, ImmutableArray <SyntaxNode>)> FixReferencesAsync( Document document, IAddImportsService addImportService, ISyncNamespaceService syncNamespaceService, IEnumerable <ReferenceLocation> refLocations, ImmutableArray <string> namespaceParts, CancellationToken cancellationToken) { var editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false); var root = editor.OriginalRoot; var containers = PooledHashSet <SyntaxNode> .GetInstance(); var generator = SyntaxGenerator.GetGenerator(document); var syntaxFacts = document.GetLanguageService <ISyntaxFactsService>(); // We need a dummy import to figure out the container for given reference. var dummyImport = CreateImport(generator, "Dummy", withFormatterAnnotation: false); foreach (var refLoc in refLocations) { Debug.Assert(document.Id == refLoc.Document.Id); // Ignore references via alias. For simple cases where the alias is defined as the type we are interested, // it will be handled properly because it is one of the reference to the type symbol. Otherwise, we don't // attempt to make a potential fix, and user might end up with errors as a result. if (refLoc.Alias != null) { continue; } // Other documents in the solution might have changed after we calculated those ReferenceLocation, // so we can't trust anything to be still up-to-date except their spans. // Get inner most node in case of type used as a base type. e.g. // // public class Foo {} // public class Bar : Foo {} // // For the reference to Foo where it is used as a base class, the BaseTypeSyntax and the TypeSyntax // have exact same span. var refNode = root.FindNode(refLoc.Location.SourceSpan, findInsideTrivia: true, getInnermostNodeForTie: true); if (syncNamespaceService.TryGetReplacementReferenceSyntax( refNode, namespaceParts, syntaxFacts, out var oldNode, out var newNode)) { editor.ReplaceNode(oldNode, newNode.WithAdditionalAnnotations(Simplifier.Annotation)); } // Use a dummy import node to figure out which container the new import will be added to. var container = addImportService.GetImportContainer(root, refNode, dummyImport); containers.Add(container); } foreach (var container in containers) { editor.TrackNode(container); } var fixedDocument = editor.GetChangedDocument(); root = await fixedDocument.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var result = (fixedDocument, containers.SelectAsArray(c => root.GetCurrentNode(c))); containers.Free(); return(result); }