Skip to content

Commit

Permalink
Merge pull request #912 from StefanMaron/prerelease
Browse files Browse the repository at this point in the history
Merge 'prerelease' into master
  • Loading branch information
Arthurvdv authored Jan 30, 2025
2 parents 0f6f3bf + 5d00d45 commit c3cb82a
Show file tree
Hide file tree
Showing 22 changed files with 278 additions and 41 deletions.
43 changes: 43 additions & 0 deletions BusinessCentral.LinterCop.Test/Rule0048.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
namespace BusinessCentral.LinterCop.Test;

public class Rule0048
{
private string _testCaseDir = "";

[SetUp]
public void Setup()
{
_testCaseDir = Path.Combine(Directory.GetParent(Environment.CurrentDirectory)!.Parent!.Parent!.FullName,
"TestCases", "Rule0048");
}

#if !LessThenSpring2024
[Test]
[TestCase("ErrorWithLiteralExpression")]
[TestCase("ErrorWithStrSubstNo")]
[TestCase("ErrorWithTextVariable")]
#endif
public async Task HasDiagnostic(string testCase)
{
var code = await File.ReadAllTextAsync(Path.Combine(_testCaseDir, "HasDiagnostic", $"{testCase}.al"))
.ConfigureAwait(false);

var fixture = RoslynFixtureFactory.Create<Rule0048ErrorWithTextConstant>();
fixture.HasDiagnostic(code, DiagnosticDescriptors.Rule0048ErrorWithTextConstant.Id);
}

[Test]
[TestCase("ErrorWithErrorInfo")]
[TestCase("ErrorWithLabel")]
#if !LessThenFall2024
[TestCase("ErrorWiththisLabel")]
#endif
public async Task NoDiagnostic(string testCase)
{
var code = await File.ReadAllTextAsync(Path.Combine(_testCaseDir, "NoDiagnostic", $"{testCase}.al"))
.ConfigureAwait(false);

var fixture = RoslynFixtureFactory.Create<Rule0048ErrorWithTextConstant>();
fixture.NoDiagnosticAtMarker(code, DiagnosticDescriptors.Rule0048ErrorWithTextConstant.Id);
}
}
2 changes: 2 additions & 0 deletions BusinessCentral.LinterCop.Test/Rule0051.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public void Setup()
[TestCase("GetMethodStrSubstNo")]
#endif
[TestCase("SetFilterFieldCode")]
[TestCase("ValidateFieldCode")]
public async Task HasDiagnostic(string testCase)
{
var code = await File.ReadAllTextAsync(Path.Combine(_testCaseDir, "HasDiagnostic", $"{testCase}.al"))
Expand All @@ -35,6 +36,7 @@ public async Task HasDiagnostic(string testCase)
[TestCase("GetMethodUserId")]
#endif
[TestCase("SetFilterFieldRef")]
[TestCase("ValidateFieldCode")]
public async Task NoDiagnostic(string testCase)
{
var code = await File.ReadAllTextAsync(Path.Combine(_testCaseDir, "NoDiagnostic", $"{testCase}.al"))
Expand Down
2 changes: 2 additions & 0 deletions BusinessCentral.LinterCop.Test/Rule0075.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ public void Setup()
[TestCase("ImplicitConversiontCodeToEnum")]
[TestCase("ImplicitConversiontEnumToAnotherEnum")]
[TestCase("RecordGetCodeFieldLengthTooLong")]
[TestCase("RecordGetEnum")]
[TestCase("RecordGetGlobalVariable")]
[TestCase("RecordGetLocalVariable")]
[TestCase("RecordGetMethod")]
Expand All @@ -41,6 +42,7 @@ public async Task HasDiagnostic(string testCase)
[TestCase("RecordGetBuiltInMethodRecordId")]
[TestCase("RecordGetCode10ToCode20")]
[TestCase("RecordGetDecimalToInteger")]
[TestCase("RecordGetEnum")]
[TestCase("RecordGetFieldRecordId")]
[TestCase("RecordGetGlobalVariable")]
[TestCase("RecordGetLocalVariable")]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
codeunit 50100 MyCodeunit
{
procedure MyProcedure()
begin
[|Error('Something went wrong...')|];
end;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
codeunit 50100 MyCodeunit
{
procedure MyProcedure()
var
MyTable: Record MyTable;
UnexpectedErr: Label '%1 is not an valid Record.';
begin
[|Error(StrSubstNo(UnexpectedErr, MyTable.MyField))|];
end;
}

table 50100 MyTable
{
fields
{
field(1; MyField; Integer) { }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
codeunit 50100 MyCodeunit
{
procedure MyProcedure()
var
UnexpectedErr: Text;
begin
[|Error(UnexpectedErr)|];
end;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
codeunit 50100 MyCodeunit
{
procedure MyProcedure()
var
MyErrorInfo: ErrorInfo;
begin
MyErrorInfo := ErrorInfo.Create(GetLastErrorText());
[|Error(MyErrorInfo)|];
end;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
codeunit 50100 MyCodeunit
{
procedure MyProcedure()
var
UnexpectedErr: Label 'Something went wrong...';
begin
[|Error(UnexpectedErr)|];
end;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
codeunit 50100 MyCodeunit
{
var
UnexpectedErr: Label 'Something went wrong...';

procedure MyProcedure()
begin
[|Error(this.UnexpectedErr)|];
end;
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ codeunit 50100 MyCodeunit
MyTable: Record MyTable;
MyFilterValue: Code[50];
begin
[|MyTable.SetFilter(MyField, '<>%1', MyFilterValue)|];
MyTable.SetFilter(MyField, '<>%1', [|MyFilterValue|]);
end;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
codeunit 50100 MyCodeunit
{
procedure MyProcedure()
var
MyTable: Record MyTable;
MyVar: Code[20];
begin
MyTable.Validate(MyField, [|MyVar|]);
end;
}

table 50100 MyTable
{
fields
{
field(1; MyField; Code[10]) { }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
codeunit 50100 MyCodeunit
{
procedure MyProcedure()
var
MyTable: Record MyTable;
MyVar: Code[10];
begin
MyTable.Validate(MyField, [|MyVar|]);
end;
}

table 50100 MyTable
{
fields
{
field(1; MyField; Code[10]) { }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
codeunit 50100 MyCodeunit
{
procedure MyProcedure()
var
MyTable: Record MyTable;
MyEnum: Enum MyEnum;
begin
[|MyTable.Get(MyEnum::MyValue)|];
end;
}

table 50100 MyTable
{
fields { field(1; MyField; Code[20]) { } }
}
enum 50100 MyEnum
{
value(0; MyValue) { }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
codeunit 50100 MyCodeunit
{
procedure MyProcedure()
var
MyTable: Record MyTable;
MyEnum: Enum MyEnum;
begin
[|MyTable.Get(MyEnum::MyValue)|];
end;
}

table 50100 MyTable
{
fields { field(1; MyField; Enum MyEnum) { } }
}
enum 50100 MyEnum
{
value(0; MyValue) { }
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ namespace BusinessCentral.LinterCop.Design;
public class Rule0005VariableCasingShouldNotDifferFromDeclaration : DiagnosticAnalyzer
{
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; }
= ImmutableArray.Create(DiagnosticDescriptors.Rule0005VariableCasingShouldNotDifferFromDeclaration);
= ImmutableArray.Create(DiagnosticDescriptors.Rule0005VariableCasingShouldNotDifferFromDeclaration, DiagnosticDescriptors.Rule0000ErrorInRule);

private static readonly HashSet<SyntaxKind> _dataTypeSyntaxKinds = Enum.GetValues(typeof(SyntaxKind)).Cast<SyntaxKind>().Where(x => x.ToString().AsSpan().EndsWith("DataType")).ToHashSet();
private static readonly string[] _areaKinds = Enum.GetValues(typeof(AreaKind)).Cast<AreaKind>().Select(x => x.ToString()).ToArray();
Expand Down Expand Up @@ -217,23 +217,35 @@ private void AnalyzeTriggerDeclaration(SyntaxNodeAnalysisContext ctx)

private void AnalyzeIdentifierName(SyntaxNodeAnalysisContext ctx)
{
if (ctx.Node is not IdentifierNameSyntax node)
return;
try // Investigate https://github.com/StefanMaron/BusinessCentral.LinterCop/issues/898
{
if (ctx.Node is not IdentifierNameSyntax node)
return;

if (node.Parent.Kind == SyntaxKind.PragmaWarningDirectiveTrivia)
return;
if (node.Parent.Kind == SyntaxKind.PragmaWarningDirectiveTrivia)
return;

if (ctx.SemanticModel.GetSymbolInfo(ctx.Node, ctx.CancellationToken).Symbol is not ISymbol fieldSymbol)
return;
if (ctx.SemanticModel.GetSymbolInfo(ctx.Node, ctx.CancellationToken).Symbol is not ISymbol fieldSymbol)
return;

// TODO: Support more SymbolKinds
if (fieldSymbol.Kind != SymbolKind.Field)
return;
// TODO: Support more SymbolKinds
if (fieldSymbol.Kind != SymbolKind.Field)
return;

string identifierName = StringExtensions.UnquoteIdentifier(node.Identifier.ValueText);
string identifierName = StringExtensions.UnquoteIdentifier(node.Identifier.ValueText);

if (!identifierName.AsSpan().Equals(fieldSymbol.Name.AsSpan(), StringComparison.Ordinal))
ctx.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.Rule0005VariableCasingShouldNotDifferFromDeclaration, node.GetLocation(), new object[] { fieldSymbol.Name.QuoteIdentifierIfNeeded(), "" }));
if (!identifierName.AsSpan().Equals(fieldSymbol.Name.AsSpan(), StringComparison.Ordinal))
ctx.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.Rule0005VariableCasingShouldNotDifferFromDeclaration, node.GetLocation(), new object[] { fieldSymbol.Name.QuoteIdentifierIfNeeded(), "" }));
}
catch (NullReferenceException)
{
ctx.ReportDiagnostic(Diagnostic.Create(
DiagnosticDescriptors.Rule0000ErrorInRule,
ctx.Node.GetLocation(),
"Rule0005",
"NullReferenceException",
$"Node: {ctx.Node.Kind}, ParentNode: {ctx.Node.Parent.Kind}"));
}
}

private void AnalyzeQualifiedName(SyntaxNodeAnalysisContext ctx)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ private void CheckForSingleFieldPrimaryKeyNotBlankProperty(SymbolAnalysisContext
if (ctx.IsObsoletePendingOrRemoved() || ctx.Symbol is not ITableTypeSymbol table)
return;

if (table.PrimaryKey.Fields == null || table.PrimaryKey.Fields.Length != 1)
if (table.PrimaryKey?.Fields == null || table.PrimaryKey.Fields.Length != 1)
return;

var field = table.PrimaryKey.Fields[0];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ private void AnalyzeErrorMethod(OperationAnalysisContext ctx)
switch (operation.Arguments[0].Syntax.Kind)
{
case SyntaxKind.IdentifierName:
case SyntaxKind.MemberAccessExpression:
if (operation.Arguments[0].Value.Kind != OperationKind.ConversionExpression)
break;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public class Rule0051PossibleOverflowAssigning : DiagnosticAnalyzer
public override void Initialize(AnalysisContext context)
{
context.RegisterOperationAction(new Action<OperationAnalysisContext>(this.AnalyzeSetFilter), OperationKind.InvocationExpression);
context.RegisterOperationAction(new Action<OperationAnalysisContext>(this.AnalyzeValidate), OperationKind.InvocationExpression);
#if !LessThenSpring2024
context.RegisterOperationAction(new Action<OperationAnalysisContext>(this.AnalyzeGetMethod), OperationKind.InvocationExpression);
#endif
Expand Down Expand Up @@ -68,19 +69,57 @@ private void AnalyzeSetFilter(OperationAnalysisContext ctx)

int expressionLength = this.CalculateMaxExpressionLength(argValue.Operand, ref isError);
if (!isError && expressionLength > typeLength)
ctx.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.Rule0051PossibleOverflowAssigning, operation.Syntax.GetLocation(), GetDisplayString(operation.Arguments[index], operation), GetDisplayString(operation.Arguments[0], operation)));
ctx.ReportDiagnostic(Diagnostic.Create(
DiagnosticDescriptors.Rule0051PossibleOverflowAssigning,
operation.Arguments[index].Syntax.GetLocation(),
GetDisplayString(operation.Arguments[index], operation),
GetDisplayString(operation.Arguments[0], operation)));
}
}

private void AnalyzeValidate(OperationAnalysisContext ctx)
{
if (ctx.IsObsoletePendingOrRemoved() || ctx.Operation is not IInvocationExpression operation)
return;

if (operation.TargetMethod.MethodKind != MethodKind.BuiltInMethod ||
operation.TargetMethod.Name != "Validate" ||
operation.TargetMethod.ContainingSymbol?.Name != "Table" ||
operation.Arguments.Length < 2 ||
operation.Arguments[0].Value.Kind != OperationKind.ConversionExpression)
return;

var fieldOperand = ((IConversionExpression)operation.Arguments[0].Value).Operand;
if (fieldOperand.Type is not ITypeSymbol fieldType)
return;

bool isError = false;
int typeLength = GetTypeLength(fieldType, ref isError);
if (isError || typeLength == int.MaxValue)
return;

if (operation.Arguments[1].Value is not IConversionExpression argValue)
return;

int expressionLength = this.CalculateMaxExpressionLength(argValue.Operand, ref isError);
if (!isError && expressionLength > typeLength)
ctx.ReportDiagnostic(
Diagnostic.Create(
DiagnosticDescriptors.Rule0051PossibleOverflowAssigning,
operation.Arguments[1].Syntax.GetLocation(),
GetDisplayString(operation.Arguments[1], operation),
GetDisplayString(operation.Arguments[0], operation)));
}

#if !LessThenSpring2024
private void AnalyzeGetMethod(OperationAnalysisContext ctx)
{
if (ctx.IsObsoletePendingOrRemoved())
if (ctx.IsObsoletePendingOrRemoved() || ctx.Operation is not IInvocationExpression operation)
return;

if ((ctx.Operation is not IInvocationExpression operation) ||
operation.TargetMethod.MethodKind != MethodKind.BuiltInMethod ||
!SemanticFacts.IsSameName(operation.TargetMethod.Name, "Get") ||
if (operation.TargetMethod.MethodKind != MethodKind.BuiltInMethod ||
operation.TargetMethod.Name != "Get" ||
operation.TargetMethod.ContainingSymbol?.Name != "Table" ||
operation.Arguments.Length < 1)
return;

Expand All @@ -90,7 +129,7 @@ private void AnalyzeGetMethod(OperationAnalysisContext ctx)
if (operation.Arguments.Length < table.PrimaryKey.Fields.Length)
return;

for (int index = 0; index < operation.Arguments.Length; index++)
for (int index = 0; index < table.PrimaryKey.Fields.Length; index++)
{
var fieldType = table.PrimaryKey.Fields[index].Type;
var argumentType = operation.Arguments[index].GetTypeSymbol();
Expand Down
Loading

0 comments on commit c3cb82a

Please sign in to comment.