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

Exclude blank files from analysis and add tests for exclusion rules #1044

Merged
merged 4 commits into from
Jul 26, 2015
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 86 additions & 0 deletions StyleCop.Analyzers/StyleCop.Analyzers.Test/ExclusionTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
namespace StyleCop.Analyzers.Test
{
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Diagnostics;
using TestHelper;
using Xunit;

/// <summary>
/// Unit tests for testing exclusion of auto generated files.
/// </summary>
public class ExclusionTests : CodeFixVerifier
{
/// <summary>
/// Gets the statements that will be used in the theory test cases.
/// </summary>
/// <value>
/// The statements that will be used in the theory test cases.
/// </value>
public static IEnumerable<object[]> ShouldBeExcluded
{
get
{
yield return new[] { "Test.cs", string.Empty };
yield return new[] { "Test.cs", " " };
yield return new[] { "Test.cs", "\r\n\r\n" };
yield return new[] { "Test.cs", "\t" };
yield return new[] { "Test.designer.cs", "class Foo { }" };
yield return new[] { "Test.cs", "// <auto-generated" };
yield return new[] { "Test.cs", "// <autogenerated" };
}
}

/// <summary>
/// Gets the statements that will be used in the theory test cases.
/// </summary>
/// <value>
/// The statements that will be used in the theory test cases.
/// </value>
public static IEnumerable<object[]> ShouldNotBeExcluded
{
get
{
yield return new[] { "Test.designerr.cs", "class Foo { }" };
yield return new[] { "Test.cs", "// <auto--generated" };
yield return new[] { "Test.cs", "/// <auto-generated" };
yield return new[] { "Test.cs", "\t\r\n class Foo { }" };
}
}

/// <summary>
/// Verifies that the source file is excluded from analysis.
/// </summary>
/// <param name="filename">The filename</param>
/// <param name="testCode">The code to test</param>
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
[Theory]
[MemberData(nameof(ShouldBeExcluded))]
public async Task TestIsExcludedAsync(string filename, string testCode)
{
await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None, filename: filename).ConfigureAwait(false);
}

/// <summary>
/// Verifies that the source file is not excluded from analysis.
/// </summary>
/// <param name="filename">The filename</param>
/// <param name="testCode">The code to test</param>
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
[Theory]
[MemberData(nameof(ShouldNotBeExcluded))]
public async Task TestIsNotExcludedAsync(string filename, string testCode)
{
var result = this.CSharpDiagnostic().WithLocation(filename, 1, 1);

await this.VerifyCSharpDiagnosticAsync(testCode, result, CancellationToken.None, filename: filename).ConfigureAwait(false);
}

/// <inheritdoc/>
protected override IEnumerable<DiagnosticAnalyzer> GetCSharpDiagnosticAnalyzers()
{
yield return new ExclusionTestAnalyzer();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -161,5 +161,17 @@ public DiagnosticResult WithLocation(string path, int line, int column)
result.locations[result.locations.Length - 1] = new DiagnosticResultLocation(path, line, column);
return result;
}

public DiagnosticResult WithLineOffset(int offset)
{
DiagnosticResult result = this;
Array.Resize(ref result.locations, result.locations?.Length ?? 0);
for (int i = 0; i < result.locations.Length; i++)
{
result.locations[i].Line += offset;
}

return result;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,12 @@ public abstract partial class DiagnosticVerifier
/// <see cref="LanguageNames"/> class.</param>
/// <param name="analyzers">The analyzers to be run on the sources.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that the task will observe.</param>
/// <param name="filenames">The filenames or null if the default filename should be used</param>
/// <returns>A collection of <see cref="Diagnostic"/>s that surfaced in the source code, sorted by
/// <see cref="Diagnostic.Location"/>.</returns>
private Task<ImmutableArray<Diagnostic>> GetSortedDiagnosticsAsync(string[] sources, string language, ImmutableArray<DiagnosticAnalyzer> analyzers, CancellationToken cancellationToken)
private Task<ImmutableArray<Diagnostic>> GetSortedDiagnosticsAsync(string[] sources, string language, ImmutableArray<DiagnosticAnalyzer> analyzers, CancellationToken cancellationToken, string[] filenames)
{
return GetSortedDiagnosticsFromDocumentsAsync(analyzers, this.GetDocuments(sources, language), cancellationToken);
return GetSortedDiagnosticsFromDocumentsAsync(analyzers, this.GetDocuments(sources, language, filenames), cancellationToken);
}

/// <summary>
Expand Down Expand Up @@ -135,20 +136,16 @@ private static Diagnostic[] SortDistinctDiagnostics(IEnumerable<Diagnostic> diag
/// <param name="sources">Classes in the form of strings.</param>
/// <param name="language">The language the source classes are in. Values may be taken from the
/// <see cref="LanguageNames"/> class.</param>
/// <param name="filenames">The filenames or null if the default filename should be used</param>
/// <returns>A collection of <see cref="Document"/>s representing the sources.</returns>
private Document[] GetDocuments(string[] sources, string language)
private Document[] GetDocuments(string[] sources, string language, string[] filenames)
{
if (language != LanguageNames.CSharp && language != LanguageNames.VisualBasic)
{
throw new ArgumentException("Unsupported Language");
}

for (int i = 0; i < sources.Length; i++)
{
string fileName = language == LanguageNames.CSharp ? "Test" + i + ".cs" : "Test" + i + ".vb";
}

var project = this.CreateProject(sources, language);
var project = this.CreateProject(sources, language, filenames);
var documents = project.Documents.ToArray();

if (sources.Length != documents.Length)
Expand Down Expand Up @@ -177,9 +174,10 @@ protected Document CreateDocument(string source, string language = LanguageNames
/// <param name="sources">Classes in the form of strings.</param>
/// <param name="language">The language the source classes are in. Values may be taken from the
/// <see cref="LanguageNames"/> class.</param>
/// <param name="filenames">The filenames or null if the default filename should be used</param>
/// <returns>A <see cref="Project"/> created out of the <see cref="Document"/>s created from the source
/// strings.</returns>
private Project CreateProject(string[] sources, string language = LanguageNames.CSharp)
private Project CreateProject(string[] sources, string language = LanguageNames.CSharp, string[] filenames = null)
{
string fileNamePrefix = DefaultFilePathPrefix;
string fileExt = language == LanguageNames.CSharp ? CSharpDefaultFileExt : VisualBasicDefaultExt;
Expand All @@ -188,9 +186,10 @@ private Project CreateProject(string[] sources, string language = LanguageNames.
var solution = this.CreateSolution(projectId, language);

int count = 0;
foreach (var source in sources)
for (int i = 0; i < sources.Length; i++)
{
var newFileName = fileNamePrefix + count + "." + fileExt;
string source = sources[i];
var newFileName = filenames?[i] ?? fileNamePrefix + count + "." + fileExt;
var documentId = DocumentId.CreateNewId(projectId, debugName: newFileName);
solution = solution.AddDocument(documentId, newFileName, SourceText.From(source));
count++;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
namespace TestHelper
{
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Text;
using StyleCop.Analyzers;

/// <summary>
/// A analyzer that will report a diagnostic at the start of the code file if the
/// file is not excluded from code analysis.
/// </summary>
[DiagnosticAnalyzer(LanguageNames.CSharp)]
internal class ExclusionTestAnalyzer : DiagnosticAnalyzer
{
internal const string DiagnosticId = "SA9999";
private const string Title = "Exclusion test";
private const string MessageFormat = "Exclusion test";
private const string Description = "Exclusion test";

private static readonly DiagnosticDescriptor Descriptor =
new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, "TestRules", DiagnosticSeverity.Warning, true, Description);

private static readonly ImmutableArray<DiagnosticDescriptor> SupportedDiagnosticsValue =
ImmutableArray.Create(Descriptor);

/// <inheritdoc/>
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics
{
get
{
return SupportedDiagnosticsValue;
}
}

/// <inheritdoc/>
public override void Initialize(AnalysisContext context)
{
context.RegisterSyntaxTreeActionHonorExclusions(this.AnalyzeTree);
}

private void AnalyzeTree(SyntaxTreeAnalysisContext context)
{
// Report a diagnostic if we got called
context.ReportDiagnostic(Diagnostic.Create(Descriptor, context.Tree.GetLocation(TextSpan.FromBounds(0, 0))));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@
<Compile Include="Helpers\CodeFixVerifier.Helper.cs" />
<Compile Include="Helpers\DiagnosticResult.cs" />
<Compile Include="Helpers\DiagnosticVerifier.Helper.cs" />
<Compile Include="Helpers\ExclusionTestAnalyzer.cs" />
<Compile Include="LayoutRules\SA1500\SA1500UnitTests.Blocks.cs" />
<Compile Include="LayoutRules\SA1500\SA1500UnitTests.Classes.cs" />
<Compile Include="LayoutRules\SA1500\SA1500UnitTests.Constructors.cs" />
Expand Down Expand Up @@ -179,6 +180,7 @@
<Compile Include="LayoutRules\SA1502\SA1502UnitTests.Namespaces.cs" />
<Compile Include="LayoutRules\SA1502\SA1502UnitTests.Properties.cs" />
<Compile Include="LayoutRules\SA1502\SA1502UnitTests.TypeDeclarations.cs" />
<Compile Include="ExclusionTests.cs" />
<Compile Include="LayoutRules\SA1503UnitTests.cs" />
<Compile Include="LayoutRules\SA1504UnitTests.cs" />
<Compile Include="LayoutRules\SA1505UnitTests.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,11 @@ public abstract partial class DiagnosticVerifier
/// <param name="expected">A <see cref="DiagnosticResult"/>s describing the <see cref="Diagnostic"/> that should
/// be reported by the analyzer for the specified source.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that the task will observe.</param>
/// <param name="filename">The filename or null if the default filename should be used</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
protected Task VerifyCSharpDiagnosticAsync(string source, DiagnosticResult expected, CancellationToken cancellationToken)
protected Task VerifyCSharpDiagnosticAsync(string source, DiagnosticResult expected, CancellationToken cancellationToken, string filename = null)
{
return this.VerifyCSharpDiagnosticAsync(source, new[] { expected }, cancellationToken);
return this.VerifyCSharpDiagnosticAsync(source, new[] { expected }, cancellationToken, filename);
}

/// <summary>
Expand All @@ -51,10 +52,11 @@ protected Task VerifyCSharpDiagnosticAsync(string source, DiagnosticResult expec
/// <param name="expected">A collection of <see cref="DiagnosticResult"/>s describing the
/// <see cref="Diagnostic"/>s that should be reported by the analyzer for the specified source.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that the task will observe.</param>
/// <param name="filename">The filename or null if the default filename should be used</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
protected Task VerifyCSharpDiagnosticAsync(string source, DiagnosticResult[] expected, CancellationToken cancellationToken)
protected Task VerifyCSharpDiagnosticAsync(string source, DiagnosticResult[] expected, CancellationToken cancellationToken, string filename = null)
{
return this.VerifyDiagnosticsAsync(new[] { source }, LanguageNames.CSharp, this.GetCSharpDiagnosticAnalyzers().ToImmutableArray(), expected, cancellationToken);
return this.VerifyDiagnosticsAsync(new[] { source }, LanguageNames.CSharp, this.GetCSharpDiagnosticAnalyzers().ToImmutableArray(), expected, cancellationToken, new[] { filename });
}

/// <summary>
Expand All @@ -68,10 +70,11 @@ protected Task VerifyCSharpDiagnosticAsync(string source, DiagnosticResult[] exp
/// <param name="expected">A collection of <see cref="DiagnosticResult"/>s describing the
/// <see cref="Diagnostic"/>s that should be reported by the analyzer for the specified sources.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that the task will observe.</param>
/// <param name="filenames">The filenames or null if the default filename should be used</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
protected Task VerifyCSharpDiagnosticAsync(string[] sources, DiagnosticResult[] expected, CancellationToken cancellationToken)
protected Task VerifyCSharpDiagnosticAsync(string[] sources, DiagnosticResult[] expected, CancellationToken cancellationToken, string[] filenames = null)
{
return this.VerifyDiagnosticsAsync(sources, LanguageNames.CSharp, this.GetCSharpDiagnosticAnalyzers().ToImmutableArray(), expected, cancellationToken);
return this.VerifyDiagnosticsAsync(sources, LanguageNames.CSharp, this.GetCSharpDiagnosticAnalyzers().ToImmutableArray(), expected, cancellationToken, filenames);
}

/// <summary>
Expand All @@ -84,11 +87,28 @@ protected Task VerifyCSharpDiagnosticAsync(string[] sources, DiagnosticResult[]
/// <param name="expected">A collection of <see cref="DiagnosticResult"/>s that should appear after the analyzer
/// is run on the sources.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that the task will observe.</param>
/// <param name="filenames">The filenames or null if the default filename should be used</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
private async Task VerifyDiagnosticsAsync(string[] sources, string language, ImmutableArray<DiagnosticAnalyzer> analyzers, DiagnosticResult[] expected, CancellationToken cancellationToken)
private async Task VerifyDiagnosticsAsync(string[] sources, string language, ImmutableArray<DiagnosticAnalyzer> analyzers, DiagnosticResult[] expected, CancellationToken cancellationToken, string[] filenames)
{
var diagnostics = await this.GetSortedDiagnosticsAsync(sources, language, analyzers, cancellationToken).ConfigureAwait(false);
VerifyDiagnosticResults(diagnostics, analyzers, expected);
VerifyDiagnosticResults(await this.GetSortedDiagnosticsAsync(sources, language, analyzers, cancellationToken, filenames).ConfigureAwait(false), analyzers, expected);

// If filenames is null we want to test for exclusions too
if (filenames == null)
{
// Also check if the analyzer honors exclusions
if (expected.Any(x => x.Id.StartsWith("SA") || x.Id.StartsWith("SX")))
{
// We want to look at non-stylecop diagnostics only. We also insert a new line at the beginning
// so we have to move all diagnostic location down by one line
var expectedResults = expected
.Where(x => !x.Id.StartsWith("SA") && !x.Id.StartsWith("SX"))
.Select(x => x.WithLineOffset(1))
.ToArray();

VerifyDiagnosticResults(await this.GetSortedDiagnosticsAsync(sources.Select(x => " // <auto-generated>\r\n" + x).ToArray(), language, analyzers, cancellationToken, null).ConfigureAwait(false), analyzers, expectedResults);
}
}
}

/// <summary>
Expand All @@ -103,7 +123,7 @@ private async Task VerifyDiagnosticsAsync(string[] sources, string language, Imm
/// <param name="analyzers">The analyzers that have been run on the sources.</param>
/// <param name="expectedResults">A collection of <see cref="DiagnosticResult"/>s describing the expected
/// diagnostics for the sources.</param>
private static void VerifyDiagnosticResults(IEnumerable<Diagnostic> actualResults, ImmutableArray<DiagnosticAnalyzer> analyzers, params DiagnosticResult[] expectedResults)
private static void VerifyDiagnosticResults(IEnumerable<Diagnostic> actualResults, ImmutableArray<DiagnosticAnalyzer> analyzers, DiagnosticResult[] expectedResults)
{
int expectedCount = expectedResults.Count();
int actualCount = actualResults.Count();
Expand Down
Loading