### LC0036: ToolTip Starting Verb Source: https://github.com/stefanmaron/businesscentral.lintercop/wiki/Home This rule enforces that `ToolTip` properties should start with the verb 'Specifies'. This provides a consistent and descriptive format for tooltips. ```AL field(1; "MyField"; Integer) { // ToolTip must start with the verb "Specifies". // ToolTip = 'This field indicates the value.'; // Incorrect ToolTip = 'Specifies the value of this field.'; // Correct DataClassification = ToBeClassified; } ``` -------------------------------- ### AL Interface Naming Convention Source: https://github.com/stefanmaron/businesscentral.lintercop/wiki/LC0054 Demonstrates the correct naming convention for interfaces in AL Language, requiring the name to start with 'I' followed by no spaces. Also shows an example of an incorrect prefix. ```AL interface ISubscriber { procedure Notify() } ``` ```AL interface "ABC ISubscriber" ``` -------------------------------- ### ToolTip Must Start with 'Specifies' Source: https://github.com/stefanmaron/businesscentral.lintercop/blob/master/README.md Enforces that the `ToolTip` property in Business Central AL must start with the verb 'Specifies'. This standardizes tooltip descriptions for better user understanding. ```AL LC0036: TOOLTIP must start with the verb "Specifies". ``` -------------------------------- ### app.json Runtime Version Example Source: https://github.com/stefanmaron/businesscentral.lintercop/wiki/LC0033 This JSON snippet demonstrates the 'runtime' property within the app.json file. It shows how to specify the application and platform versions, which the linter uses to determine if the runtime is falling behind. ```json "application": "22.5.0.0", "platform": "22.0.0.0", "runtime": "10.0", ``` -------------------------------- ### Direct Page.Run() - No Record Context Source: https://github.com/stefanmaron/businesscentral.lintercop/wiki/LC0027 An example of calling Page.Run() directly when there is no specific record context, such as opening the 'Customer Card' page without a customer record. ```AL Page.Run(Page::"Customer Card"); ``` -------------------------------- ### Configure LinterCop with LinterCop.json Source: https://github.com/stefanmaron/businesscentral.lintercop/blob/master/README.md This snippet shows how to configure the LinterCop by creating a `LinterCop.json` file in the project root. Changes to this file require a reload of VS Code to take effect. An example and default values are available. ```JSON { "//": "Example LinterCop.json configuration", "//": "Refer to LinterCop.json for default values and available options." } ``` -------------------------------- ### Copy LinterCop.json for Pipeline Usage Source: https://github.com/stefanmaron/businesscentral.lintercop/blob/master/README.md This example demonstrates how to copy the `LinterCop.json` file to the correct location within a BcContainerHelper container for use in a pipeline. The file needs to be placed at `C:\build\vsix\extension\bin\win32\LinterCop.json` before calling `Compile-AppInBcContainer`. ```PowerShell Copy-Item "LinterCop.json" "C:\build\vsix\extension\bin\win32\LinterCop.json" -Force Compile-AppInBcContainer -ContainerName $containerName -Path "C:\App" ``` -------------------------------- ### Interface Name Must Start with 'I' Source: https://github.com/stefanmaron/businesscentral.lintercop/blob/master/README.md Enforces that interface names in Business Central AL must start with the capital letter 'I' without any following spaces. This is a common naming convention for interfaces. ```AL LC0054: Interface name must start with the capital 'I' without any spaces following it. ``` -------------------------------- ### LC0030: Access Property for Install/Upgrade Codeunits Source: https://github.com/stefanmaron/businesscentral.lintercop/wiki/Home This rule recommends setting the `Access` property to `Internal` for Install and Upgrade codeunits. This limits their visibility and ensures they are only executed during the intended processes. ```AL codeunit 50100 "Install" { // Set Access property to `Internal` for Install/Upgrade codeunits. Access = Internal; trigger OnRun() begin // ... installation logic ... end; } ``` -------------------------------- ### AL: Safely Check for Empty GUID with IsNullGuid() Source: https://github.com/stefanmaron/businesscentral.lintercop/wiki/LC0087 Demonstrates the correct way to check if a GUID variable is empty in AL using the IsNullGuid() method. The incorrect method of comparing a GUID directly to an empty string is also shown, highlighting the potential for runtime errors. ```AL procedure MyProcedure() var MyGuid: Guid; begin if MyGuid = '' then; // Use 'IsNullGuid(MyGuid)' method instead of comparing the Guid 'MyGuid' with '', as this will result in a runtime error. end; ``` ```AL procedure MyProcedure() var MyGuid: Guid; begin if IsNullGuid(MyGuid) then; end; ``` -------------------------------- ### AL Cognitive Complexity Example 1 Source: https://github.com/stefanmaron/businesscentral.lintercop/wiki/LC0090 Demonstrates a nested conditional structure in AL and its calculated Cognitive Complexity. This example highlights how nesting increases complexity. ```AL procedure VerifyLineQuantity() begin if Type = Type::Item then begin // +1 (1 increment + 0 nesting penalty) if "Unit of Measure Code" <> '' then // +2 (1 increment + 1 nesting penalty) if Status = Status::Open then // +3 (1 increment + 2 nesting penalty) repeat // +4 (1 increment + 3 nesting penalty) if Quantity < 0 then // +5 (1 increment + 4 nesting penalty) HandleNegativeQuantity(); if Quantity = 0 then // +5 (1 increment + 4 nesting penalty) HandleZeroQuantity(); if Quantity > 0 then // +5 (1 increment + 4 nesting penalty) HandlePositiveQuantity(); until AllHandled(); end; end; // Cognitive Complexity: 25 ``` -------------------------------- ### Use Tok Suffix for Matching Label Values (AL) Source: https://github.com/stefanmaron/businesscentral.lintercop/wiki/LC0055 This AL code example demonstrates the correct usage of the 'Tok' suffix for labels where the label's value matches its name. It shows examples of correct and incorrect naming conventions, highlighting when the linter rule would suggest a change. ```AL GetTok: Label 'GET', Locked = true; GetLbl: Label 'GET', Locked = true; // The suffix 'Tok' is meant to be used when the value of the label matches the name. GetTxt: Label 'GET', Locked = true; // The suffix 'Tok' is meant to be used when the value of the label matches the name. NoSeriesSimulationModeStartedTxt: Label 'No. Series simulation mode started.', Locked = true; // The suffix 'Tok' is meant to be used when the value of the label matches the name. ``` ```AL PackagingLbl: Label 'Packaging'; PackagingTxt: Label 'Packaging'; ``` -------------------------------- ### AL Cognitive Complexity Example 2 Source: https://github.com/stefanmaron/businesscentral.lintercop/wiki/LC0090 Presents a refactored version of the AL code to reduce Cognitive Complexity. This example shows how early exits can simplify the structure and lower the complexity score. ```AL procedure VerifyLineQuantity() begin if Type <> Type::Item then exit; if "Unit of Measure Code" = '' then exit; if Status <> Status::Open then exit; repeat // +1 (1 increment + 0 nesting penalty) if Quantity < 0 then // +2 (1 increment + 1 nesting penalty) HandleNegativeQuantity(); if Quantity = 0 then // +2 (1 increment + 1 nesting penalty) HandleZeroQuantity(); if Quantity > 0 then // +2 (1 increment + 1 nesting penalty) HandlePositiveQuantity(); until AllHandled(); end; // Cognitive Complexity: 7 ``` -------------------------------- ### AL Enum: Correct Usage (Starting from 1) Source: https://github.com/stefanmaron/businesscentral.lintercop/wiki/LC0045 This AL code snippet demonstrates the correct way to define an Enum, starting the values from 1 and reserving 0 for a potential empty value. This adheres to the LinterCop rule LC0045. ```AL enum 50100 MyEnum { value(1; Quote) { Caption = 'Quote'; } } ``` -------------------------------- ### Invalid Record.Get() Usage Examples Source: https://github.com/stefanmaron/businesscentral.lintercop/wiki/LC0075 Demonstrates incorrect usage of the Record.Get() method with insufficient, too many, or wrongly typed arguments, leading to potential runtime errors. These examples highlight common mistakes caught by the linter. ```AL procedure GetItemVariant() var ItemVariant: Record "Item Variant"; begin // Invalid arguments in .Get() method for record "Item Variant": Insufficient arguments provided; expected 2 arguments. ItemVariant.Get('10000'); end; ``` ```AL procedure GetSalesHeader(DocumentId: Integer) var SalesHeader: Record "Sales Header"; begin // Invalid arguments in .Get() method for record "Sales Header": Argument at position 2 has an invalid type; expected 'Code[20]', found 'Integer'. SalesHeader.Get("Sales Document Type"::Order, DocumentId); end; ``` ```AL procedure GetCompanyInformation() var CompanyInformation: Record "Company Information"; begin // Invalid arguments in .Get() method for record "Company Information": Too many arguments provided; expected 1 arguments. CompanyInformation.Get('', 12345); end; ``` -------------------------------- ### Correct Line Feed Usage with Type Helper in AL Source: https://github.com/stefanmaron/businesscentral.lintercop/wiki/LC0085 Shows the recommended approach in AL for handling line feed separators by utilizing the LFSeparator function from the 'Type Helper' codeunit. It also includes an example of reading text with a separator. ```AL procedure MyProcedure() var TypeHelper: Codeunit "Type Helper"; LineSeparator: Text; begin LineSeparator := TypeHelper.LFSeparator(); while MyCondition do TextBuilder.Append(TypeHelper.ReadAsTextWithSeparator(InStream, LineSeparator)); end; ``` -------------------------------- ### Handle Database Read Return Value in AL Source: https://github.com/stefanmaron/businesscentral.lintercop/wiki/LC0084 This AL code example demonstrates how to properly handle the return value of database read methods. It shows how to exit gracefully if a record is not found or emit specific error information using an ErrorInfo data type. ```AL procedure MyProcedure() var SalesSetup: Record "Sales & Receivables Setup"; begin if not SalesSetup.Get() then exit; // gracefully stop executing when setup is not available end; procedure GetCustomer(CustomerNo: Code[20]) var Customer: Record Customer; MyErrorHandler: Codeunit "My Error Handler"; begin if not Customer.Get(CustomerNo) then MyErrorHandler.EmitErrorInfo(CustomerNotFound, CustomerNo); end; ``` -------------------------------- ### AL TableData Access Permissions Source: https://github.com/stefanmaron/businesscentral.lintercop/wiki/LC0068 This snippet demonstrates AL code examples that trigger the LC0068 diagnostic due to missing table data access permissions. It includes common operations like Insert, Modify, Read, and Delete on a table. ```AL // Insert MyTable.Insert(); // Modify MyTable.Modify(); MyTable.Rename(); // Read MyTable.Find(); MyTable.FindFirst(); MyTable.FindLast(); MyTable.FindSet(); MyTable.IsEmpty(); // Delete MyTable.Delete(); MyTable.DeleteAll(); ``` -------------------------------- ### Extracting Day of Week and Week Number (AL) Source: https://github.com/stefanmaron/businesscentral.lintercop/wiki/LC0083 Shows the usage of new methods to get the day of the week and the week number from a Date type in AL. These replace older functions like Date2DWY. ```AL MyDate.DayOfWeek() MyDate.WeekNo() ``` -------------------------------- ### CDS Table Type with Option Field (AL) Source: https://github.com/stefanmaron/businesscentral.lintercop/wiki/LC0088 Shows an example of a CDS (Common Data Service) table type in AL that includes an Option field. This Option field is generated from a Dataverse OptionSet, demonstrating a valid use case for Option types when interacting with external data sources. ```AL table 50100 "Dataverse Project PTE" { ExternalName = 'prefix_project'; TableType = CDS; Caption = 'Project'; fields { field(1; prefix_projectId; Guid) { ExternalName = 'prefix_projectid'; ExternalType = 'Uniqueidentifier'; ExternalAccess = Insert; Description = 'Unique identifier for entity instances'; Caption = 'Project'; } field(2; prefix_name; Text[100]) { ExternalName = 'prefix_name'; ExternalType = 'String'; Description = 'The name of the custom entity.'; Caption = 'Name'; } field(25; statecode; Option) { ExternalName = 'statecode'; ExternalType = 'State'; ExternalAccess = Modify; Description = 'Status of the Project'; Caption = 'Status'; InitValue = " "; OptionMembers = " ",Active,Inactive; OptionOrdinalValues = -1, 0, 1; } } } ``` -------------------------------- ### AL Documentation Comment Syntax Validation Source: https://github.com/stefanmaron/businesscentral.lintercop/wiki/LC0072 This AL code snippet demonstrates scenarios where documentation comments do not match the procedure syntax, triggering the LC0072 diagnostic. It includes examples of missing procedure parameters, missing documentation parameters, and parameter name mismatches. ```AL /// /// Documentation comment parameter but no procedure parameter. /// /// The value. // LC0072: The documentation comment must match the procedure syntax. procedure NoParameter() begin end; /// /// Procedure parameter but no documentation comment parameter. /// procedure ParameterButNoComment(Value: Boolean) // LC0072: The documentation comment must match the procedure syntax. begin end; /// /// Parameter name mismatch. /// /// The value. // LC0072: The documentation comment must match the procedure syntax. procedure NameMissmatch(Value: Boolean) // LC0072: The documentation comment must match the procedure syntax. begin end; ``` -------------------------------- ### Option Type Usage in Exception Scenario (AL) Source: https://github.com/stefanmaron/businesscentral.lintercop/wiki/LC0088 Illustrates a scenario where an Option type is used within a procedure, highlighting a potential readability issue if converted to an integer. This serves as an example of an exception to the general rule of avoiding Option types. ```AL internal procedure DeleteReservEntries() var ReservationManagement: Codeunit "Reservation Management"; Mode: Option "None","Allow deletion",Match; begin ReservationManagement.SetReservSource(Rec); ReservationManagement.SetItemTrackingHandling(Mode::"Allow deletion"); ReservationManagement.DeleteReservEntries(true, 0); end; ``` -------------------------------- ### Prevent Text Overflow in Get Procedure Source: https://github.com/stefanmaron/businesscentral.lintercop/wiki/LC0051 This example demonstrates a potential overflow when concatenating a 'Label' with a 'Code[20]' variable and passing it to the Get procedure. It highlights the risk of exceeding the target field's length. ```AL procedure MyProcedure(OrderNumber: Code[20]) var SalesHeader: Record "Sales Header"; SalesOrderPrefixTok: Label 'ORD', Locked = true; begin SalesHeader.Get(Enum::"Sales Document Type"::Order, SalesOrderPrefixTok + OrderNumber); // Possible overflow assigning 'Text[23]' to 'Code[20]'. end; ``` -------------------------------- ### Run Codespace Prep Task Source: https://github.com/stefanmaron/businesscentral.lintercop/blob/master/README.md Instructions on how to execute the 'Prep Codespace' task within GitHub Codespaces to set up the development environment. This involves opening the command palette and selecting the task. ```Shell F1 -> Run Task -> Prep Codespace ``` -------------------------------- ### Declaring Page Variable and Running Source: https://github.com/stefanmaron/businesscentral.lintercop/wiki/LC0027 Shows how to declare a page variable, set a record context for it, and then run the page. ```AL local procedure SelectCustomer() var CustomerRec: Record Customer; CustomerCard: Page "Customer Card"; begin CustomerCard.SetRecord(CustomerRec); CustomerCard.Run(); end; ``` -------------------------------- ### LC0027: Page Management for Launching Pages Source: https://github.com/stefanmaron/businesscentral.lintercop/wiki/Home This rule recommends utilizing the `Page Management` codeunit for launching pages. This provides a centralized and consistent way to manage page navigation. ```AL codeunit 50100 "MyCodeunit" { // Utilize the `Page Management` codeunit for launching page trigger OnRun() begin // Page.Run(Page::"MyPage"); // Incorrect usage // PageManagement.Run(Page::"MyPage"); // Correct usage end; } ``` -------------------------------- ### Using Page.RunModal() with Action Return Source: https://github.com/stefanmaron/businesscentral.lintercop/wiki/LC0027 Demonstrates using Page.RunModal() to open a page and checking the return action to determine if the operation was successful, like 'LookupOK'. ```AL local procedure SelectCustomer() var CustomerRec: Record Customer; begin if Page.RunModal(Page::"Customer Lookup", CustomerRec) = Action::LookupOK then DoStuffWithSelectedCustomer(CustomerRec); end; ``` -------------------------------- ### LC0087: Use IsNullGuid() for GUID Checks Source: https://github.com/stefanmaron/businesscentral.lintercop/blob/master/README.md Recommends using the `IsNullGuid()` function for checking if a GUID value is null (empty). This is a clear and standard way to perform this validation. ```AL table 50100 MyTable { fields { field(1; "MyGuid"; GUID) { Caption = 'My GUID'; } } } codeunit 50100 MyCodeunit { ... procedure CheckGuid(var MyRecord: Record MyTable) begin // Incorrect: Comparing GUID to an empty string or literal // if MyRecord."MyGuid" = ''Guid then // Correct: if MyRecord."MyGuid".IsNullGuid() then Message('GUID is null.'); else Message('GUID has a value.'); end; ... } ``` -------------------------------- ### Build and Debug Analyzer Source: https://github.com/stefanmaron/businesscentral.lintercop/blob/master/README.md Steps to build the analyzer project and attach the debugger in Visual Studio Code. This includes running the 'Build' task, which compiles the .NET code and reopens VS Code, followed by attaching the debugger to the 'Microsoft.Dynamics.Nav.EditorServices.Host' process. ```Shell F1 -> Run Task -> Build ``` ```Shell Set breakpoint in Analyzer code. Press F5. Select 'Microsoft.Dynamics.Nav.EditorServices.Host' from the process list. ``` -------------------------------- ### LC0087: Use IsNullGuid() for GUID Check Source: https://github.com/stefanmaron/businesscentral.lintercop/wiki/Home Promotes the use of the `IsNullGuid()` function for checking if a GUID variable is empty (null). This is the standard and most reliable way to perform this check in AL. ```AL var MyGuid: GUID; begin // Incorrect: Comparing GUID to an empty string or default value // if MyGuid = '' then // Message('GUID is null.'); // Correct: Using IsNullGuid() if IsNullGuid(MyGuid) then Message('GUID is null.'); end; ``` -------------------------------- ### LC0027: Use Page Management Codeunit Source: https://github.com/stefanmaron/businesscentral.lintercop/blob/master/README.md This rule recommends using the `Page Management` codeunit for launching pages. This centralizes page navigation logic and ensures consistency. ```AL PageManagement.OpenPage(Page::"My Page"); ``` -------------------------------- ### Direct Page.Run() Call Source: https://github.com/stefanmaron/businesscentral.lintercop/wiki/LC0027 Demonstrates the direct invocation of the Page.Run() function to open a specific page, such as the 'Sales Quote'. This method is contrasted with using the Page Management codeunit. ```AL local procedure OpenPage() var SalesHeader: Record "Sales Header"; begin Page.Run(Page::"Sales Quote", SalesHeader); end; ``` -------------------------------- ### Page Management for List Pages Source: https://github.com/stefanmaron/businesscentral.lintercop/wiki/LC0027 Shows how to use the Page Management codeunit for List pages. It includes logic to either run the default lookup page or the card page based on the record count. ```AL local procedure OpenPage() var SalesHeader: Record "Sales Header"; PageManagement: Codeunit "Page Management"; begin if SalesHeader.Count() = 1 then PageManagement.PageRun(SalesHeader) else Page.Run(PageManagement.GetDefaultLookupPageIDByVar(SalesHeader), SalesHeader); end; ``` -------------------------------- ### AL Procedure Declaration Example Source: https://github.com/stefanmaron/businesscentral.lintercop/wiki/LC0024 Demonstrates the correct and incorrect syntax for AL procedure declarations, highlighting the semicolon that should be removed. ```AL procedure MyProcedure(); /* <-- that ; (semicolon) should not be there */ begin end; ``` -------------------------------- ### Implement Confirm() via Confirm Management Codeunit (AL) Source: https://github.com/stefanmaron/businesscentral.lintercop/wiki/LC0021 Demonstrates how to correctly implement the Confirm() function using the 'Confirm Management' codeunit in Business Central. This method ensures automatic validation of IsGuiAllowed() and allows setting a default response, improving user interaction. ```AL var ConfirmManagement: Codeunit "Confirm Management"; DefaultAnswer: boolean; DefaultAnswer := false; if ConfirmManagement.GetResponseOrDefault(AreYouSureQst, DefaultAnswer) then DoSomething(); ``` -------------------------------- ### LC0072: Documentation Comment Syntax Mismatch Source: https://github.com/stefanmaron/businesscentral.lintercop/wiki/Home Ensures that documentation comments (using `///`) correctly match the syntax of the procedure they are documenting. This promotes accurate and useful documentation for developers. ```AL codeunit 50100 MyCodeunit { /// /// Adds two integers. /// /// The first integer. /// The second integer. /// The sum of a and b. procedure Add(a: Integer; b: Integer): Integer begin exit a + b; end; /// /// Subtracts two integers. /// // Incorrect: Missing parameter documentation for 'a' and 'b' procedure Subtract(a: Integer; b: Integer): Integer begin exit a - b; end; } ``` -------------------------------- ### Set Access Property to Internal for Install/Upgrade Codeunits Source: https://github.com/stefanmaron/businesscentral.lintercop/blob/master/README.md Advises setting the `Access` property to `Internal` for install and upgrade codeunits in Business Central AL. This helps in managing the visibility and scope of these critical codeunits. ```AL LC0030: Set Access property to Internal for Install/Upgrade codeunits. ``` -------------------------------- ### LC Source: https://github.com/stefanmaron/businesscentral.lintercop/wiki/Home No description -------------------------------- ### AL: Show Cognitive Complexity for SumOfPrimes method Source: https://github.com/stefanmaron/businesscentral.lintercop/wiki/LC0090 This AL code snippet demonstrates the 'SumOfPrimes' procedure, which calculates the sum of prime numbers up to a given maximum. The comments indicate its Cyclomatic Complexity and the calculated Cognitive Complexity, illustrating how nesting and control flow affect the latter. ```AL procedure SumOfPrimes(Max: Integer): Integer var Total: Integer; I, J : Integer; IsPrime: Boolean; begin Total := 0; for I := 1 to Max do begin IsPrime := true; for J := 2 to I - 1 do begin if (I mod J = 0) then begin IsPrime := false; break; end; end; if IsPrime then Total += I; end; exit(Total); end; // Cyclomatic Complexity: 5 ``` -------------------------------- ### Disable Rules with Pragmas Source: https://github.com/stefanmaron/businesscentral.lintercop/blob/master/README.md This example shows how to disable a specific LinterCop rule for a particular section of AL code using pragmas. This is useful for temporary exceptions or when a rule's suggestion is not applicable in a specific context. ```AL #pragma warning disable SomeRuleId // Code that might violate SomeRuleId #pragma warning restore SomeRuleId ``` -------------------------------- ### LC0082: Consider Query or Rec.Find('-') with Rec.Next() Source: https://github.com/stefanmaron/businesscentral.lintercop/wiki/Home Suggests using AL Queries or the `Rec.Find('-')` combined with `Rec.Next()` pattern for iterating through records, especially when complex filtering or sorting is involved. These methods can offer better performance and readability than manual loops. ```AL table 50100 MyTable { fields { field(1; SystemId; GUID) { DataClassification = SystemMetadata; } field(2; Name; Text[100]) { Caption = 'Name'; } } } codeunit 50100 MyCodeunit { procedure ProcessRecords() var MyRecord: Record "MyTable"; begin // Consider using a Query or Find('-')/Next() pattern for better performance // if MyRecord.FindSet() then // repeat // Message('Processing: %1', MyRecord.Name); // until MyRecord.Next() = 0; // Example using Find('-') and Next() if MyRecord.Find('-') then repeat Message('Processing: %1', MyRecord.Name); until MyRecord.Next() = 0; end; } ``` -------------------------------- ### Compare DateTime vs Direct Comparison in AL Source: https://github.com/stefanmaron/businesscentral.lintercop/wiki/LC0029 Demonstrates the difference between direct DateTime variable comparison and using the `CompareDateTime` method from the `Type Helper` codeunit in AL. The `CompareDateTime` method is recommended to account for SQL Server's rounding behavior. ```AL codeunit 50000 "Insert DateTime" { trigger OnRun() var Customer: Record Customer; NewLastModifiedDateTime: DateTime; begin Customer.Get('10000'); Evaluate(NewLastModifiedDateTime, '2022-05-24T12:40:22.468Z', 9); Customer."Last Modified Date Time" := NewLastModifiedDateTime; Customer.Modify(); Message('After Modify %1 = %2', Format(Customer."Last Modified Date Time", 0, 9), '2022-05-24T12:40:22.468Z'); Commit(); Customer.Get('10000'); Message('After Second Get %1 <> %2', Format(Customer."Last Modified Date Time", 0, 9), '2022-05-24T12:40:22.468Z'); end; } ``` ```AL // Direct comparing if (FirstRecord."DateTimeField" <> SecondRecord."DateTimeField") then // Do Magic if DateTime Fields are different // Comparing with CompareDateTime method if (TypeHelper.CompareDateTime(FirstRecord."DateTimeField", SecondRecord."DateTimeField") <> 0) then // Do Magic if DateTime Fields are different ``` -------------------------------- ### Record.Get() Exceptions for Singleton Tables and RecordId Source: https://github.com/stefanmaron/businesscentral.lintercop/wiki/LC0075 Illustrates exceptions to the Record.Get() rule, specifically for singleton pattern tables where a parameterless Get() is allowed, and when using a RecordId object which is populated at runtime. ```AL procedure GetCompanyInformation() var CompanyInformation: Record "Company Information"; begin // If a table has one single Primary Key field of type Code, it's probably a setup table with the Singleton pattern, where we allow a GET without parameters CompanyInformation.Get(); end; ``` ```AL procedure GetItemVariant(MyRecordId: RecordId) var ItemVariant: Record "Item Variant"; begin // RecordId object is populated on runtime, no diagnostics executed ItemVariant.Get(MyRecordId); end; ``` -------------------------------- ### AL: Call GlobalLanguage() via Translation Helper Source: https://github.com/stefanmaron/businesscentral.lintercop/wiki/LC0022 Illustrates the recommended approach for managing GlobalLanguage() by utilizing the 'Translation Helper' codeunit. This method simplifies language setting and restoration using dedicated functions. ```AL local procedure MyAwesomeFunction() var TranslationHelper: Codeunit "Translation Helper"; begin TranslationHelper.SetGlobalLanguageByCode(Rec."Language Code"); // Some code here TranslationHelper.RestoreGlobalLanguage(); end; ``` -------------------------------- ### Suppress LC0001 with Pragma Directive Source: https://github.com/stefanmaron/businesscentral.lintercop/wiki/LC0001 This example illustrates how to suppress the LC0001 diagnostic for a FlowField that needs to be editable. It uses a pragma directive to disable the warning and includes a comment explaining the deviation from best practice. ```AL #pragma warning disable LC0001 // FlowField needs to be editable because ... field(1; MyField; Integer) { FieldClass = FlowField; } #pragma warning restore LC0001 ``` -------------------------------- ### LC0082: Consider Query or Rec.Find('-') with Rec.Next() Source: https://github.com/stefanmaron/businesscentral.lintercop/blob/master/README.md Suggests using AL Queries or the `Rec.Find('-')` combined with `Rec.Next()` for iterating through records. These methods can be more efficient than other looping mechanisms. ```AL table 50100 MyTable { fields { field(1; "Code"; Code[20]) { Caption = 'Code'; } field(2; "Description"; Text[100]) { Caption = 'Description'; } } } codeunit 50100 MyCodeunit { ... procedure IterateRecords() var MyRecord: Record MyTable; begin // Consider using AL Queries for complex data retrieval. // Alternative 1: Using Find('-') and Next() if MyRecord.FindSet(false, false) then repeat Message('Code: %1, Desc: %2', MyRecord.Code, MyRecord.Description); until MyRecord.Next() = 0; // Alternative 2: Using Find('-') and Next() for specific order // if MyRecord.Find('-') then // repeat // Message('Code: %1, Desc: %2', MyRecord.Code, MyRecord.Description); // until MyRecord.Next() = 0; end; ... } ``` -------------------------------- ### Extracting Date Parts using New Methods (AL) Source: https://github.com/stefanmaron/businesscentral.lintercop/wiki/LC0083 Demonstrates how to use the new, more intuitive methods for extracting day, month, and year from a Date type in AL. These methods offer improved readability compared to older functions like Date2DMY. ```AL MyDate.Day() MyDate.Month() MyDate.Year() ``` -------------------------------- ### LC0067: NotBlank Property with TableRelation Source: https://github.com/stefanmaron/businesscentral.lintercop/wiki/Home Suggests setting the 'NotBlank' property to 'false' when a table field has a 'TableRelation' that includes a 'No. Series' setup. This helps prevent potential issues where a required field might conflict with the automatic numbering provided by a series. ```AL table 50100 MyTable { fields { field(1; SystemId; GUID) { DataClassification = SystemMetadata; } field(2; OrderNo; Code[20]) { Caption = 'Order No.'; TableRelation = "Sales Header"."Document No." WHERE ("Document Type" = CONST(Order)); // Consider setting NotBlank = false if 'No. Series' is used for Order No. // NotBlank = false; } } } ``` -------------------------------- ### AL: Show Cognitive Complexity for GetWords method Source: https://github.com/stefanmaron/businesscentral.lintercop/wiki/LC0090 This AL code snippet defines the 'GetWords' procedure, which converts an integer into its corresponding word representation using a case statement. The comments show its Cyclomatic Complexity and Cognitive Complexity, highlighting its simplicity compared to the 'SumOfPrimes' method. ```AL procedure GetWords(Number: Integer): Text begin case Number of 1: exit('one'); 2: exit('a couple'); 3: exit('a few'); 4: exit('a quadruple'); else exit('lots'); end; end; // Cyclomatic Complexity: 5 ``` -------------------------------- ### Avoid Empty Statements in AL Source: https://github.com/stefanmaron/businesscentral.lintercop/wiki/LC0069 This AL code example demonstrates the detection of empty statements (excess semicolons) within a procedure. The linter flags these for removal or requires a comment to explain their purpose, improving code clarity. ```AL local procedure Example(SomeVar: Boolean) begin if SomeVar then begin ; <<-- this excess semicolon should be removed Message('hello'); Message('world'); end; ; <<-- same here end; ``` -------------------------------- ### Extracting Time Parts using New Methods (AL) Source: https://github.com/stefanmaron/businesscentral.lintercop/wiki/LC0083 Illustrates the use of new methods for extracting hour, minute, second, and millisecond from a Time type in AL. These are replacements for older, more complex formatting methods. ```AL MyTime.Hour() MyTime.Minute() MyTime.Second() MyTime.Millisecond() ``` -------------------------------- ### JSON Configuration for LinterCop Threshold Source: https://github.com/stefanmaron/businesscentral.lintercop/wiki/LC0090 Shows how to configure the cognitive complexity threshold for the LC0090 diagnostic in LinterCop using a JSON file. This allows customization of complexity warnings based on project needs. ```JSON { "cognitiveComplexityThreshold": 15 } ``` -------------------------------- ### Unused Internal Method Example in AL Source: https://github.com/stefanmaron/businesscentral.lintercop/wiki/LC0052 This AL code snippet demonstrates an internal procedure 'MyProcedure' within a codeunit that is declared but not used. This is the scenario that the Business Central Linter Cop rule AA0228 aims to detect. ```AL codeunit 50100 MyCodeunit { internal procedure MyProcedure() begin // My Business Logic here end; } ``` -------------------------------- ### Page.Run() via Page Management Codeunit Source: https://github.com/stefanmaron/businesscentral.lintercop/wiki/LC0027 Illustrates calling the Page.Run() function indirectly through the 'Page Management' codeunit. This approach offers benefits like automatic page determination and GUI checks. ```AL local procedure OpenPage() var SalesHeader: Record "Sales Header"; PageManagement: Codeunit "Page Management"; begin PageManagement.PageRun(SalesHeader); end; ``` -------------------------------- ### BusinessCentral.LinterCop Test Case Folder Structure Source: https://github.com/stefanmaron/businesscentral.lintercop/blob/master/README.md This bash snippet outlines the expected directory structure for unit tests within the BusinessCentral.LinterCop project. It shows the organization of test cases, including subfolders for each rule and for positive ('HasDiagnostic') and negative ('NoDiagnostic') test scenarios. ```Bash ├───BusinessCentral.LinterCop.Test │ ├───RoslynTestKit │ │ ├───CodeActionLocators │ │ └───Utils │ └───TestCases │ ├───Rule0001 │ │ ├───HasDiagnostic │ │ │ ├───1.al │ │ │ ├───2.al │ │ │ └───X.al │ │ └───NoDiagnostic │ │ │ ├───1.al │ │ │ ├───2.al │ │ │ └───X.al │ ├───Rule0002 │ │ ├───HasDiagnostic │ │ │ ├───X.al │ │ └───NoDiagnostic │ │ │ ├───X.al │ ├───RuleXXXX │ │ ├───HasDiagnostic │ │ │ ├───X.al │ │ └───NoDiagnostic │ │ │ ├───X.al ├───Rule0001.cs ├───Rule0002.cs └───RuleXXXX.cs ``` -------------------------------- ### Event Publishers Should Be Local or Internal (AL) Source: https://github.com/stefanmaron/businesscentral.lintercop/wiki/LC0079 This AL code example demonstrates an event publisher that should be declared as local or internal. Public event publishers restrict future parameter modifications due to AppSourceCop's breaking change policy. ```AL [IntegrationEvent(false, false)] procedure OnAfterInitFromSalesHeader(var SalesHeader: Record "Sales Header"; SourceSalesHeader: Record "Sales Header") // Event Publishers should be local or internal to allow for future parameter extensions. begin end; ``` -------------------------------- ### Extracting Date and Time from DateTime (AL) Source: https://github.com/stefanmaron/businesscentral.lintercop/wiki/LC0083 Demonstrates the new methods to extract the Date and Time components from a DateTime type in AL, simplifying the process compared to older conversion functions like DT2Date and DT2Time. ```AL MyDateTime.Date() MyDateTime.Time() ``` -------------------------------- ### Avoid Triggers on Temporary Records in AL Source: https://github.com/stefanmaron/businesscentral.lintercop/wiki/LC0078 This AL code example demonstrates the incorrect usage of triggers and the Validate method on a temporary 'Sales Header' record. The rule flags these operations as they can lead to unintended database modifications outside the temporary scope. ```AL procedure TempSalesHeader(CustomerNo: Code[20]) var SalesHeader: Record "Sales Header" temporary; begin SalesHeader.Init(); SalesHeader.Validate("Sell-to Customer No.", CustomerNo); // Temporary records should not trigger the table triggers. SalesHeader.Insert(true); // Temporary records should not trigger the table triggers. end; ``` -------------------------------- ### LC0086: Use PageStyle Datatype Source: https://github.com/stefanmaron/businesscentral.lintercop/wiki/Home Recommends using the `PageStyle` datatype instead of string literals for page styling properties. This provides type safety and better IntelliSense support. ```AL page 50100 MyPage { PageType = Card; layout { area(content) { field(Name; Name) { // Incorrect: Using string literal for style // ApplicationArea = All; // Style = "Strong"; // Correct: Using PageStyle datatype ApplicationArea = All; Style = Strong; } } } } ``` -------------------------------- ### LC0077: Method Calls with Parentheses Source: https://github.com/stefanmaron/businesscentral.lintercop/wiki/Home Recommends always calling methods with parentheses, even if they have no parameters. This improves code clarity and consistency, making it explicit that a method is being invoked. ```AL codeunit 50100 MyCodeunit { procedure MyMethod() begin Message('Called'); end; procedure AnotherMethod(Value: Integer) begin Message('Value is %1', Value); end; procedure TestCalls() begin // Incorrect: Calling method without parentheses // MyMethod; // AnotherMethod 5; // Correct: Calling methods with parentheses MyMethod(); AnotherMethod(5); end; } ``` -------------------------------- ### LC0040: RunTrigger Parameter Source: https://github.com/stefanmaron/businesscentral.lintercop/wiki/Home This rule recommends explicitly setting the `RunTrigger` parameter on build-in methods. This provides clarity on when and how these methods are executed. ```AL table 50100 "MyTable" { // Explicitly set the `RunTrigger` parameter on build-in methods. fields { field(1; "Primary Key"; Integer) { DataClassification = ToBeClassified; } } keys { key(PK; "Primary Key") { Clustered = true; } } trigger OnInsert() begin // CalcFields("MyFlowField", true); // Incorrect: RunTrigger not explicitly set // CalcFields("MyFlowField", false, true); // Correct: RunTrigger explicitly set to true end; } ``` -------------------------------- ### Detect Zero Index Access in AL Lists Source: https://github.com/stefanmaron/businesscentral.lintercop/wiki/LC0070 This snippet demonstrates how the Business Central Linter Cop identifies incorrect zero-based index access on 1-based List objects in AL. It highlights a common error where a loop starts from index 0 or attempts to retrieve an element at index 0, which is invalid for 1-based lists. ```AL procedure MyProcedure() var myList: List of [Integer]; i: Integer; begin for i := 0 to myList.Count() do; end; ``` ```AL procedure MyProcedure() var myList: List of [Integer]; begin myList.Get(0); end; ``` -------------------------------- ### LC0022: GlobalLanguage() Translation Helper Source: https://github.com/stefanmaron/businesscentral.lintercop/wiki/Home This rule recommends using the `Translation Helper` codeunit for `GlobalLanguage()` calls. This promotes a standardized approach to handling language settings. ```AL codeunit 50100 "MyCodeunit" { // `GlobalLanguage()` must be implemented through the `Translation Helper` codeunit trigger OnRun() begin // var MyLang: Integer; // MyLang := GlobalLanguage(); // Incorrect usage // Use TranslationHelper.GlobalLanguage(); // Correct usage end; } ``` -------------------------------- ### CSharp Unit Test Class for LinterCop Rules Source: https://github.com/stefanmaron/businesscentral.lintercop/blob/master/README.md This C# code defines a unit test class for a specific LinterCop rule. It sets up the test environment, loads test case files, and asserts the presence or absence of diagnostics using the RoslynFixture. ```CSharp namespace BusinessCentral.LinterCop.Test; public class RuleXXXX // Id of the rule that is being tested { private string _testCaseDir = ""; [SetUp] public void Setup() { _testCaseDir = Path.Combine(Directory.GetParent(Environment.CurrentDirectory)!.Parent!.Parent!.FullName, "TestCases", "RuleXXXX"); // Set path to subfolder with test cases } [Test] [TestCase("1")] // Test case 1.al [TestCase("2")] // Test case 2.al ... [TestCase("n")] public async Task HasDiagnostic(string testCase) // Positive test { // Load code file for test case var code = await File.ReadAllTextAsync(Path.Combine(_testCaseDir, "HasDiagnostic", $"{testCase}.al")) .ConfigureAwait(false); // Create fixture for rule var fixture = RoslynFixtureFactory.Create(); // Check for reported diagnostic fixture.HasDiagnostic(code, DiagnosticDescriptors.RuleXXXXMyCodeRule.Id); } [Test] [TestCase("1")] // Test case 1.al [TestCase("2")] // Test case 2.al ... [TestCase("n")] public async Task NoDiagnostic(string testCase) // Negative test { // Load code file for test case var code = await File.ReadAllTextAsync(Path.Combine(_testCaseDir, "NoDiagnostic", $"{testCase}.al")) .ConfigureAwait(false); // Create fixture for rule var fixture = RoslynFixtureFactory.Create(); // Check for no reported diagnostic fixture.NoDiagnosticAtMarker(code, DiagnosticDescriptors.RuleXXXXMyCodeRule.Id); } } ```