Ejemplo n.º 1
0
        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);
            }