Skip to content

Commit 7acd843

Browse files
add Rule0096 unnecessary parameter in method call (#1132)
* add Rule0096 unnecessary parameter in method call * fix error * adjust Rule0096 * add exceptions
1 parent 631bdc0 commit 7acd843

11 files changed

Lines changed: 261 additions & 2 deletions

File tree

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
namespace BusinessCentral.LinterCop.Test;
2+
3+
public class Rule0094
4+
{
5+
private string _testCaseDir = "";
6+
7+
[SetUp]
8+
public void Setup()
9+
{
10+
_testCaseDir = Path.Combine(Directory.GetParent(Environment.CurrentDirectory)!.Parent!.Parent!.FullName,
11+
"TestCases", "Rule0094");
12+
}
13+
14+
[Test]
15+
[TestCase("UnneccassaryParameterCalledFromRecord")]
16+
[TestCase("UnneccassaryParameterInTable")]
17+
public async Task HasDiagnostic(string testCase)
18+
{
19+
var code = await File.ReadAllTextAsync(Path.Combine(_testCaseDir, "HasDiagnostic", $"{testCase}.al"))
20+
.ConfigureAwait(false);
21+
22+
var fixture = RoslynFixtureFactory.Create<Rule0094UnnecessaryParameterInMethodCall>();
23+
fixture.HasDiagnosticAtAllMarkers(code, DiagnosticDescriptors.Rule0094UnnecessaryParameterInMethodCall.Id);
24+
}
25+
26+
[Test]
27+
[TestCase("DifferentParameter")]
28+
[TestCase("ClearMethod")]
29+
[TestCase("EventSubscriber")]
30+
public async Task NoDiagnostic(string testCase)
31+
{
32+
var code = await File.ReadAllTextAsync(Path.Combine(_testCaseDir, "NoDiagnostic", $"{testCase}.al"))
33+
.ConfigureAwait(false);
34+
35+
var fixture = RoslynFixtureFactory.Create<Rule0094UnnecessaryParameterInMethodCall>();
36+
fixture.NoDiagnosticAtAllMarkers(code, DiagnosticDescriptors.Rule0094UnnecessaryParameterInMethodCall.Id);
37+
}
38+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
table 50100 MyTable
2+
{
3+
fields
4+
{
5+
field(1; Name; Text[100]) { }
6+
}
7+
8+
procedure DoSth(var MyTableParam: Record MyTable)
9+
begin
10+
end;
11+
}
12+
13+
14+
codeunit 50100 MyCodeunit
15+
{
16+
procedure MyProcedure()
17+
var
18+
MyTable: Record MyTable;
19+
begin
20+
MyTable.DoSth([|MyTable|]);
21+
end;
22+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
table 50100 MyTable
2+
{
3+
fields
4+
{
5+
field(1; Name; Text[100]) { }
6+
}
7+
8+
procedure DoSth(MyTable2: Record MyTable)
9+
begin
10+
DoSth([|Rec|]);
11+
end;
12+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
table 50100 MyTable
2+
{
3+
fields
4+
{
5+
field(1; Id; Integer) { }
6+
}
7+
8+
internal procedure ClearRec()
9+
begin
10+
Clear([|Rec|]);
11+
end;
12+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
table 50100 MyTable
2+
{
3+
fields
4+
{
5+
field(1; Name; Text[100]) { }
6+
}
7+
8+
procedure DoSth(var MyTableParam: Record MyTable)
9+
begin
10+
end;
11+
}
12+
13+
14+
codeunit 50100 MyCodeunit
15+
{
16+
procedure MyProcedure()
17+
var
18+
MyTable: Record MyTable;
19+
MyTable2: Record MyTable;
20+
begin
21+
MyTable.DoSth([|MyTable2|]);
22+
end;
23+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
table 50100 MyTable
2+
{
3+
fields
4+
{
5+
field(1; MyField; Integer)
6+
{
7+
trigger OnValidate()
8+
var
9+
IsHandled: Boolean;
10+
begin
11+
OnBeforeCalculateNewValue([|Rec|], IsHandled);
12+
if IsHandled then
13+
exit;
14+
end;
15+
}
16+
}
17+
18+
[IntegrationEvent(false, false)]
19+
local procedure OnBeforeCalculateNewValue(var MyTable: Record MyTable; var IsHandled: Boolean)
20+
begin
21+
end;
22+
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
using System;
2+
using System.Collections.Immutable;
3+
using System.Linq;
4+
using BusinessCentral.LinterCop.Helpers;
5+
using Microsoft.Dynamics.Nav.CodeAnalysis;
6+
using Microsoft.Dynamics.Nav.CodeAnalysis.Diagnostics;
7+
8+
namespace BusinessCentral.LinterCop.Design;
9+
10+
[DiagnosticAnalyzer]
11+
public class Rule0094UnnecessaryParameterInMethodCall : DiagnosticAnalyzer
12+
{
13+
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } =
14+
ImmutableArray.Create(DiagnosticDescriptors.Rule0094UnnecessaryParameterInMethodCall);
15+
16+
public override void Initialize(AnalysisContext context) => context.RegisterOperationAction(
17+
new Action<OperationAnalysisContext>(this.AnalyzeInvocation),
18+
OperationKind.InvocationExpression);
19+
20+
private void AnalyzeInvocation(OperationAnalysisContext context)
21+
{
22+
if (context.IsObsoletePendingOrRemoved() || context.Operation is not IInvocationExpression operation)
23+
return;
24+
25+
// Procedure does not contain arguments -> nothing to check
26+
if (operation.Arguments.IsEmpty)
27+
return;
28+
29+
// ignore Event publisher
30+
if (operation.TargetMethod is IMethodSymbol methodSymbol && methodSymbol.IsEvent)
31+
return;
32+
33+
var instance = operation.Instance;
34+
if (instance?.Type is { NavTypeKind: NavTypeKind.Record })
35+
{
36+
CheckMethodCalledFromRecord(context, operation);
37+
return;
38+
}
39+
40+
// method called in current table
41+
if (instance is null && HelperFunctions.IsOperationInvokedInTable(context, operation))
42+
{
43+
CheckMethodCalledInCurrentTable(context, operation);
44+
}
45+
}
46+
47+
private void CheckMethodCalledFromRecord(OperationAnalysisContext context, IInvocationExpression operation)
48+
{
49+
var instanceSyntax = operation.Instance?.Syntax;
50+
if (instanceSyntax == null)
51+
return;
52+
53+
var semanticModel = context.Compilation.GetSemanticModel(instanceSyntax.SyntaxTree);
54+
var instanceSymbol = semanticModel.GetSymbolInfo(instanceSyntax).Symbol;
55+
56+
if (instanceSymbol == null)
57+
return;
58+
59+
foreach (var argument in operation.Arguments)
60+
{
61+
var argumentSymbol = semanticModel.GetSymbolInfo(argument.Syntax).Symbol;
62+
63+
if (argumentSymbol != null &&
64+
instanceSymbol.Equals(argumentSymbol))
65+
{
66+
context.ReportDiagnostic(Diagnostic.Create(
67+
DiagnosticDescriptors.Rule0094UnnecessaryParameterInMethodCall,
68+
argument.Syntax.GetLocation()
69+
));
70+
}
71+
}
72+
}
73+
74+
private void CheckMethodCalledInCurrentTable(OperationAnalysisContext context, IInvocationExpression operation)
75+
{
76+
// Ignore Clear(...) invocations
77+
if (IsClearInvocation(operation))
78+
return;
79+
80+
foreach (var arg in operation.Arguments)
81+
{
82+
var semanticModel = context.Compilation.GetSemanticModel(arg.Syntax.SyntaxTree);
83+
var symbolInfo = semanticModel.GetSymbolInfo(arg.Syntax).Symbol;
84+
85+
if (symbolInfo != null && string.Equals(symbolInfo.Name, "Rec", StringComparison.OrdinalIgnoreCase))
86+
{
87+
context.ReportDiagnostic(Diagnostic.Create(
88+
DiagnosticDescriptors.Rule0094UnnecessaryParameterInMethodCall,
89+
arg.Syntax.GetLocation()
90+
));
91+
}
92+
}
93+
}
94+
95+
private static bool IsClearInvocation(IInvocationExpression operation)
96+
{
97+
var methodSymbol = operation.TargetMethod;
98+
if (methodSymbol is null)
99+
return false;
100+
101+
return string.Equals(methodSymbol.Name, "Clear", StringComparison.OrdinalIgnoreCase);
102+
}
103+
}

BusinessCentral.LinterCop/DiagnosticDescriptors.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -963,6 +963,16 @@ public static class DiagnosticDescriptors
963963
description: LinterCopAnalyzers.GetLocalizableString("Rule0093GlobalTestMethodRequiresTestAttributeDescription"),
964964
helpLinkUri: "https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0093");
965965

966+
public static readonly DiagnosticDescriptor Rule0094UnnecessaryParameterInMethodCall = new(
967+
id: LinterCopAnalyzers.AnalyzerPrefix + "0094",
968+
title: LinterCopAnalyzers.GetLocalizableString("Rule0094UnnecessaryParameterInMethodCallTitle"),
969+
messageFormat: LinterCopAnalyzers.GetLocalizableString("Rule0094UnnecessaryParameterInMethodCallFormat"),
970+
category: "Design",
971+
defaultSeverity: DiagnosticSeverity.Info,
972+
isEnabledByDefault: true,
973+
description: LinterCopAnalyzers.GetLocalizableString("Rule0094UnnecessaryParameterInMethodCallDescription"),
974+
helpLinkUri: "https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0094");
975+
966976
public static readonly DiagnosticDescriptor Rule9999AssemblyVersionCompatibilityAnalyzer = new(
967977
id: LinterCopAnalyzers.AnalyzerPrefix + "9999",
968978
title: LinterCopAnalyzers.GetLocalizableString("Rule9999AssemblyVersionCompatibilityAnalyzerTitle"),

BusinessCentral.LinterCop/Helpers/HelperFunctions.cs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
using System.Text.RegularExpressions;
2-
using Microsoft.Dynamics.Nav.CodeAnalysis;
1+
using Microsoft.Dynamics.Nav.CodeAnalysis;
32
using Microsoft.Dynamics.Nav.CodeAnalysis.Diagnostics;
3+
using Microsoft.Dynamics.Nav.CodeAnalysis.Syntax;
4+
using System.Text.RegularExpressions;
45
using Microsoft.Dynamics.Nav.CodeAnalysis.Text;
56

67
namespace BusinessCentral.LinterCop.Helpers;
@@ -63,6 +64,12 @@ public static bool MethodImplementsInterfaceMethod(IMethodSymbol methodSymbol, I
6364
return true;
6465
}
6566

67+
public static bool IsOperationInvokedInTable(OperationAnalysisContext context, IOperation operation)
68+
{
69+
var containing = operation?.Syntax?.GetContainingObjectSyntax();
70+
return containing is TableSyntax;
71+
}
72+
6673
public static void CheckMatchesPattern(SymbolAnalysisContext ctx, Location location, Regex pattern, string patternSource, string name, string indentifierKind)
6774
{
6875
CheckPattern(ctx, location, pattern, patternSource, true, name, indentifierKind);

BusinessCentral.LinterCop/LinterCopAnalyzers.resx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -972,6 +972,15 @@
972972
<data name="Rule0093GlobalTestMethodRequiresTestAttributeDescription" xml:space="preserve">
973973
<value>Global procedure in test codeunit requires test attribute. Use e.g. library codeunits for public procedures.</value>
974974
</data>
975+
<data name="Rule0094UnnecessaryParameterInMethodCallTitle" xml:space="preserve">
976+
<value>Unnecessary parameter.</value>
977+
</data>
978+
<data name="Rule0094UnnecessaryParameterInMethodCallFormat" xml:space="preserve">
979+
<value>A method invoked on a record must not contain same variable in parameter list as the one on which the call was made.</value>
980+
</data>
981+
<data name="Rule0094UnnecessaryParameterInMethodCallDescription" xml:space="preserve">
982+
<value>A method invoked on a record must not contain same variable in parameter list as the one on which the call was made.</value>
983+
</data>
975984
<data name="Rule9999AssemblyVersionCompatibilityAnalyzerDecription" xml:space="preserve">
976985
<value>Analyzer and AL Language version mismatch</value>
977986
</data>

0 commit comments

Comments
 (0)