Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add unit test for code style fixer formatting #893

Merged
merged 2 commits into from Dec 9, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
14 changes: 14 additions & 0 deletions src/Analyzers/AnalyzerFormatter.cs
Expand Up @@ -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;
Expand Down
4 changes: 2 additions & 2 deletions src/CodeFormatter.cs
Expand Up @@ -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<WorkspaceFormatResult> FormatWorkspaceAsync(
FormatOptions formatOptions,
Expand Down
57 changes: 57 additions & 0 deletions 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<string> list = new List<string>();
int count = 5;
}
}";

var expectedCode = @"
using System.Collections.Generic;

class C
{
void M()
{
var obj = new object();
var list = new List<string>();
var count = 5;
}
}";

var editorConfig = new Dictionary<string, string>()
{
/// 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);
}
}
}
8 changes: 4 additions & 4 deletions tests/Analyzers/FilterDiagnosticsTests.cs
Expand Up @@ -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);
Expand All @@ -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);
Expand All @@ -64,12 +64,12 @@ private static async Task<AnalyzersAndFixers> GetAnalyzersAndFixersAsync()
return AnalyzerFinderHelpers.LoadAnalyzersAndFixers(assemblies);
}

private Solution GetSolution()
private Task<Solution> GetSolutionAsync()
{
var text = SourceText.From("");
TestState.Sources.Add(text);

return GetSolution(
return GetSolutionAsync(
TestState.Sources.ToArray(),
TestState.AdditionalFiles.ToArray(),
TestState.AdditionalReferences.ToArray(),
Expand Down
84 changes: 38 additions & 46 deletions tests/Formatters/AbstractFormatterTests.cs
Expand Up @@ -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;
Expand All @@ -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<IExportProviderFactory> ExportProviderFactory { get; }
Expand All @@ -49,6 +42,8 @@ static AbstractFormatterTest()
LazyThreadSafetyMode.ExecutionAndPublication);
}

protected virtual ReferenceAssemblies ReferenceAssemblies => ReferenceAssemblies.NetStandard.NetStandard20;

protected virtual string DefaultFilePathPrefix => "Test";

protected virtual string DefaultTestProjectName => "TestProject";
Expand All @@ -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}");

Expand All @@ -77,7 +72,7 @@ protected AbstractFormatterTest()
/// </summary>
public abstract string Language { get; }

private static ILogger Logger => new TestLogger();
private static TestLogger Logger => new TestLogger();

public SolutionState TestState { get; }

Expand Down Expand Up @@ -122,14 +117,14 @@ 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();

var fileMatcher = SourceFileMatcher.CreateMatcher(new[] { document.FilePath }, exclude: Array.Empty<string>());
var formatOptions = new FormatOptions(
workspaceFilePath: project.FilePath,
workspaceType: WorkspaceType.Folder,
workspaceType: WorkspaceType.Solution,
logLevel: LogLevel.Trace,
fixCategory,
codeStyleSeverity,
Expand Down Expand Up @@ -174,12 +169,6 @@ protected AbstractFormatterTest()
/// </remarks>
public Dictionary<string, string> XmlReferences { get; } = new Dictionary<string, string>();

/// <summary>
/// Gets a collection of transformation functions to apply to <see cref="Workspace.Options"/> during diagnostic
/// or code fix test setup.
/// </summary>
public List<Func<OptionSet, OptionSet>> OptionsTransforms { get; } = new List<Func<OptionSet, OptionSet>>();

/// <summary>
/// Given an array of strings as sources and a language, turn them into a <see cref="Project"/> and return the
/// solution.
Expand All @@ -189,9 +178,9 @@ protected AbstractFormatterTest()
/// <param name="additionalMetadataReferences">Additional metadata references to include in the project.</param>
/// <param name="editorConfig">The .editorconfig to apply to this solution.</param>
/// <returns>A solution containing a project with the specified sources and additional files.</returns>
private protected Solution GetSolution((string filename, SourceText content)[] sources, (string filename, SourceText content)[] additionalFiles, MetadataReference[] additionalMetadataReferences, IReadOnlyDictionary<string, string> editorConfig)
private protected async Task<Solution> GetSolutionAsync((string filename, SourceText content)[] sources, (string filename, SourceText content)[] additionalFiles, MetadataReference[] additionalMetadataReferences, IReadOnlyDictionary<string, string> editorConfig)
{
return GetSolution(sources, additionalFiles, additionalMetadataReferences, ToEditorConfig(editorConfig));
return await GetSolutionAsync(sources, additionalFiles, additionalMetadataReferences, ToEditorConfig(editorConfig));
}

/// <summary>
Expand All @@ -203,9 +192,9 @@ private protected Solution GetSolution((string filename, SourceText content)[] s
/// <param name="additionalMetadataReferences">Additional metadata references to include in the project.</param>
/// <param name="editorConfig">The .editorconfig to apply to this solution.</param>
/// <returns>A solution containing a project with the specified sources and additional files.</returns>
private protected Solution GetSolution((string filename, SourceText content)[] sources, (string filename, SourceText content)[] additionalFiles, MetadataReference[] additionalMetadataReferences, string editorConfig)
private protected async Task<Solution> 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;
}

Expand All @@ -224,10 +213,10 @@ private protected Solution GetSolution((string filename, SourceText content)[] s
/// <param name="editorConfigText">The .editorconfig to apply to this project.</param>
/// <returns>A <see cref="Project"/> created out of the <see cref="Document"/>s created from the source
/// strings.</returns>
protected Project CreateProject((string filename, SourceText content)[] sources, (string filename, SourceText content)[] additionalFiles, MetadataReference[] additionalMetadataReferences, string language, SourceText editorConfigText)
protected async Task<Project> 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);
}

/// <summary>
Expand All @@ -241,18 +230,18 @@ protected Project CreateProject((string filename, SourceText content)[] sources,
/// <param name="editorConfigText">The .editorconfig to apply to this project.</param>
/// <returns>A <see cref="Project"/> created out of the <see cref="Document"/>s created from the source
/// strings.</returns>
protected virtual Project CreateProjectImpl((string filename, SourceText content)[] sources, (string filename, SourceText content)[] additionalFiles, MetadataReference[] additionalMetadataReferences, string language, SourceText editorConfigText)
protected virtual async Task<Project> 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);

for (var i = 0; i < sources.Length; i++)
{
(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++)
Expand All @@ -272,50 +261,51 @@ protected virtual Project CreateProjectImpl((string filename, SourceText content
/// <param name="language">The language for which the solution is being created.</param>
/// <param name="editorConfigText">The .editorconfig to apply to this solution.</param>
/// <returns>The created solution.</returns>
protected virtual Solution CreateSolution(ProjectId projectId, string language, SourceText editorConfigText)
protected virtual async Task<Solution> CreateSolutionAsync(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 = await ReferenceAssemblies.ResolveAsync(language, CancellationToken.None);

var editorConfigDocument = DocumentInfo.Create(
DocumentId.CreateNewId(projectId, DefaultEditorConfigPath),
name: DefaultEditorConfigPath,
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;
}

Expand All @@ -327,5 +317,7 @@ public virtual AdhocWorkspace CreateWorkspace()
}

protected abstract CompilationOptions CreateCompilationOptions();

protected abstract ParseOptions CreateParseOptions();
}
}
3 changes: 3 additions & 0 deletions tests/Formatters/CSharpFormatterTests.cs
Expand Up @@ -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);
}
}
2 changes: 1 addition & 1 deletion tests/Formatters/FormattedFilesTests.cs
Expand Up @@ -49,7 +49,7 @@ private async Task<List<FormattedFile>> 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();

Expand Down