Skip to content

Commit

Permalink
Merge pull request #2140 from otac0n/multi-file-code-fix-verification
Browse files Browse the repository at this point in the history
Multi-file code fix verification
  • Loading branch information
sharwell committed Jul 14, 2016
2 parents f044621 + 4170c61 commit 088e573
Show file tree
Hide file tree
Showing 4 changed files with 290 additions and 104 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ namespace TestHelper
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.Simplification;
using Microsoft.CodeAnalysis.Text;

/// <summary>
/// Diagnostic Producer class with extra methods dealing with applying code fixes.
Expand All @@ -23,16 +24,16 @@ public abstract partial class CodeFixVerifier : DiagnosticVerifier
/// Apply the inputted <see cref="CodeAction"/> to the inputted document.
/// Meant to be used to apply code fixes.
/// </summary>
/// <param name="document">The <see cref="Document"/> to apply the fix on</param>
/// <param name="project">The <see cref="Project"/> to apply the fix on</param>
/// <param name="codeAction">A <see cref="CodeAction"/> that will be applied to the
/// <paramref name="document"/>.</param>
/// <paramref name="project"/>.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that the task will observe.</param>
/// <returns>A <see cref="Document"/> with the changes from the <see cref="CodeAction"/>.</returns>
private static async Task<Document> ApplyFixAsync(Document document, CodeAction codeAction, CancellationToken cancellationToken)
/// <returns>A <see cref="Project"/> with the changes from the <see cref="CodeAction"/>.</returns>
private static async Task<Project> ApplyFixAsync(Project project, CodeAction codeAction, CancellationToken cancellationToken)
{
var operations = await codeAction.GetOperationsAsync(cancellationToken).ConfigureAwait(false);
var solution = operations.OfType<ApplyChangesOperation>().Single().ChangedSolution;
return solution.GetDocument(document.Id);
return solution.GetProject(project.Id);
}

/// <summary>
Expand Down Expand Up @@ -75,13 +76,20 @@ private static IEnumerable<Diagnostic> GetNewDiagnostics(IEnumerable<Diagnostic>
/// <summary>
/// Get the existing compiler diagnostics on the input document.
/// </summary>
/// <param name="document">The <see cref="Document"/> to run the compiler diagnostic analyzers on.</param>
/// <param name="project">The <see cref="Project"/> to run the compiler diagnostic analyzers on.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that the task will observe.</param>
/// <returns>The compiler diagnostics that were found in the code.</returns>
private static async Task<ImmutableArray<Diagnostic>> GetCompilerDiagnosticsAsync(Document document, CancellationToken cancellationToken)
private static async Task<ImmutableArray<Diagnostic>> GetCompilerDiagnosticsAsync(Project project, CancellationToken cancellationToken)
{
var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
return semanticModel.GetDiagnostics(cancellationToken: cancellationToken);
var allDiagnostics = ImmutableArray.Create<Diagnostic>();

foreach (var document in project.Documents)
{
var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
allDiagnostics = allDiagnostics.AddRange(semanticModel.GetDiagnostics(cancellationToken: cancellationToken));
}

return allDiagnostics;
}

/// <summary>
Expand All @@ -97,5 +105,49 @@ private static async Task<string> GetStringFromDocumentAsync(Document document,
var sourceText = await formatted.GetTextAsync(cancellationToken).ConfigureAwait(false);
return sourceText.ToString();
}

/// <summary>
/// Implements a workaround for issue #936, force re-parsing to get the same sort of syntax tree as the original document.
/// </summary>
/// <param name="project">The project to update.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/>.</param>
/// <returns>The updated <see cref="Project"/>.</returns>
private static async Task<Project> RecreateProjectDocumentsAsync(Project project, CancellationToken cancellationToken)
{
foreach (var documentId in project.DocumentIds)
{
var document = project.GetDocument(documentId);
document = await RecreateDocumentAsync(document, cancellationToken).ConfigureAwait(false);
project = document.Project;
}

return project;
}

private static async Task<Document> RecreateDocumentAsync(Document document, CancellationToken cancellationToken)
{
var newText = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);
newText = newText.WithChanges(new TextChange(new TextSpan(0, 0), " "));
newText = newText.WithChanges(new TextChange(new TextSpan(0, 1), string.Empty));
return document.WithText(newText);
}

