Skip to content

Commit 4529ef7

Browse files
committed
Analyzer emits an error if UseDelegateFromConstructorAttribute is placed on a method with generics.
1 parent ce36501 commit 4529ef7

File tree

9 files changed

+103
-20
lines changed

9 files changed

+103
-20
lines changed

Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
<PropertyGroup>
44
<Copyright>(c) $([System.DateTime]::Now.Year), Pawel Gerr. All rights reserved.</Copyright>
5-
<VersionPrefix>8.5.0</VersionPrefix>
5+
<VersionPrefix>8.5.1</VersionPrefix>
66
<Authors>Pawel Gerr</Authors>
77
<GenerateDocumentationFile>true</GenerateDocumentationFile>
88
<PackageProjectUrl>https://github.com/PawelGerr/Thinktecture.Runtime.Extensions</PackageProjectUrl>

src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/Constants.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,11 @@ public static class SmartEnum
7474
public const string KEYLESS_FULL_NAME = "Thinktecture.SmartEnumAttribute";
7575
}
7676

77+
public static class UseDelegateFromConstructorAttribute
78+
{
79+
public const string NAME = "UseDelegateFromConstructorAttribute";
80+
}
81+
7782
public static class Union
7883
{
7984
public const string NAMESPACE = "Thinktecture";

src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/Diagnostics/ThinktectureRuntimeExtensionsAnalyzer.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,8 @@ public sealed class ThinktectureRuntimeExtensionsAnalyzer : DiagnosticAnalyzer
4848
DiagnosticsDescriptors.ComplexValueObjectWithStringMembersNeedsDefaultEqualityComparer,
4949
DiagnosticsDescriptors.ExplicitComparerWithoutEqualityComparer,
5050
DiagnosticsDescriptors.ExplicitEqualityComparerWithoutComparer,
51-
DiagnosticsDescriptors.MethodWithUseDelegateFromConstructorMustBePartial
51+
DiagnosticsDescriptors.MethodWithUseDelegateFromConstructorMustBePartial,
52+
DiagnosticsDescriptors.MethodWithUseDelegateFromConstructorMustNotHaveGenerics
5253
];
5354

