Skip to content

Commit 29b0438

Browse files
committed
fix: 正确处理条件访问与字符串内的换行
1 parent 0bd9aa3 commit 29b0438

File tree

6 files changed

+301
-33
lines changed

6 files changed

+301
-33
lines changed

Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
</PropertyGroup>
1111

1212
<PropertyGroup>
13-
<Version>1.0.0</Version>
13+
<Version>1.0.1</Version>
1414

1515
<Description>Code fix provider for `C#` code log diagnosis `CA1727`, `CA2253`, `CA2254`. 针对 `C#` 代码日志诊断 `CA1727`, `CA2253`, `CA2254` 的代码修复提供程序</Description>
1616

src/Cuture.CodeAnalysis.LoggingCodeFixes/LoggingInvocationFixer.cs

Lines changed: 14 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@
44
using Microsoft.CodeAnalysis.CSharp;
55
using Microsoft.CodeAnalysis.CSharp.Syntax;
66

7+
using static Cuture.CodeAnalysis.LoggingCodeFixes.PlaceHolderNormalizer;
8+
79
namespace Cuture.CodeAnalysis.LoggingCodeFixes;
810

911
public static class LoggingInvocationFixer
1012
{
1113
#region Private 字段
1214

13-
private static readonly Regex s_ignoreRegex = new("\\.|\\(.*?\\)|\\[.*?\\]|this\\.|This\\.", RegexOptions.Compiled);
14-
1515
private static readonly Regex s_numericPlaceHolderRegex = new("\\{[\\d ]+\\}", RegexOptions.Compiled);
1616

1717
private static readonly Regex s_placeHolderRegex = new("(?<=\\{)(.+?)(?=\\})", RegexOptions.Compiled);
@@ -93,7 +93,7 @@ public static async Task<Document> FixNumericPlaceHolderAsync(Document document,
9393

9494
var arguments = invocationExpressionSyntax.ArgumentList.Arguments.ToList();
9595
arguments = arguments.SkipWhile(m => !ReferenceEquals(stringArgument, m)).Skip(1).ToList();
96-
var text = syntaxToken.ValueText;
96+
var text = syntaxToken.Text.Trim('"');
9797

9898
var index = 0;
9999

@@ -113,7 +113,7 @@ public static async Task<Document> FixNumericPlaceHolderAsync(Document document,
113113
break;
114114

115115
case MemberAccessExpressionSyntax invocationMemberAccessExpressionSyntax:
116-
result = CreatePlaceHolderName(invocationMemberAccessExpressionSyntax.ToString()) ?? match.Value;
116+
result = Normalize(invocationMemberAccessExpressionSyntax.ToString()) ?? match.Value;
117117
break;
118118
}
119119
break;
@@ -123,11 +123,15 @@ public static async Task<Document> FixNumericPlaceHolderAsync(Document document,
123123
break;
124124

125125
case LiteralExpressionSyntax literalExpressionSyntax:
126-
result = CreatePlaceHolderName(semanticModel.GetTypeInfo(literalExpressionSyntax).Type.Name) ?? match.Value;
126+
result = Normalize(semanticModel.GetTypeInfo(literalExpressionSyntax).Type.Name) ?? match.Value;
127127
break;
128128

129129
case MemberAccessExpressionSyntax memberAccessExpressionSyntax:
130-
result = CreatePlaceHolderName(memberAccessExpressionSyntax.ToString()) ?? match.Value;
130+
result = Normalize(memberAccessExpressionSyntax.ToString()) ?? match.Value;
131+
break;
132+
133+
case ConditionalAccessExpressionSyntax conditionalAccessExpressionSyntax:
134+
result = Normalize(conditionalAccessExpressionSyntax.ToString()) ?? match.Value;
131135
break;
132136
}
133137
return $"{{{result}}}";
@@ -154,7 +158,7 @@ public static async Task<Document> FixPascalCasePlaceHolderAsync(Document docume
154158

155159
text = s_placeHolderRegex.Replace(text, match =>
156160
{
157-
return CreatePlaceHolderName(match.Value);
161+
return Normalize(match.Value);
158162
});
159163

160164
var newExpressionSyntax = CreateLiteralExpressionSyntax($"\"{text}\"");
@@ -175,27 +179,7 @@ public static async Task<Document> FixPascalCasePlaceHolderAsync(Document docume
175179

176180
private static InterpolatedStringTextSyntax CreateHolderInterpolatedStringTextSyntax(string valueText)
177181
{
178-
return CreateInterpolatedStringTextSyntax($"{{{{{CreatePlaceHolderName(valueText)}}}}}");
179-
}
180-
181-
private static string CreatePlaceHolderName(string valueText)
182-
{
183-
var valueSpan = valueText.AsSpan()
184-
.Trim()
185-
.TrimStart('{')
186-
.TrimStart('@')
187-
.TrimStart('_')
188-
.TrimEnd('}')
189-
.Trim();
190-
191-
var index = 0;
192-
Span<char> newSpan = new char[valueSpan.Length + 4];
193-
var firstChar = valueSpan[0];
194-
newSpan[index++] = char.IsLetter(firstChar) ? char.ToUpper(firstChar) : firstChar;
195-
valueSpan.Slice(1).CopyTo(newSpan.Slice(index));
196-
index += valueSpan.Length - 1;
197-
198-
return s_ignoreRegex.Replace(newSpan.Slice(0, index).ToString(), "");
182+
return CreateInterpolatedStringTextSyntax($"{{{{{Normalize(valueText)}}}}}");
199183
}
200184

201185
private static IEnumerable<InterpolatedStringContentSyntaxDescriptor> EnumerateProcessedInterpolatedStringContentSyntaxes(IEnumerable<InterpolatedStringContentSyntax> contentSyntaxes)
@@ -269,12 +253,12 @@ private static IEnumerable<InterpolatedStringContentSyntaxDescriptor> EnumerateP
269253
if (identifierNameSyntax.Parent is InvocationExpressionSyntax invocationExpressionSyntax)
270254
{
271255
var text = invocationExpressionSyntax.ArgumentList.Arguments[0].Expression.ToString();
272-
return CreatePlaceHolderName(text);
256+
return Normalize(text);
273257
}
274258
}
275259
else
276260
{
277-
return CreatePlaceHolderName(identifierNameSyntax.Identifier.ValueText);
261+
return Normalize(identifierNameSyntax.Identifier.ValueText);
278262
}
279263
return null;
280264
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
using System.Buffers;
2+
using System.Text.RegularExpressions;
3+
4+
namespace Cuture.CodeAnalysis.LoggingCodeFixes;
5+
6+
/// <summary>
7+
/// 占位符规范化器
8+
/// </summary>
9+
public class PlaceHolderNormalizer
10+
{
11+
#region Private 字段
12+
13+
private static readonly Regex s_normalizeRegex = new("\\?|\\.|\\(.*?\\)|\\[.*?\\]|this\\.|This\\.|@", RegexOptions.Compiled);
14+
15+
private static readonly char[] s_shouldUpperFollowChars = ['.', '@'];
16+
17+
private static ReadOnlySpan<char> ShouldUpperFollowChars => s_shouldUpperFollowChars;
18+
19+
#endregion Private 字段
20+
21+
#region Public 方法
22+
23+
/// <summary>
24+
/// 创建规范化的占位符
25+
/// </summary>
26+
/// <param name="input"></param>
27+
/// <returns></returns>
28+
public static string Normalize(string input) => Normalize(input.AsSpan());
29+
30+
/// <summary>
31+
/// 创建规范化的占位符
32+
/// </summary>
33+
/// <param name="input"></param>
34+
/// <returns></returns>
35+
public static string Normalize(ReadOnlySpan<char> input)
36+
{
37+
input = input.Trim()
38+
.TrimStart('{')
39+
.TrimStart('@')
40+
.TrimStart('_')
41+
.TrimEnd('}')
42+
.Trim();
43+
44+
var bufferLength = 0;
45+
var buffer = ArrayPool<char>.Shared.Rent(input.Length + 4);
46+
try
47+
{
48+
Span<char> newSpan = buffer;
49+
var firstChar = input[0];
50+
newSpan[bufferLength++] = char.ToUpper(firstChar);
51+
input.Slice(1).CopyTo(newSpan.Slice(bufferLength));
52+
bufferLength += input.Length - 1;
53+
54+
var index = 0;
55+
while (newSpan.Slice(index).IndexOfAny(ShouldUpperFollowChars) is { } nextIndex
56+
&& nextIndex >= 0)
57+
{
58+
nextIndex = nextIndex + index + 1;
59+
if (nextIndex >= newSpan.Length)
60+
{
61+
break;
62+
}
63+
64+
newSpan[nextIndex] = char.ToUpper(newSpan[nextIndex]);
65+
index = nextIndex;
66+
}
67+
68+
return s_normalizeRegex.Replace(newSpan.Slice(0, bufferLength).ToString(), "");
69+
}
70+
finally
71+
{
72+
ArrayPool<char>.Shared.Return(buffer);
73+
}
74+
}
75+
76+
#endregion Public 方法
77+
}

test/Cuture.CodeAnalysis.LoggingCodeFixes.Test/NumericPlaceHolderFixTest.cs

Lines changed: 154 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,158 @@ public class NumericPlaceHolderFixTest
1010
{
1111
#region Public 方法
1212

13+
[TestMethod]
14+
public async Task Should_BeginLine_NewLine_Retain()
15+
{
16+
LoggingCodeTemplate test =
17+
@"
18+
var type = _logger.GetType();
19+
_logger.LogInformation({|#0:""\r\nValue: {1}""|}, type?.Name);
20+
";
21+
22+
LoggingCodeTemplate fixtest =
23+
@"
24+
var type = _logger.GetType();
25+
_logger.LogInformation(""\r\nValue: {TypeName}"", type?.Name);
26+
";
27+
28+
var expected = GetExpected();
29+
await VerifyCS.VerifyCodeFixAsync(test, expected, fixtest);
30+
}
31+
32+
[TestMethod]
33+
public async Task Should_ConditionalAccess_Replace_With_Memeber_Name()
34+
{
35+
LoggingCodeTemplate test =
36+
"""
37+
var type = _logger.GetType();
38+
_logger.LogInformation({|#0:"Value: {1}"|}, type?.Name);
39+
""";
40+
41+
LoggingCodeTemplate fixtest =
42+
"""
43+
var type = _logger.GetType();
44+
_logger.LogInformation("Value: {TypeName}", type?.Name);
45+
""";
46+
47+
var expected = GetExpected();
48+
await VerifyCS.VerifyCodeFixAsync(test, expected, fixtest);
49+
}
50+
51+
[TestMethod]
52+
public async Task Should_ConditionalAccess_Replace_With_Memeber_Name_ML()
53+
{
54+
LoggingCodeTemplate test =
55+
"""
56+
var type = _logger.GetType();
57+
_logger.LogInformation({|#0:"Value: {1}"|}, type?.Assembly?.EntryPoint?.Name);
58+
""";
59+
60+
LoggingCodeTemplate fixtest =
61+
"""
62+
var type = _logger.GetType();
63+
_logger.LogInformation("Value: {TypeAssemblyEntryPointName}", type?.Assembly?.EntryPoint?.Name);
64+
""";
65+
66+
var expected = GetExpected();
67+
await VerifyCS.VerifyCodeFixAsync(test, expected, fixtest);
68+
}
69+
70+
[TestMethod]
71+
public async Task Should_ConditionalAccess_Replace_With_Method_Name()
72+
{
73+
LoggingCodeTemplate test =
74+
"""
75+
var ex = new Exception();
76+
_logger.LogInformation({|#0:"Value: {1}"|}, ex?.GetType());
77+
""";
78+
79+
LoggingCodeTemplate fixtest =
80+
"""
81+
var ex = new Exception();
82+
_logger.LogInformation("Value: {ExGetType}", ex?.GetType());
83+
""";
84+
85+
var expected = GetExpected();
86+
await VerifyCS.VerifyCodeFixAsync(test, expected, fixtest);
87+
}
88+
89+
[TestMethod]
90+
public async Task Should_ConditionalAccess_Replace_With_Method_Name_ML()
91+
{
92+
LoggingCodeTemplate test =
93+
"""
94+
var ex = new Exception();
95+
_logger.LogInformation({|#0:"Value: {1}"|}, ex?.GetType()?.GetProperties()?.GetHashCode());
96+
""";
97+
98+
LoggingCodeTemplate fixtest =
99+
"""
100+
var ex = new Exception();
101+
_logger.LogInformation("Value: {ExGetTypeGetPropertiesGetHashCode}", ex?.GetType()?.GetProperties()?.GetHashCode());
102+
""";
103+
104+
var expected = GetExpected();
105+
await VerifyCS.VerifyCodeFixAsync(test, expected, fixtest);
106+
}
107+
108+
[TestMethod]
109+
public async Task Should_EndLine_NewLine_Retain()
110+
{
111+
LoggingCodeTemplate test =
112+
@"
113+
var type = _logger.GetType();
114+
_logger.LogInformation({|#0:""Value: {1}\r\n""|}, type?.Name);
115+
";
116+
117+
LoggingCodeTemplate fixtest =
118+
@"
119+
var type = _logger.GetType();
120+
_logger.LogInformation(""Value: {TypeName}\r\n"", type?.Name);
121+
";
122+
123+
var expected = GetExpected();
124+
await VerifyCS.VerifyCodeFixAsync(test, expected, fixtest);
125+
}
126+
127+
[TestMethod]
128+
public async Task Should_MidLine_NewLine_Retain()
129+
{
130+
LoggingCodeTemplate test =
131+
@"
132+
var type = _logger.GetType();
133+
_logger.LogInformation({|#0:""Value: {1}\r\n{2}""|}, type?.Name, type);
134+
";
135+
136+
LoggingCodeTemplate fixtest =
137+
@"
138+
var type = _logger.GetType();
139+
_logger.LogInformation(""Value: {TypeName}\r\n{Type}"", type?.Name, type);
140+
";
141+
142+
var expected = GetExpected();
143+
await VerifyCS.VerifyCodeFixAsync(test, [expected, expected], fixtest);
144+
}
145+
146+
[TestMethod]
147+
public async Task Should_Multi_NewLine_Retain()
148+
{
149+
LoggingCodeTemplate test =
150+
@"
151+
var type = _logger.GetType();
152+
_logger.LogInformation({|#0:""Value\r\n: {1}\r\n{2}\r\n""|}, type?.Name, type);
153+
";
154+
155+
LoggingCodeTemplate fixtest =
156+
@"
157+
var type = _logger.GetType();
158+
_logger.LogInformation(""Value\r\n: {TypeName}\r\n{Type}\r\n"", type?.Name, type);
159+
";
160+
161+
var expected = GetExpected();
162+
await VerifyCS.VerifyCodeFixAsync(test, [expected, expected], fixtest);
163+
}
164+
13165
[TestMethod]
14166
public async Task Should_Replace_With_Memeber_Name()
15167
{
@@ -131,14 +283,14 @@ public async Task Should_Success()
131283
"""
132284
var type = _logger.GetType();
133285
var ex = new Exception();
134-
_logger.LogInformation({|#0:"Value: {1} {2} {3} {4}"|}, nameof(ex), type.Assembly.EntryPoint.Name, ex.GetType().GetProperties().GetHashCode(), type);
286+
_logger.LogInformation({|#0:"Value: {1} {2} {3} {4}"|}, nameof(ex), type.Assembly?.EntryPoint.Name, ex.GetType().GetProperties()?.GetHashCode(), type);
135287
""";
136288

137289
LoggingCodeTemplate fixtest =
138290
"""
139291
var type = _logger.GetType();
140292
var ex = new Exception();
141-
_logger.LogInformation("Value: {Ex} {TypeAssemblyEntryPointName} {ExGetTypeGetPropertiesGetHashCode} {Type}", nameof(ex), type.Assembly.EntryPoint.Name, ex.GetType().GetProperties().GetHashCode(), type);
293+
_logger.LogInformation("Value: {Ex} {TypeAssemblyEntryPointName} {ExGetTypeGetPropertiesGetHashCode} {Type}", nameof(ex), type.Assembly?.EntryPoint.Name, ex.GetType().GetProperties()?.GetHashCode(), type);
142294
""";
143295

144296
var expected = GetExpected();

test/Cuture.CodeAnalysis.LoggingCodeFixes.Test/PascalCasePlaceHolderFixTest.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,28 @@ public async Task Should_Success()
3232
await VerifyCS.VerifyCodeFixAsync(test, [expected, expected, expected, expected], fixtest);
3333
}
3434

35+
[TestMethod]
36+
public async Task Should_Success_With_ConditionalAccess()
37+
{
38+
LoggingCodeTemplate test =
39+
"""
40+
var type = _logger.GetType();
41+
var ex = new Exception();
42+
_logger.LogInformation({|#0:"Value: {ex} {typeAssemblyEntryPointName} {exGetTypeGetPropertiesGetHashCode} {type}"|}, nameof(ex), type?.Assembly?.EntryPoint?.Name, ex?.GetType()?.GetProperties()?.GetHashCode(), type);
43+
""";
44+
45+
LoggingCodeTemplate fixtest =
46+
"""
47+
var type = _logger.GetType();
48+
var ex = new Exception();
49+
_logger.LogInformation("Value: {Ex} {TypeAssemblyEntryPointName} {ExGetTypeGetPropertiesGetHashCode} {Type}", nameof(ex), type?.Assembly?.EntryPoint?.Name, ex?.GetType()?.GetProperties()?.GetHashCode(), type);
50+
""";
51+
52+
var expected = GetExpected();
53+
54+
await VerifyCS.VerifyCodeFixAsync(test, [expected, expected, expected, expected], fixtest);
55+
}
56+
3557
#endregion Public 方法
3658

3759
#region Private 方法

0 commit comments

Comments
 (0)