feat: implement analyzer and code fixer for identifying and fixing when a sync flag is not propagated correctly.
This commit is contained in:
+39
@@ -0,0 +1,39 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net11.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Analyzer.Testing" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.CodeFix.Testing" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" />
|
||||
<PackageReference Include="xunit" />
|
||||
<PackageReference Include="xunit.runner.visualstudio">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Using Include="Xunit" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Remove="TestData\**" />
|
||||
<Content Include="TestData\**">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\StevanFreeborn.AsyncSyncFlagAnalyzer\StevanFreeborn.AsyncSyncFlagAnalyzer.csproj" />
|
||||
<ProjectReference Include="..\..\src\StevanFreeborn.AsyncSyncFlagAnalyzer.CodeFixes\StevanFreeborn.AsyncSyncFlagAnalyzer.CodeFixes.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,159 @@
|
||||
using Microsoft.CodeAnalysis.CSharp.Testing;
|
||||
using Microsoft.CodeAnalysis.Testing;
|
||||
|
||||
using StevanFreeborn.AsyncSyncFlagAnalyzer.CodeFixes;
|
||||
|
||||
using VerifyCS = Microsoft.CodeAnalysis.CSharp.Testing.CSharpCodeFixVerifier<
|
||||
StevanFreeborn.AsyncSyncFlagAnalyzer.SyncParameterAnalyzer,
|
||||
StevanFreeborn.AsyncSyncFlagAnalyzer.CodeFixes.SyncParameterCodeFixProvider,
|
||||
Microsoft.CodeAnalysis.Testing.DefaultVerifier>;
|
||||
|
||||
namespace StevanFreeborn.AsyncSyncFlagAnalyzer.Tests;
|
||||
|
||||
public class SyncParameterAnalyzerTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task WhenSyncIsPassedCorrectly_ItShouldNotReportDiagnostic()
|
||||
{
|
||||
var testData = await LoadTestDataAsync("WhenSyncIsPassedCorrectly");
|
||||
await VerifyCS.VerifyAnalyzerAsync(testData);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task WhenSyncIsPassedPositionally_ItShouldNotReportDiagnostic()
|
||||
{
|
||||
var testData = await LoadTestDataAsync("WhenSyncIsPassedPositionally");
|
||||
await VerifyCS.VerifyAnalyzerAsync(testData);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task WhenSyncIsPassedAsNamed_ItShouldNotReportDiagnostic()
|
||||
{
|
||||
var testData = await LoadTestDataAsync("WhenSyncIsPassedAsNamed");
|
||||
await VerifyCS.VerifyAnalyzerAsync(testData);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task WhenSyncIsOmitted_ItShouldReportDiagnosticAndApplyCodeFix()
|
||||
{
|
||||
var expectedDiagnostic = VerifyCS.Diagnostic("SYNC001")
|
||||
.WithLocation(0)
|
||||
.WithArguments("GetFrobCoreAsync", "sync", "GetCoreAsync");
|
||||
|
||||
var testData = await LoadTestDataAsync("WhenSyncIsOmitted");
|
||||
var fixedData = await LoadTestDataAsync("WhenSyncIsOmitted_Fixed");
|
||||
|
||||
await VerifyCS.VerifyCodeFixAsync(testData, expectedDiagnostic, fixedData);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task WhenSyncIsHardcodedFalse_ItShouldReportDiagnostic()
|
||||
{
|
||||
var expectedDiagnostic = VerifyCS.Diagnostic("SYNC001")
|
||||
.WithLocation(0)
|
||||
.WithArguments("GetFrobCoreAsync", "sync", "GetCoreAsync");
|
||||
|
||||
var testData = await LoadTestDataAsync("WhenSyncIsHardcodedFalse");
|
||||
|
||||
await VerifyCS.VerifyAnalyzerAsync(testData, expectedDiagnostic);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task WhenSyncIsHardcodedFalse_ItShouldApplyCodeFix()
|
||||
{
|
||||
var expectedDiagnostic = VerifyCS.Diagnostic("SYNC001")
|
||||
.WithLocation(0)
|
||||
.WithArguments("GetFrobCoreAsync", "sync", "GetCoreAsync");
|
||||
|
||||
var testData = await LoadTestDataAsync("WhenSyncIsHardcodedFalse");
|
||||
var fixedData = await LoadTestDataAsync("WhenSyncIsHardcodedFalse_Fixed");
|
||||
|
||||
await VerifyCS.VerifyCodeFixAsync(testData, expectedDiagnostic, fixedData);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task WhenSyncIsPassedAsNamedHardcoded_ItShouldReplaceNamedArgument()
|
||||
{
|
||||
var expectedDiagnostic = VerifyCS.Diagnostic("SYNC001")
|
||||
.WithLocation(0)
|
||||
.WithArguments("GetFrobCoreAsync", "sync", "GetCoreAsync");
|
||||
|
||||
var testData = await LoadTestDataAsync("WhenSyncIsPassedAsNamedHardcoded");
|
||||
var fixedData = await LoadTestDataAsync("WhenSyncIsPassedAsNamedHardcoded_Fixed");
|
||||
|
||||
await VerifyCS.VerifyCodeFixAsync(testData, expectedDiagnostic, fixedData);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task WhenOuterMethodHasNoSyncParam_ItShouldNotReportDiagnostic()
|
||||
{
|
||||
var testData = await LoadTestDataAsync("WhenOuterMethodHasNoSyncParam");
|
||||
await VerifyCS.VerifyAnalyzerAsync(testData);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task WhenTargetHasNoSyncParam_ItShouldNotReportDiagnostic()
|
||||
{
|
||||
var testData = await LoadTestDataAsync("WhenTargetHasNoSyncParam");
|
||||
await VerifyCS.VerifyAnalyzerAsync(testData);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task WhenSyncIsOmitted_CustomName_ItShouldReportDiagnosticAndApplyCodeFix()
|
||||
{
|
||||
var expectedDiagnostic = VerifyCS.Diagnostic("SYNC001")
|
||||
.WithLocation(0)
|
||||
.WithArguments("GetFrobCoreAsync", "runSynchronously", "GetMoreDataAsync");
|
||||
|
||||
var testData = await LoadTestDataAsync("WhenSyncIsOmitted_CustomName");
|
||||
var fixedData = await LoadTestDataAsync("WhenSyncIsOmitted_CustomName_Fixed");
|
||||
|
||||
var test = new CSharpCodeFixTest<SyncParameterAnalyzer, SyncParameterCodeFixProvider, DefaultVerifier>
|
||||
{
|
||||
TestCode = testData,
|
||||
FixedCode = fixedData
|
||||
};
|
||||
test.TestState.AnalyzerConfigFiles.Add(("/.editorconfig", """
|
||||
root = true
|
||||
|
||||
[*.cs]
|
||||
dotnet_diagnostic.SYNC001.additional_sync_names = runSynchronously
|
||||
"""));
|
||||
test.ExpectedDiagnostics.Add(expectedDiagnostic);
|
||||
|
||||
await test.RunAsync();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task WhenSyncIsOmitted_MultipleAwaits_OnlyOneShouldReportDiagnostic()
|
||||
{
|
||||
var expectedDiagnostic = VerifyCS.Diagnostic("SYNC001")
|
||||
.WithLocation(0)
|
||||
.WithArguments("GetFrobCoreAsync", "sync", "GetMoreDataAsync");
|
||||
|
||||
var testData = await LoadTestDataAsync("WhenSyncIsOmitted_MultipleAwaits");
|
||||
var fixedData = await LoadTestDataAsync("WhenSyncIsOmitted_MultipleAwaits_Fixed");
|
||||
|
||||
await VerifyCS.VerifyCodeFixAsync(testData, expectedDiagnostic, fixedData);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task WhenSyncIsNotLastTargetParameter_ItShouldReportDiagnosticAndApplyCodeFix()
|
||||
{
|
||||
var expectedDiagnostic = VerifyCS.Diagnostic("SYNC001")
|
||||
.WithLocation(0)
|
||||
.WithArguments("GetFrobCoreAsync", "sync", "GetDataAsync");
|
||||
|
||||
var testData = await LoadTestDataAsync("WhenSyncIsNotLastTargetParameter");
|
||||
var fixedData = await LoadTestDataAsync("WhenSyncIsNotLastTargetParameter_Fixed");
|
||||
|
||||
await VerifyCS.VerifyCodeFixAsync(testData, expectedDiagnostic, fixedData);
|
||||
}
|
||||
|
||||
private static async Task<string> LoadTestDataAsync(string name)
|
||||
{
|
||||
var path = Path.Combine(AppContext.BaseDirectory, "TestData", $"{name}.cs");
|
||||
var text = await File.ReadAllTextAsync(path);
|
||||
return text;
|
||||
}
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
public interface IDataService
|
||||
{
|
||||
Task<string> GetCoreAsync(int id, bool sync = false);
|
||||
}
|
||||
|
||||
public class BusinessLogic
|
||||
{
|
||||
private readonly IDataService _dataService;
|
||||
public BusinessLogic(IDataService dataService) { _dataService = dataService; }
|
||||
|
||||
private async Task<string> GetFrobCoreAsync()
|
||||
{
|
||||
return await _dataService.GetCoreAsync(17);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
public interface IDataService
|
||||
{
|
||||
Task<string> GetCoreAsync(int id, bool sync = false);
|
||||
}
|
||||
|
||||
public class BusinessLogic
|
||||
{
|
||||
private readonly IDataService _dataService;
|
||||
public BusinessLogic(IDataService dataService) { _dataService = dataService; }
|
||||
|
||||
private async Task<string> GetFrobCoreAsync(bool sync)
|
||||
{
|
||||
return await {|#0:_dataService.GetCoreAsync(17, false)|};
|
||||
}
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
public interface IDataService
|
||||
{
|
||||
Task<string> GetCoreAsync(int id, bool sync = false);
|
||||
}
|
||||
|
||||
public class BusinessLogic
|
||||
{
|
||||
private readonly IDataService _dataService;
|
||||
public BusinessLogic(IDataService dataService) { _dataService = dataService; }
|
||||
|
||||
private async Task<string> GetFrobCoreAsync(bool sync)
|
||||
{
|
||||
return await _dataService.GetCoreAsync(17, sync);
|
||||
}
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
public interface IDataService
|
||||
{
|
||||
Task<string> GetDataAsync(int id, bool sync = false, bool useCache = true);
|
||||
}
|
||||
|
||||
public class BusinessLogic
|
||||
{
|
||||
private readonly IDataService _dataService;
|
||||
public BusinessLogic(IDataService dataService) { _dataService = dataService; }
|
||||
|
||||
private async Task<string> GetFrobCoreAsync(bool sync)
|
||||
{
|
||||
return await {|#0:_dataService.GetDataAsync(17)|};
|
||||
}
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
public interface IDataService
|
||||
{
|
||||
Task<string> GetDataAsync(int id, bool sync = false, bool useCache = true);
|
||||
}
|
||||
|
||||
public class BusinessLogic
|
||||
{
|
||||
private readonly IDataService _dataService;
|
||||
public BusinessLogic(IDataService dataService) { _dataService = dataService; }
|
||||
|
||||
private async Task<string> GetFrobCoreAsync(bool sync)
|
||||
{
|
||||
return await _dataService.GetDataAsync(17, sync);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
public interface IDataService
|
||||
{
|
||||
Task<string> GetCoreAsync(int id, bool sync = false);
|
||||
}
|
||||
|
||||
public class BusinessLogic
|
||||
{
|
||||
private readonly IDataService _dataService;
|
||||
public BusinessLogic(IDataService dataService) { _dataService = dataService; }
|
||||
|
||||
private async Task<string> GetFrobCoreAsync(bool sync)
|
||||
{
|
||||
return await {|#0:_dataService.GetCoreAsync(17)|};
|
||||
}
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
public interface IDataService
|
||||
{
|
||||
Task<string> GetMoreDataAsync(bool runSynchronously = false);
|
||||
}
|
||||
|
||||
public class BusinessLogic
|
||||
{
|
||||
private readonly IDataService _dataService;
|
||||
public BusinessLogic(IDataService dataService) { _dataService = dataService; }
|
||||
|
||||
private async Task<string> GetFrobCoreAsync(bool runSynchronously)
|
||||
{
|
||||
return await {|#0:_dataService.GetMoreDataAsync()|};
|
||||
}
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
public interface IDataService
|
||||
{
|
||||
Task<string> GetMoreDataAsync(bool runSynchronously = false);
|
||||
}
|
||||
|
||||
public class BusinessLogic
|
||||
{
|
||||
private readonly IDataService _dataService;
|
||||
public BusinessLogic(IDataService dataService) { _dataService = dataService; }
|
||||
|
||||
private async Task<string> GetFrobCoreAsync(bool runSynchronously)
|
||||
{
|
||||
return await _dataService.GetMoreDataAsync(runSynchronously);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
public interface IDataService
|
||||
{
|
||||
Task<string> GetCoreAsync(int id, bool sync = false);
|
||||
}
|
||||
|
||||
public class BusinessLogic
|
||||
{
|
||||
private readonly IDataService _dataService;
|
||||
public BusinessLogic(IDataService dataService) { _dataService = dataService; }
|
||||
|
||||
private async Task<string> GetFrobCoreAsync(bool sync)
|
||||
{
|
||||
return await _dataService.GetCoreAsync(17, sync);
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
public interface IDataService
|
||||
{
|
||||
Task<string> GetDataAsync(bool sync = false);
|
||||
Task<string> GetMoreDataAsync(bool sync = false);
|
||||
}
|
||||
|
||||
public class BusinessLogic
|
||||
{
|
||||
private readonly IDataService _dataService;
|
||||
public BusinessLogic(IDataService dataService) { _dataService = dataService; }
|
||||
|
||||
private async Task<string> GetFrobCoreAsync(bool sync)
|
||||
{
|
||||
var a = await _dataService.GetDataAsync(sync);
|
||||
var b = await {|#0:_dataService.GetMoreDataAsync()|};
|
||||
return a + b;
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
public interface IDataService
|
||||
{
|
||||
Task<string> GetDataAsync(bool sync = false);
|
||||
Task<string> GetMoreDataAsync(bool sync = false);
|
||||
}
|
||||
|
||||
public class BusinessLogic
|
||||
{
|
||||
private readonly IDataService _dataService;
|
||||
public BusinessLogic(IDataService dataService) { _dataService = dataService; }
|
||||
|
||||
private async Task<string> GetFrobCoreAsync(bool sync)
|
||||
{
|
||||
var a = await _dataService.GetDataAsync(sync);
|
||||
var b = await _dataService.GetMoreDataAsync(sync);
|
||||
return a + b;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
public interface IDataService
|
||||
{
|
||||
Task<string> GetCoreAsync(int id, bool sync = false);
|
||||
}
|
||||
|
||||
public class BusinessLogic
|
||||
{
|
||||
private readonly IDataService _dataService;
|
||||
public BusinessLogic(IDataService dataService) { _dataService = dataService; }
|
||||
|
||||
private async Task<string> GetFrobCoreAsync(bool sync)
|
||||
{
|
||||
return await _dataService.GetCoreAsync(id: 17, sync: sync);
|
||||
}
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
public interface IDataService
|
||||
{
|
||||
Task<string> GetCoreAsync(int id, bool sync = false);
|
||||
}
|
||||
|
||||
public class BusinessLogic
|
||||
{
|
||||
private readonly IDataService _dataService;
|
||||
public BusinessLogic(IDataService dataService) { _dataService = dataService; }
|
||||
|
||||
private async Task<string> GetFrobCoreAsync(bool sync)
|
||||
{
|
||||
return await {|#0:_dataService.GetCoreAsync(17, sync: false)|};
|
||||
}
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
public interface IDataService
|
||||
{
|
||||
Task<string> GetCoreAsync(int id, bool sync = false);
|
||||
}
|
||||
|
||||
public class BusinessLogic
|
||||
{
|
||||
private readonly IDataService _dataService;
|
||||
public BusinessLogic(IDataService dataService) { _dataService = dataService; }
|
||||
|
||||
private async Task<string> GetFrobCoreAsync(bool sync)
|
||||
{
|
||||
return await _dataService.GetCoreAsync(17, sync: sync);
|
||||
}
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
public interface IDataService
|
||||
{
|
||||
Task<string> GetCoreAsync(int id, bool sync = false);
|
||||
}
|
||||
|
||||
public class BusinessLogic
|
||||
{
|
||||
private readonly IDataService _dataService;
|
||||
public BusinessLogic(IDataService dataService) { _dataService = dataService; }
|
||||
|
||||
private async Task<string> GetFrobCoreAsync(bool sync)
|
||||
{
|
||||
return await _dataService.GetCoreAsync(17, sync);
|
||||
}
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
public interface IDataService
|
||||
{
|
||||
Task<string> GetCoreAsync(int id, bool sync = false);
|
||||
}
|
||||
|
||||
public class BusinessLogic
|
||||
{
|
||||
private readonly IDataService _dataService;
|
||||
public BusinessLogic(IDataService dataService) { _dataService = dataService; }
|
||||
|
||||
private async Task<string> GetFrobCoreAsync(bool sync)
|
||||
{
|
||||
return await _dataService.GetCoreAsync(17, sync);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
public class BusinessLogic
|
||||
{
|
||||
private async Task<string> GetFrobCoreAsync(bool sync)
|
||||
{
|
||||
await Task.Delay(100);
|
||||
return "done";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user