44using BusinessCentral . LinterCop . Helpers ;
55using Microsoft . Dynamics . Nav . CodeAnalysis ;
66using Microsoft . Dynamics . Nav . CodeAnalysis . Diagnostics ;
7+ using Microsoft . Dynamics . Nav . CodeAnalysis . Semantics ;
78using Microsoft . Dynamics . Nav . CodeAnalysis . Symbols ;
9+ using Microsoft . Dynamics . Nav . CodeAnalysis . Syntax ;
810
911namespace 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