Skip to content
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
1 change: 1 addition & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ dotnet_diagnostic.RCS1162.severity = suggestion
dotnet_diagnostic.RCS1188.severity = suggestion
dotnet_diagnostic.RCS1189.severity = suggestion
dotnet_diagnostic.RCS1207.severity = suggestion
dotnet_diagnostic.RCS1228.severity = suggestion
dotnet_diagnostic.RCS1237.severity = none
dotnet_diagnostic.RCS1244.severity = suggestion
dotnet_diagnostic.RCS1248.severity = suggestion
Expand Down
5 changes: 3 additions & 2 deletions ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- Add analyzer "Dispose resource asynchronously" ([RCS1261](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1261)) ([PR](https://github.com/dotnet/roslynator/pull/1285)).
- Add analyzer "Unnecessary raw string literal" ([RCS1262](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1262)) ([PR](https://github.com/dotnet/roslynator/pull/1293)).
- Add analyzer "Dispose resource asynchronously" ([RCS1261](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1261)) ([PR](https://github.com/dotnet/roslynator/pull/1285))
- Add analyzer "Unnecessary raw string literal" ([RCS1262](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1262)) ([PR](https://github.com/dotnet/roslynator/pull/1293))
- Add analyzer "Invalid reference in a documentation comment" ([RCS1263](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1263)) ([PR](https://github.com/dotnet/roslynator/pull/1295))

### Changed

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,8 @@ private static SyntaxList<XmlNodeSyntax> SortElements<TNode>(
XmlNodeSyntax element = elementInfo.Element;

string value = (element.IsKind(SyntaxKind.XmlElement))
? ((XmlElementSyntax)element).GetAttributeValue("name")
: ((XmlEmptyElementSyntax)element).GetAttributeValue("name");
? ((XmlElementSyntax)element).GetAttributeValueText("name")
: ((XmlEmptyElementSyntax)element).GetAttributeValueText("name");

if (value is not null)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public override ImmutableArray<string> FixableDiagnosticIds
{
return ImmutableArray.Create(
DiagnosticIdentifiers.UnusedElementInDocumentationComment,
DiagnosticIdentifiers.InvalidReferenceInDocumentationComment,
DiagnosticIdentifiers.FixDocumentationCommentTag);
}
}
Expand All @@ -43,13 +44,14 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context)
switch (diagnostic.Id)
{
case DiagnosticIdentifiers.UnusedElementInDocumentationComment:
case DiagnosticIdentifiers.InvalidReferenceInDocumentationComment:
{
XmlElementInfo elementInfo = SyntaxInfo.XmlElementInfo(xmlNode);

string name = elementInfo.LocalName;

CodeAction codeAction = CodeAction.Create(
$"Remove element '{name}'",
$"Remove '{name}' element",
ct => RemoveUnusedElementInDocumentationCommentAsync(document, elementInfo, ct),
GetEquivalenceKey(diagnostic, name));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ public Dictionary<string, XmlElementSyntax> CreateNameElementMap(DocumentationCo

foreach (XmlElementSyntax element in comment.Elements(Tag))
{
string name = element.GetAttributeValue("name");
string name = element.GetAttributeValueText("name");

if (!dic.ContainsKey(name))
dic.Add(name, element);
Expand Down
25 changes: 23 additions & 2 deletions src/Analyzers.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6535,11 +6535,11 @@ class Foo
<Analyzer>
<Id>RCS1228</Id>
<Identifier>UnusedElementInDocumentationComment</Identifier>
<Title>Unused element in documentation comment</Title>
<Title>Unused element in a documentation comment</Title>
<MessageFormat>Unused '{0}' element in a documentation comment</MessageFormat>
<DefaultSeverity>Hidden</DefaultSeverity>
<IsEnabledByDefault>true</IsEnabledByDefault>
<SupportsFadeOut>true</SupportsFadeOut>
<SupportsFadeOutAnalyzer>true</SupportsFadeOutAnalyzer>
<Samples>
<Sample>
<Before><![CDATA[/// <summary>
Expand Down Expand Up @@ -7511,6 +7511,27 @@ void M()
</Link>
</Links>
</Analyzer>
<Analyzer>
<Id>RCS1263</Id>
<Identifier>InvalidReferenceInDocumentationComment</Identifier>
<Title>Invalid reference in a documentation comment</Title>
<MessageFormat>{0} '{1}' could not be found</MessageFormat>
<DefaultSeverity>Warning</DefaultSeverity>
<IsEnabledByDefault>true</IsEnabledByDefault>
<Samples>
<Sample>
<Before><![CDATA[/// <summary>
///
/// </summary>
/// <param name="barr"></param>
public string Foo(string bar)
{
/// ...
}
]]></Before>
</Sample>
</Samples>
</Analyzer>
<Analyzer>
<Id>RCS9001</Id>
<Identifier>UsePatternMatching</Identifier>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,23 @@ public sealed class SingleLineDocumentationCommentTriviaAnalyzer : BaseDiagnosti
{
private static ImmutableArray<DiagnosticDescriptor> _supportedDiagnostics;

private static readonly ImmutableArray<DiagnosticDescriptor> _supportedDiagnosticsWithoutFadeOut = ImmutableArray.Create(
DiagnosticRules.AddSummaryToDocumentationComment,
DiagnosticRules.AddSummaryElementToDocumentationComment,
DiagnosticRules.AddParamElementToDocumentationComment,
DiagnosticRules.AddTypeParamElementToDocumentationComment,
DiagnosticRules.UnusedElementInDocumentationComment,
DiagnosticRules.OrderElementsInDocumentationComment,
DiagnosticRules.FixDocumentationCommentTag);

public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics
{
get
{
if (_supportedDiagnostics.IsDefault)
ImmutableInterlocked.InterlockedInitialize(ref _supportedDiagnostics, _supportedDiagnosticsWithoutFadeOut.Add(DiagnosticRules.UnusedElementInDocumentationCommentFadeOut));
{
Immutable.InterlockedInitialize(
ref _supportedDiagnostics,
DiagnosticRules.AddSummaryToDocumentationComment,
DiagnosticRules.AddSummaryElementToDocumentationComment,
DiagnosticRules.AddParamElementToDocumentationComment,
DiagnosticRules.AddTypeParamElementToDocumentationComment,
DiagnosticRules.UnusedElementInDocumentationComment,
DiagnosticRules.OrderElementsInDocumentationComment,
DiagnosticRules.FixDocumentationCommentTag,
DiagnosticRules.InvalidReferenceInDocumentationComment);
}

return _supportedDiagnostics;
}
Expand All @@ -44,7 +46,7 @@ public override void Initialize(AnalysisContext context)
context.RegisterSyntaxNodeAction(
c =>
{
if (IsAnyEffective(c, _supportedDiagnosticsWithoutFadeOut))
if (IsAnyEffective(c, _supportedDiagnostics))
AnalyzeSingleLineDocumentationCommentTrivia(c);
},
SyntaxKind.SingleLineDocumentationCommentTrivia);
Expand All @@ -57,7 +59,7 @@ private static void AnalyzeSingleLineDocumentationCommentTrivia(SyntaxNodeAnalys
if (!documentationComment.IsPartOfMemberDeclaration())
return;

bool? useCorrectDocumentationTagEnabled = null;
bool? fixDocumentationCommentTagEnabled = null;
var containsInheritDoc = false;
var containsIncludeOrExclude = false;
var containsSummaryElement = false;
Expand All @@ -76,7 +78,8 @@ private static void AnalyzeSingleLineDocumentationCommentTrivia(SyntaxNodeAnalys

if (info.Success)
{
switch (info.GetTag())
XmlTag tag = info.GetTag();
switch (tag)
{
case XmlTag.Include:
case XmlTag.Exclude:
Expand All @@ -103,7 +106,7 @@ private static void AnalyzeSingleLineDocumentationCommentTrivia(SyntaxNodeAnalys

containsSummaryElement = true;

if (useCorrectDocumentationTagEnabled ??= DiagnosticRules.FixDocumentationCommentTag.IsEffective(context))
if (fixDocumentationCommentTagEnabled ??= DiagnosticRules.FixDocumentationCommentTag.IsEffective(context))
FixDocumentationCommentTagAnalysis.Analyze(context, info);

break;
Expand All @@ -113,31 +116,47 @@ private static void AnalyzeSingleLineDocumentationCommentTrivia(SyntaxNodeAnalys
case XmlTag.Returns:
case XmlTag.Value:
{
if (info.IsContentEmptyOrWhitespace)
ReportUnusedElement(context, info.Element, i, content);
if (fixDocumentationCommentTagEnabled ??= DiagnosticRules.FixDocumentationCommentTag.IsEffective(context))
FixDocumentationCommentTagAnalysis.Analyze(context, info);

if (useCorrectDocumentationTagEnabled ??= DiagnosticRules.FixDocumentationCommentTag.IsEffective(context))
AnalyzeUnusedElement(context, info, tag);
break;
}
case XmlTag.List:
{
if (fixDocumentationCommentTagEnabled ??= DiagnosticRules.FixDocumentationCommentTag.IsEffective(context))
FixDocumentationCommentTagAnalysis.Analyze(context, info);

break;
}
case XmlTag.Exception:
case XmlTag.List:
case XmlTag.Param:
case XmlTag.Permission:
{
if (fixDocumentationCommentTagEnabled ??= DiagnosticRules.FixDocumentationCommentTag.IsEffective(context))
FixDocumentationCommentTagAnalysis.Analyze(context, info);

AnalyzeUnusedElement(context, info, tag, checkAttributes: true);
break;
}
case XmlTag.Param:
case XmlTag.TypeParam:
{
if (useCorrectDocumentationTagEnabled ??= DiagnosticRules.FixDocumentationCommentTag.IsEffective(context))
if (fixDocumentationCommentTagEnabled ??= DiagnosticRules.FixDocumentationCommentTag.IsEffective(context))
FixDocumentationCommentTagAnalysis.Analyze(context, info);

AnalyzeUnusedElement(context, info, tag);
break;
}
case XmlTag.SeeAlso:
{
AnalyzeUnusedElement(context, info, tag, checkAttributes: true);
break;
}
case XmlTag.C:
case XmlTag.Code:
case XmlTag.Para:
case XmlTag.ParamRef:
case XmlTag.See:
case XmlTag.SeeAlso:
case XmlTag.TypeParamRef:
{
break;
Expand Down Expand Up @@ -174,14 +193,14 @@ private static void AnalyzeSingleLineDocumentationCommentTrivia(SyntaxNodeAnalys

SyntaxNode parent = documentationComment.ParentTrivia.Token.Parent;

bool unusedElement = DiagnosticRules.UnusedElementInDocumentationComment.IsEffective(context);
bool invalidReference = DiagnosticRules.InvalidReferenceInDocumentationComment.IsEffective(context);
bool orderParams = DiagnosticRules.OrderElementsInDocumentationComment.IsEffective(context);
bool addParam = DiagnosticRules.AddParamElementToDocumentationComment.IsEffective(context);
bool addTypeParam = DiagnosticRules.AddTypeParamElementToDocumentationComment.IsEffective(context);

if (addParam
|| orderParams
|| unusedElement)
|| invalidReference)
{
SeparatedSyntaxList<ParameterSyntax> parameters = CSharpUtility.GetParameters(
(parent is MemberDeclarationSyntax) ? parent : parent.Parent);
Expand All @@ -199,15 +218,15 @@ private static void AnalyzeSingleLineDocumentationCommentTrivia(SyntaxNodeAnalys
}
}

if (orderParams || unusedElement)
if (orderParams || invalidReference)
{
Analyze(context, documentationComment.Content, parameters, XmlTag.Param, (nodes, name) => nodes.IndexOf(name));
}
}

if (addTypeParam
|| orderParams
|| unusedElement)
|| invalidReference)
{
SeparatedSyntaxList<TypeParameterSyntax> typeParameters = CSharpUtility.GetTypeParameters(
(parent is MemberDeclarationSyntax) ? parent : parent.Parent);
Expand All @@ -225,13 +244,23 @@ private static void AnalyzeSingleLineDocumentationCommentTrivia(SyntaxNodeAnalys
}
}

if (orderParams || unusedElement)
if (orderParams || invalidReference)
{
Analyze(context, documentationComment.Content, typeParameters, XmlTag.TypeParam, (nodes, name) => nodes.IndexOf(name));
}
}
}

private static void AnalyzeUnusedElement(SyntaxNodeAnalysisContext context, XmlElementInfo info, XmlTag tag, bool checkAttributes = false)
{
if (DiagnosticRules.UnusedElementInDocumentationComment.IsEffective(context)
&& info.IsContentEmptyOrWhitespace
&& (!checkAttributes || !info.HasAttributes))
{
ReportDiagnostic(context, DiagnosticRules.UnusedElementInDocumentationComment, info.Element, XmlTagMapper.GetName(tag));
}
}

private static bool IsMissing(DocumentationCommentTriviaSyntax documentationComment, ParameterSyntax parameter)
{
foreach (XmlNodeSyntax xmlNode in documentationComment.Content)
Expand All @@ -244,7 +273,7 @@ private static bool IsMissing(DocumentationCommentTriviaSyntax documentationComm
{
var element = (XmlElementSyntax)elementInfo.Element;

string value = element.GetAttributeValue("name");
string value = element.GetAttributeValueText("name");

if (value is not null
&& string.Equals(parameter.Identifier.ValueText, value, StringComparison.Ordinal))
Expand All @@ -269,7 +298,7 @@ private static bool IsMissing(DocumentationCommentTriviaSyntax documentationComm
{
var element = (XmlElementSyntax)elementInfo.Element;

string value = element.GetAttributeValue("name");
string value = element.GetAttributeValueText("name");

if (value is not null
&& string.Equals(typeParameter.Identifier.ValueText, value, StringComparison.Ordinal))
Expand Down Expand Up @@ -308,21 +337,21 @@ private static void Analyze<TNode>(

XmlNodeSyntax element = elementInfo.Element;

string name = (element.IsKind(SyntaxKind.XmlElement))
IdentifierNameSyntax identifierName = (element.IsKind(SyntaxKind.XmlElement))
? ((XmlElementSyntax)element).GetAttributeValue("name")
: ((XmlEmptyElementSyntax)element).GetAttributeValue("name");

if (name is null)
if (identifierName is null)
{
firstIndex = -1;
continue;
}

int index = indexOf(nodes, name);
int index = indexOf(nodes, identifierName.Identifier.ValueText);

if (index == -1)
{
ReportUnusedElement(context, element, i, xmlNodes);
ReportDiagnosticIfEffective(context, DiagnosticRules.InvalidReferenceInDocumentationComment, identifierName, GetElementName(tag), identifierName.Identifier.ValueText);
}
else if (index < firstIndex)
{
Expand All @@ -336,45 +365,15 @@ private static void Analyze<TNode>(

firstIndex = index;
}
}

private static void ReportUnusedElement(
SyntaxNodeAnalysisContext context,
XmlNodeSyntax xmlNode,
int index,
SyntaxList<XmlNodeSyntax> xmlNodes)
{
if (!DiagnosticRules.UnusedElementInDocumentationComment.IsEffective(context))
return;

ReportDiagnostic(context, DiagnosticRules.UnusedElementInDocumentationComment, xmlNode);

if (index > 0
&& xmlNodes[index - 1] is XmlTextSyntax xmlText)
static string GetElementName(XmlTag tag)
{
SyntaxTokenList tokens = xmlText.TextTokens;

if (tokens.Count == 1)
{
if (tokens[0].IsKind(SyntaxKind.XmlTextLiteralToken))
{
SyntaxTrivia trivia = tokens[0].LeadingTrivia.SingleOrDefault(shouldThrow: false);

if (trivia.IsKind(SyntaxKind.DocumentationCommentExteriorTrivia))
ReportDiagnostic(context, DiagnosticRules.UnusedElementInDocumentationCommentFadeOut, trivia);
}
}
else if (tokens.Count == 2)
return tag switch
{
if (tokens[0].IsKind(SyntaxKind.XmlTextLiteralNewLineToken)
&& tokens[1].IsKind(SyntaxKind.XmlTextLiteralToken))
{
SyntaxTrivia trivia = tokens[1].LeadingTrivia.SingleOrDefault(shouldThrow: false);

if (trivia.IsKind(SyntaxKind.DocumentationCommentExteriorTrivia))
ReportDiagnostic(context, DiagnosticRules.UnusedElementInDocumentationCommentFadeOut, trivia);
}
}
XmlTag.Param => "Parameter",
XmlTag.TypeParam => "Type parameter",
_ => throw new InvalidOperationException(),
};
}
}
}
1 change: 1 addition & 0 deletions src/Analyzers/CSharp/DiagnosticIdentifiers.Generated.cs
Original file line number Diff line number Diff line change
Expand Up @@ -219,5 +219,6 @@ public static partial class DiagnosticIdentifiers
public const string AddOrRemoveTrailingComma = "RCS1260";
public const string DisposeResourceAsynchronously = "RCS1261";
public const string UnnecessaryRawStringLiteral = "RCS1262";
public const string InvalidReferenceInDocumentationComment = "RCS1263";
}
}
Loading