Skip to content

Commit

Permalink
New analyzers: Dictionary keys & async void + NUnit (#278)
Browse files Browse the repository at this point in the history
* Start of dictionary key analyzer

* Tweaks and fixes

* C# 12

* Enable nullable

* Types test and ability to better customize compilation on tests

* ISet is in a different assembly in net4x

* Switch to NUnit to match every other repo

* And then add NUnit analyzers and update to 4.0.1

* Change title to be more exact about what we're analyzing

* Fix duplicated diagnostic id by moving StructuredLoggingWithRepeatedToken to 26 since Core already has a PR with pragmas for PS0025

* Add async void analyzer

* Tweaks

* Extended the message a bit

* Update src/Particular.Analyzers/DiagnosticDescriptors.cs

Co-authored-by: Brandon Ording <bording@gmail.com>

---------

Co-authored-by: Brandon Ording <bording@gmail.com>
  • Loading branch information
DavidBoike and bording committed Mar 12, 2024
1 parent 7f80568 commit 9e46118
Show file tree
Hide file tree
Showing 45 changed files with 971 additions and 494 deletions.
57 changes: 57 additions & 0 deletions src/Particular.Analyzers.Tests/AsyncVoidAnalyzerTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
namespace Particular.Analyzers.Tests
{
using System.Threading.Tasks;
using NUnit.Framework;
using Particular.Analyzers.Tests.Helpers;

public class AsyncVoidAnalyzerTests : AnalyzerTestFixture<AsyncVoidAnalyzer>
{
[Test]
public Task NoAsyncVoid()
{
var code = """
public class Foo
{
async void [|Bad1|]() { await Task.Delay(1); }
private async void [|Bad2|]() { await Task.Delay(1); }
public async void [|Bad3|]() { await Task.Delay(1); }
protected async void [|Bad4|]() { await Task.Delay(1); }
static async void [|Bad5|]() { await Task.Delay(1); }
static private async void [|Bad6|]() { await Task.Delay(1); }
static public async void [|Bad7|]() { await Task.Delay(1); }
static protected async void [|Bad8|]() { await Task.Delay(1); }
async void [|Bad9|](string a, int b) { await Task.Delay(1); }

async Task ReturnsTask1() { await Task.Delay(1); }
private async Task ReturnsTask2() { await Task.Delay(1); }
public async Task ReturnsTask3() { await Task.Delay(1); }
protected async Task ReturnsTask4() { await Task.Delay(1); }
static async Task ReturnsTask5() { await Task.Delay(1); }
static private async Task ReturnsTask6() { await Task.Delay(1); }
static public async Task ReturnsTask7() { await Task.Delay(1); }
static protected async Task ReturnsTask8() { await Task.Delay(1); }
async Task ReturnsTask9(string a, int b) { await Task.Delay(1); }

async Task ReturnsValueTask1() { await Task.Delay(1); }
private async Task ReturnsValueTask2() { await Task.Delay(1); }
public async Task ReturnsValueTask3() { await Task.Delay(1); }
protected async Task ReturnsValueTask4() { await Task.Delay(1); }
static async Task ReturnsValueTask5() { await Task.Delay(1); }
static private async Task ReturnsValueTask6() { await Task.Delay(1); }
static public async Task ReturnsValueTask7() { await Task.Delay(1); }
static protected async Task ReturnsValueTask8() { await Task.Delay(1); }
async Task ReturnsValueTask9(string a, int b) { await Task.Delay(1); }

public async Task RegularMethod()
{
async void [|Local|]() => await Task.Delay(1);

await Task.Delay(1);
}
}
""";

return Assert(code, DiagnosticIds.AsyncVoid);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,13 @@
{
using System.Linq;
using System.Threading.Tasks;
using NUnit.Framework;
using Particular.Analyzers.Cancellation;
using Particular.Analyzers.Tests.Helpers;
using Xunit;
using Xunit.Abstractions;
using Data = System.Collections.Generic.IEnumerable<object[]>;

public class CancellationTryCatchAnalyzerTests : AnalyzerTestFixture<CancellationTryCatchAnalyzer>
{
public CancellationTryCatchAnalyzerTests(ITestOutputHelper output) : base(output) { }

public static readonly Data AllCatchBlocks;
public static readonly Data CatchBlocksWithoutIdentifiers;

Expand Down Expand Up @@ -65,8 +62,8 @@ static CancellationTryCatchAnalyzerTests()
CatchBlocksWithoutIdentifiers = catchBlocksWithoutIdentifiers.ToData();
}

[Theory]
[MemberData(nameof(AllCatchBlocks))]
[Test]
[TestCaseSource(nameof(AllCatchBlocks))]
public Task PassingSimpleToken(string expectedDiagnostic, string catchBlocks)
{
const string PassesSimpleTokenTemplate =
Expand All @@ -88,8 +85,8 @@ public async Task Bar(CancellationToken cancellationToken)
return Assert(GetCode(PassesSimpleTokenTemplate, catchBlocks, "cancellationToken"), expectedDiagnostic);
}

[Theory]
[MemberData(nameof(AllCatchBlocks))]
[Test]
[TestCaseSource(nameof(AllCatchBlocks))]
public Task PassingTokenProperty(string expectedDiagnostic, string catchBlocks)
{
const string PassesTokenPropertyTemplate =
Expand All @@ -115,8 +112,8 @@ public class SomeContext
return Assert(GetCode(PassesTokenPropertyTemplate, catchBlocks, "context.Token"), expectedDiagnostic);
}

[Theory]
[MemberData(nameof(CatchBlocksWithoutIdentifiers))]
[Test]
[TestCaseSource(nameof(CatchBlocksWithoutIdentifiers))]
public Task MethodThatGeneratesToken(string expectedDiagnostic, string catchBlocks)
{
const string PassesTokenPropertyTemplate =
Expand All @@ -139,8 +136,8 @@ public async Task Bar()
return Assert(GetCode(PassesTokenPropertyTemplate, catchBlocks, null), expectedDiagnostic);
}

[Theory]
[MemberData(nameof(CatchBlocksWithoutIdentifiers))]
[Test]
[TestCaseSource(nameof(CatchBlocksWithoutIdentifiers))]
public Task FuncThatGeneratesToken(string expectedDiagnostic, string catchBlocks)
{
const string PassesTokenPropertyTemplate =
Expand All @@ -164,8 +161,8 @@ public async Task Bar()
return Assert(GetCode(PassesTokenPropertyTemplate, catchBlocks, null), expectedDiagnostic);
}

[Theory]
[MemberData(nameof(AllCatchBlocks))]
[Test]
[TestCaseSource(nameof(AllCatchBlocks))]
public Task ThrowIfCancellationRequested(string expectedDiagnostic, string catchBlocks)
{
const string PassesSimpleTokenTemplate =
Expand All @@ -187,8 +184,8 @@ public async Task Bar(CancellationToken cancellationToken)
return Assert(GetCode(PassesSimpleTokenTemplate, catchBlocks, "cancellationToken"), expectedDiagnostic);
}

[Theory]
[MemberData(nameof(AllCatchBlocks))]
[Test]
[TestCaseSource(nameof(AllCatchBlocks))]
public Task PassesCancellableContext(string expectedDiagnostic, string catchBlocks)
{
const string PassesCancellableContextTemplate =
Expand All @@ -215,8 +212,8 @@ class SomeContext : ICancellableContext { public CancellationToken CancellationT
return Assert(GetCode(PassesCancellableContextTemplate, catchBlocks, "context.CancellationToken"), expectedDiagnostic);
}

[Theory]
[MemberData(nameof(AllCatchBlocks))]
[Test]
[TestCaseSource(nameof(AllCatchBlocks))]
public Task PassesTokenFromTokenSource(string expectedDiagnostic, string catchBlocks)
{
const string PassesCancellableContextTemplate =
Expand All @@ -242,8 +239,8 @@ public async Task Bar()
return Assert(GetCode(PassesCancellableContextTemplate, catchBlocks, "tokenSource.Token"), expectedDiagnostic);
}

[Theory]
[MemberData(nameof(CatchBlocksWithoutIdentifiers))]
[Test]
[TestCaseSource(nameof(CatchBlocksWithoutIdentifiers))]
public Task PassesNoTokenOrEmptyToken(string expectedDiagnostic, string catchBlocks)
{
const string PassesNoTokenOrEmptyTokenTemplate =
Expand Down Expand Up @@ -274,7 +271,7 @@ async Task Bar(CancellationToken token)
return Assert(GetCode(PassesNoTokenOrEmptyTokenTemplate, noDiagnosticCatchBlocks, null), expectedDiagnostic);
}

[Fact]
[Test]
public Task NoWarnWhenSingleTokenUsedMultipleTimesInTry()
{
const string UsesSameTokenMultipleTimes =
Expand Down Expand Up @@ -309,10 +306,10 @@ async Task Bar(CancellationToken token)
return Assert(UsesSameTokenMultipleTimes);
}

[Theory]
[InlineData("newToken")]
[InlineData("tokenSource.Token")]
[InlineData("context.Token")]
[Test]
[TestCase("newToken")]
[TestCase("tokenSource.Token")]
[TestCase("context.Token")]
public Task WarnWhenMultipleTokenUsedInTry(string tokenExpression)
{
const string UsesMultipleTokenTemplate =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@
{
using System.Collections.Generic;
using System.Threading.Tasks;
using NUnit.Framework;
using Particular.Analyzers.Cancellation;
using Particular.Analyzers.Tests.Helpers;
using Xunit;
using Xunit.Abstractions;
using Data = System.Collections.Generic.IEnumerable<object[]>;

public class ContextMethodParameterAnalyzerTests : AnalyzerTestFixture<ContextMethodParameterAnalyzer>
Expand Down Expand Up @@ -71,8 +70,6 @@ void IMyInterface.MyMethod({0}) {{ }}
static void MyMethod({0}) {{ }}
}}";

public ContextMethodParameterAnalyzerTests(ITestOutputHelper output) : base(output) { }

public static readonly Data SadParams = new List<string>
{
"CancellationToken [|foo|]",
Expand All @@ -85,62 +82,62 @@ public ContextMethodParameterAnalyzerTests(ITestOutputHelper output) : base(outp
"object foo",
}.ToData();

[Theory]
[MemberData(nameof(SadParams))]
[Test]
[TestCaseSource(nameof(SadParams))]
public Task SadClasses(string @params) => Assert(GetCode(@class, @params), DiagnosticIds.CancellableContextMethodCancellationToken);

[Theory]
[MemberData(nameof(HappyParams))]
[Test]
[TestCaseSource(nameof(HappyParams))]
public Task HappyClasses(string @params) => Assert(GetCode(@class, @params));

[Theory]
[MemberData(nameof(SadParams))]
[Test]
[TestCaseSource(nameof(SadParams))]
public Task SadInterfaces(string @params) => Assert(GetCode(@interface, @params), DiagnosticIds.CancellableContextMethodCancellationToken);

[Theory]
[MemberData(nameof(HappyParams))]
[Test]
[TestCaseSource(nameof(HappyParams))]
public Task HappyInterfaces(string @params) => Assert(GetCode(@interface, @params));

[Theory]
[MemberData(nameof(SadParams))]
[Test]
[TestCaseSource(nameof(SadParams))]
public Task SadRecords(string @params) => Assert(GetCode(@record, @params), DiagnosticIds.CancellableContextMethodCancellationToken);

[Theory]
[MemberData(nameof(HappyParams))]
[Test]
[TestCaseSource(nameof(HappyParams))]
public Task HappyRecords(string @params) => Assert(GetCode(@record, @params));

[Theory]
[MemberData(nameof(SadParams))]
[Test]
[TestCaseSource(nameof(SadParams))]
public Task SadStructs(string @params) => Assert(GetCode(@struct, @params), DiagnosticIds.CancellableContextMethodCancellationToken);

[Theory]
[MemberData(nameof(HappyParams))]
[Test]
[TestCaseSource(nameof(HappyParams))]
public Task HappyStructs(string @params) => Assert(GetCode(@struct, @params));

[Theory]
[MemberData(nameof(SadParams))]
[Test]
[TestCaseSource(nameof(SadParams))]
public Task SadOverrides(string parameters) => Assert(GetCode(@override, parameters), DiagnosticIds.CancellableContextMethodCancellationToken);

[Theory]
[MemberData(nameof(HappyParams))]
[Test]
[TestCaseSource(nameof(HappyParams))]
public Task HappyOverrides(string parameters) => Assert(GetCode(@override, parameters));

[Theory]
[MemberData(nameof(SadParams))]
[Test]
[TestCaseSource(nameof(SadParams))]
public Task SadExplicits(string parameters) => Assert(GetCode(@explicit, parameters), DiagnosticIds.CancellableContextMethodCancellationToken);

[Theory]
[MemberData(nameof(HappyParams))]
[Test]
[TestCaseSource(nameof(HappyParams))]
public Task HappyExplicits(string parameters) => Assert(GetCode(@explicit, parameters));

[Theory]
[MemberData(nameof(SadParams))]
[MemberData(nameof(HappyParams))]
[Test]
[TestCaseSource(nameof(SadParams))]
[TestCaseSource(nameof(HappyParams))]
public Task HappyConstructors(string @params) => Assert(GetCode(constructor, @params));

[Theory]
[MemberData(nameof(SadParams))]
[MemberData(nameof(HappyParams))]
[Test]
[TestCaseSource(nameof(SadParams))]
[TestCaseSource(nameof(HappyParams))]
public Task HappyStatics(string @params) => Assert(GetCode(@static, @params));

static string GetCode(string template, string @params) => string.Format(template, @params);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@
{
using System.Collections.Generic;
using System.Threading.Tasks;
using NUnit.Framework;
using Particular.Analyzers.Cancellation;
using Particular.Analyzers.Tests.Helpers;
using Xunit;
using Xunit.Abstractions;
using Data = System.Collections.Generic.IEnumerable<object[]>;

public class DelegateParametersAnalyzerTests : AnalyzerTestFixture<DelegateParametersAnalyzer>
Expand All @@ -16,8 +15,6 @@ public class DelegateParametersAnalyzerTests : AnalyzerTestFixture<DelegateParam
delegate void MyDelegate({0});
}}";

public DelegateParametersAnalyzerTests(ITestOutputHelper output) : base(output) { }

public static readonly Data SadParams = new List<string>
{
"CancellationToken [|cancellationToken|], object foo",
Expand All @@ -36,12 +33,12 @@ public DelegateParametersAnalyzerTests(ITestOutputHelper output) : base(output)
"CancellationToken cancellationToken1, CancellationToken cancellationToken2, params object[] foos",
}.ToData();

[Theory]
[MemberData(nameof(SadParams))]
[Test]
[TestCaseSource(nameof(SadParams))]
public Task Sad(string @params) => Assert(GetCode(@params), DiagnosticIds.DelegateCancellationTokenMisplaced);

[Theory]
[MemberData(nameof(HappyParams))]
[Test]
[TestCaseSource(nameof(HappyParams))]
public Task Happy(string @params) => Assert(GetCode(@params));

static string GetCode(string @params) => string.Format(@delegate, @params);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@
{
using System.Collections.Generic;
using System.Threading.Tasks;
using NUnit.Framework;
using Particular.Analyzers.Cancellation;
using Particular.Analyzers.Tests.Helpers;
using Xunit;
using Xunit.Abstractions;
using Data = System.Collections.Generic.IEnumerable<object[]>;

public class EmptyTokenAnalyzerTests : AnalyzerTestFixture<EmptyTokenAnalyzer>
Expand All @@ -16,8 +15,6 @@ public class EmptyTokenAnalyzerTests : AnalyzerTestFixture<EmptyTokenAnalyzer>
void MyMethod(CancellationToken cancellationToken) => Task.Delay(1, [|{0}|]);
}}";

public EmptyTokenAnalyzerTests(ITestOutputHelper output) : base(output) { }

public static readonly Data SadArgs = new List<(string, string)>
{
("default", DiagnosticIds.EmptyCancellationTokenDefaultLiteral),
Expand All @@ -26,12 +23,12 @@ public EmptyTokenAnalyzerTests(ITestOutputHelper output) : base(output) { }

public static readonly Data HappyArgs = new List<string> { "cancellationToken", "new CancellationToken()", "CancellationToken.None" }.ToData();

[Theory]
[MemberData(nameof(SadArgs))]
[Test]
[TestCaseSource(nameof(SadArgs))]
public Task Sad(string arg, string diagnosticId) => Assert(GetCode(arg), diagnosticId);

[Theory]
[MemberData(nameof(HappyArgs))]
[Test]
[TestCaseSource(nameof(HappyArgs))]
public Task Happy(string arg) => Assert(GetCode(arg));

static string GetCode(string arg) => string.Format(invoke, arg);
Expand Down
Loading

0 comments on commit 9e46118

Please sign in to comment.