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

Rebrand Rule0082 to Consider Query or Find with Next #975

Merged
merged 2 commits into from
Mar 3, 2025
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
4 changes: 2 additions & 2 deletions BusinessCentral.LinterCop.Test/Rule0082.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public async Task HasDiagnostic(string testCase)
.ConfigureAwait(false);

var fixture = RoslynFixtureFactory.Create<Rule0081AnalyzeCountMethod>();
fixture.HasDiagnosticAtAllMarkers(code, DiagnosticDescriptors.Rule0082UseFindWithNext.Id);
fixture.HasDiagnosticAtAllMarkers(code, DiagnosticDescriptors.Rule0082UseQueryOrFindWithNext.Id);
}

[Test]
Expand All @@ -37,6 +37,6 @@ public async Task NoDiagnostic(string testCase)
.ConfigureAwait(false);

var fixture = RoslynFixtureFactory.Create<Rule0081AnalyzeCountMethod>();
fixture.NoDiagnosticAtAllMarkers(code, DiagnosticDescriptors.Rule0082UseFindWithNext.Id);
fixture.NoDiagnosticAtAllMarkers(code, DiagnosticDescriptors.Rule0082UseQueryOrFindWithNext.Id);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@ table 50100 MyTable
{
fields
{
field(1; MyField; Integer) { }
field(1; "Entry No."; Integer) { }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@ table 50100 MyTable
{
fields
{
field(1; MyField; Integer) { }
field(1; "Entry No."; Integer) { }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@ table 50100 MyTable
{
fields
{
field(1; MyField; Integer) { }
field(1; "Entry No."; Integer) { }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@ table 50100 MyTable
{
fields
{
field(1; MyField; Integer) { }
field(1; "Entry No."; Integer) { }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@ table 50100 MyTable
{
fields
{
field(1; MyField; Integer) { }
field(1; "Entry No."; Integer) { }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@ table 50100 MyTable
{
fields
{
field(1; MyField; Integer) { }
field(1; "Entry No."; Integer) { }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@ table 50100 MyTable
{
fields
{
field(1; MyField; Integer) { }
field(1; "Entry No."; Integer) { }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@ table 50100 MyTable
{
fields
{
field(1; MyField; Integer) { }
field(1; "Entry No."; Integer) { }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@ table 50100 MyTable
{
fields
{
field(1; MyField; Integer) { }
field(1; "Entry No."; Integer) { }
}
}
49 changes: 38 additions & 11 deletions BusinessCentral.LinterCop/Design/Rule0081AnalyzeCountMethod.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ namespace BusinessCentral.LinterCop.Design;
public class Rule0081AnalyzeCountMethod : DiagnosticAnalyzer
{
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } =
ImmutableArray.Create(DiagnosticDescriptors.Rule0081UseIsEmptyMethod, DiagnosticDescriptors.Rule0082UseFindWithNext);
ImmutableArray.Create(
DiagnosticDescriptors.Rule0081UseIsEmptyMethod,
DiagnosticDescriptors.Rule0082UseQueryOrFindWithNext);

public override void Initialize(AnalysisContext context) =>
context.RegisterOperationAction(new Action<OperationAnalysisContext>(this.AnalyzeCountMethod), OperationKind.InvocationExpression);
Expand Down Expand Up @@ -52,16 +54,19 @@ private void AnalyzeCountMethod(OperationAnalysisContext ctx)
return;
}

if (IsOneComparison(leftValue, rightValue))
{
ReportUseFindWithNextDiagnostic(ctx, operation, GetOperatorKind(binaryExpression.OperatorToken.Kind));
return;
}

if (IsLessThanTwoComparison(binaryExpression, rightValue) || IsGreaterThanTwoComparison(binaryExpression, leftValue))
if (IsEligibleUseQueryOrFindWithNext(recordTypeSymbol))
{
ReportUseFindWithNextDiagnostic(ctx, operation, SyntaxKind.EqualsToken);
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;
}
}
}

Expand Down Expand Up @@ -98,6 +103,28 @@ private static class Literals
public const int MaxRelevantValue = 2;
}

// Tables with one of these identifiers in the name could possible have a large amount of records
private static readonly HashSet<string> possibleLargeTableIdentifierKeywords = new HashSet<string>
{
"Ledger", "GL", "G/L",
"Posted", "Pstd",
"Log",
"Entry",
"Archive",
};

private bool IsEligibleUseQueryOrFindWithNext(IRecordTypeSymbol record)
{
if (possibleLargeTableIdentifierKeywords.Any(keyword => record.Name.IndexOf(keyword, StringComparison.OrdinalIgnoreCase) >= 0))
return true;

// Tables with a field "Entry No." could possible have a large amount of records
if (record.OriginalDefinition is ITableTypeSymbol table)
return table.PrimaryKey.Fields.Any(field => string.Equals(field.Name, "Entry No.", StringComparison.OrdinalIgnoreCase));

return false;
}

private static void ReportUseIsEmptyDiagnostic(OperationAnalysisContext ctx, IInvocationExpression operation)
{
ctx.ReportDiagnostic(Diagnostic.Create(
Expand All @@ -111,7 +138,7 @@ private static void ReportUseFindWithNextDiagnostic(OperationAnalysisContext ctx
string operatorSign = operatorToken == SyntaxKind.EqualsToken ? "=" : "<>";

ctx.ReportDiagnostic(Diagnostic.Create(
DiagnosticDescriptors.Rule0082UseFindWithNext,
DiagnosticDescriptors.Rule0082UseQueryOrFindWithNext,
operation.Syntax.Parent.GetLocation(),
GetSymbolName(operation), operatorSign));
}
Expand Down
8 changes: 4 additions & 4 deletions BusinessCentral.LinterCop/LinterCopAnalyzers.Generated.cs
Original file line number Diff line number Diff line change
Expand Up @@ -835,14 +835,14 @@ public static class DiagnosticDescriptors
description: LinterCopAnalyzers.GetLocalizableString("Rule0081UseIsEmptyMethodDescription"),
helpLinkUri: "https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0081");

public static readonly DiagnosticDescriptor Rule0082UseFindWithNext = new(
public static readonly DiagnosticDescriptor Rule0082UseQueryOrFindWithNext = new(
id: LinterCopAnalyzers.AnalyzerPrefix + "0082",
title: LinterCopAnalyzers.GetLocalizableString("Rule0082UseFindWithNextTitle"),
messageFormat: LinterCopAnalyzers.GetLocalizableString("Rule0082UseFindWithNextFormat"),
title: LinterCopAnalyzers.GetLocalizableString("Rule0082UseQueryOrFindWithNextTitle"),
messageFormat: LinterCopAnalyzers.GetLocalizableString("Rule0082UseQueryOrFindWithNextFormat"),
category: "Design",
defaultSeverity: DiagnosticSeverity.Info,
isEnabledByDefault: true,
description: LinterCopAnalyzers.GetLocalizableString("Rule0082UseFindWithNextDescription"),
description: LinterCopAnalyzers.GetLocalizableString("Rule0082UseQueryOrFindWithNextDescription"),
helpLinkUri: "https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0082");

public static readonly DiagnosticDescriptor Rule0083BuiltInDateTimeMethod = new(
Expand Down
12 changes: 6 additions & 6 deletions BusinessCentral.LinterCop/LinterCopAnalyzers.resx
Original file line number Diff line number Diff line change
Expand Up @@ -849,14 +849,14 @@
<data name="Rule0081UseIsEmptyMethodDescription" xml:space="preserve">
<value>To check for the existence of records, use the more efficient Rec.IsEmpty() function instead of Rec.Count().</value>
</data>
<data name="Rule0082UseFindWithNextTitle" xml:space="preserve">
<value>Use Rec.Find('-') with Rec.Next() for checking exactly one record.</value>
<data name="Rule0082UseQueryOrFindWithNextTitle" xml:space="preserve">
<value>Consider using a Query object or Rec.Find('-') with Rec.Next() for checking exactly one record.</value>
</data>
<data name="Rule0082UseFindWithNextFormat" xml:space="preserve">
<value>Use {0}.Find('-') together with {0}.Next() instead of {0}.Count() for performance optimization. Replace {0}.Count() with: {0}.Find('-') and ({0}.Next() {1} 0).</value>
<data name="Rule0082UseQueryOrFindWithNextFormat" xml:space="preserve">
<value>Consider using a Query object or {0}.Find('-') together with {0}.Next() instead of {0}.Count() for faster and more efficient record checks.</value>
</data>
<data name="Rule0082UseFindWithNextDescription" xml:space="preserve">
<value>Instead of relying on Rec.Count(), you should use a combination of Rec.Find('-') and Rec.Next() for faster and more efficient record checks.</value>
<data name="Rule0082UseQueryOrFindWithNextDescription" xml:space="preserve">
<value>Instead of relying on Rec.Count(), consider using a Query object or a combination of Rec.Find('-') and Rec.Next() for faster and more efficient record checks.</value>
</data>
<data name="Rule0083BuiltInDateTimeMethodTitle" xml:space="preserve">
<value>Use new Date/Time/DateTime methods for extracting parts.</value>
Expand Down
Loading