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

#256: fix INTL0202 not showing in lambda func #255

Draft
wants to merge 11 commits into
base: main
Choose a base branch
from
2 changes: 1 addition & 1 deletion .github/workflows/dotnetBuild.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,4 @@ jobs:
run: dotnet build -p:ContinuousIntegrationBuild=True --no-restore --configuration Release

- name: Test
run: dotnet test --no-build --configuration Release --verbosity normal
run: dotnet test --no-build --configuration Release --verbosity normal
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using Microsoft.CodeAnalysis;
using System;
using System.IO;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using TestHelper;
Expand Down Expand Up @@ -31,16 +33,13 @@ static void Main(string[] args)
Id = "INTL0202",
Severity = DiagnosticSeverity.Warning,
Message = "Using the symbol 'DateTimeOffset.implicit operator DateTimeOffset(DateTime)' can result in unpredictable behavior",
Locations =
[
new DiagnosticResultLocation("Test0.cs", 10, 38)
]
Locations = [new DiagnosticResultLocation("Test0.cs", 10, 38)]
});

}

[TestMethod]
public void UsageOfImplicitConversionInComparison_ProducesWarningMessage()
public void UsageOfImplicitConversionInComparison_DateTimeToDateTimeOffset_ProducesWarningMessage()
{
string source = @"
using System;
Expand All @@ -53,15 +52,39 @@ internal class Program
static void Main(string[] args)
{
DateTime first = DateTime.Now;
DateTimeOffset second = DateTimeOffset.Now;
_ = first < second
}
}
}";

Thread.Sleep(10);
VerifyCSharpDiagnostic(source,
new DiagnosticResult
{
Id = "INTL0202",
Severity = DiagnosticSeverity.Warning,
Message = "Using the symbol 'DateTimeOffset.implicit operator DateTimeOffset(DateTime)' can result in unpredictable behavior",
Locations = [new DiagnosticResultLocation("Test0.cs", 13, 17)]
});

DateTimeOffset second = DateTimeOffset.Now;
}

if (first < second)
{
Console.WriteLine(""Time has passed..."");
}
[TestMethod]
public void UsageOfImplicitConversionInComparison_DateTimeOffsetToDateTime_ProducesWarningMessage()
{
string source = @"
using System;
using System.Threading;

namespace ConsoleApp1
{
internal class Program
{
static void Main(string[] args)
{
DateTimeOffset first = DateTimeOffset.Now;
DateTime second = DateTime.Now;
_ = first < second
}
}
}";
Expand All @@ -72,14 +95,150 @@ static void Main(string[] args)
Id = "INTL0202",
Severity = DiagnosticSeverity.Warning,
Message = "Using the symbol 'DateTimeOffset.implicit operator DateTimeOffset(DateTime)' can result in unpredictable behavior",
Locations =
[
new DiagnosticResultLocation("Test0.cs", 17, 17)
]
Locations = [new DiagnosticResultLocation("Test0.cs", 13, 25)]
});

}

[TestMethod]
public void UsageOfImplicitConversionInComparison_NullableDateTimeOffsetToDateTime_ProducesWarningMessage()
{
string source = @"
using System;
using System.Threading;

namespace ConsoleApp1
{
internal class Program
{
static void Main(string[] args)
{
DateTimeOffset? first = DateTimeOffset.Now;
DateTime second = DateTime.Now;
_ = first < second
}
}
}";

VerifyCSharpDiagnostic(
source,
new DiagnosticResult
{
Id = "INTL0202",
Severity = DiagnosticSeverity.Warning,
Message = "Using the symbol 'DateTimeOffset.implicit operator DateTimeOffset(DateTime)' can result in unpredictable behavior",
Locations = [new DiagnosticResultLocation("Test0.cs", 13, 25)]
}
);

}

[TestMethod]
public void UsageOfImplicitConversion_WithProperties_ProducesWarningMessage()
{
string source = @"
using System;
using System.Collections.Generic;
using System.Linq;

namespace ConsoleApp1
{
internal class Pair(DateTimeOffset DateTimeOffset, DateTime DateTime);

internal class Program
{
static void Main(string[] args)
{
Pair pair = new(DateTimeOffset.Now, DateTime.Now);
retirn pair.DateTimeOffset < pair.DateTime;
}
}
}";

VerifyCSharpDiagnostic(
source,
new DiagnosticResult
{
Id = "INTL0202",
Severity = DiagnosticSeverity.Warning,
Message = "Using the symbol 'DateTimeOffset.implicit operator DateTimeOffset(DateTime)' can result in unpredictable behavior",
Locations = [new DiagnosticResultLocation("Test0.cs", 14, 29)]
}
);
}