5455
/// <inheritdoc />
@@ -96,6 +97,15 @@ private static void AnalyzeMethodWithUseDelegateFromConstructor(OperationAnalysi
9697
mds.Identifier.GetLocation(),
9798
method.Name);
9899
}
100+
101+
if (!method.TypeParameters.IsDefaultOrEmpty)
102+
{
103+
ReportDiagnostic(
104+
context,
105+
DiagnosticsDescriptors.MethodWithUseDelegateFromConstructorMustNotHaveGenerics,
106+
mds.Identifier.GetLocation(),
107+
method.Name);
108+
}
99109
}
100110
catch (Exception ex)
101111
{

src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/DiagnosticsDescriptors.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ internal static class DiagnosticsDescriptors
3131
public static readonly DiagnosticDescriptor VariableMustBeInitializedWithNonDefaultValue = new("TTRESG047", "Variable must be initialed with non-default value", "Variable of type '{0}' must be initialized with non default value", nameof(ThinktectureRuntimeExtensionsAnalyzer), DiagnosticSeverity.Error, true);
3232
public static readonly DiagnosticDescriptor StringBasedValueObjectNeedsEqualityComparer = new("TTRESG048", "String-based Value Object needs equality comparer", "String-based Value Object needs equality comparer. Use ValueObjectKeyMemberEqualityComparerAttribute<TAccessor, TKey> to defined the equality comparer. Example: [ValueObjectKeyMemberEqualityComparer<ComparerAccessors.StringOrdinalIgnoreCase, string>].", nameof(ThinktectureRuntimeExtensionsAnalyzer), DiagnosticSeverity.Warning, true);
3333
public static readonly DiagnosticDescriptor ComplexValueObjectWithStringMembersNeedsDefaultEqualityComparer = new("TTRESG049", "Complex Value Object with string members needs equality comparer", "Complex Value Object with string members needs equality comparer. Use ValueObjectKeyMemberEqualityComparerAttribute<TAccessor, TKey> to defined the equality comparer. Example: [ValueObjectKeyMemberEqualityComparer<ComparerAccessors.StringOrdinalIgnoreCase, string>].", nameof(ThinktectureRuntimeExtensionsAnalyzer), DiagnosticSeverity.Warning, true);
34-
public static readonly DiagnosticDescriptor MethodWithUseDelegateFromConstructorMustBePartial = new("TTRESG050", "Method with 'UseDelegateFromConstructorAttribute' must be partial", "The method '{0}' with 'UseDelegateFromConstructorAttribute' must be marked as partial", nameof(ThinktectureRuntimeExtensionsAnalyzer), DiagnosticSeverity.Error, true);
34+
public static readonly DiagnosticDescriptor MethodWithUseDelegateFromConstructorMustBePartial = new("TTRESG050", $"Method with '{Constants.Attributes.UseDelegateFromConstructorAttribute.NAME}' must be partial", $"The method '{{0}}' with '{Constants.Attributes.UseDelegateFromConstructorAttribute.NAME}' must be marked as partial", nameof(ThinktectureRuntimeExtensionsAnalyzer), DiagnosticSeverity.Error, true);
35+
public static readonly DiagnosticDescriptor MethodWithUseDelegateFromConstructorMustNotHaveGenerics = new("TTRESG051", $"Method with '{Constants.Attributes.UseDelegateFromConstructorAttribute.NAME}' must not have generics", $"The method '{{0}}' with '{Constants.Attributes.UseDelegateFromConstructorAttribute.NAME}' must not have generic type parameters. Use inheritance approach instead.", nameof(ThinktectureRuntimeExtensionsAnalyzer), DiagnosticSeverity.Error, true);
3536

3637
public static readonly DiagnosticDescriptor ErrorDuringCodeAnalysis = new("TTRESG098", "Error during code analysis", "Error during code analysis of '{0}': '{1}'", nameof(ThinktectureRuntimeExtensionsAnalyzer), DiagnosticSeverity.Warning, true);
3738
public static readonly DiagnosticDescriptor ErrorDuringGeneration = new("TTRESG099", "Error during code generation", "Error during code generation for '{0}': '{1}'", nameof(ThinktectureRuntimeExtensionsAnalyzer), DiagnosticSeverity.Error, true);

src/Thinktecture.Runtime.Extensions.SourceGenerator/Extensions/TypeSymbolExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ public static bool IsUseDelegateFromConstructorAttribute(this ITypeSymbol? attri
146146
if (attributeType is null || attributeType.TypeKind == TypeKind.Error)
147147
return false;
148148

149-
return attributeType is { Name: "UseDelegateFromConstructorAttribute", ContainingNamespace: { Name: "Thinktecture", ContainingNamespace.IsGlobalNamespace: true } };
149+
return attributeType is { Name: Constants.Attributes.UseDelegateFromConstructorAttribute.NAME, ContainingNamespace: { Name: "Thinktecture", ContainingNamespace.IsGlobalNamespace: true } };
150150
}
151151

152152
public static bool IsEnum(
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
using System.Threading.Tasks;
2+
using Verifier = Thinktecture.Runtime.Tests.Verifiers.CodeFixVerifier<Thinktecture.CodeAnalysis.Diagnostics.ThinktectureRuntimeExtensionsAnalyzer, Thinktecture.CodeAnalysis.CodeFixes.ThinktectureRuntimeExtensionsCodeFixProvider>;
3+
4+
namespace Thinktecture.Runtime.Tests.AnalyzerAndCodeFixTests;
5+
6+
// ReSharper disable InconsistentNaming
7+
public class TTRESG051_MethodWithUseDelegateFromConstructorMustNotHaveGenerics
8+
{
9+
private const string _DIAGNOSTIC_ID = "TTRESG051";
10+
11+
[Fact]
12+
public async Task Should_trigger_on_method_with_generics()
13+
{
14+
var code = """
15+
using System;
16+
using Thinktecture;
17+
18+
namespace TestNamespace
19+
{
20+
[SmartEnum<int>]
21+
public partial class TestEnum
22+
{
23+
public static readonly TestEnum Item1 = default;
24+
25+
[UseDelegateFromConstructor]
26+
public partial string {|#0:Process|}<T>(T input);
27+
}
28+
29+
public partial class TestEnum
30+
{
31+
public partial string Process<T>(T input) => "";
32+
}
33+
}
34+
""";
35+
36+
var expected = Verifier.Diagnostic(_DIAGNOSTIC_ID).WithLocation(0).WithArguments("Process");
37+
await Verifier.VerifyAnalyzerAsync(code, [typeof(UseDelegateFromConstructorAttribute).Assembly], expected);
38+
}
39+
40+
[Fact]
41+
public async Task Should_not_trigger_on_method_without_generics()
42+
{
43+
var code = """
44+
using System;
45+
using Thinktecture;
46+
47+
namespace TestNamespace
48+
{
49+
[SmartEnum<int>]
50+
public partial class TestEnum
51+
{
52+
public static readonly TestEnum Item1 = default;
53+
54+
[UseDelegateFromConstructor]
55+
public partial string Process(int input);
56+
}
57+
58+
public partial class TestEnum
59+
{
60+
public partial string Process(int input) => "";
61+
}
62+
}
63+
""";
64+
65+
await Verifier.VerifyAnalyzerAsync(code, [typeof(UseDelegateFromConstructorAttribute).Assembly]);
66+
}
67+
}

test/Thinktecture.Runtime.Extensions.Tests.Shared/TestEnums/TestEnumWithDelegateGeneration.cs renamed to test/Thinktecture.Runtime.Extensions.Tests.Shared/TestEnums/TestEnumWithUseDelegateFromConstructor.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
namespace Thinktecture.Runtime.Tests.TestEnums;
22

33
[SmartEnum<int>]
4-
public partial class TestEnumWithDelegateGeneration
4+
public partial class TestEnumWithUseDelegateFromConstructor
55
{
6-
public static readonly TestEnumWithDelegateGeneration Item1 = new(1, i => $"{i} + 1", i => {}, s => {}, b => {}, Mixed1);
6+
public static readonly TestEnumWithUseDelegateFromConstructor Item1 = new(1, i => $"{i} + 1", i => {}, s => {}, b => {}, Mixed1);
77

8-
public static readonly TestEnumWithDelegateGeneration Item2 = new(2, i => $"{i} + 2", i => {}, s => {}, b => {}, Mixed2);
8+
public static readonly TestEnumWithUseDelegateFromConstructor Item2 = new(2, i => $"{i} + 2", i => {}, s => {}, b => {}, Mixed2);
99

1010
[UseDelegateFromConstructor]
1111
public partial string FooFunc(int bar);

test/Thinktecture.Runtime.Extensions.Tests/EnumTests/DelegateGeneration.cs

Lines changed: 0 additions & 13 deletions
This file was deleted.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using Thinktecture.Runtime.Tests.TestEnums;
2+
3+
namespace Thinktecture.Runtime.Tests.EnumTests;
4+
5+
public class UseDelegateFromConstructor
6+
{
7+
[Fact]
8+
public void Should_delegate_calls_to_provided_delegates()
9+
{
10+
TestEnumWithUseDelegateFromConstructor.Item1.FooFunc(42).Should().Be("42 + 1");
11+
TestEnumWithUseDelegateFromConstructor.Item2.FooFunc(43).Should().Be("43 + 2");
12+
}
13+
}

0 commit comments

Comments
 (0)