Skip to content

Commit 0dfcc42

Browse files
committed
Merge pull request #1960 from sharwell/fix-1948
Do not report SA1648 for inheritdoc elements with a cref attribute
2 parents 83176df + efe6438 commit 0dfcc42

6 files changed

Lines changed: 136 additions & 32 deletions

File tree

StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1648UnitTests.cs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,20 @@ public async Task TestTypeWithEmptyBaseListAsync(string declaration)
7373
await this.VerifyCSharpDiagnosticAsync(testCode + declaration, expected, CancellationToken.None).ConfigureAwait(false);
7474
}
7575

76+
[Theory(DisplayName = "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/1948")]
77+
[InlineData("interface Test { }")]
78+
[InlineData("class Test { }")]
79+
[InlineData("struct Test { }")]
80+
[InlineData("enum Test { }")]
81+
[InlineData("delegate void Test ();")]
82+
public async Task TestTypeWithEmptyBaseListAndCrefAttributeAsync(string declaration)
83+
{
84+
var testCode = @"/// <inheritdoc cref=""object""/>
85+
";
86+
87+
await this.VerifyCSharpDiagnosticAsync(testCode + declaration, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
88+
}
89+
7690
[Theory]
7791
[InlineData("Test() { }")]
7892
[InlineData("void Foo() { }")]
@@ -96,6 +110,28 @@ public async Task TestMemberThatShouldNotHaveInheritDocAsync(string declaration)
96110
await this.VerifyCSharpDiagnosticAsync(string.Format(testCode, declaration), expected, CancellationToken.None).ConfigureAwait(false);
97111
}
98112

