diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1608UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1608UnitTests.cs index 6bff418db..e869133ab 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1608UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1608UnitTests.cs @@ -185,6 +185,18 @@ public class ClassName await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); } + [Fact] + [WorkItem(3150, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3150")] + public async Task TestClassWithIncludedMissingDocumentationAsync() + { + var testCode = @" +/// +public class ClassName +{ +}"; + await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + } + [Fact] public async Task TestClassWithIncludedSummaryDocumentationAsync() { @@ -210,6 +222,35 @@ public class ClassName await VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false); } + [Fact] + [WorkItem(3150, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3150")] + public async Task TestFieldWithIncludedSummaryDocumentationAsync() + { + var testCode = @" +public class ClassName +{ + /// + public int FieldName; +}"; + await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + } + + [Fact] + [WorkItem(3150, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3150")] + public async Task TestFieldWithIncludedDefaultSummaryDocumentationAsync() + { + var testCode = @" +public class ClassName +{ + /// + public {|#0:int FieldName|}; +}"; + + DiagnosticResult expected = Diagnostic().WithLocation(0); + + await VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false); + } + private static Task VerifyCSharpDiagnosticAsync(string source, DiagnosticResult expected, CancellationToken cancellationToken) => VerifyCSharpDiagnosticAsync(source, new[] { expected }, cancellationToken); @@ -232,6 +273,20 @@ private static Task VerifyCSharpDiagnosticAsync(string source, DiagnosticResult[ Summary description for the ClassName class. +"; + string fieldContentWithSummary = @" + + + Foo + + +"; + string fieldContentWithDefaultSummary = @" + + + Summary description for the ClassName class. + + "; var test = new StyleCopDiagnosticVerifier.CSharpTest @@ -242,6 +297,8 @@ private static Task VerifyCSharpDiagnosticAsync(string source, DiagnosticResult[ { "ClassWithoutSummary.xml", contentWithoutSummary }, { "ClassWithSummary.xml", contentWithSummary }, { "ClassWithDefaultSummary.xml", contentWithDefaultSummary }, + { "FieldWithSummary.xml", fieldContentWithSummary }, + { "FieldWithDefaultSummary.xml", fieldContentWithDefaultSummary }, }, }; diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1611UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1611UnitTests.cs index 31f7f74e4..1544c2e75 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1611UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1611UnitTests.cs @@ -312,6 +312,36 @@ public void TestMethod(string param1, string param2, string param3) await VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false); } + /// + /// Verifies that included documentation with missing documentation file produces no diagnostics. + /// + /// A representing the asynchronous unit test. + [Fact] + [WorkItem(3150, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3150")] + public async Task VerifyIncludedMissingDocumentationAsync() + { + var testCode = @" +/// +/// Foo +/// +public class ClassName +{ + /// + public void TestMethod(string {|#0:param1|}, string {|#1:param2|}, string {|#2:param3|}) + { + } +}"; + + DiagnosticResult[] expected = + { + Diagnostic().WithLocation(0).WithArguments("param1"), + Diagnostic().WithLocation(1).WithArguments("param2"), + Diagnostic().WithLocation(2).WithArguments("param3"), + }; + + await VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false); + } + /// /// Verifies that included documentation with missing elements documented produces the expected diagnostics. /// diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1612UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1612UnitTests.cs index b5690dbe0..f17d5f9f8 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1612UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1612UnitTests.cs @@ -285,6 +285,22 @@ public class ClassName await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); } + [Fact] + [WorkItem(3150, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3150")] + public async Task VerifyIncludedMissingFileIsNotReportedAsync() + { + var testCode = @" +/// +/// Foo +/// +public class ClassName +{ + /// + public ClassName Method() { return null; } +}"; + await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + } + [Fact] public async Task VerifyIncludedMemberWithValidParamsIsNotReportedAsync() { diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1613UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1613UnitTests.cs index 9249f6031..56c72c605 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1613UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1613UnitTests.cs @@ -154,6 +154,22 @@ public class ClassName await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); } + [Fact] + [WorkItem(3150, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3150")] + public async Task VerifyIncludedMissingFileAsync() + { + var testCode = @" +/// +/// Foo +/// +public class ClassName +{ + /// + public ClassName Method(string foo, string bar) { return null; } +}"; + await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + } + [Fact] public async Task VerifyMemberWithValidParamsAndIncludedDocumentationAsync() { diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1614UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1614UnitTests.cs index b2d96f45d..e3e2fe0c3 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1614UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1614UnitTests.cs @@ -154,6 +154,22 @@ public class ClassName await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); } + [Fact] + [WorkItem(3150, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3150")] + public async Task VerifyIncludedMissingFileAsync() + { + var testCode = @" +/// +/// Foo +/// +public class ClassName +{ + /// + public ClassName Method(string foo, string bar) { return null; } +}"; + await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + } + [Fact] public async Task VerifyMemberIncludedDocumentationWithoutParamsAsync() { diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1616UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1616UnitTests.cs index 5aaac6432..4cc26502a 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1616UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1616UnitTests.cs @@ -443,6 +443,22 @@ public class ClassName await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); } + [Fact] + [WorkItem(3150, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3150")] + public async Task VerifyMemberIncludedMissingFileAsync() + { + var testCode = @" +/// +/// Foo +/// +public class ClassName +{ + /// + public ClassName Method(string foo, string bar) { return null; } +}"; + await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + } + [Fact] public async Task VerifyMemberIncludedDocumentationWithoutReturnsAsync() { diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1625UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1625UnitTests.cs index 1b14516cc..590ad2893 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1625UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1625UnitTests.cs @@ -321,6 +321,21 @@ public class TestClass2 {{ }} await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); } + [Fact] + [WorkItem(3150, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3150")] + public async Task VerifyThatMissingIncludedDocumentationDoesNotReportADiagnosticAsync() + { + var testCode = $@" +public class TestClass +{{ + /// + public void Test() {{ }} +}} +public class TestClass2 {{ }} +"; + await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + } + [Fact] public async Task VerifyThatTheAnalyzerDoesNotCrashOnIncludedInheritDocAsync() { diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1629UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1629UnitTests.cs index 9c4bfe4c4..7eab515e4 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1629UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1629UnitTests.cs @@ -315,6 +315,20 @@ public class TestClass await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); } + [Fact] + [WorkItem(3150, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3150")] + public async Task TestIncludedMissingFileAsync() + { + var testCode = @" +/// +public class TestClass +{ +} +"; + + await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + } + [Fact] [WorkItem(2680, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/2680")] public async Task TestReportingAfterEmptyElementAsync() diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test/StyleCop.Analyzers.Test.csproj b/StyleCop.Analyzers/StyleCop.Analyzers.Test/StyleCop.Analyzers.Test.csproj index 385ad7dfc..28420c7b2 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test/StyleCop.Analyzers.Test.csproj +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test/StyleCop.Analyzers.Test.csproj @@ -18,7 +18,7 @@ - + diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/DocumentationRules/ElementDocumentationBase.cs b/StyleCop.Analyzers/StyleCop.Analyzers/DocumentationRules/ElementDocumentationBase.cs index 8484579b5..6a1981fe4 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/DocumentationRules/ElementDocumentationBase.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/DocumentationRules/ElementDocumentationBase.cs @@ -234,29 +234,37 @@ private void HandleDeclaration(SyntaxNodeAnalysisContext context, StyleCopSettin if (hasIncludedDocumentation) { - var declaration = context.SemanticModel.GetDeclaredSymbol(node, context.CancellationToken); - var rawDocumentation = declaration?.GetDocumentationCommentXml(expandIncludes: true, cancellationToken: context.CancellationToken); - var completeDocumentation = XElement.Parse(rawDocumentation, LoadOptions.None); + var declaration = node switch + { + BaseFieldDeclarationSyntax baseFieldDeclaration => baseFieldDeclaration.Declaration.Variables.FirstOrDefault() ?? node, + _ => node, + }; - if (this.inheritDocSuppressesWarnings && - completeDocumentation.Nodes().OfType().Any(element => element.Name == XmlCommentHelper.InheritdocXmlTag)) + var declaredSymbol = context.SemanticModel.GetDeclaredSymbol(declaration, context.CancellationToken); + if (declaredSymbol is not null) { - // Ignore nodes with an tag in the included XML. + var rawDocumentation = declaredSymbol?.GetDocumentationCommentXml(expandIncludes: true, cancellationToken: context.CancellationToken); + var completeDocumentation = XElement.Parse(rawDocumentation ?? "", LoadOptions.None); + + if (this.inheritDocSuppressesWarnings && + completeDocumentation.Nodes().OfType().Any(element => element.Name == XmlCommentHelper.InheritdocXmlTag)) + { + // Ignore nodes with an tag in the included XML. + return; + } + + this.HandleCompleteDocumentation(context, needsComment, completeDocumentation, locations); return; } - - this.HandleCompleteDocumentation(context, needsComment, completeDocumentation, locations); } - else - { - IEnumerable matchingXmlElements = string.IsNullOrEmpty(this.matchElementName) - ? documentation.Content - .Where(x => x is XmlElementSyntax || x is XmlEmptyElementSyntax) - .Where(x => !string.Equals(x.GetName()?.ToString(), XmlCommentHelper.IncludeXmlTag, StringComparison.Ordinal)) - : documentation.Content.GetXmlElements(this.matchElementName); - this.HandleXmlElement(context, settings, needsComment, matchingXmlElements, locations); - } + IEnumerable matchingXmlElements = string.IsNullOrEmpty(this.matchElementName) + ? documentation.Content + .Where(x => x is XmlElementSyntax || x is XmlEmptyElementSyntax) + .Where(x => !string.Equals(x.GetName()?.ToString(), XmlCommentHelper.IncludeXmlTag, StringComparison.Ordinal)) + : documentation.Content.GetXmlElements(this.matchElementName); + + this.HandleXmlElement(context, settings, needsComment, matchingXmlElements, locations); } } }