Skip to content

Commit c7a52a4

Browse files
authored
Extend Rule0051 on exit() method with Label variable (#1097)
1 parent 0f0a661 commit c7a52a4

5 files changed

Lines changed: 109 additions & 0 deletions

File tree

BusinessCentral.LinterCop.Test/Rule0051.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ public void Setup()
1313
}
1414

1515
[Test]
16+
[TestCase("ExitStatementLabel")]
1617
#if !LessThenSpring2024
1718
[TestCase("GetMethodStringLiteral")]
1819
[TestCase("GetMethodStrSubstNo")]
@@ -29,6 +30,8 @@ public async Task HasDiagnostic(string testCase)
2930
}
3031

3132
[Test]
33+
[TestCase("ExitStatementLabelWithLocked")]
34+
[TestCase("ExitStatementLabelWithMaxLength")]
3235
#if !LessThenSpring2024
3336
[TestCase("GetMethodCompanyName")]
3437
[TestCase("GetMethodStringLiteral")]
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
codeunit 50000 MyCodeunit
2+
{
3+
internal procedure MyProcedure(): Text[10]
4+
var
5+
MyLabelLbl: Label 'My Label';
6+
begin
7+
exit([|MyLabelLbl|]);
8+
end;
9+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
codeunit 50000 MyCodeunit
2+
{
3+
internal procedure MyProcedure(): Text[10]
4+
var
5+
MyLabelLbl: Label 'My Label', Locked = true;
6+
begin
7+
exit([|MyLabelLbl|]);
8+
end;
9+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
codeunit 50000 MyCodeunit
2+
{
3+
internal procedure MyProcedure(): Text[10]
4+
var
5+
MyLabelLbl: Label 'My Label', MaxLength = 10;
6+
begin
7+
exit([|MyLabelLbl|]);
8+
end;
9+
}

BusinessCentral.LinterCop/Design/Rule0051PossibleOverflowAssigning.cs

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
using BusinessCentral.LinterCop.Helpers;
55
using Microsoft.Dynamics.Nav.CodeAnalysis;
66
using Microsoft.Dynamics.Nav.CodeAnalysis.Diagnostics;
7+
using Microsoft.Dynamics.Nav.CodeAnalysis.Semantics;
78
using Microsoft.Dynamics.Nav.CodeAnalysis.Symbols;
9+
using Microsoft.Dynamics.Nav.CodeAnalysis.Syntax;
810

911
namespace BusinessCentral.LinterCop.Design;
1012

@@ -31,6 +33,7 @@ public override void Initialize(AnalysisContext context)
3133
#if !LessThenSpring2024
3234
context.RegisterOperationAction(AnalyzeGetMethod, OperationKind.InvocationExpression);
3335
#endif
36+
context.RegisterOperationAction(AnalyzeExitStatement, OperationKind.ExitStatement);
3437
}
3538
private void AnalyzeSetFilter(OperationAnalysisContext ctx)
3639
{
@@ -161,6 +164,82 @@ private void AnalyzeGetMethod(OperationAnalysisContext ctx)
161164
}
162165
#endif
163166

167+
// This rule is an extension of the CodeCop AA0139 to only check for Label variables without the MaxLength or Locked property explicitly set.
168+
// https://learn.microsoft.com/en-us/dynamics365/business-central/dev-itpro/developer/analyzers/codecop-aa0139
169+
private void AnalyzeExitStatement(OperationAnalysisContext ctx)
170+
{
171+
if (ctx.IsObsoletePendingOrRemoved() || ctx.Operation is not IExitStatement operation)
172+
return;
173+
174+
// Early return if there is no returned value on the exit statement
175+
if (operation.ReturnedValue is null || operation.ReturnedValue is not IConversionExpression argValue)
176+
return;
177+
178+
if (argValue.Operand.Type.GetNavTypeKindSafe() != NavTypeKind.Label)
179+
return;
180+
181+
if (argValue.Operand.GetSymbol() is not IVariableSymbol variable)
182+
return;
183+
184+
var syntax = variable.DeclaringSyntaxReference?.GetSyntax();
185+
if (syntax is null)
186+
return;
187+
188+
var propertiesNode = syntax.DescendantNodes()
189+
.FirstOrDefault(n => n.Kind == SyntaxKind.CommaSeparatedIdentifierEqualsLiteralList);
190+
191+
bool hasMaxLengthOrLocked = false;
192+
if (propertiesNode is not null)
193+
{
194+
foreach (var child in propertiesNode.ChildNodes())
195+
{
196+
if (child is IdentifierEqualsLiteralSyntax prop)
197+
{
198+
string name = prop.Identifier.ToString();
199+
if (name.Equals("MaxLength", StringComparison.OrdinalIgnoreCase) ||
200+
name.Equals("Locked", StringComparison.OrdinalIgnoreCase))
201+
{
202+
hasMaxLengthOrLocked = true;
203+
break;
204+
}
205+
}
206+
}
207+
}
208+
209+
// If the property MaxLength or Locked is set, then let the CodeCop AA0139 rule handle it.
210+
if (hasMaxLengthOrLocked)
211+
return;
212+
213+
if (ctx.Operation.Syntax.GetFirstParent(SyntaxKind.MethodDeclaration) is not MethodDeclarationSyntax methodDeclaration)
214+
return;
215+
216+
var semanticModel = ctx.Compilation.GetSemanticModel(methodDeclaration.SyntaxTree);
217+
var returnValueSymbol = semanticModel.GetDeclaredSymbol(methodDeclaration.ReturnValue);
218+
219+
if (returnValueSymbol?.GetTypeSymbol() is not ITypeSymbol returnTypeSymbol)
220+
return;
221+
222+
bool isError = false;
223+
int typeLength = GetTypeLength(returnTypeSymbol, ref isError);
224+
if (isError || typeLength == int.MaxValue)
225+
return;
226+
227+
int expressionLength = this.CalculateMaxExpressionLength(argValue.Operand, ref isError);
228+
if (!isError && expressionLength > typeLength)
229+
{
230+
string lengthSuffix = expressionLength < int.MaxValue
231+
? $"[{expressionLength}]"
232+
: string.Empty;
233+
234+
ctx.ReportDiagnostic(
235+
Diagnostic.Create(
236+
DiagnosticDescriptors.Rule0051PossibleOverflowAssigning,
237+
operation.ReturnedValue.Syntax.GetLocation(),
238+
$"{variable.GetTypeSymbol().ToDisplayString()}{lengthSuffix}",
239+
returnTypeSymbol.ToDisplayString()));
240+
}
241+
}
242+
164243
private static int GetTypeLength(ITypeSymbol type, ref bool isError)
165244
{
166245
if (!type.IsTextType())

0 commit comments

Comments
 (0)