Skip to content

Commit

Permalink
New Rule0074 FlowFilter Assignment (#820)
Browse files Browse the repository at this point in the history
* New Rule0074 FlowFilter Assignment
  • Loading branch information
Arthurvdv authored Nov 30, 2024
1 parent 318fdca commit c8534f3
Show file tree
Hide file tree
Showing 8 changed files with 172 additions and 1 deletion.
36 changes: 36 additions & 0 deletions BusinessCentral.LinterCop.Test/Rule0074.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
namespace BusinessCentral.LinterCop.Test;

public class Rule0074
{
private string _testCaseDir = "";

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

[Test]
[TestCase("AssignmentStatement")]
[TestCase("CompoundAssignmentStatement")]
public async Task HasDiagnostic(string testCase)
{
var code = await File.ReadAllTextAsync(Path.Combine(_testCaseDir, "HasDiagnostic", $"{testCase}.al"))
.ConfigureAwait(false);

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

[Test]
[TestCase("SetRange")]
public async Task NoDiagnostic(string testCase)
{
var code = await File.ReadAllTextAsync(Path.Combine(_testCaseDir, "NoDiagnostic", $"{testCase}.al"))
.ConfigureAwait(false);

var fixture = RoslynFixtureFactory.Create<Rule0074FlowFilterAssignment>();
fixture.NoDiagnosticAtMarker(code, Rule0074FlowFilterAssignment.DiagnosticDescriptors.Rule0074FlowFilterAssignment.Id);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
codeunit 50100 MyCodeunit
{
procedure MyProcedure()
var
MyTable: Record MyTable;
begin
MyTable.[|"My Filter"|] := '1';
end;
}

table 50100 MyTable
{
fields
{
field(1; MyField; Integer) { }
field(2; "My Filter"; Code[10])
{
FieldClass = FlowFilter;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
codeunit 50100 MyCodeunit
{
procedure MyProcedure()
var
MyTable: Record MyTable;
begin
MyTable.[|"My Filter"|] += 1;
end;
}

table 50100 MyTable
{
fields
{
field(1; MyField; Integer) { }
field(2; "My Filter"; Integer)
{
FieldClass = FlowFilter;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
codeunit 50100 MyCodeunit
{
procedure MyProcedure()
var
MyTable: Record MyTable;
begin
MyTable.SetRange([|"My Filter"|], '1');
end;
}

table 50100 MyTable
{
fields
{
field(1; MyField; Integer) { }
field(2; "My Filter"; Code[10])
{
FieldClass = FlowFilter;
}
}
}
57 changes: 57 additions & 0 deletions BusinessCentral.LinterCop/Design/Rule0074FlowFilterAssignment.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using BusinessCentral.LinterCop.AnalysisContextExtension;
using Microsoft.Dynamics.Nav.CodeAnalysis;
using Microsoft.Dynamics.Nav.CodeAnalysis.Diagnostics;
using Microsoft.Dynamics.Nav.CodeAnalysis.Syntax;
using Microsoft.Dynamics.Nav.CodeAnalysis.Utilities;
using System.Collections.Immutable;

namespace BusinessCentral.LinterCop.Design;

[DiagnosticAnalyzer]
public class Rule0074FlowFilterAssignment : DiagnosticAnalyzer
{
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create<DiagnosticDescriptor>(DiagnosticDescriptors.Rule0074FlowFilterAssignment);

public override void Initialize(AnalysisContext context)
{
context.RegisterSyntaxNodeAction(AnalyzeAssignmentStatement, SyntaxKind.AssignmentStatement, SyntaxKind.CompoundAssignmentStatement);
}

private void AnalyzeAssignmentStatement(SyntaxNodeAnalysisContext ctx)
{
if (ctx.CancellationToken.IsCancellationRequested || ctx.IsObsoletePendingOrRemoved())
return;

var target = ctx.Node switch
{
AssignmentStatementSyntax assignment => assignment.Target,
CompoundAssignmentStatementSyntax compoundAssignment => compoundAssignment.Target,
_ => null
};

if (target is not { Kind: SyntaxKind.MemberAccessExpression })
return;

if (ctx.SemanticModel.GetSymbolInfo(target, ctx.CancellationToken).Symbol is not IFieldSymbol fieldSymbol)
return;

if (fieldSymbol.FieldClass == FieldClassKind.FlowFilter)
{
ctx.ReportDiagnostic(Diagnostic.Create(
DiagnosticDescriptors.Rule0074FlowFilterAssignment,
target.GetIdentifierNameSyntax().GetLocation(), new object[] { fieldSymbol.Name.QuoteIdentifierIfNeeded() }));
}
}

public static class DiagnosticDescriptors
{
public static readonly DiagnosticDescriptor Rule0074FlowFilterAssignment = new(
id: LinterCopAnalyzers.AnalyzerPrefix + "0074",
title: LinterCopAnalyzers.GetLocalizableString("Rule0074FlowFilterAssignmentTitle"),
messageFormat: LinterCopAnalyzers.GetLocalizableString("Rule0074FlowFilterAssignmentFormat"),
category: "Design",
defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: true,
description: LinterCopAnalyzers.GetLocalizableString("Rule0074FlowFilterAssignmentDescription"),
helpLinkUri: "https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0074");
}
}
5 changes: 5 additions & 0 deletions BusinessCentral.LinterCop/LinterCop.ruleset.json
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,11 @@
"id": "LC0073",
"action": "Warning",
"justification": "Handled parameters in event signatures should be passed by var."
},
{
"id": "LC0074",
"action": "Warning",
"justification": "Set values for FlowFilter fields using filtering methods."
}
]
}
9 changes: 9 additions & 0 deletions BusinessCentral.LinterCop/LinterCopAnalyzers.resx
Original file line number Diff line number Diff line change
Expand Up @@ -777,4 +777,13 @@
<data name="Rule0073EventPublisherIsHandledByVarDescription" xml:space="preserve">
<value>Identifies event parameters of type boolean named IsHandled or Handled that are not passed by var.</value>
</data>
<data name="Rule0074FlowFilterAssignmentTitle" xml:space="preserve">
<value>Set values for FlowFilter fields using filtering methods.</value>
</data>
<data name="Rule0074FlowFilterAssignmentFormat" xml:space="preserve">
<value>Direct assignment to the {0} field of type FlowFilter invalidates the filter logic for calculations. Use .SetFilter() or .SetRange() to set the filter correctly.</value>
</data>
<data name="Rule0074FlowFilterAssignmentDescription" xml:space="preserve">
<value>Directly assigning values to FlowFilter fields bypasses their purpose and invalidates the filtering logic, resulting in incorrect or unintended calculations. Instead, use the .SetFilter() or .SetRange() methods to define the appropriate filters.</value>
</data>
</root>
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -226,4 +226,5 @@ For an example and the default values see: [LinterCop.ruleset.json](./BusinessCe
|[LC0070](https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0070)|Zero index access on 1-based List objects.|Warning|
|[LC0071](https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0071)|Incorrect 'IsHandled' parameter assignment.|Info|
|[LC0072](https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0072)|The documentation comment must match the procedure syntax.|Info|
|[LC0073](https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0073)|Handled parameters in event signatures should be passed by var.|Warning|
|[LC0073](https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0073)|Handled parameters in event signatures should be passed by var.|Warning|
|[LC0074](https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0074)|Set values for FlowFilter fields using filtering methods.|Warning|

0 comments on commit c8534f3

Please sign in to comment.