Skip to content

Commit d88313b

Browse files
authored
Add extension members for static extensions in generated enums (#154)
* Add initial support for checking for C#14 * Add support for extension enums * Add Integration tests
1 parent 8439485 commit d88313b

14 files changed

+2314
-20
lines changed

src/NetEscapades.EnumGenerators/EnumGenerator.cs

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
1616
context.RegisterPostInitializationOutput(ctx => ctx.AddSource(
1717
"EnumExtensionsAttribute.g.cs", SourceText.From(SourceGenerationHelper.Attribute, Encoding.UTF8)));
1818

19+
var csharp14IsSupported = context.CompilationProvider
20+
.Select((x,_) => x is CSharpCompilation
21+
{
22+
LanguageVersion: LanguageVersion.Preview or >= (LanguageVersion)1400 // C#14
23+
});
24+
1925
IncrementalValuesProvider<EnumToGenerate> enumsToGenerate = context.SyntaxProvider
2026
.ForAttributeWithMetadataName(Attributes.EnumExtensionsAttribute,
2127
predicate: static (node, _) => node is EnumDeclarationSyntax,
@@ -34,16 +40,16 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
3440
.SelectMany(static (m, _) => m!.Value)
3541
.WithTrackingName(TrackingNames.InitialExternalExtraction);
3642

37-
context.RegisterSourceOutput(enumsToGenerate,
38-
static (spc, enumToGenerate) => Execute(in enumToGenerate, spc));
43+
context.RegisterSourceOutput(enumsToGenerate.Combine(csharp14IsSupported),
44+
static (spc, enumToGenerate) => Execute(in enumToGenerate.Left, enumToGenerate.Right, spc));
3945

40-
context.RegisterSourceOutput(externalEnums,
41-
static (spc, enumToGenerate) => Execute(in enumToGenerate, spc));
46+
context.RegisterSourceOutput(externalEnums.Combine(csharp14IsSupported),
47+
static (spc, enumToGenerate) => Execute(in enumToGenerate.Left, enumToGenerate.Right, spc));
4248
}
4349

44-
static void Execute(in EnumToGenerate enumToGenerate, SourceProductionContext context)
50+
static void Execute(in EnumToGenerate enumToGenerate, bool csharp14IsSupported, SourceProductionContext context)
4551
{
46-
var (result, filename) = SourceGenerationHelper.GenerateExtensionClass(in enumToGenerate);
52+
var (result, filename) = SourceGenerationHelper.GenerateExtensionClass(in enumToGenerate, csharp14IsSupported);
4753
context.AddSource(filename, SourceText.From(result, Encoding.UTF8));
4854
}
4955

src/NetEscapades.EnumGenerators/SourceGenerationHelper.cs

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ public class EnumExtensionsAttribute<T> : System.Attribute
101101
102102
""";
103103

104-
public static (string Content, string HintName) GenerateExtensionClass(in EnumToGenerate enumToGenerate)
104+
public static (string Content, string HintName) GenerateExtensionClass(in EnumToGenerate enumToGenerate, bool csharp14IsSupported)
105105
{
106106
var constantValues = new HashSet<object>();
107107

@@ -343,6 +343,22 @@ public static
343343
}
344344
""");
345345