/// <summary>
/// Formats the whitespace in all documents of the specified <see cref="Project"/>.
/// </summary>
/// <param name="project">The project to update.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/>.</param>
/// <returns>The updated <see cref="Project"/>.</returns>
private static async Task<Project> ReformatProjectDocumentsAsync(Project project, CancellationToken cancellationToken)
{
foreach (var documentId in project.DocumentIds)
{
var document = project.GetDocument(documentId);
document = await Formatter.FormatAsync(document, Formatter.Annotation, cancellationToken: cancellationToken).ConfigureAwait(false);
project = document.Project;
}

return project;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

namespace StyleCop.Analyzers.Test.MaintainabilityRules
{
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using TestHelper;
Expand All @@ -20,7 +21,10 @@ public async Task TestOneElementAsync()
var testCode = @"%1 Foo
{
}";
await this.VerifyCSharpDiagnosticAsync(testCode.Replace("%1", this.Keyword), EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);

testCode = testCode.Replace("%1", this.Keyword);

await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
}

[Fact]
Expand All @@ -32,18 +36,29 @@ public async Task TestTwoElementsAsync()
%1 Bar
{
}";
var fixedCode = @"%1 Foo

var fixedCode = new[]
{
@"%1 Foo
{
}
";
",
@"%1 Bar
{
}"
};

testCode = testCode.Replace("%1", this.Keyword);
fixedCode = fixedCode.Select(c => c.Replace("%1", this.Keyword)).ToArray();

DiagnosticResult expected = this.CSharpDiagnostic().WithLocation(4, this.Keyword.Length + 2);

await this.VerifyCSharpDiagnosticAsync(testCode.Replace("%1", this.Keyword), expected, CancellationToken.None).ConfigureAwait(false);
await this.VerifyCSharpDiagnosticAsync(fixedCode.Replace("%1", this.Keyword), EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);

if (this.SupportsCodeFix)
{
await this.VerifyCSharpFixAsync(testCode.Replace("%1", this.Keyword), fixedCode.Replace("%1", this.Keyword), cancellationToken: CancellationToken.None).ConfigureAwait(false);
await this.VerifyCSharpFixAsync(new[] { testCode }, fixedCode, cancellationToken: CancellationToken.None).ConfigureAwait(false);
}
}

Expand All @@ -59,22 +74,37 @@ public async Task TestThreeElementsAsync()
%1 FooBar
{
}";
var fixedCode = @"%1 Foo

var fixedCode = new[]
{
@"%1 Foo
{
}
",
@"%1 Bar
{
}
";
",
@"%1 FooBar
{
}"
};

testCode = testCode.Replace("%1", this.Keyword);
fixedCode = fixedCode.Select(code => code.Replace("%1", this.Keyword)).ToArray();

DiagnosticResult[] expected =
{
this.CSharpDiagnostic().WithLocation(4, this.Keyword.Length + 2),
this.CSharpDiagnostic().WithLocation(7, this.Keyword.Length + 2)
};
{
this.CSharpDiagnostic().WithLocation(4, this.Keyword.Length + 2),
this.CSharpDiagnostic().WithLocation(7, this.Keyword.Length + 2)
};

await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);

await this.VerifyCSharpDiagnosticAsync(testCode.Replace("%1", this.Keyword), expected, CancellationToken.None).ConfigureAwait(false);
await this.VerifyCSharpDiagnosticAsync(fixedCode.Replace("%1", this.Keyword), EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
if (this.SupportsCodeFix)
{
await this.VerifyCSharpFixAsync(testCode.Replace("%1", this.Keyword), fixedCode.Replace("%1", this.Keyword), cancellationToken: CancellationToken.None).ConfigureAwait(false);
await this.VerifyCSharpFixAsync(new[] { testCode }, fixedCode, cancellationToken: CancellationToken.None).ConfigureAwait(false);
}
}

Expand All @@ -89,18 +119,31 @@ public async Task TestRemoveWarningSuppressionAsync()
#pragma warning disable SomeWarning
#pragma warning restore SomeWarning
}";
var fixedCode = @"%1 Foo

var fixedCode = new[]
{
@"%1 Foo
{
}
";
",
@"%1 Bar
{
#pragma warning disable SomeWarning
#pragma warning restore SomeWarning
}"
};

testCode = testCode.Replace("%1", this.Keyword);
fixedCode = fixedCode.Select(code => code.Replace("%1", this.Keyword)).ToArray();

DiagnosticResult expected = this.CSharpDiagnostic().WithLocation(4, this.Keyword.Length + 2);

await this.VerifyCSharpDiagnosticAsync(testCode.Replace("%1", this.Keyword), expected, CancellationToken.None).ConfigureAwait(false);
await this.VerifyCSharpDiagnosticAsync(fixedCode.Replace("%1", this.Keyword), EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);

