Skip to content
Open
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
37 changes: 37 additions & 0 deletions BusinessCentral.LinterCop.Test/Rule0095.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
namespace BusinessCentral.LinterCop.Test;

public class Rule0095
{
private string _testCaseDir = "";

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

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

var fixture = RoslynFixtureFactory.Create<Rule0095UnusedParameter>();
fixture.HasDiagnosticAtAllMarkers(code, DiagnosticDescriptors.Rule0095UnusedProcedureParameter.Id);
}

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

var fixture = RoslynFixtureFactory.Create<Rule0095UnusedParameter>();
fixture.NoDiagnosticAtAllMarkers(code, DiagnosticDescriptors.Rule0095UnusedProcedureParameter.Id);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
codeunit 70020 MyCodeunit
{
internal procedure TestProcedure([|NotUsed|]: Text; Used: Text; [|NotUsed2|]: Text; Used2: Text)
begin
Used := '42';
Used2 := '42';
end;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
codeunit 70020 MyCodeunit
{
procedure TestProcedure([|NotUsed|]: Text; Used: Text; [|NotUsed2|]: Text; Used2: Text)
begin
Used := '42';
Used2 := '42';
end;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
codeunit 70020 MyCodeunit
{
internal procedure TestProcedure([|Used1|]: Text; [|Used2|]: Text; [|Used3|]: Text; [|Used4|]: Text)
begin
Used1 := '42';
Used2 := '42';
Used3 := '42';
Used4 := '42';
end;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
codeunit 70020 MyCodeunit
{
local procedure TestProcedure([|NotUsed|]: Text; Used: Text; [|NotUsed2|]: Text; Used2: Text)
begin
Used := '42';
Used2 := '42';
end;
}
93 changes: 93 additions & 0 deletions BusinessCentral.LinterCop/Design/Rule0095UnusedParameter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
using System.Collections.Immutable;
using BusinessCentral.LinterCop.Helpers;
using Microsoft.Dynamics.Nav.CodeAnalysis;
using Microsoft.Dynamics.Nav.CodeAnalysis.Diagnostics;
using Microsoft.Dynamics.Nav.CodeAnalysis.Symbols;

namespace BusinessCentral.LinterCop.Design;

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

public override void Initialize(AnalysisContext context)
{
context.RegisterSymbolAction(
new Action<SymbolAnalysisContext>(this.AnalyzeMethod),
SymbolKind.Method
);
}

private void AnalyzeMethod(SymbolAnalysisContext context)
{
if (context.IsObsoletePendingOrRemoved() || context.Symbol is not IMethodSymbol methodSymbol)
return;

// If containing object is not an internal object, we do not need to check
if (methodSymbol.DeclaredAccessibility != Accessibility.Internal)
return;

// Skip event publishers and event subscribers
if (methodSymbol.IsEvent)
return;

// Skip if method has no parameters
if (methodSymbol.Parameters.IsEmpty)
return;

// Get method body for analysis
var syntaxReference = methodSymbol.DeclaringSyntaxReference;
if (syntaxReference == null)
return;

var methodSyntax = syntaxReference.GetSyntax();
if (methodSyntax == null)
return;

foreach (var parameter in methodSymbol.Parameters)
{
// Check if parameter is used in method body
if (!IsParameterUsed(parameter, methodSyntax, context.Compilation))
{
context.ReportDiagnostic(Diagnostic.Create(
DiagnosticDescriptors.Rule0095UnusedProcedureParameter,
parameter.GetLocation(),
parameter.Name
));
}
}
}

private static bool IsParameterUsed(IParameterSymbol parameter, SyntaxNode methodSyntax, Compilation compilation)
{
var semanticModel = compilation.GetSemanticModel(methodSyntax.SyntaxTree);

// Find all identifier nodes in the method body
foreach (var node in methodSyntax.DescendantNodes())
{
if (!node.IsKind(SyntaxKind.IdentifierName))
continue;

// Quick name check first (avoid expensive GetSymbolInfo call)
var nodeText = node.ToString();
if (!string.Equals(nodeText, parameter.Name, StringComparison.OrdinalIgnoreCase))
continue;

// Skip parameter declarations (parent is Parameter node)
if (node.Parent?.IsKind(SyntaxKind.Parameter) == true)
continue;

// Now check if identifier actually references parameter
var symbolInfo = semanticModel.GetSymbolInfo(node);
if (symbolInfo.Symbol is IParameterSymbol paramSymbol &&
string.Equals(paramSymbol.Name, parameter.Name, StringComparison.OrdinalIgnoreCase))
{
return true;
}
}

return false;
}
}
10 changes: 10 additions & 0 deletions BusinessCentral.LinterCop/DiagnosticDescriptors.cs
Original file line number Diff line number Diff line change
Expand Up @@ -973,6 +973,16 @@ public static class DiagnosticDescriptors
description: LinterCopAnalyzers.GetLocalizableString("Rule0094UnnecessaryParameterInMethodCallDescription"),
helpLinkUri: "https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0094");

public static readonly DiagnosticDescriptor Rule0095UnusedProcedureParameter = new(
id: LinterCopAnalyzers.AnalyzerPrefix + "0095",
title: LinterCopAnalyzers.GetLocalizableString("Rule0095UnusedParameterInProcedureTitle"),
messageFormat: LinterCopAnalyzers.GetLocalizableString("Rule0095UnusedParameterInProcedureFormat"),
category: "Design",
defaultSeverity: DiagnosticSeverity.Info,
isEnabledByDefault: true,
description: LinterCopAnalyzers.GetLocalizableString("Rule0095UnusedParameterInProcedureDescription"),
helpLinkUri: "https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0095");

public static readonly DiagnosticDescriptor Rule9999AssemblyVersionCompatibilityAnalyzer = new(
id: LinterCopAnalyzers.AnalyzerPrefix + "9999",
title: LinterCopAnalyzers.GetLocalizableString("Rule9999AssemblyVersionCompatibilityAnalyzerTitle"),
Expand Down
10 changes: 10 additions & 0 deletions BusinessCentral.LinterCop/LinterCop.ruleset.json
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,16 @@
"action": "Info",
"justification": "Global test method requires test attribute. Use e.g. library codeunits for public procedures."
},
{
"id": "LC0094",
"action": "Info",
"justification": "Unnecessary parameters in method calls should be avoided."
},
{
"id": "LC0095",
"action": "Info",
"justification": "Unused internal procedure parameters should be removed to improve code readability."
},
{
"id": "LC9999",
"action": "Error",
Expand Down
9 changes: 9 additions & 0 deletions BusinessCentral.LinterCop/LinterCopAnalyzers.resx
Original file line number Diff line number Diff line change
Expand Up @@ -981,6 +981,15 @@
<data name="Rule0094UnnecessaryParameterInMethodCallDescription" xml:space="preserve">
<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>
</data>
<data name="Rule0095UnusedParameterInProcedureTitle" xml:space="preserve">
<value>Unused parameter.</value>
</data>
<data name="Rule0095UnusedParameterInProcedureFormat" xml:space="preserve">
<value>Parameter '{0}' is not used in the method body.</value>
</data>
<data name="Rule0095UnusedParameterInProcedureDescription" xml:space="preserve">
<value>Unused parameters should be avoided.</value>
</data>
<data name="Rule9999AssemblyVersionCompatibilityAnalyzerDecription" xml:space="preserve">
<value>Analyzer and AL Language version mismatch</value>
</data>
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ For an example and the default values see: [LinterCop.ruleset.json](./BusinessCe
|[LC0092](https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0092)|Names must match the allowed pattern and must not match the disallowed pattern|Info|
|[LC0093](https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0093)|Global procedure in test codeunit requires test attribute.|Info||
|[LC0094](https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0094)|A method invoked on a record must not contain same variable in parameter list as the one on which the call was made.|Info||
|[LC0095](https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0095)|Internal procedure parameter is unused.|Info||
|[LC9999](https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0099)|The version of LinterCop does not match the version of the AL Language compiler it is running on.|Error||

## Codespace
Expand Down