Skip to content

Commit 63f585b

Browse files
committed
Finish MEN018 code fixer
1 parent 2a2cf64 commit 63f585b

File tree

5 files changed

+85
-23
lines changed

5 files changed

+85
-23
lines changed

src/Menees.Analyzers.CodeFixes/Men018UseDigitSeparatorsFixer.cs

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -60,23 +60,37 @@ private static async Task<Document> GetTransformedDocumentAsync(
6060
SyntaxNode? syntaxRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
6161
if (syntaxRoot != null)
6262
{
63-
SyntaxNode violatingNode = syntaxRoot.FindNode(diagnostic.Location.SourceSpan);
64-
if (violatingNode is LiteralExpressionSyntax literalExpression)
63+
SyntaxNode oldNode = syntaxRoot.FindNode(diagnostic.Location.SourceSpan);
64+
if (oldNode is LiteralExpressionSyntax literalExpression)
6565
{
6666
SyntaxToken literalToken = literalExpression.Token;
67-
68-
SyntaxNode oldNode = literalExpression;
69-
SyntaxNode newNode = SyntaxFactory.LiteralExpression(
70-
SyntaxKind.NumericLiteralExpression,
71-
SyntaxFactory.Token(
72-
literalToken.LeadingTrivia,
73-
SyntaxKind.NumericLiteralToken,
74-
preferredText,
75-
preferredText,
76-
literalToken.TrailingTrivia));
77-
78-
var newSyntaxRoot = syntaxRoot.ReplaceNode(oldNode, newNode);
79-
result = document.WithSyntaxRoot(newSyntaxRoot);
67+
SyntaxTriviaList lead = literalToken.LeadingTrivia;
68+
SyntaxTriviaList trail = literalToken.TrailingTrivia;
69+
SyntaxToken? newToken = Type.GetTypeCode(literalToken.Value?.GetType()) switch
70+
{
71+
TypeCode.SByte => SyntaxFactory.Literal(lead, preferredText, (sbyte)literalToken.Value!, trail),
72+
TypeCode.Byte => SyntaxFactory.Literal(lead, preferredText, (byte)literalToken.Value!, trail),
73+
TypeCode.Int16 => SyntaxFactory.Literal(lead, preferredText, (short)literalToken.Value!, trail),
74+
TypeCode.UInt16 => SyntaxFactory.Literal(lead, preferredText, (ushort)literalToken.Value!, trail),
75+
TypeCode.Int32 => SyntaxFactory.Literal(lead, preferredText, (int)literalToken.Value!, trail),
76+
TypeCode.UInt32 => SyntaxFactory.Literal(lead, preferredText, (uint)literalToken.Value!, trail),
77+
TypeCode.Int64 => SyntaxFactory.Literal(lead, preferredText, (long)literalToken.Value!, trail),
78+
TypeCode.UInt64 => SyntaxFactory.Literal(lead, preferredText, (ulong)literalToken.Value!, trail),
79+
TypeCode.Single => SyntaxFactory.Literal(lead, preferredText, (float)literalToken.Value!, trail),
80+
TypeCode.Double => SyntaxFactory.Literal(lead, preferredText, (double)literalToken.Value!, trail),
81+
TypeCode.Decimal => SyntaxFactory.Literal(lead, preferredText, (decimal)literalToken.Value!, trail),
82+
_ => null,
83+
};
84+
85+
if (newToken != null)
86+
{
87+
SyntaxNode newNode = SyntaxFactory.LiteralExpression(
88+
SyntaxKind.NumericLiteralExpression,
89+
newToken.Value);
90+
91+
var newSyntaxRoot = syntaxRoot.ReplaceNode(oldNode, newNode);
92+
result = document.WithSyntaxRoot(newSyntaxRoot);
93+
}
8094
}
8195
}
8296

src/Menees.Analyzers/Men018UseDigitSeparators.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ private static void HandleNumericLiteral(SyntaxNodeAnalysisContext context)
8686
builder.Add(PreferredKey, preferredText);
8787
ImmutableDictionary<string, string?> fixerProperties = builder.ToImmutable();
8888

89-
Location location = literalExpression.GetFirstLineLocation();
89+
Location location = literalExpression.GetLocation();
9090
context.ReportDiagnostic(Diagnostic.Create(Rule, location, fixerProperties, literalText));
9191
}
9292
}

src/Menees.Analyzers/NumericLiteral.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public sealed class NumericLiteral
2424
private static readonly HashSet<char> HexadecimalDigits = [.. DecimalDigits, 'A', 'B', 'C', 'D', 'E', 'F', 'a', 'b', 'c', 'd', 'e', 'f'];
2525
private static readonly char[] ExponentChar = ['e', 'E'];
2626

27-
private string originalText;
27+
private readonly string originalText;
2828

2929
#endregion
3030

tests/Menees.Analyzers.Test/Men018UnitTests.cs

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
namespace Menees.Analyzers.Test;
22