if (this.SupportsCodeFix)
{
await this.VerifyCSharpFixAsync(testCode.Replace("%1", this.Keyword), fixedCode.Replace("%1", this.Keyword), cancellationToken: CancellationToken.None).ConfigureAwait(false);
await this.VerifyCSharpFixAsync(new[] { testCode }, fixedCode, cancellationToken: CancellationToken.None).ConfigureAwait(false);
}
}

Expand All @@ -116,18 +159,30 @@ public async Task TestPreserveWarningSuppressionAsync()
}";

// See https://github.com/dotnet/roslyn/issues/3999
var fixedCode = @"%1 Foo
var fixedCode = new[]
{
@"%1 Foo
{
}
";
",
@"%1 Bar
{
#pragma warning disable SomeWarning
}"
};

DiagnosticResult expected = this.CSharpDiagnostic().WithLocation(4, this.Keyword.Length + 2);

await this.VerifyCSharpDiagnosticAsync(testCode.Replace("%1", this.Keyword), expected, CancellationToken.None).ConfigureAwait(false);
await this.VerifyCSharpDiagnosticAsync(fixedCode.Replace("%1", this.Keyword), EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);

foreach (var code in fixedCode)
{
await this.VerifyCSharpDiagnosticAsync(code.Replace("%1", this.Keyword), EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
}

if (this.SupportsCodeFix)
{
await this.VerifyCSharpFixAsync(testCode.Replace("%1", this.Keyword), fixedCode.Replace("%1", this.Keyword), cancellationToken: CancellationToken.None).ConfigureAwait(false);
await this.VerifyCSharpFixAsync(new[] { testCode.Replace("%1", this.Keyword) }, fixedCode.Select(c => c.Replace("%1", this.Keyword)).ToArray(), cancellationToken: CancellationToken.None).ConfigureAwait(false);
}
}

Expand All @@ -142,18 +197,31 @@ public async Task TestRemovePreprocessorDirectivesAsync()
#if true
#endif
}";
var fixedCode = @"%1 Foo

var fixedCode = new[]
{
@"%1 Foo
{
}
";
",
@"%1 Bar
{
#if true
#endif
}"
};

testCode = testCode.Replace("%1", this.Keyword);
fixedCode = fixedCode.Select(code => code.Replace("%1", this.Keyword)).ToArray();

DiagnosticResult expected = this.CSharpDiagnostic().WithLocation(4, this.Keyword.Length + 2);

await this.VerifyCSharpDiagnosticAsync(testCode.Replace("%1", this.Keyword), expected, CancellationToken.None).ConfigureAwait(false);
await this.VerifyCSharpDiagnosticAsync(fixedCode.Replace("%1", this.Keyword), EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);

if (this.SupportsCodeFix)
{
await this.VerifyCSharpFixAsync(testCode.Replace("%1", this.Keyword), fixedCode.Replace("%1", this.Keyword), cancellationToken: CancellationToken.None).ConfigureAwait(false);
await this.VerifyCSharpFixAsync(new[] { testCode }, fixedCode, cancellationToken: CancellationToken.None).ConfigureAwait(false);
}
}

Expand All @@ -170,21 +238,34 @@ public async Task TestPreservePreprocessorDirectivesAsync()
}";

// See https://github.com/dotnet/roslyn/issues/3999
var fixedCode = @"%1 Foo
var fixedCode = new[]
{
@"%1 Foo
{
#if true
}
#endif
";
",
@"
#if true
%1 Bar
{
#endif
}"
};

testCode = testCode.Replace("%1", this.Keyword);
fixedCode = fixedCode.Select(code => code.Replace("%1", this.Keyword)).ToArray();

DiagnosticResult expected = this.CSharpDiagnostic().WithLocation(5, this.Keyword.Length + 2);

await this.VerifyCSharpDiagnosticAsync(testCode.Replace("%1", this.Keyword), expected, CancellationToken.None).ConfigureAwait(false);
await this.VerifyCSharpDiagnosticAsync(fixedCode.Replace("%1", this.Keyword), EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);

if (this.SupportsCodeFix)
{
await this.VerifyCSharpFixAsync(testCode.Replace("%1", this.Keyword), fixedCode.Replace("%1", this.Keyword), cancellationToken: CancellationToken.None).ConfigureAwait(false);
await this.VerifyCSharpFixAsync(new[] { testCode }, fixedCode, cancellationToken: CancellationToken.None).ConfigureAwait(false);
}
}
}
Expand Down
Loading

0 comments on commit 088e573

Please sign in to comment.