-
Notifications
You must be signed in to change notification settings - Fork 508
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
Make SA1600 trigger on undocumented record primary ctor parameters #3783
base: master
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,8 +6,12 @@ | |
namespace StyleCop.Analyzers.DocumentationRules | ||
{ | ||
using System; | ||
using System.Collections; | ||
using System.Collections.Generic; | ||
using System.Collections.Immutable; | ||
using System.Linq; | ||
using System.Reflection.Metadata; | ||
using System.Xml.Linq; | ||
using Microsoft.CodeAnalysis; | ||
using Microsoft.CodeAnalysis.CSharp; | ||
using Microsoft.CodeAnalysis.CSharp.Syntax; | ||
|
@@ -143,11 +147,29 @@ public static void HandleBaseTypeDeclaration(SyntaxNodeAnalysisContext context, | |
|
||
Accessibility declaredAccessibility = declaration.GetDeclaredAccessibility(context.SemanticModel, context.CancellationToken); | ||
Accessibility effectiveAccessibility = declaration.GetEffectiveAccessibility(context.SemanticModel, context.CancellationToken); | ||
if (NeedsComment(settings.DocumentationRules, declaration.Kind(), declaration.Parent.Kind(), declaredAccessibility, effectiveAccessibility)) | ||
if (!NeedsComment(settings.DocumentationRules, declaration.Kind(), declaration.Parent.Kind(), declaredAccessibility, effectiveAccessibility)) | ||
{ | ||
if (!XmlCommentHelper.HasDocumentation(declaration)) | ||
return; | ||
} | ||
|
||
if (!XmlCommentHelper.HasDocumentation(declaration)) | ||
{ | ||
context.ReportDiagnostic(Diagnostic.Create(Descriptor, declaration.Identifier.GetLocation())); | ||
} | ||
|
||
if (context.Node is TypeDeclarationSyntax typeDeclaration && RecordDeclarationSyntaxWrapper.IsInstance(typeDeclaration)) | ||
{ | ||
RecordDeclarationSyntaxWrapper recordDeclaration = typeDeclaration; | ||
|
||
string[] documentedParameterNames = GetDocumentedParameters(context, typeDeclaration, out bool hasInheritdoc); | ||
IEnumerable<ParameterSyntax> parameters = recordDeclaration.ParameterList?.Parameters ?? Enumerable.Empty<ParameterSyntax>(); | ||
|
||
foreach (var parameter in parameters) | ||
{ | ||
context.ReportDiagnostic(Diagnostic.Create(Descriptor, declaration.Identifier.GetLocation())); | ||
if (!hasInheritdoc && !documentedParameterNames.Contains(parameter.Identifier.ValueText)) | ||
{ | ||
context.ReportDiagnostic(Diagnostic.Create(Descriptor, parameter.Identifier.GetLocation())); | ||
} | ||
} | ||
} | ||
} | ||
|
@@ -341,6 +363,61 @@ public static void HandleEventFieldDeclaration(SyntaxNodeAnalysisContext context | |
} | ||
} | ||
} | ||
|
||
private static string[] GetDocumentedParameters(SyntaxNodeAnalysisContext context, RecordDeclarationSyntaxWrapper typeDeclarationSyntax, out bool hasInheritdoc) | ||
{ | ||
hasInheritdoc = false; | ||
|
||
var documentation = context.Node.GetDocumentationCommentTriviaSyntax(); | ||
if (documentation == null) | ||
{ | ||
return EmptyArray<string>.Instance; | ||
} | ||
|
||
if (documentation.Content.GetFirstXmlElement(XmlCommentHelper.InheritdocXmlTag) != null) | ||
{ | ||
hasInheritdoc = true; | ||
return EmptyArray<string>.Instance; | ||
} | ||
|
||
var hasIncludedDocumentation = | ||
documentation.Content.GetFirstXmlElement(XmlCommentHelper.IncludeXmlTag) != null; | ||
|
||
if (hasIncludedDocumentation) | ||
{ | ||
var declaredSymbol = context.SemanticModel.GetDeclaredSymbol(typeDeclarationSyntax, context.CancellationToken); | ||
var rawDocumentation = declaredSymbol?.GetDocumentationCommentXml(expandIncludes: true, cancellationToken: context.CancellationToken); | ||
var includedDocumentation = XElement.Parse(rawDocumentation ?? "<doc></doc>", LoadOptions.None); | ||
|
||
if (includedDocumentation.Nodes().OfType<XElement>().Any(element => element.Name == XmlCommentHelper.InheritdocXmlTag)) | ||
{ | ||
hasInheritdoc = true; | ||
return EmptyArray<string>.Instance; | ||
} | ||
|
||
IEnumerable<XElement> paramElements = includedDocumentation.Nodes() | ||
.OfType<XElement>() | ||
.Where(x => x.Name == XmlCommentHelper.ParamXmlTag); | ||
|
||
return paramElements | ||
.SelectMany(x => x.Attributes().Where(y => y.Name == XmlCommentHelper.NameArgumentName)) | ||
.Select(x => x.Value) | ||
.ToArray(); | ||
} | ||
else | ||
{ | ||
IEnumerable<XmlNodeSyntax> xmlNodes = documentation.Content.GetXmlElements(XmlCommentHelper.ParamXmlTag); | ||
return xmlNodes.Select(XmlCommentHelper.GetFirstAttributeOrDefault<XmlNameAttributeSyntax>) | ||
.Where(x => x != null) | ||
.Select(x => x.Identifier.Identifier.ValueText) | ||
.ToArray(); | ||
} | ||
} | ||
|
||
private static class EmptyArray<T> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would move it to the Helpers folder so it can be used in other places as well if needed There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I did a bit of refactoring and was no longer using this, so I've removed it |
||
{ | ||
public static T[] Instance { get; } = new T[0]; | ||
} | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. | ||
// Licensed under the MIT License. See LICENSE in the project root for license information. | ||
|
||
namespace StyleCop.Analyzers.Lightup; | ||
|
||
using Microsoft.CodeAnalysis.CSharp.Syntax; | ||
|
||
internal partial struct RecordDeclarationSyntaxWrapper : ISyntaxWrapper<TypeDeclarationSyntax> | ||
{ | ||
public static implicit operator RecordDeclarationSyntaxWrapper(TypeDeclarationSyntax node) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This corresponds to a conversion from a base type to a sub type. Adding an implicit conversion operator for that is not a good thing in my book. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see your point, I am happy to make it an explicit operator. With the way it is being used I could also do something like: bool RecordDeclarationSyntaxWrapper.TryWrap(TypeDeclarationSyntax node, out RecordDeclarationSyntaxWrapper result) similar to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just to be clear: The explicit conversion operator already exists, so you can just remove this file and add a cast where you get an error. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, understood. Removed this file and used the explicit operator. |
||
{ | ||
return new RecordDeclarationSyntaxWrapper(node); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This code is not executed in tests. I have no idea why it was needed where you copied it from :-), but maybe you can try to find out to see if more tests should be added.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It handles the case where your XML comment includes an XML file, which itself specifies an
<inheritdoc />
.I've added a test that should cover this case.