Skip to content

Commit 8520778

Browse files
Add utility analyzer's UTs for C#12 syntax (#8135)
1 parent 6360ba4 commit 8520778

File tree

9 files changed

+202
-12
lines changed

9 files changed

+202
-12
lines changed

analyzers/tests/SonarAnalyzer.UnitTest/Rules/Utilities/CopyPasteTokenAnalyzerTest.cs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,16 @@ public void Verify_Unique_CSharp11() =>
5555
info.Count(x => x.TokenValue == "$char").Should().Be(2);
5656
});
5757

58+
[TestMethod]
59+
public void Verify_Unique_CSharp12() =>
60+
Verify("Unique.Csharp12.cs", info =>
61+
{
62+
info.Should().HaveCount(81);
63+
info.Count(x => x.TokenValue == "$str").Should().Be(4);
64+
info.Count(x => x.TokenValue == "$num").Should().Be(4);
65+
info.Count(x => x.TokenValue == "$char").Should().Be(4);
66+
});
67+
5868
#endif
5969

6070
[TestMethod]
@@ -81,7 +91,7 @@ public void Verify_Duplicated_CS_GlobalUsings() =>
8191
.WithAdditionalFilePath(AnalysisScaffolding.CreateSonarProjectConfig(TestContext, ProjectType.Product))
8292
.VerifyUtilityAnalyzer<CopyPasteTokenInfo>(messages =>
8393
{
84-
messages.Should().HaveCount(1);
94+
messages.Should().ContainSingle();
8595
var info = messages.Single();
8696
info.FilePath.Should().Be(Path.Combine(BasePath, "Duplicated.CSharp10.cs"));
8797
info.TokenInfo.Should().HaveCount(39);
@@ -113,7 +123,7 @@ private void Verify(string fileName, Action<IReadOnlyList<CopyPasteTokenInfo.Typ
113123
.WithAdditionalFilePath(AnalysisScaffolding.CreateSonarProjectConfig(TestContext, ProjectType.Product))
114124
.VerifyUtilityAnalyzer<CopyPasteTokenInfo>(messages =>
115125
{
116-
messages.Should().HaveCount(1);
126+
messages.Should().ContainSingle();
117127
var info = messages.Single();
118128
info.FilePath.Should().Be(Path.Combine(BasePath, fileName));
119129
verifyTokenInfo(info.TokenInfo);

analyzers/tests/SonarAnalyzer.UnitTest/Rules/Utilities/MetricsAnalyzerTest.cs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
using System.IO;
2222
using SonarAnalyzer.Protobuf;
2323
using SonarAnalyzer.Rules.CSharp;
24-
using SonarAnalyzer.UnitTest.Helpers;
2524

2625
namespace SonarAnalyzer.UnitTest.Rules
2726
{
@@ -32,6 +31,7 @@ public class MetricsAnalyzerTest
3231
private const string AllMetricsFileName = "AllMetrics.cs";
3332
private const string RazorFileName = "Razor.razor";
3433
private const string CsHtmlFileName = "Razor.cshtml";
34+
private const string CSharp12FileName = "Metrics.CSharp12.cs";
3535

3636
public TestContext TestContext { get; set; }
3737

@@ -87,6 +87,25 @@ public void VerifyMetrics_CsHtml() =>
8787
// There should be no metrics messages for the cshtml files.
8888
messages.Select(x => Path.GetFileName(x.FilePath)).Should().BeEquivalentTo("_Imports.razor"));
8989

90+
[TestMethod]
91+
public void VerifyMetrics_CSharp12() =>
92+
CreateBuilder(false, CSharp12FileName)
93+
.WithAdditionalFilePath(AnalysisScaffolding.CreateSonarProjectConfig(TestContext, ProjectType.Product))
94+
.VerifyUtilityAnalyzer<MetricsInfo>(messages =>
95+
{
96+
messages.Should().ContainSingle();
97+
var metrics = messages.Single();
98+
metrics.ClassCount.Should().Be(1); // no changes
99+
metrics.CodeLine.Should().HaveCount(13);
100+
metrics.CognitiveComplexity.Should().Be(1); // no changes
101+
metrics.Complexity.Should().Be(3); // no changes
102+
metrics.ExecutableLines.Should().HaveCount(3); // 5, 7, 9
103+
metrics.FunctionCount.Should().Be(2); // no changes
104+
metrics.NoSonarComment.Should().BeEmpty();
105+
metrics.NonBlankComment.Should().ContainSingle();
106+
metrics.StatementCount.Should().Be(3);
107+
});
108+
90109
#endif
91110

92111
[TestMethod]

analyzers/tests/SonarAnalyzer.UnitTest/Rules/Utilities/SymbolReferenceAnalyzerTest.cs

Lines changed: 62 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@
1919
*/
2020

2121
using System.IO;
22+
using Google.Protobuf.Collections;
2223
using SonarAnalyzer.Protobuf;
2324
using SonarAnalyzer.Rules;
24-
using SonarAnalyzer.UnitTest.Helpers;
2525
using CS = SonarAnalyzer.Rules.CSharp;
2626
using VB = SonarAnalyzer.Rules.VisualBasic;
2727

@@ -56,9 +56,12 @@ public void Verify_Method_PreciseLocation_VB(ProjectType projectType) =>
5656

5757
var procedureDeclaration = references.Single(x => x.Declaration.StartLine == 3);
5858
procedureDeclaration.Declaration.Should().BeEquivalentTo(new TextRange { StartLine = 3, EndLine = 3, StartOffset = 15, EndOffset = 21 });
59-
procedureDeclaration.Reference.Should().Equal(
60-
new TextRange { StartLine = 11, EndLine = 11, StartOffset = 8, EndOffset = 14 },
61-
new TextRange { StartLine = 13, EndLine = 13, StartOffset = 8, EndOffset = 14 });
59+
procedureDeclaration.Reference.Should().BeEquivalentTo(
60+
new RepeatedField<TextRange>
61+
{
62+
new TextRange { StartLine = 11, EndLine = 11, StartOffset = 8, EndOffset = 14 },
63+
new TextRange { StartLine = 13, EndLine = 13, StartOffset = 8, EndOffset = 14 }
64+
});
6265

6366
var functionDeclaration = references.Single(x => x.Declaration.StartLine == 6);
6467
functionDeclaration.Declaration.Should().BeEquivalentTo(new TextRange { StartLine = 6, EndLine = 6, StartOffset = 13, EndOffset = 23 });
@@ -212,6 +215,61 @@ public void Verify_Razor() =>
212215
VerifyReferences(orderedSymbols[1].Reference, 9, 44, 41); // LocalMethod
213216
});
214217

218+
[DataTestMethod]
219+
[DataRow(ProjectType.Product)]
220+
[DataRow(ProjectType.Test)]
221+
public void Verify_PrimaryConstructor_PreciseLocation_CSharp12(ProjectType projectType) =>
222+
Verify("PrimaryConstructor.cs", projectType, references =>
223+
{
224+
references.Select(x => x.Declaration.StartLine).Should().BeEquivalentTo(new[] { 1, 3, 6, 6, 8, 8, 10, 11, 12, 14, 17, 17, 19, 20, 21, 21, 23, 23, 25 });
225+
226+
var primaryCtorParameter = references.Single(x => x.Declaration.StartLine == 8 && x.Declaration.StartOffset == 19); // b1, primary ctor
227+
primaryCtorParameter.Declaration.Should().BeEquivalentTo(new TextRange { StartLine = 8, EndLine = 8, StartOffset = 19, EndOffset = 21 });
228+
primaryCtorParameter.Reference.Should().BeEquivalentTo(
229+
new RepeatedField<TextRange>
230+
{
231+
new TextRange { StartLine = 10, EndLine = 10, StartOffset = 24, EndOffset = 26 }, // Field
232+
new TextRange { StartLine = 11, EndLine = 11, StartOffset = 41, EndOffset = 43 }, // Property
233+
new TextRange { StartLine = 12, EndLine = 12, StartOffset = 21, EndOffset = 23 } // b1
234+
});
235+
236+
var ctorDeclaration = references.Single(x => x.Declaration.StartLine == 17 && x.Declaration.StartOffset == 6); // B
237+
ctorDeclaration.Reference.Should().BeEmpty(); // FN, not reporting constructor 'B' and 'this' (line 21)
238+
239+
var fieldNameEqualToParameter = references.Single(x => x.Declaration.StartLine == 12 && x.Declaration.StartOffset == 16); // b1, field
240+
fieldNameEqualToParameter.Reference.Should().Equal(
241+
new TextRange { StartLine = 14, EndLine = 14, StartOffset = 20, EndOffset = 22 }); // b1, returned by Method
242+
243+
var ctorParameterDeclaration = references.Single(x => x.Declaration.StartLine == 21 && x.Declaration.StartOffset == 17); // b1, internal ctor
244+
ctorParameterDeclaration.Reference.Should().Equal(
245+
new TextRange { StartLine = 21, EndLine = 21, StartOffset = 36, EndOffset = 38 }); // b1, this parameter
246+
247+
var primaryCtorParameterB = references.Single(x => x.Declaration.StartLine == 17 && x.Declaration.StartOffset == 12); // b1, primary ctor B
248+
primaryCtorParameterB.Reference.Should().BeEquivalentTo(
249+
new RepeatedField<TextRange>
250+
{
251+
new TextRange { StartLine = 19, EndLine = 19, StartOffset = 24, EndOffset = 26 }, // Field
252+
new TextRange { StartLine = 20, EndLine = 20, StartOffset = 41, EndOffset = 43 }, // Property
253+
new TextRange { StartLine = 25, EndLine = 25, StartOffset = 20, EndOffset = 22 } // returned by Method
254+
});
255+
256+
var classADeclaration = references.Single(x => x.Declaration.StartLine == 1 && x.Declaration.StartOffset == 13); // A
257+
classADeclaration.Reference.Should().BeEquivalentTo(
258+
new RepeatedField<TextRange>
259+
{
260+
new TextRange { StartLine = 6, EndLine = 6, StartOffset = 34, EndOffset = 35 }, // primary ctor default parameter
261+
new TextRange { StartLine = 23, EndLine = 23, StartOffset = 25, EndOffset = 26 } // lambda default parameter
262+
});
263+
264+
var constFieldDeclaration = references.Single(x => x.Declaration.StartLine == 3 && x.Declaration.StartOffset == 21); // I
265+
constFieldDeclaration.Reference.Should().BeEquivalentTo(
266+
new RepeatedField<TextRange>
267+
{
268+
new TextRange { StartLine = 6, EndLine = 6, StartOffset = 36, EndOffset = 37 }, // primary ctor default parameter
269+
new TextRange { StartLine = 23, EndLine = 23, StartOffset = 27, EndOffset = 28 } // lambda default parameter
270+
});
271+
});
272+
215273
#endif
216274

217275
private void Verify(string fileName, ProjectType projectType, int expectedDeclarationCount, int assertedDeclarationLine, params int[] assertedDeclarationLineReferences) =>

analyzers/tests/SonarAnalyzer.UnitTest/Rules/Utilities/TokenTypeAnalyzerTest.Classifier.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1375,4 +1375,23 @@ public class C
13751375
{{fieldDeclaration}}
13761376
}
13771377
""", allowSemanticModel);
1378+
1379+
#if NET
1380+
1381+
[TestMethod]
1382+
public void CSharp12Syntax_Classification() =>
1383+
ClassifierTestHarness.AssertTokenTypes("""
1384+
using System;
1385+
1386+
class PrimaryConstructor([t:Int32] [u:i] = [n:1])
1387+
{
1388+
public PrimaryConstructor(int a1, int a2) : [k:this]([u:a1])
1389+
{
1390+
var f = ([t:Int32] [u:i] = [n:1]) => i;
1391+
}
1392+
}
1393+
""");
1394+
1395+
#endif
1396+
13781397
}

analyzers/tests/SonarAnalyzer.UnitTest/Rules/Utilities/TokenTypeAnalyzerTest.cs

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ public void Verify_MainTokens_CS(ProjectType projectType) =>
5151
[DataTestMethod]
5252
[DataRow(ProjectType.Product)]
5353
[DataRow(ProjectType.Test)]
54-
public void Verify_MainTokens_CSSharp11(ProjectType projectType) =>
54+
public void Verify_MainTokens_CSharp11(ProjectType projectType) =>
5555
Verify("Tokens.Csharp11.cs", projectType, info =>
5656
{
5757
info.Should().HaveCount(42);
@@ -61,6 +61,20 @@ public void Verify_MainTokens_CSSharp11(ProjectType projectType) =>
6161
info.Should().ContainSingle(x => x.TokenType == TokenType.NumericLiteral);
6262
});
6363

64+
[DataTestMethod]
65+
[DataRow(ProjectType.Product)]
66+
[DataRow(ProjectType.Test)]
67+
public void Verify_MainTokens_CSharp12(ProjectType projectType) =>
68+
Verify("Tokens.Csharp12.cs", projectType, info =>
69+
{
70+
info.Should().HaveCount(34);
71+
info.Where(x => x.TokenType == TokenType.Keyword).Should().HaveCount(17);
72+
info.Where(x => x.TokenType == TokenType.StringLiteral).Should().HaveCount(5);
73+
info.Where(x => x.TokenType == TokenType.NumericLiteral).Should().HaveCount(4);
74+
info.Where(x => x.TokenType == TokenType.TypeName).Should().HaveCount(7);
75+
info.Should().ContainSingle(x => x.TokenType == TokenType.Comment);
76+
});
77+
6478
[DataTestMethod]
6579
[DataRow("Razor.razor")]
6680
[DataRow("Razor.cshtml")]
@@ -134,10 +148,8 @@ public void Verify_Trivia_VB(ProjectType projectType) =>
134148
[TestMethod]
135149
public void Verify_IdentifierTokenThreshold() =>
136150
Verify("IdentifierTokenThreshold.cs", ProjectType.Product, tokenInfo =>
137-
{
138151
// IdentifierTokenThreshold.cs has 4001 identifiers which exceeds current threshold of 4000. Due to this, the identifiers are not classified
139-
tokenInfo.Where(token => token.TokenType == TokenType.TypeName).Should().BeEmpty();
140-
});
152+
tokenInfo.Should().NotContain(token => token.TokenType == TokenType.TypeName));
141153

142154
[DataTestMethod]
143155
[DataRow("Tokens.cs", true)]
@@ -160,7 +172,7 @@ private void Verify(string fileName, ProjectType projectType, Action<IReadOnlyLi
160172
.WithAdditionalFilePath(AnalysisScaffolding.CreateSonarProjectConfig(TestContext, projectType))
161173
.VerifyUtilityAnalyzer<TokenTypeInfo>(messages =>
162174
{
163-
messages.Should().HaveCount(1);
175+
messages.Should().ContainSingle();
164176
var info = messages.Single();
165177
info.FilePath.Should().Be(Path.Combine(BasePath, fileName));
166178
verifyTokenInfo(info.TokenInfo);
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
class PrimaryConstructor(string a = "a", char b = 'b', /* Comment */ double c = 1)
2+
{
3+
void Method()
4+
{
5+
var lambdaWithDefaultValues = (string x = "y",
6+
char y = 'z', // Comment
7+
double z = 1) => x;
8+
string[] collectionExpressionStr = ["Hello",
9+
"World"];
10+
char[] collectionExpressionChar = ['a',
11+
'b'];
12+
double[] collectionExpressionDouble = [1, // Comment
13+
2];
14+
}
15+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
class PrimaryConstructor(bool condition /* comment */)
2+
{
3+
bool Field = condition;
4+
5+
int Method()
6+
{
7+
if (condition)
8+
{
9+
return 42;
10+
}
11+
return 0;
12+
}
13+
14+
PrimaryConstructor(bool condition, int n) : this(condition) { }
15+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
static class A
2+
{
3+
public const int I = 1;
4+
}
5+
6+
class PrimaryConstructor(int a1 = A.I) { }
7+
8+
class SubClass(int b1) : PrimaryConstructor(1)
9+
{
10+
private int Field = b1;
11+
private int Property { get; set; } = b1;
12+
private int b1 = b1;
13+
14+
int Method() => b1;
15+
}
16+
17+
class B(int b1)
18+
{
19+
private int Field = b1;
20+
private int Property { get; set; } = b1;
21+
public B(int b1, int b2) : this(b1)
22+
{
23+
var f = (int i = A.I) => i;
24+
}
25+
int Method() => b1;
26+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using Point = (int, int);
2+
3+
class PrimaryConstructor(System.String p1, string p2 = "default value that should be tokenized as string" /* a comment */, int p3 = 1)
4+
{
5+
void Method()
6+
{
7+
var lambdaWithDefaultValues = (string l1 = "default value that should be tokenized as string", int l2 = 2) => l1;
8+
var usingAliasDirective = new Point(0, 0);
9+
string[] collectionExpression = ["Hello", "World"];
10+
}
11+
}
12+
13+
class SubClass() : PrimaryConstructor("something")
14+
{
15+
public SubClass(int p1) : this() { }
16+
}

0 commit comments

Comments
 (0)