Skip to content

Commit

Permalink
Add utility analyzer's UTs for C#12 syntax (#8135)
Browse files Browse the repository at this point in the history
  • Loading branch information
cristian-ambrosini-sonarsource authored Oct 9, 2023
1 parent 6360ba4 commit 8520778
Show file tree
Hide file tree
Showing 9 changed files with 202 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,16 @@ public void Verify_Unique_CSharp11() =>
info.Count(x => x.TokenValue == "$char").Should().Be(2);
});

[TestMethod]
public void Verify_Unique_CSharp12() =>
Verify("Unique.Csharp12.cs", info =>
{
info.Should().HaveCount(81);
info.Count(x => x.TokenValue == "$str").Should().Be(4);
info.Count(x => x.TokenValue == "$num").Should().Be(4);
info.Count(x => x.TokenValue == "$char").Should().Be(4);
});

#endif

[TestMethod]
Expand All @@ -81,7 +91,7 @@ public void Verify_Duplicated_CS_GlobalUsings() =>
.WithAdditionalFilePath(AnalysisScaffolding.CreateSonarProjectConfig(TestContext, ProjectType.Product))
.VerifyUtilityAnalyzer<CopyPasteTokenInfo>(messages =>
{
messages.Should().HaveCount(1);
messages.Should().ContainSingle();
var info = messages.Single();
info.FilePath.Should().Be(Path.Combine(BasePath, "Duplicated.CSharp10.cs"));
info.TokenInfo.Should().HaveCount(39);
Expand Down Expand Up @@ -113,7 +123,7 @@ private void Verify(string fileName, Action<IReadOnlyList<CopyPasteTokenInfo.Typ
.WithAdditionalFilePath(AnalysisScaffolding.CreateSonarProjectConfig(TestContext, ProjectType.Product))
.VerifyUtilityAnalyzer<CopyPasteTokenInfo>(messages =>
{
messages.Should().HaveCount(1);
messages.Should().ContainSingle();
var info = messages.Single();
info.FilePath.Should().Be(Path.Combine(BasePath, fileName));
verifyTokenInfo(info.TokenInfo);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
using System.IO;
using SonarAnalyzer.Protobuf;
using SonarAnalyzer.Rules.CSharp;
using SonarAnalyzer.UnitTest.Helpers;

namespace SonarAnalyzer.UnitTest.Rules
{
Expand All @@ -32,6 +31,7 @@ public class MetricsAnalyzerTest
private const string AllMetricsFileName = "AllMetrics.cs";
private const string RazorFileName = "Razor.razor";
private const string CsHtmlFileName = "Razor.cshtml";
private const string CSharp12FileName = "Metrics.CSharp12.cs";

public TestContext TestContext { get; set; }

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

[TestMethod]
public void VerifyMetrics_CSharp12() =>
CreateBuilder(false, CSharp12FileName)
.WithAdditionalFilePath(AnalysisScaffolding.CreateSonarProjectConfig(TestContext, ProjectType.Product))
.VerifyUtilityAnalyzer<MetricsInfo>(messages =>
{
messages.Should().ContainSingle();
var metrics = messages.Single();
metrics.ClassCount.Should().Be(1); // no changes
metrics.CodeLine.Should().HaveCount(13);
metrics.CognitiveComplexity.Should().Be(1); // no changes
metrics.Complexity.Should().Be(3); // no changes
metrics.ExecutableLines.Should().HaveCount(3); // 5, 7, 9
metrics.FunctionCount.Should().Be(2); // no changes
metrics.NoSonarComment.Should().BeEmpty();
metrics.NonBlankComment.Should().ContainSingle();
metrics.StatementCount.Should().Be(3);
});

#endif

[TestMethod]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@
*/

using System.IO;
using Google.Protobuf.Collections;
using SonarAnalyzer.Protobuf;
using SonarAnalyzer.Rules;
using SonarAnalyzer.UnitTest.Helpers;
using CS = SonarAnalyzer.Rules.CSharp;
using VB = SonarAnalyzer.Rules.VisualBasic;

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

var procedureDeclaration = references.Single(x => x.Declaration.StartLine == 3);
procedureDeclaration.Declaration.Should().BeEquivalentTo(new TextRange { StartLine = 3, EndLine = 3, StartOffset = 15, EndOffset = 21 });
procedureDeclaration.Reference.Should().Equal(
new TextRange { StartLine = 11, EndLine = 11, StartOffset = 8, EndOffset = 14 },
new TextRange { StartLine = 13, EndLine = 13, StartOffset = 8, EndOffset = 14 });
procedureDeclaration.Reference.Should().BeEquivalentTo(
new RepeatedField<TextRange>
{
new TextRange { StartLine = 11, EndLine = 11, StartOffset = 8, EndOffset = 14 },
new TextRange { StartLine = 13, EndLine = 13, StartOffset = 8, EndOffset = 14 }
});

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

[DataTestMethod]
[DataRow(ProjectType.Product)]
[DataRow(ProjectType.Test)]
public void Verify_PrimaryConstructor_PreciseLocation_CSharp12(ProjectType projectType) =>
Verify("PrimaryConstructor.cs", projectType, references =>
{
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 });

var primaryCtorParameter = references.Single(x => x.Declaration.StartLine == 8 && x.Declaration.StartOffset == 19); // b1, primary ctor
primaryCtorParameter.Declaration.Should().BeEquivalentTo(new TextRange { StartLine = 8, EndLine = 8, StartOffset = 19, EndOffset = 21 });
primaryCtorParameter.Reference.Should().BeEquivalentTo(
new RepeatedField<TextRange>
{
new TextRange { StartLine = 10, EndLine = 10, StartOffset = 24, EndOffset = 26 }, // Field
new TextRange { StartLine = 11, EndLine = 11, StartOffset = 41, EndOffset = 43 }, // Property
new TextRange { StartLine = 12, EndLine = 12, StartOffset = 21, EndOffset = 23 } // b1
});

var ctorDeclaration = references.Single(x => x.Declaration.StartLine == 17 && x.Declaration.StartOffset == 6); // B
ctorDeclaration.Reference.Should().BeEmpty(); // FN, not reporting constructor 'B' and 'this' (line 21)

var fieldNameEqualToParameter = references.Single(x => x.Declaration.StartLine == 12 && x.Declaration.StartOffset == 16); // b1, field
fieldNameEqualToParameter.Reference.Should().Equal(
new TextRange { StartLine = 14, EndLine = 14, StartOffset = 20, EndOffset = 22 }); // b1, returned by Method

var ctorParameterDeclaration = references.Single(x => x.Declaration.StartLine == 21 && x.Declaration.StartOffset == 17); // b1, internal ctor
ctorParameterDeclaration.Reference.Should().Equal(
new TextRange { StartLine = 21, EndLine = 21, StartOffset = 36, EndOffset = 38 }); // b1, this parameter

var primaryCtorParameterB = references.Single(x => x.Declaration.StartLine == 17 && x.Declaration.StartOffset == 12); // b1, primary ctor B
primaryCtorParameterB.Reference.Should().BeEquivalentTo(
new RepeatedField<TextRange>
{
new TextRange { StartLine = 19, EndLine = 19, StartOffset = 24, EndOffset = 26 }, // Field
new TextRange { StartLine = 20, EndLine = 20, StartOffset = 41, EndOffset = 43 }, // Property
new TextRange { StartLine = 25, EndLine = 25, StartOffset = 20, EndOffset = 22 } // returned by Method
});

var classADeclaration = references.Single(x => x.Declaration.StartLine == 1 && x.Declaration.StartOffset == 13); // A
classADeclaration.Reference.Should().BeEquivalentTo(
new RepeatedField<TextRange>
{
new TextRange { StartLine = 6, EndLine = 6, StartOffset = 34, EndOffset = 35 }, // primary ctor default parameter
new TextRange { StartLine = 23, EndLine = 23, StartOffset = 25, EndOffset = 26 } // lambda default parameter
});

var constFieldDeclaration = references.Single(x => x.Declaration.StartLine == 3 && x.Declaration.StartOffset == 21); // I
constFieldDeclaration.Reference.Should().BeEquivalentTo(
new RepeatedField<TextRange>
{
new TextRange { StartLine = 6, EndLine = 6, StartOffset = 36, EndOffset = 37 }, // primary ctor default parameter
new TextRange { StartLine = 23, EndLine = 23, StartOffset = 27, EndOffset = 28 } // lambda default parameter
});
});

#endif

private void Verify(string fileName, ProjectType projectType, int expectedDeclarationCount, int assertedDeclarationLine, params int[] assertedDeclarationLineReferences) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1375,4 +1375,23 @@ public class C
{{fieldDeclaration}}
}
""", allowSemanticModel);

#if NET

[TestMethod]
public void CSharp12Syntax_Classification() =>
ClassifierTestHarness.AssertTokenTypes("""
using System;
class PrimaryConstructor([t:Int32] [u:i] = [n:1])
{
public PrimaryConstructor(int a1, int a2) : [k:this]([u:a1])
{
var f = ([t:Int32] [u:i] = [n:1]) => i;
}
}
""");

#endif

}
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public void Verify_MainTokens_CS(ProjectType projectType) =>
[DataTestMethod]
[DataRow(ProjectType.Product)]
[DataRow(ProjectType.Test)]
public void Verify_MainTokens_CSSharp11(ProjectType projectType) =>
public void Verify_MainTokens_CSharp11(ProjectType projectType) =>
Verify("Tokens.Csharp11.cs", projectType, info =>
{
info.Should().HaveCount(42);
Expand All @@ -61,6 +61,20 @@ public void Verify_MainTokens_CSSharp11(ProjectType projectType) =>
info.Should().ContainSingle(x => x.TokenType == TokenType.NumericLiteral);
});

[DataTestMethod]
[DataRow(ProjectType.Product)]
[DataRow(ProjectType.Test)]
public void Verify_MainTokens_CSharp12(ProjectType projectType) =>
Verify("Tokens.Csharp12.cs", projectType, info =>
{
info.Should().HaveCount(34);
info.Where(x => x.TokenType == TokenType.Keyword).Should().HaveCount(17);
info.Where(x => x.TokenType == TokenType.StringLiteral).Should().HaveCount(5);
info.Where(x => x.TokenType == TokenType.NumericLiteral).Should().HaveCount(4);
info.Where(x => x.TokenType == TokenType.TypeName).Should().HaveCount(7);
info.Should().ContainSingle(x => x.TokenType == TokenType.Comment);
});

[DataTestMethod]
[DataRow("Razor.razor")]
[DataRow("Razor.cshtml")]
Expand Down Expand Up @@ -134,10 +148,8 @@ public void Verify_Trivia_VB(ProjectType projectType) =>
[TestMethod]
public void Verify_IdentifierTokenThreshold() =>
Verify("IdentifierTokenThreshold.cs", ProjectType.Product, tokenInfo =>
{
// IdentifierTokenThreshold.cs has 4001 identifiers which exceeds current threshold of 4000. Due to this, the identifiers are not classified
tokenInfo.Where(token => token.TokenType == TokenType.TypeName).Should().BeEmpty();
});
tokenInfo.Should().NotContain(token => token.TokenType == TokenType.TypeName));

[DataTestMethod]
[DataRow("Tokens.cs", true)]
Expand All @@ -160,7 +172,7 @@ private void Verify(string fileName, ProjectType projectType, Action<IReadOnlyLi
.WithAdditionalFilePath(AnalysisScaffolding.CreateSonarProjectConfig(TestContext, projectType))
.VerifyUtilityAnalyzer<TokenTypeInfo>(messages =>
{
messages.Should().HaveCount(1);
messages.Should().ContainSingle();
var info = messages.Single();
info.FilePath.Should().Be(Path.Combine(BasePath, fileName));
verifyTokenInfo(info.TokenInfo);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
class PrimaryConstructor(string a = "a", char b = 'b', /* Comment */ double c = 1)
{
void Method()
{
var lambdaWithDefaultValues = (string x = "y",
char y = 'z', // Comment
double z = 1) => x;
string[] collectionExpressionStr = ["Hello",
"World"];
char[] collectionExpressionChar = ['a',
'b'];
double[] collectionExpressionDouble = [1, // Comment
2];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
class PrimaryConstructor(bool condition /* comment */)
{
bool Field = condition;

int Method()
{
if (condition)
{
return 42;
}
return 0;
}

PrimaryConstructor(bool condition, int n) : this(condition) { }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
static class A
{
public const int I = 1;
}

class PrimaryConstructor(int a1 = A.I) { }

class SubClass(int b1) : PrimaryConstructor(1)
{
private int Field = b1;
private int Property { get; set; } = b1;
private int b1 = b1;

int Method() => b1;
}

class B(int b1)
{
private int Field = b1;
private int Property { get; set; } = b1;
public B(int b1, int b2) : this(b1)
{
var f = (int i = A.I) => i;
}
int Method() => b1;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using Point = (int, int);

class PrimaryConstructor(System.String p1, string p2 = "default value that should be tokenized as string" /* a comment */, int p3 = 1)
{
void Method()
{
var lambdaWithDefaultValues = (string l1 = "default value that should be tokenized as string", int l2 = 2) => l1;
var usingAliasDirective = new Point(0, 0);
string[] collectionExpression = ["Hello", "World"];
}
}

class SubClass() : PrimaryConstructor("something")
{
public SubClass(int p1) : this() { }
}

0 comments on commit 8520778

Please sign in to comment.