Skip to content
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

Implement S4060 - Avoid unsealed attributes - for VB.NET #5728

Merged
30 changes: 30 additions & 0 deletions analyzers/rspec/vbnet/S4060_vb.net.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<p>The .NET framework class library provides methods for retrieving custom attributes. Sealing the attribute eliminates the search through the
inheritance hierarchy, and can improve performance.</p>
<p>This rule raises an issue when a public type inherits from <code>System.Attribute</code>, is not abstract, and is not sealed.</p>
<h2>Noncompliant Code Example</h2>
<pre>
Public Class MyAttribute ' Noncompliant
Inherits Attribute

Public ReadOnly Property Name As String

Public Sub New(Name As String)
Me.Name = Name
End Sub

End Class
</pre>
<h2>Compliant Solution</h2>
<pre>
Public NotInheritable Class MyAttribute
Inherits Attribute

Public ReadOnly Property Name As String

Public Sub New(Name As String)
Me.Name = Name
End Sub

End Class
</pre>

17 changes: 17 additions & 0 deletions analyzers/rspec/vbnet/S4060_vb.net.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"title": "Non-abstract attributes should be sealed",
"type": "CODE_SMELL",
"status": "ready",
"remediation": {
"func": "Constant\/Issue",
"constantCost": "2min"
},
"tags": [
"performance"
],
"defaultSeverity": "Minor",
"ruleSpecification": "RSPEC-4060",
"sqKey": "S4060",
"scope": "Main",
"quickfix": "unknown"
}
86 changes: 43 additions & 43 deletions analyzers/src/SonarAnalyzer.CSharp/Facade/CSharpSyntaxKindFacade.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,49 +21,49 @@
using Microsoft.CodeAnalysis.CSharp;
using StyleCop.Analyzers.Lightup;

namespace SonarAnalyzer.Helpers.Facade
namespace SonarAnalyzer.Helpers.Facade;

