Skip to content

Commit

Permalink
Merge pull request #3777 from SapiensAnatis/feature/SA-1611-primary-ctor
Browse files Browse the repository at this point in the history
Fix SA1611 not triggering on class primary constructors
  • Loading branch information
sharwell authored Jan 16, 2024
2 parents b88cf52 + 7e20fa1 commit 54a37e3
Show file tree
Hide file tree
Showing 4 changed files with 176 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,109 @@

namespace StyleCop.Analyzers.Test.CSharp12.DocumentationRules
{
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Testing;
using StyleCop.Analyzers.Test.CSharp11.DocumentationRules;
using Xunit;
using static StyleCop.Analyzers.Test.Verifiers.CustomDiagnosticVerifier<StyleCop.Analyzers.DocumentationRules.SA1611ElementParametersMustBeDocumented>;

public partial class SA1611CSharp12UnitTests : SA1611CSharp11UnitTests
{
public static TheoryData<string> NonRecordDeclarationKeywords { get; } = new TheoryData<string>() { "class", "struct" };

[Theory]
[MemberData(nameof(NonRecordDeclarationKeywords))]
[WorkItem(3770, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3770")]
public async Task TestPrimaryConstructorMissingParametersAsync(string keyword)
{
var testCode = $@"
/// <summary>
/// Type.
/// </summary>
public {keyword} C(int {{|#0:param1|}}, string {{|#1:param2|}}) {{ }}";

DiagnosticResult[] expectedResults = new[]
{
Diagnostic().WithLocation(0).WithArguments("param1"),
Diagnostic().WithLocation(1).WithArguments("param2"),
};

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

[Theory]
[MemberData(nameof(NonRecordDeclarationKeywords))]
[WorkItem(3770, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3770")]
public async Task TestPrimaryConstructorPartiallyMissingParametersAsync(string keyword)
{
var testCode = $@"
/// <summary>
/// Type.
/// </summary>
/// <param name=""param1"">Parameter one.</param>
public {keyword} C(int param1, string {{|#0:param2|}}) {{ }}";

await VerifyCSharpDiagnosticAsync(testCode, new[] { Diagnostic().WithLocation(0).WithArguments("param2") }, CancellationToken.None).ConfigureAwait(false);
}

[Theory]
[MemberData(nameof(NonRecordDeclarationKeywords))]
[WorkItem(3770, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3770")]
public async Task TestPrimaryConstructorNoMissingParametersAsync(string keyword)
{
var testCode = $@"
/// <summary>
/// Type.
/// </summary>
/// <param name=""param1"">Parameter one.</param>
/// <param name=""param2"">Parameter two.</param>
public {keyword} C(int param1, string param2) {{ }}";

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

[Theory]
[MemberData(nameof(NonRecordDeclarationKeywords))]
[WorkItem(3770, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3770")]
public async Task TestPrimaryConstructorIncludeMissingParametersAsync(string keyword)
{
var testCode = $@"
/// <include file='MissingClassDocumentation.xml' path='/TestType/*' />
public {keyword} C(int {{|#0:param1|}}, string {{|#1:param2|}}, bool {{|#2:param3|}}) {{ }}";

DiagnosticResult[] expectedResults = new[]
{
Diagnostic().WithLocation(0).WithArguments("param1"),
Diagnostic().WithLocation(1).WithArguments("param2"),
Diagnostic().WithLocation(2).WithArguments("param3"),
};

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

[Theory]
[MemberData(nameof(NonRecordDeclarationKeywords))]
[WorkItem(3770, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3770")]
public async Task TestPrimaryConstructorIncludePartiallyMissingParametersAsync(string keyword)
{
var testCode = $@"
/// <include file='WithPartialClassDocumentation.xml' path='/TestType/*' />
public {keyword} C(int {{|#0:param1|}}, string param2, bool param3) {{ }}";

await VerifyCSharpDiagnosticAsync(testCode, new[] { Diagnostic().WithLocation(0).WithArguments("param1") }, CancellationToken.None).ConfigureAwait(false);
}

[Theory]
[MemberData(nameof(NonRecordDeclarationKeywords))]
[WorkItem(3770, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3770")]
public async Task TestPrimaryConstructorIncludeNoMissingParametersAsync(string keyword)
{
var testCode = $@"
/// <include file='WithClassDocumentation.xml' path='/TestType/*' />
public {keyword} C(int param1, string param2, bool param3) {{ }}";

await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,39 @@

namespace StyleCop.Analyzers.Test.CSharp9.DocumentationRules
{
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Testing;
using StyleCop.Analyzers.Test.CSharp8.DocumentationRules;
using StyleCop.Analyzers.Test.Helpers;
using Xunit;

public partial class SA1611CSharp9UnitTests : SA1611CSharp8UnitTests
{
[Theory]
[MemberData(nameof(CommonMemberData.RecordTypeDeclarationKeywords), MemberType = typeof(CommonMemberData))]
[WorkItem(3770, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3770")]
public async Task TestPrimaryRecordConstructorMissingParametersAsync(string keyword)
{
var testCode = $@"
/// <summary>
/// Record.
/// </summary>
public {keyword} R(int Param1, string Param2);";

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

[Theory]
[MemberData(nameof(CommonMemberData.RecordTypeDeclarationKeywords), MemberType = typeof(CommonMemberData))]
[WorkItem(3770, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3770")]
public async Task TestPrimaryRecordConstructorIncludeMissingParametersAsync(string keyword)
{
var testCode = $@"
/// <include file='MissingClassDocumentation.xml' path='/TestType/*' />
public {keyword} R(int Param1, string Param2);";

await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -414,7 +414,7 @@ public void TestMethod(string param1, string param2, string param3)
await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
}

private static Task VerifyCSharpDiagnosticAsync(string source, DiagnosticResult[] expected, CancellationToken cancellationToken)
protected static Task VerifyCSharpDiagnosticAsync(string source, DiagnosticResult[] expected, CancellationToken cancellationToken)
{
string contentWithoutElementDocumentation = @"<?xml version=""1.0"" encoding=""utf-8"" ?>
<TestClass>
Expand Down Expand Up @@ -455,6 +455,32 @@ private static Task VerifyCSharpDiagnosticAsync(string source, DiagnosticResult[
</TestMethod>
</TestClass>
";
string classWithoutElementDocumentation = @"<?xml version=""1.0"" encoding=""utf-8"" ?>
<TestType>
<summary>
Foo
</summary>
</TestType>
";
string classWithPartialElementDocumentation = @"<?xml version=""1.0"" encoding=""utf-8"" ?>
<TestType>
<summary>
Foo
</summary>
<param name=""param2"">Param 2</param>
<param name=""param3"">Param 3</param>
</TestType>
";
string classWithElementDocumentation = @"<?xml version=""1.0"" encoding=""utf-8"" ?>
<TestType>
<summary>
Foo
</summary>
<param name=""param1"">Param 1</param>
<param name=""param2"">Param 2</param>
<param name=""param3"">Param 3</param>
</TestType>
";

var test = new StyleCopDiagnosticVerifier<SA1611ElementParametersMustBeDocumented>.CSharpTest
{
Expand All @@ -465,6 +491,9 @@ private static Task VerifyCSharpDiagnosticAsync(string source, DiagnosticResult[
{ "WithElementDocumentation.xml", contentWithElementDocumentation },
{ "WithPartialElementDocumentation.xml", contentWithPartialElementDocumentation },
{ "InheritedDocumentation.xml", contentWithInheritedDocumentation },
{ "MissingClassDocumentation.xml", classWithoutElementDocumentation },
{ "WithClassDocumentation.xml", classWithElementDocumentation },
{ "WithPartialClassDocumentation.xml", classWithPartialElementDocumentation },
},
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ namespace StyleCop.Analyzers.DocumentationRules
using System.Linq;
using System.Xml.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using StyleCop.Analyzers.Helpers;
using StyleCop.Analyzers.Lightup;
using StyleCop.Analyzers.Settings.ObjectModel;

/// <summary>
Expand Down Expand Up @@ -60,6 +62,12 @@ protected override void HandleXmlElement(SyntaxNodeAnalysisContext context, Styl
}

var node = context.Node;
if (node.IsKind(SyntaxKindEx.RecordDeclaration) || node.IsKind(SyntaxKindEx.RecordStructDeclaration))
{
// Record parameters are covered by SA1600 instead.
return;
}

var parameterList = GetParameters(node);
if (parameterList == null)
{
Expand All @@ -84,6 +92,12 @@ protected override void HandleCompleteDocumentation(SyntaxNodeAnalysisContext co
}

var node = context.Node;
if (node.IsKind(SyntaxKindEx.RecordDeclaration) || node.IsKind(SyntaxKindEx.RecordStructDeclaration))
{
// Record parameters are covered by SA1600 instead.
return;
}

var parameterList = GetParameters(node);
if (parameterList == null)
{
Expand All @@ -106,7 +120,8 @@ private static IEnumerable<ParameterSyntax> GetParameters(SyntaxNode node)
{
return (node as BaseMethodDeclarationSyntax)?.ParameterList?.Parameters
?? (node as IndexerDeclarationSyntax)?.ParameterList?.Parameters
?? (node as DelegateDeclarationSyntax)?.ParameterList?.Parameters;
?? (node as DelegateDeclarationSyntax)?.ParameterList?.Parameters
?? (node as TypeDeclarationSyntax)?.ParameterList()?.Parameters;
}

private static void ReportMissingParameters(SyntaxNodeAnalysisContext context, IEnumerable<ParameterSyntax> parameterList, IEnumerable<string> documentationParameterNames)
Expand Down

0 comments on commit 54a37e3

Please sign in to comment.