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 Rule0083: Use new Date/Time/DateTime methods for extracting parts #841

Merged
merged 2 commits into from
Dec 19, 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
43 changes: 43 additions & 0 deletions BusinessCentral.LinterCop.Test/Rule0083.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
namespace BusinessCentral.LinterCop.Test;

#if !LessThenFall2024
public class Rule0083
{
private string _testCaseDir = "";

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

[Test]
[TestCase("Date2DMY")]
[TestCase("Date2DWY")]
[TestCase("DT2Date")]
[TestCase("DT2Time")]
[TestCase("FormatHour")]
[TestCase("FormatMillisecond")]
[TestCase("FormatMinute")]
[TestCase("FormatSecond")]
public async Task HasDiagnostic(string testCase)
{
var code = await File.ReadAllTextAsync(Path.Combine(_testCaseDir, "HasDiagnostic", $"{testCase}.al"))
.ConfigureAwait(false);

var fixture = RoslynFixtureFactory.Create<Rule0083BuiltInDateTimeMethod>();
fixture.HasDiagnostic(code, Rule0083BuiltInDateTimeMethod.DiagnosticDescriptors.Rule0083BuiltInDateTimeMethod.Id);
}

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

// var fixture = RoslynFixtureFactory.Create<Rule0083BuiltInDateTimeMethod>();
// fixture.NoDiagnosticAtMarker(code, Rule0083BuiltInDateTimeMethod.DiagnosticDescriptors.Rule0083BuiltInDateTimeMethod.Id);
// }
}
#endif
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
codeunit 50100 MyCodeunit
{
procedure MyProcedure()
var
MyDateTime: DateTime;
MyDate: Date;
begin
MyDate := [|DT2Date(MyDateTime)|];
end;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
codeunit 50100 MyCodeunit
{
procedure MyProcedure()
var
MyDateTime: DateTime;
MyTime: Time;
begin
MyTime := [|DT2Time(MyDateTime)|];
end;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
codeunit 50100 MyCodeunit
{
procedure MyProcedure()
var
MyDate: Date;
MyInteger: Integer;
begin
MyInteger := [|Date2DMY(MyDate, 1)|];
end;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
codeunit 50100 MyCodeunit
{
procedure MyProcedure()
var
MyDate: Date;
MyInteger: Integer;
begin
MyInteger := [|Date2DMY(MyDate, 1)|];
end;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
codeunit 50100 MyCodeunit
{
procedure MyProcedure()
var
MyTime: Time;
i: Integer;
begin
Evaluate(i, [|Format(MyTime, 2, '<HOURS24>')|]);
end;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
codeunit 50100 MyCodeunit
{
procedure MyProcedure()
var
MyTime: Time;
i: Integer;
begin
Evaluate(i, [|Format(MyTime, 2, '<THOUSANDS>')|]);
end;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
codeunit 50100 MyCodeunit
{
procedure MyProcedure()
var
MyTime: Time;
i: Integer;
begin
Evaluate(i, [|Format(MyTime, 2, '<MINUTES>')|]);
end;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
codeunit 50100 MyCodeunit
{
procedure MyProcedure()
var
MyTime: Time;
i: Integer;
begin
Evaluate(i, [|Format(MyTime, 2, '<SECONDS>')|]);
end;
}
112 changes: 112 additions & 0 deletions BusinessCentral.LinterCop/Design/Rule0083BuiltInDateTimeMethod.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
#if !LessThenFall2024
using BusinessCentral.LinterCop.AnalysisContextExtension;
using Microsoft.Dynamics.Nav.CodeAnalysis;
using Microsoft.Dynamics.Nav.CodeAnalysis.Diagnostics;
using Microsoft.Dynamics.Nav.CodeAnalysis.Utilities;
using System.Collections.Immutable;

namespace BusinessCentral.LinterCop.Design
{
[DiagnosticAnalyzer]
public class Rule0083BuiltInDateTimeMethod : DiagnosticAnalyzer
{
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create(DiagnosticDescriptors.Rule0083BuiltInDateTimeMethod);

public override VersionCompatibility SupportedVersions => VersionCompatibility.Fall2024OrGreater;

public override void Initialize(AnalysisContext context) =>
context.RegisterOperationAction(new Action<OperationAnalysisContext>(this.AnalyzeInvocation), OperationKind.InvocationExpression);

private void AnalyzeInvocation(OperationAnalysisContext ctx)
{
if (ctx.IsObsoletePendingOrRemoved())
return;

if ((ctx.Operation is not IInvocationExpression operation) ||
operation.TargetMethod is null ||
operation.TargetMethod.MethodKind != MethodKind.BuiltInMethod ||
operation.Arguments.Count() < 1)
return;

string? recommendedMethod = operation.TargetMethod.Name switch
{
"Date2DMY" => GetDate2DMYReplacement(operation),
"Date2DWY" => GetDate2DWYReplacement(operation),
"DT2Date" => "Date",
"DT2Time" => "Time",
"Format" => GetFormatReplacement(operation),
_ => null
};

if (string.IsNullOrEmpty(recommendedMethod))
return;

ctx.ReportDiagnostic(Diagnostic.Create(
DiagnosticDescriptors.Rule0083BuiltInDateTimeMethod,
ctx.Operation.Syntax.GetLocation(),
new object[] { operation.Arguments[0].Value.Syntax.ToString().QuoteIdentifierIfNeeded(), recommendedMethod }));
}

private string? GetDate2DMYReplacement(IInvocationExpression operation)
{
if (operation.Arguments.Length < 2)
return null;

return operation.Arguments[1].Value.ConstantValue.Value switch
{
1 => "Day",
2 => "Month",
3 => "Year",
_ => "<Day/Month/Year>"
};
}

private string? GetDate2DWYReplacement(IInvocationExpression operation)
{
int formatSpecifier = -1;

if (operation.Arguments.Length >= 2 &&
operation.Arguments[1].Value.ConstantValue.Value is int extractedValue)
{
formatSpecifier = extractedValue;
}

return formatSpecifier switch
{
1 => "DayOfWeek",
2 => "Year",
_ => "<DayOfWeek/Year>"
};
}

private string? GetFormatReplacement(IInvocationExpression operation)
{
string? formatSpecifier = String.Empty;

if (operation.Arguments.Length >= 3)
formatSpecifier = operation.Arguments[2].Value.ConstantValue.Value?.ToString();

return formatSpecifier switch
{
"<HOURS24>" => "Hour",
"<MINUTES>" => "Minute",
"<SECONDS>" => "Second",
"<THOUSANDS>" => "Millisecond",
_ => "<Hour/Minute/Second/Millisecond>"
};
}

public static class DiagnosticDescriptors
{
public static readonly DiagnosticDescriptor Rule0083BuiltInDateTimeMethod = new(
id: LinterCopAnalyzers.AnalyzerPrefix + "0083",
title: LinterCopAnalyzers.GetLocalizableString("Rule0083BuiltInDateTimeMethodTitle"),
messageFormat: LinterCopAnalyzers.GetLocalizableString("Rule0083BuiltInDateTimeMethodFormat"),
category: "Design",
defaultSeverity: DiagnosticSeverity.Info, isEnabledByDefault: true,
description: LinterCopAnalyzers.GetLocalizableString("Rule0083BuiltInDateTimeMethodDescription"),
helpLinkUri: "https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0082");
}
}
}
#endif
5 changes: 5 additions & 0 deletions BusinessCentral.LinterCop/LinterCop.ruleset.json
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,11 @@
"id": "LC0082",
"action": "Info",
"justification": "Use Rec.Find('-') with Rec.Next() for checking exactly one record."
},
{
"id": "LC0083",
"action": "Info",
"justification": "Use new Date/Time/DateTime methods for extracting parts."
}
]
}
9 changes: 9 additions & 0 deletions BusinessCentral.LinterCop/LinterCopAnalyzers.resx
Original file line number Diff line number Diff line change
Expand Up @@ -858,4 +858,13 @@
<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>
<data name="Rule0083BuiltInDateTimeMethodTitle" xml:space="preserve">
<value>Use new Date/Time/DateTime methods for extracting parts.</value>
</data>
<data name="Rule0083BuiltInDateTimeMethodFormat" xml:space="preserve">
<value>Use the new method '{0}.{1}' to extract specific parts of date/time values.</value>
</data>
<data name="Rule0083BuiltInDateTimeMethodDescription" xml:space="preserve">
<value>Replace outdated functions for extracting specific parts of Date, Time, and DateTime types (such as day, month, hour, or second) with the new, modernized methods.</value>
</data>
</root>
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -235,4 +235,7 @@ For an example and the default values see: [LinterCop.ruleset.json](./BusinessCe
|[LC0079](https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0079)|Event publishers should not be public.|Info|
|[LC0080](https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0080)|Replace double quotes in JPath expressions with two single quotes.|Warning|
|[LC0081](https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0081)|Use `Rec.IsEmpty()` for checking record existence.|Info|
|[LC0082](https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0082)|Use `Rec.Find('-')` with `Rec.Next()` for checking exactly one record.|Info|
|[LC0082](https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0082)|Use `Rec.Find('-')` with `Rec.Next()` for checking exactly one record.|Info|
|[LC0083](https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0083)|Use new Date/Time/DateTime methods for extracting parts.|Info|


Loading