forked from StefanMaron/BusinessCentral.LinterCop
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
New Rule0082: Use Rec.Find('-') with Rec.Next() for checking exactly …
…one record. (StefanMaron#834)
- Loading branch information
Showing
16 changed files
with
361 additions
and
88 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
namespace BusinessCentral.LinterCop.Test; | ||
|
||
public class Rule0082 | ||
{ | ||
private string _testCaseDir = ""; | ||
|
||
[SetUp] | ||
public void Setup() | ||
{ | ||
_testCaseDir = Path.Combine(Directory.GetParent(Environment.CurrentDirectory)!.Parent!.Parent!.FullName, | ||
"TestCases", "Rule0082"); | ||
} | ||
|
||
[Test] | ||
[TestCase("RecordCountEqualsOne")] | ||
[TestCase("RecordCountGreaterThanOne")] | ||
[TestCase("RecordCountGreaterThanOrEqualOne")] | ||
[TestCase("RecordCountLessThanOrEqualZero")] | ||
[TestCase("RecordCountLessThanTwo")] | ||
[TestCase("RecordCountNotEqualsOne")] | ||
[TestCase("TwoGreaterThanRecordCount")] | ||
public async Task HasDiagnostic(string testCase) | ||
{ | ||
var code = await File.ReadAllTextAsync(Path.Combine(_testCaseDir, "HasDiagnostic", $"{testCase}.al")) | ||
.ConfigureAwait(false); | ||
|
||
var fixture = RoslynFixtureFactory.Create<Rule0081AnalyzeCountMethod>(); | ||
fixture.HasDiagnostic(code, Rule0081AnalyzeCountMethod.DiagnosticDescriptors.Rule0082UseFindWithNext.Id); | ||
} | ||
|
||
[Test] | ||
[TestCase("RecordCountEqualsTwo")] | ||
[TestCase("RecordTemporaryCountEqualsOne")] | ||
public async Task NoDiagnostic(string testCase) | ||
{ | ||
var code = await File.ReadAllTextAsync(Path.Combine(_testCaseDir, "NoDiagnostic", $"{testCase}.al")) | ||
.ConfigureAwait(false); | ||
|
||
var fixture = RoslynFixtureFactory.Create<Rule0081AnalyzeCountMethod>(); | ||
fixture.NoDiagnosticAtMarker(code, Rule0081AnalyzeCountMethod.DiagnosticDescriptors.Rule0082UseFindWithNext.Id); | ||
} | ||
} |
17 changes: 17 additions & 0 deletions
17
BusinessCentral.LinterCop.Test/TestCases/Rule0082/HasDiagnostic/RecordCountEqualsOne.al
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
codeunit 50100 MyCodeunit | ||
{ | ||
procedure MyProcedure() | ||
var | ||
MyTable: Record MyTable; | ||
begin | ||
if [|MyTable.Count() = 1|] then; | ||
end; | ||
} | ||
|
||
table 50100 MyTable | ||
{ | ||
fields | ||
{ | ||
field(1; MyField; Integer) { } | ||
} | ||
} |
17 changes: 17 additions & 0 deletions
17
BusinessCentral.LinterCop.Test/TestCases/Rule0082/HasDiagnostic/RecordCountGreaterThanOne.al
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
codeunit 50100 MyCodeunit | ||
{ | ||
procedure MyProcedure() | ||
var | ||
MyTable: Record MyTable; | ||
begin | ||
if [|MyTable.Count() > 1|] then; | ||
end; | ||
} | ||
|
||
table 50100 MyTable | ||
{ | ||
fields | ||
{ | ||
field(1; MyField; Integer) { } | ||
} | ||
} |
17 changes: 17 additions & 0 deletions
17
...ntral.LinterCop.Test/TestCases/Rule0082/HasDiagnostic/RecordCountGreaterThanOrEqualOne.al
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
codeunit 50100 MyCodeunit | ||
{ | ||
procedure MyProcedure() | ||
var | ||
MyTable: Record MyTable; | ||
begin | ||
if [|MyTable.Count() >= 1|] then; | ||
end; | ||
} | ||
|
||
table 50100 MyTable | ||
{ | ||
fields | ||
{ | ||
field(1; MyField; Integer) { } | ||
} | ||
} |
17 changes: 17 additions & 0 deletions
17
...Central.LinterCop.Test/TestCases/Rule0082/HasDiagnostic/RecordCountLessThanOrEqualZero.al
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
codeunit 50100 MyCodeunit | ||
{ | ||
procedure MyProcedure() | ||
var | ||
MyTable: Record MyTable; | ||
begin | ||
if [|MyTable.Count() <= 1|] then; | ||
end; | ||
} | ||
|
||
table 50100 MyTable | ||
{ | ||
fields | ||
{ | ||
field(1; MyField; Integer) { } | ||
} | ||
} |
17 changes: 17 additions & 0 deletions
17
BusinessCentral.LinterCop.Test/TestCases/Rule0082/HasDiagnostic/RecordCountLessThanTwo.al
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
codeunit 50100 MyCodeunit | ||
{ | ||
procedure MyProcedure() | ||
var | ||
MyTable: Record MyTable; | ||
begin | ||
if [|MyTable.Count() < 2|] then; | ||
end; | ||
} | ||
|
||
table 50100 MyTable | ||
{ | ||
fields | ||
{ | ||
field(1; MyField; Integer) { } | ||
} | ||
} |
17 changes: 17 additions & 0 deletions
17
BusinessCentral.LinterCop.Test/TestCases/Rule0082/HasDiagnostic/RecordCountNotEqualsOne.al
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
codeunit 50100 MyCodeunit | ||
{ | ||
procedure MyProcedure() | ||
var | ||
MyTable: Record MyTable; | ||
begin | ||
if [|MyTable.Count() <> 1|] then; | ||
end; | ||
} | ||
|
||
table 50100 MyTable | ||
{ | ||
fields | ||
{ | ||
field(1; MyField; Integer) { } | ||
} | ||
} |
17 changes: 17 additions & 0 deletions
17
BusinessCentral.LinterCop.Test/TestCases/Rule0082/HasDiagnostic/TwoGreaterThanRecordCount.al
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
codeunit 50100 MyCodeunit | ||
{ | ||
procedure MyProcedure() | ||
var | ||
MyTable: Record MyTable; | ||
begin | ||
if [|2 > MyTable.Count()|] then; | ||
end; | ||
} | ||
|
||
table 50100 MyTable | ||
{ | ||
fields | ||
{ | ||
field(1; MyField; Integer) { } | ||
} | ||
} |
17 changes: 17 additions & 0 deletions
17
BusinessCentral.LinterCop.Test/TestCases/Rule0082/NoDiagnostic/RecordCountEqualsTwo.al
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
codeunit 50100 MyCodeunit | ||
{ | ||
procedure MyProcedure() | ||
var | ||
MyTable: Record MyTable; | ||
begin | ||
if [|MyTable.Count() = 2|] then; | ||
end; | ||
} | ||
|
||
table 50100 MyTable | ||
{ | ||
fields | ||
{ | ||
field(1; MyField; Integer) { } | ||
} | ||
} |
17 changes: 17 additions & 0 deletions
17
...ssCentral.LinterCop.Test/TestCases/Rule0082/NoDiagnostic/RecordTemporaryCountEqualsOne.al
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
codeunit 50100 MyCodeunit | ||
{ | ||
procedure MyProcedure() | ||
var | ||
TempMyTable: Record MyTable temporary; | ||
begin | ||
if [|TempMyTable.Count() = 1|] then; | ||
end; | ||
} | ||
|
||
table 50100 MyTable | ||
{ | ||
fields | ||
{ | ||
field(1; MyField; Integer) { } | ||
} | ||
} |
146 changes: 146 additions & 0 deletions
146
BusinessCentral.LinterCop/Design/Rule0081AnalyzeCountMethod.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
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 Rule0081AnalyzeCountMethod : DiagnosticAnalyzer | ||
{ | ||
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = | ||
ImmutableArray.Create(DiagnosticDescriptors.Rule0081UseIsEmptyMethod, DiagnosticDescriptors.Rule0082UseFindWithNext); | ||
|
||
public override void Initialize(AnalysisContext context) => | ||
context.RegisterOperationAction(new Action<OperationAnalysisContext>(this.AnalyzeCountMethod), OperationKind.InvocationExpression); | ||
|
||
private void AnalyzeCountMethod(OperationAnalysisContext ctx) | ||
{ | ||
if (ctx.IsObsoletePendingOrRemoved()) | ||
return; | ||
|
||
if (ctx.Operation is not IInvocationExpression operation) | ||
return; | ||
|
||
if (operation.TargetMethod.MethodKind != MethodKind.BuiltInMethod || | ||
operation.TargetMethod.Name != "Count" || | ||
operation.TargetMethod.ContainingSymbol?.Name != "Table") | ||
return; | ||
|
||
if (operation.Instance?.GetSymbol() is not IVariableSymbol { Type: IRecordTypeSymbol recordTypeSymbol } || recordTypeSymbol.Temporary) | ||
return; | ||
|
||
if (operation.Syntax.Parent is not BinaryExpressionSyntax binaryExpression) | ||
return; | ||
|
||
int rightValue = GetLiteralExpressionValue(binaryExpression.Right); | ||
if (rightValue > Literals.MaxRelevantValue) | ||
return; | ||
|
||
int leftValue = GetLiteralExpressionValue(binaryExpression.Left); | ||
if (leftValue > Literals.MaxRelevantValue) | ||
return; | ||
|
||
if (IsZeroComparison(leftValue, rightValue)) | ||
{ | ||
ReportUseIsEmptyDiagnostic(ctx, operation); | ||
return; | ||
} | ||
|
||
if (IsLessThanOneComparison(binaryExpression, rightValue) || IsGreaterThanOneComparison(binaryExpression, leftValue)) | ||
{ | ||
ReportUseIsEmptyDiagnostic(ctx, operation); | ||
return; | ||
} | ||
|
||
if (IsOneComparison(leftValue, rightValue)) | ||
{ | ||
ReportUseFindWithNextDiagnostic(ctx, operation, GetOperatorKind(binaryExpression.OperatorToken.Kind)); | ||
return; | ||
} | ||
|
||
if (IsLessThanTwoComparison(binaryExpression, rightValue) || IsGreaterThanTwoComparison(binaryExpression, leftValue)) | ||
{ | ||
ReportUseFindWithNextDiagnostic(ctx, operation, SyntaxKind.EqualsToken); | ||
return; | ||
} | ||
} | ||
|
||
private static int GetLiteralExpressionValue(CodeExpressionSyntax codeExpression) => | ||
codeExpression is LiteralExpressionSyntax { Literal.Kind: SyntaxKind.Int32SignedLiteralValue } literalExpression && | ||
literalExpression.Literal.GetLiteralValue() is int value ? value : -1; | ||
|
||
private static SyntaxKind GetOperatorKind(SyntaxKind tokenKind) => | ||
tokenKind == SyntaxKind.EqualsToken ? SyntaxKind.EqualsToken : SyntaxKind.NotEqualsToken; | ||
|
||
private static bool IsZeroComparison(int left, int right) | ||
=> left == Literals.Zero || right == Literals.Zero; | ||
|
||
private static bool IsLessThanOneComparison(BinaryExpressionSyntax expr, int right) => | ||
expr.OperatorToken.Kind == SyntaxKind.LessThanToken && right == Literals.One; | ||
|
||
private static bool IsGreaterThanOneComparison(BinaryExpressionSyntax expr, int left) => | ||
expr.OperatorToken.Kind == SyntaxKind.GreaterThanToken && left == Literals.One; | ||
|
||
private static bool IsOneComparison(int left, int right) => | ||
left == Literals.One || right == Literals.One; | ||
|
||
private static bool IsLessThanTwoComparison(BinaryExpressionSyntax expr, int right) => | ||
expr.OperatorToken.Kind == SyntaxKind.LessThanToken && right == Literals.Two; | ||
|
||
private static bool IsGreaterThanTwoComparison(BinaryExpressionSyntax expr, int left) => | ||
expr.OperatorToken.Kind == SyntaxKind.GreaterThanToken && left == Literals.Two; | ||
|
||
private static class Literals | ||
{ | ||
public const int Zero = 0; | ||
public const int One = 1; | ||
public const int Two = 2; | ||
public const int MaxRelevantValue = 2; | ||
} | ||
|
||
private static void ReportUseIsEmptyDiagnostic(OperationAnalysisContext ctx, IInvocationExpression operation) | ||
{ | ||
ctx.ReportDiagnostic(Diagnostic.Create( | ||
DiagnosticDescriptors.Rule0081UseIsEmptyMethod, | ||
operation.Syntax.Parent.GetLocation(), | ||
new object[] { GetSymbolName(operation) })); | ||
} | ||
|
||
private static void ReportUseFindWithNextDiagnostic(OperationAnalysisContext ctx, IInvocationExpression operation, SyntaxKind operatorToken) | ||
{ | ||
string operatorSign = operatorToken == SyntaxKind.EqualsToken ? "=" : "<>"; | ||
|
||
ctx.ReportDiagnostic(Diagnostic.Create( | ||
DiagnosticDescriptors.Rule0082UseFindWithNext, | ||
operation.Syntax.Parent.GetLocation(), | ||
new object[] { GetSymbolName(operation), operatorSign })); | ||
} | ||
|
||
private static string GetSymbolName(IInvocationExpression operation) => | ||
operation.Instance?.GetSymbol()?.Name.QuoteIdentifierIfNeeded() ?? string.Empty; | ||
|
||
public static class DiagnosticDescriptors | ||
{ | ||
public static readonly DiagnosticDescriptor Rule0081UseIsEmptyMethod = new( | ||
id: LinterCopAnalyzers.AnalyzerPrefix + "0081", | ||
title: LinterCopAnalyzers.GetLocalizableString("Rule0081UseIsEmptyMethodTitle"), | ||
messageFormat: LinterCopAnalyzers.GetLocalizableString("Rule0081UseIsEmptyMethodFormat"), | ||
category: "Design", | ||
defaultSeverity: DiagnosticSeverity.Info, isEnabledByDefault: true, | ||
description: LinterCopAnalyzers.GetLocalizableString("Rule0081UseIsEmptyMethodDescription"), | ||
helpLinkUri: "https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0081"); | ||
|
||
public static readonly DiagnosticDescriptor Rule0082UseFindWithNext = new( | ||
id: LinterCopAnalyzers.AnalyzerPrefix + "0082", | ||
title: LinterCopAnalyzers.GetLocalizableString("Rule0082UseFindWithNextTitle"), | ||
messageFormat: LinterCopAnalyzers.GetLocalizableString("Rule0082UseFindWithNextFormat"), | ||
category: "Design", | ||
defaultSeverity: DiagnosticSeverity.Info, isEnabledByDefault: true, | ||
description: LinterCopAnalyzers.GetLocalizableString("Rule0082UseFindWithNextDescription"), | ||
helpLinkUri: "https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0082"); | ||
} | ||
} | ||
} |
Oops, something went wrong.