Skip to content

Commit

Permalink
New Rule T0017: Use short name for common types (#9150)
Browse files Browse the repository at this point in the history
  • Loading branch information
pavel-mikula-sonarsource authored Apr 24, 2024
1 parent 39a552a commit eaaf96a
Show file tree
Hide file tree
Showing 4 changed files with 255 additions and 1 deletion.
78 changes: 78 additions & 0 deletions analyzers/src/SonarAnalyzer.CSharp.Styling/Rules/UseShortName.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
* SonarAnalyzer for .NET
* Copyright (C) 2015-2024 SonarSource SA
* mailto: contact AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

namespace SonarAnalyzer.Rules.CSharp.Styling;

[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class UseShortName : StylingAnalyzer
{
private static readonly RenameInfo[] RenameCandidates =
[
new("CancellationToken", "cancellationToken", "cancel"),
new("CancellationToken", "CancellationToken", "Cancel"),
new("DiagnosticDescriptor", "diagnosticDescriptor", "descriptor"),
new("DiagnosticDescriptor", "DiagnosticDescriptor", "Descriptor"),
new("SyntaxNode", "syntaxNode", "node"),
new("SyntaxNode", "SyntaxNode", "Node"),
new("SyntaxToken", "syntaxToken", "token"),
new("SyntaxToken", "SyntaxToken", "Token"),
new("SyntaxTree", "syntaxTree", "tree"),
new("SyntaxTree", "SyntaxTree", "Tree"),
new("SyntaxTrivia", "syntaxTrivia", "trivia"),
new("SyntaxTrivia", "SyntaxTrivia", "Trivia"),
new("SemanticModel", "semanticModel", "model"),
new("SemanticModel", "SemanticModel", "Model")
];

public UseShortName() : base("T0017", "Use short name '{0}'.") { }

protected override void Initialize(SonarAnalysisContext context)
{
context.RegisterNodeAction(c => ValidateDeclaration(c, ((VariableDeclaratorSyntax)c.Node).Identifier), SyntaxKind.VariableDeclarator);
context.RegisterNodeAction(c => ValidateDeclaration(c, ((PropertyDeclarationSyntax)c.Node).Identifier), SyntaxKind.PropertyDeclaration);
context.RegisterNodeAction(c =>
{
if (!FollowsPredefinedName(c.ContainingSymbol))
{
ValidateDeclaration(c, ((ParameterSyntax)c.Node).Identifier);
}
},
SyntaxKind.Parameter);
}

private void ValidateDeclaration(SonarSyntaxNodeReportingContext context, SyntaxToken identifier)
{
if (FindRename(identifier.ValueText) is { } name
&& context.SemanticModel.GetDeclaredSymbol(context.Node).GetSymbolType() is { } type
&& type.Name == name.TypeName)
{
context.ReportIssue(Rule, identifier, identifier.ValueText.Replace(name.UsedName, name.SuggestedName));
}
}

private static RenameInfo FindRename(string name) =>
Array.Find(RenameCandidates, x => name.Contains(x.UsedName));

private static bool FollowsPredefinedName(ISymbol symbol) =>
symbol is IMethodSymbol method
&& (symbol.IsOverride || symbol.GetInterfaceMember() is not null || method.PartialDefinitionPart is not null);

private sealed record RenameInfo(string TypeName, string UsedName, string SuggestedName);
}
2 changes: 1 addition & 1 deletion analyzers/src/SonarAnalyzer.Common/Helpers/SymbolHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@

namespace SonarAnalyzer.Helpers
{
internal static class SymbolHelper
public static class SymbolHelper
{
private static readonly PropertyInfo ITypeSymbolIsRecord = typeof(ITypeSymbol).GetProperty("IsRecord");

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* SonarAnalyzer for .NET
* Copyright (C) 2015-2024 SonarSource SA
* mailto: contact AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

namespace SonarAnalyzer.CSharp.Styling.Test.Rules;

[TestClass]
public class UseShortNameTest
{
[TestMethod]
public void UseShortName() =>
StylingVerifierBuilder.Create<UseShortName>().AddPaths("UseShortName.cs").Verify();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
using System.Threading;

public class Sample
{
public void SuggestedNames(SyntaxNode node, SyntaxTree tree, SemanticModel model, CancellationToken cancel) { }
public void OtherNames(SyntaxNode song, SyntaxTree wood, SemanticModel sculpture, CancellationToken nuke) { }

public void LongName1(SyntaxNode syntaxNode) { } // Noncompliant {{Use short name 'node'.}}
// ^^^^^^^^^^
public void LongName2(SyntaxNode prefixedSyntaxNode) { } // Noncompliant {{Use short name 'prefixedNode'.}}
public void LongName3(SyntaxNode syntaxNodeCount) { } // Noncompliant {{Use short name 'nodeCount'.}}
public void LongName4(SyntaxTree syntaxTree) { } // Noncompliant {{Use short name 'tree'.}}
public void LongName5(SyntaxTree firstSyntaxTreeCount) { } // Noncompliant {{Use short name 'firstTreeCount'.}}
public void LongName6(CancellationToken cancellationToken) { } // Noncompliant {{Use short name 'cancel'.}}

private SyntaxNode node;
private SyntaxNode otherNode, syntaxNode; // Noncompliant {{Use short name 'node'.}}
// ^^^^^^^^^^
private SyntaxTree syntaxTree; // Noncompliant
private SemanticModel semanticModel; // Noncompliant

public SyntaxNode SyntaxNode { get; } // Noncompliant {{Use short name 'Node'.}}
// ^^^^^^^^^^

private SyntaxToken syntaxToken; // Noncompliant {{Use short name 'token'.}}
private SyntaxToken SyntaxToken { get; } // Noncompliant {{Use short name 'Token'.}}

private SyntaxTrivia syntaxTrivia; // Noncompliant {{Use short name 'trivia'.}}
private SyntaxTrivia SyntaxTrivia{ get; } // Noncompliant {{Use short name 'Trivia'.}}

private DiagnosticDescriptor diagnosticDescriptor; // Noncompliant {{Use short name 'descriptor'.}}
private DiagnosticDescriptor DiagnosticDescriptor { get; } // Noncompliant {{Use short name 'Descriptor'.}}

public void TypedDeclarations()
{
SyntaxNode nodeNode;
SyntaxNode otherNode;
SyntaxNode node;
SyntaxNode syntaxNode; // Noncompliant {{Use short name 'node'.}} If there exist 'node' and 'syntaxNode' in the same scope, both need a rename.

SyntaxTree syntaxTree; // Noncompliant
SemanticModel semanticModel; // Noncompliant
CancellationToken cancellationToken; // Noncompliant

void SyntaxNode() { } // Not in the scope (for now)
}

public void VarDeclarations()
{
var nodeNode = CreateNode();
var otherNode = CreateNode();
var node = CreateNode();
var syntaxNode = CreateNode(); // Noncompliant {{Use short name 'node'.}} If there exist 'node' and 'syntaxNode' in the same scope, both need a rename.
// ^^^^^^^^^^

var syntaxTree = CreateTree(); // Noncompliant
var semanticModel = CreateModel(); // Noncompliant
var cancellationToken = CreateCancel(); // Noncompliant

void SyntaxNode() { } // Not in the scope (for now)
}

public void UnexpectedType(SyntaxNode syntaxTree) // Wrong, but compliant
{
var semanticModel = CreateNode(); // Wrong, but compliant
SemanticModel syntaxNode = null; // Wrong, but compliant
}

private SyntaxNode CreateNode() => null;
private SyntaxTree CreateTree() => null;
private SemanticModel CreateModel() => null;
private CancellationToken CreateCancel() => default;

private class NestedWithPublicFields
{
public SyntaxNode SyntaxNode; // Noncompliant {{Use short name 'Node'.}}
public SyntaxTree SyntaxTree; // Noncompliant
public SemanticModel SemanticModel; // Noncompliant
}
}

public class ArrowProperty
{
public SyntaxNode SyntaxNode => null; // Noncompliant
}

public class BodyProperty
{
public SyntaxNode SyntaxNode // Noncompliant
{
get => null;
set { }
}
}

public class Methods
{
// It does not appy to method names
public void SyntaxNode() { }
public void SyntaxTree() { }
public void SemanticModel() { }
public void CancellationToken() { }
}

public abstract class Base
{
protected abstract void DoSomething(SyntaxNode syntaxNode); // Noncompliant {{Use short name 'node'.}}
}

public class Inherited : Base
{
protected override void DoSomething(SyntaxNode syntaxNode) // Compliant so we don't contradict S927
{
}
}

public interface IInterface
{
void DoSomething(SyntaxNode syntaxNode); // Noncompliant
}

public class Implemented : IInterface
{
public void DoSomething(SyntaxNode syntaxNode) // Compliant so we don't contradict S927
{
}
}

public partial class Partial
{
public partial void DoSomething(SyntaxNode syntaxNode); // Noncompliant
}

public partial class Partial
{
public partial void DoSomething(SyntaxNode syntaxNode) // Compliant so we don't contradict S927
{
// Implementation
}
}

public class SyntaxNode { }
public class SyntaxToken { }
public class SyntaxTree { }
public class SyntaxTrivia { }
public class SemanticModel { }
public class DiagnosticDescriptor { }

0 comments on commit eaaf96a

Please sign in to comment.