Skip to content

Commit 8ed3c90

Browse files
Fix S2479 FN: Support utf-8 strings, ignore raw and verbatim strings (#6345)
* Fix S2479 FN for utf-8 and utf-8+raw string literals * Changed S2479 FN comment to Compliant for interpolated strings * Remove some useless lines fro a testcase * Empty: Trigger workflows * S2479 FN Fix: Apply PR Review changes * Empty: Trigger workflows * PR Review: Fixed some UTs * PR Review: Fixed some UTs * PR Review: Apply changes * Fix ITs for S2479 FN and also add Net7 on update-expected.ps1
1 parent 29f88fb commit 8ed3c90

4 files changed

Lines changed: 54 additions & 53 deletions

File tree

analyzers/its/expected/Net7/Net7--net7.0-S2479.json

Lines changed: 0 additions & 17 deletions
This file was deleted.

analyzers/its/update-expected.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
param
33
(
44
[Parameter(HelpMessage = "The name of single project to update issues for. If ommited, issues for all projects will be updated.")]
5-
[ValidateSet("AnalyzeGenerated", "AnalyzeGeneratedVb", "akka.net", "Automapper", "Ember-MM", "Nancy", "NetCore31", "Net5", "Net6", "NetCore31WithConfigurableRules", "ManuallyAddedNoncompliantIssues", "ManuallyAddedNoncompliantIssuesVB", "SkipGenerated", "SkipGeneratedVb", "WebConfig")]
5+
[ValidateSet("AnalyzeGenerated", "AnalyzeGeneratedVb", "akka.net", "Automapper", "Ember-MM", "Nancy", "NetCore31", "Net5", "Net6", "Net7", "NetCore31WithConfigurableRules", "ManuallyAddedNoncompliantIssues", "ManuallyAddedNoncompliantIssuesVB", "SkipGenerated", "SkipGeneratedVb", "WebConfig")]
66
[string]
77
$project
88
)

analyzers/src/SonarAnalyzer.CSharp/Rules/ControlCharacterInString.cs

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,15 @@ namespace SonarAnalyzer.Rules.CSharp
3131
[DiagnosticAnalyzer(LanguageNames.CSharp)]
3232
public sealed class ControlCharacterInString : SonarDiagnosticAnalyzer
3333
{
34-
internal const string DiagnosticId = "S2479";
34+
private const string DiagnosticId = "S2479";
3535
private const string MessageFormat = "Replace the control character at position {0} by its escape sequence '{1}'.";
3636

37-
private static readonly DiagnosticDescriptor rule =
37+
private static readonly DiagnosticDescriptor Rule =
3838
DescriptorFactory.Create(DiagnosticId, MessageFormat);
3939

40-
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(rule);
40+
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule);
4141

42-
private static readonly IDictionary<char, string> EscapedControlCharacters = new Dictionary<char, string>()
42+
private static readonly IDictionary<char, string> EscapedControlCharacters = new Dictionary<char, string>
4343
{
4444
{'\u0000', "\\0"},
4545
{'\u0001', "\\u0001"},
@@ -102,7 +102,8 @@ protected override void Initialize(SonarAnalysisContext context)
102102
{
103103
context.RegisterSyntaxNodeActionInNonGenerated(
104104
c => CheckControlCharacter(c, ((LiteralExpressionSyntax)c.Node).Token.Text, 0),
105-
SyntaxKind.StringLiteralExpression);
105+
SyntaxKind.StringLiteralExpression,
106+
SyntaxKindEx.Utf8StringLiteralExpression);
106107

107108
context.RegisterSyntaxNodeActionInNonGenerated(
108109
c => CheckControlCharacter(c, ((InterpolatedStringTextSyntax)c.Node).TextToken.Text, 1),
@@ -111,7 +112,7 @@ protected override void Initialize(SonarAnalysisContext context)
111112

112113
private static void CheckControlCharacter(SyntaxNodeAnalysisContext c, string text, int displayPosIncrement)
113114
{
114-
if (IsSimpleVerbatimString(c.Node) || IsInterpolatedVerbatimString(c.Node.Parent))
115+
if (IsInescapableString(c.Node) || IsInescepableInterpolatedString(c.Node.Parent) || IsInescapableUtf8String(c.Node))
115116
{
116117
return;
117118
}
@@ -120,17 +121,24 @@ private static void CheckControlCharacter(SyntaxNodeAnalysisContext c, string te
120121
{
121122
if (EscapedControlCharacters.TryGetValue(text[charPos], out var escapeSequence))
122123
{
123-
c.ReportIssue(Diagnostic.Create(rule, c.Node.GetLocation(), displayPosIncrement + charPos,
124-
escapeSequence));
124+
c.ReportIssue(Diagnostic.Create(Rule, c.Node.GetLocation(), displayPosIncrement + charPos, escapeSequence));
125125
return;
126126
}
127127
}
128128
}
129129

130-
private static bool IsSimpleVerbatimString(SyntaxNode syntaxNode) =>
131-
syntaxNode.GetFirstToken().IsVerbatimStringLiteral();
130+
private static bool IsInescapableString(SyntaxNode syntaxNode) =>
131+
syntaxNode.GetFirstToken() is var token
132+
&& (token.IsVerbatimStringLiteral()
133+
|| token.IsAnyKind(SyntaxKindEx.SingleLineRawStringLiteralToken, SyntaxKindEx.MultiLineRawStringLiteralToken));
132134

133-
private static bool IsInterpolatedVerbatimString(SyntaxNode syntaxNode) =>
134-
syntaxNode.GetFirstToken().IsKind(SyntaxKind.InterpolatedVerbatimStringStartToken);
135+
private static bool IsInescepableInterpolatedString(SyntaxNode syntaxNode) =>
136+
syntaxNode.GetFirstToken().IsAnyKind(
137+
SyntaxKind.InterpolatedVerbatimStringStartToken,
138+
SyntaxKindEx.InterpolatedSingleLineRawStringStartToken,
139+
SyntaxKindEx.InterpolatedMultiLineRawStringStartToken);
140+
141+
private static bool IsInescapableUtf8String(SyntaxNode syntaxNode) =>
142+
syntaxNode.GetFirstToken().IsAnyKind(SyntaxKindEx.Utf8SingleLineRawStringLiteralToken, SyntaxKindEx.Utf8MultiLineRawStringLiteralToken);
135143
}
136144
}

analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ControlCharacterInString.CSharp11.cs

Lines changed: 33 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,36 +4,46 @@ namespace Tests.Diagnostics
44
{
55
class Program
66
{
7-
public const string RawStringLiteralsNonCompliant = """test"""; // Noncompliant
8-
public const string RawStringLiteralsCompliant = """test""";
7+
public const string RawCompliant = """test"""; // Compliant
8+
public const string RawCompliantWithSpecialCharacter = """test"""; // Compliant, raw string
99

10-
public const string InterpolatedStringNonCompliant = $"""test{RawStringLiteralsNonCompliant}"""; // FN
11-
public const string InterpolatedStringNonCompliant2 = $"""test{RawStringLiteralsCompliant}"""; // Noncompliant
12-
public const string InterpolatedStringCompliant = $"""test{RawStringLiteralsCompliant}""";
10+
public const string RawCompliantWithInterpolation = $"""test{RawCompliantWithSpecialCharacter}"""; // Compliant, raw string
11+
public const string RawCompliantWithInterpolationAndSpecialCharacter = $"""test{RawCompliant}"""; // Compliant, raw string
1312

1413
void Utf8StringLiterals()
1514
{
16-
ReadOnlySpan<byte> Utf8Compliant = "test"u8;
17-
ReadOnlySpan<byte> Utf8Compliant2 = """test"""u8;
18-
ReadOnlySpan<byte> Utf8NonCompliant = "test"u8; // FN
19-
ReadOnlySpan<byte> Uft8NonCompliant2 = """test"""u8; // FN
15+
ReadOnlySpan<byte> Utf8Compliant = "test"u8; // Compliant
16+
ReadOnlySpan<byte> Utf8Noncompliant = "test"u8; // Noncompliant
17+
ReadOnlySpan<byte> Utf8VerbatimCompliant = @"test"u8; // Compliant, verbatim utf-8 string
18+
19+
ReadOnlySpan<byte> Utf8CompliantRaw = """test"""u8; // Compliant, raw string
20+
ReadOnlySpan<byte> Utf8CompliantRawWithSpecialCharacter = """test"""u8; // Compliant, raw string
2021
}
2122

2223
void NewlinesInStringInterpolation()
23-
{
24-
string NewlinesInterpolatedStringNonCompliant = $"test{RawStringLiteralsNonCompliant +
25-
RawStringLiteralsNonCompliant}"; // FN
26-
string NewlinesInterpolatedStringNonCompliant2 = $"test{RawStringLiteralsCompliant +
27-
RawStringLiteralsCompliant}"; // Noncompliant@-1
28-
string NewlinesInterpolatedStringCompliant = $"test{RawStringLiteralsCompliant +
29-
RawStringLiteralsCompliant}";
30-
31-
string NewlinesInterpolatedStringRawNonCompliant = $$"""test{{RawStringLiteralsNonCompliant +
32-
RawStringLiteralsNonCompliant}}"""; // FN
33-
string NewlinesInterpolatedStringRawNonCompliant2 = $$"""test{{RawStringLiteralsCompliant +
34-
RawStringLiteralsCompliant}}"""; // Noncompliant@-1
35-
string NewlinesInterpolatedStringRawCompliant = $$"""test{{RawStringLiteralsCompliant +
36-
RawStringLiteralsCompliant}}""";
24+
{
25+
var compliant = "test"; // Compliant
26+
var nonCompliant = "test"; // Noncompliant
27+
28+
var baseCase = $"test{
29+
compliant
30+
}"; // Compliant
31+
32+
var interpolatedTextHasControlCharacter = $"test{
33+
nonCompliant
34+
}"; // Compliant, interpolated text ignored
35+
36+
var normalTextHasControlCharacter = $"test{
37+
nonCompliant
38+
}"; // Noncompliant@-2
39+
40+
var verbatimWithControlCharacter = @$"test{
41+
nonCompliant
42+
}"; // Compliant, verbatim
43+
44+
var rawWithControlCharacter = $"""test{
45+
nonCompliant
46+
}"""; // Compliant, raw
3747
}
3848
}
3949
}

0 commit comments

Comments
 (0)