Skip to content

Commit e2ed577

Browse files
authored
Refactor Rule0088AvoidOptionTypes analyzer for improving performance (#1166)
1 parent 75ce528 commit e2ed577

1 file changed

Lines changed: 112 additions & 52 deletions

File tree

BusinessCentral.LinterCop/Design/Rule0088AvoidOptionTypes.cs

Lines changed: 112 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -18,23 +18,19 @@ public override void Initialize(AnalysisContext context)
1818
{
1919
context.RegisterSyntaxNodeAction(
2020
new Action<SyntaxNodeAnalysisContext>(this.AnalyzeSyntaxNodes),
21-
new SyntaxKind[]{
22-
SyntaxKind.OptionDataType
23-
}
24-
);
21+
SyntaxKind.OptionDataType);
2522

2623
context.RegisterSymbolAction(
2724
new Action<SymbolAnalysisContext>(this.AnalyzeVariables),
2825
SymbolKind.GlobalVariable,
2926
SymbolKind.LocalVariable);
3027
}
3128

29+
#region SyntaxNodeAnalysis
3230
private void AnalyzeSyntaxNodes(SyntaxNodeAnalysisContext ctx)
3331
{
3432
if (ctx.IsObsoletePendingOrRemoved() || ctx.Node is not OptionDataTypeSyntax optionDataType)
35-
{
3633
return;
37-
}
3834

3935
bool skipDueToLocalOrGlobalVariable = optionDataType.Parent is SimpleTypeReferenceSyntax && !IsParameterOrReturnValue(optionDataType);
4036
if (skipDueToLocalOrGlobalVariable)
@@ -48,23 +44,10 @@ private void AnalyzeSyntaxNodes(SyntaxNodeAnalysisContext ctx)
4844
if (skipDueToTableIsOfTypeCDS)
4945
return;
5046

51-
// Handle FlowField with CalcFormula to a Field of Type option
52-
if (optionDataType.Parent is FieldSyntax fieldSyntax)
47+
if (optionDataType.Parent is FieldSyntax fieldSyntax &&
48+
IsFlowFieldWithOptionCalculation(fieldSyntax, ctx))
5349
{
54-
var calcFormulaPropertySyntax = fieldSyntax.PropertyList?.Properties
55-
.OfType<PropertySyntax>()
56-
.Select(p => p.Value)
57-
.OfType<CalculationFormulaPropertyValueSyntax>()
58-
.FirstOrDefault();
59-
60-
if (calcFormulaPropertySyntax is not null &&
61-
calcFormulaPropertySyntax is FieldCalculationFormulaSyntax fieldCalculation &&
62-
fieldCalculation.Field is QualifiedNameSyntax qualifiedNameSyntax &&
63-
ctx.SemanticModel.GetSymbolInfo(qualifiedNameSyntax, ctx.CancellationToken).Symbol is ISymbol fieldSymbol &&
64-
fieldSymbol.GetTypeSymbol().GetNavTypeKindSafe() == NavTypeKind.Option)
65-
{
66-
return;
67-
}
50+
return;
6851
}
6952

7053
ctx.ReportDiagnostic(Diagnostic.Create(
@@ -79,6 +62,32 @@ private static bool IsParameterOrReturnValue(OptionDataTypeSyntax optionDataType
7962
return parent is not null && (parent.Kind == SyntaxKind.Parameter || parent.Kind == SyntaxKind.ReturnValue);
8063
}
8164

65+
private static bool IsFlowFieldWithOptionCalculation(FieldSyntax fieldSyntax, SyntaxNodeAnalysisContext ctx)
66+
{
67+
var propertyList = fieldSyntax.PropertyList?.Properties;
68+
if (propertyList is null)
69+
return false;
70+
71+
foreach (var property in propertyList)
72+
{
73+
ctx.CancellationToken.ThrowIfCancellationRequested();
74+
75+
if (property is PropertySyntax propertySyntax &&
76+
propertySyntax.Value is FieldCalculationFormulaSyntax fieldCalculation &&
77+
fieldCalculation.Field is QualifiedNameSyntax qualifiedNameSyntax)
78+
{
79+
var fieldSymbol = ctx.SemanticModel.GetSymbolInfo(qualifiedNameSyntax, ctx.CancellationToken).Symbol;
80+
if (fieldSymbol?.GetTypeSymbol().GetNavTypeKindSafe() == NavTypeKind.Option)
81+
{
82+
return true;
83+
}
84+
}
85+
}
86+
return false;
87+
}
88+
#endregion
89+
90+
#region VariableAnalysis
8291
private void AnalyzeVariables(SymbolAnalysisContext ctx)
8392
{
8493
if (ctx.IsObsoletePendingOrRemoved() || ctx.Symbol is not IVariableSymbol variable)
@@ -95,31 +104,23 @@ private void AnalyzeVariables(SymbolAnalysisContext ctx)
95104
if (containingSymbol is null)
96105
return;
97106

98-
var LocalVariablesName = GetReferencedVariableNames(containingSymbol, variable);
99-
HasVariablesNotInSource = ((IMethodSymbol)containingSymbol.OriginalDefinition).LocalVariables
100-
.Where(var => !var.Type.GetLocation().IsInSource)
101-
.Where(var => LocalVariablesName?.Contains(var.OriginalDefinition.Name) == true)
102-
.Any();
107+
var localVariablesName = GetReferencedVariableNames(containingSymbol, variable);
108+
var localVariables = ((IMethodSymbol)containingSymbol.OriginalDefinition).LocalVariables;
109+
HasVariablesNotInSource = HasReferencedVariablesNotInSource(localVariables, localVariablesName);
103110
break;
104111

105112
case SymbolKind.GlobalVariable:
106113
IApplicationObjectTypeSymbol? applicationObjectTypeSymbol = variable.GetContainingApplicationObjectTypeSymbol();
107114
if (applicationObjectTypeSymbol is null)
108115
return;
109116

110-
var GlobalVariablesName = GetReferencedVariableNames(applicationObjectTypeSymbol, variable);
111-
HasVariablesNotInSource = applicationObjectTypeSymbol.GetMembers()
112-
.Where(member => member.Kind == SymbolKind.GlobalVariable || member.Kind == SymbolKind.Method)
113-
.SelectMany(member => member is IMethodSymbol methodSymbol
114-
? methodSymbol.LocalVariables
115-
: member is IVariableSymbol variableSymbol ? Enumerable.Repeat(variableSymbol, 1) : Enumerable.Empty<IVariableSymbol>())
116-
.Where(var => !var.Type.GetLocation().IsInSource)
117-
.Where(var => GlobalVariablesName?.Contains(var.OriginalDefinition.Name) == true)
118-
.Any();
117+
var globalVariablesName = GetReferencedVariableNames(applicationObjectTypeSymbol, variable);
118+
var globalVariables = GetGlobalVariablesFromApplicationObject(applicationObjectTypeSymbol);
119+
HasVariablesNotInSource = HasReferencedVariablesNotInSource(globalVariables, globalVariablesName);
119120
break;
120121
}
121122

122-
if (HasVariablesNotInSource == false)
123+
if (HasVariablesNotInSource is false)
123124
{
124125
ctx.ReportDiagnostic(Diagnostic.Create(
125126
DiagnosticDescriptors.Rule0088AvoidOptionTypes,
@@ -128,25 +129,84 @@ private void AnalyzeVariables(SymbolAnalysisContext ctx)
128129
}
129130
}
130131

131-
private static IEnumerable<string>? GetReferencedVariableNames(ISymbol? containingSymbol, IVariableSymbol variable)
132+
private static bool HasReferencedVariablesNotInSource(
133+
IEnumerable<IVariableSymbol> variables,
134+
HashSet<string>? referencedNames)
135+
{
136+
if (referencedNames is null)
137+
return false;
138+
139+
// Filter by name first (cheaper operation), then check location
140+
return variables
141+
.Where(var => referencedNames.Contains(var.OriginalDefinition.Name))
142+
.Any(var => !var.Type.GetLocation().IsInSource);
143+
}
144+
145+
private static List<IVariableSymbol> GetGlobalVariablesFromApplicationObject(IApplicationObjectTypeSymbol applicationObjectTypeSymbol)
146+
{
147+
var variables = new List<IVariableSymbol>();
148+
149+
foreach (var member in applicationObjectTypeSymbol.GetMembers())
150+
{
151+
if (member.Kind == SymbolKind.GlobalVariable && member is IVariableSymbol globalVar)
152+
{
153+
variables.Add(globalVar);
154+
}
155+
else if (member.Kind == SymbolKind.Method && member is IMethodSymbol method)
156+
{
157+
variables.AddRange(method.LocalVariables);
158+
}
159+
}
160+
161+
return variables;
162+
}
163+
164+
private static HashSet<string>? GetReferencedVariableNames(ISymbol? containingSymbol, IVariableSymbol variable)
132165
{
133166
SyntaxNode? syntaxNode = containingSymbol?.DeclaringSyntaxReference?.GetSyntax();
134167
if (syntaxNode is null)
135168
return null;
136169

137-
var nodes = syntaxNode.DescendantNodes()
138-
.OfType<ArgumentListSyntax>()
139-
.Where(argList => argList.Arguments.Any(argument =>
140-
(argument is OptionAccessExpressionSyntax optionAccess &&
141-
optionAccess.Expression.GetIdentifierOrLiteralValue() == variable.Name) ||
142-
argument.GetIdentifierOrLiteralValue() == variable.Name));
143-
144-
return nodes
145-
.SelectMany(node => node.AncestorsAndSelf()
146-
.OfType<ExpressionStatementSyntax>()
147-
.SelectMany(exprStmt => exprStmt.DescendantNodes()
148-
.OfType<MemberAccessExpressionSyntax>()
149-
.Select(memberAccess => memberAccess.Expression.ToString().UnquoteIdentifier())))
150-
.Distinct();
170+
var variableName = variable.Name;
171+
var referencedNames = new HashSet<string>();
172+
173+
// Find all argument lists that reference the variable
174+
var argumentLists = syntaxNode.DescendantNodes()
175+
.OfType<ArgumentListSyntax>();
176+
177+
foreach (var argList in argumentLists)
178+
{
179+
// Check if any argument in this list references our variable
180+
bool hasVariableReference = false;
181+
foreach (var argument in argList.Arguments)
182+
{
183+
if ((argument is OptionAccessExpressionSyntax optionAccess &&
184+
optionAccess.Expression.GetIdentifierOrLiteralValue() == variableName) ||
185+
argument.GetIdentifierOrLiteralValue() == variableName)
186+
{
187+
hasVariableReference = true;
188+
break;
189+
}
190+
}
191+
192+
if (hasVariableReference)
193+
{
194+
// Get the member access expressions from the containing expression statement
195+
foreach (var exprStmt in argList.AncestorsAndSelf().OfType<ExpressionStatementSyntax>())
196+
{
197+
foreach (var memberAccess in exprStmt.DescendantNodes().OfType<MemberAccessExpressionSyntax>())
198+
{
199+
var expressionName = memberAccess.Expression.ToString().UnquoteIdentifier();
200+
if (!string.IsNullOrEmpty(expressionName))
201+
{
202+
referencedNames.Add(expressionName);
203+
}
204+
}
205+
}
206+
}
207+
}
208+
209+
return referencedNames;
151210
}
211+
#endregion
152212
}

0 commit comments

Comments
 (0)