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

add ForbidRawSqlOutsideDbProviderSpecificCodeRule #48

Merged
merged 3 commits into from
Jun 21, 2024
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
15 changes: 15 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,21 @@ indent_style = tab

csharp_style_namespace_declarations = file_scoped

# IDE0290 Use primary constructors
dotnet_diagnostic.IDE0290.severity = none

# Error IDE0300 : Collection initialization can be simplified (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0300)
dotnet_diagnostic.IDE0300.severity = suggestion

# Error IDE0301 : Collection initialization can be simplified (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0301)
dotnet_diagnostic.IDE0301.severity = suggestion

# Error IDE0305 : Collection initialization can be simplified (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0305)
dotnet_diagnostic.IDE0305.severity = suggestion

# IDE0028 collection initialization simplification
dotnet_diagnostic.IDE0028.severity = none

# CA2007: Consider calling ConfigureAwait on the awaited task
dotnet_diagnostic.CA2007.severity = none

Expand Down
3 changes: 2 additions & 1 deletion Mindbox.Analyzers/MindboxAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ static MindboxAnalyzer()
new NoTestWithoutOwnerRule(),
new ModelApplicationHostControllerServiceLocatorProhibitedRule(),
new NamedObjectModelConfigurationRegisterProhibitedRule(),
new DataContractRequireIfUsingDataMemberRule()
new DataContractRequireIfUsingDataMemberRule(),
new ForbidRawSqlOutsideDbProviderSpecificCodeRule()
};

_supportedDiagnostics =
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
using System.Collections.Generic;
using System.Data;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;

namespace MindboxAnalyzers.Rules;

public class ForbidRawSqlOutsideDbProviderSpecificCodeRule : AnalyzerRule, ISemanticModelAnalyzerRule
{
private const string RuleId = "Mindbox2004";
private const string Title =
"Forbids raw sql usage outside database provider-specific classes.";
private const string InvalidNamespaceLinkMessage =
"Dont use raw sql outside database provider-specific classes. Extract the provider-specific code " +
"with raw SQL queries into separate classes and configure them through Dependency Injection. " +
"The class names should start either with 'SqlServer' or 'Postgres'.";
private const string Description =
"This rule is intended to prevent the use of raw SQL in shared code when working with a solution " +
"that uses the Entity Framework and supports SQL Server and PostgreSQL databases. Raw SQL is only valid " +
"in database provider-specific classes whose names begin with 'SqlServer' or 'Postgres'.";

private readonly string _dbCommandInterfaceType = typeof(IDbCommand).ToString();

public ForbidRawSqlOutsideDbProviderSpecificCodeRule()
: base(
ruleId: RuleId,
title: Title,
messageFormat: InvalidNamespaceLinkMessage,
description: Description,
isEnabledByDefault: false)
{
}

public void AnalyzeModel(SemanticModel model, out ICollection<Diagnostic> foundProblems)
{
foundProblems = new List<Diagnostic>();

var memberAccessExpressionSyntaxNodes = model.SyntaxTree.GetRoot().DescendantNodes().OfType<MemberAccessExpressionSyntax>();

foreach (var syntaxNode in memberAccessExpressionSyntaxNodes)
{
var parentClassName = GetParentClassNode(syntaxNode);

if (parentClassName.StartsWith("SqlServer") || parentClassName.StartsWith("Postgres"))
continue;

var type = model.GetTypeInfo(syntaxNode.Expression).Type;

if (IsIDbCommandOrDerived(type))
{
foundProblems.Add(CreateDiagnosticForLocation(Location.Create(syntaxNode.SyntaxTree, syntaxNode.FullSpan)));
}
}
}

private bool IsIDbCommandOrDerived(ITypeSymbol typeSymbol)
{
if (typeSymbol == null)
{
return false;
}

if (typeSymbol.ToString() == _dbCommandInterfaceType ||
typeSymbol.Interfaces.Any(item => item.ToString() == _dbCommandInterfaceType))
{
return true;
}

return IsIDbCommandOrDerived(typeSymbol.BaseType);
}

private string GetParentClassNode(SyntaxNode syntaxNode)
{
return syntaxNode switch
{
null => string.Empty,
ClassDeclarationSyntax syntax => syntax.Identifier.ValueText,
RecordDeclarationSyntax syntax => syntax.Identifier.ValueText,
_ => GetParentClassNode(syntaxNode.Parent)
};
}
}
Loading