Skip to content

Commit 64df4e7

Browse files
Extend INTL0202 analyzer to flag DateTimeOffset(DateTime) constructor
Co-authored-by: BenjaminMichaelis <22186029+BenjaminMichaelis@users.noreply.github.com>
1 parent c4c977f commit 64df4e7

2 files changed

Lines changed: 140 additions & 3 deletions

File tree

IntelliTect.Analyzer/IntelliTect.Analyzer.Test/DateTimeConversionTests.cs

Lines changed: 106 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ static void Main(string[] args)
3030
{
3131
Id = "INTL0202",
3232
Severity = DiagnosticSeverity.Warning,
33-
Message = "Using the symbol 'DateTimeOffset.implicit operator DateTimeOffset(DateTime)' can result in unpredictable behavior",
33+
Message = "Using 'DateTimeOffset.implicit operator DateTimeOffset(DateTime)' or 'new DateTimeOffset(DateTime)' can result in unpredictable behavior",
3434
Locations =
3535
[
3636
new DiagnosticResultLocation("Test0.cs", 10, 38)
@@ -71,7 +71,7 @@ static void Main(string[] args)
7171
{
7272
Id = "INTL0202",
7373
Severity = DiagnosticSeverity.Warning,
74-
Message = "Using the symbol 'DateTimeOffset.implicit operator DateTimeOffset(DateTime)' can result in unpredictable behavior",
74+
Message = "Using 'DateTimeOffset.implicit operator DateTimeOffset(DateTime)' or 'new DateTimeOffset(DateTime)' can result in unpredictable behavior",
7575
Locations =
7676
[
7777
new DiagnosticResultLocation("Test0.cs", 17, 17)
@@ -101,6 +101,110 @@ static void Main(string[] args)
101101

102102
}
103103

104+
[TestMethod]
105+
public void UsageOfDateTimeOffsetConstructorWithDateTime_ProducesWarningMessage()
106+
{
107+
string source = @"
108+
using System;
109+
110+
namespace ConsoleApp44
111+
{
112+
class Program
113+
{
114+
static void Main(string[] args)
115+
{
116+
DateTimeOffset ofs = new DateTimeOffset(DateTime.Now);
117+
}
118+
}
119+
}";
120+
121+
VerifyCSharpDiagnostic(source,
122+
new DiagnosticResult
123+
{
124+
Id = "INTL0202",
125+
Severity = DiagnosticSeverity.Warning,
126+
Message = "Using 'DateTimeOffset.implicit operator DateTimeOffset(DateTime)' or 'new DateTimeOffset(DateTime)' can result in unpredictable behavior",
127+
Locations =
128+
[
129+
new DiagnosticResultLocation("Test0.cs", 10, 38)
130+
]
131+
});
132+
133+
}
134+
135+
[TestMethod]
136+
public void UsageOfDateTimeOffsetConstructorWithDateTimeAndTimeSpan_ProducesNothing()
137+
{
138+
string source = @"
139+
using System;
140+
141+
namespace ConsoleApp45
142+
{
143+
class Program
144+
{
145+
static void Main(string[] args)
146+
{
147+
DateTimeOffset ofs = new DateTimeOffset(DateTime.Now, TimeSpan.Zero);
148+
}
149+
}
150+
}";
151+
152+
VerifyCSharpDiagnostic(source);
153+
154+
}
155+
156+
[TestMethod]
157+
public void UsageOfDateTimeOffsetConstructorWithYearMonthDay_ProducesNothing()
158+
{
159+
string source = @"
160+
using System;
161+
162+
namespace ConsoleApp46
163+
{
164+
class Program
165+
{
166+
static void Main(string[] args)
167+
{
168+
DateTimeOffset ofs = new DateTimeOffset(2022, 2, 2, 0, 0, 0, TimeSpan.Zero);
169+
}
170+
}
171+
}";
172+
173+
VerifyCSharpDiagnostic(source);
174+
175+
}
176+
177+
[TestMethod]
178+
public void UsageOfDateTimeOffsetConstructorWithNewDateTime_ProducesWarningMessage()
179+
{
180+
string source = @"
181+
using System;
182+
183+
namespace ConsoleApp47
184+
{
185+
class Program
186+
{
187+
static void Main(string[] args)
188+
{
189+
DateTimeOffset dto = new DateTimeOffset(new DateTime(2022, 2, 2));
190+
}
191+
}
192+
}";
193+
194+
VerifyCSharpDiagnostic(source,
195+
new DiagnosticResult
196+
{
197+
Id = "INTL0202",
198+
Severity = DiagnosticSeverity.Warning,
199+
Message = "Using 'DateTimeOffset.implicit operator DateTimeOffset(DateTime)' or 'new DateTimeOffset(DateTime)' can result in unpredictable behavior",
200+
Locations =
201+
[
202+
new DiagnosticResultLocation("Test0.cs", 10, 38)
203+
]
204+
});
205+
206+
}
207+
104208
protected override DiagnosticAnalyzer GetCSharpDiagnosticAnalyzer()
105209
{
106210
return new Analyzers.BanImplicitDateTimeToDateTimeOffsetConversion();

IntelliTect.Analyzer/IntelliTect.Analyzer/Analyzers/BanImplicitDateTimeToDateTimeOffsetConversion.cs

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ public override void Initialize(AnalysisContext context)
2929
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics);
3030
context.EnableConcurrentExecution();
3131
context.RegisterOperationAction(AnalyzeInvocation, OperationKind.Conversion);
32+
context.RegisterOperationAction(AnalyzeObjectCreation, OperationKind.ObjectCreation);
3233
}
3334

3435
private void AnalyzeInvocation(OperationAnalysisContext context)
@@ -51,11 +52,43 @@ private void AnalyzeInvocation(OperationAnalysisContext context)
5152

5253
}
5354

55+
private void AnalyzeObjectCreation(OperationAnalysisContext context)
56+
{
57+
if (context.Operation is not IObjectCreationOperation objectCreation)
58+
{
59+
return;
60+
}
61+
62+
INamedTypeSymbol dateTimeOffsetType = context.Compilation.GetTypeByMetadataName("System.DateTimeOffset");
63+
INamedTypeSymbol dateTimeType = context.Compilation.GetTypeByMetadataName("System.DateTime");
64+
65+
if (dateTimeOffsetType is null || dateTimeType is null)
66+
{
67+
return;
68+
}
69+
70+
// Check if we're creating a DateTimeOffset
71+
if (!SymbolEqualityComparer.Default.Equals(objectCreation.Type, dateTimeOffsetType))
72+
{
73+
return;
74+
}
75+
76+
// Check if the constructor has exactly one parameter and it's a DateTime
77+
if (objectCreation.Constructor?.Parameters.Length == 1)
78+
{
79+
IParameterSymbol parameter = objectCreation.Constructor.Parameters[0];
80+
if (SymbolEqualityComparer.Default.Equals(parameter.Type, dateTimeType))
81+
{
82+
context.ReportDiagnostic(Diagnostic.Create(_Rule202, objectCreation.Syntax.GetLocation()));
83+
}
84+
}
85+
}
86+
5487
private static class Rule202
5588
{
5689
internal const string DiagnosticId = "INTL0202";
5790
internal const string Title = "Do not use implicit conversion from `DateTime` to `DateTimeOffset`";
58-
internal const string MessageFormat = "Using the symbol 'DateTimeOffset.implicit operator DateTimeOffset(DateTime)' can result in unpredictable behavior";
91+
internal const string MessageFormat = "Using 'DateTimeOffset.implicit operator DateTimeOffset(DateTime)' or 'new DateTimeOffset(DateTime)' can result in unpredictable behavior";
5992
#pragma warning disable INTL0001 // Allow field to not be prefixed with an underscore to match the style
6093
internal static readonly string HelpMessageUri = DiagnosticUrlBuilder.GetUrl(Title,
6194
DiagnosticId);

0 commit comments

Comments
 (0)