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

Fix handling of <include> on fields #3315

Merged
merged 2 commits into from
Mar 10, 2021
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
Original file line number Diff line number Diff line change
Expand Up @@ -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 = @"
/// <include file='MissingFile.xml' path='/ClassName/*' />
public class ClassName
{
}";
await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
}

[Fact]
public async Task TestClassWithIncludedSummaryDocumentationAsync()
{
Expand All @@ -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
{
/// <include file='FieldWithSummary.xml' path='/FieldName/*' />
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
{
/// <include file='FieldWithDefaultSummary.xml' path='/FieldName/*' />
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);

Expand All @@ -232,6 +273,20 @@ private static Task VerifyCSharpDiagnosticAsync(string source, DiagnosticResult[
Summary description for the ClassName class.
</summary>
</ClassName>
";
string fieldContentWithSummary = @"<?xml version=""1.0"" encoding=""utf-8"" ?>
<FieldName>
<summary>
Foo
</summary>
</FieldName>
";
string fieldContentWithDefaultSummary = @"<?xml version=""1.0"" encoding=""utf-8"" ?>
<FieldName>
<summary>
Summary description for the ClassName class.
</summary>
</FieldName>
";

var test = new StyleCopDiagnosticVerifier<SA1608ElementDocumentationMustNotHaveDefaultSummary>.CSharpTest
Expand All @@ -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 },
},
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,36 @@ public void TestMethod(string param1, string param2, string param3)
await VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
}

/// <summary>
/// Verifies that included documentation with missing documentation file produces no diagnostics.
/// </summary>
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
[Fact]
[WorkItem(3150, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3150")]
public async Task VerifyIncludedMissingDocumentationAsync()
{
var testCode = @"
/// <summary>
/// Foo
/// </summary>
public class ClassName
{
/// <include file='MissingFile.xml' path='/TestClass/TestMethod/*' />
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);
}

/// <summary>
/// Verifies that included documentation with missing elements documented produces the expected diagnostics.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 = @"
/// <summary>
/// Foo
/// </summary>
public class ClassName
{
/// <include file='MissingFile.xml' path='/ClassName/Method/*' />
public ClassName Method() { return null; }
}";
await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
}

[Fact]
public async Task VerifyIncludedMemberWithValidParamsIsNotReportedAsync()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 = @"
/// <summary>
/// Foo
/// </summary>
public class ClassName
{
/// <include file='MissingFile.xml' path='/ClassName/Method/*' />
public ClassName Method(string foo, string bar) { return null; }
}";
await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
}

[Fact]
public async Task VerifyMemberWithValidParamsAndIncludedDocumentationAsync()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 = @"
/// <summary>
/// Foo
/// </summary>
public class ClassName
{
/// <include file='MissingFile.xml' path='/ClassName/Method/*' />
public ClassName Method(string foo, string bar) { return null; }
}";
await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
}

[Fact]
public async Task VerifyMemberIncludedDocumentationWithoutParamsAsync()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 = @"
/// <summary>
/// Foo
/// </summary>
public class ClassName
{
/// <include file='MissingFile.xml' path='/ClassName/Method/*' />
public ClassName Method(string foo, string bar) { return null; }
}";
await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
}

[Fact]
public async Task VerifyMemberIncludedDocumentationWithoutReturnsAsync()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
{{
/// <include file='MissingFile.xml' path='/TestClass/Test/*' />
public void Test() {{ }}
}}
public class TestClass2 {{ }}
";
await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
}

[Fact]
public async Task VerifyThatTheAnalyzerDoesNotCrashOnIncludedInheritDocAsync()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 = @"
/// <include file='MissingFile.xml' path='/TestClass/*'/>
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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="1.3.2" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.XUnit" Version="1.0.1-beta1.20623.3" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.XUnit" Version="1.0.1-beta1.21159.2" />
<PackageReference Include="Microsoft.VisualStudio.Composition" Version="16.1.8" />
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
<PackageReference Include="xunit" Version="2.4.1" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<XElement>().Any(element => element.Name == XmlCommentHelper.InheritdocXmlTag))
var declaredSymbol = context.SemanticModel.GetDeclaredSymbol(declaration, context.CancellationToken);
if (declaredSymbol is not null)
{
// Ignore nodes with an <inheritdoc/> tag in the included XML.
var rawDocumentation = declaredSymbol?.GetDocumentationCommentXml(expandIncludes: true, cancellationToken: context.CancellationToken);
var completeDocumentation = XElement.Parse(rawDocumentation ?? "<doc></doc>", LoadOptions.None);

if (this.inheritDocSuppressesWarnings &&
completeDocumentation.Nodes().OfType<XElement>().Any(element => element.Name == XmlCommentHelper.InheritdocXmlTag))
{
// Ignore nodes with an <inheritdoc/> tag in the included XML.
return;
}

this.HandleCompleteDocumentation(context, needsComment, completeDocumentation, locations);
return;
}

this.HandleCompleteDocumentation(context, needsComment, completeDocumentation, locations);
}
else
{
IEnumerable<XmlNodeSyntax> 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<XmlNodeSyntax> 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);
}
}
}