-
-
Notifications
You must be signed in to change notification settings - Fork 1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
DefaultValue attribute roslyn analyzers (#1040)
* setup tests for code fixx * refactor and make roslyn code fix to include protobuf packages * prepare test infra for validating code-fix-provider * more tests to understand the behavior * implement ShouldDeclareDefault_CodeFixProvider ! * remove decimal from`ShouldDeclareDefault` diagnostic * refactor the diagnostic part on different scenarios * make previous tests pass * split up tests * more tests and fixes * add the constructor parameter equality checks * refactor to use semantic model value * simplify and split up tests * and implement IsRequired * merge main & resolve conflicts * remove warnings * lightxunitverifier to fix glitch * Revert "lightxunitverifier to fix glitch" This reverts commit 4143f21. * try use SemanticStructure validation only * validate using addition in a separate test + fix glitch * and fix last glitching test * add tests for long syntax * try to parse in the way it is done by DefaultValue itself * support long syntax + short syntax separately * make `decimal` work with long syntax for both short and long syntax * use long syntax for other types as well (tests not working) * fix the converting issues in all flows + transfer `nint` and `nuint` to `IsRequired` scenario * reduce warnings * use cast in all short-syntax default value code fixes * more warning fixes * disable warning on usages * suppress RS1022 warning on class directly
- Loading branch information
1 parent
9618de9
commit 1cab873
Showing
28 changed files
with
1,979 additions
and
211 deletions.
There are no files selected for viewing
14 changes: 14 additions & 0 deletions
14
src/BuildToolsUnitTests/Abstractions/RoslynAnalysisTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
using Microsoft.CodeAnalysis.CSharp; | ||
using Microsoft.CodeAnalysis.CSharp.Syntax; | ||
|
||
namespace BuildToolsUnitTests.Abstractions | ||
{ | ||
public abstract class RoslynAnalysisTests | ||
{ | ||
protected CompilationUnitSyntax BuildCompilationUnitSyntax(string programText) | ||
{ | ||
var tree = CSharpSyntaxTree.ParseText(programText); | ||
return tree.GetCompilationUnitRoot(); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
84 changes: 84 additions & 0 deletions
84
src/BuildToolsUnitTests/CodeFixes/Abstractions/CodeFixProviderTestsBase.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
using Microsoft.CodeAnalysis.CodeFixes; | ||
using Microsoft.CodeAnalysis.CSharp.Testing; | ||
using Microsoft.CodeAnalysis.Diagnostics; | ||
using Microsoft.CodeAnalysis.Testing.Verifiers; | ||
using Microsoft.CodeAnalysis.Testing; | ||
using System.Threading.Tasks; | ||
using Microsoft.CodeAnalysis; | ||
using System.Reflection; | ||
using System.Linq; | ||
using System.Runtime.Versioning; | ||
|
||
namespace BuildToolsUnitTests.CodeFixes.Abstractions | ||
{ | ||
public abstract class CodeFixProviderTestsBase<TCodeFixProvider> | ||
where TCodeFixProvider : CodeFixProvider, new() | ||
{ | ||
static TargetFrameworkAttribute CurrentRunningAssemblyTargetFramework | ||
=> (TargetFrameworkAttribute)Assembly.GetExecutingAssembly() | ||
.GetCustomAttributes(typeof(TargetFrameworkAttribute), false) | ||
.Single(); | ||
|
||
protected async Task RunCodeFixTestAsync<TDiagnosticAnalyzer>( | ||
string sourceCode, | ||
string expectedCode, | ||
DiagnosticResult? diagnosticResult = null, | ||
string? targetFramework = null, | ||
params DiagnosticResult[] standardExpectedDiagnostics) | ||
where TDiagnosticAnalyzer : DiagnosticAnalyzer, new() | ||
{ | ||
var codeFixTest = BuildCSharpCodeFixTest<TDiagnosticAnalyzer>(sourceCode, expectedCode, targetFramework); | ||
|
||
if (diagnosticResult is not null) | ||
{ | ||
// expect a diagnostic in sourceCode | ||
codeFixTest.TestState.ExpectedDiagnostics.Add(diagnosticResult.Value); | ||
} | ||
|
||
// we expect some standard diagnostics in both of code compilations | ||
AddStandardDiagnostics(codeFixTest.TestState, standardExpectedDiagnostics); | ||
AddStandardDiagnostics(codeFixTest.FixedState, standardExpectedDiagnostics); | ||
|
||
await codeFixTest.RunAsync(); | ||
} | ||
|
||
CSharpCodeFixTest<TDiagnosticAnalyzer, TCodeFixProvider, XUnitVerifier> BuildCSharpCodeFixTest<TDiagnosticAnalyzer>( | ||
string sourceCode, string expectedCode, string? targetFramework = null) | ||
where TDiagnosticAnalyzer : DiagnosticAnalyzer, new() | ||
{ | ||
if (string.IsNullOrEmpty(targetFramework)) | ||
{ | ||
targetFramework = CurrentRunningAssemblyTargetFramework.FrameworkDisplayName!; | ||
} | ||
|
||
var codeFixTest = new CSharpCodeFixTest<TDiagnosticAnalyzer, TCodeFixProvider, XUnitVerifier> | ||
{ | ||
ReferenceAssemblies = new ReferenceAssemblies(targetFramework), | ||
TestState = { Sources = { sourceCode }, OutputKind = OutputKind.DynamicallyLinkedLibrary }, | ||
FixedState = { Sources = { expectedCode }, OutputKind = OutputKind.DynamicallyLinkedLibrary } | ||
}; | ||
|
||
codeFixTest.CodeActionValidationMode = CodeActionValidationMode.SemanticStructure; | ||
|
||
AddAdditionalReferences(codeFixTest.TestState); | ||
AddAdditionalReferences(codeFixTest.FixedState); | ||
|
||
return codeFixTest; | ||
} | ||
|
||
private static void AddAdditionalReferences(SolutionState solutionState) | ||
{ | ||
solutionState.AdditionalReferences.AddRange(MetadataReferenceHelpers.ProtoBufReferences); | ||
solutionState.AdditionalReferences.AddRange(MetadataReferenceHelpers.WellKnownReferences); | ||
} | ||
|
||
private static void AddStandardDiagnostics(SolutionState solutionState, params DiagnosticResult[] diagnosticResults) | ||
{ | ||
if (diagnosticResults.Length <= 0) return; | ||
foreach (var diagnosticResult in diagnosticResults) | ||
{ | ||
solutionState.ExpectedDiagnostics.Add(diagnosticResult); | ||
} | ||
} | ||
} | ||
} |
255 changes: 255 additions & 0 deletions
255
src/BuildToolsUnitTests/CodeFixes/ShouldDeclareDefaultCodeFixProviderTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,255 @@ | ||
using BuildToolsUnitTests.CodeFixes.Abstractions; | ||
using Microsoft.CodeAnalysis; | ||
using Microsoft.CodeAnalysis.Testing; | ||
using ProtoBuf.BuildTools.Analyzers; | ||
using System.Threading.Tasks; | ||
using ProtoBuf.CodeFixes.DefaultValue; | ||
using Xunit; | ||
|
||
namespace BuildToolsUnitTests.CodeFixes | ||
{ | ||
public class ShouldDeclareDefaultCodeFixProviderTests : CodeFixProviderTestsBase<ShouldDeclareDefaultCodeFixProvider> | ||
{ | ||
private readonly DiagnosticResult[] _standardExpectedDiagnostics = new[] { | ||
new DiagnosticResult(DataContractAnalyzer.MissingCompatibilityLevel) | ||
}; | ||
|
||
[Theory] | ||
[InlineData("int", "-2")] | ||
public async Task CodeFixValidate_ShouldDeclareDefault_NonProtoMemberAttributeExists( | ||
string propertyType, string propertyDefaultValue) | ||
{ | ||
var sourceCode = $@" | ||
using ProtoBuf; | ||
using System; | ||
[ProtoContract] | ||
public class Foo | ||
{{ | ||
public {propertyType} Bar {{ get; set; }} = {propertyDefaultValue}; | ||
}} | ||
"; | ||
|
||
var expectedCode = $@" | ||
using ProtoBuf; | ||
using System; | ||
[ProtoContract] | ||
public class Foo | ||
{{ | ||
public {propertyType} Bar {{ get; set; }} = {propertyDefaultValue}; | ||
}} | ||
"; | ||
|
||
await RunCodeFixTestAsync<DataContractAnalyzer>( | ||
sourceCode, | ||
expectedCode, | ||
diagnosticResult: null, // no diagnostic expected! | ||
standardExpectedDiagnostics: _standardExpectedDiagnostics); | ||
} | ||
|
||
[Theory] | ||
[InlineData("int", "-2")] | ||
public async Task CodeFixValidate_ShouldDeclareDefault_AnotherCustomAttributeExists( | ||
string propertyType, string propertyDefaultValue) | ||
{ | ||
var sourceCode = $@" | ||
using ProtoBuf; | ||
using System; | ||
using System.ComponentModel; | ||
[ProtoContract] | ||
public class Foo | ||
{{ | ||
[ProtoMember(1)] | ||
[Custom] | ||
public {propertyType} Bar {{ get; set; }} = {propertyDefaultValue}; | ||
}} | ||
public class CustomAttribute : Attribute {{ }}"; | ||
|
||
// System.ComponentModel is added as part of code-fix | ||
var expectedCode = $@" | ||
using ProtoBuf; | ||
using System; | ||
using System.ComponentModel; | ||
[ProtoContract] | ||
public class Foo | ||
{{ | ||
[ProtoMember(1), DefaultValue(({propertyType}){propertyDefaultValue})] | ||
[Custom] | ||
public {propertyType} Bar {{ get; set; }} = {propertyDefaultValue}; | ||
}} | ||
public class CustomAttribute : Attribute {{ }}"; | ||
|
||
var diagnosticResult = PrepareDiagnosticResult( | ||
DataContractAnalyzer.ShouldDeclareDefault, | ||
9, 6, 9, 20, | ||
propertyDefaultValue); | ||
|
||
await RunCodeFixTestAsync<DataContractAnalyzer>( | ||
sourceCode, | ||
expectedCode, | ||
diagnosticResult, | ||
standardExpectedDiagnostics: _standardExpectedDiagnostics); | ||
} | ||
|
||
[Theory] | ||
[InlineData("int", "-2")] | ||
public async Task CodeFixValidate_ShouldDeclareDefault_UsingDirectiveAlreadyExists( | ||
string propertyType, string propertyDefaultValue) | ||
{ | ||
var sourceCode = $@" | ||
using ProtoBuf; | ||
using System; | ||
using System.ComponentModel; | ||
[ProtoContract] | ||
public class Foo | ||
{{ | ||
[ProtoMember(1)] | ||
public {propertyType} Bar {{ get; set; }} = {propertyDefaultValue}; | ||
}}"; | ||
|
||
// System.ComponentModel is added as part of code-fix | ||
var expectedCode = $@" | ||
using ProtoBuf; | ||
using System; | ||
using System.ComponentModel; | ||
[ProtoContract] | ||
public class Foo | ||
{{ | ||
[ProtoMember(1), DefaultValue(({propertyType}){propertyDefaultValue})] | ||
public {propertyType} Bar {{ get; set; }} = {propertyDefaultValue}; | ||
}}"; | ||
|
||
var diagnosticResult = PrepareDiagnosticResult( | ||
DataContractAnalyzer.ShouldDeclareDefault, | ||
9, 6, 9, 20, | ||
propertyDefaultValue); | ||
|
||
await RunCodeFixTestAsync<DataContractAnalyzer>( | ||
sourceCode, | ||
expectedCode, | ||
diagnosticResult, | ||
standardExpectedDiagnostics: _standardExpectedDiagnostics); | ||
} | ||
|
||
[Theory] | ||
[InlineData("decimal", "2.1", "2.1m")] | ||
[InlineData("sbyte", "1", "1")] | ||
[InlineData("uint", "6", "6u")] | ||
[InlineData("ulong", "6758493021", "6758493021UL")] | ||
[InlineData("ushort", "4", "4")] | ||
public async Task CodeFixValidate_ShouldDeclareDefault_ReportsDiagnostic_LongSyntax(string propertyType, string attributeValue, string propertyDefaultValue) | ||
{ | ||
var sourceCode = $@" | ||
using ProtoBuf; | ||
using System; | ||
using System.ComponentModel; | ||
[ProtoContract] | ||
public class Foo | ||
{{ | ||
[ProtoMember(1)] | ||
public {propertyType} Bar {{ get; set; }} = {propertyDefaultValue}; | ||
}}"; | ||
|
||
// note: System.ComponentModel is added as part of code-fix | ||
// and this behavior is tested in separate class, since "usingDirective" addition produces | ||
// wrong line endings, which fail in roslyn codeFix test | ||
// https://github.com/dotnet/roslyn/issues/62976 | ||
var expectedCode = $@" | ||
using ProtoBuf; | ||
using System; | ||
using System.ComponentModel; | ||
[ProtoContract] | ||
public class Foo | ||
{{ | ||
[ProtoMember(1), DefaultValue(typeof({propertyType}), ""{attributeValue}"")] | ||
public {propertyType} Bar {{ get; set; }} = {propertyDefaultValue}; | ||
}}"; | ||
|
||
var diagnosticResult = PrepareDiagnosticResult( | ||
DataContractAnalyzer.ShouldDeclareDefault, | ||
9, 6, 9, 20, | ||
propertyDefaultValue); | ||
|
||
await RunCodeFixTestAsync<DataContractAnalyzer>( | ||
sourceCode, | ||
expectedCode, | ||
diagnosticResult, | ||
standardExpectedDiagnostics: _standardExpectedDiagnostics); | ||
} | ||
|
||
[Theory] | ||
[InlineData("bool", "true")] | ||
[InlineData("DayOfWeek", "DayOfWeek.Monday", false)] | ||
[InlineData("char", "'x'")] | ||
[InlineData("byte", "0x2")] | ||
[InlineData("short", "0b0000_0011")] | ||
[InlineData("int", "-2")] | ||
[InlineData("long", "1234567890123456789L")] | ||
[InlineData("float", "2.71828f")] | ||
[InlineData("double", "3.14159265")] | ||
public async Task CodeFixValidate_ShouldDeclareDefault_ReportsDiagnostic_ShortSyntax( | ||
string propertyType, string propertyDefaultValue, bool isCasted = true) | ||
{ | ||
var sourceCode = $@" | ||
using ProtoBuf; | ||
using System; | ||
using System.ComponentModel; | ||
[ProtoContract] | ||
public class Foo | ||
{{ | ||
[ProtoMember(1)] | ||
public {propertyType} Bar {{ get; set; }} = {propertyDefaultValue}; | ||
}}"; | ||
|
||
// note: System.ComponentModel is added as part of code-fix | ||
// and this behavior is tested in separate class, since "usingDirective" addition produces | ||
// wrong line endings, which fail in roslyn codeFix test | ||
// https://github.com/dotnet/roslyn/issues/62976 | ||
|
||
var castExpression = isCasted ? $"({propertyType})" : string.Empty; | ||
|
||
var expectedCode = $@" | ||
using ProtoBuf; | ||
using System; | ||
using System.ComponentModel; | ||
[ProtoContract] | ||
public class Foo | ||
{{ | ||
[ProtoMember(1), DefaultValue({castExpression}{propertyDefaultValue})] | ||
public {propertyType} Bar {{ get; set; }} = {propertyDefaultValue}; | ||
}}"; | ||
|
||
var diagnosticResult = PrepareDiagnosticResult( | ||
DataContractAnalyzer.ShouldDeclareDefault, | ||
9, 6, 9, 20, | ||
propertyDefaultValue); | ||
|
||
await RunCodeFixTestAsync<DataContractAnalyzer>( | ||
sourceCode, | ||
expectedCode, | ||
diagnosticResult, | ||
standardExpectedDiagnostics: _standardExpectedDiagnostics); | ||
} | ||
|
||
static DiagnosticResult PrepareDiagnosticResult( | ||
DiagnosticDescriptor diagnosticDescriptor, | ||
int startLine, int startColumn, int endLine, int endColumn, | ||
string propertyDefaultValue) | ||
{ | ||
return new DiagnosticResult(diagnosticDescriptor) | ||
.WithSpan(startLine, startColumn, endLine, endColumn) | ||
.WithArguments("Bar", propertyDefaultValue); | ||
} | ||
} | ||
} |
Oops, something went wrong.