346+
// Use c#14 style extension members so can be accessed from root type
347+
if (csharp14IsSupported)
348+
{
349+
sb.Append(
350+
"""
351+
352+
353+
// C#14 Extension member syntax
354+
extension(
355+
""").Append(fullyQualifiedName).Append(
356+
"""
357+
)
358+
{
359+
""");
360+
}
361+
346362
sb.Append(
347363
"""
348364
@@ -1456,6 +1472,21 @@ public static string[] GetNames()
14561472
14571473
};
14581474
}
1475+
""");
1476+
1477+
// Close the extension everything block
1478+
if (csharp14IsSupported)
1479+
{
1480+
sb.Append(
1481+
"""
1482+
1483+
}
1484+
""");
1485+
}
1486+
1487+
sb.Append(
1488+
"""
1489+
14591490
}
14601491
#pragma warning restore CS0612 // Ignore usages of obsolete members or enums
14611492
#pragma warning restore CS0618 // Ignore usages of obsolete members or enums

tests/Directory.Build.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
<!-- Attempt workaround for https://github.com/dotnet/sdk/issues/37636 -->
1313
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
14+
<LangVersion>Preview</LangVersion>
1415
</PropertyGroup>
1516
<ItemGroup>
1617
<!-- Ignore 2.1.0 advisories -->
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
using System;
2+
using Foo;
3+
using Xunit;
4+
5+
#if INTEGRATION_TESTS
6+
namespace NetEscapades.EnumGenerators.IntegrationTests;
7+
#elif NETSTANDARD_INTEGRATION_TESTS
8+
namespace NetEscapades.EnumGenerators.NetStandard.IntegrationTests;
9+
#elif INTERCEPTOR_TESTS
10+
namespace NetEscapades.EnumGenerators.Interceptors.IntegrationTests;
11+
#elif NUGET_ATTRS_INTEGRATION_TESTS
12+
namespace NetEscapades.EnumGenerators.Nuget.Attributes.IntegrationTests;
13+
#elif NUGET_INTEGRATION_TESTS
14+
namespace NetEscapades.EnumGenerators.Nuget.IntegrationTests;
15+
#elif NUGET_INTERCEPTOR_TESTS
16+
namespace NetEscapades.EnumGenerators.Nuget.Interceptors.IntegrationTests;
17+
#else
18+
#error Unknown integration tests
19+
#endif
20+
21+
public class EnumInFooExtensionEverythingTests : EnumInFooExtensionsTests
22+
{
23+
protected override string[] GetNames() => EnumInFoo.GetNames();
24+
protected override EnumInFoo[] GetValues() => EnumInFoo.GetValues();
25+
protected override int[] GetValuesAsUnderlyingType() => EnumInFoo.GetValuesAsUnderlyingType();
26+
protected override int AsUnderlyingValue(EnumInFoo value) => value.AsUnderlyingType();
27+
28+
protected override string ToStringFast(EnumInFoo value) => value.ToStringFast();
29+
protected override string ToStringFast(EnumInFoo value, bool withMetadata) => value.ToStringFast(withMetadata);
30+
protected override bool IsDefined(EnumInFoo value) => EnumInFoo.IsDefined(value);
31+
protected override bool IsDefined(string name, bool allowMatchingMetadataAttribute) => EnumInFoo.IsDefined(name, allowMatchingMetadataAttribute);
32+
#if READONLYSPAN
33+
protected override bool IsDefined(in ReadOnlySpan<char> name, bool allowMatchingMetadataAttribute) => EnumInFoo.IsDefined(name, allowMatchingMetadataAttribute);
34+
#endif
35+
protected override bool TryParse(string name, out EnumInFoo parsed, bool ignoreCase, bool allowMatchingMetadataAttribute)
36+
=> EnumInFoo.TryParse(name, out parsed, ignoreCase);
37+
#if READONLYSPAN
38+
protected override bool TryParse(in ReadOnlySpan<char> name, out EnumInFoo parsed, bool ignoreCase, bool allowMatchingMetadataAttribute)
39+
=> EnumInFoo.TryParse(name, out parsed, ignoreCase);
40+
#endif
41+
42+
protected override EnumInFoo Parse(string name, bool ignoreCase, bool allowMatchingMetadataAttribute)
43+
=> EnumInFoo.Parse(name, ignoreCase);
44+
#if READONLYSPAN
45+
protected override EnumInFoo Parse(in ReadOnlySpan<char> name, bool ignoreCase, bool allowMatchingMetadataAttribute)
46+
=> EnumInFoo.Parse(name, ignoreCase);
47+
#endif
48+
}

tests/NetEscapades.EnumGenerators.Tests/EnumGeneratorTests.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System.Linq;
22
using System.Threading.Tasks;
33
using Microsoft.CodeAnalysis;
4+
using Microsoft.CodeAnalysis.CSharp;
45
using NetEscapades.EnumGenerators.Interceptors;
56
using VerifyTests;
67
using VerifyXunit;
@@ -55,6 +56,26 @@ public enum MyEnum
5556
return Verifier.Verify(output, Settings());
5657
}
5758

59+
[Fact]
60+
public Task CanGenerateEnumExtensionsInGlobalNamespace_CSharp14()
61+
{
62+
const string input =
63+
"""
64+
using NetEscapades.EnumGenerators;
65+
66+
[EnumExtensions]
67+
public enum MyEnum
68+
{
69+
First,
70+
Second,
71+
}
72+
""";
73+
var (diagnostics, output) = TestHelpers.GetGeneratedOutput(Generators(), new(LanguageVersion.Preview, options: null!, input));
74+
75+
Assert.Empty(diagnostics);
76+
return Verifier.Verify(output, Settings());
77+
}
78+
5879
[Fact]
5980
public Task CanGenerateEnumExtensionsInChildNamespace()
6081
{

tests/NetEscapades.EnumGenerators.Tests/NetEscapades.EnumGenerators.Tests.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
</PropertyGroup>
99

1010
<ItemGroup>
11-
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.11.0" PrivateAssets="all" />
11+
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.14.0" PrivateAssets="all" />
1212
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
1313
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" Condition="'$(TargetFramework)' == 'net48'" />
1414
<PackageReference Include="Verify.Xunit" Version="14.3.0" />

0 commit comments

Comments
 (0)