Skip to content

Commit

Permalink
GroupByEntityRule (#50)
Browse files Browse the repository at this point in the history
* GroupByEntityRule

* update ci

* Do not forbid System.Linq.Enumerable.GroupBy

* refactor

* update readme

* refactor

* refactor error message
  • Loading branch information
LipatovAlexander authored Jul 9, 2024
1 parent ed559e5 commit 5d2d27b
Show file tree
Hide file tree
Showing 4 changed files with 133 additions and 5 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/publish-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name: Main Build
on:
push:
branches:
- publish-dev
- publish-dev/**

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

_supportedDiagnostics =
Expand Down
125 changes: 125 additions & 0 deletions Mindbox.Analyzers/Rules/ForbidGroupingByNavigationPropertiesRule.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
using System.Collections.Generic;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;

namespace MindboxAnalyzers.Rules;

public class ForbidGroupingByNavigationPropertiesRule : AnalyzerRule, ISemanticModelAnalyzerRule
{
private const string RuleId = "Mindbox2005";

private const string Title = "Grouping by navigation property check";

private const string MessageFormat =
"Grouping by navigation properties is not allowed to avoid issues with generated query. " +
"Instead, group by key columns of related entity and load it manually after materialization (if necessary)";

private const string Description =
"Grouping by navigation properties is not allowed to avoid issues " +
"with xmin/xid in Postgres (see https://github.com/npgsql/efcore.pg/issues/3202)";

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

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

var invocationExpressions = model.SyntaxTree
.GetRoot()
.DescendantNodes()
.OfType<InvocationExpressionSyntax>();

foreach (var invocationExpression in invocationExpressions)
{
if (!TryGetMethodSymbol(model, invocationExpression, out var methodSymbol)
|| !IsGroupByMethod(methodSymbol)
|| !TryGetGroupByProperty(model, invocationExpression, out var groupByProperty)
|| !HasTableAttribute(groupByProperty.Type))
{
continue;
}

var diagnostic = CreateDiagnosticForLocation(invocationExpression.GetLocation());
foundProblems.Add(diagnostic);
}
}

private static bool HasTableAttribute(ITypeSymbol typeSymbol)
{
var attributes = typeSymbol.GetAttributes();

var attributeData = attributes.FirstOrDefault(x =>
x.AttributeClass is not null
&& x.AttributeClass.ContainingNamespace.ToString() == "System.ComponentModel.DataAnnotations.Schema"
&& x.AttributeClass.Name == "TableAttribute");

return attributeData is not null;
}

private static bool TryGetGroupByProperty(
SemanticModel model,
InvocationExpressionSyntax invocationExpression,
out IPropertySymbol groupByProperty)
{
groupByProperty = null;

var argumentList = invocationExpression.ArgumentList.Arguments;
if (argumentList.Count == 0)
{
return false;
}

if (argumentList[0].Expression is not SimpleLambdaExpressionSyntax lambdaExpr)
{
return false;
}

if (lambdaExpr.Body is not MemberAccessExpressionSyntax memberAccessExpression)
{
return false;
}

if (model.GetSymbolInfo(memberAccessExpression).Symbol is not IPropertySymbol propertySymbol)
{
return false;
}

groupByProperty = propertySymbol;
return true;
}

private static bool IsGroupByMethod(IMethodSymbol methodSymbol)
{
return
methodSymbol.ContainingNamespace.ToString() == "System.Linq"
&& methodSymbol.ContainingType.Name == "Queryable"
&& methodSymbol.Name == "GroupBy";
}

private static bool TryGetMethodSymbol(SemanticModel model, InvocationExpressionSyntax invocationExpression, out IMethodSymbol methodSymbol)
{
methodSymbol = null;

if (invocationExpression.Expression is not MemberAccessExpressionSyntax memberAccessExpr)
{
return false;
}

if (model.GetSymbolInfo(memberAccessExpr).Symbol is not IMethodSymbol memberSymbol)
{
return false;
}

methodSymbol = memberSymbol;
return true;
}
}
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

Если добавляешь рулы с использованием SemanticModel, помни, что консолька местами не вывозит в анализ сложных солюшенов (из нескольких проектов). Например, не сможет определить тип переменной, потому что она отдается методом из другой сборки.
В таком случае для проверки рулов лучше собрать пакет и подключить в свой солюшен:
- Билдишь анализатор (перед этим в csproj впиши ему любую новую версию)
- Перейди в папку проекта анализтора в терминале и выполни dotnet pack
- Подключи свой пакет в солюшен через локальный фид (инструкция [тут](https://github.com/mindbox-cloud/Mindbox.Framework/wiki/%D0%9A%D0%B0%D0%BA-%D0%BE%D1%82%D0%BB%D0%B0%D0%B4%D0%B8%D1%82%D1%8C-%D0%BB%D0%BE%D0%BA%D0%B0%D0%BB%D1%8C%D0%BD%D1%8B%D0%B5-%D0%B8%D0%B7%D0%BC%D0%B5%D0%BD%D0%B5%D0%BD%D0%B8%D1%8F-%D0%B2-%D0%BF%D0%B0%D0%BA%D0%B5%D1%82%D0%B5))
- Через локальных фид
- Билдишь анализатор (перед этим в csproj впиши ему любую новую версию)
- Перейди в папку проекта анализтора в терминале и выполни dotnet pack
- Подключи свой пакет в солюшен через локальный фид (инструкция [тут](https://github.com/mindbox-cloud/Mindbox.Framework/wiki/%D0%9A%D0%B0%D0%BA-%D0%BE%D1%82%D0%BB%D0%B0%D0%B4%D0%B8%D1%82%D1%8C-%D0%BB%D0%BE%D0%BA%D0%B0%D0%BB%D1%8C%D0%BD%D1%8B%D0%B5-%D0%B8%D0%B7%D0%BC%D0%B5%D0%BD%D0%B5%D0%BD%D0%B8%D1%8F-%D0%B2-%D0%BF%D0%B0%D0%BA%D0%B5%D1%82%D0%B5))
- Через dev версию пакета (инструкция [тут](https://github.com/mindbox-cloud/Mindbox.Framework/wiki/%D0%9A%D0%B0%D0%BA-%D0%BE%D1%82%D0%BB%D0%B0%D0%B4%D0%B8%D1%82%D1%8C-%D0%BB%D0%BE%D0%BA%D0%B0%D0%BB%D1%8C%D0%BD%D1%8B%D0%B5-%D0%B8%D0%B7%D0%BC%D0%B5%D0%BD%D0%B5%D0%BD%D0%B8%D1%8F-%D0%B2-%D0%BF%D0%B0%D0%BA%D0%B5%D1%82%D0%B5))

0 comments on commit 5d2d27b

Please sign in to comment.