internal sealed class CSharpSyntaxKindFacade : ISyntaxKindFacade<SyntaxKind>
{
internal sealed class CSharpSyntaxKindFacade : ISyntaxKindFacade<SyntaxKind>
public SyntaxKind Attribute => SyntaxKind.Attribute;
public SyntaxKind[] ClassAndRecordDeclaration => new[]
{
SyntaxKind.ClassDeclaration,
SyntaxKindEx.RecordClassDeclaration,
};
public SyntaxKind ClassDeclaration => SyntaxKind.ClassDeclaration;
public SyntaxKind[] ComparisonKinds => new[]
{
SyntaxKind.GreaterThanExpression,
SyntaxKind.GreaterThanOrEqualExpression,
SyntaxKind.LessThanExpression,
SyntaxKind.LessThanOrEqualExpression,
SyntaxKind.EqualsExpression,
SyntaxKind.NotEqualsExpression,
};
public SyntaxKind ConstructorDeclaration => SyntaxKind.ConstructorDeclaration;
public SyntaxKind[] DefaultExpressions => new[] { SyntaxKind.DefaultExpression, SyntaxKindEx.DefaultLiteralExpression };
public SyntaxKind EnumDeclaration => SyntaxKind.EnumDeclaration;
public SyntaxKind FieldDeclaration => SyntaxKind.FieldDeclaration;
public SyntaxKind IdentifierName => SyntaxKind.IdentifierName;
public SyntaxKind IdentifierToken => SyntaxKind.IdentifierToken;
public SyntaxKind InvocationExpression => SyntaxKind.InvocationExpression;
public SyntaxKind InterpolatedStringExpression => SyntaxKind.InterpolatedStringExpression;
public SyntaxKind[] MethodDeclarations => new[] { SyntaxKind.MethodDeclaration };
public SyntaxKind[] ObjectCreationExpressions => new[] { SyntaxKind.ObjectCreationExpression, SyntaxKindEx.ImplicitObjectCreationExpression };
public SyntaxKind Parameter => SyntaxKind.Parameter;
public SyntaxKind ParameterList => SyntaxKind.ParameterList;
public SyntaxKind ReturnStatement => SyntaxKind.ReturnStatement;
public SyntaxKind SimpleAssignment => SyntaxKind.SimpleAssignmentExpression;
public SyntaxKind SimpleMemberAccessExpression => SyntaxKind.SimpleMemberAccessExpression;
public SyntaxKind StringLiteralExpression => SyntaxKind.StringLiteralExpression;
public SyntaxKind[] TypeDeclaration => new[]
{
public SyntaxKind Attribute => SyntaxKind.Attribute;
public SyntaxKind[] ClassAndRecordDeclaration => new[]
{
SyntaxKind.ClassDeclaration,
SyntaxKindEx.RecordClassDeclaration,
};
public SyntaxKind[] ComparisonKinds => new[]
{
SyntaxKind.GreaterThanExpression,
SyntaxKind.GreaterThanOrEqualExpression,
SyntaxKind.LessThanExpression,
SyntaxKind.LessThanOrEqualExpression,
SyntaxKind.EqualsExpression,
SyntaxKind.NotEqualsExpression,
};
public SyntaxKind ConstructorDeclaration => SyntaxKind.ConstructorDeclaration;
public SyntaxKind[] DefaultExpressions => new[] { SyntaxKind.DefaultExpression, SyntaxKindEx.DefaultLiteralExpression };
public SyntaxKind EnumDeclaration => SyntaxKind.EnumDeclaration;
public SyntaxKind FieldDeclaration => SyntaxKind.FieldDeclaration;
public SyntaxKind IdentifierName => SyntaxKind.IdentifierName;
public SyntaxKind IdentifierToken => SyntaxKind.IdentifierToken;
public SyntaxKind InvocationExpression => SyntaxKind.InvocationExpression;
public SyntaxKind InterpolatedStringExpression => SyntaxKind.InterpolatedStringExpression;
public SyntaxKind[] MethodDeclarations => new[] { SyntaxKind.MethodDeclaration };
public SyntaxKind[] ObjectCreationExpressions => new[] { SyntaxKind.ObjectCreationExpression, SyntaxKindEx.ImplicitObjectCreationExpression };
public SyntaxKind Parameter => SyntaxKind.Parameter;
public SyntaxKind ParameterList => SyntaxKind.ParameterList;
public SyntaxKind ReturnStatement => SyntaxKind.ReturnStatement;
public SyntaxKind SimpleAssignment => SyntaxKind.SimpleAssignmentExpression;
public SyntaxKind SimpleMemberAccessExpression => SyntaxKind.SimpleMemberAccessExpression;
public SyntaxKind StringLiteralExpression => SyntaxKind.StringLiteralExpression;
public SyntaxKind[] TypeDeclaration => new[]
{
SyntaxKind.ClassDeclaration,
SyntaxKind.StructDeclaration,
SyntaxKind.InterfaceDeclaration,
SyntaxKind.EnumDeclaration,
SyntaxKindEx.RecordClassDeclaration,
SyntaxKindEx.RecordStructDeclaration,
};
}
SyntaxKind.ClassDeclaration,
SyntaxKind.StructDeclaration,
SyntaxKind.InterfaceDeclaration,
SyntaxKind.EnumDeclaration,
SyntaxKindEx.RecordClassDeclaration,
SyntaxKindEx.RecordStructDeclaration,
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,44 +18,15 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using SonarAnalyzer.Helpers;

namespace SonarAnalyzer.Rules.CSharp
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class AvoidUnsealedAttributes : SonarDiagnosticAnalyzer
{
internal const string DiagnosticId = "S4060";
private const string MessageFormat = "Seal this attribute or make it abstract.";

private static readonly DiagnosticDescriptor rule =
DescriptorFactory.Create(DiagnosticId, MessageFormat);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create(rule);
namespace SonarAnalyzer.Rules.CSharp;

protected override void Initialize(SonarAnalysisContext context)
{
context.RegisterSyntaxNodeActionInNonGenerated(
c =>
{
var classDeclaration = (ClassDeclarationSyntax)c.Node;
var classSymbol = c.SemanticModel.GetDeclaredSymbol(classDeclaration);

if (!classDeclaration.Identifier.IsMissing &&
classSymbol != null &&
classSymbol.DerivesFrom(KnownType.System_Attribute) &&
classSymbol.IsPubliclyAccessible() &&
!classSymbol.IsAbstract &&
!classSymbol.IsSealed)
{
c.ReportIssue(Diagnostic.Create(rule, classDeclaration.Identifier.GetLocation()));
}
},
SyntaxKind.ClassDeclaration);
}
}
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class AvoidUnsealedAttributes : AvoidUnsealedAttributesBase<SyntaxKind>
{
protected override ILanguageFacade<SyntaxKind> Language => CSharpFacade.Instance;
}
50 changes: 25 additions & 25 deletions analyzers/src/SonarAnalyzer.Common/Facade/ISyntaxKindFacade.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,30 +18,30 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

namespace SonarAnalyzer.Helpers.Facade
namespace SonarAnalyzer.Helpers.Facade;

public interface ISyntaxKindFacade<out TSyntaxKind>
where TSyntaxKind : struct
{
public interface ISyntaxKindFacade<out TSyntaxKind>
where TSyntaxKind : struct
{
abstract TSyntaxKind Attribute { get; }
abstract TSyntaxKind[] ClassAndRecordDeclaration { get; }
abstract TSyntaxKind[] ComparisonKinds { get; }
abstract TSyntaxKind ConstructorDeclaration { get; }
abstract TSyntaxKind[] DefaultExpressions { get; }
abstract TSyntaxKind EnumDeclaration { get; }
abstract TSyntaxKind FieldDeclaration { get; }
abstract TSyntaxKind IdentifierName { get; }
abstract TSyntaxKind IdentifierToken { get; }
abstract TSyntaxKind InvocationExpression { get; }
abstract TSyntaxKind InterpolatedStringExpression { get; }
abstract TSyntaxKind[] MethodDeclarations { get; }
abstract TSyntaxKind[] ObjectCreationExpressions { get; }
abstract TSyntaxKind Parameter { get; }
abstract TSyntaxKind ParameterList { get; }
abstract TSyntaxKind ReturnStatement { get; }
abstract TSyntaxKind SimpleAssignment { get; }
abstract TSyntaxKind SimpleMemberAccessExpression { get; }
abstract TSyntaxKind StringLiteralExpression { get; }
abstract TSyntaxKind[] TypeDeclaration { get; }
}
abstract TSyntaxKind Attribute { get; }
abstract TSyntaxKind[] ClassAndRecordDeclaration { get; }
abstract TSyntaxKind ClassDeclaration { get; }
abstract TSyntaxKind[] ComparisonKinds { get; }
abstract TSyntaxKind ConstructorDeclaration { get; }
abstract TSyntaxKind[] DefaultExpressions { get; }
abstract TSyntaxKind EnumDeclaration { get; }
abstract TSyntaxKind FieldDeclaration { get; }
abstract TSyntaxKind IdentifierName { get; }
abstract TSyntaxKind IdentifierToken { get; }
abstract TSyntaxKind InvocationExpression { get; }
abstract TSyntaxKind InterpolatedStringExpression { get; }
abstract TSyntaxKind[] MethodDeclarations { get; }
abstract TSyntaxKind[] ObjectCreationExpressions { get; }
abstract TSyntaxKind Parameter { get; }
abstract TSyntaxKind ParameterList { get; }
abstract TSyntaxKind ReturnStatement { get; }
abstract TSyntaxKind SimpleAssignment { get; }
abstract TSyntaxKind SimpleMemberAccessExpression { get; }
abstract TSyntaxKind StringLiteralExpression { get; }
abstract TSyntaxKind[] TypeDeclaration { get; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* SonarAnalyzer for .NET
* Copyright (C) 2015-2022 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.
*/

using Microsoft.CodeAnalysis;
using SonarAnalyzer.Helpers;

namespace SonarAnalyzer.Rules;

public abstract class AvoidUnsealedAttributesBase<TSyntaxKind> : SonarDiagnosticAnalyzer<TSyntaxKind>
where TSyntaxKind : struct
{
private const string DiagnosticId = "S4060";

protected override string MessageFormat => "Seal this attribute or make it abstract.";

protected AvoidUnsealedAttributesBase() : base(DiagnosticId) { }

protected sealed override void Initialize(SonarAnalysisContext context) =>
context.RegisterSyntaxNodeActionInNonGenerated(
Language.GeneratedCodeRecognizer,
c =>
{
if (Language.Syntax.NodeIdentifier(c.Node) is { IsMissing: false } identifier
pavel-mikula-sonarsource marked this conversation as resolved.
Show resolved Hide resolved
&& c.SemanticModel.GetDeclaredSymbol(c.Node) is INamedTypeSymbol { IsAbstract: false, IsSealed: false } symbol
&& symbol.DerivesFrom(KnownType.System_Attribute)
pavel-mikula-sonarsource marked this conversation as resolved.
Show resolved Hide resolved
&& symbol.IsPubliclyAccessible())
{
c.ReportIssue(Diagnostic.Create(Rule, identifier.GetLocation()));
}
},
Language.SyntaxKind.ClassDeclaration);
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,37 +20,37 @@

using Microsoft.CodeAnalysis.VisualBasic;

namespace SonarAnalyzer.Helpers.Facade
namespace SonarAnalyzer.Helpers.Facade;

internal sealed class VisualBasicSyntaxKindFacade : ISyntaxKindFacade<SyntaxKind>
{
internal sealed class VisualBasicSyntaxKindFacade : ISyntaxKindFacade<SyntaxKind>
public SyntaxKind Attribute => SyntaxKind.Attribute;
public SyntaxKind[] ClassAndRecordDeclaration => new[] { SyntaxKind.ClassBlock };
public SyntaxKind ClassDeclaration => SyntaxKind.ClassBlock;
public SyntaxKind[] ComparisonKinds => new[]
{
public SyntaxKind Attribute => SyntaxKind.Attribute;
public SyntaxKind[] ClassAndRecordDeclaration => new[] { SyntaxKind.ClassBlock };
public SyntaxKind[] ComparisonKinds => new[]
{
SyntaxKind.GreaterThanExpression,
SyntaxKind.GreaterThanOrEqualExpression,
SyntaxKind.LessThanExpression,
SyntaxKind.LessThanOrEqualExpression,
SyntaxKind.EqualsExpression,
SyntaxKind.NotEqualsExpression,
};
public SyntaxKind ConstructorDeclaration => SyntaxKind.ConstructorBlock;
public SyntaxKind[] DefaultExpressions => new[] { SyntaxKind.NothingLiteralExpression };
public SyntaxKind EnumDeclaration => SyntaxKind.EnumStatement;
public SyntaxKind FieldDeclaration => SyntaxKind.FieldDeclaration;
public SyntaxKind IdentifierName => SyntaxKind.IdentifierName;
public SyntaxKind IdentifierToken => SyntaxKind.IdentifierToken;
public SyntaxKind InvocationExpression => SyntaxKind.InvocationExpression;
public SyntaxKind InterpolatedStringExpression => SyntaxKind.InterpolatedStringExpression;
public SyntaxKind[] MethodDeclarations => new[] { SyntaxKind.FunctionStatement, SyntaxKind.SubStatement };
public SyntaxKind[] ObjectCreationExpressions => new[] { SyntaxKind.ObjectCreationExpression };
public SyntaxKind Parameter => SyntaxKind.Parameter;
public SyntaxKind ParameterList => SyntaxKind.ParameterList;
public SyntaxKind ReturnStatement => SyntaxKind.ReturnStatement;
public SyntaxKind SimpleAssignment => SyntaxKind.SimpleAssignmentStatement;
public SyntaxKind SimpleMemberAccessExpression => SyntaxKind.SimpleMemberAccessExpression;
public SyntaxKind StringLiteralExpression => SyntaxKind.StringLiteralExpression;
public SyntaxKind[] TypeDeclaration => new[] { SyntaxKind.ClassBlock, SyntaxKind.StructureBlock, SyntaxKind.InterfaceBlock, SyntaxKind.EnumBlock };
}
SyntaxKind.GreaterThanExpression,
SyntaxKind.GreaterThanOrEqualExpression,
SyntaxKind.LessThanExpression,
SyntaxKind.LessThanOrEqualExpression,
SyntaxKind.EqualsExpression,
SyntaxKind.NotEqualsExpression,
};
public SyntaxKind ConstructorDeclaration => SyntaxKind.ConstructorBlock;
public SyntaxKind[] DefaultExpressions => new[] { SyntaxKind.NothingLiteralExpression };
public SyntaxKind EnumDeclaration => SyntaxKind.EnumStatement;
public SyntaxKind FieldDeclaration => SyntaxKind.FieldDeclaration;
public SyntaxKind IdentifierName => SyntaxKind.IdentifierName;
public SyntaxKind IdentifierToken => SyntaxKind.IdentifierToken;
public SyntaxKind InvocationExpression => SyntaxKind.InvocationExpression;
public SyntaxKind InterpolatedStringExpression => SyntaxKind.InterpolatedStringExpression;
public SyntaxKind[] MethodDeclarations => new[] { SyntaxKind.FunctionStatement, SyntaxKind.SubStatement };
public SyntaxKind[] ObjectCreationExpressions => new[] { SyntaxKind.ObjectCreationExpression };
public SyntaxKind Parameter => SyntaxKind.Parameter;
public SyntaxKind ParameterList => SyntaxKind.ParameterList;
public SyntaxKind ReturnStatement => SyntaxKind.ReturnStatement;
public SyntaxKind SimpleAssignment => SyntaxKind.SimpleAssignmentStatement;
public SyntaxKind SimpleMemberAccessExpression => SyntaxKind.SimpleMemberAccessExpression;
public SyntaxKind StringLiteralExpression => SyntaxKind.StringLiteralExpression;
public SyntaxKind[] TypeDeclaration => new[] { SyntaxKind.ClassBlock, SyntaxKind.StructureBlock, SyntaxKind.InterfaceBlock, SyntaxKind.EnumBlock };
}
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ private static bool IsOn(this ExpressionSyntax expression, SyntaxKind onKind)
public static SyntaxToken? GetIdentifier(this SyntaxNode node) =>
node?.RemoveParentheses() switch
{
ClassBlockSyntax x => x.ClassStatement.Identifier,
ClassStatementSyntax x => x.Identifier,
IdentifierNameSyntax x => x.Identifier,
MemberAccessExpressionSyntax x => x.Name.Identifier,
Expand Down
Loading