예제 #1
0
            /// <summary>
            /// Create a qualified identifier as the suffix of namespace based on a list of folder names.
            /// </summary>
            private static string TryBuildNamespaceFromFolders(
                AbstractSyncNamespaceCodeRefactoringProvider <TNamespaceDeclarationSyntax, TCompilationUnitSyntax, TMemberDeclarationSyntax> service,
                IEnumerable <string> folders,
                ISyntaxFactsService syntaxFacts)
            {
                var parts = folders.SelectMany(folder => folder.Split(new[] { '.' }).SelectAsArray(service.EscapeIdentifier));

                return(parts.All(syntaxFacts.IsValidIdentifier) ? string.Join(".", parts) : null);
            }
예제 #2
0
            public static async Task <State> CreateAsync(
                AbstractSyncNamespaceCodeRefactoringProvider <TNamespaceDeclarationSyntax, TCompilationUnitSyntax, TMemberDeclarationSyntax> provider,
                Document document,
                TextSpan textSpan,
                CancellationToken cancellationToken)
            {
                // User must put cursor on one of the nodes described below to trigger the refactoring.
                // For each scenario, all requirements must be met. Some of them are checked by `TryGetApplicableInvocationNodeAsync`,
                // rest by `IChangeNamespaceService.CanChangeNamespaceAsync`.
                //
                // - A namespace declaration node that is the only namespace declaration in the document and all types are declared in it:
                //    1. No nested namespace declarations (even it's empty).
                //    2. The cursor is on the name of the namespace declaration.
                //    3. The name of the namespace is valid (i.e. no errors).
                //    4. No partial type declared in the namespace. Otherwise its multiple declaration will
                //       end up in different namespace.
                //
                // - A compilation unit node that contains no namespace declaration:
                //    1. The cursor is on the name of first declared type.
                //    2. No partial type declared in the document. Otherwise its multiple declaration will
                //       end up in different namespace.

                var applicableNode = await provider.TryGetApplicableInvocationNodeAsync(document, textSpan, cancellationToken).ConfigureAwait(false);

                if (applicableNode == null)
                {
                    return(null);
                }

                var changenameSpaceService = document.GetLanguageService <IChangeNamespaceService>();
                var canChange = await changenameSpaceService.CanChangeNamespaceAsync(document, applicableNode, cancellationToken).ConfigureAwait(false);

                if (!canChange || !IsDocumentPathRootedInProjectFolder(document))
                {
                    return(null);
                }

                var syntaxFacts = document.GetLanguageService <ISyntaxFactsService>();
                var solution    = document.Project.Solution;

                // We can't determine what the expected namespace would be without knowing the default namespace.
                var defaultNamespace = GetDefaultNamespace(document, syntaxFacts);

                if (defaultNamespace == null)
                {
                    return(null);
                }

                string declaredNamespace;

                if (applicableNode is TCompilationUnitSyntax)
                {
                    declaredNamespace = string.Empty;
                }
                else if (applicableNode is TNamespaceDeclarationSyntax)
                {
                    var syntaxGenerator = SyntaxGenerator.GetGenerator(document);
                    declaredNamespace = syntaxGenerator.GetName(applicableNode);
                }
                else
                {
                    throw ExceptionUtilities.Unreachable;
                }

                // Namespace can't be changed if we can't construct a valid qualified identifier from folder names.
                // In this case, we might still be able to provide refactoring to move file to new location.
                var namespaceFromFolders = TryBuildNamespaceFromFolders(provider, document.Folders, syntaxFacts);
                var targetNamespace      = namespaceFromFolders == null
                    ? null
                    : ConcatNamespace(defaultNamespace, namespaceFromFolders);

                // No action required if namespace already matches folders.
                if (syntaxFacts.StringComparer.Equals(targetNamespace, declaredNamespace))
                {
                    return(null);
                }

                // Only provide "move file" action if default namespace contains declared namespace.
                // For example, if the default namespace is `Microsoft.CodeAnalysis`, and declared
                // namespace is `System.Diagnostics`, it's very likely this document is an outlier
                // in the project and user probably has some special rule for it.
                var relativeNamespace = GetRelativeNamespace(defaultNamespace, declaredNamespace, syntaxFacts);

                return(new State(document, applicableNode, targetNamespace, relativeNamespace));
            }