From df707da3a7f835e3e0da495bf43904736a93f5ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9rald=20Barr=C3=A9?= Date: Tue, 25 Apr 2023 19:58:23 -0400 Subject: [PATCH] New rule: MA0133 - Use DateTimeOffset instead of DateTime to avoid an implicit conversion (#506) --- README.md | 1 + docs/README.md | 7 +++++ docs/Rules/MA0133.md | 11 +++++++ src/Meziantou.Analyzer/RuleIdentifiers.cs | 1 + ...ConvertDateTimeToDateTimeOffsetAnalyzer.cs | 24 +++++++++++---- ...WithDateTimeOffsetAnalyzerTests_MA0132.cs} | 6 ++-- ...eWithDateTimeOffsetAnalyzerTests_MA0133.cs | 29 +++++++++++++++++++ 7 files changed, 72 insertions(+), 7 deletions(-) create mode 100644 docs/Rules/MA0133.md rename tests/Meziantou.Analyzer.Test/Rules/{DoNotCompareDateTimeWithDateTimeOffsetAnalyzerTests.cs => DoNotCompareDateTimeWithDateTimeOffsetAnalyzerTests_MA0132.cs} (97%) create mode 100644 tests/Meziantou.Analyzer.Test/Rules/DoNotCompareDateTimeWithDateTimeOffsetAnalyzerTests_MA0133.cs diff --git a/README.md b/README.md index 046c14a44..d9ce452b9 100644 --- a/README.md +++ b/README.md @@ -148,6 +148,7 @@ If you are already using other analyzers, you can check [which rules are duplica |[MA0130](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0130.md)|Usage|GetType() should not be used on System.Type instances|⚠️|✔️|❌| |[MA0131](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0131.md)|Usage|ArgumentNullException.ThrowIfNull should not be used with non-nullable types|⚠️|✔️|❌| |[MA0132](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0132.md)|Design|Do not convert implicitly to DateTimeOffset|⚠️|✔️|❌| +|[MA0133](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0133.md)|Design|Use DateTimeOffset instead of relying on the implicit conversion|ℹ️|✔️|❌| diff --git a/docs/README.md b/docs/README.md index bc881e162..1e8d22b33 100644 --- a/docs/README.md +++ b/docs/README.md @@ -132,6 +132,7 @@ |[MA0130](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0130.md)|Usage|GetType() should not be used on System.Type instances|⚠️|✔️|❌| |[MA0131](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0131.md)|Usage|ArgumentNullException.ThrowIfNull should not be used with non-nullable types|⚠️|✔️|❌| |[MA0132](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0132.md)|Design|Do not convert implicitly to DateTimeOffset|⚠️|✔️|❌| +|[MA0133](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0133.md)|Design|Use DateTimeOffset instead of relying on the implicit conversion|ℹ️|✔️|❌| |Id|Suppressed rule|Justification| |--|---------------|-------------| @@ -534,6 +535,9 @@ dotnet_diagnostic.MA0131.severity = warning # MA0132: Do not convert implicitly to DateTimeOffset dotnet_diagnostic.MA0132.severity = warning + +# MA0133: Use DateTimeOffset instead of relying on the implicit conversion +dotnet_diagnostic.MA0133.severity = suggestion ``` # .editorconfig - all rules disabled @@ -931,4 +935,7 @@ dotnet_diagnostic.MA0131.severity = none # MA0132: Do not convert implicitly to DateTimeOffset dotnet_diagnostic.MA0132.severity = none + +# MA0133: Use DateTimeOffset instead of relying on the implicit conversion +dotnet_diagnostic.MA0133.severity = none ``` diff --git a/docs/Rules/MA0133.md b/docs/Rules/MA0133.md new file mode 100644 index 000000000..8b69c5ccf --- /dev/null +++ b/docs/Rules/MA0133.md @@ -0,0 +1,11 @@ +# MA0133 - Use DateTimeOffset instead of relying on the implicit conversion + +Replace `DateTime.UtcNow` or `DateTime.Now` with `DateTimeOffset.UtcNow` or `DateTimeOffset.Now` to avoid an implicit conversion. + +````c# +Sample(DateTime.UtcNow); // non-compliant + +Sample(DateTimeOffset.UtcNow); // ok + +void Sample(DateTimeOffset datetime) { } +```` diff --git a/src/Meziantou.Analyzer/RuleIdentifiers.cs b/src/Meziantou.Analyzer/RuleIdentifiers.cs index 5ace6dc40..5c88f88ea 100644 --- a/src/Meziantou.Analyzer/RuleIdentifiers.cs +++ b/src/Meziantou.Analyzer/RuleIdentifiers.cs @@ -135,6 +135,7 @@ internal static class RuleIdentifiers public const string ObjectGetTypeOnTypeInstance = "MA0130"; public const string ThrowIfNullWithNonNullableInstance = "MA0131"; public const string DoNotImplicitlyConvertDateTimeToDateTimeOffset = "MA0132"; + public const string UseDateTimeOffsetInsteadOfDateTime = "MA0133"; public static string GetHelpUri(string identifier) { diff --git a/src/Meziantou.Analyzer/Rules/DoNotImplicitlyConvertDateTimeToDateTimeOffsetAnalyzer.cs b/src/Meziantou.Analyzer/Rules/DoNotImplicitlyConvertDateTimeToDateTimeOffsetAnalyzer.cs index 4646ff145..df2753046 100644 --- a/src/Meziantou.Analyzer/Rules/DoNotImplicitlyConvertDateTimeToDateTimeOffsetAnalyzer.cs +++ b/src/Meziantou.Analyzer/Rules/DoNotImplicitlyConvertDateTimeToDateTimeOffsetAnalyzer.cs @@ -9,7 +9,7 @@ namespace Meziantou.Analyzer.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class DoNotImplicitlyConvertDateTimeToDateTimeOffsetAnalyzer : DiagnosticAnalyzer { - private static readonly DiagnosticDescriptor s_rule = new( + private static readonly DiagnosticDescriptor s_ruleImplicitConversion = new( RuleIdentifiers.DoNotImplicitlyConvertDateTimeToDateTimeOffset, title: "Do not convert implicitly to DateTimeOffset", messageFormat: "Do not convert implicitly to DateTimeOffset", @@ -19,7 +19,17 @@ public sealed class DoNotImplicitlyConvertDateTimeToDateTimeOffsetAnalyzer : Dia description: "", helpLinkUri: RuleIdentifiers.GetHelpUri(RuleIdentifiers.DoNotImplicitlyConvertDateTimeToDateTimeOffset)); - public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(s_rule); + private static readonly DiagnosticDescriptor s_ruleUseDateTimeOffset = new( + RuleIdentifiers.UseDateTimeOffsetInsteadOfDateTime, + title: "Use DateTimeOffset instead of relying on the implicit conversion", + messageFormat: "Use DateTimeOffset instead of relying on the implicit conversion", + RuleCategories.Design, + DiagnosticSeverity.Info, + isEnabledByDefault: true, + description: "", + helpLinkUri: RuleIdentifiers.GetHelpUri(RuleIdentifiers.UseDateTimeOffsetInsteadOfDateTime)); + + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(s_ruleImplicitConversion, s_ruleUseDateTimeOffset); public override void Initialize(AnalysisContext context) { @@ -46,9 +56,13 @@ private static void AnalyzeConversion(OperationAnalysisContext context, INamedTy { // DateTime.Now and DateTime.UtcNow set the DateTime.Kind, so the conversion result is well-known if (operation.Operand is IMemberReferenceOperation { Member.Name: "UtcNow" or "Now", Member.ContainingType.SpecialType: SpecialType.System_DateTime }) - return; - - context.ReportDiagnostic(s_rule, operation); + { + context.ReportDiagnostic(s_ruleUseDateTimeOffset, operation); + } + else + { + context.ReportDiagnostic(s_ruleImplicitConversion, operation); + } } } } diff --git a/tests/Meziantou.Analyzer.Test/Rules/DoNotCompareDateTimeWithDateTimeOffsetAnalyzerTests.cs b/tests/Meziantou.Analyzer.Test/Rules/DoNotCompareDateTimeWithDateTimeOffsetAnalyzerTests_MA0132.cs similarity index 97% rename from tests/Meziantou.Analyzer.Test/Rules/DoNotCompareDateTimeWithDateTimeOffsetAnalyzerTests.cs rename to tests/Meziantou.Analyzer.Test/Rules/DoNotCompareDateTimeWithDateTimeOffsetAnalyzerTests_MA0132.cs index eba53d121..deb348729 100644 --- a/tests/Meziantou.Analyzer.Test/Rules/DoNotCompareDateTimeWithDateTimeOffsetAnalyzerTests.cs +++ b/tests/Meziantou.Analyzer.Test/Rules/DoNotCompareDateTimeWithDateTimeOffsetAnalyzerTests_MA0132.cs @@ -5,13 +5,15 @@ using Xunit; namespace Meziantou.Analyzer.Test.Rules; -public class DoNotCompareDateTimeWithDateTimeOffsetAnalyzerTests + +public class DoNotCompareDateTimeWithDateTimeOffsetAnalyzerTests_MA0132 + { private static ProjectBuilder CreateProjectBuilder() { return new ProjectBuilder() .WithOutputKind(OutputKind.ConsoleApplication) - .WithAnalyzer(); + .WithAnalyzer(id: "MA0132"); } [Fact] diff --git a/tests/Meziantou.Analyzer.Test/Rules/DoNotCompareDateTimeWithDateTimeOffsetAnalyzerTests_MA0133.cs b/tests/Meziantou.Analyzer.Test/Rules/DoNotCompareDateTimeWithDateTimeOffsetAnalyzerTests_MA0133.cs new file mode 100644 index 000000000..7581f47c7 --- /dev/null +++ b/tests/Meziantou.Analyzer.Test/Rules/DoNotCompareDateTimeWithDateTimeOffsetAnalyzerTests_MA0133.cs @@ -0,0 +1,29 @@ +using System.Threading.Tasks; +using Meziantou.Analyzer.Rules; +using Microsoft.CodeAnalysis; +using TestHelper; +using Xunit; + +namespace Meziantou.Analyzer.Test.Rules; + +public class DoNotCompareDateTimeWithDateTimeOffsetAnalyzerTests_MA0133 +{ + private static ProjectBuilder CreateProjectBuilder() + { + return new ProjectBuilder() + .WithOutputKind(OutputKind.ConsoleApplication) + .WithAnalyzer(id: "MA0133"); + } + + [Fact] + public async Task ImplicitConversion_BinaryOperation_Subtract_UtcNow() + { + await CreateProjectBuilder() + .WithSourceCode(""" + using System; + + _ = [|DateTime.UtcNow|] - DateTimeOffset.UtcNow; + """) + .ValidateAsync(); + } +}