/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/Analyzers/CSharp/Tests/MatchFolderAndNamespace/CSharpMatchFolderAndNamespaceTests.cs
1 071 строка
28 KB
Rikki Gibson
File-based apps: avoid running a few irrelevant editor features (#84575)
22 июл 2026, 04:42
Не верифицирован
22 июл 2026, 04:42
6e8d159
Код
Авторство
О чём код?
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System.Collections.Generic; using System.IO; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CSharp.Analyzers.MatchFolderAndNamespace; using Microsoft.CodeAnalysis.CSharp.CodeFixes.MatchFolderAndNamespace; using Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions; using Microsoft.CodeAnalysis.Shared.Extensions; using Roslyn.Test.Utilities; using Xunit; namespace Microsoft.CodeAnalysis.CSharp.Analyzers.UnitTests.MatchFolderAndNamespace; using VerifyCS = CSharpCodeFixVerifier< CSharpMatchFolderAndNamespaceDiagnosticAnalyzer, CSharpChangeNamespaceToMatchFolderCodeFixProvider>; public sealed class CSharpMatchFolderAndNamespaceTests { private static readonly string Directory = "/0/"; // DefaultNamespace gets exposed as RootNamespace in the build properties private const string DefaultNamespace = "Test.Root.Namespace"; private static readonly string EditorConfig = $""" is_global=true build_property.ProjectDir = {Directory} build_property.RootNamespace = {DefaultNamespace} """; private static string CreateFolderPath(params string[] folders) => Path.Combine(Directory, Path.Combine(folders)); private static Task RunTestAsync(string fileName, string fileContents, string? directory = null, string? editorConfig = null, string? fixedCode = null, string? defaultNamespace = null) { var filePath = Path.Combine(directory ?? Directory, fileName); fixedCode ??= fileContents; return RunTestAsync( new[] { (filePath, fileContents) }, new[] { (filePath, fixedCode) }, editorConfig, defaultNamespace); } private static Task RunTestAsync(IEnumerable<(string, string)> originalSources, IEnumerable<(string, string)>? fixedSources = null, string? editorconfig = null, string? defaultNamespace = null) { // When a namespace isn't provided we will fallback on our default defaultNamespace ??= DefaultNamespace; var testState = new VerifyCS.Test { EditorConfig = editorconfig ?? EditorConfig, CodeFixTestBehaviors = CodeAnalysis.Testing.CodeFixTestBehaviors.SkipFixAllInDocumentCheck, LanguageVersion = LanguageVersion.CSharp10, }; foreach (var (fileName, content) in originalSources) testState.TestState.Sources.Add((fileName, content)); fixedSources ??= []; foreach (var (fileName, content) in fixedSources) testState.FixedState.Sources.Add((fileName, content)); // If empty string was provided as the namespace, then we will not set a default if (defaultNamespace.Length > 0) { testState.SolutionTransforms.Add((solution, projectId) => { var project = solution.GetRequiredProject(projectId); return project.WithDefaultNamespace(defaultNamespace).Solution; }); } return testState.RunAsync(); } [Fact] public Task InvalidFolderName1_NoDiagnostic() { // No change namespace action because the folder name is not valid identifier var folder = CreateFolderPath(["3B", "C"]); return RunTestAsync( "File1.cs", """ namespace A.B { class Class1 { } } """, directory: folder); } [Fact] public Task InvalidFolderName1_NoDiagnostic_FileScopedNamespace() { // No change namespace action because the folder name is not valid identifier var folder = CreateFolderPath(["3B", "C"]); return RunTestAsync( "File1.cs", """ namespace A.B; class Class1 { } """, directory: folder); } [Fact] public Task InvalidFolderName2_NoDiagnostic() { // No change namespace action because the folder name is not valid identifier var folder = CreateFolderPath(["B.3C", "D"]); return RunTestAsync( "File1.cs", """ namespace A.B { class Class1 { } } """, directory: folder); } [Fact] public Task InvalidFolderName3_NoDiagnostic() { // No change namespace action because the folder name is not valid identifier var folder = CreateFolderPath([".folder", "..subfolder", "name"]); return RunTestAsync( "File1.cs", """ namespace A.B { class Class1 { } } """, directory: folder); } [Fact] public Task CaseInsensitiveMatch_NoDiagnostic() { var folder = CreateFolderPath(["A", "B"]); return RunTestAsync( "File1.cs", $$""" namespace {{DefaultNamespace}}.a.b { class Class1 { } } """, directory: folder); } [Fact] public async Task CodeStyleOptionIsFalse() { var folder = CreateFolderPath("B", "C"); await RunTestAsync( fileName: "Class1.cs", fileContents: """ namespace A.B { class Class1 { } } """, directory: folder, editorConfig: EditorConfig + """ dotnet_style_namespace_match_folder = false """); } [Fact] public async Task SingleDocumentNoReference() { var folder = CreateFolderPath("B", "C"); var fixedCode = $$""" namespace {{DefaultNamespace}}.B.C { class Class1 { } } """; await RunTestAsync( fileName: "Class1.cs", fileContents: """ namespace [|A.B|] { class Class1 { } } """, directory: folder, fixedCode: fixedCode); } [Fact] public async Task SingleDocumentNoReference_FileScopedNamespace() { var folder = CreateFolderPath("B", "C"); var fixedCode = $$""" namespace {{DefaultNamespace}}.B.C; class Class1 { } """; await RunTestAsync( fileName: "Class1.cs", fileContents: """ namespace [|A.B|]; class Class1 { } """, directory: folder, fixedCode: fixedCode); } [Fact, WorkItem("https://github.com/dotnet/vscode-csharp/issues/9550")] public async Task FileBasedProgram_NoDiagnostic() { // The namespace does not match the folder structure, but the document is part of a file-based // program, so IDE0130 should not fire. var folder = CreateFolderPath("B", "C"); var filePath = Path.Combine(folder, "Class1.cs"); var testState = new VerifyCS.Test { EditorConfig = EditorConfig, CodeFixTestBehaviors = CodeAnalysis.Testing.CodeFixTestBehaviors.SkipFixAllInDocumentCheck, LanguageVersion = LanguageVersion.CSharp10, }; testState.TestState.Sources.Add((filePath, """ namespace A.B { class Class1 { } } """)); testState.SolutionTransforms.Add((solution, projectId) => { var project = solution.GetRequiredProject(projectId); var parseOptions = (CSharpParseOptions)project.ParseOptions!; return solution .WithProjectParseOptions(projectId, parseOptions.WithFeatures([.. parseOptions.Features, new("FileBasedProgram", "true")])) .GetRequiredProject(projectId) .WithDefaultNamespace(DefaultNamespace) .Solution; }); await testState.RunAsync(); } [Fact] public async Task SingleDocumentNoReference_NoDefaultNamespace() { var editorConfig = $""" is_global=true build_property.ProjectDir = {Directory} """; var folder = CreateFolderPath("B", "C"); var fixedCode = $$""" namespace B.C { class Class1 { } } """; await RunTestAsync( fileName: "Class1.cs", fileContents: """ namespace [|A.B|] { class Class1 { } } """, directory: folder, fixedCode: fixedCode, editorConfig: editorConfig, // passing empty string means that a default namespace isn't set on the test Project defaultNamespace: string.Empty); } [Fact] public async Task SingleDocumentNoReference_NoDefaultNamespace_FileScopedNamespace() { var editorConfig = $""" is_global=true build_property.ProjectDir = {Directory} """; var folder = CreateFolderPath("B", "C"); var fixedCode = """ namespace B.C; class Class1 { } """; await RunTestAsync( fileName: "Class1.cs", fileContents: """ namespace [|A.B|]; class Class1 { } """, directory: folder, fixedCode: fixedCode, editorConfig: editorConfig, // passing empty string means that a default namespace isn't set on the test Project defaultNamespace: string.Empty); } [Fact] public async Task NamespaceWithSpaces_NoDiagnostic() { var folder = CreateFolderPath("A", "B"); await RunTestAsync( fileName: "Class1.cs", fileContents: $$""" namespace {{DefaultNamespace}}.A . B { class Class1 { } } """, directory: folder); } [Fact] public async Task NestedNamespaces_NoDiagnostic() { // The code fix doesn't currently support nested namespaces for sync, so // diagnostic does not report. var folder = CreateFolderPath("B", "C"); await RunTestAsync( fileName: "Class1.cs", fileContents: """ namespace A.B { namespace C.D { class CDClass { } } class ABClass { } } """, directory: folder); } [Fact] public async Task PartialTypeWithMultipleDeclarations_NoDiagnostic() { // The code fix doesn't currently support nested namespaces for sync, so // diagnostic does not report. var folder = CreateFolderPath("B", "C"); var code1 = """ namespace A.B { partial class ABClass { void M1() {} } } """; var code2 = """ namespace A.B { partial class ABClass { void M2() {} } } """; var sources = new[] { (Path.Combine(folder, "ABClass1.cs"), code1), (Path.Combine(folder, "ABClass2.cs"), code2), }; await RunTestAsync(sources); } [Fact] public async Task FileNotInProjectFolder_NoDiagnostic() { // Default directory is Test\Directory for the project, // putting the file outside the directory should have no // diagnostic shown. var folder = Path.Combine("B", "C"); await RunTestAsync( fileName: "Class1.cs", fileContents: $$""" namespace A.B { class ABClass { } } """, directory: folder); } [Fact] public async Task SingleDocumentLocalReference() { var @namespace = "Bar.Baz"; var folder = CreateFolderPath("A", "B", "C"); await RunTestAsync( "Class1.cs", $$""" namespace [|{{@namespace}}|] { delegate void D1(); interface Class1 { void M1(); } class Class2 : {{@namespace}}.Class1 { {{@namespace}}.D1 d; void {{@namespace}}.Class1.M1() { } } } """, folder, fixedCode: $$""" namespace {{DefaultNamespace}}.A.B.C { delegate void D1(); interface Class1 { void M1(); } class Class2 : Class1 { D1 d; void Class1.M1() { } } } """); } [Fact] public async Task ChangeUsingsInMultipleContainers() { var declaredNamespace = "Bar.Baz"; var folder = CreateFolderPath("A", "B", "C"); var code1 = $$""" namespace [|{{declaredNamespace}}|] { class Class1 { } } """; var code2 = $$""" namespace NS1 { using {{declaredNamespace}}; class Class2 { Class1 c2; } namespace NS2 { using {{declaredNamespace}}; class Class2 { Class1 c1; } } } """; var fixed1 = $$""" namespace {{DefaultNamespace}}.A.B.C { class Class1 { } } """; var fixed2 = $$""" namespace NS1 { using {{DefaultNamespace}}.A.B.C; class Class2 { Class1 c2; } namespace NS2 { class Class2 { Class1 c1; } } } """; var originalSources = new[] { (Path.Combine(folder, "Class1.cs"), code1), ("Class2.cs", code2) }; var fixedSources = new[] { (Path.Combine(folder, "Class1.cs"), fixed1), ("Class2.cs", fixed2) }; await RunTestAsync(originalSources, fixedSources); } [Fact] public async Task DocumentAtRoot_NoDiagnostic() { var folder = CreateFolderPath(); await RunTestAsync( "File1.cs", $$""" namespace {{DefaultNamespace}} { class C { } } """, folder); } [Fact] public async Task DocumentAtRoot_ChangeNamespace() { var folder = CreateFolderPath(); var fixedCode = $$""" namespace {{DefaultNamespace}} { class C { } } """; await RunTestAsync( "File1.cs", $$""" namespace [|{{DefaultNamespace}}.Test|] { class C { } } """, folder, fixedCode: fixedCode); } [Fact] public async Task ChangeNamespace_WithAliasReferencesInOtherDocument() { var declaredNamespace = $"Bar.Baz"; var folder = CreateFolderPath("A", "B", "C"); var code1 = $$""" namespace [|{{declaredNamespace}}|] { class Class1 { } } """; var code2 = $$""" using System; using {{declaredNamespace}}; using Class1Alias = {{declaredNamespace}}.Class1; namespace Foo { class RefClass { private Class1Alias c1; } } """; var fixed1 = $$""" namespace {{DefaultNamespace}}.A.B.C { class Class1 { } } """; var fixed2 = $$""" using System; using Class1Alias = {{DefaultNamespace}}.A.B.C.Class1; namespace Foo { class RefClass { private Class1Alias c1; } } """; var originalSources = new[] { (Path.Combine(folder, "Class1.cs"), code1), ("Class2.cs", code2) }; var fixedSources = new[] { (Path.Combine(folder, "Class1.cs"), fixed1), ("Class2.cs", fixed2) }; await RunTestAsync(originalSources, fixedSources); } [Fact] public async Task FixAll() { var declaredNamespace = "Bar.Baz"; var folder1 = CreateFolderPath("A", "B", "C"); var fixedNamespace1 = $"{DefaultNamespace}.A.B.C"; var folder2 = CreateFolderPath("Second", "Folder", "Path"); var fixedNamespace2 = $"{DefaultNamespace}.Second.Folder.Path"; var folder3 = CreateFolderPath("Third", "Folder", "Path"); var fixedNamespace3 = $"{DefaultNamespace}.Third.Folder.Path"; var code1 = $$""" namespace [|{{declaredNamespace}}|] { class Class1 { Class2 C2 { get; } Class3 C3 { get; } } } """; var fixed1 = $$""" using {{fixedNamespace2}}; using {{fixedNamespace3}}; namespace {{fixedNamespace1}} { class Class1 { Class2 C2 { get; } Class3 C3 { get; } } } """; var code2 = $$""" namespace [|{{declaredNamespace}}|] { class Class2 { Class1 C1 { get; } Class3 C3 { get; } } } """; var fixed2 = $$""" using {{fixedNamespace1}}; using {{fixedNamespace3}}; namespace {{fixedNamespace2}} { class Class2 { Class1 C1 { get; } Class3 C3 { get; } } } """; var code3 = $$""" namespace [|{{declaredNamespace}}|] { class Class3 { Class1 C1 { get; } Class2 C2 { get; } } } """; var fixed3 = $$""" using {{fixedNamespace1}}; using {{fixedNamespace2}}; namespace {{fixedNamespace3}} { class Class3 { Class1 C1 { get; } Class2 C2 { get; } } } """; var sources = new[] { (Path.Combine(folder1, "Class1.cs"), code1), (Path.Combine(folder2, "Class2.cs"), code2), (Path.Combine(folder3, "Class3.cs"), code3), }; var fixedSources = new[] { (Path.Combine(folder1, "Class1.cs"), fixed1), (Path.Combine(folder2, "Class2.cs"), fixed2), (Path.Combine(folder3, "Class3.cs"), fixed3), }; await RunTestAsync(sources, fixedSources); } [Fact] public async Task FixAll_MultipleProjects() { var declaredNamespace = "Bar.Baz"; var folder1 = CreateFolderPath("A", "B", "C"); var fixedNamespace1 = $"{DefaultNamespace}.A.B.C"; var folder2 = CreateFolderPath("Second", "Folder", "Path"); var fixedNamespace2 = $"{DefaultNamespace}.Second.Folder.Path"; var folder3 = CreateFolderPath("Third", "Folder", "Path"); var fixedNamespace3 = $"{DefaultNamespace}.Third.Folder.Path"; var code1 = $$""" namespace [|{{declaredNamespace}}|] { public class Class1 { Class2 C2 { get; } Class3 C3 { get; } } } """; var fixed1 = $$""" using {{fixedNamespace2}}; using {{fixedNamespace3}}; namespace {{fixedNamespace1}} { public class Class1 { Class2 C2 { get; } Class3 C3 { get; } } } """; var code2 = $$""" namespace [|{{declaredNamespace}}|] { class Class2 { Class1 C1 { get; } Class3 C3 { get; } } } """; var fixed2 = $$""" using {{fixedNamespace1}}; using {{fixedNamespace3}}; namespace {{fixedNamespace2}} { class Class2 { Class1 C1 { get; } Class3 C3 { get; } } } """; var code3 = $$""" namespace [|{{declaredNamespace}}|] { class Class3 { Class1 C1 { get; } Class2 C2 { get; } } } """; var fixed3 = $$""" using {{fixedNamespace1}}; using {{fixedNamespace2}}; namespace {{fixedNamespace3}} { class Class3 { Class1 C1 { get; } Class2 C2 { get; } } } """; var project2Directory = "/Project2/"; var project2folder = Path.Combine(project2Directory, "A", "B", "C"); var project2EditorConfig = $""" is_global=true build_property.ProjectDir = {project2Directory} build_property.RootNamespace = {DefaultNamespace} """; var project2Source = $$""" using {{declaredNamespace}}; namespace [|Project2.Test|] { class P { Class1 _c1; } } """; var project2FixedSource = $$""" namespace {{fixedNamespace1}} { class P { Class1 _c1; } } """; var testState = new VerifyCS.Test { EditorConfig = EditorConfig, CodeFixTestBehaviors = CodeAnalysis.Testing.CodeFixTestBehaviors.SkipFixAllInDocumentCheck | CodeAnalysis.Testing.CodeFixTestBehaviors.SkipFixAllInProjectCheck, LanguageVersion = LanguageVersion.CSharp10, TestState = { Sources = { (Path.Combine(folder1, "Class1.cs"), code1), (Path.Combine(folder2, "Class2.cs"), code2), (Path.Combine(folder3, "Class3.cs"), code3), }, AdditionalProjects = { ["Project2"] = { AdditionalProjectReferences = { "TestProject" }, Sources = { (Path.Combine(project2folder, "P.cs"), project2Source) }, AnalyzerConfigFiles = { (Path.Combine(project2Directory, ".editorconfig"), project2EditorConfig) }, }, }, }, FixedState = { Sources = { (Path.Combine(folder1, "Class1.cs"), fixed1), (Path.Combine(folder2, "Class2.cs"), fixed2), (Path.Combine(folder3, "Class3.cs"), fixed3), }, AdditionalProjects = { ["Project2"] = { AdditionalProjectReferences = { "TestProject" }, Sources = { (Path.Combine(project2folder, "P.cs"), project2FixedSource) }, AnalyzerConfigFiles = { (Path.Combine(project2Directory, ".editorconfig"), project2EditorConfig) }, } } } }; testState.SolutionTransforms.Add((solution, projectId) => { foreach (var id in solution.ProjectIds) { var project = solution.GetRequiredProject(id); solution = project.WithDefaultNamespace(DefaultNamespace).Solution; } return solution; }); await testState.RunAsync(); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/58372")] public async Task InvalidProjectName_ChangeNamespace() { var defaultNamespace = "Invalid-Namespace"; var editorConfig = $""" is_global=true build_property.ProjectDir = {Directory} build_property.RootNamespace = {defaultNamespace} """; var folder = CreateFolderPath(["B", "C"]); // The project name is invalid so the default namespace is not prepended var fixedCode = $$""" namespace B.C { class Class1 { } } """; await RunTestAsync( "Class1.cs", fileContents: """ namespace [|A.B|] { class Class1 { } } """, fixedCode: fixedCode, directory: folder, editorConfig: editorConfig, defaultNamespace: defaultNamespace); } [Fact] public async Task InvalidProjectName_DocumentAtRoot_ChangeNamespace() { var defaultNamespace = "Invalid-Namespace"; var editorConfig = $""" is_global=true build_property.ProjectDir = {Directory} build_property.RootNamespace = {defaultNamespace} """; var folder = CreateFolderPath(); await RunTestAsync( "Class1.cs", fileContents: $$""" namespace Test.Code { class C { } } """, directory: folder, editorConfig: editorConfig, defaultNamespace: defaultNamespace); } [Fact] public async Task InvalidRootNamespace_DocumentAtRoot_ChangeNamespace() { var editorConfig = $""" is_global=true build_property.ProjectDir = {Directory} build_property.RootNamespace = Test.Code # not an editorconfig comment even though it looks like one """; var folder = CreateFolderPath(); await RunTestAsync( "Class1.cs", fileContents: $$""" namespace Test.Code { class C { } } """, directory: folder, editorConfig: editorConfig, defaultNamespace: "Invalid-Namespace"); } }