├── global.json
├── icon.png
├── icon_32.png
├── .nuke
└── parameters.json
├── tests
├── NetEscapades.EnumGenerators.IntegrationTests
│ ├── xunit.runner.json
│ ├── .editorconfig
│ ├── EnumParseOptionsTests.cs
│ ├── SerializationOptionsTests.cs
│ ├── AnalyzerTests.cs
│ ├── NetEscapades.EnumGenerators.IntegrationTests.csproj
│ ├── EnumInFooExtensionEverythingTests.cs
│ ├── EnumInFooExtensionsTests.cs
│ ├── LongEnumExtensionsTests.cs
│ ├── EnumInNamespaceExtensionsTests.cs
│ ├── ExternalEnumExtensionsTests.cs
│ ├── EnumWithReservedKeywordsExtensionsTests.cs
│ ├── EnumWithSameDisplayNameExtensionsTests.cs
│ ├── EnumWithEnumMemberInNamespaceExtensionsTests.cs
│ ├── EnumWithDescriptionInNamespaceExtensionsTests.cs
│ ├── EnumWithDisplayNameInNamespaceExtensionsTests.cs
│ ├── EnumWithNoMetadataSourcesInNamespaceExtensionsTests.cs
│ ├── FlagsEnumExtensionsTests.cs
│ ├── ExternalFlagsEnumExtensionsTests.cs
│ └── Enums.cs
├── NetEscapades.EnumGenerators.Benchmarks
│ ├── Directory.Build.props
│ ├── NetEscapades.EnumGenerators.Benchmarks.csproj
│ └── EnumHelper.cs
├── NetEscapades.EnumGenerators.Interceptors.IntegrationTests
│ ├── xunit.runner.json
│ ├── Enums.cs
│ ├── NetEscapades.EnumGenerators.Interceptors.IntegrationTests.csproj
│ └── InterceptorTests.cs
├── NetEscapades.EnumGenerators.Tests
│ ├── NetEscapades.EnumGenerators.Tests.Roslyn4_04.csproj
│ ├── NetEscapades.EnumGenerators.Tests.Project.targets
│ ├── EquatableArrayTests.cs
│ ├── NetEscapades.EnumGenerators.Tests.csproj
│ └── SourceGenerationHelperSnapshotTests.cs
├── NetEscapades.EnumGenerators.NetStandard.Interceptors.IntegrationTests
│ ├── InterceptionAttributes.cs
│ └── NetEscapades.EnumGenerators.NetStandard.Interceptors.IntegrationTests.csproj
├── NetEscapades.EnumGenerators.Nuget.NetStandard
│ └── NetEscapades.EnumGenerators.Nuget.NetStandard.csproj
├── NetEscapades.EnumGenerators.Nuget.NetStandard.SystemMemory
│ └── NetEscapades.EnumGenerators.Nuget.NetStandard.SystemMemory.csproj
├── NetEscapades.EnumGenerators.Nuget.NetStandard.Interceptors.IntegrationTests
│ ├── InterceptionAttributes.cs
│ └── NetEscapades.EnumGenerators.Nuget.NetStandard.Interceptors.IntegrationTests.csproj
├── NetEscapades.EnumGenerators.NetStandard
│ └── NetEscapades.EnumGenerators.NetStandard.csproj
├── NetEscapades.EnumGenerators.NetStandard.SystemMemory
│ └── NetEscapades.EnumGenerators.NetStandard.SystemMemory.csproj
├── Directory.Build.props
├── NetEscapades.EnumGenerators.NetStandard.IntegrationTests
│ └── NetEscapades.EnumGenerators.NetStandard.IntegrationTests.csproj
├── NetEscapades.EnumGenerators.Nuget.Interceptors.IntegrationTests
│ └── NetEscapades.EnumGenerators.Nuget.Interceptors.IntegrationTests.csproj
├── NetEscapades.EnumGenerators.Nuget.IntegrationTests
│ ├── NetEscapades.EnumGenerators.Nuget.IntegrationTests.csproj
│ └── NetEscapades.EnumGenerators.Nuget.IntegrationTests.Project.targets
├── NetEscapades.EnumGenerators.NetStandard.SystemMemory.IntegrationTests
│ └── NetEscapades.EnumGenerators.NetStandard.SystemMemory.IntegrationTests.csproj
└── NetEscapades.EnumGenerators.Nuget.NetStandard.SystemMemory.IntegrationTests
│ └── NetEscapades.EnumGenerators.Nuget.NetStandard.SystemMemory.IntegrationTests.csproj
├── src
├── NetEscapades.EnumGenerators
│ ├── GlobalUsings.cs
│ ├── Constants.cs
│ ├── TrackingNames.cs
│ ├── NetEscapades.EnumGenerators.props
│ ├── SymbolHelpers.cs
│ ├── NetEscapades.EnumGenerators.targets
│ ├── Attributes.cs
│ ├── LocationInfo.cs
│ ├── EnumToGenerate.cs
│ ├── EnumValueOption.cs
│ ├── NetEscapades.EnumGenerators.csproj
│ ├── Diagnostics
│ │ ├── HasFlagCodeFixProvider.cs
│ │ ├── EnumInGenericTypeAnalyzer.cs
│ │ ├── HasFlagAnalyzer.cs
│ │ ├── AnalyzerHelpers.cs
│ │ ├── DuplicateEnumValueAnalyzer.cs
│ │ └── DuplicateExtensionClassAnalyzer.cs
│ └── EquatableArray.cs
├── NetEscapades.EnumGenerators.Interceptors
│ ├── NetEscapades.EnumGenerators.Interceptors.props
│ ├── Constants.cs
│ ├── NetEscapades.EnumGenerators.Interceptors.targets
│ ├── DiagnosticHelper.cs
│ ├── LocationInfo.cs
│ ├── TrackingNames.cs
│ ├── EnumToIntercept.cs
│ ├── NetEscapades.EnumGenerators.Interceptors.csproj
│ ├── EquatableArray.cs
│ ├── SourceGenerationHelper.cs
│ └── README.md
├── NetEscapades.EnumGenerators.Attributes
│ ├── NetEscapades.EnumGenerators.Attributes.csproj
│ ├── SerializationTransform.cs
│ ├── SerializationOptions.cs
│ ├── MetadataSource.cs
│ ├── EnumParseOptions.cs
│ └── EnumExtensionsAttribute.cs
└── NetEscapades.EnumGenerators.Interceptors.Attributes
│ ├── NetEscapades.EnumGenerators.Interceptors.Attributes.csproj
│ └── InterceptableAttribute.cs
├── NuGet.integration-tests.config
├── version.props
├── releasenotes.props
├── samples
└── ToStringFastExample
│ ├── Program.cs
│ └── ToStringFastExample.csproj
├── LICENSE
├── Directory.Build.props
├── .gitattributes
├── .github
└── workflows
│ └── BuildAndPack.yml
└── .gitignore
/global.json:
--------------------------------------------------------------------------------
1 | {
2 | "sdk": {
3 | "allowPrerelease": true
4 | }
5 | }
--------------------------------------------------------------------------------
/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/andrewlock/NetEscapades.EnumGenerators/HEAD/icon.png
--------------------------------------------------------------------------------
/icon_32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/andrewlock/NetEscapades.EnumGenerators/HEAD/icon_32.png
--------------------------------------------------------------------------------
/.nuke/parameters.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "./build.schema.json",
3 | "Solution": "NetEscapades.EnumGenerators.sln"
4 | }
--------------------------------------------------------------------------------
/tests/NetEscapades.EnumGenerators.IntegrationTests/xunit.runner.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://xunit.net/schema/current/xunit.runner.schema.json",
3 | "shadowCopy": false
4 | }
--------------------------------------------------------------------------------
/tests/NetEscapades.EnumGenerators.Benchmarks/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 | false
4 |
5 |
--------------------------------------------------------------------------------
/tests/NetEscapades.EnumGenerators.Interceptors.IntegrationTests/xunit.runner.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://xunit.net/schema/current/xunit.runner.schema.json",
3 | "shadowCopy": false
4 | }
--------------------------------------------------------------------------------
/src/NetEscapades.EnumGenerators/GlobalUsings.cs:
--------------------------------------------------------------------------------
1 | global using ExternalEnumDictionary = System.Collections.Generic.Dictionary;
--------------------------------------------------------------------------------
/src/NetEscapades.EnumGenerators.Interceptors/NetEscapades.EnumGenerators.Interceptors.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/NuGet.integration-tests.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/tests/NetEscapades.EnumGenerators.IntegrationTests/.editorconfig:
--------------------------------------------------------------------------------
1 | #### .NET Coding Conventions ####
2 | [*.{cs,vb}]
3 |
4 | dotnet_diagnostic.NEEG001.severity = error
5 | dotnet_diagnostic.NEEG002.severity = error
6 | dotnet_diagnostic.NEEG003.severity = error
7 | dotnet_diagnostic.NEEG004.severity = error
8 | dotnet_diagnostic.NEEG005.severity = error
--------------------------------------------------------------------------------
/src/NetEscapades.EnumGenerators.Interceptors/Constants.cs:
--------------------------------------------------------------------------------
1 | namespace NetEscapades.EnumGenerators.Interceptors;
2 |
3 | internal static class Constants
4 | {
5 | public const string EnabledPropertyName = "EnableEnumGeneratorInterceptor";
6 | public const string InterceptableAttribute = "NetEscapades.EnumGenerators.InterceptableAttribute`1";
7 | }
--------------------------------------------------------------------------------
/src/NetEscapades.EnumGenerators/Constants.cs:
--------------------------------------------------------------------------------
1 | namespace NetEscapades.EnumGenerators;
2 |
3 | public static class Constants
4 | {
5 | public const string Version = "1.0.0-beta16";
6 | public const string MetadataSourcePropertyName = "EnumGenerator_EnumMetadataSource";
7 | public const string ForceExtensionMembers = "EnumGenerator_ForceExtensionMembers";
8 | }
--------------------------------------------------------------------------------
/version.props:
--------------------------------------------------------------------------------
1 |
2 |
3 | 1.0.0
4 | beta16
5 | $(VersionPrefix)
6 | $(VersionPrefix)-$(VersionSuffix)
7 |
8 |
9 |
--------------------------------------------------------------------------------
/src/NetEscapades.EnumGenerators.Attributes/NetEscapades.EnumGenerators.Attributes.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0;net451
5 | enable
6 | NetEscapades.EnumGenerators
7 | false
8 | true
9 |
10 |
11 |
--------------------------------------------------------------------------------
/src/NetEscapades.EnumGenerators/TrackingNames.cs:
--------------------------------------------------------------------------------
1 | namespace NetEscapades.EnumGenerators;
2 |
3 | ///
4 | /// Names that are attached to incremental generator stages for tracking
5 | ///
6 | public class TrackingNames
7 | {
8 | public const string InitialExtraction = nameof(InitialExtraction);
9 | public const string InitialExternalExtraction = nameof(InitialExternalExtraction);
10 | public const string RemovingNulls = nameof(RemovingNulls);
11 | }
--------------------------------------------------------------------------------
/src/NetEscapades.EnumGenerators.Interceptors.Attributes/NetEscapades.EnumGenerators.Interceptors.Attributes.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0;net451
5 | enable
6 | NetEscapades.EnumGenerators
7 | false
8 | true
9 |
10 |
11 |
--------------------------------------------------------------------------------
/tests/NetEscapades.EnumGenerators.Tests/NetEscapades.EnumGenerators.Tests.Roslyn4_04.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 4.04.0
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/releasenotes.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 | $(PackageReleaseNotes)
12 | See $(PackageProjectUrl)/blob/main/CHANGELOG.md#v$(PackageVersion.Replace('.','')) for more details.
13 |
14 |
--------------------------------------------------------------------------------
/src/NetEscapades.EnumGenerators.Interceptors/NetEscapades.EnumGenerators.Interceptors.targets:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | $(InterceptorsNamespaces);NetEscapades.EnumGenerators
5 | $(InterceptorsPreviewNamespaces);NetEscapades.EnumGenerators
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/NetEscapades.EnumGenerators/NetEscapades.EnumGenerators.props:
--------------------------------------------------------------------------------
1 |
2 |
3 | EnumMemberAttribute
4 | false
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/NetEscapades.EnumGenerators/SymbolHelpers.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.CodeAnalysis;
2 |
3 | namespace NetEscapades.EnumGenerators;
4 |
5 | internal static class SymbolHelpers
6 | {
7 | public static bool IsNestedInGenericType(INamedTypeSymbol enumSymbol)
8 | {
9 | var containingType = enumSymbol.ContainingType;
10 | while (containingType is not null)
11 | {
12 | if (containingType.IsGenericType)
13 | {
14 | return true;
15 | }
16 | containingType = containingType.ContainingType;
17 | }
18 | return false;
19 | }
20 | }
--------------------------------------------------------------------------------
/src/NetEscapades.EnumGenerators.Interceptors/DiagnosticHelper.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.CodeAnalysis;
2 |
3 | namespace NetEscapades.EnumGenerators.Interceptors;
4 |
5 | public static class DiagnosticHelper
6 | {
7 | public static readonly DiagnosticDescriptor CsharpVersionLooLow = new(
8 | #pragma warning disable RS2008 // Enable Analyzer Release Tracking
9 | id: "NEEG001",
10 | #pragma warning restore RS2008
11 | title: "Language version too low",
12 | messageFormat: "Interceptors require at least C# version 11",
13 | category: "Requirements",
14 | defaultSeverity: DiagnosticSeverity.Warning,
15 | isEnabledByDefault: true);
16 | }
--------------------------------------------------------------------------------
/samples/ToStringFastExample/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using NetEscapades.EnumGenerators;
3 |
4 | var value = ExampleEnums.First;
5 | Console.WriteLine(value.ToStringFast());
6 |
7 | var flags = FlagsEnum.Flag1 | FlagsEnum.Flag3;
8 |
9 | Console.WriteLine(flags.ToStringFast());
10 | Console.WriteLine($"HasFlag(Flag1), {flags.HasFlagFast(FlagsEnum.Flag1)}");
11 | Console.WriteLine($"HasFlag(Flag1), {flags.HasFlagFast(FlagsEnum.Flag2)}");
12 |
13 | [EnumExtensions]
14 | internal enum ExampleEnums
15 | {
16 | First,
17 | Second,
18 | Third,
19 | }
20 |
21 | [EnumExtensions]
22 | [Flags]
23 | internal enum FlagsEnum
24 | {
25 | Flag1,
26 | Flag2,
27 | Flag3,
28 | }
--------------------------------------------------------------------------------
/src/NetEscapades.EnumGenerators.Attributes/SerializationTransform.cs:
--------------------------------------------------------------------------------
1 | namespace NetEscapades.EnumGenerators;
2 |
3 | ///
4 | /// Transform to apply when calling ToStringFast
5 | ///
6 | public enum SerializationTransform
7 | {
8 | ///
9 | /// Don't apply a transform to the ToStringFast() result.
10 | ///
11 | None,
12 |
13 | ///
14 | /// Call on the ToStringFast() result.
15 | ///
16 | LowerInvariant,
17 |
18 | ///
19 | /// Call on the ToStringFast() result.
20 | ///
21 | UpperInvariant,
22 | }
--------------------------------------------------------------------------------
/src/NetEscapades.EnumGenerators/NetEscapades.EnumGenerators.targets:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | true
5 |
6 |
7 | $(DefineConstants);NETESCAPADES_ENUMGENERATORS_SYSTEM_MEMORY
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/NetEscapades.EnumGenerators/Attributes.cs:
--------------------------------------------------------------------------------
1 | namespace NetEscapades.EnumGenerators;
2 |
3 | internal static class Attributes
4 | {
5 | public const string DisplayAttribute = "System.ComponentModel.DataAnnotations.DisplayAttribute";
6 | public const string DescriptionAttribute = "System.ComponentModel.DescriptionAttribute";
7 | public const string EnumMemberAttribute = "System.Runtime.Serialization.EnumMemberAttribute";
8 |
9 | public const string EnumExtensionsAttribute = "NetEscapades.EnumGenerators.EnumExtensionsAttribute";
10 | public const string ExternalEnumExtensionsAttribute = "NetEscapades.EnumGenerators.EnumExtensionsAttribute`1";
11 | public const string FlagsAttribute = "System.FlagsAttribute";
12 | }
--------------------------------------------------------------------------------
/tests/NetEscapades.EnumGenerators.NetStandard.Interceptors.IntegrationTests/InterceptionAttributes.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using Foo;
4 | using NetEscapades.EnumGenerators;
5 | using NetEscapades.EnumGenerators.NetStandard.IntegrationTests;
6 |
7 | [assembly:Interceptable]
8 | [assembly:Interceptable]
9 | [assembly:Interceptable]
10 | [assembly:Interceptable]
11 | [assembly:Interceptable]
12 | [assembly:Interceptable(ExtensionClassName="SomeExtension", ExtensionClassNamespace = "SomethingElse")]
13 | [assembly:Interceptable]
14 | [assembly:Interceptable]
15 | [assembly:Interceptable]
16 |
--------------------------------------------------------------------------------
/src/NetEscapades.EnumGenerators/LocationInfo.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.CodeAnalysis;
2 | using Microsoft.CodeAnalysis.Text;
3 |
4 | namespace NetEscapades.EnumGenerators;
5 |
6 | internal record LocationInfo(string FilePath, TextSpan TextSpan, LinePositionSpan LineSpan)
7 | {
8 | public Location ToLocation()
9 | => Location.Create(FilePath, TextSpan, LineSpan);
10 |
11 | public static LocationInfo? CreateFrom(SyntaxNode node)
12 | => CreateFrom(node.GetLocation());
13 |
14 | public static LocationInfo? CreateFrom(Location location)
15 | {
16 | if (location.SourceTree is null)
17 | {
18 | return null;
19 | }
20 |
21 | return new LocationInfo(location.SourceTree.FilePath, location.SourceSpan, location.GetLineSpan().Span);
22 | }
23 | }
--------------------------------------------------------------------------------
/src/NetEscapades.EnumGenerators.Interceptors/LocationInfo.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.CodeAnalysis;
2 | using Microsoft.CodeAnalysis.Text;
3 |
4 | namespace NetEscapades.EnumGenerators.Interceptors;
5 |
6 | internal record LocationInfo(string FilePath, TextSpan TextSpan, LinePositionSpan LineSpan)
7 | {
8 | public Location ToLocation()
9 | => Location.Create(FilePath, TextSpan, LineSpan);
10 |
11 | public static LocationInfo? CreateFrom(SyntaxNode node)
12 | => CreateFrom(node.GetLocation());
13 |
14 | public static LocationInfo? CreateFrom(Location location)
15 | {
16 | if (location.SourceTree is null)
17 | {
18 | return null;
19 | }
20 |
21 | return new LocationInfo(location.SourceTree.FilePath, location.SourceSpan, location.GetLineSpan().Span);
22 | }
23 | }
--------------------------------------------------------------------------------
/tests/NetEscapades.EnumGenerators.Nuget.NetStandard/NetEscapades.EnumGenerators.Nuget.NetStandard.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0
5 | false
6 | enable
7 | $(DefineConstants);NUGET_NETSTANDARD_INTERCEPTOR_TESTS
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/tests/NetEscapades.EnumGenerators.Nuget.NetStandard.SystemMemory/NetEscapades.EnumGenerators.Nuget.NetStandard.SystemMemory.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0
5 | false
6 | enable
7 | $(DefineConstants);NUGET_SYSTEMMEMORY_INTEGRATION_TESTS
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/tests/NetEscapades.EnumGenerators.Nuget.NetStandard.Interceptors.IntegrationTests/InterceptionAttributes.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using Foo;
4 | using NetEscapades.EnumGenerators;
5 | #if NETSTANDARD_INTERCEPTOR_TESTS
6 | using NetEscapades.EnumGenerators.NetStandard.IntegrationTests;
7 | #elif NUGET_NETSTANDARD_INTERCEPTOR_TESTS
8 | using NetEscapades.EnumGenerators.Nuget.NetStandard.Interceptors.IntegrationTests;
9 | #else
10 | #error Unknown integration tests
11 | #endif
12 |
13 | [assembly:Interceptable]
14 | [assembly:Interceptable]
15 | [assembly:Interceptable]
16 | [assembly:Interceptable]
17 | [assembly:Interceptable]
18 | [assembly:Interceptable(ExtensionClassName="SomeExtension", ExtensionClassNamespace = "SomethingElse")]
19 | [assembly:Interceptable]
20 | [assembly:Interceptable]
21 | [assembly:Interceptable]
22 |
--------------------------------------------------------------------------------
/src/NetEscapades.EnumGenerators.Interceptors/TrackingNames.cs:
--------------------------------------------------------------------------------
1 | namespace NetEscapades.EnumGenerators.Interceptors;
2 |
3 | ///
4 | /// Names that are attached to incremental generator stages for tracking
5 | ///
6 | public class TrackingNames
7 | {
8 | public const string InitialExtraction = nameof(InitialExtraction);
9 | public const string InitialExternalExtraction = nameof(InitialExternalExtraction);
10 | public const string InitialInterceptable = nameof(InitialInterceptable);
11 | public const string InitialInterceptableOnly = nameof(InitialInterceptableOnly);
12 | public const string RemovingNulls = nameof(RemovingNulls);
13 | public const string InterceptedLocations = nameof(InterceptedLocations);
14 | public const string Settings = nameof(Settings);
15 | public const string EnumInterceptions = nameof(EnumInterceptions);
16 | public const string ExternalInterceptions = nameof(ExternalInterceptions);
17 | public const string AdditionalInterceptions = nameof(AdditionalInterceptions);
18 | }
--------------------------------------------------------------------------------
/src/NetEscapades.EnumGenerators.Interceptors/EnumToIntercept.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.CodeAnalysis.CSharp;
2 |
3 | namespace NetEscapades.EnumGenerators.Interceptors;
4 |
5 | public readonly record struct EnumToIntercept(string Name, string FullyQualifiedName, string Namespace);
6 |
7 | #pragma warning disable RSEXPERIMENTAL002 // / Experimental interceptable location API
8 | public record CandidateInvocation(InterceptableLocation Location, string EnumName, InterceptorTarget Target);
9 | #pragma warning restore RSEXPERIMENTAL002
10 |
11 | public record MethodToIntercept(
12 | EquatableArray Invocations,
13 | string ExtensionTypeName,
14 | string FullyQualifiedName,
15 | string EnumNamespace)
16 | {
17 | public MethodToIntercept(EquatableArray invocations, EnumToIntercept enumToIntercept)
18 | : this(invocations, enumToIntercept.Name, enumToIntercept.FullyQualifiedName, enumToIntercept.Namespace)
19 | {
20 | }
21 | }
22 |
23 | public enum InterceptorTarget
24 | {
25 | ToString,
26 | HasFlag,
27 | }
--------------------------------------------------------------------------------
/tests/NetEscapades.EnumGenerators.NetStandard/NetEscapades.EnumGenerators.NetStandard.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0
5 | false
6 | enable
7 | $(DefineConstants);NETSTANDARD_INTEGRATION_TESTS
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2019 andrewlock
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/tests/NetEscapades.EnumGenerators.Benchmarks/NetEscapades.EnumGenerators.Benchmarks.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net48;net6.0;net10.0
6 | enable
7 | enable
8 | false
9 | latest
10 | false
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/tests/NetEscapades.EnumGenerators.IntegrationTests/EnumParseOptionsTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using FluentAssertions;
3 | using Xunit;
4 |
5 | namespace NetEscapades.EnumGenerators.Benchmarks;
6 |
7 | public class EnumParseOptionsTests
8 | {
9 | [Fact]
10 | public void Default()
11 | {
12 | EnumParseOptions options = default;
13 | options.AllowMatchingMetadataAttribute.Should().BeFalse();
14 | options.EnableNumberParsing.Should().BeTrue();
15 | options.ComparisonType.Should().Be(StringComparison.Ordinal);
16 | }
17 |
18 | [Fact]
19 | public void DefaultConstructor()
20 | {
21 | var options = new EnumParseOptions();
22 | options.AllowMatchingMetadataAttribute.Should().BeFalse();
23 | options.EnableNumberParsing.Should().BeTrue();
24 | options.ComparisonType.Should().Be(StringComparison.Ordinal);
25 | }
26 |
27 | [Fact]
28 | public void SingleValue()
29 | {
30 | var options = new EnumParseOptions(comparisonType: StringComparison.OrdinalIgnoreCase);
31 | options.AllowMatchingMetadataAttribute.Should().BeFalse();
32 | options.EnableNumberParsing.Should().BeTrue();
33 | options.ComparisonType.Should().Be(StringComparison.OrdinalIgnoreCase);
34 | }
35 | }
--------------------------------------------------------------------------------
/src/NetEscapades.EnumGenerators/EnumToGenerate.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.CodeAnalysis.CSharp;
2 | using Microsoft.CodeAnalysis.Text;
3 |
4 | namespace NetEscapades.EnumGenerators;
5 |
6 | public readonly record struct EnumToGenerate
7 | {
8 | public readonly string Name;
9 | public readonly string FullyQualifiedName;
10 | public readonly string Namespace;
11 | public readonly bool IsPublic;
12 | public readonly bool HasFlags;
13 | public readonly string UnderlyingType;
14 | public readonly MetadataSource? MetadataSource;
15 |
16 | ///
17 | /// Key is the enum name.
18 | ///
19 | public readonly EquatableArray<(string Key, EnumValueOption Value)> Names;
20 |
21 | public EnumToGenerate(
22 | string name,
23 | string ns,
24 | string fullyQualifiedName,
25 | string underlyingType,
26 | bool isPublic,
27 | List<(string Key, EnumValueOption Value)> names,
28 | bool hasFlags,
29 | MetadataSource? metadataSource)
30 | {
31 | Name = name;
32 | Namespace = ns;
33 | UnderlyingType = underlyingType;
34 | Names = new EquatableArray<(string, EnumValueOption)>(names.ToArray());
35 | HasFlags = hasFlags;
36 | IsPublic = isPublic;
37 | FullyQualifiedName = fullyQualifiedName;
38 | MetadataSource = metadataSource;
39 | }
40 | }
--------------------------------------------------------------------------------
/tests/NetEscapades.EnumGenerators.NetStandard.SystemMemory/NetEscapades.EnumGenerators.NetStandard.SystemMemory.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | netstandard2.0
7 | false
8 | enable
9 | $(DefineConstants);NETSTANDARD_SYSTEMMEMORY_INTEGRATION_TESTS
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Andrew Lock
7 | Copyright © AndrewLock
8 | en-GB
9 | false
10 | MIT
11 | https://github.com/andrewlock/NetEscapades.EnumGenerators
12 | enums attribute generator generation codegen codegenerator codegeneration netescapades
13 | false
14 | true
15 | true
16 | icon.png
17 |
18 |
19 |
20 | true
21 | latest
22 | False
23 | $(MSBuildThisFileDirectory)\artifacts
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/src/NetEscapades.EnumGenerators.Interceptors.Attributes/InterceptableAttribute.cs:
--------------------------------------------------------------------------------
1 | namespace NetEscapades.EnumGenerators
2 | {
3 | ///
4 | /// Add to an assembly to indicate that usages of the enum should
5 | /// be automatically intercepted to use the extension methods
6 | /// generated by EnumExtensionsAttribute in this project.
7 | /// Note that the extension methods must be accessible from this project,
8 | /// otherwise you will receive compilation errors
9 | ///
10 | [System.AttributeUsage(System.AttributeTargets.Assembly, AllowMultiple = true)]
11 | [System.Diagnostics.Conditional("NETESCAPADES_ENUMGENERATORS_USAGES")]
12 | public class InterceptableAttribute : System.Attribute
13 | where T: System.Enum
14 | {
15 | ///
16 | /// The namespace generated for the extension class. If not provided,
17 | /// and the referenced enum is in a different project, the namespace
18 | /// of the extension methods are assumed to be the same as the enum.
19 | ///
20 | public string? ExtensionClassNamespace { get; set; }
21 |
22 | ///
23 | /// The name used for the extension class. If not provided,
24 | /// and the referenced enum is in a different project, the enum name
25 | /// with an Extensions suffix will be assumed. For example for
26 | /// an Enum called StatusCodes, the assumed name will be StatusCodesExtensions.
27 | ///
28 | public string? ExtensionClassName { get; set; }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/tests/NetEscapades.EnumGenerators.Tests/NetEscapades.EnumGenerators.Tests.Project.targets:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | NetEscapades.EnumGenerators.Tests
5 | $(AssemblyName)
6 | enable
7 | false
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | runtime; build; native; contentfiles; analyzers; buildtransitive
18 | all
19 |
20 |
21 | runtime; build; native; contentfiles; analyzers; buildtransitive
22 | all
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/src/NetEscapades.EnumGenerators/EnumValueOption.cs:
--------------------------------------------------------------------------------
1 | namespace NetEscapades.EnumGenerators;
2 |
3 | public readonly struct EnumValueOption : IEquatable
4 | {
5 | ///
6 | /// Custom name set by the [Display(Name)] attribute.
7 | ///
8 | private readonly string? _displayName;
9 | private readonly string? _description;
10 | private readonly string? _enumMemberValue;
11 | public object ConstantValue { get; }
12 |
13 | public EnumValueOption(string? displayName, string? description, string? enumMemberValue, object constantValue)
14 | {
15 | _displayName = displayName;
16 | ConstantValue = constantValue;
17 | _description = description;
18 | _enumMemberValue = enumMemberValue;
19 | }
20 |
21 | public bool Equals(EnumValueOption other)
22 | {
23 | return _displayName == other._displayName &&
24 | _description == other._description &&
25 | _enumMemberValue == other._enumMemberValue &&
26 | Equals(ConstantValue, other.ConstantValue);
27 | }
28 |
29 | public string? GetMetadataName(MetadataSource metadataSource)
30 | => metadataSource switch
31 | {
32 | MetadataSource.DisplayAttribute => _displayName,
33 | MetadataSource.DescriptionAttribute => _description,
34 | MetadataSource.EnumMemberAttribute => _enumMemberValue,
35 | _ => null,
36 | };
37 |
38 | public static EnumValueOption CreateWithoutAttributes(object constantValue)
39 | => new(null, null, null, constantValue);
40 | }
41 |
--------------------------------------------------------------------------------
/tests/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | false
6 | $(MSBuildThisFileDirectory)../artifacts
7 | true
8 | NU1901;NU1902;NU1903;NU1904
9 | net6.0;net7.0;net8.0;net9.0;net10.0
10 | net48;netcoreapp2.1;netcoreapp3.1;$(TargetFrameworks)
11 |
12 |
13 | en
14 | latest
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/tests/NetEscapades.EnumGenerators.IntegrationTests/SerializationOptionsTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using FluentAssertions;
3 | using Xunit;
4 |
5 | namespace NetEscapades.EnumGenerators.IntegrationTests;
6 |
7 | public class SerializationOptionsTests
8 | {
9 | [Fact]
10 | public void Default()
11 | {
12 | SerializationOptions options = default;
13 | options.UseMetadataAttributes.Should().BeFalse();
14 | options.Transform.Should().Be(SerializationTransform.None);
15 | }
16 |
17 | [Fact]
18 | public void DefaultConstructor()
19 | {
20 | var options = new SerializationOptions();
21 | options.UseMetadataAttributes.Should().BeFalse();
22 | options.Transform.Should().Be(SerializationTransform.None);
23 | }
24 |
25 | [Fact]
26 | public void Transform()
27 | {
28 | var options = new SerializationOptions(transform: SerializationTransform.UpperInvariant);
29 | options.UseMetadataAttributes.Should().BeFalse();
30 | options.Transform.Should().Be(SerializationTransform.UpperInvariant);
31 | }
32 |
33 | [Fact]
34 | public void Metadata()
35 | {
36 | var options = new SerializationOptions(true);
37 | options.UseMetadataAttributes.Should().BeTrue();
38 | options.Transform.Should().Be(SerializationTransform.None);
39 | }
40 |
41 | [Fact]
42 | public void TransformAndMetadata()
43 | {
44 | var options = new SerializationOptions(true, transform: SerializationTransform.UpperInvariant);
45 | options.UseMetadataAttributes.Should().BeTrue();
46 | options.Transform.Should().Be(SerializationTransform.UpperInvariant);
47 | }
48 | }
--------------------------------------------------------------------------------
/src/NetEscapades.EnumGenerators.Attributes/SerializationOptions.cs:
--------------------------------------------------------------------------------
1 | namespace NetEscapades.EnumGenerators;
2 |
3 | ///
4 | /// Options to apply when calling ToStringFast on an enum.
5 | ///
6 | public readonly struct SerializationOptions
7 | {
8 | ///
9 | /// Create an instance of
10 | ///
11 | /// Sets whether the value of any metadata value attribute
12 | /// values applied to an enum should be used in the ToStringFast call.
13 | /// Sets the to use when serializing the enum value.
14 | public SerializationOptions(
15 | bool useMetadataAttributes = false,
16 | SerializationTransform transform = SerializationTransform.None)
17 | {
18 | UseMetadataAttributes = useMetadataAttributes;
19 | Transform = transform;
20 | }
21 |
22 | ///
23 | /// Gets whether the value of any metadata attributes applied to an enum member
24 | /// should be used as the ToString() value for the enum.
25 | ///
26 | ///
27 | /// By default, it's set to , so the value of metadata attributes on the
28 | /// enum values are ignored.
29 | ///
30 | public bool UseMetadataAttributes { get; }
31 |
32 | ///
33 | /// Gets the to use during parsing.
34 | ///
35 | ///
36 | /// By default, it's set to , and the value is not transformed.
37 | ///
38 | public SerializationTransform Transform { get; }
39 | }
--------------------------------------------------------------------------------
/tests/NetEscapades.EnumGenerators.NetStandard.IntegrationTests/NetEscapades.EnumGenerators.NetStandard.IntegrationTests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | false
5 | enable
6 | $(DefineConstants);NETSTANDARD_INTEGRATION_TESTS
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | runtime; build; native; contentfiles; analyzers; buildtransitive
29 | all
30 |
31 |
32 | runtime; build; native; contentfiles; analyzers; buildtransitive
33 | all
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/tests/NetEscapades.EnumGenerators.Nuget.Interceptors.IntegrationTests/NetEscapades.EnumGenerators.Nuget.Interceptors.IntegrationTests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | false
5 | enable
6 | $(DefineConstants);NUGET_INTERCEPTOR_TESTS
7 | $(DefineConstants);READONLYSPAN
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | runtime; build; native; contentfiles; analyzers; buildtransitive
28 | all
29 |
30 |
31 | runtime; build; native; contentfiles; analyzers; buildtransitive
32 | all
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/tests/NetEscapades.EnumGenerators.Nuget.IntegrationTests/NetEscapades.EnumGenerators.Nuget.IntegrationTests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | false
5 | enable
6 | $(DefineConstants);NUGET_INTEGRATION_TESTS
7 | $(DefineConstants);READONLYSPAN
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | runtime; build; native; contentfiles; analyzers; buildtransitive
29 | all
30 |
31 |
32 | runtime; build; native; contentfiles; analyzers; buildtransitive
33 | all
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/tests/NetEscapades.EnumGenerators.NetStandard.SystemMemory.IntegrationTests/NetEscapades.EnumGenerators.NetStandard.SystemMemory.IntegrationTests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | false
5 | enable
6 | $(DefineConstants);NETSTANDARD_SYSTEMMEMORY_INTEGRATION_TESTS;READONLYSPAN
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | runtime; build; native; contentfiles; analyzers; buildtransitive
29 | all
30 |
31 |
32 | runtime; build; native; contentfiles; analyzers; buildtransitive
33 | all
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/src/NetEscapades.EnumGenerators.Attributes/MetadataSource.cs:
--------------------------------------------------------------------------------
1 | namespace NetEscapades.EnumGenerators
2 | {
3 | ///
4 | /// Defines where to obtain metadata for serializing and deserializing the enum
5 | ///
6 | public enum MetadataSource
7 | {
8 | ///
9 | /// Don't use attributes applied to enum members as a source of metadata for
10 | /// ToStringFast() and TryParse(). The name of the enum member
11 | /// will always be used for serialization.
12 | ///
13 | None,
14 |
15 | ///
16 | /// Use values provided in System.ComponentModel.DataAnnotations.DisplayAttribute for
17 | /// determining the value to use for ToStringFast() and TryParse().
18 | /// The value of the attribute will be used if available, otherwise the
19 | /// name of the enum member will be used for serialization.
20 | ///
21 | DisplayAttribute,
22 |
23 | ///
24 | /// Use values provided in for
25 | /// determining the value to use for ToStringFast() and TryParse().
26 | /// The value of the attribute will be used if available, otherwise the
27 | /// name of the enum member will be used for serialization.
28 | ///
29 | DescriptionAttribute,
30 |
31 | ///
32 | /// Use values provided in for
33 | /// determining the value to use for ToStringFast() and TryParse().
34 | /// The value of the attribute will be used if available, otherwise the
35 | /// name of the enum member will be used for serialization.
36 | ///
37 | EnumMemberAttribute,
38 | }
39 | }
--------------------------------------------------------------------------------
/samples/ToStringFastExample/ToStringFastExample.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | netcoreapp2.1;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0
6 | net48;$(TargetFrameworks)
7 | enable
8 | true
9 | false
10 | false
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/tests/NetEscapades.EnumGenerators.Nuget.IntegrationTests/NetEscapades.EnumGenerators.Nuget.IntegrationTests.Project.targets:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp2.1;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0
5 | net48;$(TargetFrameworks)
6 | false
7 | enable
8 | $(DefineConstants);READONLYSPAN
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | runtime; build; native; contentfiles; analyzers; buildtransitive
30 | all
31 |
32 |
33 | runtime; build; native; contentfiles; analyzers; buildtransitive
34 | all
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/tests/NetEscapades.EnumGenerators.Nuget.NetStandard.SystemMemory.IntegrationTests/NetEscapades.EnumGenerators.Nuget.NetStandard.SystemMemory.IntegrationTests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | false
5 | enable
6 | $(DefineConstants);NUGET_SYSTEMMEMORY_INTEGRATION_TESTS;READONLYSPAN
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | runtime; build; native; contentfiles; analyzers; buildtransitive
32 | all
33 |
34 |
35 | runtime; build; native; contentfiles; analyzers; buildtransitive
36 | all
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/tests/NetEscapades.EnumGenerators.Nuget.NetStandard.Interceptors.IntegrationTests/NetEscapades.EnumGenerators.Nuget.NetStandard.Interceptors.IntegrationTests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | false
5 | enable
6 | $(DefineConstants);NUGET_NETSTANDARD_INTERCEPTOR_TESTS
7 | $(DefineConstants);READONLYSPAN
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | runtime; build; native; contentfiles; analyzers; buildtransitive
32 | all
33 |
34 |
35 | runtime; build; native; contentfiles; analyzers; buildtransitive
36 | all
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/tests/NetEscapades.EnumGenerators.Tests/EquatableArrayTests.cs:
--------------------------------------------------------------------------------
1 | using FluentAssertions;
2 | using Xunit;
3 |
4 | namespace NetEscapades.EnumGenerators.Tests;
5 |
6 | public class EquatableArrayTests
7 | {
8 | [Fact]
9 | public void PrimitiveComparison()
10 | {
11 | int[] val1 = [1, 2, 3, 4, 5];
12 | int[] val2 = [1, 2, 3, 4, 5];
13 |
14 | var arr1 = new EquatableArray(val1);
15 | var arr2 = new EquatableArray(val2);
16 |
17 | arr1.Equals(arr2).Should().BeTrue();
18 | }
19 |
20 | [Fact]
21 | public void RecordComparison()
22 | {
23 | Record[] val1 = [new(1), new(2), new(3), new(4), new(5)];
24 | Record[] val2 = [new(1), new(2), new(3), new(4), new(5)];
25 |
26 | var arr1 = new EquatableArray(val1);
27 | var arr2 = new EquatableArray(val2);
28 |
29 | arr1.Equals(arr2).Should().BeTrue();
30 | }
31 |
32 | [Fact]
33 | public void NestedEquatableArrayComparison()
34 | {
35 | EquatableArray[] val1 = [new([1]), new([2]), new([3]), new([4]), new([5])];
36 | EquatableArray[] val2 = [new([1]), new([2]), new([3]), new([4]), new([5])];
37 |
38 | var arr1 = new EquatableArray>(val1);
39 | var arr2 = new EquatableArray>(val2);
40 |
41 | arr1.Equals(arr2).Should().BeTrue();
42 | }
43 |
44 | [Fact]
45 | public void BoxedNestedEquatableArrayComparison()
46 | {
47 | EquatableArray[] val1 = [new([1]), new([2]), new([3]), new([4]), new([5])];
48 | EquatableArray[] val2 = [new([1]), new([2]), new([3]), new([4]), new([5])];
49 |
50 | object arr1 = new EquatableArray>(val1);
51 | var arr2 = new EquatableArray>(val2);
52 |
53 | arr1.Equals(arr2).Should().BeTrue();
54 | arr2.Equals(arr1).Should().BeTrue();
55 | }
56 |
57 | public record Record
58 | {
59 | public Record(int value)
60 | {
61 | Value = value;
62 | }
63 |
64 | public int Value { get; }
65 | }
66 | }
--------------------------------------------------------------------------------
/tests/NetEscapades.EnumGenerators.IntegrationTests/AnalyzerTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Xunit;
3 |
4 | #if INTEGRATION_TESTS
5 | using NetEscapades.EnumGenerators.IntegrationTests;
6 | #elif NETSTANDARD_INTEGRATION_TESTS
7 | using NetEscapades.EnumGenerators.NetStandard.IntegrationTests;
8 | #elif NETSTANDARD_SYSTEMMEMORY_INTEGRATION_TESTS
9 | using NetEscapades.EnumGenerators.NetStandard.SystemMemory.IntegrationTests;
10 | #elif INTERCEPTOR_TESTS
11 | using NetEscapades.EnumGenerators.Interceptors.IntegrationTests;
12 | #elif NUGET_INTEGRATION_TESTS
13 | using NetEscapades.EnumGenerators.Nuget.IntegrationTests;
14 | #elif NUGET_INTERCEPTOR_TESTS
15 | using NetEscapades.EnumGenerators.Nuget.Interceptors.IntegrationTests;
16 | #elif NUGET_NETSTANDARD_INTERCEPTOR_TESTS
17 | using NetEscapades.EnumGenerators.Nuget.NetStandard.Interceptors.IntegrationTests;
18 | #elif NUGET_SYSTEMMEMORY_INTEGRATION_TESTS
19 | using NetEscapades.EnumGenerators.Nuget.SystemMemory.IntegrationTests;
20 | #else
21 | #error Unknown integration tests
22 | #endif
23 |
24 | namespace NetEscapades.EnumGenerators.Benchmarks;
25 |
26 | public class AnalyzerTests
27 | {
28 | // These method calls should all be flagged by the analyzer
29 | [Fact]
30 | public void Neeg004Testing()
31 | {
32 | #pragma warning disable NEEG004
33 | var test = EnumInSystem.First;
34 | _ = test.ToString();
35 | _ = EnumInSystem.First.ToString();
36 | _ = EnumInSystem.First.ToString("G");
37 | _ = EnumInSystem.First.ToString("x"); // no error
38 | _ = EnumInSystem.First.ToString(format: "g");
39 | _ = EnumInSystem.First.ToString(format: null); // no error
40 | _ = DateTimeKind.Local.ToString();
41 | _ = $"Some value: {test} <-";
42 | _ = $"Some value: {test:G} <-";
43 | _ = $"Some value: {EnumInSystem.First} <-";
44 | _ = $"Some value: {EnumInSystem.First:G} <-";
45 | #pragma warning restore NEEG004
46 | }
47 |
48 | [Fact]
49 | public void Neeg005Testing()
50 | {
51 | #pragma warning disable NEEG005
52 | var test = FlagsEnum.First;
53 | _ = test.HasFlag(FlagsEnum.Second);
54 | _ = $"Some value: {test.HasFlag(FlagsEnum.Second)} <-";
55 | #pragma warning restore NEEG005
56 | }
57 | }
--------------------------------------------------------------------------------
/tests/NetEscapades.EnumGenerators.IntegrationTests/NetEscapades.EnumGenerators.IntegrationTests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | false
5 | $(DefineConstants);INTEGRATION_TESTS
6 | $(DefineConstants);READONLYSPAN;
7 | true
8 | EnumMemberAttribute
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | runtime; build; native; contentfiles; analyzers; buildtransitive
33 | all
34 |
35 |
36 | runtime; build; native; contentfiles; analyzers; buildtransitive
37 | all
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/tests/NetEscapades.EnumGenerators.Interceptors.IntegrationTests/Enums.cs:
--------------------------------------------------------------------------------
1 | using Foo;
2 | using System;
3 | using System.ComponentModel.DataAnnotations;
4 | using System.IO;
5 | using NetEscapades.EnumGenerators;
6 | using Xunit;
7 |
8 | [assembly:NetEscapades.EnumGenerators.EnumExtensions()]
9 | [assembly:NetEscapades.EnumGenerators.EnumExtensions()]
10 |
11 | namespace Foo
12 | {
13 | // causes Error CS0426 : The type name 'TestEnum' does not exist in the type 'Foo'.
14 | // workaround is to use global prefix
15 | public class Foo { }
16 |
17 | [EnumExtensions]
18 | public enum EnumInFoo
19 | {
20 | First = 0,
21 | [Display(Name = "2nd")]
22 | Second = 1,
23 | Third = 2,
24 | }
25 | }
26 |
27 | #if NUGET_INTERCEPTOR_TESTS
28 | namespace NetEscapades.EnumGenerators.Nuget.Interceptors.IntegrationTests
29 | #elif INTERCEPTOR_TESTS
30 | namespace NetEscapades.EnumGenerators.Interceptors.IntegrationTests
31 | #else
32 | #error Unknown project combination
33 | #endif
34 | {
35 | [EnumExtensions]
36 | public enum EnumWithDisplayNameInNamespace
37 | {
38 | First = 0,
39 |
40 | [System.ComponentModel.Description("2nd")] Second = 1,
41 |
42 | Third = 2,
43 | }
44 |
45 | [EnumExtensions(ExtensionClassName="SomeExtension", ExtensionClassNamespace = "SomethingElse")]
46 | public enum EnumWithExtensionInOtherNamespace
47 | {
48 | First = 0,
49 |
50 | [Display(Name = "2nd")] Second = 1,
51 |
52 | Third = 2,
53 | }
54 |
55 | [EnumExtensions]
56 | [Flags]
57 | public enum FlagsEnum
58 | {
59 | None = 0,
60 | First = 1 << 0,
61 | Second = 1 << 1,
62 | Third = 1 << 2,
63 | Fourth = 1 << 3,
64 | ThirdAndFourth = Third | Fourth,
65 | }
66 |
67 | [EnumExtensions]
68 | public enum StringTesting
69 | {
70 | [System.ComponentModel.Description("Quotes \"")]
71 | Quotes,
72 |
73 | [System.ComponentModel.Description(@"Literal Quotes """)]
74 | LiteralQuotes,
75 |
76 | [System.ComponentModel.Description("Backslash \\")]
77 | Backslash,
78 |
79 | [System.ComponentModel.Description(@"LiteralBackslash \")]
80 | BackslashLiteral,
81 |
82 | [System.ComponentModel.Description("Line\nBreak")]
83 | LineBreak,
84 | }
85 |
86 | [EnumExtensions(IsInterceptable = false)]
87 | public enum NonInterceptableEnum
88 | {
89 | First = 0,
90 | [Display(Name = "2nd")] Second = 1,
91 | Third = 2,
92 | }
93 | }
--------------------------------------------------------------------------------
/tests/NetEscapades.EnumGenerators.Benchmarks/EnumHelper.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel.DataAnnotations;
2 | using System.Reflection;
3 |
4 | namespace NetEscapades.EnumGenerators.Benchmarks;
5 |
6 | internal static class EnumHelper where T : struct
7 | {
8 | internal static bool TryParseByDisplayName(string name, bool ignoreCase, out T enumValue)
9 | {
10 | enumValue = default;
11 |
12 | var stringComparisonOption = ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal;
13 | var enumValues = (T[])Enum.GetValues(typeof(T));
14 | foreach (var value in enumValues)
15 | {
16 | if (TryGetDisplayName(value.ToString(), out var displayName) && displayName!.Equals(name, stringComparisonOption))
17 | {
18 | enumValue = value;
19 | return true;
20 | }
21 | }
22 |
23 | return false;
24 | }
25 |
26 | private static bool TryGetDisplayName(
27 | string? value,
28 | #if NETCOREAPP3_0_OR_GREATER
29 | [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out string? displayName)
30 | #else
31 | out string? displayName)
32 | #endif
33 | {
34 | displayName = default;
35 |
36 | if (typeof(T).IsEnum)
37 | {
38 | // Prevent: Warning CS8604 Possible null reference argument for parameter 'name' in 'MemberInfo[] Type.GetMember(string name)'
39 | if (value is not null)
40 | {
41 | var memberInfo = typeof(T).GetMember(value);
42 | if (memberInfo.Length > 0)
43 | {
44 | displayName = memberInfo[0].GetCustomAttribute()?.GetName();
45 | if (displayName is null)
46 | {
47 | return false;
48 | }
49 |
50 | return true;
51 | }
52 | }
53 | }
54 |
55 | return false;
56 | }
57 |
58 | internal static string GetDisplayName(T value)
59 | {
60 | if (typeof(T).IsEnum)
61 | {
62 | var memberInfo = typeof(T).GetMember(value.ToString()!);
63 | if (memberInfo.Length > 0)
64 | {
65 | var displayName = memberInfo[0].GetCustomAttribute()!.GetName();
66 | if (displayName is null)
67 | {
68 | return string.Empty;
69 | }
70 |
71 | return displayName;
72 | }
73 | }
74 |
75 | return string.Empty;
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/NetEscapades.EnumGenerators/NetEscapades.EnumGenerators.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0
5 | false
6 | NetEscapades.EnumGenerators
7 | $(PackageId)
8 | $(PackageId)
9 | enable
10 | true
11 | A source generator for creating helper extension methods on enums using a [EnumExtensions] attribute
12 | README.md
13 | true
14 | true
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | all
23 | runtime; build; native; contentfiles; analyzers; buildtransitive
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Set default behavior to automatically normalize line endings.
3 | ###############################################################################
4 | * text=auto
5 | *.sh eol=lf
6 |
7 | ###############################################################################
8 | # Set default behavior for command prompt diff.
9 | #
10 | # This is need for earlier builds of msysgit that does not have it on by
11 | # default for csharp files.
12 | # Note: This is only used by command line
13 | ###############################################################################
14 | #*.cs diff=csharp
15 |
16 | ###############################################################################
17 | # Set the merge driver for project and solution files
18 | #
19 | # Merging from the command prompt will add diff markers to the files if there
20 | # are conflicts (Merging from VS is not affected by the settings below, in VS
21 | # the diff markers are never inserted). Diff markers may cause the following
22 | # file extensions to fail to load in VS. An alternative would be to treat
23 | # these files as binary and thus will always conflict and require user
24 | # intervention with every merge. To do so, just uncomment the entries below
25 | ###############################################################################
26 | #*.sln merge=binary
27 | #*.csproj merge=binary
28 | #*.vbproj merge=binary
29 | #*.vcxproj merge=binary
30 | #*.vcproj merge=binary
31 | #*.dbproj merge=binary
32 | #*.fsproj merge=binary
33 | #*.lsproj merge=binary
34 | #*.wixproj merge=binary
35 | #*.modelproj merge=binary
36 | #*.sqlproj merge=binary
37 | #*.wwaproj merge=binary
38 |
39 | ###############################################################################
40 | # behavior for image files
41 | #
42 | # image files are treated as binary by default.
43 | ###############################################################################
44 | #*.jpg binary
45 | #*.png binary
46 | #*.gif binary
47 |
48 | ###############################################################################
49 | # diff behavior for common document formats
50 | #
51 | # Convert binary document formats to text before diffing them. This feature
52 | # is only available from the command line. Turn it on by uncommenting the
53 | # entries below.
54 | ###############################################################################
55 | #*.doc diff=astextplain
56 | #*.DOC diff=astextplain
57 | #*.docx diff=astextplain
58 | #*.DOCX diff=astextplain
59 | #*.dot diff=astextplain
60 | #*.DOT diff=astextplain
61 | #*.pdf diff=astextplain
62 | #*.PDF diff=astextplain
63 | #*.rtf diff=astextplain
64 | #*.RTF diff=astextplain
65 |
--------------------------------------------------------------------------------
/tests/NetEscapades.EnumGenerators.Tests/NetEscapades.EnumGenerators.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | NetEscapades.EnumGenerators.Tests
5 | $(AssemblyName)
6 | enable
7 | false
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | runtime; build; native; contentfiles; analyzers; buildtransitive
22 | all
23 |
24 |
25 | runtime; build; native; contentfiles; analyzers; buildtransitive
26 | all
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | EnumExtensionsAttribute.cs
41 |
42 |
43 | MetadataSource.cs
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/tests/NetEscapades.EnumGenerators.Interceptors.IntegrationTests/NetEscapades.EnumGenerators.Interceptors.IntegrationTests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | false
5 | $(DefineConstants);INTERCEPTOR_TESTS
6 | $(DefineConstants);READONLYSPAN
7 | true
8 | true
9 | $(InterceptorsPreviewNamespaces);NetEscapades.EnumGenerators
10 | $(InterceptorsNamespaces);NetEscapades.EnumGenerators
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
26 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 | runtime; build; native; contentfiles; analyzers; buildtransitive
40 | all
41 |
42 |
43 | runtime; build; native; contentfiles; analyzers; buildtransitive
44 | all
45 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/tests/NetEscapades.EnumGenerators.NetStandard.Interceptors.IntegrationTests/NetEscapades.EnumGenerators.NetStandard.Interceptors.IntegrationTests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | false
5 | enable
6 | $(DefineConstants);NETSTANDARD_INTERCEPTOR_TESTS
7 | true
8 | $(InterceptorsPreviewNamespaces);NetEscapades.EnumGenerators
9 | $(InterceptorsNamespaces);NetEscapades.EnumGenerators
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
27 |
29 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | runtime; build; native; contentfiles; analyzers; buildtransitive
45 | all
46 |
47 |
48 | runtime; build; native; contentfiles; analyzers; buildtransitive
49 | all
50 |
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/src/NetEscapades.EnumGenerators.Attributes/EnumParseOptions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace NetEscapades.EnumGenerators;
4 |
5 | ///
6 | /// Defines the options use when parsing enums using members provided by NetEscapades.EnumGenerator.
7 | ///
8 | public readonly struct EnumParseOptions
9 | {
10 | private const StringComparison DefaultComparisonType = StringComparison.Ordinal;
11 |
12 | private readonly StringComparison? _comparisonType;
13 | private readonly bool _blockNumberParsing;
14 |
15 | ///
16 | /// Create an instance of
17 | ///
18 | /// Sets the to use during parsing.
19 | /// Sets whether the value of the selected metadata value attribute
20 | /// values applied to an enum should be used as the parse value for an enum.
21 | /// Sets a value defining whether numbers should be parsed as a fallback when
22 | /// other parsing fails.
23 | public EnumParseOptions(
24 | StringComparison comparisonType = DefaultComparisonType,
25 | bool allowMatchingMetadataAttribute = false,
26 | bool enableNumberParsing = true)
27 | {
28 | _comparisonType = comparisonType;
29 | AllowMatchingMetadataAttribute = allowMatchingMetadataAttribute;
30 | _blockNumberParsing = !enableNumberParsing;
31 | }
32 |
33 | ///
34 | /// Gets or sets the to use during parsing.
35 | ///
36 | ///
37 | /// By default, it's set to , and a case-sensitive
38 | /// comparison will be used.
39 | ///
40 | public StringComparison ComparisonType => _comparisonType ?? DefaultComparisonType;
41 |
42 | ///
43 | /// Gets or sets whether the value of the selected metadata value attribute
44 | /// values applied to an enum should be used as the parse value for an enum.
45 | ///
46 | ///
47 | /// By default, it's set to , so the value of any metadata attributes on the
48 | /// enum values are ignored. Set to to enable parsing using the applicable
49 | /// for each enum member.
50 | ///
51 | public bool AllowMatchingMetadataAttribute { get; }
52 |
53 | ///
54 | /// Gets or sets a value defining whether numbers should be parsed as a fallback when
55 | /// other parsing fails.
56 | ///
57 | ///
58 | /// By default, it's set to , and numbers will be parsed as well as names.
59 | ///
60 | public bool EnableNumberParsing => !_blockNumberParsing;
61 | }
--------------------------------------------------------------------------------
/src/NetEscapades.EnumGenerators.Interceptors/NetEscapades.EnumGenerators.Interceptors.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0
5 | false
6 | NetEscapades.EnumGenerators.Interceptors
7 | $(PackageId)
8 | $(PackageId)
9 | enable
10 | true
11 | A source generator interceptor for automatically intercepting calls to ToString() on enums, and replacing them with calls to ToStringFast() generated from NetEscapades.EnumGenerators
12 | README.md
13 | true
14 | true
15 | $(PackageTags) interceptor
16 |
17 |
18 |
19 |
20 |
21 |
22 | all
23 | runtime; build; native; contentfiles; analyzers; buildtransitive
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/src/NetEscapades.EnumGenerators/Diagnostics/HasFlagCodeFixProvider.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Immutable;
2 | using System.Composition;
3 | using Microsoft.CodeAnalysis;
4 | using Microsoft.CodeAnalysis.CodeActions;
5 | using Microsoft.CodeAnalysis.CodeFixes;
6 | using Microsoft.CodeAnalysis.CSharp;
7 | using Microsoft.CodeAnalysis.CSharp.Syntax;
8 |
9 | namespace NetEscapades.EnumGenerators.Diagnostics;
10 |
11 | [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(HasFlagCodeFixProvider)), Shared]
12 | public class HasFlagCodeFixProvider : CodeFixProvider
13 | {
14 | private const string Title = "Replace with HasFlagFast()";
15 |
16 | public sealed override ImmutableArray FixableDiagnosticIds
17 | => ImmutableArray.Create(HasFlagAnalyzer.DiagnosticId);
18 |
19 | public sealed override FixAllProvider GetFixAllProvider()
20 | => WellKnownFixAllProviders.BatchFixer;
21 |
22 | public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
23 | {
24 | var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
25 | if (root is null)
26 | {
27 | return;
28 | }
29 |
30 | var diagnostic = context.Diagnostics.First();
31 | var diagnosticSpan = diagnostic.Location.SourceSpan;
32 |
33 | // Find the node at the diagnostic location
34 | var node = root.FindNode(diagnosticSpan);
35 |
36 | // Check if this is a HasFlag invocation
37 | if (node is IdentifierNameSyntax identifierName)
38 | {
39 | // Register a code action for HasFlag() replacement
40 | context.RegisterCodeFix(
41 | CodeAction.Create(
42 | title: Title,
43 | createChangedDocument: c => ReplaceHasFlagWithHasFlagFast(context.Document, identifierName, c),
44 | equivalenceKey: Title),
45 | context.Diagnostics);
46 | }
47 | }
48 |
49 | private static async Task ReplaceHasFlagWithHasFlagFast(
50 | Document document,
51 | IdentifierNameSyntax identifierName,
52 | CancellationToken cancellationToken)
53 | {
54 | var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
55 | if (root is null)
56 | {
57 | return document;
58 | }
59 |
60 | // Create the new identifier with "HasFlagFast"
61 | var newIdentifier = SyntaxFactory.IdentifierName("HasFlagFast")
62 | .WithTriviaFrom(identifierName);
63 |
64 | // Create new member access with the new identifier
65 | var memberAccess = identifierName.Parent as MemberAccessExpressionSyntax;
66 | if (memberAccess is null)
67 | {
68 | return document;
69 | }
70 |
71 | var newMemberAccess = memberAccess.WithName(newIdentifier);
72 |
73 | // Replace the old member access with the new one
74 | var newRoot = root.ReplaceNode(memberAccess, newMemberAccess);
75 |
76 | return document.WithSyntaxRoot(newRoot);
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/.github/workflows/BuildAndPack.yml:
--------------------------------------------------------------------------------
1 | # ------------------------------------------------------------------------------
2 | #
3 | #
4 | # This code was generated.
5 | #
6 | # - To turn off auto-generation set:
7 | #
8 | # [GitHubActions (AutoGenerate = false)]
9 | #
10 | # - To trigger manual generation invoke:
11 | #
12 | # nuke --generate-configuration GitHubActions_BuildAndPack --host GitHubActions
13 | #
14 | #
15 | # ------------------------------------------------------------------------------
16 |
17 | name: BuildAndPack
18 |
19 | on:
20 | push:
21 | branches:
22 | - master
23 | - main
24 | tags:
25 | - '*'
26 | pull_request:
27 | branches:
28 | - '*'
29 |
30 | jobs:
31 | build-and-test:
32 | strategy:
33 | matrix:
34 | include:
35 | - os: windows
36 | vm: windows-latest
37 | - os: linux
38 | vm: ubuntu-latest
39 | - os: macos
40 | vm: macos-15
41 | env:
42 | MSBuildEnableWorkloadResolver: false
43 | DOTNET_SYSTEM_GLOBALIZATION_INVARIANT: "true"
44 | name: ${{ matrix.os}}
45 | runs-on: ${{ matrix.vm}}
46 | steps:
47 | - uses: actions/checkout@v4
48 | - uses: actions/setup-dotnet@v4
49 | with:
50 | dotnet-version: |
51 | 10.0.x
52 | 9.0.x
53 | 8.0.x
54 | 7.0.x
55 | 6.0.x
56 | 3.1.x
57 | 2.1.x
58 |
59 | # - run: dotnet new globaljson --sdk-version "8.0.402" --force
60 | - name: Cache .nuke/temp, ~/.nuget/packages
61 | uses: actions/cache@v3
62 | with:
63 | path: |
64 | .nuke/temp
65 | ~/.nuget/packages
66 | !~/.nuget/packages/netescapades.enumgenerators
67 | !~/.nuget/packages/netescapades.enumgenerators.interceptors
68 | key: ${{ runner.os }}-${{ hashFiles('**/global.json', '**/*.csproj') }}
69 |
70 | - name: Run './build.cmd Clean Test TestPackage PushToNuGet
71 | run: ./build.cmd Clean Test TestPackage PushToNuGet
72 | env:
73 | NuGetToken: ${{ secrets.NUGET_TOKEN || 'NOT_SET'}}
74 |
75 | - uses: actions/upload-artifact@v4
76 | with:
77 | name: packages-${{ matrix.os}}
78 | path: artifacts/packages
79 | - uses: actions/upload-artifact@v4
80 | with:
81 | name: results-${{ matrix.os}}
82 | path: artifacts/results
83 |
84 | publish-test-results:
85 | name: "Publish Tests Results"
86 | needs: build-and-test
87 | runs-on: ubuntu-latest
88 | permissions:
89 | checks: write
90 | pull-requests: write # needed unless run with comment_mode: off
91 | # contents: read # only needed for private repository
92 | # issues: read # only needed for private repository
93 | if: always()
94 |
95 | steps:
96 | - name: Download Artifacts
97 | uses: actions/download-artifact@v4
98 | with:
99 | path: artifacts/results
100 |
101 | - name: Publish Test Results
102 | uses: EnricoMi/publish-unit-test-result-action@v2
103 | with:
104 | files: "artifacts/**/*.trx"
105 | json_thousands_separator: ","
106 | check_run_annotations_branch: "*"
107 |
--------------------------------------------------------------------------------
/src/NetEscapades.EnumGenerators/Diagnostics/EnumInGenericTypeAnalyzer.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Immutable;
2 | using Microsoft.CodeAnalysis;
3 | using Microsoft.CodeAnalysis.CSharp;
4 | using Microsoft.CodeAnalysis.CSharp.Syntax;
5 | using Microsoft.CodeAnalysis.Diagnostics;
6 |
7 | namespace NetEscapades.EnumGenerators.Diagnostics;
8 |
9 | [DiagnosticAnalyzer(LanguageNames.CSharp)]
10 | public class EnumInGenericTypeAnalyzer : DiagnosticAnalyzer
11 | {
12 | public const string DiagnosticId = "NEEG002";
13 | public static readonly DiagnosticDescriptor Rule = new(
14 | #pragma warning disable RS2008 // Enable Analyzer Release Tracking
15 | id: DiagnosticId,
16 | #pragma warning restore RS2008
17 | title: "Enum in generic type not supported",
18 | messageFormat: "The enum '{0}' is nested inside a generic type. [EnumExtension] attribute is not supported.",
19 | category: "Usage",
20 | defaultSeverity: DiagnosticSeverity.Warning,
21 | isEnabledByDefault: true);
22 |
23 | public override ImmutableArray SupportedDiagnostics
24 | => ImmutableArray.Create(Rule);
25 |
26 | public override void Initialize(AnalysisContext context)
27 | {
28 | context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
29 | context.EnableConcurrentExecution();
30 | context.RegisterSyntaxNodeAction(AnalyzeEnumDeclaration, SyntaxKind.EnumDeclaration);
31 | }
32 |
33 | private static void AnalyzeEnumDeclaration(SyntaxNodeAnalysisContext context)
34 | {
35 | var enumDeclaration = (EnumDeclarationSyntax)context.Node;
36 |
37 | // Check if enum has [EnumExtensions] attribute and capture its location
38 | AttributeSyntax? enumExtensionsAttribute = null;
39 | foreach (var attributeList in enumDeclaration.AttributeLists)
40 | {
41 | foreach (var attribute in attributeList.Attributes)
42 | {
43 | // Check attribute name syntactically first
44 | var attributeName = attribute.Name.ToString();
45 | if (attributeName == "EnumExtensions" || attributeName == "EnumExtensionsAttribute")
46 | {
47 | // Verify with semantic model if needed for precision
48 | var symbolInfo = context.SemanticModel.GetSymbolInfo(attribute);
49 | if (symbolInfo.Symbol is IMethodSymbol method &&
50 | method.ContainingType.ToDisplayString() == Attributes.EnumExtensionsAttribute)
51 | {
52 | enumExtensionsAttribute = attribute;
53 | break;
54 | }
55 | }
56 | }
57 |
58 | if (enumExtensionsAttribute is not null)
59 | {
60 | break;
61 | }
62 | }
63 |
64 | if (enumExtensionsAttribute is null)
65 | {
66 | return;
67 | }
68 |
69 | // Get the enum symbol
70 | var enumSymbol = context.SemanticModel.GetDeclaredSymbol(enumDeclaration);
71 | if (enumSymbol is null)
72 | {
73 | return;
74 | }
75 |
76 | // Check if nested in generic type
77 | if (SymbolHelpers.IsNestedInGenericType(enumSymbol))
78 | {
79 | var diagnostic = Diagnostic.Create(
80 | Rule,
81 | enumExtensionsAttribute.GetLocation(),
82 | enumSymbol.Name);
83 |
84 | context.ReportDiagnostic(diagnostic);
85 | }
86 | }
87 |
88 | }
--------------------------------------------------------------------------------
/src/NetEscapades.EnumGenerators/EquatableArray.cs:
--------------------------------------------------------------------------------
1 | using System.Collections;
2 | using System.Collections.Immutable;
3 |
4 | namespace NetEscapades.EnumGenerators;
5 |
6 | ///
7 | /// An immutable, equatable array. This is equivalent to but with value equality support.
8 | ///
9 | /// The type of values in the array.
10 | public readonly struct EquatableArray : IEquatable>, IReadOnlyCollection
11 | where T : IEquatable
12 | {
13 | ///
14 | /// The underlying array.
15 | ///
16 | private readonly T[]? _array;
17 |
18 | ///
19 | /// Creates a new instance.
20 | ///
21 | /// The input to wrap.
22 | public EquatableArray(T[] array)
23 | {
24 | _array = array;
25 | }
26 |
27 | ///
28 | public bool Equals(EquatableArray array)
29 | {
30 | return AsSpan().SequenceEqual(array.AsSpan());
31 | }
32 |
33 | ///
34 | public override bool Equals(object? obj)
35 | {
36 | return obj is EquatableArray array && this.Equals(array);
37 | }
38 |
39 | ///
40 | public override int GetHashCode()
41 | {
42 | if (_array is not T[] array)
43 | {
44 | return 0;
45 | }
46 |
47 | HashCode hashCode = default;
48 |
49 | foreach (T item in array)
50 | {
51 | hashCode.Add(item);
52 | }
53 |
54 | return hashCode.ToHashCode();
55 | }
56 |
57 | ///
58 | /// Returns a wrapping the current items.
59 | ///
60 | /// A wrapping the current items.
61 | public ReadOnlySpan AsSpan()
62 | {
63 | return _array.AsSpan();
64 | }
65 |
66 | ///
67 | IEnumerator IEnumerable.GetEnumerator()
68 | {
69 | return ((IEnumerable)(_array ?? Array.Empty())).GetEnumerator();
70 | }
71 |
72 | ///
73 | IEnumerator IEnumerable.GetEnumerator()
74 | {
75 | return ((IEnumerable)(_array ?? Array.Empty())).GetEnumerator();
76 | }
77 |
78 | public int Count => _array?.Length ?? 0;
79 |
80 | ///
81 | /// Checks whether two values are the same.
82 | ///
83 | /// The first value.
84 | /// The second value.
85 | /// Whether and are equal.
86 | public static bool operator ==(EquatableArray left, EquatableArray right)
87 | {
88 | return left.Equals(right);
89 | }
90 |
91 | ///
92 | /// Checks whether two values are not the same.
93 | ///
94 | /// The first value.
95 | /// The second value.
96 | /// Whether and are not equal.
97 | public static bool operator !=(EquatableArray left, EquatableArray right)
98 | {
99 | return !left.Equals(right);
100 | }
101 | }
--------------------------------------------------------------------------------
/tests/NetEscapades.EnumGenerators.IntegrationTests/EnumInFooExtensionEverythingTests.cs:
--------------------------------------------------------------------------------
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 NETSTANDARD_SYSTEMMEMORY_INTEGRATION_TESTS
10 | namespace NetEscapades.EnumGenerators.NetStandard.SystemMemory.IntegrationTests;
11 | #elif INTERCEPTOR_TESTS
12 | namespace NetEscapades.EnumGenerators.Interceptors.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 | #elif NUGET_SYSTEMMEMORY_INTEGRATION_TESTS
18 | namespace NetEscapades.EnumGenerators.Nuget.SystemMemory.IntegrationTests;
19 | #else
20 | #error Unknown integration tests
21 | #endif
22 |
23 | public class EnumInFooExtensionEverythingTests : EnumInFooExtensionsTests
24 | {
25 | protected override string[] GetNames() => EnumInFoo.GetNames();
26 | protected override EnumInFoo[] GetValues() => EnumInFoo.GetValues();
27 | protected override int[] GetValuesAsUnderlyingType() => EnumInFoo.GetValuesAsUnderlyingType();
28 | protected override int AsUnderlyingValue(EnumInFoo value) => value.AsUnderlyingType();
29 |
30 | protected override string ToStringFast(EnumInFoo value) => value.ToStringFast();
31 | protected override string ToStringFast(EnumInFoo value, bool withMetadata) => value.ToStringFast(withMetadata);
32 | protected override bool IsDefined(EnumInFoo value) => EnumInFoo.IsDefined(value);
33 | protected override bool IsDefined(string name, bool allowMatchingMetadataAttribute) => EnumInFoo.IsDefined(name, allowMatchingMetadataAttribute);
34 | #if READONLYSPAN
35 | protected override bool IsDefined(in ReadOnlySpan name, bool allowMatchingMetadataAttribute) => EnumInFoo.IsDefined(name, allowMatchingMetadataAttribute);
36 | #endif
37 | protected override bool TryParse(string name, out EnumInFoo parsed, bool ignoreCase, bool allowMatchingMetadataAttribute)
38 | => EnumInFoo.TryParse(name, out parsed, ignoreCase, allowMatchingMetadataAttribute);
39 | #if READONLYSPAN
40 | protected override bool TryParse(in ReadOnlySpan name, out EnumInFoo parsed, bool ignoreCase, bool allowMatchingMetadataAttribute)
41 | => EnumInFoo.TryParse(name, out parsed, ignoreCase, allowMatchingMetadataAttribute);
42 | #endif
43 | protected override bool TryParse(string name, out EnumInFoo parsed, EnumParseOptions parseOptions)
44 | => EnumInFoo.TryParse(name, out parsed, parseOptions);
45 | #if READONLYSPAN
46 | protected override bool TryParse(in ReadOnlySpan name, out EnumInFoo parsed, EnumParseOptions parseOptions)
47 | => EnumInFoo.TryParse(name, out parsed, parseOptions);
48 | #endif
49 |
50 | protected override EnumInFoo Parse(string name, bool ignoreCase, bool allowMatchingMetadataAttribute)
51 | => EnumInFoo.Parse(name, ignoreCase, allowMatchingMetadataAttribute);
52 | #if READONLYSPAN
53 | protected override EnumInFoo Parse(in ReadOnlySpan name, bool ignoreCase, bool allowMatchingMetadataAttribute)
54 | => EnumInFoo.Parse(name, ignoreCase, allowMatchingMetadataAttribute);
55 | #endif
56 | protected override EnumInFoo Parse(string name, EnumParseOptions parseOptions)
57 | => EnumInFoo.Parse(name, parseOptions);
58 | #if READONLYSPAN
59 | protected override EnumInFoo Parse(in ReadOnlySpan name, EnumParseOptions parseOptions)
60 | => EnumInFoo.Parse(name, parseOptions);
61 | #endif
62 | }
63 |
--------------------------------------------------------------------------------
/src/NetEscapades.EnumGenerators.Interceptors/EquatableArray.cs:
--------------------------------------------------------------------------------
1 | using System.Collections;
2 | using System.Collections.Immutable;
3 |
4 | namespace NetEscapades.EnumGenerators.Interceptors;
5 |
6 | ///
7 | /// An immutable, equatable array. This is equivalent to but with value equality support.
8 | ///
9 | /// The type of values in the array.
10 | public readonly struct EquatableArray : IEquatable>, IEnumerable
11 | where T : IEquatable
12 | {
13 | ///
14 | /// The underlying array.
15 | ///
16 | private readonly T[]? _array;
17 |
18 | ///
19 | /// Creates a new instance.
20 | ///
21 | /// The input to wrap.
22 | public EquatableArray(T[] array)
23 | {
24 | _array = array;
25 | }
26 |
27 | ///
28 | public bool Equals(EquatableArray array)
29 | {
30 | return AsSpan().SequenceEqual(array.AsSpan());
31 | }
32 |
33 | ///
34 | public override bool Equals(object? obj)
35 | {
36 | return obj is EquatableArray array && this.Equals(array);
37 | }
38 |
39 | ///
40 | public override int GetHashCode()
41 | {
42 | if (_array is not T[] array)
43 | {
44 | return 0;
45 | }
46 |
47 | HashCode hashCode = default;
48 |
49 | foreach (T item in array)
50 | {
51 | hashCode.Add(item);
52 | }
53 |
54 | return hashCode.ToHashCode();
55 | }
56 |
57 | ///
58 | /// Returns a wrapping the current items.
59 | ///
60 | /// A wrapping the current items.
61 | public ReadOnlySpan AsSpan()
62 | {
63 | return _array.AsSpan();
64 | }
65 |
66 | ///
67 | IEnumerator IEnumerable.GetEnumerator()
68 | {
69 | return ((IEnumerable)(_array ?? Array.Empty())).GetEnumerator();
70 | }
71 |
72 | ///
73 | IEnumerator IEnumerable.GetEnumerator()
74 | {
75 | return ((IEnumerable)(_array ?? Array.Empty())).GetEnumerator();
76 | }
77 |
78 | public int Count => _array?.Length ?? 0;
79 |
80 | ///
81 | /// Checks whether two values are the same.
82 | ///
83 | /// The first value.
84 | /// The second value.
85 | /// Whether and are equal.
86 | public static bool operator ==(EquatableArray left, EquatableArray right)
87 | {
88 | return left.Equals(right);
89 | }
90 |
91 | ///
92 | /// Checks whether two values are not the same.
93 | ///
94 | /// The first value.
95 | /// The second value.
96 | /// Whether and are not equal.
97 | public static bool operator !=(EquatableArray left, EquatableArray right)
98 | {
99 | return !left.Equals(right);
100 | }
101 | }
--------------------------------------------------------------------------------
/tests/NetEscapades.EnumGenerators.Tests/SourceGenerationHelperSnapshotTests.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Text;
3 | using System.Threading.Tasks;
4 | using VerifyXunit;
5 | using Xunit;
6 |
7 | namespace NetEscapades.EnumGenerators.Tests;
8 |
9 | [UsesVerify]
10 | public class SourceGenerationHelperSnapshotTests
11 | {
12 | private const MetadataSource DefaultMetadataSource = MetadataSource.EnumMemberAttribute;
13 |
14 | [Theory]
15 | [CombinatorialData]
16 | public Task GeneratesEnumCorrectly(
17 | bool csharp14IsSupported,
18 | [CombinatorialValues(MetadataSource.None, MetadataSource.EnumMemberAttribute)]
19 | MetadataSource defaultSource,
20 | bool useCollectionExpressions)
21 | {
22 | var value = new EnumToGenerate(
23 | "ShortName",
24 | "Something.Blah",
25 | "Something.Blah.ShortName",
26 | "int",
27 | isPublic: true,
28 | new List<(string Key, EnumValueOption Value)>
29 | {
30 | ("First", EnumValueOption.CreateWithoutAttributes(0)),
31 | ("Second", EnumValueOption.CreateWithoutAttributes(1)),
32 | },
33 | hasFlags: false,
34 | metadataSource: null);
35 |
36 | var result = SourceGenerationHelper.GenerateExtensionClass(
37 | value,
38 | csharp14IsSupported,
39 | useCollectionExpressions: useCollectionExpressions,
40 | defaultSource).Content;
41 |
42 | return Verifier.Verify(result)
43 | .ScrubExpectedChanges()
44 | .UseDirectory("Snapshots")
45 | .UseTextForParameters($"{csharp14IsSupported}_{defaultSource}_{useCollectionExpressions}");
46 | }
47 |
48 | [Theory]
49 | [CombinatorialData]
50 | public Task GeneratesEnumWithRepeatedValuesCorrectly(bool csharp14IsSupported)
51 | {
52 | var value = new EnumToGenerate(
53 | "ShortName",
54 | "Something.Blah",
55 | "Something.Blah.ShortName",
56 | "int",
57 | isPublic: true,
58 | new List<(string Key, EnumValueOption Value)>
59 | {
60 | ("First", EnumValueOption.CreateWithoutAttributes(0)),
61 | ("Second", EnumValueOption.CreateWithoutAttributes(1)),
62 | ("Third", EnumValueOption.CreateWithoutAttributes(0)),
63 | },
64 | hasFlags: false,
65 | null);
66 |
67 | var result = SourceGenerationHelper.GenerateExtensionClass(value, csharp14IsSupported,
68 | useCollectionExpressions: false, DefaultMetadataSource).Content;
69 |
70 | return Verifier.Verify(result)
71 | .ScrubExpectedChanges()
72 | .UseDirectory("Snapshots")
73 | .UseParameters(csharp14IsSupported);
74 | }
75 |
76 | [Theory]
77 | [CombinatorialData]
78 | public Task GeneratesFlagsEnumCorrectly(bool csharp14IsSupported)
79 | {
80 | var value = new EnumToGenerate(
81 | "ShortName",
82 | "Something.Blah",
83 | "Something.Blah.ShortName",
84 | "int",
85 | isPublic: true,
86 | new List<(string, EnumValueOption)>
87 | {
88 | ("First", EnumValueOption.CreateWithoutAttributes(0)),
89 | ("Second", EnumValueOption.CreateWithoutAttributes(1)),
90 | },
91 | hasFlags: true,
92 | metadataSource: null);
93 |
94 | var result = SourceGenerationHelper.GenerateExtensionClass(value, csharp14IsSupported,
95 | useCollectionExpressions: false, DefaultMetadataSource).Content;
96 |
97 | return Verifier.Verify(result)
98 | .ScrubExpectedChanges()
99 | .UseDirectory("Snapshots")
100 | .UseParameters(csharp14IsSupported);
101 | }
102 | }
--------------------------------------------------------------------------------
/src/NetEscapades.EnumGenerators.Attributes/EnumExtensionsAttribute.cs:
--------------------------------------------------------------------------------
1 | namespace NetEscapades.EnumGenerators
2 | {
3 | ///
4 | /// Add to enums to indicate that extension methods should be generated for the type
5 | ///
6 | [System.AttributeUsage(System.AttributeTargets.Enum)]
7 | [System.Diagnostics.Conditional("NETESCAPADES_ENUMGENERATORS_USAGES")]
8 | public class EnumExtensionsAttribute : System.Attribute
9 | {
10 | ///
11 | /// The namespace to generate the extension class.
12 | /// If not provided, the namespace of the enum will be used.
13 | ///
14 | public string? ExtensionClassNamespace { get; set; }
15 |
16 | ///
17 | /// The name to use for the extension class.
18 | /// If not provided, the enum name with an Extensions suffix will be used.
19 | /// For example for an Enum called StatusCodes, the default name
20 | /// will be StatusCodesExtensions.
21 | ///
22 | public string? ExtensionClassName { get; set; }
23 |
24 | ///
25 | /// The metadata source to use when serializing and deserializing using
26 | /// ToStringFast() and TryParse(). If not provided
27 | /// will be
28 | /// used to provide the values. Alternatively, you can disable this feature
29 | /// entirely by using .
30 | ///
31 | public MetadataSource MetadataSource { get; set; } = MetadataSource.EnumMemberAttribute;
32 |
33 | ///
34 | /// By default, when used with NetEscapades.EnumGenerators.Interceptors
35 | /// any interceptable usages of the enum will be replaced by usages of
36 | /// the extension method in this project. To disable interception of
37 | /// the enum in this project when used with the interceptable package,
38 | /// set to .
39 | ///
40 | public bool IsInterceptable { get; set; } = true;
41 | }
42 |
43 | ///
44 | /// Add to enums to indicate that extension methods should be generated for the type
45 | ///
46 | [System.AttributeUsage(System.AttributeTargets.Assembly, AllowMultiple = true)]
47 | [System.Diagnostics.Conditional("NETESCAPADES_ENUMGENERATORS_USAGES")]
48 | public class EnumExtensionsAttribute : System.Attribute
49 | where T: System.Enum
50 | {
51 | ///
52 | /// The namespace to generate the extension class.
53 | /// If not provided, the namespace of the enum will be used.
54 | ///
55 | public string? ExtensionClassNamespace { get; set; }
56 |
57 | ///
58 | /// The name to use for the extension class.
59 | /// If not provided, the enum name with an Extensions suffix will be used.
60 | /// For example for an Enum called StatusCodes, the default name
61 | /// will be StatusCodesExtensions.
62 | ///
63 | public string? ExtensionClassName { get; set; }
64 |
65 | ///
66 | /// The metadata source to use when serializing and deserializing using
67 | /// ToStringFast() and TryParse(). If not provided, the
68 | /// will be
69 | /// used to provide the values. Alternatively, you can disable this feature
70 | /// entirely by using .
71 | ///
72 | public MetadataSource MetadataSource { get; set; } = MetadataSource.EnumMemberAttribute;
73 |
74 | ///
75 | /// By default, when used with NetEscapades.EnumGenerators.Interceptors
76 | /// any interceptable usages of the enum will be replaced by usages of
77 | /// the extension method in this project. To disable interception of
78 | /// the enum in this project when used with the interceptable package,
79 | /// set to .
80 | ///
81 | public bool IsInterceptable { get; set; } = true;
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/NetEscapades.EnumGenerators/Diagnostics/HasFlagAnalyzer.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Immutable;
2 | using Microsoft.CodeAnalysis;
3 | using Microsoft.CodeAnalysis.CSharp;
4 | using Microsoft.CodeAnalysis.CSharp.Syntax;
5 | using Microsoft.CodeAnalysis.Diagnostics;
6 |
7 | namespace NetEscapades.EnumGenerators.Diagnostics;
8 |
9 | [DiagnosticAnalyzer(LanguageNames.CSharp)]
10 | public class HasFlagAnalyzer : DiagnosticAnalyzer
11 | {
12 | public const string DiagnosticId = "NEEG005";
13 | public static readonly DiagnosticDescriptor Rule = new(
14 | #pragma warning disable RS2008 // Enable Analyzer Release Tracking
15 | id: DiagnosticId,
16 | #pragma warning restore RS2008
17 | title: "Use HasFlagFast() instead of HasFlag()",
18 | messageFormat: "Use HasFlagFast() instead of HasFlag() for better performance on enum '{0}'",
19 | category: "Usage",
20 | defaultSeverity: DiagnosticSeverity.Info,
21 | isEnabledByDefault: true);
22 |
23 | public override ImmutableArray SupportedDiagnostics
24 | => ImmutableArray.Create(Rule);
25 |
26 | public override void Initialize(AnalysisContext context)
27 | {
28 | context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
29 | context.EnableConcurrentExecution();
30 | context.RegisterCompilationStartAction(ctx =>
31 | {
32 | var (enumExtensionsAttr, externalEnumTypes) = AnalyzerHelpers.GetEnumExtensionAttributes(ctx.Compilation);
33 | if (enumExtensionsAttr is null || externalEnumTypes is null)
34 | {
35 | return;
36 | }
37 |
38 | ctx.RegisterSyntaxNodeAction(
39 | c => AnalyzeInvocation(c, enumExtensionsAttr, externalEnumTypes),
40 | SyntaxKind.InvocationExpression);
41 | });
42 | }
43 |
44 | private static void AnalyzeInvocation(SyntaxNodeAnalysisContext context, INamedTypeSymbol enumExtensionsAttr, ExternalEnumDictionary externalEnumTypes)
45 | {
46 | var invocation = (InvocationExpressionSyntax)context.Node;
47 |
48 | // Check if this is a member access expression (e.g., value.HasFlag())
49 | if (invocation.Expression is not MemberAccessExpressionSyntax memberAccess)
50 | {
51 | return;
52 | }
53 |
54 | // Check if the method name is "HasFlag"
55 | if (memberAccess.Name.Identifier.Text != "HasFlag")
56 | {
57 | return;
58 | }
59 |
60 | // Check if there is exactly one argument
61 | if (invocation.ArgumentList.Arguments.Count != 1)
62 | {
63 | return;
64 | }
65 |
66 | // Get the symbol information for the invocation
67 | var symbolInfo = context.SemanticModel.GetSymbolInfo(invocation);
68 | if (symbolInfo.Symbol is not IMethodSymbol methodSymbol)
69 | {
70 | return;
71 | }
72 |
73 | // Verify this is the HasFlag() method from System.Enum
74 | if (methodSymbol.Name != "HasFlag" ||
75 | methodSymbol.Parameters.Length != 1 ||
76 | methodSymbol.ContainingType.SpecialType != SpecialType.System_Enum)
77 | {
78 | return;
79 | }
80 |
81 | // Get the type of the receiver (the thing before .HasFlag())
82 | var receiverType = context.SemanticModel.GetTypeInfo(memberAccess.Expression).Type;
83 | if (receiverType is null || receiverType.TypeKind != TypeKind.Enum)
84 | {
85 | return;
86 | }
87 |
88 | if (!AnalyzerHelpers.IsEnumWithExtensions(receiverType, enumExtensionsAttr, externalEnumTypes, out var extensionNamespace, out var extensionClass))
89 | {
90 | return;
91 | }
92 |
93 | // Report the diagnostic
94 | var diagnostic = Diagnostic.Create(
95 | descriptor: Rule,
96 | location: memberAccess.Name.GetLocation(),
97 | messageArgs: receiverType.Name,
98 | properties: ImmutableDictionary.CreateRange([
99 | new(nameof(EnumExtensionsAttribute.ExtensionClassNamespace), extensionNamespace),
100 | new(nameof(EnumExtensionsAttribute.ExtensionClassName), extensionClass),
101 | ]));
102 |
103 | context.ReportDiagnostic(diagnostic);
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/tests/NetEscapades.EnumGenerators.IntegrationTests/EnumInFooExtensionsTests.cs:
--------------------------------------------------------------------------------
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 NETSTANDARD_SYSTEMMEMORY_INTEGRATION_TESTS
10 | namespace NetEscapades.EnumGenerators.NetStandard.SystemMemory.IntegrationTests;
11 | #elif INTERCEPTOR_TESTS
12 | namespace NetEscapades.EnumGenerators.Interceptors.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 | #elif NUGET_SYSTEMMEMORY_INTEGRATION_TESTS
18 | namespace NetEscapades.EnumGenerators.Nuget.SystemMemory.IntegrationTests;
19 | #else
20 | #error Unknown integration tests
21 | #endif
22 |
23 | public class EnumInFooExtensionsTests : ExtensionTests, ITestData
24 | {
25 | public TheoryData ValidEnumValues() => new()
26 | {
27 | EnumInFoo.First,
28 | EnumInFoo.Second,
29 | (EnumInFoo)3,
30 | };
31 |
32 | public TheoryData ValuesToParse() => new()
33 | {
34 | "First",
35 | "Second",
36 | "2nd",
37 | "2ND",
38 | "first",
39 | "SECOND",
40 | "3",
41 | "267",
42 | "-267",
43 | "2147483647",
44 | "3000000000",
45 | "Fourth",
46 | "Fifth",
47 | };
48 |
49 | protected override string[] GetNames() => EnumInFooExtensions.GetNames();
50 | protected override EnumInFoo[] GetValues() => EnumInFooExtensions.GetValues();
51 | protected override int[] GetValuesAsUnderlyingType() => EnumInFooExtensions.GetValuesAsUnderlyingType();
52 | protected override int AsUnderlyingValue(EnumInFoo value) => value.AsUnderlyingType();
53 |
54 | protected override string ToStringFast(EnumInFoo value) => value.ToStringFast();
55 | protected override string ToStringFast(EnumInFoo value, bool withMetadata) => value.ToStringFast(withMetadata);
56 | protected override string ToStringFast(EnumInFoo value, SerializationOptions options) => value.ToStringFast(options);
57 | protected override bool IsDefined(EnumInFoo value) => EnumInFooExtensions.IsDefined(value);
58 | protected override bool IsDefined(string name, bool allowMatchingMetadataAttribute) => EnumInFooExtensions.IsDefined(name, allowMatchingMetadataAttribute);
59 | #if READONLYSPAN
60 | protected override bool IsDefined(in ReadOnlySpan name, bool allowMatchingMetadataAttribute) => EnumInFooExtensions.IsDefined(name, allowMatchingMetadataAttribute);
61 | #endif
62 | protected override bool TryParse(string name, out EnumInFoo parsed, bool ignoreCase, bool allowMatchingMetadataAttribute)
63 | => EnumInFooExtensions.TryParse(name, out parsed, ignoreCase, allowMatchingMetadataAttribute);
64 | #if READONLYSPAN
65 | protected override bool TryParse(in ReadOnlySpan name, out EnumInFoo parsed, bool ignoreCase, bool allowMatchingMetadataAttribute)
66 | => EnumInFooExtensions.TryParse(name, out parsed, ignoreCase, allowMatchingMetadataAttribute);
67 | #endif
68 | protected override bool TryParse(string name, out EnumInFoo parsed, EnumParseOptions parseOptions)
69 | => EnumInFooExtensions.TryParse(name, out parsed, parseOptions);
70 | #if READONLYSPAN
71 | protected override bool TryParse(in ReadOnlySpan name, out EnumInFoo parsed, EnumParseOptions parseOptions)
72 | => EnumInFooExtensions.TryParse(name, out parsed, parseOptions);
73 | #endif
74 |
75 | protected override EnumInFoo Parse(string name, bool ignoreCase, bool allowMatchingMetadataAttribute)
76 | => EnumInFooExtensions.Parse(name, ignoreCase, allowMatchingMetadataAttribute);
77 | #if READONLYSPAN
78 | protected override EnumInFoo Parse(in ReadOnlySpan name, bool ignoreCase, bool allowMatchingMetadataAttribute)
79 | => EnumInFooExtensions.Parse(name, ignoreCase, allowMatchingMetadataAttribute);
80 | #endif
81 | protected override EnumInFoo Parse(string name, EnumParseOptions parseOptions)
82 | => EnumInFooExtensions.Parse(name, parseOptions);
83 | #if READONLYSPAN
84 | protected override EnumInFoo Parse(in ReadOnlySpan name, EnumParseOptions parseOptions)
85 | => EnumInFooExtensions.Parse(name, parseOptions);
86 | #endif
87 | }
--------------------------------------------------------------------------------
/tests/NetEscapades.EnumGenerators.IntegrationTests/LongEnumExtensionsTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using NetEscapades.EnumGenerators;
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 NETSTANDARD_SYSTEMMEMORY_INTEGRATION_TESTS
10 | namespace NetEscapades.EnumGenerators.NetStandard.SystemMemory.IntegrationTests;
11 | #elif INTERCEPTOR_TESTS
12 | namespace NetEscapades.EnumGenerators.Interceptors.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 | #elif NUGET_SYSTEMMEMORY_INTEGRATION_TESTS
18 | namespace NetEscapades.EnumGenerators.Nuget.SystemMemory.IntegrationTests;
19 | #else
20 | #error Unknown integration tests
21 | #endif
22 |
23 | public class LongEnumExtensionsTests : ExtensionTests, ITestData
24 | {
25 | public TheoryData ValidEnumValues() => new()
26 | {
27 | LongEnum.First,
28 | LongEnum.Second,
29 | (LongEnum)3,
30 | };
31 |
32 | public TheoryData ValuesToParse() => new()
33 | {
34 | "First",
35 | "Second",
36 | "2nd",
37 | "2ND",
38 | "first",
39 | "SECOND",
40 | "3",
41 | "267",
42 | "-267",
43 | "2147483647",
44 | "3000000000",
45 | "Fourth",
46 | "Fifth",
47 | };
48 |
49 | protected override string[] GetNames() => LongEnumExtensions.GetNames();
50 | protected override LongEnum[] GetValues() => LongEnumExtensions.GetValues();
51 | protected override long[] GetValuesAsUnderlyingType() => LongEnumExtensions.GetValuesAsUnderlyingType();
52 | protected override long AsUnderlyingValue(LongEnum value) => value.AsUnderlyingType();
53 |
54 | protected override string ToStringFast(LongEnum value) => value.ToStringFast();
55 | protected override string ToStringFast(LongEnum value, bool withMetadata) => value.ToStringFast(withMetadata);
56 | protected override string ToStringFast(LongEnum value, SerializationOptions options) => value.ToStringFast(options);
57 | protected override bool IsDefined(LongEnum value) => LongEnumExtensions.IsDefined(value);
58 | protected override bool IsDefined(string name, bool allowMatchingMetadataAttribute) => LongEnumExtensions.IsDefined(name, allowMatchingMetadataAttribute: false);
59 | #if READONLYSPAN
60 | protected override bool IsDefined(in ReadOnlySpan name, bool allowMatchingMetadataAttribute) => LongEnumExtensions.IsDefined(name, allowMatchingMetadataAttribute: false);
61 | #endif
62 | protected override bool TryParse(string name, out LongEnum parsed, bool ignoreCase, bool allowMatchingMetadataAttribute)
63 | => LongEnumExtensions.TryParse(name, out parsed, ignoreCase, allowMatchingMetadataAttribute);
64 | #if READONLYSPAN
65 | protected override bool TryParse(in ReadOnlySpan name, out LongEnum parsed, bool ignoreCase, bool allowMatchingMetadataAttribute)
66 | => LongEnumExtensions.TryParse(name, out parsed, ignoreCase, allowMatchingMetadataAttribute);
67 | #endif
68 | protected override bool TryParse(string name, out LongEnum parsed, EnumParseOptions parseOptions)
69 | => LongEnumExtensions.TryParse(name, out parsed, parseOptions);
70 | #if READONLYSPAN
71 | protected override bool TryParse(in ReadOnlySpan name, out LongEnum parsed, EnumParseOptions parseOptions)
72 | => LongEnumExtensions.TryParse(name, out parsed, parseOptions);
73 | #endif
74 |
75 | protected override LongEnum Parse(string name, bool ignoreCase, bool allowMatchingMetadataAttribute)
76 | => LongEnumExtensions.Parse(name, ignoreCase, allowMatchingMetadataAttribute);
77 | #if READONLYSPAN
78 | protected override LongEnum Parse(in ReadOnlySpan name, bool ignoreCase, bool allowMatchingMetadataAttribute)
79 | => LongEnumExtensions.Parse(name, ignoreCase, allowMatchingMetadataAttribute);
80 | #endif
81 | protected override LongEnum Parse(string name, EnumParseOptions parseOptions)
82 | => LongEnumExtensions.Parse(name, parseOptions);
83 | #if READONLYSPAN
84 | protected override LongEnum Parse(in ReadOnlySpan name, EnumParseOptions parseOptions)
85 | => LongEnumExtensions.Parse(name, parseOptions);
86 | #endif
87 | }
--------------------------------------------------------------------------------
/src/NetEscapades.EnumGenerators/Diagnostics/AnalyzerHelpers.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics.CodeAnalysis;
2 | using Microsoft.CodeAnalysis;
3 |
4 | namespace NetEscapades.EnumGenerators.Diagnostics;
5 |
6 | public static class AnalyzerHelpers
7 | {
8 | public static (INamedTypeSymbol? enumExtensionsAttr, ExternalEnumDictionary? externalEnumTypes) GetEnumExtensionAttributes(Compilation compilation)
9 | {
10 | var enumExtensionsAttr =
11 | compilation.GetTypeByMetadataName(Attributes.EnumExtensionsAttribute);
12 | var externalEnumExtensionsAttr =
13 | compilation.GetTypeByMetadataName(Attributes.ExternalEnumExtensionsAttribute);
14 |
15 | if (enumExtensionsAttr is null)
16 | {
17 | return (enumExtensionsAttr, null);
18 | }
19 |
20 | // Collect all enum types that have EnumExtensions attributes
21 | var externalEnumTypes = new ExternalEnumDictionary(SymbolEqualityComparer.Default);
22 | if (externalEnumExtensionsAttr is not null)
23 | {
24 | foreach (var attribute in compilation.Assembly.GetAttributes())
25 | {
26 | if (attribute.AttributeClass is { IsGenericType: true } attrClass &&
27 | SymbolEqualityComparer.Default.Equals(attrClass.ConstructedFrom, externalEnumExtensionsAttr) &&
28 | attrClass.TypeArguments is [INamedTypeSymbol { TypeKind: TypeKind.Enum } enumType])
29 | {
30 | var details = ExtractExtensionClassDetails(enumType, attribute);
31 | externalEnumTypes.Add(enumType, details);
32 | }
33 | }
34 | }
35 |
36 | return (enumExtensionsAttr, externalEnumTypes);
37 | }
38 |
39 | public static bool IsEnumWithExtensions(
40 | ITypeSymbol receiverType,
41 | INamedTypeSymbol enumExtensionsAttr,
42 | ExternalEnumDictionary externalEnumTypes,
43 | [NotNullWhen(true)] out string? extensionNamespace,
44 | [NotNullWhen(true)] out string? extensionClass)
45 | {
46 | // Check if the enum has the [EnumExtensions] attribute or is referenced in EnumExtensions
47 | // First check if the enum itself has the attribute
48 | foreach (var attributeData in receiverType.GetAttributes())
49 | {
50 | if (SymbolEqualityComparer.Default.Equals(
51 | attributeData.AttributeClass,
52 | enumExtensionsAttr))
53 | {
54 | (extensionNamespace, extensionClass) = ExtractExtensionClassDetails(receiverType, attributeData);
55 | return true;
56 | }
57 | }
58 |
59 | // If not, check if it's in the external enum types (EnumExtensions)
60 | if(receiverType is INamedTypeSymbol namedType
61 | && externalEnumTypes.TryGetValue(namedType, out var details))
62 | {
63 | extensionNamespace = details.Namespace;
64 | extensionClass = details.Class;
65 | return true;
66 | }
67 |
68 | extensionNamespace = null;
69 | extensionClass = null;
70 | return false;
71 | }
72 |
73 | private static (string Namespace, string Class) ExtractExtensionClassDetails(
74 | ITypeSymbol receiverType,
75 | AttributeData attributeData)
76 | {
77 | string? nameSpace = null;
78 | string? className = null;
79 |
80 | if (!attributeData.NamedArguments.IsDefaultOrEmpty)
81 | {
82 | // extract the extension namespace
83 | foreach (var (key, value) in attributeData.NamedArguments)
84 | {
85 | if (key == nameof(EnumExtensionsAttribute.ExtensionClassNamespace) &&
86 | value is { Kind: TypedConstantKind.Primitive, Value: string ns } &&
87 | !string.IsNullOrWhiteSpace(ns))
88 | {
89 | nameSpace = ns;
90 | }
91 | if (key == nameof(EnumExtensionsAttribute.ExtensionClassName) &&
92 | value is { Kind: TypedConstantKind.Primitive, Value: string name } &&
93 | !string.IsNullOrWhiteSpace(name))
94 | {
95 | className = name;
96 | }
97 | }
98 | }
99 |
100 | return (Namespace: nameSpace ?? EnumGenerator.GetEnumExtensionNamespace(receiverType),
101 | Class: className ?? EnumGenerator.GetEnumExtensionName(receiverType));
102 | }
103 | }
--------------------------------------------------------------------------------
/tests/NetEscapades.EnumGenerators.IntegrationTests/EnumInNamespaceExtensionsTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Xunit;
3 |
4 | #if INTEGRATION_TESTS
5 | namespace NetEscapades.EnumGenerators.IntegrationTests;
6 | #elif NETSTANDARD_INTEGRATION_TESTS
7 | namespace NetEscapades.EnumGenerators.NetStandard.IntegrationTests;
8 | #elif NETSTANDARD_SYSTEMMEMORY_INTEGRATION_TESTS
9 | namespace NetEscapades.EnumGenerators.NetStandard.SystemMemory.IntegrationTests;
10 | #elif INTERCEPTOR_TESTS
11 | namespace NetEscapades.EnumGenerators.Interceptors.IntegrationTests;
12 | #elif NUGET_INTEGRATION_TESTS
13 | namespace NetEscapades.EnumGenerators.Nuget.IntegrationTests;
14 | #elif NUGET_INTERCEPTOR_TESTS
15 | namespace NetEscapades.EnumGenerators.Nuget.Interceptors.IntegrationTests;
16 | #elif NUGET_SYSTEMMEMORY_INTEGRATION_TESTS
17 | namespace NetEscapades.EnumGenerators.Nuget.SystemMemory.IntegrationTests;
18 | #else
19 | #error Unknown integration tests
20 | #endif
21 |
22 | public class EnumInNamespaceExtensionsTests : ExtensionTests, ITestData
23 | {
24 | public TheoryData ValidEnumValues() => new()
25 | {
26 | EnumInNamespace.First,
27 | EnumInNamespace.Second,
28 | (EnumInNamespace)3,
29 | };
30 |
31 | public TheoryData ValuesToParse() => new()
32 | {
33 | "First",
34 | "Second",
35 | "2nd",
36 | "2ND",
37 | "first",
38 | "SECOND",
39 | "3",
40 | "267",
41 | "-267",
42 | "2147483647",
43 | "3000000000",
44 | "Fourth",
45 | "Fifth",
46 | };
47 |
48 | protected override string[] GetNames() => EnumInNamespaceExtensions.GetNames();
49 | protected override EnumInNamespace[] GetValues() => EnumInNamespaceExtensions.GetValues();
50 | protected override int[] GetValuesAsUnderlyingType() => EnumInNamespaceExtensions.GetValuesAsUnderlyingType();
51 | protected override int AsUnderlyingValue(EnumInNamespace value) => value.AsUnderlyingType();
52 |
53 | protected override string ToStringFast(EnumInNamespace value) => value.ToStringFast();
54 | protected override string ToStringFast(EnumInNamespace value, bool withMetadata) => value.ToStringFast(withMetadata);
55 | protected override string ToStringFast(EnumInNamespace value, SerializationOptions options) => value.ToStringFast(options);
56 | protected override bool IsDefined(EnumInNamespace value) => EnumInNamespaceExtensions.IsDefined(value);
57 | protected override bool IsDefined(string name, bool allowMatchingMetadataAttribute) => EnumInNamespaceExtensions.IsDefined(name, allowMatchingMetadataAttribute);
58 | #if READONLYSPAN
59 | protected override bool IsDefined(in ReadOnlySpan name, bool allowMatchingMetadataAttribute) => EnumInNamespaceExtensions.IsDefined(name, allowMatchingMetadataAttribute);
60 | #endif
61 | protected override bool TryParse(string name, out EnumInNamespace parsed, bool ignoreCase, bool allowMatchingMetadataAttribute)
62 | => EnumInNamespaceExtensions.TryParse(name, out parsed, ignoreCase, allowMatchingMetadataAttribute);
63 | #if READONLYSPAN
64 | protected override bool TryParse(in ReadOnlySpan name, out EnumInNamespace parsed, bool ignoreCase, bool allowMatchingMetadataAttribute)
65 | => EnumInNamespaceExtensions.TryParse(name, out parsed, ignoreCase, allowMatchingMetadataAttribute);
66 | #endif
67 | protected override bool TryParse(string name, out EnumInNamespace parsed, EnumParseOptions parseOptions)
68 | => EnumInNamespaceExtensions.TryParse(name, out parsed, parseOptions);
69 | #if READONLYSPAN
70 | protected override bool TryParse(in ReadOnlySpan name, out EnumInNamespace parsed, EnumParseOptions parseOptions)
71 | => EnumInNamespaceExtensions.TryParse(name, out parsed, parseOptions);
72 | #endif
73 |
74 | protected override EnumInNamespace Parse(string name, bool ignoreCase, bool allowMatchingMetadataAttribute)
75 | => EnumInNamespaceExtensions.Parse(name, ignoreCase, allowMatchingMetadataAttribute);
76 | #if READONLYSPAN
77 | protected override EnumInNamespace Parse(in ReadOnlySpan name, bool ignoreCase, bool allowMatchingMetadataAttribute)
78 | => EnumInNamespaceExtensions.Parse(name, ignoreCase, allowMatchingMetadataAttribute);
79 | #endif
80 | protected override EnumInNamespace Parse(string name, EnumParseOptions parseOptions)
81 | => EnumInNamespaceExtensions.Parse(name, parseOptions);
82 | #if READONLYSPAN
83 | protected override EnumInNamespace Parse(in ReadOnlySpan name, EnumParseOptions parseOptions)
84 | => EnumInNamespaceExtensions.Parse(name, parseOptions);
85 | #endif
86 | }
--------------------------------------------------------------------------------
/tests/NetEscapades.EnumGenerators.IntegrationTests/ExternalEnumExtensionsTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using FluentAssertions;
3 | using Foo;
4 | using Xunit;
5 |
6 | #if INTEGRATION_TESTS
7 | namespace NetEscapades.EnumGenerators.IntegrationTests;
8 | #elif NETSTANDARD_INTEGRATION_TESTS
9 | namespace NetEscapades.EnumGenerators.NetStandard.IntegrationTests;
10 | #elif NETSTANDARD_SYSTEMMEMORY_INTEGRATION_TESTS
11 | namespace NetEscapades.EnumGenerators.NetStandard.SystemMemory.IntegrationTests;
12 | #elif INTERCEPTOR_TESTS
13 | namespace NetEscapades.EnumGenerators.Interceptors.IntegrationTests;
14 | #elif NUGET_INTEGRATION_TESTS
15 | namespace NetEscapades.EnumGenerators.Nuget.IntegrationTests;
16 | #elif NUGET_INTERCEPTOR_TESTS
17 | namespace NetEscapades.EnumGenerators.Nuget.Interceptors.IntegrationTests;
18 | #elif NUGET_SYSTEMMEMORY_INTEGRATION_TESTS
19 | namespace NetEscapades.EnumGenerators.Nuget.SystemMemory.IntegrationTests;
20 | #else
21 | #error Unknown integration tests
22 | #endif
23 |
24 | public class ExternalEnumExtensionsTests : ExtensionTests, ITestData
25 | {
26 | public TheoryData ValidEnumValues() => new()
27 | {
28 | DateTimeKind.Unspecified,
29 | DateTimeKind.Utc,
30 | (DateTimeKind)3, // not actually valid
31 | };
32 |
33 | public TheoryData ValuesToParse() => new()
34 | {
35 | "Unspecified",
36 | "Utc",
37 | "Local",
38 | "NotLocal",
39 | "SomethingElse",
40 | "utc",
41 | "UTC",
42 | "3",
43 | "267",
44 | "-267",
45 | "2147483647",
46 | "3000000000",
47 | "Fourth",
48 | "Fifth",
49 | };
50 |
51 | protected override string[] GetNames() => DateTimeKindExtensions.GetNames();
52 | protected override DateTimeKind[] GetValues() => DateTimeKindExtensions.GetValues();
53 | protected override int[] GetValuesAsUnderlyingType() => DateTimeKindExtensions.GetValuesAsUnderlyingType();
54 | protected override int AsUnderlyingValue(DateTimeKind value) => value.AsUnderlyingType();
55 |
56 | protected override string ToStringFast(DateTimeKind value) => value.ToStringFast();
57 | protected override string ToStringFast(DateTimeKind value, bool withMetadata) => value.ToStringFast(withMetadata);
58 | protected override string ToStringFast(DateTimeKind value, SerializationOptions options) => value.ToStringFast(options);
59 | protected override bool IsDefined(DateTimeKind value) => DateTimeKindExtensions.IsDefined(value);
60 | protected override bool IsDefined(string name, bool allowMatchingMetadataAttribute) => DateTimeKindExtensions.IsDefined(name, allowMatchingMetadataAttribute: false);
61 | #if READONLYSPAN
62 | protected override bool IsDefined(in ReadOnlySpan name, bool allowMatchingMetadataAttribute) => DateTimeKindExtensions.IsDefined(name, allowMatchingMetadataAttribute: false);
63 | #endif
64 | protected override bool TryParse(string name, out DateTimeKind parsed, bool ignoreCase, bool allowMatchingMetadataAttribute)
65 | => DateTimeKindExtensions.TryParse(name, out parsed, ignoreCase, allowMatchingMetadataAttribute);
66 | #if READONLYSPAN
67 | protected override bool TryParse(in ReadOnlySpan name, out DateTimeKind parsed, bool ignoreCase, bool allowMatchingMetadataAttribute)
68 | => DateTimeKindExtensions.TryParse(name, out parsed, ignoreCase, allowMatchingMetadataAttribute);
69 | #endif
70 | protected override bool TryParse(string name, out DateTimeKind parsed, EnumParseOptions parseOptions)
71 | => DateTimeKindExtensions.TryParse(name, out parsed, parseOptions);
72 | #if READONLYSPAN
73 | protected override bool TryParse(in ReadOnlySpan name, out DateTimeKind parsed, EnumParseOptions parseOptions)
74 | => DateTimeKindExtensions.TryParse(name, out parsed, parseOptions);
75 | #endif
76 |
77 | protected override DateTimeKind Parse(string name, bool ignoreCase, bool allowMatchingMetadataAttribute)
78 | => DateTimeKindExtensions.Parse(name, ignoreCase, allowMatchingMetadataAttribute);
79 | #if READONLYSPAN
80 | protected override DateTimeKind Parse(in ReadOnlySpan name, bool ignoreCase, bool allowMatchingMetadataAttribute)
81 | => DateTimeKindExtensions.Parse(name, ignoreCase, allowMatchingMetadataAttribute);
82 | #endif
83 | protected override DateTimeKind Parse(string name, EnumParseOptions parseOptions)
84 | => DateTimeKindExtensions.Parse(name, parseOptions);
85 | #if READONLYSPAN
86 | protected override DateTimeKind Parse(in ReadOnlySpan name, EnumParseOptions parseOptions)
87 | => DateTimeKindExtensions.Parse(name, parseOptions);
88 | #endif
89 | }
--------------------------------------------------------------------------------
/src/NetEscapades.EnumGenerators/Diagnostics/DuplicateEnumValueAnalyzer.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Immutable;
2 | using System.Linq;
3 | using Microsoft.CodeAnalysis;
4 | using Microsoft.CodeAnalysis.CSharp;
5 | using Microsoft.CodeAnalysis.CSharp.Syntax;
6 | using Microsoft.CodeAnalysis.Diagnostics;
7 |
8 | namespace NetEscapades.EnumGenerators.Diagnostics;
9 |
10 | [DiagnosticAnalyzer(LanguageNames.CSharp)]
11 | public class DuplicateEnumValueAnalyzer : DiagnosticAnalyzer
12 | {
13 | public const string DiagnosticId = "NEEG003";
14 | public static readonly DiagnosticDescriptor Rule = new(
15 | #pragma warning disable RS2008 // Enable Analyzer Release Tracking
16 | id: DiagnosticId,
17 | #pragma warning restore RS2008
18 | title: "Enum has duplicate values and will give inconsistent values for ToStringFast()",
19 | messageFormat: "The enum member '{0}' has the same value as a previous member so will return the '{1}' value for ToStringFast()",
20 | category: "Usage",
21 | defaultSeverity: DiagnosticSeverity.Info,
22 | isEnabledByDefault: true);
23 |
24 | public override ImmutableArray SupportedDiagnostics
25 | => ImmutableArray.Create(Rule);
26 |
27 | public override void Initialize(AnalysisContext context)
28 | {
29 | context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
30 | context.EnableConcurrentExecution();
31 | context.RegisterSyntaxNodeAction(AnalyzeEnumDeclaration, SyntaxKind.EnumDeclaration);
32 | }
33 |
34 | private static void AnalyzeEnumDeclaration(SyntaxNodeAnalysisContext context)
35 | {
36 | var enumDeclaration = (EnumDeclarationSyntax)context.Node;
37 |
38 | // Check if enum has [EnumExtensions] attribute
39 | bool hasEnumExtensionsAttribute = false;
40 | foreach (var attributeList in enumDeclaration.AttributeLists)
41 | {
42 | foreach (var attribute in attributeList.Attributes)
43 | {
44 | // Check attribute name syntactically first
45 | var attributeName = attribute.Name.ToString();
46 | if (attributeName == "EnumExtensions" || attributeName == "EnumExtensionsAttribute")
47 | {
48 | // Verify with semantic model for precision
49 | var symbolInfo = context.SemanticModel.GetSymbolInfo(attribute);
50 | if (symbolInfo.Symbol is IMethodSymbol method &&
51 | method.ContainingType.ToDisplayString() == Attributes.EnumExtensionsAttribute)
52 | {
53 | hasEnumExtensionsAttribute = true;
54 | break;
55 | }
56 | }
57 | }
58 |
59 | if (hasEnumExtensionsAttribute)
60 | {
61 | break;
62 | }
63 | }
64 |
65 | if (!hasEnumExtensionsAttribute)
66 | {
67 | return;
68 | }
69 |
70 | // Get the enum symbol
71 | var enumSymbol = context.SemanticModel.GetDeclaredSymbol(enumDeclaration);
72 | if (enumSymbol is null)
73 | {
74 | return;
75 | }
76 |
77 | // Track which constant values we've seen and the first name
78 | var seenValues = new Dictionary