[TestMethod]
public void UsageOfImplicitConversion_WithPropertiesInLinq_ProducesWarningMessage()
{
string source = @"
using System;
using System.Collections.Generic;
using System.Linq;

namespace ConsoleApp1
{
internal class Pair(DateTimeOffset DateTimeOffset, DateTime DateTime);

internal class Program
{
static void Main(string[] args)
{
List<Pair> list = new(){ new(DateTimeOffset.Now, DateTime.Now) }; // <-- L14
_ = list.Where(pair => pair.DateTimeOffset < pair.DateTime);
}
}
}";

VerifyCSharpDiagnostic(
source,
new DiagnosticResult
{
Id = "INTL0202",
Severity = DiagnosticSeverity.Warning,
Message = "Using the symbol 'DateTimeOffset.implicit operator DateTimeOffset(DateTime)' can result in unpredictable behavior",
Locations = [new DiagnosticResultLocation("Test0.cs", 14, 42)]
}
);
}

[TestMethod]
public void UsageOfImplicitConversion_InLinqWithVariables_ProducesWarningMessage()
{
string source = @"
using System;
using System.Collections.Generic;
using System.Linq;

namespace ConsoleApp1
{
internal class Pair(DateTimeOffset DateTimeOffset, DateTime DateTime);

internal class Program
{
static void Main(string[] args)
{
List<Pair> list = new(){ new(DateTimeOffset.Now, DateTime.Now) };
_ = list.Where(pair => {
DateTimeOffset first = pair.DateTimeOffset;
DateTime second = pair.DateTime;
return first < second;
});
}
}
}";

VerifyCSharpDiagnostic(
source,
new DiagnosticResult
{
Id = "INTL0202",
Severity = DiagnosticSeverity.Warning,
Message = "Using the symbol 'DateTimeOffset.implicit operator DateTimeOffset(DateTime)' can result in unpredictable behavior",
Locations = [new DiagnosticResultLocation("Test0.cs", 18, 24)]
}
);
}

[TestMethod]
public void UsageOfExplicitConversion_ProducesNothing()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,16 +167,16 @@ private static void VerifyDiagnosticLocation(DiagnosticAnalyzer analyzer, Diagno
// Only check line position if there is an actual line in the real diagnostic
if (actualLinePosition.Line > 0)
{
Assert.AreEqual(actualLinePosition.Line + 1,
expected.Line,
Assert.AreEqual(expected.Line,
actualLinePosition.Line + 1,
$"Expected diagnostic to be on line \"{expected.Line}\" was actually on line \"{actualLinePosition.Line + 1}\"{Environment.NewLine}{Environment.NewLine}Diagnostic:{Environment.NewLine} {FormatDiagnostics(analyzer, diagnostic)}{Environment.NewLine}");
}

// Only check column position if there is an actual column position in the real diagnostic
if (actualLinePosition.Character > 0)
{
Assert.AreEqual(actualLinePosition.Character + 1,
expected.Column,
Assert.AreEqual(expected.Column,
actualLinePosition.Character + 1,
$"Expected diagnostic to start at column \"{expected.Column}\" was actually at column \"{actualLinePosition.Character + 1}\"{Environment.NewLine}{Environment.NewLine}Diagnostic:{Environment.NewLine} {FormatDiagnostics(analyzer, diagnostic)}{Environment.NewLine}");
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Operations;
Expand Down Expand Up @@ -33,22 +34,35 @@ public override void Initialize(AnalysisContext context)

private void AnalyzeInvocation(OperationAnalysisContext context)
{
if (context.Operation is not IConversionOperation conversionOperation)
if (context.Operation is not IConversionOperation conversionOperation || !conversionOperation.Conversion.IsImplicit)
{
return;
}

if (conversionOperation.Conversion.IsImplicit && conversionOperation.Conversion.MethodSymbol is object && conversionOperation.Conversion.MethodSymbol.ContainingType is object)
if (conversionOperation.Conversion.MethodSymbol is object && conversionOperation.Conversion.MethodSymbol.ContainingType is object)
{
INamedTypeSymbol containingType = conversionOperation.Conversion.MethodSymbol.ContainingType;
INamedTypeSymbol dateTimeOffsetType = context.Compilation.GetTypeByMetadataName("System.DateTimeOffset");
if (SymbolEqualityComparer.Default.Equals(containingType, dateTimeOffsetType))
if (IsDateTimeOffsetSymbol(context, containingType))
{
context.ReportDiagnostic(Diagnostic.Create(_Rule202, conversionOperation.Syntax.GetLocation()));
}
}
else
{
IOperation implicitDateTimeOffsetOp = conversionOperation.Operand.ChildOperations
.Where(op => op.Kind == OperationKind.Argument && IsDateTimeOffsetSymbol(context, ((IArgumentOperation)op).Value.Type))
.FirstOrDefault();
if (implicitDateTimeOffsetOp != default)
{
context.ReportDiagnostic(Diagnostic.Create(_Rule202, implicitDateTimeOffsetOp.Syntax.GetLocation()));
}
}
}


private static bool IsDateTimeOffsetSymbol(OperationAnalysisContext context, ITypeSymbol symbol)
{
INamedTypeSymbol dateTimeOffsetType = context.Compilation.GetTypeByMetadataName("System.DateTimeOffset");
return SymbolEqualityComparer.Default.Equals(symbol, dateTimeOffsetType);
}

private static class Rule202
Expand Down
Loading