113+
[Theory(DisplayName = "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/1948")]
114+
[InlineData("Test() { }")]
115+
[InlineData("void Foo() { }")]
116+
[InlineData("string foo;")]
117+
[InlineData("string Foo { get; set; }")]
118+
[InlineData("string this [string f] { get { return f; } }")]
119+
[InlineData("event System.Action foo;")]
120+
[InlineData("event System.Action Foo { add { } remove { } }")]
121+
[InlineData("~Test() { }")]
122+
[InlineData("public static Test operator +(Test value) { return value; }")]
123+
[InlineData("public static explicit operator Test(int value) { return new Test(); }")]
124+
public async Task TestMemberThatShouldNotHaveInheritDocButHasCrefAttributeAsync(string declaration)
125+
{
126+
var testCode = @"class Test
127+
{{
128+
/// <inheritdoc cref=""object""></inheritdoc>
129+
{0}
130+
}}";
131+
132+
await this.VerifyCSharpDiagnosticAsync(string.Format(testCode, declaration), EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
133+
}
134+
99135
[Theory]
100136
[InlineData("public override void Foo() { }")]
101137
[InlineData("public override string Bar { get; set; }")]

StyleCop.Analyzers/StyleCop.Analyzers/DocumentationRules/DocumentationResources.Designer.cs

Lines changed: 27 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

StyleCop.Analyzers/StyleCop.Analyzers/DocumentationRules/DocumentationResources.resx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,15 @@
261261
<data name="SA1642SA1643CodeFix" xml:space="preserve">
262262
<value>Add standard text</value>
263263
</data>
264+
<data name="SA1648Description" xml:space="preserve">
265+
<value>&lt;inheritdoc&gt; has been used on an element that doesn't inherit from a base class or implement an interface.</value>
266+
</data>
267+
<data name="SA1648MessageFormat" xml:space="preserve">
268+
<value>inheritdoc must be used with inheriting class</value>
269+
</data>
270+
<data name="SA1648Title" xml:space="preserve">
271+
<value>inheritdoc must be used with inheriting class</value>
272+
</data>
264273
<data name="SA1649CodeFix" xml:space="preserve">
265274
<value>Rename file to match first type name</value>
266275
</data>

StyleCop.Analyzers/StyleCop.Analyzers/DocumentationRules/SA1648InheritDocMustBeUsedWithInheritingClass.cs

Lines changed: 51 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -15,24 +15,18 @@ namespace StyleCop.Analyzers.DocumentationRules
1515
/// <c>&lt;inheritdoc&gt;</c> has been used on an element that doesn't inherit from a base class or implement an
1616
/// interface.
1717
/// </summary>
18-
/// <remarks>
19-
/// <para>Verifies that an <c>inheritdoc</c> tag is not used when the class or interface does not inherit from a
20-
/// base class or interface.</para>
21-
///
22-
/// <para>A violation of this rule occurs when the element having the <c>inheritdoc</c> tag doesn't inherit from a
23-
/// base case or implement an interface.</para>
24-
/// </remarks>
18+
/// <seealso href="https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1648.md">SA1648</seealso>
2519
[DiagnosticAnalyzer(LanguageNames.CSharp)]
2620
internal class SA1648InheritDocMustBeUsedWithInheritingClass : DiagnosticAnalyzer
2721
{
2822
/// <summary>
2923
/// The ID for diagnostics produced by the <see cref="SA1648InheritDocMustBeUsedWithInheritingClass"/> analyzer.
3024
/// </summary>
3125
public const string DiagnosticId = "SA1648";
32-
private const string Title = "inheritdoc must be used with inheriting class";
33-
private const string MessageFormat = "inheritdoc must be used with inheriting class";
34-
private const string Description = "<inheritdoc> has been used on an element that doesn't inherit from a base class or implement an interface.";
35-
private const string HelpLink = "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1648.md";
26+
private static readonly LocalizableString Title = new LocalizableResourceString(nameof(DocumentationResources.SA1648Title), DocumentationResources.ResourceManager, typeof(DocumentationResources));
27+
private static readonly LocalizableString MessageFormat = new LocalizableResourceString(nameof(DocumentationResources.SA1648MessageFormat), DocumentationResources.ResourceManager, typeof(DocumentationResources));
28+
private static readonly LocalizableString Description = new LocalizableResourceString(nameof(DocumentationResources.SA1648Description), DocumentationResources.ResourceManager, typeof(DocumentationResources));
29+
private static readonly string HelpLink = "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1648.md";
3630

3731
private static readonly DiagnosticDescriptor Descriptor =
3832
new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, AnalyzerCategory.DocumentationRules, DiagnosticSeverity.Warning, AnalyzerConstants.EnabledByDefault, Description, HelpLink);
@@ -86,11 +80,17 @@ private static void HandleBaseTypeLikeDeclaration(SyntaxNodeAnalysisContext cont
8680
DocumentationCommentTriviaSyntax documentation = context.Node.GetDocumentationCommentTriviaSyntax();
8781

8882
XmlNodeSyntax inheritDocElement = documentation?.Content.GetFirstXmlElement(XmlCommentHelper.InheritdocXmlTag);
83+
if (inheritDocElement == null)
84+
{
85+
return;
86+
}
8987

90-
if (inheritDocElement != null)
88+
if (HasXmlCrefAttribute(inheritDocElement))
9189
{
92-
context.ReportDiagnostic(Diagnostic.Create(Descriptor, inheritDocElement.GetLocation()));
90+
return;
9391
}
92+
93+
context.ReportDiagnostic(Diagnostic.Create(Descriptor, inheritDocElement.GetLocation()));
9494
}
9595

9696
private static void HandleMemberDeclaration(SyntaxNodeAnalysisContext context)
@@ -107,27 +107,50 @@ private static void HandleMemberDeclaration(SyntaxNodeAnalysisContext context)
107107
DocumentationCommentTriviaSyntax documentation = memberSyntax.GetDocumentationCommentTriviaSyntax();
108108

109109
XmlNodeSyntax inheritDocElement = documentation?.Content.GetFirstXmlElement(XmlCommentHelper.InheritdocXmlTag);
110+
if (inheritDocElement == null)
111+
{
112+
return;
113+
}
110114

111-
if (inheritDocElement != null)
115+
if (HasXmlCrefAttribute(inheritDocElement))
112116
{
113-
ISymbol declaredSymbol = context.SemanticModel.GetDeclaredSymbol(memberSyntax, context.CancellationToken);
114-
if (declaredSymbol == null && memberSyntax.IsKind(SyntaxKind.EventFieldDeclaration))
115-
{
116-
var eventFieldDeclarationSyntax = (EventFieldDeclarationSyntax)memberSyntax;
117-
VariableDeclaratorSyntax firstVariable = eventFieldDeclarationSyntax.Declaration?.Variables.FirstOrDefault();
118-
if (firstVariable != null)
119-
{
120-
declaredSymbol = context.SemanticModel.GetDeclaredSymbol(firstVariable, context.CancellationToken);
121-
}
122-
}
117+
return;
118+
}
123119

124-
// If we don't have a declared symbol we have some kind of field declaration. A field can not override or
125-
// implement anything so we want to report a diagnostic.
126-
if (declaredSymbol == null || !NamedTypeHelpers.IsImplementingAnInterfaceMember(declaredSymbol))
120+
ISymbol declaredSymbol = context.SemanticModel.GetDeclaredSymbol(memberSyntax, context.CancellationToken);
121+
if (declaredSymbol == null && memberSyntax.IsKind(SyntaxKind.EventFieldDeclaration))
122+
{
123+
var eventFieldDeclarationSyntax = (EventFieldDeclarationSyntax)memberSyntax;
124+
VariableDeclaratorSyntax firstVariable = eventFieldDeclarationSyntax.Declaration?.Variables.FirstOrDefault();
125+
if (firstVariable != null)
127126
{
128-
context.ReportDiagnostic(Diagnostic.Create(Descriptor, inheritDocElement.GetLocation()));
127+
declaredSymbol = context.SemanticModel.GetDeclaredSymbol(firstVariable, context.CancellationToken);
129128
}
130129
}
130+
131+
// If we don't have a declared symbol we have some kind of field declaration. A field can not override or
132+
// implement anything so we want to report a diagnostic.
133+
if (declaredSymbol == null || !NamedTypeHelpers.IsImplementingAnInterfaceMember(declaredSymbol))
134+
{
135+
context.ReportDiagnostic(Diagnostic.Create(Descriptor, inheritDocElement.GetLocation()));
136+
}
137+
}
138+
139+
private static bool HasXmlCrefAttribute(XmlNodeSyntax inheritDocElement)
140+
{
141+
XmlElementSyntax xmlElementSyntax = inheritDocElement as XmlElementSyntax;
142+
if (xmlElementSyntax?.StartTag?.Attributes.Any(SyntaxKind.XmlCrefAttribute) ?? false)
143+
{
144+
return true;
145+
}
146+
147+
XmlEmptyElementSyntax xmlEmptyElementSyntax = inheritDocElement as XmlEmptyElementSyntax;
148+
if (xmlEmptyElementSyntax?.Attributes.Any(SyntaxKind.XmlCrefAttribute) ?? false)
149+
{
150+
return true;
151+
}
152+
153+
return false;
131154
}
132155
}
133156
}

