### 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);
}
}
```