3+
using Microsoft.CodeAnalysis.CodeFixes;
4+
35
[TestClass]
46
public class Men018UnitTests : CodeFixVerifier
57
{
68
#region Protected Properties
79

810
protected override DiagnosticAnalyzer CSharpDiagnosticAnalyzer => new Men018UseDigitSeparators();
911

12+
protected override CodeFixProvider? CSharpCodeFixProvider => new Men018UseDigitSeparatorsFixer();
13+
1014
#endregion
1115

1216
#region ValidCodeTest
@@ -52,7 +56,7 @@ public class Test
5256
private const int Thousand = 1000;
5357
private const uint HexHundred = 0x100u;
5458
private const ulong BinaryMillion = 0b1000000UL;
55-
private const double BigDouble = 123456.789;
59+
private const double BigDouble = 123456.7890;
5660
}";
5761

5862
var analyzer = this.CSharpDiagnosticAnalyzer;
@@ -90,14 +94,53 @@ public class Test
9094
},
9195
new DiagnosticResult(analyzer)
9296
{
93-
Message = "The numeric literal 123456.789 should use digit separators.",
97+
Message = "The numeric literal 123456.7890 should use digit separators.",
9498
Locations = [new DiagnosticResultLocation("Test0.cs", 10, 35)]
9599
},
96100
];
97101

98-
// TODO: Add code fixer unit tests. [Bill, 5/26/2024]
99102
this.VerifyCSharpDiagnostic(test, expected);
100103
}
101104

105+
[TestMethod]
106+
public void InvalidCodeFixerTest()
107+
{
108+
const string before = @"
109+
public class Test
110+
{
111+
private const int Million = 1000000;
112+
private const uint MaxUint = 0xFFFFFFFFu;
113+
private const decimal TenBillion = 10000000000m;
114+
private const int Thousand = 1000;
115+
private const uint HexHundred = 0x100u;
116+
private const ulong BinaryMillion = 0b1000000UL;
117+
private const double BigDouble = 123456.7890;
118+
private static readonly double Conditional = Convert.ToBoolean(0) ? 0xFFFFFF : 1234.56789;
119+
public Test()
120+
{
121+
if (DateTime.Now.TimeOfDay.TotalSeconds >= 43200) Console.WriteLine(""Afternoon"");
122+
}
123+
}";
124+
125+
const string after = @"
126+
public class Test
127+
{
128+
private const int Million = 1_000_000;
129+
private const uint MaxUint = 0x_FF_FF_FF_FFu;
130+
private const decimal TenBillion = 10_000_000_000m;
131+
private const int Thousand = 1_000;
132+
private const uint HexHundred = 0x1_00u;
133+
private const ulong BinaryMillion = 0b100_0000UL;
134+
private const double BigDouble = 123_456.789_0;
135+
private static readonly double Conditional = Convert.ToBoolean(0) ? 0x_FF_FF_FF : 1_234.567_89;
136+
public Test()
137+
{
138+
if (DateTime.Now.TimeOfDay.TotalSeconds >= 43_200) Console.WriteLine(""Afternoon"");
139+
}
140+
}";
141+
142+
this.VerifyCSharpFix(before, after);
143+
}
144+
102145
#endregion
103146
}

tests/Menees.Analyzers.Test/Verifiers/CodeFixVerifier.cs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,13 @@ protected void VerifyCSharpFix(string oldSource, string newSource, int? codeFixI
4242
/// <param name="allowNewCompilerDiagnostics">A bool controlling whether or not the test will fail if the CodeFix introduces other warnings after being applied</param>
4343
private static void VerifyFix(string language, DiagnosticAnalyzer analyzer, CodeFixProvider? codeFixProvider, string oldSource, string newSource, int? codeFixIndex, bool allowNewCompilerDiagnostics)
4444
{
45-
if (IsEnabled(analyzer) && codeFixProvider != null)
45+
if (IsEnabled(analyzer))
4646
{
47+
if (codeFixProvider is null)
48+
{
49+
throw new ArgumentNullException(nameof(codeFixProvider), $"{nameof(VerifyFix)} requires a code fix provider.");
50+
}
51+
4752
var document = CreateDocument(oldSource, language);
4853
var analyzerDiagnostics = GetSortedDiagnosticsFromDocuments(analyzer, [document]);
4954
var compilerDiagnostics = GetCompilerDiagnostics(document);
@@ -83,10 +88,10 @@ private static void VerifyFix(string language, DiagnosticAnalyzer analyzer, Code
8388

8489
newCompilerDiagnostics = GetNewDiagnostics(compilerDiagnostics, GetCompilerDiagnostics(document));
8590

86-
Assert.IsTrue(false,
91+
Assert.Fail(
8792
string.Format("Fix introduced new compiler diagnostics:\r\n{0}\r\n\r\nNew document:\r\n{1}\r\n",
8893
string.Join("\r\n", newCompilerDiagnostics.Select(d => d.ToString())),
89-
document.GetSyntaxRootAsync().Result.ToFullString()));
94+
document.GetSyntaxRootAsync().Result?.ToFullString()));
9095
}
9196

9297
//check if there are analyzer diagnostics left after the code fix

0 commit comments

Comments
 (0)