documentation/KnownChanges.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,12 @@ public class ApiStatus
246246
}
247247
```
248248

249+
### SA1648
250+
251+
This rule has been modified to adhere more closely to the use of `<inheritdoc>` with Sandcastle Help File Builder. As a
252+
result, some code which resulted in SA1648 being reported by StyleCop Classic will no longer report this warning in
253+
StyleCop Analyzers.
254+
249255
### SA1649
250256

251257
StyleCop Analyzers modified SA1649 to check the first type against the actual file name as opposed to checking against a

documentation/SA1648.md

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,20 @@
1717

1818
## Cause
1919

20-
`<inheritdoc>` has been used on an element that doesnt inherit from a base class or implement an interface..
20+
`<inheritdoc>` has been used on an element that doesn't inherit from a base class or implement an interface.
2121

2222
## Rule description
2323

24-
Verifies that an 'inheritdoc' tag is not used when the class or interface does not inherit from a base class or interface.
24+
Verifies that an `<inheritdoc>` element is not used when the class or interface does not inherit from a base class or
25+
interface. A violation of this rule occurs when the element having the `<inheritdoc>` element doesn't inherit from a
26+
base case or implement an interface.
2527

26-
A violation of this rule occurs when the element having the inheritdoc tag doesn't inherit from a base case or implement an interface.
28+
`<inheritdoc/>` elements are always allowed when they contain a `cref` attribute, which specifies the target element
29+
from which documentation is inherited.
2730

2831
## How to fix violations
2932

30-
To fix a violation of this rule, remove the `<inheritdoc>` tag and document the element appropriately.
33+
To fix a violation of this rule, remove the `<inheritdoc>` element and document the element appropriately.
3134

3235
## How to suppress violations
3336

0 commit comments

Comments
 (0)