Skip to content

Commit

Permalink
Merge pull request #893 from JoeRobich/add-codestyle-test
Browse files Browse the repository at this point in the history
Add unit test for code style fixer formatting
  • Loading branch information
JoeRobich committed Dec 9, 2020
2 parents d6abf72 + 589aa8a commit 392654c
Show file tree
Hide file tree
Showing 7 changed files with 119 additions and 53 deletions.
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

0 comments on commit 392654c

Please sign in to comment.