Skip to content

Commit eaaf96a

Browse files
New Rule T0017: Use short name for common types (#9150)
1 parent 39a552a commit eaaf96a

File tree

4 files changed

+255
-1
lines changed

4 files changed

+255
-1
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/*
2+
* SonarAnalyzer for .NET
3+
* Copyright (C) 2015-2024 SonarSource SA
4+
* mailto: contact AT sonarsource DOT com
5+
*
6+
* This program is free software; you can redistribute it and/or
7+
* modify it under the terms of the GNU Lesser General Public
8+
* License as published by the Free Software Foundation; either
9+
* version 3 of the License, or (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14+
* Lesser General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU Lesser General Public License
17+
* along with this program; if not, write to the Free Software Foundation,
18+
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19+
*/
20+
21+
namespace SonarAnalyzer.Rules.CSharp.Styling;
22+
23+
[DiagnosticAnalyzer(LanguageNames.CSharp)]
24+
public sealed class UseShortName : StylingAnalyzer
25+
{
26+
private static readonly RenameInfo[] RenameCandidates =
27+
[
28+
new("CancellationToken", "cancellationToken", "cancel"),
29+
new("CancellationToken", "CancellationToken", "Cancel"),
30+
new("DiagnosticDescriptor", "diagnosticDescriptor", "descriptor"),
31+
new("DiagnosticDescriptor", "DiagnosticDescriptor", "Descriptor"),
32+
new("SyntaxNode", "syntaxNode", "node"),
33+
new("SyntaxNode", "SyntaxNode", "Node"),
34+
new("SyntaxToken", "syntaxToken", "token"),
35+
new("SyntaxToken", "SyntaxToken", "Token"),
36+
new("SyntaxTree", "syntaxTree", "tree"),
37+
new("SyntaxTree", "SyntaxTree", "Tree"),
38+
new("SyntaxTrivia", "syntaxTrivia", "trivia"),
39+
new("SyntaxTrivia", "SyntaxTrivia", "Trivia"),
40+
new("SemanticModel", "semanticModel", "model"),
41+
new("SemanticModel", "SemanticModel", "Model")
42+
];
43+
44+
public UseShortName() : base("T0017", "Use short name '{0}'.") { }
45+
46+
protected override void Initialize(SonarAnalysisContext context)
47+
{
48+
context.RegisterNodeAction(c => ValidateDeclaration(c, ((VariableDeclaratorSyntax)c.Node).Identifier), SyntaxKind.VariableDeclarator);
49+
context.RegisterNodeAction(c => ValidateDeclaration(c, ((PropertyDeclarationSyntax)c.Node).Identifier), SyntaxKind.PropertyDeclaration);
50+
context.RegisterNodeAction(c =>
51+
{
52+
if (!FollowsPredefinedName(c.ContainingSymbol))
53+
{
54+
ValidateDeclaration(c, ((ParameterSyntax)c.Node).Identifier);
55+
}
56+
},
57+
SyntaxKind.Parameter);
58+
}
59+
60+
private void ValidateDeclaration(SonarSyntaxNodeReportingContext context, SyntaxToken identifier)
61+
{
62+
if (FindRename(identifier.ValueText) is { } name
63+
&& context.SemanticModel.GetDeclaredSymbol(context.Node).GetSymbolType() is { } type
64+
&& type.Name == name.TypeName)
65+
{
66+
context.ReportIssue(Rule, identifier, identifier.ValueText.Replace(name.UsedName, name.SuggestedName));
67+
}
68+
}
69+
70+
private static RenameInfo FindRename(string name) =>
71+
Array.Find(RenameCandidates, x => name.Contains(x.UsedName));
72+
73+
private static bool FollowsPredefinedName(ISymbol symbol) =>
74+
symbol is IMethodSymbol method
75+
&& (symbol.IsOverride || symbol.GetInterfaceMember() is not null || method.PartialDefinitionPart is not null);
76+
77+
private sealed record RenameInfo(string TypeName, string UsedName, string SuggestedName);
78+
}

analyzers/src/SonarAnalyzer.Common/Helpers/SymbolHelper.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222

2323
namespace SonarAnalyzer.Helpers
2424
{
25-
internal static class SymbolHelper
25+
public static class SymbolHelper
2626
{
2727
private static readonly PropertyInfo ITypeSymbolIsRecord = typeof(ITypeSymbol).GetProperty("IsRecord");
2828

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
* SonarAnalyzer for .NET
3+
* Copyright (C) 2015-2024 SonarSource SA
4+
* mailto: contact AT sonarsource DOT com
5+
*
6+
* This program is free software; you can redistribute it and/or
7+
* modify it under the terms of the GNU Lesser General Public
8+
* License as published by the Free Software Foundation; either
9+
* version 3 of the License, or (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14+
* Lesser General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU Lesser General Public License
17+
* along with this program; if not, write to the Free Software Foundation,
18+
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19+
*/
20+
21+
namespace SonarAnalyzer.CSharp.Styling.Test.Rules;
22+
23+
[TestClass]
24+
public class UseShortNameTest
25+
{
26+
[TestMethod]
27+
public void UseShortName() =>
28+
StylingVerifierBuilder.Create<UseShortName>().AddPaths("UseShortName.cs").Verify();
29+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
using System.Threading;
2+
3+
public class Sample
4+
{
5+
public void SuggestedNames(SyntaxNode node, SyntaxTree tree, SemanticModel model, CancellationToken cancel) { }
6+
public void OtherNames(SyntaxNode song, SyntaxTree wood, SemanticModel sculpture, CancellationToken nuke) { }
7+
8+
public void LongName1(SyntaxNode syntaxNode) { } // Noncompliant {{Use short name 'node'.}}
9+
// ^^^^^^^^^^
10+
public void LongName2(SyntaxNode prefixedSyntaxNode) { } // Noncompliant {{Use short name 'prefixedNode'.}}
11+
public void LongName3(SyntaxNode syntaxNodeCount) { } // Noncompliant {{Use short name 'nodeCount'.}}
12+
public void LongName4(SyntaxTree syntaxTree) { } // Noncompliant {{Use short name 'tree'.}}
13+
public void LongName5(SyntaxTree firstSyntaxTreeCount) { } // Noncompliant {{Use short name 'firstTreeCount'.}}
14+
public void LongName6(CancellationToken cancellationToken) { } // Noncompliant {{Use short name 'cancel'.}}
15+
16+
private SyntaxNode node;
17+
private SyntaxNode otherNode, syntaxNode; // Noncompliant {{Use short name 'node'.}}
18+
// ^^^^^^^^^^
19+
private SyntaxTree syntaxTree; // Noncompliant
20+
private SemanticModel semanticModel; // Noncompliant
21+
22+
public SyntaxNode SyntaxNode { get; } // Noncompliant {{Use short name 'Node'.}}
23+
// ^^^^^^^^^^
24+
25+
private SyntaxToken syntaxToken; // Noncompliant {{Use short name 'token'.}}
26+
private SyntaxToken SyntaxToken { get; } // Noncompliant {{Use short name 'Token'.}}
27+
28+
private SyntaxTrivia syntaxTrivia; // Noncompliant {{Use short name 'trivia'.}}
29+
private SyntaxTrivia SyntaxTrivia{ get; } // Noncompliant {{Use short name 'Trivia'.}}
30+
31+
private DiagnosticDescriptor diagnosticDescriptor; // Noncompliant {{Use short name 'descriptor'.}}
32+
private DiagnosticDescriptor DiagnosticDescriptor { get; } // Noncompliant {{Use short name 'Descriptor'.}}
33+
34+
public void TypedDeclarations()
35+
{
36+
SyntaxNode nodeNode;
37+
SyntaxNode otherNode;
38+
SyntaxNode node;
39+
SyntaxNode syntaxNode; // Noncompliant {{Use short name 'node'.}} If there exist 'node' and 'syntaxNode' in the same scope, both need a rename.
40+
41+
SyntaxTree syntaxTree; // Noncompliant
42+
SemanticModel semanticModel; // Noncompliant
43+
CancellationToken cancellationToken; // Noncompliant
44+
45+
void SyntaxNode() { } // Not in the scope (for now)
46+
}
47+
48+
public void VarDeclarations()
49+
{
50+
var nodeNode = CreateNode();
51+
var otherNode = CreateNode();
52+
var node = CreateNode();
53+
var syntaxNode = CreateNode(); // Noncompliant {{Use short name 'node'.}} If there exist 'node' and 'syntaxNode' in the same scope, both need a rename.
54+
// ^^^^^^^^^^
55+
56+
var syntaxTree = CreateTree(); // Noncompliant
57+
var semanticModel = CreateModel(); // Noncompliant
58+
var cancellationToken = CreateCancel(); // Noncompliant
59+
60+
void SyntaxNode() { } // Not in the scope (for now)
61+
}
62+
63+
public void UnexpectedType(SyntaxNode syntaxTree) // Wrong, but compliant
64+
{
65+
var semanticModel = CreateNode(); // Wrong, but compliant
66+
SemanticModel syntaxNode = null; // Wrong, but compliant
67+
}
68+
69+
private SyntaxNode CreateNode() => null;
70+
private SyntaxTree CreateTree() => null;
71+
private SemanticModel CreateModel() => null;
72+
private CancellationToken CreateCancel() => default;
73+
74+
private class NestedWithPublicFields
75+
{
76+
public SyntaxNode SyntaxNode; // Noncompliant {{Use short name 'Node'.}}
77+
public SyntaxTree SyntaxTree; // Noncompliant
78+
public SemanticModel SemanticModel; // Noncompliant
79+
}
80+
}
81+
82+
public class ArrowProperty
83+
{
84+
public SyntaxNode SyntaxNode => null; // Noncompliant
85+
}
86+
87+
public class BodyProperty
88+
{
89+
public SyntaxNode SyntaxNode // Noncompliant
90+
{
91+
get => null;
92+
set { }
93+
}
94+
}
95+
96+
public class Methods
97+
{
98+
// It does not appy to method names
99+
public void SyntaxNode() { }
100+
public void SyntaxTree() { }
101+
public void SemanticModel() { }
102+
public void CancellationToken() { }
103+
}
104+
105+
public abstract class Base
106+
{
107+
protected abstract void DoSomething(SyntaxNode syntaxNode); // Noncompliant {{Use short name 'node'.}}
108+
}
109+
110+
public class Inherited : Base
111+
{
112+
protected override void DoSomething(SyntaxNode syntaxNode) // Compliant so we don't contradict S927
113+
{
114+
}
115+
}
116+
117+
public interface IInterface
118+
{
119+
void DoSomething(SyntaxNode syntaxNode); // Noncompliant
120+
}
121+
122+
public class Implemented : IInterface
123+
{
124+
public void DoSomething(SyntaxNode syntaxNode) // Compliant so we don't contradict S927
125+
{
126+
}
127+
}
128+
129+
public partial class Partial
130+
{
131+
public partial void DoSomething(SyntaxNode syntaxNode); // Noncompliant
132+
}
133+
134+
public partial class Partial
135+
{
136+
public partial void DoSomething(SyntaxNode syntaxNode) // Compliant so we don't contradict S927
137+
{
138+
// Implementation
139+
}
140+
}
141+
142+
public class SyntaxNode { }
143+
public class SyntaxToken { }
144+
public class SyntaxTree { }
145+
public class SyntaxTrivia { }
146+
public class SemanticModel { }
147+
public class DiagnosticDescriptor { }

0 commit comments

Comments
 (0)