From b65cd06d46d5d143b284842a36d941a4828ed61d Mon Sep 17 00:00:00 2001 From: Joey Robichaud Date: Tue, 8 Dec 2020 15:28:40 -0800 Subject: [PATCH 1/2] Add unit test for code style fixer formatting --- src/Analyzers/AnalyzerFormatter.cs | 14 ++++ src/CodeFormatter.cs | 4 +- .../CodeStyleAnalyzerFormatterTests.cs | 57 +++++++++++++++++ tests/Formatters/AbstractFormatterTests.cs | 64 ++++++++----------- tests/Formatters/CSharpFormatterTests.cs | 3 + 5 files changed, 104 insertions(+), 38 deletions(-) create mode 100644 tests/Analyzers/CodeStyleAnalyzerFormatterTests.cs diff --git a/src/Analyzers/AnalyzerFormatter.cs b/src/Analyzers/AnalyzerFormatter.cs index 2676e3a549..07bed02a8c 100644 --- a/src/Analyzers/AnalyzerFormatter.cs +++ b/src/Analyzers/AnalyzerFormatter.cs @@ -16,6 +16,20 @@ namespace Microsoft.CodeAnalysis.Tools.Analyzers { internal class AnalyzerFormatter : ICodeFormatter { + public static AnalyzerFormatter CodeStyleFormatter => new AnalyzerFormatter( + Resources.Code_Style, + FixCategory.CodeStyle, + new CodeStyleInformationProvider(), + new AnalyzerRunner(), + new SolutionCodeFixApplier()); + + public static AnalyzerFormatter ThirdPartyFormatter => new AnalyzerFormatter( + Resources.Analyzer_Reference, + FixCategory.Analyzers, + new AnalyzerReferenceInformationProvider(), + new AnalyzerRunner(), + new SolutionCodeFixApplier()); + private readonly string _name; private readonly IAnalyzerInformationProvider _informationProvider; private readonly IAnalyzerRunner _runner; diff --git a/src/CodeFormatter.cs b/src/CodeFormatter.cs index 86bace4ce8..e69e55dc04 100644 --- a/src/CodeFormatter.cs +++ b/src/CodeFormatter.cs @@ -25,8 +25,8 @@ internal static class CodeFormatter new CharsetFormatter(), new OrganizeImportsFormatter(), new UnnecessaryImportsFormatter(), - new AnalyzerFormatter(Resources.Code_Style, FixCategory.CodeStyle, new CodeStyleInformationProvider(), new AnalyzerRunner(), new SolutionCodeFixApplier()), - new AnalyzerFormatter(Resources.Analyzer_Reference, FixCategory.Analyzers, new AnalyzerReferenceInformationProvider(), new AnalyzerRunner(), new SolutionCodeFixApplier())); + AnalyzerFormatter.CodeStyleFormatter, + AnalyzerFormatter.ThirdPartyFormatter); public static async Task FormatWorkspaceAsync( FormatOptions formatOptions, diff --git a/tests/Analyzers/CodeStyleAnalyzerFormatterTests.cs b/tests/Analyzers/CodeStyleAnalyzerFormatterTests.cs new file mode 100644 index 0000000000..9cd2bdc2a0 --- /dev/null +++ b/tests/Analyzers/CodeStyleAnalyzerFormatterTests.cs @@ -0,0 +1,57 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Tools.Analyzers; +using Microsoft.CodeAnalysis.Tools.Formatters; +using Microsoft.CodeAnalysis.Tools.Tests.Formatters; +using Xunit; + +namespace Microsoft.CodeAnalysis.Tools.Tests.Analyzers +{ + public class CodeStyleAnalyzerFormatterTests : CSharpFormatterTests + { + private protected override ICodeFormatter Formatter => AnalyzerFormatter.CodeStyleFormatter; + + [Fact] + public async Task TestUseVarCodeStyle_AppliesWhenNotUsingVar() + { + var testCode = @" +using System.Collections.Generic; + +class C +{ + void M() + { + object obj = new object(); + List list = new List(); + int count = 5; + } +}"; + + var expectedCode = @" +using System.Collections.Generic; + +class C +{ + void M() + { + var obj = new object(); + var list = new List(); + var count = 5; + } +}"; + + var editorConfig = new Dictionary() + { + /// Prefer "var" everywhere + ["dotnet_diagnostic.IDE0007.severity"] = "error", + ["csharp_style_var_for_built_in_types"] = "true:error", + ["csharp_style_var_when_type_is_apparent"] = "true:error", + ["csharp_style_var_elsewhere"] = "true:error", + }; + + await AssertCodeChangedAsync(testCode, expectedCode, editorConfig, fixCategory: FixCategory.CodeStyle); + } + } +} diff --git a/tests/Formatters/AbstractFormatterTests.cs b/tests/Formatters/AbstractFormatterTests.cs index 12949fd4e4..8d4e895dd3 100644 --- a/tests/Formatters/AbstractFormatterTests.cs +++ b/tests/Formatters/AbstractFormatterTests.cs @@ -9,7 +9,6 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Testing; using Microsoft.CodeAnalysis.Text; using Microsoft.CodeAnalysis.Tools.Formatters; @@ -23,12 +22,6 @@ namespace Microsoft.CodeAnalysis.Tools.Tests.Formatters { public abstract class AbstractFormatterTest { - private static MetadataReference CorlibReference => MetadataReference.CreateFromFile(typeof(object).Assembly.Location).WithAliases(ImmutableArray.Create("global", "corlib")); - private static MetadataReference SystemReference => MetadataReference.CreateFromFile(typeof(System.Diagnostics.Debug).Assembly.Location).WithAliases(ImmutableArray.Create("global", "system")); - private static MetadataReference SystemCoreReference => MetadataReference.CreateFromFile(typeof(Enumerable).Assembly.Location); - private static MetadataReference CodeAnalysisReference => MetadataReference.CreateFromFile(typeof(Compilation).Assembly.Location); - - private static MetadataReference SystemCollectionsImmutableReference => MetadataReference.CreateFromFile(typeof(ImmutableArray).Assembly.Location); private static MetadataReference MicrosoftVisualBasicReference => MetadataReference.CreateFromFile(typeof(Microsoft.VisualBasic.Strings).Assembly.Location); private static Lazy ExportProviderFactory { get; } @@ -49,6 +42,8 @@ static AbstractFormatterTest() LazyThreadSafetyMode.ExecutionAndPublication); } + protected virtual ReferenceAssemblies ReferenceAssemblies => ReferenceAssemblies.Default; + protected virtual string DefaultFilePathPrefix => "Test"; protected virtual string DefaultTestProjectName => "TestProject"; @@ -59,7 +54,7 @@ static AbstractFormatterTest() protected virtual string DefaultTestProjectPath => Path.Combine(DefaultFolderPath, $"{DefaultTestProjectName}.{DefaultFileExt}proj"); - protected virtual string DefaultEditorConfigPath => Path.Combine(DefaultFolderPath + ".editorconfig"); + protected virtual string DefaultEditorConfigPath => Path.Combine(DefaultFolderPath, ".editorconfig"); protected virtual string DefaultFilePath => Path.Combine(DefaultFolderPath, $"{DefaultFilePathPrefix}0.{DefaultFileExt}"); @@ -77,7 +72,7 @@ protected AbstractFormatterTest() /// public abstract string Language { get; } - private static ILogger Logger => new TestLogger(); + private static TestLogger Logger => new TestLogger(); public SolutionState TestState { get; } @@ -129,7 +124,7 @@ protected AbstractFormatterTest() var fileMatcher = SourceFileMatcher.CreateMatcher(new[] { document.FilePath }, exclude: Array.Empty()); var formatOptions = new FormatOptions( workspaceFilePath: project.FilePath, - workspaceType: WorkspaceType.Folder, + workspaceType: WorkspaceType.Solution, logLevel: LogLevel.Trace, fixCategory, codeStyleSeverity, @@ -174,12 +169,6 @@ protected AbstractFormatterTest() /// public Dictionary XmlReferences { get; } = new Dictionary(); - /// - /// Gets a collection of transformation functions to apply to during diagnostic - /// or code fix test setup. - /// - public List> OptionsTransforms { get; } = new List>(); - /// /// Given an array of strings as sources and a language, turn them into a and return the /// solution. @@ -252,7 +241,7 @@ protected virtual Project CreateProjectImpl((string filename, SourceText content { (var newFileName, var source) = sources[i]; var documentId = DocumentId.CreateNewId(projectId, debugName: newFileName); - solution = solution.AddDocument(documentId, newFileName, source, filePath: Path.Combine(DefaultTestProjectPath, newFileName)); + solution = solution.AddDocument(documentId, newFileName, source, filePath: Path.Combine(DefaultFolderPath, newFileName)); } for (var i = 0; i < additionalFiles.Length; i++) @@ -274,15 +263,18 @@ protected virtual Project CreateProjectImpl((string filename, SourceText content /// The created solution. protected virtual Solution CreateSolution(ProjectId projectId, string language, SourceText editorConfigText) { - var compilationOptions = CreateCompilationOptions(); - var xmlReferenceResolver = new TestXmlReferenceResolver(); foreach (var xmlReference in XmlReferences) { xmlReferenceResolver.XmlReferences.Add(xmlReference.Key, xmlReference.Value); } - compilationOptions = compilationOptions.WithXmlReferenceResolver(xmlReferenceResolver); + var compilationOptions = CreateCompilationOptions() + .WithXmlReferenceResolver(xmlReferenceResolver) + .WithAssemblyIdentityComparer(ReferenceAssemblies.AssemblyIdentityComparer); + + var parseOptions = CreateParseOptions(); + var referenceAssemblies = ReferenceAssemblies.ResolveAsync(language, CancellationToken.None).GetAwaiter().GetResult(); var editorConfigDocument = DocumentInfo.Create( DocumentId.CreateNewId(projectId, DefaultEditorConfigPath), @@ -290,32 +282,30 @@ protected virtual Solution CreateSolution(ProjectId projectId, string language, loader: TextLoader.From(TextAndVersion.Create(editorConfigText, VersionStamp.Create())), filePath: DefaultEditorConfigPath); - var projectInfo = ProjectInfo.Create(projectId, VersionStamp.Create(), DefaultTestProjectName, DefaultTestProjectName, language, filePath: DefaultTestProjectPath) + var projectInfo = ProjectInfo.Create( + projectId, + VersionStamp.Create(), + name: DefaultTestProjectName, + assemblyName: DefaultTestProjectName, + language, + filePath: DefaultTestProjectPath, + outputFilePath: Path.ChangeExtension(DefaultTestProjectPath, "dll"), + compilationOptions: compilationOptions, + parseOptions: parseOptions, + metadataReferences: referenceAssemblies, + isSubmission: false) + .WithDefaultNamespace(DefaultTestProjectName) .WithAnalyzerConfigDocuments(ImmutableArray.Create(editorConfigDocument)); var solution = CreateWorkspace() .CurrentSolution - .AddProject(projectInfo) - .WithProjectCompilationOptions(projectId, compilationOptions) - .AddMetadataReference(projectId, CorlibReference) - .AddMetadataReference(projectId, SystemReference) - .AddMetadataReference(projectId, SystemCoreReference) - .AddMetadataReference(projectId, CodeAnalysisReference) - .AddMetadataReference(projectId, SystemCollectionsImmutableReference); + .AddProject(projectInfo); if (language == LanguageNames.VisualBasic) { solution = solution.AddMetadataReference(projectId, MicrosoftVisualBasicReference); } - foreach (var transform in OptionsTransforms) - { - solution.Workspace.TryApplyChanges(solution.WithOptions(transform(solution.Workspace.Options))); - } - - var parseOptions = solution.GetProject(projectId).ParseOptions; - solution = solution.WithProjectParseOptions(projectId, parseOptions.WithDocumentationMode(DocumentationMode.Diagnose)); - return solution; } @@ -327,5 +317,7 @@ public virtual AdhocWorkspace CreateWorkspace() } protected abstract CompilationOptions CreateCompilationOptions(); + + protected abstract ParseOptions CreateParseOptions(); } } diff --git a/tests/Formatters/CSharpFormatterTests.cs b/tests/Formatters/CSharpFormatterTests.cs index 255a0422b2..1c77af21c0 100644 --- a/tests/Formatters/CSharpFormatterTests.cs +++ b/tests/Formatters/CSharpFormatterTests.cs @@ -12,5 +12,8 @@ public abstract class CSharpFormatterTests : AbstractFormatterTest protected override CompilationOptions CreateCompilationOptions() => new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, allowUnsafe: true); + + protected override ParseOptions CreateParseOptions() + => new CSharpParseOptions(LanguageVersion.Default, DocumentationMode.Diagnose); } } From 589aa8abec174467aa0b6efc79e2eb2316cf80ef Mon Sep 17 00:00:00 2001 From: Joey Robichaud Date: Wed, 9 Dec 2020 13:01:46 -0800 Subject: [PATCH 2/2] Always specify .NETStandard 2.0 references --- tests/Analyzers/FilterDiagnosticsTests.cs | 8 ++++---- tests/Formatters/AbstractFormatterTests.cs | 24 +++++++++++----------- tests/Formatters/FormattedFilesTests.cs | 2 +- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/tests/Analyzers/FilterDiagnosticsTests.cs b/tests/Analyzers/FilterDiagnosticsTests.cs index f504014376..00c5c88524 100644 --- a/tests/Analyzers/FilterDiagnosticsTests.cs +++ b/tests/Analyzers/FilterDiagnosticsTests.cs @@ -19,7 +19,7 @@ public class FilterDiagnosticsTests : CSharpFormatterTests [Fact] public async Task TestFilterWarning() { - var solution = GetSolution(); + var solution = await GetSolutionAsync(); var projectAnalyzersAndFixers = await GetProjectAnalyzersAndFixersAsync(solution); var project = solution.Projects.First(); var formattablePaths = ImmutableHashSet.Create(project.Documents.First().FilePath); @@ -37,7 +37,7 @@ public async Task TestFilterWarning() [Fact] public async Task TestFilterError() { - var solution = GetSolution(); + var solution = await GetSolutionAsync(); var projectAnalyzersAndFixers = await GetProjectAnalyzersAndFixersAsync(solution); var project = solution.Projects.First(); var formattablePaths = ImmutableHashSet.Create(project.Documents.First().FilePath); @@ -64,12 +64,12 @@ private static async Task GetAnalyzersAndFixersAsync() return AnalyzerFinderHelpers.LoadAnalyzersAndFixers(assemblies); } - private Solution GetSolution() + private Task GetSolutionAsync() { var text = SourceText.From(""); TestState.Sources.Add(text); - return GetSolution( + return GetSolutionAsync( TestState.Sources.ToArray(), TestState.AdditionalFiles.ToArray(), TestState.AdditionalReferences.ToArray(), diff --git a/tests/Formatters/AbstractFormatterTests.cs b/tests/Formatters/AbstractFormatterTests.cs index 8d4e895dd3..bc5551fa79 100644 --- a/tests/Formatters/AbstractFormatterTests.cs +++ b/tests/Formatters/AbstractFormatterTests.cs @@ -42,7 +42,7 @@ static AbstractFormatterTest() LazyThreadSafetyMode.ExecutionAndPublication); } - protected virtual ReferenceAssemblies ReferenceAssemblies => ReferenceAssemblies.Default; + protected virtual ReferenceAssemblies ReferenceAssemblies => ReferenceAssemblies.NetStandard.NetStandard20; protected virtual string DefaultFilePathPrefix => "Test"; @@ -117,7 +117,7 @@ protected AbstractFormatterTest() var text = SourceText.From(testCode, encoding ?? Encoding.UTF8); TestState.Sources.Add(text); - var solution = GetSolution(TestState.Sources.ToArray(), TestState.AdditionalFiles.ToArray(), TestState.AdditionalReferences.ToArray(), editorConfig); + var solution = await GetSolutionAsync(TestState.Sources.ToArray(), TestState.AdditionalFiles.ToArray(), TestState.AdditionalReferences.ToArray(), editorConfig); var project = solution.Projects.Single(); var document = project.Documents.Single(); @@ -178,9 +178,9 @@ protected AbstractFormatterTest() /// Additional metadata references to include in the project. /// The .editorconfig to apply to this solution. /// A solution containing a project with the specified sources and additional files. - private protected Solution GetSolution((string filename, SourceText content)[] sources, (string filename, SourceText content)[] additionalFiles, MetadataReference[] additionalMetadataReferences, IReadOnlyDictionary editorConfig) + private protected async Task GetSolutionAsync((string filename, SourceText content)[] sources, (string filename, SourceText content)[] additionalFiles, MetadataReference[] additionalMetadataReferences, IReadOnlyDictionary editorConfig) { - return GetSolution(sources, additionalFiles, additionalMetadataReferences, ToEditorConfig(editorConfig)); + return await GetSolutionAsync(sources, additionalFiles, additionalMetadataReferences, ToEditorConfig(editorConfig)); } /// @@ -192,9 +192,9 @@ private protected Solution GetSolution((string filename, SourceText content)[] s /// Additional metadata references to include in the project. /// The .editorconfig to apply to this solution. /// A solution containing a project with the specified sources and additional files. - private protected Solution GetSolution((string filename, SourceText content)[] sources, (string filename, SourceText content)[] additionalFiles, MetadataReference[] additionalMetadataReferences, string editorConfig) + private protected async Task GetSolutionAsync((string filename, SourceText content)[] sources, (string filename, SourceText content)[] additionalFiles, MetadataReference[] additionalMetadataReferences, string editorConfig) { - var project = CreateProject(sources, additionalFiles, additionalMetadataReferences, Language, SourceText.From(editorConfig, Encoding.UTF8)); + var project = await CreateProjectAsync(sources, additionalFiles, additionalMetadataReferences, Language, SourceText.From(editorConfig, Encoding.UTF8)); return project.Solution; } @@ -213,10 +213,10 @@ private protected Solution GetSolution((string filename, SourceText content)[] s /// The .editorconfig to apply to this project. /// A created out of the s created from the source /// strings. - protected Project CreateProject((string filename, SourceText content)[] sources, (string filename, SourceText content)[] additionalFiles, MetadataReference[] additionalMetadataReferences, string language, SourceText editorConfigText) + protected async Task CreateProjectAsync((string filename, SourceText content)[] sources, (string filename, SourceText content)[] additionalFiles, MetadataReference[] additionalMetadataReferences, string language, SourceText editorConfigText) { language ??= Language; - return CreateProjectImpl(sources, additionalFiles, additionalMetadataReferences, language, editorConfigText); + return await CreateProjectImplAsync(sources, additionalFiles, additionalMetadataReferences, language, editorConfigText); } /// @@ -230,10 +230,10 @@ protected Project CreateProject((string filename, SourceText content)[] sources, /// The .editorconfig to apply to this project. /// A created out of the s created from the source /// strings. - protected virtual Project CreateProjectImpl((string filename, SourceText content)[] sources, (string filename, SourceText content)[] additionalFiles, MetadataReference[] additionalMetadataReferences, string language, SourceText editorConfigText) + protected virtual async Task CreateProjectImplAsync((string filename, SourceText content)[] sources, (string filename, SourceText content)[] additionalFiles, MetadataReference[] additionalMetadataReferences, string language, SourceText editorConfigText) { var projectId = ProjectId.CreateNewId(debugName: DefaultTestProjectName); - var solution = CreateSolution(projectId, language, editorConfigText); + var solution = await CreateSolutionAsync(projectId, language, editorConfigText); solution = solution.AddMetadataReferences(projectId, additionalMetadataReferences); @@ -261,7 +261,7 @@ protected virtual Project CreateProjectImpl((string filename, SourceText content /// The language for which the solution is being created. /// The .editorconfig to apply to this solution. /// The created solution. - protected virtual Solution CreateSolution(ProjectId projectId, string language, SourceText editorConfigText) + protected virtual async Task CreateSolutionAsync(ProjectId projectId, string language, SourceText editorConfigText) { var xmlReferenceResolver = new TestXmlReferenceResolver(); foreach (var xmlReference in XmlReferences) @@ -274,7 +274,7 @@ protected virtual Solution CreateSolution(ProjectId projectId, string language, .WithAssemblyIdentityComparer(ReferenceAssemblies.AssemblyIdentityComparer); var parseOptions = CreateParseOptions(); - var referenceAssemblies = ReferenceAssemblies.ResolveAsync(language, CancellationToken.None).GetAwaiter().GetResult(); + var referenceAssemblies = await ReferenceAssemblies.ResolveAsync(language, CancellationToken.None); var editorConfigDocument = DocumentInfo.Create( DocumentId.CreateNewId(projectId, DefaultEditorConfigPath), diff --git a/tests/Formatters/FormattedFilesTests.cs b/tests/Formatters/FormattedFilesTests.cs index 2f0c1ac4a4..eec4c2482a 100644 --- a/tests/Formatters/FormattedFilesTests.cs +++ b/tests/Formatters/FormattedFilesTests.cs @@ -49,7 +49,7 @@ private async Task> TestFormattedFiles(string testCode) var text = SourceText.From(testCode, Encoding.UTF8); TestState.Sources.Add(text); - var solution = GetSolution(TestState.Sources.ToArray(), TestState.AdditionalFiles.ToArray(), TestState.AdditionalReferences.ToArray(), EditorConfig); + var solution = await GetSolutionAsync(TestState.Sources.ToArray(), TestState.AdditionalFiles.ToArray(), TestState.AdditionalReferences.ToArray(), EditorConfig); var project = solution.Projects.Single(); var document = project.Documents.Single();