diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test/ExclusionTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test/ExclusionTests.cs
new file mode 100644
index 000000000..62502a792
--- /dev/null
+++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test/ExclusionTests.cs
@@ -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;
+
+ ///
+ /// Unit tests for testing exclusion of auto generated files.
+ ///
+ public class ExclusionTests : CodeFixVerifier
+ {
+ ///
+ /// Gets the statements that will be used in the theory test cases.
+ ///
+ ///
+ /// The statements that will be used in the theory test cases.
+ ///
+ public static IEnumerable 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", "//
+ /// Gets the statements that will be used in the theory test cases.
+ ///
+ ///
+ /// The statements that will be used in the theory test cases.
+ ///
+ public static IEnumerable ShouldNotBeExcluded
+ {
+ get
+ {
+ yield return new[] { "Test.designerr.cs", "class Foo { }" };
+ yield return new[] { "Test.cs", "//
+ /// Verifies that the source file is excluded from analysis.
+ ///
+ /// The filename
+ /// The code to test
+ /// A representing the asynchronous unit test.
+ [Theory]
+ [MemberData(nameof(ShouldBeExcluded))]
+ public async Task TestIsExcludedAsync(string filename, string testCode)
+ {
+ await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None, filename: filename).ConfigureAwait(false);
+ }
+
+ ///
+ /// Verifies that the source file is not excluded from analysis.
+ ///
+ /// The filename
+ /// The code to test
+ /// A representing the asynchronous unit test.
+ [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);
+ }
+
+ ///
+ protected override IEnumerable GetCSharpDiagnosticAnalyzers()
+ {
+ yield return new ExclusionTestAnalyzer();
+ }
+ }
+}
diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test/Helpers/DiagnosticResult.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test/Helpers/DiagnosticResult.cs
index 4c45ec619..b058d0fad 100644
--- a/StyleCop.Analyzers/StyleCop.Analyzers.Test/Helpers/DiagnosticResult.cs
+++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test/Helpers/DiagnosticResult.cs
@@ -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;
+ }
}
}
\ No newline at end of file
diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test/Helpers/DiagnosticVerifier.Helper.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test/Helpers/DiagnosticVerifier.Helper.cs
index dea4c45de..2fea3d7d7 100644
--- a/StyleCop.Analyzers/StyleCop.Analyzers.Test/Helpers/DiagnosticVerifier.Helper.cs
+++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test/Helpers/DiagnosticVerifier.Helper.cs
@@ -40,11 +40,12 @@ public abstract partial class DiagnosticVerifier
/// class.
/// The analyzers to be run on the sources.
/// The that the task will observe.
+ /// The filenames or null if the default filename should be used
/// A collection of s that surfaced in the source code, sorted by
/// .
- private Task> GetSortedDiagnosticsAsync(string[] sources, string language, ImmutableArray analyzers, CancellationToken cancellationToken)
+ private Task> GetSortedDiagnosticsAsync(string[] sources, string language, ImmutableArray analyzers, CancellationToken cancellationToken, string[] filenames)
{
- return GetSortedDiagnosticsFromDocumentsAsync(analyzers, this.GetDocuments(sources, language), cancellationToken);
+ return GetSortedDiagnosticsFromDocumentsAsync(analyzers, this.GetDocuments(sources, language, filenames), cancellationToken);
}
///
@@ -135,20 +136,16 @@ private static Diagnostic[] SortDistinctDiagnostics(IEnumerable diag
/// Classes in the form of strings.
/// The language the source classes are in. Values may be taken from the
/// class.
+ /// The filenames or null if the default filename should be used
/// A collection of s representing the sources.
- 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)
@@ -177,9 +174,10 @@ protected Document CreateDocument(string source, string language = LanguageNames
/// Classes in the form of strings.
/// The language the source classes are in. Values may be taken from the
/// class.
+ /// The filenames or null if the default filename should be used
/// A created out of the s created from the source
/// strings.
- 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;
@@ -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++;
diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test/Helpers/ExclusionTestAnalyzer.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test/Helpers/ExclusionTestAnalyzer.cs
new file mode 100644
index 000000000..ed69662c9
--- /dev/null
+++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test/Helpers/ExclusionTestAnalyzer.cs
@@ -0,0 +1,48 @@
+namespace TestHelper
+{
+ using System.Collections.Immutable;
+ using Microsoft.CodeAnalysis;
+ using Microsoft.CodeAnalysis.Diagnostics;
+ using Microsoft.CodeAnalysis.Text;
+ using StyleCop.Analyzers;
+
+ ///
+ /// A analyzer that will report a diagnostic at the start of the code file if the
+ /// file is not excluded from code analysis.
+ ///
+ [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 SupportedDiagnosticsValue =
+ ImmutableArray.Create(Descriptor);
+
+ ///
+ public override ImmutableArray SupportedDiagnostics
+ {
+ get
+ {
+ return SupportedDiagnosticsValue;
+ }
+ }
+
+ ///
+ 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))));
+ }
+ }
+}
diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test/StyleCop.Analyzers.Test.csproj b/StyleCop.Analyzers/StyleCop.Analyzers.Test/StyleCop.Analyzers.Test.csproj
index 338f23102..db24f891b 100644
--- a/StyleCop.Analyzers/StyleCop.Analyzers.Test/StyleCop.Analyzers.Test.csproj
+++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test/StyleCop.Analyzers.Test.csproj
@@ -146,6 +146,7 @@
+
@@ -179,6 +180,7 @@
+
diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test/Verifiers/DiagnosticVerifier.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test/Verifiers/DiagnosticVerifier.cs
index 436e2aad0..6c014e907 100644
--- a/StyleCop.Analyzers/StyleCop.Analyzers.Test/Verifiers/DiagnosticVerifier.cs
+++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test/Verifiers/DiagnosticVerifier.cs
@@ -35,10 +35,11 @@ public abstract partial class DiagnosticVerifier
/// A s describing the that should
/// be reported by the analyzer for the specified source.
/// The that the task will observe.
+ /// The filename or null if the default filename should be used
/// A representing the asynchronous operation.
- 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);
}
///
@@ -51,10 +52,11 @@ protected Task VerifyCSharpDiagnosticAsync(string source, DiagnosticResult expec
/// A collection of s describing the
/// s that should be reported by the analyzer for the specified source.
/// The that the task will observe.
+ /// The filename or null if the default filename should be used
/// A representing the asynchronous operation.
- 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 });
}
///
@@ -68,10 +70,11 @@ protected Task VerifyCSharpDiagnosticAsync(string source, DiagnosticResult[] exp
/// A collection of s describing the
/// s that should be reported by the analyzer for the specified sources.
/// The that the task will observe.
+ /// The filenames or null if the default filename should be used
/// A representing the asynchronous operation.
- 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);
}
///
@@ -84,11 +87,28 @@ protected Task VerifyCSharpDiagnosticAsync(string[] sources, DiagnosticResult[]
/// A collection of s that should appear after the analyzer
/// is run on the sources.
/// The that the task will observe.
+ /// The filenames or null if the default filename should be used
/// A representing the asynchronous operation.
- private async Task VerifyDiagnosticsAsync(string[] sources, string language, ImmutableArray analyzers, DiagnosticResult[] expected, CancellationToken cancellationToken)
+ private async Task VerifyDiagnosticsAsync(string[] sources, string language, ImmutableArray 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 => " // \r\n" + x).ToArray(), language, analyzers, cancellationToken, null).ConfigureAwait(false), analyzers, expectedResults);
+ }
+ }
}
///
@@ -103,7 +123,7 @@ private async Task VerifyDiagnosticsAsync(string[] sources, string language, Imm
/// The analyzers that have been run on the sources.
/// A collection of s describing the expected
/// diagnostics for the sources.
- private static void VerifyDiagnosticResults(IEnumerable actualResults, ImmutableArray analyzers, params DiagnosticResult[] expectedResults)
+ private static void VerifyDiagnosticResults(IEnumerable actualResults, ImmutableArray analyzers, DiagnosticResult[] expectedResults)
{
int expectedCount = expectedResults.Count();
int actualCount = actualResults.Count();
diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/AnalyzerExtensions.cs b/StyleCop.Analyzers/StyleCop.Analyzers/AnalyzerExtensions.cs
index 6ffcdcdba..8d412bf7b 100644
--- a/StyleCop.Analyzers/StyleCop.Analyzers/AnalyzerExtensions.cs
+++ b/StyleCop.Analyzers/StyleCop.Analyzers/AnalyzerExtensions.cs
@@ -8,9 +8,20 @@ namespace StyleCop.Analyzers
using System;
using Microsoft.CodeAnalysis.Diagnostics;
- internal static class AnalyzerExtensions
+ ///
+ /// Provides extension methods to deal for analyzers.
+ ///
+ public static class AnalyzerExtensions
{
- internal static void RegisterSyntaxTreeActionHonorExclusions(this AnalysisContext context, Action action)
+ ///
+ /// Register an action to be executed at completion of parsing of a code document.
+ /// A syntax tree action reports Microsoft.CodeAnalysis.Diagnostics about the Microsoft.CodeAnalysis.SyntaxTree
+ /// of a document.
+ ///
+ /// This method honors exclusions
+ /// The analysis context.
+ /// Action to be executed at completion of parsing of a document.
+ public static void RegisterSyntaxTreeActionHonorExclusions(this AnalysisContext context, Action action)
{
context.RegisterSyntaxTreeAction(
c =>
@@ -28,7 +39,15 @@ internal static void RegisterSyntaxTreeActionHonorExclusions(this AnalysisContex
});
}
- internal static void RegisterSyntaxTreeActionHonorExclusions(this CompilationStartAnalysisContext context, Action action)
+ ///
+ /// Register an action to be executed at completion of parsing of a code document.
+ /// A syntax tree action reports Microsoft.CodeAnalysis.Diagnostics about the Microsoft.CodeAnalysis.SyntaxTree
+ /// of a document.
+ ///
+ /// This method honors exclusions
+ /// The analysis context.
+ /// Action to be executed at completion of parsing of a document.
+ public static void RegisterSyntaxTreeActionHonorExclusions(this CompilationStartAnalysisContext context, Action action)
{
context.RegisterSyntaxTreeAction(
c =>
@@ -46,7 +65,18 @@ internal static void RegisterSyntaxTreeActionHonorExclusions(this CompilationSta
});
}
- internal static void RegisterSyntaxNodeActionHonorExclusions(this AnalysisContext context, Action action, params TLanguageKindEnum[] syntaxKinds) where TLanguageKindEnum : struct
+ ///
+ /// Register an action to be executed at completion of semantic analysis of a Microsoft.CodeAnalysis.SyntaxNode
+ /// with an appropriate Kind. A syntax node action can report Microsoft.CodeAnalysis.Diagnostics
+ /// about Microsoft.CodeAnalysis.SyntaxNodes, and can also collect state information
+ /// to be used by other syntax node actions or code block end actions.
+ ///
+ /// This method honors exclusions
+ /// Action will be executed only if a Microsoft.CodeAnalysis.SyntaxNode's Kind matches one of the syntax kind values.
+ /// Action to be executed at completion of semantic analysis of a Microsoft.CodeAnalysis.SyntaxNode.
+ /// The kinds of syntax that should be analyzed.
+ /// Enum type giving the syntax node kinds of the source language for which the action applies.
+ public static void RegisterSyntaxNodeActionHonorExclusions(this AnalysisContext context, Action action, params TLanguageKindEnum[] syntaxKinds) where TLanguageKindEnum : struct
{
context.RegisterSyntaxNodeAction(
c =>
@@ -65,7 +95,18 @@ internal static void RegisterSyntaxNodeActionHonorExclusions(
syntaxKinds);
}
- internal static void RegisterSyntaxNodeActionHonorExclusions(this CompilationStartAnalysisContext context, Action action, params TLanguageKindEnum[] syntaxKinds) where TLanguageKindEnum : struct
+ ///
+ /// Register an action to be executed at completion of semantic analysis of a Microsoft.CodeAnalysis.SyntaxNode
+ /// with an appropriate Kind. A syntax node action can report Microsoft.CodeAnalysis.Diagnostics
+ /// about Microsoft.CodeAnalysis.SyntaxNodes, and can also collect state information
+ /// to be used by other syntax node actions or code block end actions.
+ ///
+ /// This method honors exclusions
+ /// Action will be executed only if a Microsoft.CodeAnalysis.SyntaxNode's Kind matches one of the syntax kind values.
+ /// Action to be executed at completion of semantic analysis of a Microsoft.CodeAnalysis.SyntaxNode.
+ /// The kinds of syntax that should be analyzed.
+ /// Enum type giving the syntax node kinds of the source language for which the action applies.
+ public static void RegisterSyntaxNodeActionHonorExclusions(this CompilationStartAnalysisContext context, Action action, params TLanguageKindEnum[] syntaxKinds) where TLanguageKindEnum : struct
{
context.RegisterSyntaxNodeAction(
c =>
diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/DocumentationRules/SA1651DoNotUsePlaceholderElements.cs b/StyleCop.Analyzers/StyleCop.Analyzers/DocumentationRules/SA1651DoNotUsePlaceholderElements.cs
index 106406d01..38394b934 100644
--- a/StyleCop.Analyzers/StyleCop.Analyzers/DocumentationRules/SA1651DoNotUsePlaceholderElements.cs
+++ b/StyleCop.Analyzers/StyleCop.Analyzers/DocumentationRules/SA1651DoNotUsePlaceholderElements.cs
@@ -57,8 +57,8 @@ public override ImmutableArray SupportedDiagnostics
///
public override void Initialize(AnalysisContext context)
{
- context.RegisterSyntaxNodeAction(this.HandleXmlElement, SyntaxKind.XmlElement);
- context.RegisterSyntaxNodeAction(this.HandleXmlEmptyElement, SyntaxKind.XmlEmptyElement);
+ context.RegisterSyntaxNodeActionHonorExclusions(this.HandleXmlElement, SyntaxKind.XmlElement);
+ context.RegisterSyntaxNodeActionHonorExclusions(this.HandleXmlEmptyElement, SyntaxKind.XmlEmptyElement);
}
private void HandleXmlElement(SyntaxNodeAnalysisContext context)
diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/GeneratedCodeAnalysisExtensions.cs b/StyleCop.Analyzers/StyleCop.Analyzers/GeneratedCodeAnalysisExtensions.cs
index 76add5410..b5945edf8 100644
--- a/StyleCop.Analyzers/StyleCop.Analyzers/GeneratedCodeAnalysisExtensions.cs
+++ b/StyleCop.Analyzers/StyleCop.Analyzers/GeneratedCodeAnalysisExtensions.cs
@@ -10,6 +10,7 @@ namespace StyleCop.Analyzers
using System.Runtime.CompilerServices;
using System.Text.RegularExpressions;
using System.Threading;
+ using Helpers;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
@@ -112,7 +113,9 @@ public static bool IsGeneratedDocument(this SyntaxTree tree, CancellationToken c
private static bool IsGeneratedDocumentNoCache(SyntaxTree tree, CancellationToken cancellationToken)
{
- return IsGeneratedFileName(tree.FilePath) || HasAutoGeneratedComment(tree, cancellationToken);
+ return IsGeneratedFileName(tree.FilePath)
+ || HasAutoGeneratedComment(tree, cancellationToken)
+ || IsEmpty(tree, cancellationToken);
}
///
@@ -179,5 +182,28 @@ private static bool IsGeneratedFileName(string filePath)
@"\.designer\.cs$",
RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture);
}
+
+ ///
+ /// Checks if a given only contains whitespaces. We don't want to analyze empty files.
+ ///
+ /// The syntax tree to examine.
+ /// The that the task will observe.
+ ///
+ /// if only contains whitespaces; otherwise, .
+ ///
+ private static bool IsEmpty(SyntaxTree tree, CancellationToken cancellationToken)
+ {
+ var root = tree.GetRoot(cancellationToken);
+
+ if (root == null)
+ {
+ return false;
+ }
+
+ var firstToken = root.GetFirstToken(includeZeroWidth: true);
+
+ return firstToken.IsKind(SyntaxKind.EndOfFileToken)
+ && TriviaHelper.IndexOfFirstNonWhitespaceTrivia(firstToken.LeadingTrivia) == -1;
+ }
}
}