Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New Rule0074 FlowFilter Assignment #820

Merged
merged 2 commits into from
Nov 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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|