未验证 提交 891fc9b3 编写于 作者: D dotnet-automerge-bot 提交者: GitHub

Merge pull request #31215 from dotnet/merges/dev16.0-preview2-to-master

Merge dev16.0-preview2 to master
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeRefactorings;
using Microsoft.CodeAnalysis.CodeRefactorings.SyncNamespace;
using Microsoft.CodeAnalysis.CSharp.CodeRefactorings.SyncNamespace;
using Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions;
using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces;
using Microsoft.CodeAnalysis.Text;
using Microsoft.CodeAnalysis.UnitTests;
using Roslyn.Utilities;
using Xunit;
namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.CodeActions.SyncNamespace
{
public abstract class CSharpSyncNamespaceTestsBase : AbstractCodeActionTest
{
protected override ParseOptions GetScriptOptions() => Options.Script;
protected override string GetLanguage() => LanguageNames.CSharp;
protected override CodeRefactoringProvider CreateCodeRefactoringProvider(Workspace workspace, TestParameters parameters)
=> new SyncNamespaceCodeRefactoringProvider();
protected override TestWorkspace CreateWorkspaceFromFile(string initialMarkup, TestParameters parameters)
{
return TestWorkspace.IsWorkspaceElement(initialMarkup)
? TestWorkspace.Create(initialMarkup)
: TestWorkspace.CreateCSharp(initialMarkup, parameters.parseOptions, parameters.compilationOptions);
}
protected string ProjectRootPath
=> PathUtilities.IsUnixLikePlatform
? @"/ProjectA/"
: @"C:\ProjectA\";
protected string ProjectFilePath
=> PathUtilities.CombineAbsoluteAndRelativePaths(ProjectRootPath, "ProjectA.csproj");
protected (string folder, string filePath) CreateDocumentFilePath(string[] folder, string fileName = "DocumentA.cs")
{
if (folder == null || folder.Length == 0)
{
return (string.Empty, PathUtilities.CombineAbsoluteAndRelativePaths(ProjectRootPath, fileName));
}
else
{
var folderPath = CreateFolderPath(folder);
var relativePath = PathUtilities.CombinePossiblyRelativeAndRelativePaths(folderPath, fileName);
return (folderPath, PathUtilities.CombineAbsoluteAndRelativePaths(ProjectRootPath, relativePath));
}
}
protected string CreateFolderPath(params string[] folders)
{
return string.Join(PathUtilities.DirectorySeparatorStr, folders);
}
protected async Task TestMoveFileToMatchNamespace(string initialMarkup, List<string[]> expectedFolders = null)
{
var testOptions = new TestParameters();
using (var workspace = CreateWorkspaceFromOptions(initialMarkup, testOptions))
{
if (expectedFolders?.Count > 0)
{
var expectedFolderPaths = expectedFolders.Select(f => string.Join(PathUtilities.DirectorySeparatorStr, f));
var oldDocument = workspace.Documents[0];
var oldDocumentId = oldDocument.Id;
var expectedText = workspace.Documents[0].TextBuffer.CurrentSnapshot.GetText();
// a new document with the same text as old document is added.
var allResults = await TestOperationAsync(testOptions, workspace, expectedText);
var actualFolderPaths = new HashSet<string>();
foreach (var result in allResults)
{
// the original source document does not exist in the new solution.
var oldSolution = result.Item1;
var newSolution = result.Item2;
Assert.Null(newSolution.GetDocument(oldDocumentId));
var newDocument = GetDocumentToVerify(expectedChangedDocumentId: null, oldSolution, newSolution);
actualFolderPaths.Add(string.Join(PathUtilities.DirectorySeparatorStr, newDocument.Folders));
}
Assert.True(expectedFolderPaths.Count() == actualFolderPaths.Count, "Number of available \"Move file\" actions are not equal.");
foreach (var expected in expectedFolderPaths)
{
Assert.True(actualFolderPaths.Contains(expected));
}
}
else
{
var (actions, _) = await GetCodeActionsAsync(workspace, testOptions);
if (actions.Length > 0)
{
var renameFileAction = actions.Any(action => action is CSharpSyncNamespaceService.MoveFileCodeAction);
Assert.False(renameFileAction, "Rename File to match type code action was not expected, but shows up.");
}
}
}
async Task<List<Tuple<Solution, Solution>>> TestOperationAsync(
TestParameters parameters,
TestWorkspace workspace,
string expectedCode)
{
var results = new List<Tuple<Solution, Solution>>();
var (actions, _) = await GetCodeActionsAsync(workspace, parameters);
var moveFileActions = actions.Where(a => a is CSharpSyncNamespaceService.MoveFileCodeAction);
foreach (var action in moveFileActions)
{
var operations = await action.GetOperationsAsync(CancellationToken.None);
results.Add(
await TestOperationsAsync(workspace,
expectedText: expectedCode,
operations: operations,
conflictSpans: ImmutableArray<TextSpan>.Empty,
renameSpans: ImmutableArray<TextSpan>.Empty,
warningSpans: ImmutableArray<TextSpan>.Empty,
navigationSpans: ImmutableArray<TextSpan>.Empty,
expectedChangedDocumentId: null));
}
return results;
}
}
protected async Task TestChangeNamespaceAsync(
string initialMarkUp,
string expectedSourceOriginal,
string expectedSourceReference = null)
{
var testOptions = new TestParameters();
using (var workspace = CreateWorkspaceFromOptions(initialMarkUp, testOptions))
{
if (workspace.Projects.Count == 2)
{
var project = workspace.Documents.Single(doc => !doc.SelectedSpans.IsEmpty()).Project;
var dependentProject = workspace.Projects.Single(proj => proj.Id != project.Id);
var references = dependentProject.ProjectReferences.ToList();
references.Add(new ProjectReference(project.Id));
dependentProject.ProjectReferences = references;
workspace.OnProjectReferenceAdded(dependentProject.Id, new ProjectReference(project.Id));
}
if (expectedSourceOriginal != null)
{
var originalDocument = workspace.Documents.Single(doc => !doc.SelectedSpans.IsEmpty());
var originalDocumentId = originalDocument.Id;
var refDocument = workspace.Documents.Where(doc => doc.Id != originalDocumentId).SingleOrDefault();
var refDocumentId = refDocument?.Id;
var oldAndNewSolution = await TestOperationAsync(testOptions, workspace);
var oldSolution = oldAndNewSolution.Item1;
var newSolution = oldAndNewSolution.Item2;
var changedDocumentIds = SolutionUtilities.GetChangedDocuments(oldSolution, newSolution);
Assert.True(changedDocumentIds.Contains(originalDocumentId), "original document was not changed.");
Assert.True(expectedSourceReference == null || changedDocumentIds.Contains(refDocumentId), "reference document was not changed.");
var modifiedOriginalDocument = newSolution.GetDocument(originalDocumentId);
var modifiedOringinalRoot = await modifiedOriginalDocument.GetSyntaxRootAsync();
// One node/token will contain the warning we attached for change namespace action.
Assert.Single(modifiedOringinalRoot.DescendantNodesAndTokensAndSelf().Where(n =>
{
IEnumerable<SyntaxAnnotation> annotations;
if (n.IsNode)
{
annotations = n.AsNode().GetAnnotations(WarningAnnotation.Kind);
}
else
{
annotations = n.AsToken().GetAnnotations(WarningAnnotation.Kind);
}
return annotations.Any(annotation =>
WarningAnnotation.GetDescription(annotation) == FeaturesResources.Warning_colon_changing_namespace_may_produce_invalid_code_and_change_code_meaning);
}));
var actualText = (await modifiedOriginalDocument.GetTextAsync()).ToString();
Assert.Equal(expectedSourceOriginal, actualText);
if (expectedSourceReference != null)
{
var actualRefText = (await newSolution.GetDocument(refDocumentId).GetTextAsync()).ToString();
Assert.Equal(expectedSourceReference, actualRefText);
}
}
else
{
var (actions, _) = await GetCodeActionsAsync(workspace, testOptions);
if (actions.Length > 0)
{
var hasChangeNamespaceAction = actions.Any(action => action is CSharpSyncNamespaceService.ChangeNamespaceCodeAction);
Assert.False(hasChangeNamespaceAction, "Change namespace to match folder action was not expected, but shows up.");
}
}
}
async Task<Tuple<Solution, Solution>> TestOperationAsync(TestParameters parameters, TestWorkspace workspace)
{
var (actions, _) = await GetCodeActionsAsync(workspace, parameters);
var changeNamespaceAction = actions.Single(a => a is CSharpSyncNamespaceService.ChangeNamespaceCodeAction);
var operations = await changeNamespaceAction.GetOperationsAsync(CancellationToken.None);
return ApplyOperationsAndGetSolution(workspace, operations);
}
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Test.Utilities;
using Roslyn.Test.Utilities;
using Xunit;
namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.CodeActions.SyncNamespace
{
public partial class SyncNamespaceTests : CSharpSyncNamespaceTestsBase
{
[WpfFact, Trait(Traits.Feature, Traits.Features.CodeActionsSyncNamespace)]
public async Task MoveFile_DeclarationNotContainedInDefaultNamespace()
{
// No "move file" action because default namespace is not container of declared namespace
var defaultNamespace = "A";
var declaredNamespace = "Foo.Bar";
var expectedFolders = new List<string[]>();
var documentPath = CreateDocumentFilePath(Array.Empty<string>(), "File1.cs");
var code =
$@"
<Workspace>
<Project Language=""C#"" AssemblyName=""Assembly1"" FilePath=""{ProjectFilePath}"" DefaultNamespace=""{defaultNamespace}"" CommonReferences=""true"">
<Document Folders=""{documentPath.folder}"" FilePath=""{documentPath.filePath}"">
namespace [||]{declaredNamespace}
{{
class Class1
{{
}}
}}
</Document>
</Project>
</Workspace>";
await TestMoveFileToMatchNamespace(code, expectedFolders);
}
[WpfFact, Trait(Traits.Feature, Traits.Features.CodeActionsSyncNamespace)]
public async Task MoveFile_SingleAction1()
{
// current path is <root>\
// expected new path is <root>\B\C\
var defaultNamespace = "A";
var declaredNamespace = "A.B.C";
var expectedFolders = new List<string[]>();
expectedFolders.Add(new[] { "B", "C" });
var documentPath = CreateDocumentFilePath(Array.Empty<string>());
var code =
$@"
<Workspace>
<Project Language=""C#"" AssemblyName=""Assembly1"" FilePath=""{ProjectFilePath}"" DefaultNamespace=""{defaultNamespace}"" CommonReferences=""true"">
<Document Folders=""{documentPath.folder}"" FilePath=""{documentPath.filePath}"">
namespace [||]{declaredNamespace}
{{
class Class1
{{
}}
}}
</Document>
</Project>
</Workspace>";
await TestMoveFileToMatchNamespace(code, expectedFolders);
}
[WpfFact, Trait(Traits.Feature, Traits.Features.CodeActionsSyncNamespace)]
public async Task MoveFile_SingleAction2()
{
// current path is <root>\
// expected new path is <root>\B\C\D\E\
var defaultNamespace = "A";
var declaredNamespace = "A.B.C.D.E";
var expectedFolders = new List<string[]>();
expectedFolders.Add(new[] { "B", "C", "D", "E" });
var documentPath1 = CreateDocumentFilePath(Array.Empty<string>(), "File1.cs");
var documentPath2 = CreateDocumentFilePath(new[] { "B", "C" }, "File2.cs"); // file2 is in <root>\B\C\
var code =
$@"
<Workspace>
<Project Language=""C#"" AssemblyName=""Assembly1"" FilePath=""{ProjectFilePath}"" DefaultNamespace=""{defaultNamespace}"" CommonReferences=""true"">
<Document Folders=""{documentPath1.folder}"" FilePath=""{documentPath1.filePath}"">
namespace [||]{declaredNamespace}
{{
class Class1
{{
}}
}}
</Document>
<Document Folders=""{documentPath2.folder}"" FilePath=""{documentPath2.filePath}"">
namespace Foo
{{
class Class2
{{
}}
}}
</Document>
</Project>
</Workspace>";
await TestMoveFileToMatchNamespace(code, expectedFolders);
}
[WpfFact, Trait(Traits.Feature, Traits.Features.CodeActionsSyncNamespace)]
public async Task MoveFile_MoveToRoot()
{
// current path is <root>\A\B\C\
// expected new path is <root>
var defaultNamespace = "";
var expectedFolders = new List<string[]>();
expectedFolders.Add(Array.Empty<string>());
var documentPath = CreateDocumentFilePath(new[] { "A", "B", "C" });
var code =
$@"
<Workspace>
<Project Language=""C#"" AssemblyName=""Assembly1"" FilePath=""{ProjectFilePath}"" DefaultNamespace=""{defaultNamespace}"" CommonReferences=""true"">
<Document Folders=""{documentPath.folder}"" FilePath=""{documentPath.filePath}"">
class [||]Class1
{{
}}
class Class2
{{
}}
</Document>
</Project>
</Workspace>";
await TestMoveFileToMatchNamespace(code, expectedFolders);
}
[WpfFact, Trait(Traits.Feature, Traits.Features.CodeActionsSyncNamespace)]
public async Task MoveFile_MultipleAction1()
{
// current path is <root>\
// expected new paths are"
// 1. <root>\B\C\D\E\
// 2. <root>\B.C\D\E\
var defaultNamespace = "A";
var declaredNamespace = "A.B.C.D.E";
var expectedFolders = new List<string[]>();
expectedFolders.Add(new[] { "B", "C", "D", "E" });
expectedFolders.Add(new[] { "B.C", "D", "E" });
var documentPath1 = CreateDocumentFilePath(Array.Empty<string>(), "File1.cs");
var documentPath2 = CreateDocumentFilePath(new[] { "B.C" }, "File2.cs"); // file2 is in <root>\B.C\
var code =
$@"
<Workspace>
<Project Language=""C#"" AssemblyName=""Assembly1"" FilePath=""{ProjectFilePath}"" DefaultNamespace=""{defaultNamespace}"" CommonReferences=""true"">
<Document Folders=""{documentPath1.folder}"" FilePath=""{documentPath1.filePath}"">
namespace [||]{declaredNamespace}
{{
class Class1
{{
}}
}}
</Document>
<Document Folders=""{documentPath2.folder}"" FilePath=""{documentPath2.filePath}"">
namespace Foo
{{
class Class2
{{
}}
}}
</Document>
</Project>
</Workspace>";
await TestMoveFileToMatchNamespace(code, expectedFolders);
}
[WpfFact, Trait(Traits.Feature, Traits.Features.CodeActionsSyncNamespace)]
public async Task MoveFile_MultipleAction2()
{
// current path is <root>\
// expected new paths are:
// 1. <root>\B\C\D\E\
// 2. <root>\B.C\D\E\
// 3. <root>\B\C.D\E\
var defaultNamespace = "A";
var declaredNamespace = "A.B.C.D.E";
var expectedFolders = new List<string[]>();
expectedFolders.Add(new[] { "B", "C", "D", "E" });
expectedFolders.Add(new[] { "B.C", "D", "E" });
expectedFolders.Add(new[] { "B", "C.D", "E" });
var documentPath1 = CreateDocumentFilePath(Array.Empty<string>(), "File1.cs");
var documentPath2 = CreateDocumentFilePath(new[] { "B", "C.D" }, "File2.cs"); // file2 is in <root>\B\C.D\
var documentPath3 = CreateDocumentFilePath(new[] { "B.C" }, "File3.cs"); // file3 is in <root>\B.C\
var code =
$@"
<Workspace>
<Project Language=""C#"" AssemblyName=""Assembly1"" FilePath=""{ProjectFilePath}"" DefaultNamespace=""{defaultNamespace}"" CommonReferences=""true"">
<Document Folders=""{documentPath1.folder}"" FilePath=""{documentPath1.filePath}"">
namespace [||]{declaredNamespace}
{{
class Class1
{{
}}
}}
</Document>
<Document Folders=""{documentPath2.folder}"" FilePath=""{documentPath2.filePath}"">
namespace Foo
{{
class Class2
{{
}}
}}
</Document>
<Document Folders=""{documentPath3.folder}"" FilePath=""{documentPath3.filePath}"">
namespace Foo
{{
class Class2
{{
}}
}}
</Document>
</Project>
</Workspace>";
await TestMoveFileToMatchNamespace(code, expectedFolders);
}
[WpfFact, Trait(Traits.Feature, Traits.Features.CodeActionsSyncNamespace)]
public async Task MoveFile_FromOneFolderToAnother1()
{
var defaultNamespace = "A";
var declaredNamespace = "A.B.C.D.E";
var expectedFolders = new List<string[]>();
expectedFolders.Add(new[] { "B", "C", "D", "E" });
expectedFolders.Add(new[] { "B.C", "D", "E" });
var documentPath1 = CreateDocumentFilePath(new[] { "B.C" }, "File1.cs"); // file1 is in <root>\B.C\
var documentPath2 = CreateDocumentFilePath(new[] { "B", "Foo" }, "File2.cs"); // file2 is in <root>\B\Foo\
var code =
$@"
<Workspace>
<Project Language=""C#"" AssemblyName=""Assembly1"" FilePath=""{ProjectFilePath}"" DefaultNamespace=""{defaultNamespace}"" CommonReferences=""true"">
<Document Folders=""{documentPath1.folder}"" FilePath=""{documentPath1.filePath}"">
namespace [||]{declaredNamespace}
{{
class Class1
{{
}}
}}
</Document>
<Document Folders=""{documentPath2.folder}"" FilePath=""{documentPath2.filePath}"">
namespace Foo
{{
class Class2
{{
}}
}}
</Document>
</Project>
</Workspace>";
await TestMoveFileToMatchNamespace(code, expectedFolders);
}
[WpfFact, Trait(Traits.Feature, Traits.Features.CodeActionsSyncNamespace)]
public async Task MoveFile_FromOneFolderToAnother2()
{
var defaultNamespace = "A";
var declaredNamespace = "A.B.C.D.E";
var expectedFolders = new List<string[]>();
expectedFolders.Add(new[] { "B", "C", "D", "E" });
var documentPath1 = CreateDocumentFilePath(new[] { "Foo.Bar", "Baz" }, "File1.cs"); // file1 is in <root>\Foo.Bar\Baz\
var documentPath2 = CreateDocumentFilePath(new[] { "B", "Foo" }, "File2.cs"); // file2 is in <root>\B\Foo\
var code =
$@"
<Workspace>
<Project Language=""C#"" AssemblyName=""Assembly1"" FilePath=""{ProjectFilePath}"" DefaultNamespace=""{defaultNamespace}"" CommonReferences=""true"">
<Document Folders=""{documentPath1.folder}"" FilePath=""{documentPath1.filePath}"">
namespace [||]{declaredNamespace}
{{
class Class1
{{
}}
}}
</Document>
<Document Folders=""{documentPath2.folder}"" FilePath=""{documentPath2.filePath}"">
namespace Foo
{{
class Class2
{{
}}
}}
</Document>
</Project>
</Workspace>";
await TestMoveFileToMatchNamespace(code, expectedFolders);
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Test.Utilities;
using Roslyn.Test.Utilities;
using Roslyn.Utilities;
using Xunit;
namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.CodeActions.SyncNamespace
{
public partial class SyncNamespaceTests : CSharpSyncNamespaceTestsBase
{
[WpfFact, Trait(Traits.Feature, Traits.Features.CodeActionsSyncNamespace)]
public async Task NoAction_NotOnNamespaceDeclaration()
{
var folders = new[] { "A", "B" };
var documentPath = CreateDocumentFilePath(folders);
var code =
$@"
<Workspace>
<Project Language=""C#"" AssemblyName=""Assembly1"" FilePath=""{ProjectFilePath}"" CommonReferences=""true"">
<Document Folders=""{documentPath.folder}"" FilePath=""{documentPath.filePath}"">
namespace NS
{{
class [||]Class1
{{
}}
}}
</Document>
</Project>
</Workspace>";
await TestMissingInRegularAndScriptAsync(code);
}
[WpfFact, Trait(Traits.Feature, Traits.Features.CodeActionsSyncNamespace)]
public async Task NoAction_NotOnFirstMemberInGlobal()
{
var folders = new[] { "A" };
var documentPath = CreateDocumentFilePath(folders);
var code =
$@"
<Workspace>
<Project Language=""C#"" AssemblyName=""Assembly1"" FilePath=""{ProjectFilePath}"" DefaultNamespace="""" CommonReferences=""true"">
<Document Folders=""{documentPath.folder}"" FilePath=""{documentPath.filePath}"">
class Class1
{{
}}
class [||]Class2
{{
}}
</Document>
</Project>
</Workspace>";
await TestMissingInRegularAndScriptAsync(code);
}
[WpfFact, Trait(Traits.Feature, Traits.Features.CodeActionsSyncNamespace)]
public async Task NoAction_MultipleNamespaceDeclarations()
{
var folders = new[] { "A", "B" };
var documentPath = CreateDocumentFilePath(folders);
var code =
$@"
<Workspace>
<Project Language=""C#"" AssemblyName=""Assembly1"" FilePath=""{ProjectFilePath}"" CommonReferences=""true"">
<Document Folders=""{documentPath.folder}"" FilePath=""{documentPath.filePath}"">
namespace [||]NS1
{{
class Class1
{{
}}
}}
namespace NS2
{{
class Class1
{{
}}
}}
</Document>
</Project>
</Workspace>";
await TestMissingInRegularAndScriptAsync(code);
}
[WpfFact, Trait(Traits.Feature, Traits.Features.CodeActionsSyncNamespace)]
public async Task NoAction_MembersInBothGlobalAndNamespaceDeclaration_CursorOnNamespace()
{
var folders = new[] { "A", "B" };
var documentPath = CreateDocumentFilePath(folders);
var code =
$@"
<Workspace>
<Project Language=""C#"" AssemblyName=""Assembly1"" FilePath=""{ProjectFilePath}"" CommonReferences=""true"">
<Document Folders=""{documentPath.folder}"" FilePath=""{documentPath.filePath}"">
namespace [||]NS1
{{
class Class1
{{
}}
}}
class Class2
{{
}}
</Document>
</Project>
</Workspace>";
await TestMissingInRegularAndScriptAsync(code);
}
[WpfFact, Trait(Traits.Feature, Traits.Features.CodeActionsSyncNamespace)]
public async Task NoAction_MembersInBothGlobalAndNamespaceDeclaration_CursorOnFirstGlobalMember()
{
var folders = new[] { "A", "B" };
var documentPath = CreateDocumentFilePath(folders);
var code =
$@"
<Workspace>
<Project Language=""C#"" AssemblyName=""Assembly1"" FilePath=""{ProjectFilePath}"" CommonReferences=""true"">
<Document Folders=""{documentPath.folder}"" FilePath=""{documentPath.filePath}"">
class [||]Class1
{{
}}
namespace NS1
{{
class Class2
{{
}}
}}
</Document>
</Project>
</Workspace>";
await TestMissingInRegularAndScriptAsync(code);
}
[WpfFact, Trait(Traits.Feature, Traits.Features.CodeActionsSyncNamespace)]
public async Task NoAction_NestedNamespaceDeclarations()
{
var folders = new[] { "A", "B" };
var documentPath = CreateDocumentFilePath(folders);
var code =
$@"
<Workspace>
<Project Language=""C#"" AssemblyName=""Assembly1"" FilePath=""{ProjectFilePath}"" CommonReferences=""true"">
<Document Folders=""{documentPath.folder}"" FilePath=""{documentPath.filePath}"">
namespace [||]NS1
{{
namespace NS2
{{
class Class1
{{
}}
}}
}}
</Document>
</Project>
</Workspace>";
await TestMissingInRegularAndScriptAsync(code);
}
[WpfFact, Trait(Traits.Feature, Traits.Features.CodeActionsSyncNamespace)]
public async Task NoAction_InvalidNamespaceIdentifier()
{
var folders = new[] { "A", "B" };
var documentPath = CreateDocumentFilePath(folders);
var code =
$@"
<Workspace>
<Project Language=""C#"" AssemblyName=""Assembly1"" FilePath=""{ProjectFilePath}"" DefaultNamespace="""" CommonReferences=""true"">
<Document Folders=""{documentPath.folder}"" FilePath=""{documentPath.filePath}"">
namespace [||]
{{
class Class1
{{
}}
}}
</Document>
</Project>
</Workspace>";
await TestMissingInRegularAndScriptAsync(code);
}
[WpfFact, Trait(Traits.Feature, Traits.Features.CodeActionsSyncNamespace)]
public async Task NoAction_MatchingNamespace_InGlobalNamespace()
{
var folders = Array.Empty<string>();
var documentPath = CreateDocumentFilePath(folders);
var code =
$@"
<Workspace>
<Project Language=""C#"" AssemblyName=""Assembly1"" FilePath=""{ProjectFilePath}"" DefaultNamespace="""" CommonReferences=""true"">
<Document Folders=""{documentPath.folder}"" FilePath=""{documentPath.filePath}"">
class [||]Class1
{{
}}
</Document>
</Project>
</Workspace>";
await TestMissingInRegularAndScriptAsync(code);
}
[WpfFact, Trait(Traits.Feature, Traits.Features.CodeActionsSyncNamespace)]
public async Task NoAction_MatchingNamespace_DefaultGlobalNamespace()
{
var folders = new[] { "A", "B", "C" };
var documentPath = CreateDocumentFilePath(folders);
var code =
$@"
<Workspace>
<Project Language=""C#"" AssemblyName=""Assembly1"" FilePath=""{ProjectFilePath}"" DefaultNamespace="""" CommonReferences=""true"">
<Document Folders=""{documentPath.folder}"" FilePath=""{documentPath.filePath}"">
namespace [||]A.B.C
{{
class Class1
{{
}}
}}
</Document>
</Project>
</Workspace>";
await TestMissingInRegularAndScriptAsync(code);
}
[WpfFact, Trait(Traits.Feature, Traits.Features.CodeActionsSyncNamespace)]
public async Task NoAction_MatchingNamespace_InNamespaceDeclaration()
{
var folders = new[] { "B", "C" };
var documentPath = CreateDocumentFilePath(folders);
var code =
$@"
<Workspace>
<Project Language=""C#"" AssemblyName=""Assembly1"" FilePath=""{ProjectFilePath}"" DefaultNamespace=""A"" CommonReferences=""true"">
<Document Folders=""{documentPath.folder}"" FilePath=""{documentPath.filePath}"">
namespace [||]A.B.C
{{
class Class1
{{
}}
}}
</Document>
</Project>
</Workspace>";
await TestMissingInRegularAndScriptAsync(code);
}
[WpfFact, Trait(Traits.Feature, Traits.Features.CodeActionsSyncNamespace)]
public async Task NoAction_FileNotRooted()
{
var filePath = PathUtilities.CombineAbsoluteAndRelativePaths(PathUtilities.GetPathRoot(ProjectFilePath), "Foo.cs");
var code =
$@"
<Workspace>
<Project Language=""C#"" AssemblyName=""Assembly1"" FilePath=""{ProjectFilePath}"" CommonReferences=""true"">
<Document FilePath=""{filePath}"">
namespace [||]NS
{{
class Class1
{{
}}
}}
</Document>
</Project>
</Workspace>";
await TestMissingInRegularAndScriptAsync(code);
}
[WpfFact, Trait(Traits.Feature, Traits.Features.CodeActionsSyncNamespace)]
public async Task NoAction_NoDeclaration()
{
var folders = new[] { "A" };
var documentPath = CreateDocumentFilePath(folders);
var code =
$@"
<Workspace>
<Project Language=""C#"" AssemblyName=""Assembly1"" FilePath=""{ProjectFilePath}"" CommonReferences=""true"">
<Document Folders=""{documentPath.folder}"" FilePath=""{documentPath.filePath}"">
using System;
[||]
</Document>
</Project>
</Workspace>";
await TestMissingInRegularAndScriptAsync(code);
}
}
}
...@@ -462,7 +462,7 @@ void TestAnnotations(ImmutableArray<TextSpan> expectedSpans, string annotationKi ...@@ -462,7 +462,7 @@ void TestAnnotations(ImmutableArray<TextSpan> expectedSpans, string annotationKi
} }
} }
private static Document GetDocumentToVerify(DocumentId expectedChangedDocumentId, Solution oldSolution, Solution newSolution) protected static Document GetDocumentToVerify(DocumentId expectedChangedDocumentId, Solution oldSolution, Solution newSolution)
{ {
Document document; Document document;
// If the expectedChangedDocumentId is not mentioned then we expect only single document to be changed // If the expectedChangedDocumentId is not mentioned then we expect only single document to be changed
......
...@@ -19,7 +19,6 @@ public class TestHostProject ...@@ -19,7 +19,6 @@ public class TestHostProject
private readonly ProjectId _id; private readonly ProjectId _id;
private readonly string _name; private readonly string _name;
private readonly IEnumerable<ProjectReference> _projectReferences;
private readonly IEnumerable<MetadataReference> _metadataReferences; private readonly IEnumerable<MetadataReference> _metadataReferences;
private readonly IEnumerable<AnalyzerReference> _analyzerReferences; private readonly IEnumerable<AnalyzerReference> _analyzerReferences;
private readonly CompilationOptions _compilationOptions; private readonly CompilationOptions _compilationOptions;
...@@ -30,9 +29,11 @@ public class TestHostProject ...@@ -30,9 +29,11 @@ public class TestHostProject
private readonly VersionStamp _version; private readonly VersionStamp _version;
private readonly string _filePath; private readonly string _filePath;
private readonly string _outputFilePath; private readonly string _outputFilePath;
private readonly string _defaultNamespace;
public IEnumerable<TestHostDocument> Documents; public IEnumerable<TestHostDocument> Documents;
public IEnumerable<TestHostDocument> AdditionalDocuments; public IEnumerable<TestHostDocument> AdditionalDocuments;
public IEnumerable<ProjectReference> ProjectReferences;
public string Name public string Name
{ {
...@@ -42,14 +43,6 @@ public string Name ...@@ -42,14 +43,6 @@ public string Name
} }
} }
public IEnumerable<ProjectReference> ProjectReferences
{
get
{
return _projectReferences;
}
}
public IEnumerable<MetadataReference> MetadataReferences public IEnumerable<MetadataReference> MetadataReferences
{ {
get get
...@@ -135,6 +128,11 @@ public string OutputFilePath ...@@ -135,6 +128,11 @@ public string OutputFilePath
get { return _outputFilePath; } get { return _outputFilePath; }
} }
public string DefaultNamespace
{
get { return _defaultNamespace; }
}
internal TestHostProject( internal TestHostProject(
HostLanguageServices languageServices, HostLanguageServices languageServices,
CompilationOptions compilationOptions, CompilationOptions compilationOptions,
...@@ -171,7 +169,8 @@ public string OutputFilePath ...@@ -171,7 +169,8 @@ public string OutputFilePath
Type hostObjectType = null, Type hostObjectType = null,
bool isSubmission = false, bool isSubmission = false,
string filePath = null, string filePath = null,
IList<AnalyzerReference> analyzerReferences = null) IList<AnalyzerReference> analyzerReferences = null,
string defaultNamespace = null)
{ {
_assemblyName = assemblyName; _assemblyName = assemblyName;
_name = projectName; _name = projectName;
...@@ -183,12 +182,13 @@ public string OutputFilePath ...@@ -183,12 +182,13 @@ public string OutputFilePath
_analyzerReferences = analyzerReferences ?? SpecializedCollections.EmptyEnumerable<AnalyzerReference>(); _analyzerReferences = analyzerReferences ?? SpecializedCollections.EmptyEnumerable<AnalyzerReference>();
this.Documents = documents; this.Documents = documents;
this.AdditionalDocuments = additionalDocuments ?? SpecializedCollections.EmptyEnumerable<TestHostDocument>(); this.AdditionalDocuments = additionalDocuments ?? SpecializedCollections.EmptyEnumerable<TestHostDocument>();
_projectReferences = SpecializedCollections.EmptyEnumerable<ProjectReference>(); ProjectReferences = SpecializedCollections.EmptyEnumerable<ProjectReference>();
_isSubmission = isSubmission; _isSubmission = isSubmission;
_hostObjectType = hostObjectType; _hostObjectType = hostObjectType;
_version = VersionStamp.Create(); _version = VersionStamp.Create();
_filePath = filePath; _filePath = filePath;
_outputFilePath = GetTestOutputFilePath(filePath); _outputFilePath = GetTestOutputFilePath(filePath);
_defaultNamespace = defaultNamespace;
} }
public TestHostProject( public TestHostProject(
...@@ -201,8 +201,9 @@ public string OutputFilePath ...@@ -201,8 +201,9 @@ public string OutputFilePath
IEnumerable<TestHostProject> projectReferences = null, IEnumerable<TestHostProject> projectReferences = null,
IEnumerable<MetadataReference> metadataReferences = null, IEnumerable<MetadataReference> metadataReferences = null,
IEnumerable<AnalyzerReference> analyzerReferences = null, IEnumerable<AnalyzerReference> analyzerReferences = null,
string assemblyName = null) string assemblyName = null,
: this(workspace, name, language, compilationOptions, parseOptions, SpecializedCollections.SingletonEnumerable(document), SpecializedCollections.EmptyEnumerable<TestHostDocument>(), projectReferences, metadataReferences, analyzerReferences, assemblyName) string defaultNamespace = null)
: this(workspace, name, language, compilationOptions, parseOptions, SpecializedCollections.SingletonEnumerable(document), SpecializedCollections.EmptyEnumerable<TestHostDocument>(), projectReferences, metadataReferences, analyzerReferences, assemblyName, defaultNamespace)
{ {
} }
...@@ -217,7 +218,8 @@ public string OutputFilePath ...@@ -217,7 +218,8 @@ public string OutputFilePath
IEnumerable<TestHostProject> projectReferences = null, IEnumerable<TestHostProject> projectReferences = null,
IEnumerable<MetadataReference> metadataReferences = null, IEnumerable<MetadataReference> metadataReferences = null,
IEnumerable<AnalyzerReference> analyzerReferences = null, IEnumerable<AnalyzerReference> analyzerReferences = null,
string assemblyName = null) string assemblyName = null,
string defaultNamespace = null)
{ {
_name = name ?? "TestProject"; _name = name ?? "TestProject";
...@@ -230,12 +232,13 @@ public string OutputFilePath ...@@ -230,12 +232,13 @@ public string OutputFilePath
_parseOptions = parseOptions ?? this.LanguageServiceProvider.GetService<ISyntaxTreeFactoryService>().GetDefaultParseOptions(); _parseOptions = parseOptions ?? this.LanguageServiceProvider.GetService<ISyntaxTreeFactoryService>().GetDefaultParseOptions();
this.Documents = documents ?? SpecializedCollections.EmptyEnumerable<TestHostDocument>(); this.Documents = documents ?? SpecializedCollections.EmptyEnumerable<TestHostDocument>();
this.AdditionalDocuments = additionalDocuments ?? SpecializedCollections.EmptyEnumerable<TestHostDocument>(); this.AdditionalDocuments = additionalDocuments ?? SpecializedCollections.EmptyEnumerable<TestHostDocument>();
_projectReferences = projectReferences != null ? projectReferences.Select(p => new ProjectReference(p.Id)) : SpecializedCollections.EmptyEnumerable<ProjectReference>(); ProjectReferences = projectReferences != null ? projectReferences.Select(p => new ProjectReference(p.Id)) : SpecializedCollections.EmptyEnumerable<ProjectReference>();
_metadataReferences = metadataReferences ?? new MetadataReference[] { TestReferences.NetFx.v4_0_30319.mscorlib }; _metadataReferences = metadataReferences ?? new MetadataReference[] { TestReferences.NetFx.v4_0_30319.mscorlib };
_analyzerReferences = analyzerReferences ?? SpecializedCollections.EmptyEnumerable<AnalyzerReference>(); _analyzerReferences = analyzerReferences ?? SpecializedCollections.EmptyEnumerable<AnalyzerReference>();
_assemblyName = assemblyName ?? "TestProject"; _assemblyName = assemblyName ?? "TestProject";
_version = VersionStamp.Create(); _version = VersionStamp.Create();
_outputFilePath = GetTestOutputFilePath(_filePath); _outputFilePath = GetTestOutputFilePath(_filePath);
_defaultNamespace = defaultNamespace;
if (documents != null) if (documents != null)
{ {
...@@ -327,7 +330,8 @@ public ProjectInfo ToProjectInfo() ...@@ -327,7 +330,8 @@ public ProjectInfo ToProjectInfo()
this.AnalyzerReferences, this.AnalyzerReferences,
this.AdditionalDocuments.Select(d => d.ToDocumentInfo()), this.AdditionalDocuments.Select(d => d.ToDocumentInfo()),
this.IsSubmission, this.IsSubmission,
this.HostObjectType); this.HostObjectType)
.WithDefaultNamespace(this.DefaultNamespace);
} }
// It is identical with the internal extension method 'GetDefaultExtension' defined in OutputKind.cs. // It is identical with the internal extension method 'GetDefaultExtension' defined in OutputKind.cs.
......
...@@ -61,6 +61,7 @@ public partial class TestWorkspace ...@@ -61,6 +61,7 @@ public partial class TestWorkspace
private const string ProjectNameAttribute = "Name"; private const string ProjectNameAttribute = "Name";
private const string CheckOverflowAttributeName = "CheckOverflow"; private const string CheckOverflowAttributeName = "CheckOverflow";
private const string OutputKindName = "OutputKind"; private const string OutputKindName = "OutputKind";
private const string DefaultNamespaceAttributeName = "DefaultNamespace";
/// <summary> /// <summary>
/// Creates a single buffer in a workspace. /// Creates a single buffer in a workspace.
......
...@@ -261,6 +261,7 @@ public static TestWorkspace Create(string xmlDefinition, bool completed = true, ...@@ -261,6 +261,7 @@ public static TestWorkspace Create(string xmlDefinition, bool completed = true,
var language = GetLanguage(workspace, projectElement); var language = GetLanguage(workspace, projectElement);
var assemblyName = GetAssemblyName(workspace, projectElement, ref projectId); var assemblyName = GetAssemblyName(workspace, projectElement, ref projectId);
var defaultNamespace = GetDefaultNamespace(workspace, projectElement);
string filePath; string filePath;
...@@ -309,7 +310,7 @@ public static TestWorkspace Create(string xmlDefinition, bool completed = true, ...@@ -309,7 +310,7 @@ public static TestWorkspace Create(string xmlDefinition, bool completed = true,
documentElementToFilePath.Add(documentElement, document.FilePath); documentElementToFilePath.Add(documentElement, document.FilePath);
} }
return new TestHostProject(languageServices, compilationOptions, parseOptions, assemblyName, projectName, references, documents, filePath: filePath, analyzerReferences: analyzers); return new TestHostProject(languageServices, compilationOptions, parseOptions, assemblyName, projectName, references, documents, filePath: filePath, analyzerReferences: analyzers, defaultNamespace: defaultNamespace);
} }
private static ParseOptions GetParseOptions(XElement projectElement, string language, HostLanguageServices languageServices) private static ParseOptions GetParseOptions(XElement projectElement, string language, HostLanguageServices languageServices)
...@@ -445,6 +446,20 @@ private static string GetLanguage(TestWorkspace workspace, XElement projectEleme ...@@ -445,6 +446,20 @@ private static string GetLanguage(TestWorkspace workspace, XElement projectEleme
return languageName; return languageName;
} }
private static string GetDefaultNamespace(TestWorkspace workspace, XElement projectElement)
{
// Default namespace is a C# only concept, for all other language, the value is set to null.
// The empty string returned in case no such property is define means global namespace.
var language = GetLanguage(workspace, projectElement);
if (language != LanguageNames.CSharp)
{
return null;
}
var defaultNamespaceAttribute = projectElement.Attribute(DefaultNamespaceAttributeName);
return defaultNamespaceAttribute?.Value ?? string.Empty;
}
private static CompilationOptions CreateCompilationOptions( private static CompilationOptions CreateCompilationOptions(
TestWorkspace workspace, TestWorkspace workspace,
XElement projectElement, XElement projectElement,
...@@ -702,7 +717,7 @@ private static IReadOnlyList<string> GetFolders(XElement documentElement) ...@@ -702,7 +717,7 @@ private static IReadOnlyList<string> GetFolders(XElement documentElement)
return null; return null;
} }
var folderContainers = folderAttribute.Value.Split(new[] { '\\' }, StringSplitOptions.RemoveEmptyEntries); var folderContainers = folderAttribute.Value.Split(new[] { PathUtilities.DirectorySeparatorChar }, StringSplitOptions.RemoveEmptyEntries);
return new ReadOnlyCollection<string>(folderContainers.ToList()); return new ReadOnlyCollection<string>(folderContainers.ToList());
} }
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Immutable;
using System.Composition;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeRefactorings.SyncNamespace;
using Microsoft.CodeAnalysis.CSharp.CodeGeneration;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.LanguageServices;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CSharp.CodeRefactorings.SyncNamespace
{
[ExportLanguageService(typeof(ISyncNamespaceService), LanguageNames.CSharp), Shared]
internal sealed class CSharpSyncNamespaceService :
AbstractSyncNamespaceService<NamespaceDeclarationSyntax, CompilationUnitSyntax, MemberDeclarationSyntax>
{
protected override SyntaxList<MemberDeclarationSyntax> GetMemberDeclarationsInContainer(SyntaxNode compilationUnitOrNamespaceDecl)
{
if (compilationUnitOrNamespaceDecl is NamespaceDeclarationSyntax namespaceDecl)
{
return namespaceDecl.Members;
}
else if (compilationUnitOrNamespaceDecl is CompilationUnitSyntax compilationUnit)
{
return compilationUnit.Members;
}
throw ExceptionUtilities.Unreachable;
}
/// <summary>
/// Try to get a new node to replace given node, which is a reference to a top-level type declared inside the namespce to be changed.
/// If this reference is the right side of a qualified name, the new node returned would be the entire qualified name. Depends on
/// whether <paramref name="newNamespaceParts"/> is provided, the name in the new node might be qualified with this new namespace instead.
/// </summary>
/// <param name="reference">A reference to a type declared inside the namespce to be changed, which is calculated based on results from
/// `SymbolFinder.FindReferencesAsync`.</param>
/// <param name="newNamespaceParts">If specified, and the reference is qualified with namespace, the namespace part of original reference
/// will be replaced with given namespace in the new node.</param>
/// <param name="old">The node to be replaced. This might be an ancestor of original reference.</param>
/// <param name="new">The replacement node.</param>
public override bool TryGetReplacementReferenceSyntax(
SyntaxNode reference,
ImmutableArray<string> newNamespaceParts,
ISyntaxFactsService syntaxFacts,
out SyntaxNode old,
out SyntaxNode @new)
{
if (!(reference is SimpleNameSyntax nameRef))
{
old = @new = null;
return false;
}
// A few different cases are handled here:
//
// 1. When the reference is not qualified (i.e. just a simple name), then there's nothing need to be done.
// And both old and new will point to the original reference.
//
// 2. When the new namespace is not specified, we don't need to change the qualified part of reference.
// Both old and new will point to the qualified reference.
//
// 3. When the new namespace is "", i.e. we are moving type referenced by name here to global namespace.
// As a result, we need replace qualified reference with the simple name.
//
// 4. When the namespace is specified and not "", i.e. we are moving referenced type to a different non-global
// namespace. We need to replace the qualified reference with a new qualified reference (which is qualified
// with new namespace.)
if (syntaxFacts.IsRightSideOfQualifiedName(nameRef))
{
old = nameRef.Parent;
var aliasQualifier = GetAliasQualifierOpt(old);
if (IsGlobalNamespace(newNamespaceParts))
{
// If new namespace is "", then name will be declared in global namespace.
// We will replace qualified reference with simple name qualified with alias (global if it's not alias qualified)
var aliasNode = aliasQualifier?.ToIdentifierName() ?? SyntaxFactory.IdentifierName(SyntaxFactory.Token(SyntaxKind.GlobalKeyword));
@new = SyntaxFactory.AliasQualifiedName(aliasNode, nameRef.WithoutTrivia());
}
else
{
var qualifiedNamespaceName = CreateNameSyntax(newNamespaceParts, aliasQualifier, newNamespaceParts.Length - 1);
@new = SyntaxFactory.QualifiedName(qualifiedNamespaceName, nameRef.WithoutTrivia());
}
// We might lose some trivia associated with children of `outerMostNode`.
@new = @new.WithTriviaFrom(old);
return true;
}
if (nameRef.Parent is NameMemberCrefSyntax crefName && crefName.Parent is QualifiedCrefSyntax qualifiedCref)
{
// This is the case where the reference is the right most part of a qualified name in `cref`.
// for example, `<see cref="Foo.Baz.Bar"/>` and `<see cref="SomeAlias::Foo.Baz.Bar"/>`.
// This is the form of `cref` we need to handle as a spacial case when changing namespace name or
// changing namespace from non-global to global, other cases in these 2 scenarios can be handled in the
// same way we handle non cref references, for example, `<see cref="SomeAlias::Foo"/>` and `<see cref="Foo"/>`.
var container = qualifiedCref.Container;
var aliasQualifier = GetAliasQualifierOpt(container);
if (IsGlobalNamespace(newNamespaceParts))
{
// If new namespace is "", then name will be declared in global namespace.
// We will replace entire `QualifiedCrefSyntax` with a `TypeCrefSyntax`,
// which is a alias qualified simple name, similar to the regular case above.
old = qualifiedCref;
var aliasNode = aliasQualifier?.ToIdentifierName() ?? SyntaxFactory.IdentifierName(SyntaxFactory.Token(SyntaxKind.GlobalKeyword));
var aliasQualifiedNode = SyntaxFactory.AliasQualifiedName(aliasNode, nameRef.WithoutTrivia());
@new = SyntaxFactory.TypeCref(aliasQualifiedNode);
}
else
{
// if the new namespace is not global, then we just need to change the container in `QualifiedCrefSyntax`,
// which is just a regular namespace node, no cref node involve here.
old = container;
@new = CreateNameSyntax(newNamespaceParts, aliasQualifier, newNamespaceParts.Length - 1);
}
return true;
}
// Simple name reference, nothing to be done.
// The name will be resolved by adding proper import.
old = @new = nameRef;
return false;
}
protected override string EscapeIdentifier(string identifier)
=> identifier.EscapeIdentifier();
protected override async Task<SyntaxNode> ShouldPositionTriggerRefactoringAsync(Document document, int position, CancellationToken cancellationToken)
{
var compilationUnit = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false) as CompilationUnitSyntax;
// Here's conditions that trigger the refactoring (all have to be true in each scenario):
//
// - There's only one namespace declaration in the document and all types are declared in it:
// 1. No nested namespace declaration (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.
//
// - There's no namespace declaration and all types in the document are declared in global namespace:
// 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 triggeringNode = GetTriggeringNode(compilationUnit, position);
if (triggeringNode != null)
{
var containsPartial = await ContainsPartialTypeWithMultipleDeclarationsAsync(document, triggeringNode, cancellationToken)
.ConfigureAwait(false);
if (!containsPartial)
{
return triggeringNode;
}
}
return default;
SyntaxNode GetTriggeringNode(CompilationUnitSyntax compUnit, int pos)
{
var namespaceDecls = compilationUnit.DescendantNodes(n => n is CompilationUnitSyntax || n is NamespaceDeclarationSyntax)
.OfType<NamespaceDeclarationSyntax>().ToImmutableArray();
if (namespaceDecls.Length == 1 && compilationUnit.Members.Count == 1)
{
var namespaceDeclaration = namespaceDecls[0];
Debug.Assert(namespaceDeclaration == compilationUnit.Members[0]);
if (namespaceDeclaration.Name.Span.IntersectsWith(position)
&& namespaceDeclaration.Name.GetDiagnostics().All(diag => diag.DefaultSeverity != DiagnosticSeverity.Error))
{
return namespaceDeclaration;
}
}
else if (namespaceDecls.Length == 0)
{
var firstMemberDeclarationName = compilationUnit.Members.FirstOrDefault().GetNameToken();
if (firstMemberDeclarationName != default
&& firstMemberDeclarationName.Span.IntersectsWith(position))
{
return compilationUnit;
}
}
return null;
}
}
/// <summary>
/// Try to change the namespace declaration based on the refacoring rules:
/// - if neither declared and target namespace are "" (i.e. global namespace),
/// then we try to change the name of the namespace.
/// - if declared namespace is "", then we try to move all types declared
/// in global namespace in the document into a new namespace declaration.
/// - if target namespace is "", then we try to move all members in declared
/// namespace to global namespace (i.e. remove the namespace declaration).
/// </summary>
protected override CompilationUnitSyntax ChangeNamespaceDeclaration(
CompilationUnitSyntax compilationUnit,
ImmutableArray<string> declaredNamespaceParts,
ImmutableArray<string> targetNamespaceParts)
{
Debug.Assert(!declaredNamespaceParts.IsDefault && !targetNamespaceParts.IsDefault);
// Move everything from global namespace to a namespace declaration
if (IsGlobalNamespace(declaredNamespaceParts))
{
var targetNamespaceDecl = SyntaxFactory.NamespaceDeclaration(
name: CreateNameSyntax(targetNamespaceParts, aliasQualifier: null, targetNamespaceParts.Length - 1)
.WithAdditionalAnnotations(WarningAnnotation),
externs: default,
usings: default,
members: compilationUnit.Members);
return compilationUnit.WithMembers(new SyntaxList<MemberDeclarationSyntax>(targetNamespaceDecl));
}
// We should have a single member which is a namespace declaration in this compilation unit.
var namespaceDeclaration = compilationUnit.DescendantNodes().OfType<NamespaceDeclarationSyntax>().Single();
// Move everything to global namespace
if (IsGlobalNamespace(targetNamespaceParts))
{
var (namespaceOpeningTrivia, namespaceClosingTrivia) =
GetOpeningAndClosingTriviaOfNamespaceDeclaration(namespaceDeclaration);
var members = namespaceDeclaration.Members;
var eofToken = compilationUnit.EndOfFileToken
.WithAdditionalAnnotations(WarningAnnotation);
// Try to preserve trivia from original namesapce declaration.
// If there's any member inside the declaration, we attach them to the
// first and last member, otherwise, simply attach all to the EOF token.
if (members.Count > 0)
{
var first = members.First();
var firstWithTrivia = first.WithPrependedLeadingTrivia(namespaceOpeningTrivia);
members = members.Replace(first, firstWithTrivia);
var last = members.Last();
var lastWithTrivia = last.WithAppendedTrailingTrivia(namespaceClosingTrivia);
members = members.Replace(last, lastWithTrivia);
}
else
{
eofToken = eofToken.WithPrependedLeadingTrivia(
namespaceOpeningTrivia.Concat(namespaceClosingTrivia));
}
// Moving inner imports out of the namespace declaration can lead to a break in semantics.
// For example:
//
// namespace A.B.C
// {
// using D.E.F;
// }
//
// The using of D.E.F is looked up iwith in the context of A.B.C first. If it's moved outside,
// it may fail to resolve.
return compilationUnit.Update(
compilationUnit.Externs.AddRange(namespaceDeclaration.Externs),
compilationUnit.Usings.AddRange(namespaceDeclaration.Usings),
compilationUnit.AttributeLists,
members,
eofToken);
}
// Change namespace name
return compilationUnit.ReplaceNode(namespaceDeclaration,
namespaceDeclaration.WithName(
CreateNameSyntax(targetNamespaceParts, aliasQualifier: null, targetNamespaceParts.Length - 1)
.WithTriviaFrom(namespaceDeclaration.Name)
.WithAdditionalAnnotations(WarningAnnotation)));
}
private static bool IsGlobalNamespace(ImmutableArray<string> parts)
=> parts.Length == 1 && parts[0].Length == 0;
private static string GetAliasQualifierOpt(SyntaxNode name)
{
while (true)
{
switch (name.Kind())
{
case SyntaxKind.QualifiedName:
name = ((QualifiedNameSyntax)name).Left;
continue;
case SyntaxKind.AliasQualifiedName:
return ((AliasQualifiedNameSyntax)name).Alias.Identifier.ValueText;
}
return null;
}
}
private NameSyntax CreateNameSyntax(ImmutableArray<string> namespaceParts, string aliasQualifier, int index)
{
var part = namespaceParts[index].EscapeIdentifier();
Debug.Assert(part.Length > 0);
var namePiece = SyntaxFactory.IdentifierName(part);
if (index == 0)
{
return aliasQualifier == null ? (NameSyntax)namePiece : SyntaxFactory.AliasQualifiedName(aliasQualifier, namePiece);
}
else
{
return SyntaxFactory.QualifiedName(CreateNameSyntax(namespaceParts, aliasQualifier, index - 1), namePiece);
}
}
/// <summary>
/// return trivia attached to namespace declaration.
/// Leading trivia of the node and trivia around opening brace, as well as
/// trivia around closing brace are concatenated together respectively.
/// </summary>
private static (ImmutableArray<SyntaxTrivia> openingTrivia, ImmutableArray<SyntaxTrivia> closingTrivia)
GetOpeningAndClosingTriviaOfNamespaceDeclaration(NamespaceDeclarationSyntax namespaceDeclaration)
{
var openingBuilder = ArrayBuilder<SyntaxTrivia>.GetInstance();
openingBuilder.AddRange(namespaceDeclaration.GetLeadingTrivia());
openingBuilder.AddRange(namespaceDeclaration.OpenBraceToken.LeadingTrivia);
openingBuilder.AddRange(namespaceDeclaration.OpenBraceToken.TrailingTrivia);
var closingBuilder = ArrayBuilder<SyntaxTrivia>.GetInstance();
closingBuilder.AddRange(namespaceDeclaration.CloseBraceToken.LeadingTrivia);
closingBuilder.AddRange(namespaceDeclaration.CloseBraceToken.TrailingTrivia);
return (openingBuilder.ToImmutableAndFree(), closingBuilder.ToImmutableAndFree());
}
}
}
...@@ -20,7 +20,7 @@ internal partial class AbstractCSharpRemoveUnnecessaryImportsService : ...@@ -20,7 +20,7 @@ internal partial class AbstractCSharpRemoveUnnecessaryImportsService :
AbstractRemoveUnnecessaryImportsService<UsingDirectiveSyntax> AbstractRemoveUnnecessaryImportsService<UsingDirectiveSyntax>
{ {
public override async Task<Document> RemoveUnnecessaryImportsAsync( public override async Task<Document> RemoveUnnecessaryImportsAsync(
Document document, Document document,
Func<SyntaxNode, bool> predicate, Func<SyntaxNode, bool> predicate,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
......
...@@ -32,5 +32,6 @@ internal static class PredefinedCodeRefactoringProviderNames ...@@ -32,5 +32,6 @@ internal static class PredefinedCodeRefactoringProviderNames
public const string UseExplicitType = "Use Explicit Type Code Action Provider"; public const string UseExplicitType = "Use Explicit Type Code Action Provider";
public const string UseImplicitType = "Use Implicit Type Code Action Provider"; public const string UseImplicitType = "Use Implicit Type Code Action Provider";
public const string UseExpressionBody = "Use Expression Body Code Action Provider"; public const string UseExpressionBody = "Use Expression Body Code Action Provider";
public const string SyncNamespace = "Sync Namespace and Folder Name Code Action Provider";
} }
} }
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.PooledObjects;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CodeRefactorings.SyncNamespace
{
internal abstract partial class AbstractSyncNamespaceService<TNamespaceDeclarationSyntax, TCompilationUnitSyntax, TMemberDeclarationSyntax>
where TNamespaceDeclarationSyntax : SyntaxNode
where TCompilationUnitSyntax : SyntaxNode
where TMemberDeclarationSyntax : SyntaxNode
{
internal sealed class MoveFileCodeAction : CodeAction
{
private readonly State _state;
private readonly ImmutableArray<string> _newfolders;
public override string Title
=> _newfolders.Length > 0
? string.Format(FeaturesResources.Move_file_to_0, string.Join(PathUtilities.DirectorySeparatorStr, _newfolders))
: FeaturesResources.Move_file_to_project_root_folder;
public MoveFileCodeAction(State state, ImmutableArray<string> newFolders)
{
_state = state;
_newfolders = newFolders;
}
internal override bool PerformFinalApplicabilityCheck => true;
internal override bool IsApplicable(Workspace workspace)
{
// Due to some existing issue, move file action is not available for CPS projects.
return workspace.CanRenameFilesDuringCodeActions(workspace.CurrentSolution.GetDocument(_state.OriginalDocumentId).Project);
}
protected override async Task<IEnumerable<CodeActionOperation>> ComputeOperationsAsync(CancellationToken cancellationToken)
{
var id = _state.OriginalDocumentId;
var solution = _state.Solution;
var document = solution.GetDocument(id);
var newDocumentId = DocumentId.CreateNewId(document.Project.Id, document.Name);
solution = solution.RemoveDocument(id);
var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);
solution = solution.AddDocument(newDocumentId, document.Name, text, folders: _newfolders);
return ImmutableArray.Create<CodeActionOperation>(
new ApplyChangesOperation(solution),
new OpenDocumentOperation(newDocumentId, activateIfAlreadyOpen: true));
}
public static ImmutableArray<MoveFileCodeAction> Create(State state)
{
Debug.Assert(state.RelativeDeclaredNamespace != null);
// Since all documents have identical folder structure, we can do the computation on any of them.
var document = state.Solution.GetDocument(state.OriginalDocumentId);
// In case the relative namespace is "", the file should be moved to project root,
// set `parts` to empty to indicate that.
var parts = state.RelativeDeclaredNamespace.Length == 0
? ImmutableArray<string>.Empty
: state.RelativeDeclaredNamespace.Split(new[] { '.' }).ToImmutableArray();
// Invalid char can only appear in namespace name when there's error,
// which we have checked before creating any code actions.
Debug.Assert(parts.IsEmpty || parts.Any(s => s.IndexOfAny(Path.GetInvalidPathChars()) < 0));
var projectRootFolder = FolderInfo.CreateFolderHierarchyForProject(document.Project);
var candidateFolders = FindCandidateFolders(projectRootFolder, parts, ImmutableArray<string>.Empty);
return candidateFolders.SelectAsArray(folders => new MoveFileCodeAction(state, folders));
}
/// <summary>
/// We try to provide additional "move file" options if we can find existing folders that matches target namespace.
/// For example, if the target namespace is 'DefaultNamesapce.A.B.C', and there's a folder 'ProjectRoot\A.B\' already
/// exists, then will provide two actions, "move file to ProjectRoot\A.B\C\" and "move file to ProjectRoot\A\B\C\".
/// </summary>
private static ImmutableArray<ImmutableArray<string>> FindCandidateFolders(
FolderInfo currentFolderInfo,
ImmutableArray<string> parts,
ImmutableArray<string> currentFolder)
{
if (parts.IsEmpty)
{
return ImmutableArray.Create(currentFolder);
}
// Try to figure out all possible folder names that can match the target namespace.
// For example, if the target is "A.B.C", then the matching folder names include
// "A", "A.B" and "A.B.C". The item "index" in the result tuple is the number
// of items in namespace parts used to construct iten "foldername".
var candidates = Enumerable.Range(1, parts.Length)
.Select(i => (foldername: string.Join(".", parts.Take(i)), index: i))
.ToImmutableDictionary(t => t.foldername, t => t.index, PathUtilities.Comparer);
var subFolders = currentFolderInfo.ChildFolders;
var builder = ArrayBuilder<ImmutableArray<string>>.GetInstance();
foreach (var (folderName, index) in candidates)
{
if (subFolders.TryGetValue(folderName, out var matchingFolderInfo))
{
var newParts = index >= parts.Length
? ImmutableArray<string>.Empty
: ImmutableArray.Create(parts, index, parts.Length - index);
var newCurrentFolder = currentFolder.Add(matchingFolderInfo.Name);
builder.AddRange(FindCandidateFolders(matchingFolderInfo, newParts, newCurrentFolder));
}
}
// Make sure we always have the default path as an available option to the user
// (which might have been found by the search above, therefore the check here)
// For example, if the target namespace is "A.B.C.D", and there's folder <ROOT>\A.B\,
// the search above would only return "<ROOT>\A.B\C\D". We'd want to provide
// "<ROOT>\A\B\C\D" as the default path.
var defaultPathBasedOnCurrentFolder = currentFolder.AddRange(parts);
if (builder.All(folders => !folders.SequenceEqual(defaultPathBasedOnCurrentFolder, PathUtilities.Comparer)))
{
builder.Add(defaultPathBasedOnCurrentFolder);
}
return builder.ToImmutableAndFree();
}
private class FolderInfo
{
private Dictionary<string, FolderInfo> _childFolders;
public string Name { get; }
public IReadOnlyDictionary<string, FolderInfo> ChildFolders => _childFolders;
private FolderInfo(string name)
{
Name = name;
_childFolders = new Dictionary<string, FolderInfo>(StringComparer.Ordinal);
}
private void AddFolder(IEnumerable<string> folder)
{
if (!folder.Any())
{
return;
}
var firstFolder = folder.First();
if (!_childFolders.TryGetValue(firstFolder, out var firstFolderInfo))
{
firstFolderInfo = new FolderInfo(firstFolder);
_childFolders[firstFolder] = firstFolderInfo;
}
firstFolderInfo.AddFolder(folder.Skip(1));
}
// TODO:
// Since we are getting folder data from documents, only non-empty folders
// in the project are discovered. It's possible to get complete folder structure
// from VS but it requires UI thread to do so. We might want to revisit this later.
public static FolderInfo CreateFolderHierarchyForProject(Project project)
{
var handledFolders = new HashSet<string>(StringComparer.Ordinal);
var rootFolderInfo = new FolderInfo("<ROOT>");
foreach (var document in project.Documents)
{
var folders = document.Folders;
if (handledFolders.Add(string.Join(PathUtilities.DirectorySeparatorStr, folders)))
{
rootFolderInfo.AddFolder(folders);
}
}
return rootFolderInfo;
}
}
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.LanguageServices;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CodeRefactorings.SyncNamespace
{
internal abstract partial class AbstractSyncNamespaceService<TNamespaceDeclarationSyntax, TCompilationUnitSyntax, TMemberDeclarationSyntax>
where TNamespaceDeclarationSyntax : SyntaxNode
where TCompilationUnitSyntax : SyntaxNode
where TMemberDeclarationSyntax : SyntaxNode
{
internal sealed class State
{
private static readonly SymbolDisplayFormat s_qualifiedNameOnlyFormat =
new SymbolDisplayFormat(globalNamespaceStyle: SymbolDisplayGlobalNamespaceStyle.Omitted,
typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces);
public Solution Solution { get; }
/// <summary>
/// The document in which the refactoring is triggered.
/// </summary>
public DocumentId OriginalDocumentId { get; }
/// <summary>
/// The refactoring is also enabled for document in a multi-targeting project,
/// which is the only form of linked document allowed. This property returns IDs
/// of the original document that triggered the refactoring plus every such linked
/// documents.
/// </summary>
public ImmutableArray<DocumentId> DocumentIds { get; }
/// <summary>
/// This is the default namespace defined in the project file.
/// </summary>
public string DefaultNamespace { get; }
/// <summary>
/// This is the name of the namespace declaration that trigger the refactoring.
/// </summary>
public string DeclaredNamespace { get; }
/// <summary>
/// This is the new name we want to change the namespace to.
/// Empty string means global namespace, whereas null means change namespace action is not available.
/// </summary>
public string TargetNamespace { get; }
/// <summary>
/// This is the part of the declared namespace that is contained in default namespace.
/// We will use this to construct target folder to move the file to.
/// For example, if default namespace is `A` and declared namespace is `A.B.C`,
/// this would be `B.C`.
/// </summary>
public string RelativeDeclaredNamespace { get; }
private State(
Solution solution,
DocumentId originalDocumentId,
ImmutableArray<DocumentId> documentIds,
string rootNamespce,
string targetNamespace,
string declaredNamespace,
string relativeDeclaredNamespace)
{
Solution = solution;
OriginalDocumentId = originalDocumentId;
DocumentIds = documentIds;
DefaultNamespace = rootNamespce;
TargetNamespace = targetNamespace;
DeclaredNamespace = declaredNamespace;
RelativeDeclaredNamespace = relativeDeclaredNamespace;
}
/// <summary>
/// This refactoring only supports non-linked document and linked document in the form of
/// documents in multi-targeting project. Also for simplicity, we also don't support document
/// what has different file path and logical path in project (i.e. [ProjectRoot] + `Document.Folders`).
/// If the requirements above is met, we will return IDs of all documents linked to the specified
/// document (inclusive), an array of single element will be returned for non-linked document.
/// </summary>
private static bool IsSupportedLinkedDocument(Document document, out ImmutableArray<DocumentId> allDocumentIds)
{
var solution = document.Project.Solution;
var linkedDocumentids = document.GetLinkedDocumentIds();
// TODO: figure out how to properly determine if and how a document is linked using project system.
// If we found a linked document which is part of a project with differenct project file,
// then it's an actual linked file (i.e. not a multi-targeting project). We don't support that, because
// we don't know which default namespace and folder path we should use to construct target
// namespace.
if (linkedDocumentids.Any(id =>
!PathUtilities.PathsEqual(solution.GetDocument(id).Project.FilePath, document.Project.FilePath)))
{
allDocumentIds = default;
return false;
}
// Now determine if the actual file path matches its logical path in project
// which is constructed as <project root path>\Logical\Folders\. The refactoring
// is triggered only when the two match. The reason of doing this is we don't really know
// the user's intention of keeping the file path out-of-sync with its logical path.
var projectRoot = PathUtilities.GetDirectoryName(document.Project.FilePath);
var folderPath = Path.Combine(document.Folders.ToArray());
var absoluteDircetoryPath = PathUtilities.GetDirectoryName(document.FilePath);
var logicalDirectoryPath = PathUtilities.CombineAbsoluteAndRelativePaths(projectRoot, folderPath);
if (PathUtilities.PathsEqual(absoluteDircetoryPath, logicalDirectoryPath))
{
allDocumentIds = linkedDocumentids.Add(document.Id);
return true;
}
else
{
allDocumentIds = default;
return false;
}
}
private static string GetDefaultNamespace(ImmutableArray<Document> documents, ISyntaxFactsService syntaxFacts)
{
// For all projects containing all the linked documents, bail if
// 1. Any of them doesn't have default namespace, or
// 2. Multiple default namespace are found. (this might be possible by tweaking project file).
// The refactoring depends on a single default namespace to operate.
var defaultNamespaceFromProjects = new HashSet<string>(
documents.Select(d => d.Project.DefaultNamespace),
syntaxFacts.StringComparer);
if (defaultNamespaceFromProjects.Count != 1
|| defaultNamespaceFromProjects.First() == null)
{
return default;
}
return defaultNamespaceFromProjects.Single();
}
private static async Task<(bool shouldTrigger, string declaredNamespace)> TryGetNamespaceDeclarationAsync(
TextSpan textSpan,
ImmutableArray<Document> documents,
AbstractSyncNamespaceService<TNamespaceDeclarationSyntax, TCompilationUnitSyntax, TMemberDeclarationSyntax> service,
CancellationToken cancellationToken)
{
// If the cursor location doesn't meet the requirement to trigger the refactoring in any of the documents
// (See `ShouldPositionTriggerRefactoringAsync`), or we are getting different namespace declarations among
// those documents, then we know we can't make a proper code change. We will return false and the refactoring
// will then bail. We use span of namespace declaration found in each document to decide if they are identical.
var spansForNamespaceDeclaration = PooledDictionary<TextSpan, TNamespaceDeclarationSyntax>.GetInstance();
try
{
foreach (var document in documents)
{
var compilationUnitOrNamespaceDeclOpt = await service.ShouldPositionTriggerRefactoringAsync(document, textSpan.Start, cancellationToken)
.ConfigureAwait(false);
if (compilationUnitOrNamespaceDeclOpt is TNamespaceDeclarationSyntax namespaceDeclaration)
{
spansForNamespaceDeclaration[namespaceDeclaration.Span] = namespaceDeclaration;
}
else if (compilationUnitOrNamespaceDeclOpt is TCompilationUnitSyntax)
{
// In case there's no namespace declaration in the document, we used an empty span as key,
// since a valid namespace declaration node can't have zero length.
spansForNamespaceDeclaration[default] = null;
}
else
{
return default;
}
}
if (spansForNamespaceDeclaration.Count != 1)
{
return default;
}
var namespaceDecl = spansForNamespaceDeclaration.Values.Single();
var declaredNamespace = namespaceDecl == null
// namespaceDecl == null means the target namespace is global namespace.
? string.Empty
// Since the node in each document has identical type and span,
// they should have same name.
: SyntaxGenerator.GetGenerator(documents.First()).GetName(namespaceDecl);
return (true, declaredNamespace);
}
finally
{
spansForNamespaceDeclaration.Free();
}
}
public static async Task<State> CreateAsync(
AbstractSyncNamespaceService<TNamespaceDeclarationSyntax, TCompilationUnitSyntax, TMemberDeclarationSyntax> service,
Document document,
TextSpan textSpan,
CancellationToken cancellationToken)
{
if (document.Project.FilePath == null
|| !textSpan.IsEmpty
|| document.Project.Solution.Workspace.Kind == WorkspaceKind.MiscellaneousFiles
|| document.IsGeneratedCode(cancellationToken))
{
return null;
}
if (!IsSupportedLinkedDocument(document, out var documentIds))
{
return null;
}
var syntaxFacts = document.GetLanguageService<ISyntaxFactsService>();
var solution = document.Project.Solution;
var documents = documentIds.SelectAsArray(id => solution.GetDocument(id));
var defaultNamespace = GetDefaultNamespace(documents, syntaxFacts);
if (defaultNamespace == null)
{
return null;
}
var (shouldTrigger, declaredNamespace) =
await TryGetNamespaceDeclarationAsync(textSpan, documents, service, cancellationToken).ConfigureAwait(false);
if (!shouldTrigger)
{
return null;
}
// 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(service, 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(
solution,
document.Id,
documentIds,
defaultNamespace,
targetNamespace,
declaredNamespace,
relativeNamespace);
}
/// <summary>
/// Create a qualified identifier as the suffix of namespace based on a list of folder names.
/// </summary>
private static string TryBuildNamespaceFromFolders(
AbstractSyncNamespaceService<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;
}
private static string ConcatNamespace(string rootNamespace, string namespaceSuffix)
{
Debug.Assert(rootNamespace != null && namespaceSuffix != null);
if (namespaceSuffix.Length == 0)
{
return rootNamespace;
}
else if (rootNamespace.Length == 0)
{
return namespaceSuffix;
}
else
{
return rootNamespace + "." + namespaceSuffix;
}
}
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.LanguageServices;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
namespace Microsoft.CodeAnalysis.CodeRefactorings.SyncNamespace
{
internal abstract partial class AbstractSyncNamespaceService<TNamespaceDeclarationSyntax, TCompilationUnitSyntax, TMemberDeclarationSyntax> :
ISyncNamespaceService
where TNamespaceDeclarationSyntax : SyntaxNode
where TCompilationUnitSyntax : SyntaxNode
where TMemberDeclarationSyntax : SyntaxNode
{
public async Task<ImmutableArray<CodeAction>> GetRefactoringsAsync(
Document document, TextSpan textSpan, CancellationToken cancellationToken)
{
var state = await State.CreateAsync(this, document, textSpan, cancellationToken).ConfigureAwait(false);
if (state == null)
{
return default;
}
return CreateCodeActions(this, state);
}
public abstract bool TryGetReplacementReferenceSyntax(
SyntaxNode reference, ImmutableArray<string> newNamespaceParts, ISyntaxFactsService syntaxFacts, out SyntaxNode old, out SyntaxNode @new);
protected abstract string EscapeIdentifier(string identifier);
protected abstract TCompilationUnitSyntax ChangeNamespaceDeclaration(
TCompilationUnitSyntax root, ImmutableArray<string> declaredNamespaceParts, ImmutableArray<string> targetNamespaceParts);
protected abstract SyntaxList<TMemberDeclarationSyntax> GetMemberDeclarationsInContainer(SyntaxNode compilationUnitOrNamespaceDecl);
/// <summary>
/// Determine if this refactoring should be triggered based on current cursor position and if there's any partial
/// type declarations. It should only be triggered if the cursor is:
/// (1) in the name of only namespace declaration
/// (2) in the name of first declaration in global namespace if there's no namespace declaration in this document.
/// </summary>
/// <returns>
/// If the refactoring should be triggered, then returns the only namespace declaration node in the document (or type
/// <typeparamref name="TNamespaceDeclarationSyntax"/>) or the compilation unit node (of type <typeparamref name="TCompilationUnitSyntax"/>)
/// if no namespace declaration in the document. Otherwise, return null.
/// </returns>
protected abstract Task<SyntaxNode> ShouldPositionTriggerRefactoringAsync(Document document, int position, CancellationToken cancellationToken);
protected static SyntaxAnnotation WarningAnnotation { get; }
= CodeActions.WarningAnnotation.Create(
FeaturesResources.Warning_colon_changing_namespace_may_produce_invalid_code_and_change_code_meaning);
protected async Task<bool> ContainsPartialTypeWithMultipleDeclarationsAsync(
Document document, SyntaxNode compilationUnitOrNamespaceDecl, CancellationToken cancellationToken)
{
var memberDecls = GetMemberDeclarationsInContainer(compilationUnitOrNamespaceDecl);
var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
var semanticFacts = document.GetLanguageService<ISemanticFactsService>();
foreach (var memberDecl in memberDecls)
{
var memberSymbol = semanticModel.GetDeclaredSymbol(memberDecl, cancellationToken);
// Simplify the check by assuming no multiple partial declarations in one document
if (memberSymbol is ITypeSymbol typeSymbol
&& typeSymbol.DeclaringSyntaxReferences.Length > 1
&& semanticFacts.IsPartial(typeSymbol, cancellationToken))
{
return true;
}
}
return false;
}
/// <summary>
/// Try get the relative namespace for <paramref name="namespace"/> based on <paramref name="relativeTo"/>,
/// if <paramref name="relativeTo"/> is the containing namespace of <paramref name="namespace"/>.
/// For example:
/// - If <paramref name="relativeTo"/> is "A.B" and <paramref name="namespace"/> is "A.B.C.D", then
/// the relative namespace is "C.D".
/// - If <paramref name="relativeTo"/> is "A.B" and <paramref name="namespace"/> is also "A.B", then
/// the relative namespace is "".
/// - If <paramref name="relativeTo"/> is "" then the relative namespace us <paramref name="namespace"/>.
/// </summary>
private static string GetRelativeNamespace(string relativeTo, string @namespace, ISyntaxFactsService syntaxFacts)
{
Debug.Assert(relativeTo != null && @namespace != null);
if (syntaxFacts.StringComparer.Equals(@namespace, relativeTo))
{
return string.Empty;
}
else if (relativeTo.Length == 0)
{
return @namespace;
}
else if (relativeTo.Length >= @namespace.Length)
{
return null;
}
var containingText = relativeTo + ".";
var namespacePrefix = @namespace.Substring(0, containingText.Length);
return syntaxFacts.StringComparer.Equals(containingText, namespacePrefix)
? @namespace.Substring(relativeTo.Length + 1)
: null;
}
private static ImmutableArray<CodeAction> CreateCodeActions(
AbstractSyncNamespaceService<TNamespaceDeclarationSyntax, TCompilationUnitSyntax, TMemberDeclarationSyntax> service, State state)
{
var builder = ArrayBuilder<CodeAction>.GetInstance();
// No move file action if rootnamespace isn't a prefix of current declared namespace
if (state.RelativeDeclaredNamespace != null)
{
builder.AddRange(MoveFileCodeAction.Create(state));
}
// No change namespace action if we can't construct a valid namespace from rootnamespace and folder names.
if (state.TargetNamespace != null)
{
builder.Add(new ChangeNamespaceCodeAction(service, state));
}
return builder.ToImmutableAndFree();
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Immutable;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.LanguageServices;
using Microsoft.CodeAnalysis.Text;
namespace Microsoft.CodeAnalysis.CodeRefactorings.SyncNamespace
{
internal interface ISyncNamespaceService : ILanguageService
{
Task<ImmutableArray<CodeAction>> GetRefactoringsAsync(Document document, TextSpan textSpan, CancellationToken cancellationToken);
/// <summary>
/// Try to get a new node to replace given node, which is a reference to a top-level type declared inside the
/// namespce to be changed. If this reference is the right side of a qualified name, the new node returned would
/// be the entire qualified name. Depends on whether <paramref name="newNamespaceParts"/> is provided, the name
/// in the new node might be qualified with this new namespace instead.
/// </summary>
/// <param name="reference">A reference to a type declared inside the namespce to be changed, which is calculated
/// based on results from `SymbolFinder.FindReferencesAsync`.</param>
/// <param name="newNamespaceParts">If specified, the namespace of original reference will be replaced with given
/// namespace in the replacement node.</param>
/// <param name="old">The node to be replaced. This might be an ancestor of original </param>
/// <param name="new">The replacement node.</param>
bool TryGetReplacementReferenceSyntax(
SyntaxNode reference,
ImmutableArray<string> newNamespaceParts,
ISyntaxFactsService syntaxFacts,
out SyntaxNode old,
out SyntaxNode @new);
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Composition;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Shared.Extensions;
namespace Microsoft.CodeAnalysis.CodeRefactorings.SyncNamespace
{
[ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = PredefinedCodeRefactoringProviderNames.SyncNamespace), Shared]
internal sealed class SyncNamespaceCodeRefactoringProvider : CodeRefactoringProvider
{
public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context)
{
var document = context.Document;
var textSpan = context.Span;
var cancellationToken = context.CancellationToken;
var service = document.GetLanguageService<ISyncNamespaceService>();
var actions = await service.GetRefactoringsAsync(document, textSpan, cancellationToken).ConfigureAwait(false);
context.RegisterRefactorings(actions);
}
}
}
...@@ -723,6 +723,15 @@ internal class FeaturesResources { ...@@ -723,6 +723,15 @@ internal class FeaturesResources {
} }
} }
/// <summary>
/// Looks up a localized string similar to Change namespace to &apos;{0}&apos;.
/// </summary>
internal static string Change_namespace_to_0 {
get {
return ResourceManager.GetString("Change_namespace_to_0", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Change signature.... /// Looks up a localized string similar to Change signature....
/// </summary> /// </summary>
...@@ -732,6 +741,15 @@ internal class FeaturesResources { ...@@ -732,6 +741,15 @@ internal class FeaturesResources {
} }
} }
/// <summary>
/// Looks up a localized string similar to Change to global namespace.
/// </summary>
internal static string Change_to_global_namespace {
get {
return ResourceManager.GetString("Change_to_global_namespace", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Changes to expression trees may result in behavior changes at runtime. /// Looks up a localized string similar to Changes to expression trees may result in behavior changes at runtime.
/// </summary> /// </summary>
...@@ -2454,6 +2472,24 @@ internal class FeaturesResources { ...@@ -2454,6 +2472,24 @@ internal class FeaturesResources {
} }
} }
/// <summary>
/// Looks up a localized string similar to Move file to &apos;{0}&apos;.
/// </summary>
internal static string Move_file_to_0 {
get {
return ResourceManager.GetString("Move_file_to_0", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Move file to project root folder.
/// </summary>
internal static string Move_file_to_project_root_folder {
get {
return ResourceManager.GetString("Move_file_to_project_root_folder", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Move type to {0}. /// Looks up a localized string similar to Move type to {0}.
/// </summary> /// </summary>
...@@ -4105,6 +4141,16 @@ internal class FeaturesResources { ...@@ -4105,6 +4141,16 @@ internal class FeaturesResources {
} }
} }
/// <summary>
/// Looks up a localized string similar to Warning: Changing namespace may produce invalid code and change code meaning..
/// </summary>
internal static string Warning_colon_changing_namespace_may_produce_invalid_code_and_change_code_meaning {
get {
return ResourceManager.GetString("Warning_colon_changing_namespace_may_produce_invalid_code_and_change_code_meaning" +
"", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Warning: Collection may be modified during iteration.. /// Looks up a localized string similar to Warning: Collection may be modified during iteration..
/// </summary> /// </summary>
......
...@@ -1442,6 +1442,21 @@ This version used in: {2}</value> ...@@ -1442,6 +1442,21 @@ This version used in: {2}</value>
<data name="Modifying_source_file_will_prevent_the_debug_session_from_continuing_due_to_internal_error" xml:space="preserve"> <data name="Modifying_source_file_will_prevent_the_debug_session_from_continuing_due_to_internal_error" xml:space="preserve">
<value>Modifying source file {0} will prevent the debug session from continuing due to internal error: {1}.</value> <value>Modifying source file {0} will prevent the debug session from continuing due to internal error: {1}.</value>
</data> </data>
<data name="Change_namespace_to_0" xml:space="preserve">
<value>Change namespace to '{0}'</value>
</data>
<data name="Move_file_to_0" xml:space="preserve">
<value>Move file to '{0}'</value>
</data>
<data name="Move_file_to_project_root_folder" xml:space="preserve">
<value>Move file to project root folder</value>
</data>
<data name="Change_to_global_namespace" xml:space="preserve">
<value>Change to global namespace</value>
</data>
<data name="Warning_colon_changing_namespace_may_produce_invalid_code_and_change_code_meaning" xml:space="preserve">
<value>Warning: Changing namespace may produce invalid code and change code meaning.</value>
</data>
<data name="Use_compound_assignment" xml:space="preserve"> <data name="Use_compound_assignment" xml:space="preserve">
<value>Use compound assignment</value> <value>Use compound assignment</value>
</data> </data>
......
...@@ -12,9 +12,9 @@ ...@@ -12,9 +12,9 @@
namespace Microsoft.CodeAnalysis.RemoveUnnecessaryImports namespace Microsoft.CodeAnalysis.RemoveUnnecessaryImports
{ {
internal abstract class AbstractRemoveUnnecessaryImportsService<T> : internal abstract class AbstractRemoveUnnecessaryImportsService<T> :
IRemoveUnnecessaryImportsService, IRemoveUnnecessaryImportsService,
IUnnecessaryImportsService, IUnnecessaryImportsService,
IEqualityComparer<T> where T : SyntaxNode IEqualityComparer<T> where T : SyntaxNode
{ {
public Task<Document> RemoveUnnecessaryImportsAsync(Document document, CancellationToken cancellationToken) public Task<Document> RemoveUnnecessaryImportsAsync(Document document, CancellationToken cancellationToken)
...@@ -48,7 +48,7 @@ protected SyntaxToken StripNewLines(Document document, SyntaxToken token) ...@@ -48,7 +48,7 @@ protected SyntaxToken StripNewLines(Document document, SyntaxToken token)
} }
protected abstract ImmutableArray<T> GetUnnecessaryImports( protected abstract ImmutableArray<T> GetUnnecessaryImports(
SemanticModel model, SyntaxNode root, SemanticModel model, SyntaxNode root,
Func<SyntaxNode, bool> predicate, CancellationToken cancellationToken); Func<SyntaxNode, bool> predicate, CancellationToken cancellationToken);
protected async Task<HashSet<T>> GetCommonUnnecessaryImportsOfAllContextAsync( protected async Task<HashSet<T>> GetCommonUnnecessaryImportsOfAllContextAsync(
......
...@@ -32,6 +32,16 @@ ...@@ -32,6 +32,16 @@
<target state="new">Adding a method with an explicit interface specifier will prevent the debug session from continuing.</target> <target state="new">Adding a method with an explicit interface specifier will prevent the debug session from continuing.</target>
<note /> <note />
</trans-unit> </trans-unit>
<trans-unit id="Change_namespace_to_0">
<source>Change namespace to '{0}'</source>
<target state="new">Change namespace to '{0}'</target>
<note />
</trans-unit>
<trans-unit id="Change_to_global_namespace">
<source>Change to global namespace</source>
<target state="new">Change to global namespace</target>
<note />
</trans-unit>
<trans-unit id="Code_Quality"> <trans-unit id="Code_Quality">
<source>Code Quality</source> <source>Code Quality</source>
<target state="new">Code Quality</target> <target state="new">Code Quality</target>
...@@ -87,6 +97,16 @@ ...@@ -87,6 +97,16 @@
<target state="translated">Formátuje se dokument.</target> <target state="translated">Formátuje se dokument.</target>
<note /> <note />
</trans-unit> </trans-unit>
<trans-unit id="Move_file_to_0">
<source>Move file to '{0}'</source>
<target state="new">Move file to '{0}'</target>
<note />
</trans-unit>
<trans-unit id="Move_file_to_project_root_folder">
<source>Move file to project root folder</source>
<target state="new">Move file to project root folder</target>
<note />
</trans-unit>
<trans-unit id="Indexing_can_be_simplified"> <trans-unit id="Indexing_can_be_simplified">
<source>Indexing can be simplified</source> <source>Indexing can be simplified</source>
<target state="new">Indexing can be simplified</target> <target state="new">Indexing can be simplified</target>
...@@ -182,6 +202,11 @@ ...@@ -182,6 +202,11 @@
<target state="new">Use interpolated verbatim string</target> <target state="new">Use interpolated verbatim string</target>
<note /> <note />
</trans-unit> </trans-unit>
<trans-unit id="Warning_colon_changing_namespace_may_produce_invalid_code_and_change_code_meaning">
<source>Warning: Changing namespace may produce invalid code and change code meaning.</source>
<target state="new">Warning: Changing namespace may produce invalid code and change code meaning.</target>
<note />
</trans-unit>
<trans-unit id="Use_range_operator"> <trans-unit id="Use_range_operator">
<source>Use range operator</source> <source>Use range operator</source>
<target state="new">Use range operator</target> <target state="new">Use range operator</target>
......
...@@ -32,6 +32,16 @@ ...@@ -32,6 +32,16 @@
<target state="new">Adding a method with an explicit interface specifier will prevent the debug session from continuing.</target> <target state="new">Adding a method with an explicit interface specifier will prevent the debug session from continuing.</target>
<note /> <note />
</trans-unit> </trans-unit>
<trans-unit id="Change_namespace_to_0">
<source>Change namespace to '{0}'</source>
<target state="new">Change namespace to '{0}'</target>
<note />
</trans-unit>
<trans-unit id="Change_to_global_namespace">
<source>Change to global namespace</source>
<target state="new">Change to global namespace</target>
<note />
</trans-unit>
<trans-unit id="Code_Quality"> <trans-unit id="Code_Quality">
<source>Code Quality</source> <source>Code Quality</source>
<target state="new">Code Quality</target> <target state="new">Code Quality</target>
...@@ -87,6 +97,16 @@ ...@@ -87,6 +97,16 @@
<target state="translated">Dokument wird formatiert</target> <target state="translated">Dokument wird formatiert</target>
<note /> <note />
</trans-unit> </trans-unit>
<trans-unit id="Move_file_to_0">
<source>Move file to '{0}'</source>
<target state="new">Move file to '{0}'</target>
<note />
</trans-unit>
<trans-unit id="Move_file_to_project_root_folder">
<source>Move file to project root folder</source>
<target state="new">Move file to project root folder</target>
<note />
</trans-unit>
<trans-unit id="Indexing_can_be_simplified"> <trans-unit id="Indexing_can_be_simplified">
<source>Indexing can be simplified</source> <source>Indexing can be simplified</source>
<target state="new">Indexing can be simplified</target> <target state="new">Indexing can be simplified</target>
...@@ -182,6 +202,11 @@ ...@@ -182,6 +202,11 @@
<target state="new">Use interpolated verbatim string</target> <target state="new">Use interpolated verbatim string</target>
<note /> <note />
</trans-unit> </trans-unit>
<trans-unit id="Warning_colon_changing_namespace_may_produce_invalid_code_and_change_code_meaning">
<source>Warning: Changing namespace may produce invalid code and change code meaning.</source>
<target state="new">Warning: Changing namespace may produce invalid code and change code meaning.</target>
<note />
</trans-unit>
<trans-unit id="Use_range_operator"> <trans-unit id="Use_range_operator">
<source>Use range operator</source> <source>Use range operator</source>
<target state="new">Use range operator</target> <target state="new">Use range operator</target>
......
...@@ -32,6 +32,16 @@ ...@@ -32,6 +32,16 @@
<target state="new">Adding a method with an explicit interface specifier will prevent the debug session from continuing.</target> <target state="new">Adding a method with an explicit interface specifier will prevent the debug session from continuing.</target>
<note /> <note />
</trans-unit> </trans-unit>
<trans-unit id="Change_namespace_to_0">
<source>Change namespace to '{0}'</source>
<target state="new">Change namespace to '{0}'</target>
<note />
</trans-unit>
<trans-unit id="Change_to_global_namespace">
<source>Change to global namespace</source>
<target state="new">Change to global namespace</target>
<note />
</trans-unit>
<trans-unit id="Code_Quality"> <trans-unit id="Code_Quality">
<source>Code Quality</source> <source>Code Quality</source>
<target state="new">Code Quality</target> <target state="new">Code Quality</target>
...@@ -87,6 +97,16 @@ ...@@ -87,6 +97,16 @@
<target state="translated">Aplicando formato al documento</target> <target state="translated">Aplicando formato al documento</target>
<note /> <note />
</trans-unit> </trans-unit>
<trans-unit id="Move_file_to_0">
<source>Move file to '{0}'</source>
<target state="new">Move file to '{0}'</target>
<note />
</trans-unit>
<trans-unit id="Move_file_to_project_root_folder">
<source>Move file to project root folder</source>
<target state="new">Move file to project root folder</target>
<note />
</trans-unit>
<trans-unit id="Indexing_can_be_simplified"> <trans-unit id="Indexing_can_be_simplified">
<source>Indexing can be simplified</source> <source>Indexing can be simplified</source>
<target state="new">Indexing can be simplified</target> <target state="new">Indexing can be simplified</target>
...@@ -182,6 +202,11 @@ ...@@ -182,6 +202,11 @@
<target state="new">Use interpolated verbatim string</target> <target state="new">Use interpolated verbatim string</target>
<note /> <note />
</trans-unit> </trans-unit>
<trans-unit id="Warning_colon_changing_namespace_may_produce_invalid_code_and_change_code_meaning">
<source>Warning: Changing namespace may produce invalid code and change code meaning.</source>
<target state="new">Warning: Changing namespace may produce invalid code and change code meaning.</target>
<note />
</trans-unit>
<trans-unit id="Use_range_operator"> <trans-unit id="Use_range_operator">
<source>Use range operator</source> <source>Use range operator</source>
<target state="new">Use range operator</target> <target state="new">Use range operator</target>
......
...@@ -32,6 +32,16 @@ ...@@ -32,6 +32,16 @@
<target state="new">Adding a method with an explicit interface specifier will prevent the debug session from continuing.</target> <target state="new">Adding a method with an explicit interface specifier will prevent the debug session from continuing.</target>
<note /> <note />
</trans-unit> </trans-unit>
<trans-unit id="Change_namespace_to_0">
<source>Change namespace to '{0}'</source>
<target state="new">Change namespace to '{0}'</target>
<note />
</trans-unit>
<trans-unit id="Change_to_global_namespace">
<source>Change to global namespace</source>
<target state="new">Change to global namespace</target>
<note />
</trans-unit>
<trans-unit id="Code_Quality"> <trans-unit id="Code_Quality">
<source>Code Quality</source> <source>Code Quality</source>
<target state="new">Code Quality</target> <target state="new">Code Quality</target>
...@@ -87,6 +97,16 @@ ...@@ -87,6 +97,16 @@
<target state="translated">Mise en forme du document</target> <target state="translated">Mise en forme du document</target>
<note /> <note />
</trans-unit> </trans-unit>
<trans-unit id="Move_file_to_0">
<source>Move file to '{0}'</source>
<target state="new">Move file to '{0}'</target>
<note />
</trans-unit>
<trans-unit id="Move_file_to_project_root_folder">
<source>Move file to project root folder</source>
<target state="new">Move file to project root folder</target>
<note />
</trans-unit>
<trans-unit id="Indexing_can_be_simplified"> <trans-unit id="Indexing_can_be_simplified">
<source>Indexing can be simplified</source> <source>Indexing can be simplified</source>
<target state="new">Indexing can be simplified</target> <target state="new">Indexing can be simplified</target>
...@@ -182,6 +202,11 @@ ...@@ -182,6 +202,11 @@
<target state="new">Use interpolated verbatim string</target> <target state="new">Use interpolated verbatim string</target>
<note /> <note />
</trans-unit> </trans-unit>
<trans-unit id="Warning_colon_changing_namespace_may_produce_invalid_code_and_change_code_meaning">
<source>Warning: Changing namespace may produce invalid code and change code meaning.</source>
<target state="new">Warning: Changing namespace may produce invalid code and change code meaning.</target>
<note />
</trans-unit>
<trans-unit id="Use_range_operator"> <trans-unit id="Use_range_operator">
<source>Use range operator</source> <source>Use range operator</source>
<target state="new">Use range operator</target> <target state="new">Use range operator</target>
......
...@@ -32,6 +32,16 @@ ...@@ -32,6 +32,16 @@
<target state="new">Adding a method with an explicit interface specifier will prevent the debug session from continuing.</target> <target state="new">Adding a method with an explicit interface specifier will prevent the debug session from continuing.</target>
<note /> <note />
</trans-unit> </trans-unit>
<trans-unit id="Change_namespace_to_0">
<source>Change namespace to '{0}'</source>
<target state="new">Change namespace to '{0}'</target>
<note />
</trans-unit>
<trans-unit id="Change_to_global_namespace">
<source>Change to global namespace</source>
<target state="new">Change to global namespace</target>
<note />
</trans-unit>
<trans-unit id="Code_Quality"> <trans-unit id="Code_Quality">
<source>Code Quality</source> <source>Code Quality</source>
<target state="new">Code Quality</target> <target state="new">Code Quality</target>
...@@ -87,6 +97,16 @@ ...@@ -87,6 +97,16 @@
<target state="translated">Formattazione del documento</target> <target state="translated">Formattazione del documento</target>
<note /> <note />
</trans-unit> </trans-unit>
<trans-unit id="Move_file_to_0">
<source>Move file to '{0}'</source>
<target state="new">Move file to '{0}'</target>
<note />
</trans-unit>
<trans-unit id="Move_file_to_project_root_folder">
<source>Move file to project root folder</source>
<target state="new">Move file to project root folder</target>
<note />
</trans-unit>
<trans-unit id="Indexing_can_be_simplified"> <trans-unit id="Indexing_can_be_simplified">
<source>Indexing can be simplified</source> <source>Indexing can be simplified</source>
<target state="new">Indexing can be simplified</target> <target state="new">Indexing can be simplified</target>
...@@ -182,6 +202,11 @@ ...@@ -182,6 +202,11 @@
<target state="new">Use interpolated verbatim string</target> <target state="new">Use interpolated verbatim string</target>
<note /> <note />
</trans-unit> </trans-unit>
<trans-unit id="Warning_colon_changing_namespace_may_produce_invalid_code_and_change_code_meaning">
<source>Warning: Changing namespace may produce invalid code and change code meaning.</source>
<target state="new">Warning: Changing namespace may produce invalid code and change code meaning.</target>
<note />
</trans-unit>
<trans-unit id="Use_range_operator"> <trans-unit id="Use_range_operator">
<source>Use range operator</source> <source>Use range operator</source>
<target state="new">Use range operator</target> <target state="new">Use range operator</target>
......
...@@ -32,6 +32,16 @@ ...@@ -32,6 +32,16 @@
<target state="new">Adding a method with an explicit interface specifier will prevent the debug session from continuing.</target> <target state="new">Adding a method with an explicit interface specifier will prevent the debug session from continuing.</target>
<note /> <note />
</trans-unit> </trans-unit>
<trans-unit id="Change_namespace_to_0">
<source>Change namespace to '{0}'</source>
<target state="new">Change namespace to '{0}'</target>
<note />
</trans-unit>
<trans-unit id="Change_to_global_namespace">
<source>Change to global namespace</source>
<target state="new">Change to global namespace</target>
<note />
</trans-unit>
<trans-unit id="Code_Quality"> <trans-unit id="Code_Quality">
<source>Code Quality</source> <source>Code Quality</source>
<target state="new">Code Quality</target> <target state="new">Code Quality</target>
...@@ -87,6 +97,16 @@ ...@@ -87,6 +97,16 @@
<target state="translated">ドキュメントの書式設定</target> <target state="translated">ドキュメントの書式設定</target>
<note /> <note />
</trans-unit> </trans-unit>
<trans-unit id="Move_file_to_0">
<source>Move file to '{0}'</source>
<target state="new">Move file to '{0}'</target>
<note />
</trans-unit>
<trans-unit id="Move_file_to_project_root_folder">
<source>Move file to project root folder</source>
<target state="new">Move file to project root folder</target>
<note />
</trans-unit>
<trans-unit id="Indexing_can_be_simplified"> <trans-unit id="Indexing_can_be_simplified">
<source>Indexing can be simplified</source> <source>Indexing can be simplified</source>
<target state="new">Indexing can be simplified</target> <target state="new">Indexing can be simplified</target>
...@@ -182,6 +202,11 @@ ...@@ -182,6 +202,11 @@
<target state="new">Use interpolated verbatim string</target> <target state="new">Use interpolated verbatim string</target>
<note /> <note />
</trans-unit> </trans-unit>
<trans-unit id="Warning_colon_changing_namespace_may_produce_invalid_code_and_change_code_meaning">
<source>Warning: Changing namespace may produce invalid code and change code meaning.</source>
<target state="new">Warning: Changing namespace may produce invalid code and change code meaning.</target>
<note />
</trans-unit>
<trans-unit id="Use_range_operator"> <trans-unit id="Use_range_operator">
<source>Use range operator</source> <source>Use range operator</source>
<target state="new">Use range operator</target> <target state="new">Use range operator</target>
......
...@@ -32,6 +32,16 @@ ...@@ -32,6 +32,16 @@
<target state="new">Adding a method with an explicit interface specifier will prevent the debug session from continuing.</target> <target state="new">Adding a method with an explicit interface specifier will prevent the debug session from continuing.</target>
<note /> <note />
</trans-unit> </trans-unit>
<trans-unit id="Change_namespace_to_0">
<source>Change namespace to '{0}'</source>
<target state="new">Change namespace to '{0}'</target>
<note />
</trans-unit>
<trans-unit id="Change_to_global_namespace">
<source>Change to global namespace</source>
<target state="new">Change to global namespace</target>
<note />
</trans-unit>
<trans-unit id="Code_Quality"> <trans-unit id="Code_Quality">
<source>Code Quality</source> <source>Code Quality</source>
<target state="new">Code Quality</target> <target state="new">Code Quality</target>
...@@ -87,6 +97,16 @@ ...@@ -87,6 +97,16 @@
<target state="translated">문서 서식 지정</target> <target state="translated">문서 서식 지정</target>
<note /> <note />
</trans-unit> </trans-unit>
<trans-unit id="Move_file_to_0">
<source>Move file to '{0}'</source>
<target state="new">Move file to '{0}'</target>
<note />
</trans-unit>
<trans-unit id="Move_file_to_project_root_folder">
<source>Move file to project root folder</source>
<target state="new">Move file to project root folder</target>
<note />
</trans-unit>
<trans-unit id="Indexing_can_be_simplified"> <trans-unit id="Indexing_can_be_simplified">
<source>Indexing can be simplified</source> <source>Indexing can be simplified</source>
<target state="new">Indexing can be simplified</target> <target state="new">Indexing can be simplified</target>
...@@ -182,6 +202,11 @@ ...@@ -182,6 +202,11 @@
<target state="new">Use interpolated verbatim string</target> <target state="new">Use interpolated verbatim string</target>
<note /> <note />
</trans-unit> </trans-unit>
<trans-unit id="Warning_colon_changing_namespace_may_produce_invalid_code_and_change_code_meaning">
<source>Warning: Changing namespace may produce invalid code and change code meaning.</source>
<target state="new">Warning: Changing namespace may produce invalid code and change code meaning.</target>
<note />
</trans-unit>
<trans-unit id="Use_range_operator"> <trans-unit id="Use_range_operator">
<source>Use range operator</source> <source>Use range operator</source>
<target state="new">Use range operator</target> <target state="new">Use range operator</target>
......
...@@ -32,6 +32,16 @@ ...@@ -32,6 +32,16 @@
<target state="new">Adding a method with an explicit interface specifier will prevent the debug session from continuing.</target> <target state="new">Adding a method with an explicit interface specifier will prevent the debug session from continuing.</target>
<note /> <note />
</trans-unit> </trans-unit>
<trans-unit id="Change_namespace_to_0">
<source>Change namespace to '{0}'</source>
<target state="new">Change namespace to '{0}'</target>
<note />
</trans-unit>
<trans-unit id="Change_to_global_namespace">
<source>Change to global namespace</source>
<target state="new">Change to global namespace</target>
<note />
</trans-unit>
<trans-unit id="Code_Quality"> <trans-unit id="Code_Quality">
<source>Code Quality</source> <source>Code Quality</source>
<target state="new">Code Quality</target> <target state="new">Code Quality</target>
...@@ -87,6 +97,16 @@ ...@@ -87,6 +97,16 @@
<target state="translated">Formatowanie dokumentu</target> <target state="translated">Formatowanie dokumentu</target>
<note /> <note />
</trans-unit> </trans-unit>
<trans-unit id="Move_file_to_0">
<source>Move file to '{0}'</source>
<target state="new">Move file to '{0}'</target>
<note />
</trans-unit>
<trans-unit id="Move_file_to_project_root_folder">
<source>Move file to project root folder</source>
<target state="new">Move file to project root folder</target>
<note />
</trans-unit>
<trans-unit id="Indexing_can_be_simplified"> <trans-unit id="Indexing_can_be_simplified">
<source>Indexing can be simplified</source> <source>Indexing can be simplified</source>
<target state="new">Indexing can be simplified</target> <target state="new">Indexing can be simplified</target>
...@@ -182,6 +202,11 @@ ...@@ -182,6 +202,11 @@
<target state="new">Use interpolated verbatim string</target> <target state="new">Use interpolated verbatim string</target>
<note /> <note />
</trans-unit> </trans-unit>
<trans-unit id="Warning_colon_changing_namespace_may_produce_invalid_code_and_change_code_meaning">
<source>Warning: Changing namespace may produce invalid code and change code meaning.</source>
<target state="new">Warning: Changing namespace may produce invalid code and change code meaning.</target>
<note />
</trans-unit>
<trans-unit id="Use_range_operator"> <trans-unit id="Use_range_operator">
<source>Use range operator</source> <source>Use range operator</source>
<target state="new">Use range operator</target> <target state="new">Use range operator</target>
......
...@@ -32,6 +32,16 @@ ...@@ -32,6 +32,16 @@
<target state="new">Adding a method with an explicit interface specifier will prevent the debug session from continuing.</target> <target state="new">Adding a method with an explicit interface specifier will prevent the debug session from continuing.</target>
<note /> <note />
</trans-unit> </trans-unit>
<trans-unit id="Change_namespace_to_0">
<source>Change namespace to '{0}'</source>
<target state="new">Change namespace to '{0}'</target>
<note />
</trans-unit>
<trans-unit id="Change_to_global_namespace">
<source>Change to global namespace</source>
<target state="new">Change to global namespace</target>
<note />
</trans-unit>
<trans-unit id="Code_Quality"> <trans-unit id="Code_Quality">
<source>Code Quality</source> <source>Code Quality</source>
<target state="new">Code Quality</target> <target state="new">Code Quality</target>
...@@ -87,6 +97,16 @@ ...@@ -87,6 +97,16 @@
<target state="translated">Formatando documento</target> <target state="translated">Formatando documento</target>
<note /> <note />
</trans-unit> </trans-unit>
<trans-unit id="Move_file_to_0">
<source>Move file to '{0}'</source>
<target state="new">Move file to '{0}'</target>
<note />
</trans-unit>
<trans-unit id="Move_file_to_project_root_folder">
<source>Move file to project root folder</source>
<target state="new">Move file to project root folder</target>
<note />
</trans-unit>
<trans-unit id="Indexing_can_be_simplified"> <trans-unit id="Indexing_can_be_simplified">
<source>Indexing can be simplified</source> <source>Indexing can be simplified</source>
<target state="new">Indexing can be simplified</target> <target state="new">Indexing can be simplified</target>
...@@ -182,6 +202,11 @@ ...@@ -182,6 +202,11 @@
<target state="new">Use interpolated verbatim string</target> <target state="new">Use interpolated verbatim string</target>
<note /> <note />
</trans-unit> </trans-unit>
<trans-unit id="Warning_colon_changing_namespace_may_produce_invalid_code_and_change_code_meaning">
<source>Warning: Changing namespace may produce invalid code and change code meaning.</source>
<target state="new">Warning: Changing namespace may produce invalid code and change code meaning.</target>
<note />
</trans-unit>
<trans-unit id="Use_range_operator"> <trans-unit id="Use_range_operator">
<source>Use range operator</source> <source>Use range operator</source>
<target state="new">Use range operator</target> <target state="new">Use range operator</target>
......
...@@ -125,6 +125,7 @@ public static class Features ...@@ -125,6 +125,7 @@ public static class Features
public const string CodeActionsSimplifyTypeNames = "CodeActions.SimplifyTypeNames"; public const string CodeActionsSimplifyTypeNames = "CodeActions.SimplifyTypeNames";
public const string CodeActionsSpellcheck = "CodeActions.Spellcheck"; public const string CodeActionsSpellcheck = "CodeActions.Spellcheck";
public const string CodeActionsSuppression = "CodeActions.Suppression"; public const string CodeActionsSuppression = "CodeActions.Suppression";
public const string CodeActionsSyncNamespace = "CodeActions.SyncNamespace";
public const string CodeActionsUseInterpolatedVerbatimString = "CodeActions.UseInterpolatedVerbatimString"; public const string CodeActionsUseInterpolatedVerbatimString = "CodeActions.UseInterpolatedVerbatimString";
public const string CodeActionsUseAutoProperty = "CodeActions.UseAutoProperty"; public const string CodeActionsUseAutoProperty = "CodeActions.UseAutoProperty";
public const string CodeActionsUseCoalesceExpression = "CodeActions.UseCoalesceExpression"; public const string CodeActionsUseCoalesceExpression = "CodeActions.UseCoalesceExpression";
......
...@@ -88,6 +88,11 @@ public bool IsKeyword(SyntaxToken token) ...@@ -88,6 +88,11 @@ public bool IsKeyword(SyntaxToken token)
SyntaxFacts.IsKeywordKind(kind); // both contextual and reserved keywords SyntaxFacts.IsKeywordKind(kind); // both contextual and reserved keywords
} }
public bool IsReservedKeyword(string text)
{
return SyntaxFacts.GetKeywordKind(text) != SyntaxKind.None; // reserved keywords only
}
public bool IsContextualKeyword(SyntaxToken token) public bool IsContextualKeyword(SyntaxToken token)
{ {
var kind = (SyntaxKind)token.RawKind; var kind = (SyntaxKind)token.RawKind;
......
...@@ -105,6 +105,10 @@ Namespace Microsoft.CodeAnalysis.VisualBasic ...@@ -105,6 +105,10 @@ Namespace Microsoft.CodeAnalysis.VisualBasic
Return token.IsKeyword() Return token.IsKeyword()
End Function End Function
Public Function IsReservedKeyword(text As String) As Boolean Implements ISyntaxFactsService.IsReservedKeyword
Return GetKeywordKind(text) <> SyntaxKind.None
End Function
Public Function IsPreprocessorKeyword(token As SyntaxToken) As Boolean Implements ISyntaxFactsService.IsPreprocessorKeyword Public Function IsPreprocessorKeyword(token As SyntaxToken) As Boolean Implements ISyntaxFactsService.IsPreprocessorKeyword
Return token.IsPreprocessorKeyword() Return token.IsPreprocessorKeyword()
End Function End Function
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册