├── .gitattributes
├── .github
└── workflows
│ └── BuildAndPack.yml
├── .gitignore
├── .nuke
└── parameters.json
├── CHANGELOG.md
├── Directory.Build.props
├── LICENSE
├── NetEscapades.EnumGenerators.sln
├── NuGet.integration-tests.config
├── README.md
├── build.cmd
├── build.ps1
├── build.sh
├── build
├── .editorconfig
├── Build.cs
├── Configuration.cs
├── Directory.Build.props
├── Directory.Build.targets
├── _build.csproj
└── _build.csproj.DotSettings
├── global.json
├── icon.png
├── icon_32.png
├── releasenotes.props
├── samples
└── ToStringFastExample
│ ├── Program.cs
│ └── ToStringFastExample.csproj
├── src
├── NetEscapades.EnumGenerators.Attributes
│ ├── EnumExtensionsAttribute.cs
│ └── NetEscapades.EnumGenerators.Attributes.csproj
├── NetEscapades.EnumGenerators.Interceptors.Attributes
│ ├── InterceptableAttribute.cs
│ └── NetEscapades.EnumGenerators.Interceptors.Attributes.csproj
├── NetEscapades.EnumGenerators.Interceptors
│ ├── Constants.cs
│ ├── DiagnosticHelper.cs
│ ├── EnumToIntercept.cs
│ ├── EquatableArray.cs
│ ├── HashCode.cs
│ ├── InterceptorGenerator.cs
│ ├── LocationInfo.cs
│ ├── NetEscapades.EnumGenerators.Interceptors.csproj
│ ├── NetEscapades.EnumGenerators.Interceptors.props
│ ├── NetEscapades.EnumGenerators.Interceptors.targets
│ ├── README.md
│ ├── SourceGenerationHelper.cs
│ └── TrackingNames.cs
└── NetEscapades.EnumGenerators
│ ├── Attributes.cs
│ ├── Constants.cs
│ ├── EnumGenerator.cs
│ ├── EnumToGenerate.cs
│ ├── EnumValueOption.cs
│ ├── EquatableArray.cs
│ ├── HashCode.cs
│ ├── LocationInfo.cs
│ ├── NetEscapades.EnumGenerators.csproj
│ ├── README.md
│ ├── SourceGenerationHelper.cs
│ └── TrackingNames.cs
├── tests
├── Directory.Build.props
├── NetEscapades.EnumGenerators.Benchmarks
│ ├── EnumHelper.cs
│ ├── NetEscapades.EnumGenerators.Benchmarks.csproj
│ └── Program.cs
├── NetEscapades.EnumGenerators.IntegrationTests
│ ├── EnumInFooExtensionsTests.cs
│ ├── EnumInNamespaceExtensionsTests.cs
│ ├── EnumWithDescriptionInNamespaceExtensionsTests.cs
│ ├── EnumWithDisplayNameInNamespaceExtensionsTests.cs
│ ├── EnumWithSameDisplayNameExtensionsTests.cs
│ ├── Enums.cs
│ ├── ExtensionTests.cs
│ ├── ExternalEnumExtensionsTests.cs
│ ├── ExternalFlagsEnumExtensionsTests.cs
│ ├── FlagsEnumExtensionsTests.cs
│ ├── LongEnumExtensionsTests.cs
│ ├── NetEscapades.EnumGenerators.IntegrationTests.csproj
│ └── xunit.runner.json
├── NetEscapades.EnumGenerators.Interceptors.IntegrationTests
│ ├── Enums.cs
│ ├── InterceptorTests.cs
│ ├── NetEscapades.EnumGenerators.Interceptors.IntegrationTests.csproj
│ └── xunit.runner.json
├── NetEscapades.EnumGenerators.NetStandard.IntegrationTests
│ └── NetEscapades.EnumGenerators.NetStandard.IntegrationTests.csproj
├── NetEscapades.EnumGenerators.NetStandard.Interceptors.IntegrationTests
│ ├── InterceptionAttributes.cs
│ └── NetEscapades.EnumGenerators.NetStandard.Interceptors.IntegrationTests.csproj
├── NetEscapades.EnumGenerators.NetStandard
│ └── NetEscapades.EnumGenerators.NetStandard.csproj
├── NetEscapades.EnumGenerators.Nuget.Attributes.IntegrationTests
│ └── NetEscapades.EnumGenerators.Nuget.Attributes.IntegrationTests.csproj
├── NetEscapades.EnumGenerators.Nuget.IntegrationTests
│ ├── NetEscapades.EnumGenerators.Nuget.IntegrationTests.Project.targets
│ └── NetEscapades.EnumGenerators.Nuget.IntegrationTests.csproj
├── NetEscapades.EnumGenerators.Nuget.Interceptors.IntegrationTests
│ └── NetEscapades.EnumGenerators.Nuget.Interceptors.IntegrationTests.csproj
├── NetEscapades.EnumGenerators.Nuget.NetStandard.Interceptors.IntegrationTests
│ ├── InterceptionAttributes.cs
│ └── NetEscapades.EnumGenerators.Nuget.NetStandard.Interceptors.IntegrationTests.csproj
├── NetEscapades.EnumGenerators.Nuget.NetStandard
│ └── NetEscapades.EnumGenerators.Nuget.NetStandard.csproj
└── NetEscapades.EnumGenerators.Tests
│ ├── EnumGeneratorTests.cs
│ ├── EquatableArrayTests.cs
│ ├── InterceptorTests.cs
│ ├── NetEscapades.EnumGenerators.Tests.Project.targets
│ ├── NetEscapades.EnumGenerators.Tests.Roslyn4_04.csproj
│ ├── NetEscapades.EnumGenerators.Tests.csproj
│ ├── Snapshots
│ ├── EnumGeneratorTests.CanGenerateEnumExtensionsForFlagsEnum_Params.verified.txt
│ ├── EnumGeneratorTests.CanGenerateEnumExtensionsInChildNamespace.verified.txt
│ ├── EnumGeneratorTests.CanGenerateEnumExtensionsInGlobalNamespace.verified.txt
│ ├── EnumGeneratorTests.CanGenerateEnumExtensionsInNestedClass.verified.txt
│ ├── EnumGeneratorTests.CanGenerateEnumExtensionsWithCustomName.verified.txt
│ ├── EnumGeneratorTests.CanGenerateEnumExtensionsWithCustomNames.verified.txt
│ ├── EnumGeneratorTests.CanGenerateEnumExtensionsWithCustomNamespace.verified.txt
│ ├── EnumGeneratorTests.CanGenerateEnumExtensionsWithCustomNamespaceAndName.verified.txt
│ ├── EnumGeneratorTests.CanGenerateEnumExtensionsWithSameDisplayName.verified.txt
│ ├── EnumGeneratorTests.CanGenerateExternalEnumExtensionsWithCustomName.verified.txt
│ ├── EnumGeneratorTests.CanGenerateExternalEnumExtensionsWithCustomNamespace.verified.txt
│ ├── EnumGeneratorTests.CanGenerateExternalEnumExtensionsWithCustomNamespaceAndName.verified.txt
│ ├── EnumGeneratorTests.CanGenerateForExternalEnum.verified.txt
│ ├── EnumGeneratorTests.CanGenerateForExternalFlagsEnum.verified.txt
│ ├── EnumGeneratorTests.CanGenerateForMultipleExternalEnums.verified.txt
│ ├── EnumGeneratorTests.CanHandleNamespaceAndClassNameAreTheSame.verified.txt
│ ├── EnumGeneratorTests.DoesNotGenerateWarningsForObsoleteEnums_CS0612_Issue97.verified.txt
│ ├── EnumGeneratorTests.DoesNotGenerateWarningsForObsoleteEnums_CS0618_Issue97.verified.txt
│ ├── EnumGeneratorTests.DoesNotGenerateWarningsForObsoleteMembers_CS0612_Issue97.verified.txt
│ ├── EnumGeneratorTests.DoesNotGenerateWarningsForObsoleteMembers_CS0618_Issue97.verified.txt
│ ├── EnumGeneratorTests.HandlesStringsWithQuotesAndSlashesInDescription.verified.txt
│ ├── InterceptorTests.CanHandleEnumsWithSameNameInDifferentNamespaces.verified.txt
│ ├── InterceptorTests.CanInterceptEnumInDifferentNamespace.verified.txt
│ ├── InterceptorTests.CanInterceptEnumInGlobalNamespace.verified.txt
│ ├── InterceptorTests.CanInterceptExternalEnumToString.verified.txt
│ ├── InterceptorTests.CanInterceptHasFlag.verified.txt
│ ├── InterceptorTests.CanInterceptMultipleEnumsToString.verified.txt
│ ├── InterceptorTests.CanInterceptToString.verified.txt
│ ├── InterceptorTests.CanInterceptToStringWhenCsharp11.verified.txt
│ ├── InterceptorTests.CanInterceptUsingInterceptableAttributeToString.verified.txt
│ ├── InterceptorTests.CanNotInterceptEnumWithoutInterceptorPackage.verified.txt
│ ├── InterceptorTests.CanNotInterceptUsageInStringInterpolation.verified.txt
│ ├── InterceptorTests.CanNotInterceptUsageWithEnumDirectly.verified.txt
│ ├── InterceptorTests.DoesNotGenerateWarningsForUseOfObsoleteEnums_CS0612_Issue97.verified.txt
│ ├── InterceptorTests.DoesNotGenerateWarningsForUseOfObsoleteEnums_CS0618_Issue97.verified.txt
│ ├── InterceptorTests.DoesNotInterceptEnumMarkedAsNotInterceptable.verified.txt
│ ├── InterceptorTests.DoesNotInterceptExternalEnumMarkedAsNotInterceptable.verified.txt
│ ├── InterceptorTests.DoesNotInterceptToStringWhenDisabled.verified.txt
│ ├── InterceptorTests.DoesNotInterceptToStringWhenOldCsharp.verified.txt
│ ├── SourceGenerationHelperSnapshotTests.GeneratesEnumCorrectly.verified.txt
│ └── SourceGenerationHelperSnapshotTests.GeneratesFlagsEnumCorrectly.verified.txt
│ ├── SourceGenerationHelperSnapshotTests.cs
│ └── TestHelpers.cs
└── version.props
/.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 |
--------------------------------------------------------------------------------
/.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.attributes
68 | !~/.nuget/packages/netescapades.enumgenerators.interceptors
69 | !~/.nuget/packages/netescapades.enumgenerators.interceptors.attributes
70 | key: ${{ runner.os }}-${{ hashFiles('**/global.json', '**/*.csproj') }}
71 |
72 | - name: Run './build.cmd Clean Test TestPackage PushToNuGet
73 | run: ./build.cmd Clean Test TestPackage PushToNuGet
74 | env:
75 | NuGetToken: ${{ secrets.NUGET_TOKEN || 'NOT_SET'}}
76 |
77 | - uses: actions/upload-artifact@v4
78 | with:
79 | name: packages-${{ matrix.os}}
80 | path: artifacts/packages
81 | - uses: actions/upload-artifact@v4
82 | with:
83 | name: results-${{ matrix.os}}
84 | path: artifacts/results
85 |
86 | publish-test-results:
87 | name: "Publish Tests Results"
88 | needs: build-and-test
89 | runs-on: ubuntu-latest
90 | permissions:
91 | checks: write
92 | pull-requests: write # needed unless run with comment_mode: off
93 | # contents: read # only needed for private repository
94 | # issues: read # only needed for private repository
95 | if: always()
96 |
97 | steps:
98 | - name: Download Artifacts
99 | uses: actions/download-artifact@v4
100 | with:
101 | path: artifacts/results
102 |
103 | - name: Publish Test Results
104 | uses: EnricoMi/publish-unit-test-result-action@v2
105 | with:
106 | files: "artifacts/**/*.trx"
107 | json_thousands_separator: ","
108 | check_run_annotations_branch: "*"
109 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 |
4 | # User-specific files
5 | .idea
6 | *.suo
7 | *.user
8 | *.userosscache
9 | *.sln.docstates
10 |
11 | # User-specific files (MonoDevelop/Xamarin Studio)
12 | *.userprefs
13 |
14 | # Build results
15 | [Dd]ebug/
16 | [Dd]ebugPublic/
17 | [Rr]elease/
18 | [Rr]eleases/
19 | [Xx]64/
20 | [Xx]86/
21 | bld/
22 | [Bb]in/
23 | [Oo]bj/
24 |
25 | # Visual Studio 2015 cache/options directory
26 | .vs/
27 | # Uncomment if you have tasks that create the project's static files in wwwroot
28 | #wwwroot/
29 |
30 | # MSTest test Results
31 | [Tt]est[Rr]esult*/
32 | [Bb]uild[Ll]og.*
33 |
34 | # NUNIT
35 | *.VisualState.xml
36 | TestResult.xml
37 |
38 | # Build Results of an ATL Project
39 | [Dd]ebugPS/
40 | [Rr]eleasePS/
41 | dlldata.c
42 |
43 | # DNX
44 | project.lock.json
45 | artifacts/
46 |
47 | *_i.c
48 | *_p.c
49 | *_i.h
50 | *.ilk
51 | *.meta
52 | *.obj
53 | *.pch
54 | *.pdb
55 | *.pgc
56 | *.pgd
57 | *.rsp
58 | *.sbr
59 | *.tlb
60 | *.tli
61 | *.tlh
62 | *.tmp
63 | *.tmp_proj
64 | *.log
65 | *.vspscc
66 | *.vssscc
67 | .builds
68 | *.pidb
69 | *.svclog
70 | *.scc
71 |
72 | # Chutzpah Test files
73 | _Chutzpah*
74 |
75 | # Visual C++ cache files
76 | ipch/
77 | *.aps
78 | *.ncb
79 | *.opendb
80 | *.opensdf
81 | *.sdf
82 | *.cachefile
83 | *.VC.db
84 |
85 | # Visual Studio profiler
86 | *.psess
87 | *.vsp
88 | *.vspx
89 | *.sap
90 |
91 | # TFS 2012 Local Workspace
92 | $tf/
93 |
94 | # Guidance Automation Toolkit
95 | *.gpState
96 |
97 | # ReSharper is a .NET coding add-in
98 | _ReSharper*/
99 | *.[Rr]e[Ss]harper
100 | *.DotSettings.user
101 |
102 | # JustCode is a .NET coding add-in
103 | .JustCode
104 |
105 | # TeamCity is a build add-in
106 | _TeamCity*
107 |
108 | # DotCover is a Code Coverage Tool
109 | *.dotCover
110 |
111 | # NCrunch
112 | _NCrunch_*
113 | .*crunch*.local.xml
114 | nCrunchTemp_*
115 |
116 | # MightyMoose
117 | *.mm.*
118 | AutoTest.Net/
119 |
120 | # Web workbench (sass)
121 | .sass-cache/
122 |
123 | # Installshield output folder
124 | [Ee]xpress/
125 |
126 | # DocProject is a documentation generator add-in
127 | DocProject/buildhelp/
128 | DocProject/Help/*.HxT
129 | DocProject/Help/*.HxC
130 | DocProject/Help/*.hhc
131 | DocProject/Help/*.hhk
132 | DocProject/Help/*.hhp
133 | DocProject/Help/Html2
134 | DocProject/Help/html
135 |
136 | # Click-Once directory
137 | publish/
138 |
139 | # Publish Web Output
140 | *.[Pp]ublish.xml
141 | *.azurePubxml
142 |
143 | # TODO: Un-comment the next line if you do not want to checkin
144 | # your web deploy settings because they may include unencrypted
145 | # passwords
146 | #*.pubxml
147 | *.publishproj
148 |
149 | # NuGet Packages
150 | *.nupkg
151 | # The packages folder can be ignored because of Package Restore
152 | **/packages/*
153 | # except build/, which is used as an MSBuild target.
154 | !**/packages/build/
155 | # Uncomment if necessary however generally it will be regenerated when needed
156 | #!**/packages/repositories.config
157 | # NuGet v3's project.json files produces more ignoreable files
158 | *.nuget.props
159 | *.nuget.targets
160 |
161 | # Microsoft Azure Build Output
162 | csx/
163 | *.build.csdef
164 |
165 | # Microsoft Azure Emulator
166 | ecf/
167 | rcf/
168 |
169 | # Microsoft Azure ApplicationInsights config file
170 | ApplicationInsights.config
171 |
172 | # Windows Store app package directory
173 | AppPackages/
174 | BundleArtifacts/
175 |
176 | # Visual Studio cache files
177 | # files ending in .cache can be ignored
178 | *.[Cc]ache
179 | # but keep track of directories ending in .cache
180 | !*.[Cc]ache/
181 |
182 | # Others
183 | ClientBin/
184 | [Ss]tyle[Cc]op.*
185 | ~$*
186 | *~
187 | *.dbmdl
188 | *.dbproj.schemaview
189 | *.pfx
190 | *.publishsettings
191 | node_modules/
192 | orleans.codegen.cs
193 |
194 | # RIA/Silverlight projects
195 | Generated_Code/
196 |
197 | # Backup & report files from converting an old project file
198 | # to a newer Visual Studio version. Backup files are not needed,
199 | # because we have git ;-)
200 | _UpgradeReport_Files/
201 | Backup*/
202 | UpgradeLog*.XML
203 | UpgradeLog*.htm
204 |
205 | # SQL Server files
206 | *.mdf
207 | *.ldf
208 |
209 | # Business Intelligence projects
210 | *.rdl.data
211 | *.bim.layout
212 | *.bim_*.settings
213 |
214 | # Microsoft Fakes
215 | FakesAssemblies/
216 |
217 | # GhostDoc plugin setting file
218 | *.GhostDoc.xml
219 |
220 | # Node.js Tools for Visual Studio
221 | .ntvs_analysis.dat
222 |
223 | # Visual Studio 6 build log
224 | *.plg
225 |
226 | # Visual Studio 6 workspace options file
227 | *.opt
228 |
229 | # Visual Studio LightSwitch build output
230 | **/*.HTMLClient/GeneratedArtifacts
231 | **/*.DesktopClient/GeneratedArtifacts
232 | **/*.DesktopClient/ModelManifest.xml
233 | **/*.Server/GeneratedArtifacts
234 | **/*.Server/ModelManifest.xml
235 | _Pvt_Extensions
236 |
237 | # LightSwitch generated files
238 | GeneratedArtifacts/
239 | ModelManifest.xml
240 |
241 | # Paket dependency manager
242 | .paket/paket.exe
243 |
244 | # FAKE - F# Make
245 | .fake/
246 | tools/*
247 | !tools/packages.config
248 |
249 |
250 | *.received.txt
251 | BenchmarkDotNet.Artifacts
252 |
253 | # Nuke.build files
254 | .nuke/build.schema.json
--------------------------------------------------------------------------------
/.nuke/parameters.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "./build.schema.json",
3 | "Solution": "NetEscapades.EnumGenerators.sln"
4 | }
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | Changelog
2 | ---
3 |
4 | ## [v1.0.0-beta13](https://github.com/andrewlock/NetEscapades.EnumGenerators/compare/v1.0.0-beta12..v1.0.0-beta13) (2025-05-11)
5 |
6 | ### Features
7 | - Added `AsUnderlyingType()` and `GetValuesAsUnderlyingType()` (#125) Thanks [@TheConstructor](https://github.com/TheConstructor)!
8 |
9 | ### Fixes
10 | - Replaced multiline verbatim strings (`@""`) with raw string literals (#87) Thanks [@Guiorgy](https://github.com/Guiorgy)!
11 | - Update `GetValues`, `GetNames` and `GetValuesAsUnderlyingType` to match System.Enum order (#134) Thanks [@TheConstructor](https://github.com/TheConstructor)!
12 | - Directly call `ToString` on unnamed numeric values to reduce overhead (#135) [@TheConstructor](https://github.com/TheConstructor)!
13 |
14 | ## [v1.0.0-beta12](https://github.com/andrewlock/NetEscapades.EnumGenerators/compare/v1.0.0-beta11..v1.0.0-beta12) (2025-01-26)
15 |
16 | ### Breaking Changes:
17 | - By default, `ToStringFast()` no longer uses `[DisplayName]` and `[Description]` by default. The original behaviour can be restored by passing `allowMatchingMetadataAttribute:true` (#122)
18 | - Split the experimental interceptor support into a separate project, NetEscapades.EnumGenerators.Interceptors (#125)
19 | - Enable interception by default when NetEscapades.EnumGenerators.Interceptors is added (#127)
20 |
21 | ### Features
22 | - Added a package logo (#125)
23 |
24 | ### Fixes
25 | - Fixed indentation in generated code so it aligns properly with 4 spaces (#120) Thanks [@karl-sjogren](https://github.com/karl-sjogren)!
26 | - Fix missing global on System namespace usages (#118) Thanks [@henrikwidlund](https://github.com/henrikwidlund)!
27 | - Don't use `using`s in generated code (#129)
28 |
29 | ## [v1.0.0-beta11](https://github.com/andrewlock/NetEscapades.EnumGenerators/compare/v1.0.0-beta09..v1.0.0-beta11) (2024-10-24)
30 |
31 | ### Features
32 | - Add optional interceptor for ToString() and HasFlag() (#94, #101, #104, #105, #106, #108, #113)
33 | - Ignore usages of obsolete enum members in generated code (#111)
34 |
35 | ### Fixes
36 | - Fix escaping of strings in description and display attributes (#109)
37 | - Ensure we can handle enums with the same name in different namespaces (#114)
38 | - Fix naming conflicts in System namespace (#118) Thanks [@henrikwidlund](https://github.com/henrikwidlund)!
39 |
40 | ## [v1.0.0-beta09](https://github.com/andrewlock/NetEscapades.EnumGenerators/compare/v1.0.0-beta08..v1.0.0-beta09) (2024-05-15)
41 |
42 | ### Features
43 | - Add support for generating extensions for external enums that come from other assemblies (#82)
44 | - Add support for new Parse overloads (#85)
45 |
46 | ### Fixes
47 | - Add `[GeneratedCodeAttribute]` to the generated extensions (#83)
48 | - Split TryParse code method to try to fix coverlet issue (#84)
49 |
50 |
51 | ## [v1.0.0-beta08](https://github.com/andrewlock/NetEscapades.EnumGenerators/compare/v1.0.0-beta07..v1.0.0-beta08) (2023-06-05)
52 |
53 | ### Fixes
54 |
55 | - Exclude embedded attribute from code coverage [#59](https://github.com/andrewlock/NetEscapades.EnumGenerators/pull/59) (Thanks [@erri120](https://github.com/erri120)!)
56 | - Fix Error when a class with the same name as namespace [#62](https://github.com/andrewlock/NetEscapades.EnumGenerators/pull/62)
57 | - Support quotes and slashes in description/displayname attribute [#63](https://github.com/andrewlock/NetEscapades.EnumGenerators/pull/63)
58 |
59 | ## [v1.0.0-beta07](https://github.com/andrewlock/NetEscapades.EnumGenerators/compare/v1.0.0-beta06..v1.0.0-beta07) (2023-03-09)
60 |
61 | ### Fixes
62 |
63 | * Add `global::` prefix to System namespace references [#55](https://github.com/andrewlock/NetEscapades.EnumGenerators/pull/55)
64 |
65 | ## [v1.0.0-beta06](https://github.com/andrewlock/NetEscapades.EnumGenerators/compare/v1.0.0-beta05..v1.0.0-beta06) (2022-12-20)
66 |
67 | ### Fixes
68 |
69 | * Fix XML comments for public generated members [#49](https://github.com/andrewlock/NetEscapades.EnumGenerators/pull/49)
70 |
71 | ## [v1.0.0-beta05](https://github.com/andrewlock/NetEscapades.EnumGenerators/compare/v1.0.0-beta04..v1.0.0-beta05) (2022-12-19)
72 |
73 | ### Features
74 |
75 | * Add support for overriding `ToStringFast()` and related methods by adding `[Description]` attribute to members [#46](https://github.com/andrewlock/NetEscapades.EnumGenerators/pull/46)
76 | * Add support for overriding `ToStringFast()` and related methods by adding `[Display]` attribute to members [#13](https://github.com/andrewlock/NetEscapades.EnumGenerators/pull/13) (thanks [@adamradocz](https://github.com/adamradocz))
77 | * Add a .NET 4.5.1 target to the attributes dll, to reduce dependencies introduced by .NET Standard [#45](https://github.com/andrewlock/NetEscapades.EnumGenerators/pull/45)
78 | * Add parsing overloads for `ReadOnlySpan` [#16](https://github.com/andrewlock/NetEscapades.EnumGenerators/pull/16) (thanks [@adamradocz](https://github.com/adamradocz))
79 | * Add `Length` extension method [#7](https://github.com/andrewlock/NetEscapades.EnumGenerators/pull/7) (thanks [@tothalexlaszlo](https://github.com/tothalexlaszlo))
80 |
81 | ### Fixes
82 | * Fix `HasFlagsFast()` implementation [#44](https://github.com/andrewlock/NetEscapades.EnumGenerators/pull/46)
83 | * Add XML documentation for attributes [9a38580cdc9e](https://github.com/andrewlock/NetEscapades.EnumGenerators/commit/9a38580cdc9e51b113dcd08bff168e0151b87e2d)
84 | * Add XML comments to public generated members and fix formatting [#42](https://github.com/andrewlock/NetEscapades.EnumGenerators/pull/42)
85 | * Fixed spelling of `isDisplayAttributeUsed` property [#17](https://github.com/andrewlock/NetEscapades.EnumGenerators/pull/17) (thanks [@JasonLautzenheiser](https://github.com/JasonLautzenheiser))
86 |
87 | ### Refactoring
88 |
89 | * Use DotNet.ReproducibleBuilds [#35](https://github.com/andrewlock/NetEscapades.EnumGenerators/pull/35) (thanks [@HavenDV](https://github.com/HavenDV))
90 | * Switch to `ForAttributeWithMetadataName` [#41](https://github.com/andrewlock/NetEscapades.EnumGenerators/pull/41)
91 | * Update README [#8](https://github.com/andrewlock/NetEscapades.EnumGenerators/pull/8) [#20](https://github.com/andrewlock/NetEscapades.EnumGenerators/pull/20) [#1](https://github.com/andrewlock/NetEscapades.EnumGenerators/pull/1) (thanks [@tothalexlaszlo](https://github.com/tothalexlaszlo), [@Rekkonnect](https://github.com/Rekkonnect))
92 | * Fix typo [#5](https://github.com/andrewlock/NetEscapades.EnumGenerators/pull/5)
93 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/NetEscapades.EnumGenerators.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.30114.105
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{78D56637-F921-437D-A9AC-FBEAD78344BE}"
7 | EndProject
8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{FF6F43E8-36EA-4BF0-9B2E-8ACC91C5F939}"
9 | EndProject
10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetEscapades.EnumGenerators.IntegrationTests", "tests\NetEscapades.EnumGenerators.IntegrationTests\NetEscapades.EnumGenerators.IntegrationTests.csproj", "{443BC4C1-CF11-45E3-B772-72F886826C69}"
11 | EndProject
12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetEscapades.EnumGenerators.Nuget.IntegrationTests", "tests\NetEscapades.EnumGenerators.Nuget.IntegrationTests\NetEscapades.EnumGenerators.Nuget.IntegrationTests.csproj", "{F03831B0-630E-4B0E-BD25-AFE975B35E69}"
13 | EndProject
14 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetEscapades.EnumGenerators.Tests", "tests\NetEscapades.EnumGenerators.Tests\NetEscapades.EnumGenerators.Tests.csproj", "{328EFEB4-1D15-453E-9929-6FD0FACF3512}"
15 | EndProject
16 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "_build", "build\_build.csproj", "{C4F84B6F-D2AE-4B3D-9CAD-90E6BC71F524}"
17 | EndProject
18 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetEscapades.EnumGenerators.Benchmarks", "tests\NetEscapades.EnumGenerators.Benchmarks\NetEscapades.EnumGenerators.Benchmarks.csproj", "{C7CFC4AC-BC5B-4F07-BE93-4F245D5547D9}"
19 | EndProject
20 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetEscapades.EnumGenerators.Nuget.Attributes.IntegrationTests", "tests\NetEscapades.EnumGenerators.Nuget.Attributes.IntegrationTests\NetEscapades.EnumGenerators.Nuget.Attributes.IntegrationTests.csproj", "{E7D66FE9-65BC-4DC8-AC86-5C5BE10D8E61}"
21 | EndProject
22 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{AC0DDDB0-385D-43B8-A770-C79EE2077D05}"
23 | EndProject
24 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ToStringFastExample", "samples\ToStringFastExample\ToStringFastExample.csproj", "{BDF8C04B-D0E3-4FF5-82C3-E8FDF3916C16}"
25 | EndProject
26 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetEscapades.EnumGenerators.NetStandard", "tests\NetEscapades.EnumGenerators.NetStandard\NetEscapades.EnumGenerators.NetStandard.csproj", "{40B2D8D1-7523-498D-9753-CECC3A44071A}"
27 | EndProject
28 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetEscapades.EnumGenerators.NetStandard.IntegrationTests", "tests\NetEscapades.EnumGenerators.NetStandard.IntegrationTests\NetEscapades.EnumGenerators.NetStandard.IntegrationTests.csproj", "{7A0F5EA5-DB7B-49E0-827D-946C80175322}"
29 | EndProject
30 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetEscapades.EnumGenerators.Nuget.Interceptors.IntegrationTests", "tests\NetEscapades.EnumGenerators.Nuget.Interceptors.IntegrationTests\NetEscapades.EnumGenerators.Nuget.Interceptors.IntegrationTests.csproj", "{1A1B48ED-DDE7-4017-A86B-5F7D2FB3A3D0}"
31 | EndProject
32 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetEscapades.EnumGenerators.Interceptors.IntegrationTests", "tests\NetEscapades.EnumGenerators.Interceptors.IntegrationTests\NetEscapades.EnumGenerators.Interceptors.IntegrationTests.csproj", "{E390E06A-27EE-49B3-9113-37812B92060A}"
33 | EndProject
34 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetEscapades.EnumGenerators.NetStandard.Interceptors.IntegrationTests", "tests\NetEscapades.EnumGenerators.NetStandard.Interceptors.IntegrationTests\NetEscapades.EnumGenerators.NetStandard.Interceptors.IntegrationTests.csproj", "{B7C9B52C-94C5-4729-9C5E-F49B3A112130}"
35 | EndProject
36 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetEscapades.EnumGenerators.Attributes", "src\NetEscapades.EnumGenerators.Attributes\NetEscapades.EnumGenerators.Attributes.csproj", "{EA85D7C6-8691-47B9-92D1-DCDA98E16D02}"
37 | EndProject
38 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetEscapades.EnumGenerators.Nuget.NetStandard", "tests\NetEscapades.EnumGenerators.Nuget.NetStandard\NetEscapades.EnumGenerators.Nuget.NetStandard.csproj", "{0011A3F4-BBFC-40CE-B8A3-A23FDAC60DE7}"
39 | EndProject
40 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetEscapades.EnumGenerators.Nuget.NetStandard.Interceptors.IntegrationTests", "tests\NetEscapades.EnumGenerators.Nuget.NetStandard.Interceptors.IntegrationTests\NetEscapades.EnumGenerators.Nuget.NetStandard.Interceptors.IntegrationTests.csproj", "{99AC1B59-04D1-4B19-957B-D7DE4D19A7CD}"
41 | EndProject
42 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetEscapades.EnumGenerators.Interceptors", "src\NetEscapades.EnumGenerators.Interceptors\NetEscapades.EnumGenerators.Interceptors.csproj", "{C26CDBC7-5CC3-4F55-8910-095D77DF7FD4}"
43 | EndProject
44 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetEscapades.EnumGenerators", "src\NetEscapades.EnumGenerators\NetEscapades.EnumGenerators.csproj", "{55000E98-4A4E-4AD5-BC11-A4F1F38726DC}"
45 | EndProject
46 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetEscapades.EnumGenerators.Interceptors.Attributes", "src\NetEscapades.EnumGenerators.Interceptors.Attributes\NetEscapades.EnumGenerators.Interceptors.Attributes.csproj", "{69A5F485-3DC8-48AF-9732-503E4926C92B}"
47 | EndProject
48 | Global
49 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
50 | Debug|Any CPU = Debug|Any CPU
51 | Release|Any CPU = Release|Any CPU
52 | EndGlobalSection
53 | GlobalSection(SolutionProperties) = preSolution
54 | HideSolutionNode = FALSE
55 | EndGlobalSection
56 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
57 | {443BC4C1-CF11-45E3-B772-72F886826C69}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
58 | {443BC4C1-CF11-45E3-B772-72F886826C69}.Debug|Any CPU.Build.0 = Debug|Any CPU
59 | {443BC4C1-CF11-45E3-B772-72F886826C69}.Release|Any CPU.ActiveCfg = Release|Any CPU
60 | {443BC4C1-CF11-45E3-B772-72F886826C69}.Release|Any CPU.Build.0 = Release|Any CPU
61 | {F03831B0-630E-4B0E-BD25-AFE975B35E69}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
62 | {F03831B0-630E-4B0E-BD25-AFE975B35E69}.Release|Any CPU.ActiveCfg = Release|Any CPU
63 | {328EFEB4-1D15-453E-9929-6FD0FACF3512}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
64 | {328EFEB4-1D15-453E-9929-6FD0FACF3512}.Debug|Any CPU.Build.0 = Debug|Any CPU
65 | {328EFEB4-1D15-453E-9929-6FD0FACF3512}.Release|Any CPU.ActiveCfg = Release|Any CPU
66 | {328EFEB4-1D15-453E-9929-6FD0FACF3512}.Release|Any CPU.Build.0 = Release|Any CPU
67 | {C4F84B6F-D2AE-4B3D-9CAD-90E6BC71F524}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
68 | {C4F84B6F-D2AE-4B3D-9CAD-90E6BC71F524}.Release|Any CPU.ActiveCfg = Release|Any CPU
69 | {C7CFC4AC-BC5B-4F07-BE93-4F245D5547D9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
70 | {C7CFC4AC-BC5B-4F07-BE93-4F245D5547D9}.Debug|Any CPU.Build.0 = Debug|Any CPU
71 | {C7CFC4AC-BC5B-4F07-BE93-4F245D5547D9}.Release|Any CPU.ActiveCfg = Release|Any CPU
72 | {C7CFC4AC-BC5B-4F07-BE93-4F245D5547D9}.Release|Any CPU.Build.0 = Release|Any CPU
73 | {E7D66FE9-65BC-4DC8-AC86-5C5BE10D8E61}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
74 | {E7D66FE9-65BC-4DC8-AC86-5C5BE10D8E61}.Release|Any CPU.ActiveCfg = Release|Any CPU
75 | {BDF8C04B-D0E3-4FF5-82C3-E8FDF3916C16}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
76 | {BDF8C04B-D0E3-4FF5-82C3-E8FDF3916C16}.Debug|Any CPU.Build.0 = Debug|Any CPU
77 | {BDF8C04B-D0E3-4FF5-82C3-E8FDF3916C16}.Release|Any CPU.ActiveCfg = Release|Any CPU
78 | {BDF8C04B-D0E3-4FF5-82C3-E8FDF3916C16}.Release|Any CPU.Build.0 = Release|Any CPU
79 | {40B2D8D1-7523-498D-9753-CECC3A44071A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
80 | {40B2D8D1-7523-498D-9753-CECC3A44071A}.Debug|Any CPU.Build.0 = Debug|Any CPU
81 | {40B2D8D1-7523-498D-9753-CECC3A44071A}.Release|Any CPU.ActiveCfg = Release|Any CPU
82 | {40B2D8D1-7523-498D-9753-CECC3A44071A}.Release|Any CPU.Build.0 = Release|Any CPU
83 | {7A0F5EA5-DB7B-49E0-827D-946C80175322}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
84 | {7A0F5EA5-DB7B-49E0-827D-946C80175322}.Debug|Any CPU.Build.0 = Debug|Any CPU
85 | {7A0F5EA5-DB7B-49E0-827D-946C80175322}.Release|Any CPU.ActiveCfg = Release|Any CPU
86 | {7A0F5EA5-DB7B-49E0-827D-946C80175322}.Release|Any CPU.Build.0 = Release|Any CPU
87 | {1A1B48ED-DDE7-4017-A86B-5F7D2FB3A3D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
88 | {1A1B48ED-DDE7-4017-A86B-5F7D2FB3A3D0}.Release|Any CPU.ActiveCfg = Release|Any CPU
89 | {E390E06A-27EE-49B3-9113-37812B92060A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
90 | {E390E06A-27EE-49B3-9113-37812B92060A}.Debug|Any CPU.Build.0 = Debug|Any CPU
91 | {E390E06A-27EE-49B3-9113-37812B92060A}.Release|Any CPU.ActiveCfg = Release|Any CPU
92 | {E390E06A-27EE-49B3-9113-37812B92060A}.Release|Any CPU.Build.0 = Release|Any CPU
93 | {B7C9B52C-94C5-4729-9C5E-F49B3A112130}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
94 | {B7C9B52C-94C5-4729-9C5E-F49B3A112130}.Debug|Any CPU.Build.0 = Debug|Any CPU
95 | {B7C9B52C-94C5-4729-9C5E-F49B3A112130}.Release|Any CPU.ActiveCfg = Release|Any CPU
96 | {B7C9B52C-94C5-4729-9C5E-F49B3A112130}.Release|Any CPU.Build.0 = Release|Any CPU
97 | {EA85D7C6-8691-47B9-92D1-DCDA98E16D02}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
98 | {EA85D7C6-8691-47B9-92D1-DCDA98E16D02}.Debug|Any CPU.Build.0 = Debug|Any CPU
99 | {EA85D7C6-8691-47B9-92D1-DCDA98E16D02}.Release|Any CPU.ActiveCfg = Release|Any CPU
100 | {EA85D7C6-8691-47B9-92D1-DCDA98E16D02}.Release|Any CPU.Build.0 = Release|Any CPU
101 | {0011A3F4-BBFC-40CE-B8A3-A23FDAC60DE7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
102 | {0011A3F4-BBFC-40CE-B8A3-A23FDAC60DE7}.Release|Any CPU.ActiveCfg = Release|Any CPU
103 | {99AC1B59-04D1-4B19-957B-D7DE4D19A7CD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
104 | {99AC1B59-04D1-4B19-957B-D7DE4D19A7CD}.Release|Any CPU.ActiveCfg = Release|Any CPU
105 | {C26CDBC7-5CC3-4F55-8910-095D77DF7FD4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
106 | {C26CDBC7-5CC3-4F55-8910-095D77DF7FD4}.Debug|Any CPU.Build.0 = Debug|Any CPU
107 | {C26CDBC7-5CC3-4F55-8910-095D77DF7FD4}.Release|Any CPU.ActiveCfg = Release|Any CPU
108 | {C26CDBC7-5CC3-4F55-8910-095D77DF7FD4}.Release|Any CPU.Build.0 = Release|Any CPU
109 | {55000E98-4A4E-4AD5-BC11-A4F1F38726DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
110 | {55000E98-4A4E-4AD5-BC11-A4F1F38726DC}.Debug|Any CPU.Build.0 = Debug|Any CPU
111 | {55000E98-4A4E-4AD5-BC11-A4F1F38726DC}.Release|Any CPU.ActiveCfg = Release|Any CPU
112 | {55000E98-4A4E-4AD5-BC11-A4F1F38726DC}.Release|Any CPU.Build.0 = Release|Any CPU
113 | {69A5F485-3DC8-48AF-9732-503E4926C92B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
114 | {69A5F485-3DC8-48AF-9732-503E4926C92B}.Debug|Any CPU.Build.0 = Debug|Any CPU
115 | {69A5F485-3DC8-48AF-9732-503E4926C92B}.Release|Any CPU.ActiveCfg = Release|Any CPU
116 | {69A5F485-3DC8-48AF-9732-503E4926C92B}.Release|Any CPU.Build.0 = Release|Any CPU
117 | EndGlobalSection
118 | GlobalSection(NestedProjects) = preSolution
119 | {443BC4C1-CF11-45E3-B772-72F886826C69} = {FF6F43E8-36EA-4BF0-9B2E-8ACC91C5F939}
120 | {F03831B0-630E-4B0E-BD25-AFE975B35E69} = {FF6F43E8-36EA-4BF0-9B2E-8ACC91C5F939}
121 | {328EFEB4-1D15-453E-9929-6FD0FACF3512} = {FF6F43E8-36EA-4BF0-9B2E-8ACC91C5F939}
122 | {C7CFC4AC-BC5B-4F07-BE93-4F245D5547D9} = {FF6F43E8-36EA-4BF0-9B2E-8ACC91C5F939}
123 | {E7D66FE9-65BC-4DC8-AC86-5C5BE10D8E61} = {FF6F43E8-36EA-4BF0-9B2E-8ACC91C5F939}
124 | {BDF8C04B-D0E3-4FF5-82C3-E8FDF3916C16} = {AC0DDDB0-385D-43B8-A770-C79EE2077D05}
125 | {40B2D8D1-7523-498D-9753-CECC3A44071A} = {FF6F43E8-36EA-4BF0-9B2E-8ACC91C5F939}
126 | {7A0F5EA5-DB7B-49E0-827D-946C80175322} = {FF6F43E8-36EA-4BF0-9B2E-8ACC91C5F939}
127 | {1A1B48ED-DDE7-4017-A86B-5F7D2FB3A3D0} = {FF6F43E8-36EA-4BF0-9B2E-8ACC91C5F939}
128 | {E390E06A-27EE-49B3-9113-37812B92060A} = {FF6F43E8-36EA-4BF0-9B2E-8ACC91C5F939}
129 | {B7C9B52C-94C5-4729-9C5E-F49B3A112130} = {FF6F43E8-36EA-4BF0-9B2E-8ACC91C5F939}
130 | {EA85D7C6-8691-47B9-92D1-DCDA98E16D02} = {78D56637-F921-437D-A9AC-FBEAD78344BE}
131 | {0011A3F4-BBFC-40CE-B8A3-A23FDAC60DE7} = {FF6F43E8-36EA-4BF0-9B2E-8ACC91C5F939}
132 | {99AC1B59-04D1-4B19-957B-D7DE4D19A7CD} = {FF6F43E8-36EA-4BF0-9B2E-8ACC91C5F939}
133 | {C26CDBC7-5CC3-4F55-8910-095D77DF7FD4} = {78D56637-F921-437D-A9AC-FBEAD78344BE}
134 | {55000E98-4A4E-4AD5-BC11-A4F1F38726DC} = {78D56637-F921-437D-A9AC-FBEAD78344BE}
135 | {69A5F485-3DC8-48AF-9732-503E4926C92B} = {78D56637-F921-437D-A9AC-FBEAD78344BE}
136 | EndGlobalSection
137 | EndGlobal
138 |
--------------------------------------------------------------------------------
/NuGet.integration-tests.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/build.cmd:
--------------------------------------------------------------------------------
1 | :; set -eo pipefail
2 | :; SCRIPT_DIR=$(cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd)
3 | :; ${SCRIPT_DIR}/build.sh "$@"
4 | :; exit $?
5 |
6 | @ECHO OFF
7 | powershell -ExecutionPolicy ByPass -NoProfile -File "%~dp0build.ps1" %*
8 |
--------------------------------------------------------------------------------
/build.ps1:
--------------------------------------------------------------------------------
1 | [CmdletBinding()]
2 | Param(
3 | [Parameter(Position=0,Mandatory=$false,ValueFromRemainingArguments=$true)]
4 | [string[]]$BuildArguments
5 | )
6 |
7 | Write-Output "PowerShell $($PSVersionTable.PSEdition) version $($PSVersionTable.PSVersion)"
8 |
9 | Set-StrictMode -Version 2.0; $ErrorActionPreference = "Stop"; $ConfirmPreference = "None"; trap { Write-Error $_ -ErrorAction Continue; exit 1 }
10 | $PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent
11 |
12 | ###########################################################################
13 | # CONFIGURATION
14 | ###########################################################################
15 |
16 | $BuildProjectFile = "$PSScriptRoot\build\_build.csproj"
17 | $TempDirectory = "$PSScriptRoot\\.nuke\temp"
18 |
19 | $DotNetGlobalFile = "$PSScriptRoot\\global.json"
20 | $DotNetInstallUrl = "https://dot.net/v1/dotnet-install.ps1"
21 | $DotNetChannel = "STS"
22 |
23 | $env:DOTNET_CLI_TELEMETRY_OPTOUT = 1
24 | $env:DOTNET_NOLOGO = 1
25 |
26 | ###########################################################################
27 | # EXECUTION
28 | ###########################################################################
29 |
30 | function ExecSafe([scriptblock] $cmd) {
31 | & $cmd
32 | if ($LASTEXITCODE) { exit $LASTEXITCODE }
33 | }
34 |
35 | # If dotnet CLI is installed globally and it matches requested version, use for execution
36 | if ($null -ne (Get-Command "dotnet" -ErrorAction SilentlyContinue) -and `
37 | $(dotnet --version) -and $LASTEXITCODE -eq 0) {
38 | $env:DOTNET_EXE = (Get-Command "dotnet").Path
39 | }
40 | else {
41 | # Download install script
42 | $DotNetInstallFile = "$TempDirectory\dotnet-install.ps1"
43 | New-Item -ItemType Directory -Path $TempDirectory -Force | Out-Null
44 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
45 | (New-Object System.Net.WebClient).DownloadFile($DotNetInstallUrl, $DotNetInstallFile)
46 |
47 | # If global.json exists, load expected version
48 | if (Test-Path $DotNetGlobalFile) {
49 | $DotNetGlobal = $(Get-Content $DotNetGlobalFile | Out-String | ConvertFrom-Json)
50 | if ($DotNetGlobal.PSObject.Properties["sdk"] -and $DotNetGlobal.sdk.PSObject.Properties["version"]) {
51 | $DotNetVersion = $DotNetGlobal.sdk.version
52 | }
53 | }
54 |
55 | # Install by channel or version
56 | $DotNetDirectory = "$TempDirectory\dotnet-win"
57 | if (!(Test-Path variable:DotNetVersion)) {
58 | ExecSafe { & powershell $DotNetInstallFile -InstallDir $DotNetDirectory -Channel $DotNetChannel -NoPath }
59 | } else {
60 | ExecSafe { & powershell $DotNetInstallFile -InstallDir $DotNetDirectory -Version $DotNetVersion -NoPath }
61 | }
62 | $env:DOTNET_EXE = "$DotNetDirectory\dotnet.exe"
63 | $env:PATH = "$DotNetDirectory;$env:PATH"
64 | }
65 |
66 | Write-Output "Microsoft (R) .NET SDK version $(& $env:DOTNET_EXE --version)"
67 |
68 | if (Test-Path env:NUKE_ENTERPRISE_TOKEN) {
69 | & $env:DOTNET_EXE nuget remove source "nuke-enterprise" > $null
70 | & $env:DOTNET_EXE nuget add source "https://f.feedz.io/nuke/enterprise/nuget" --name "nuke-enterprise" --username "PAT" --password $env:NUKE_ENTERPRISE_TOKEN > $null
71 | }
72 |
73 | ExecSafe { & $env:DOTNET_EXE build $BuildProjectFile /nodeReuse:false /p:UseSharedCompilation=false -nologo -clp:NoSummary --verbosity quiet }
74 | ExecSafe { & $env:DOTNET_EXE run --project $BuildProjectFile --no-build -- $BuildArguments }
--------------------------------------------------------------------------------
/build.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | bash --version 2>&1 | head -n 1
4 |
5 | set -eo pipefail
6 | SCRIPT_DIR=$(cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd)
7 |
8 | ###########################################################################
9 | # CONFIGURATION
10 | ###########################################################################
11 |
12 | BUILD_PROJECT_FILE="$SCRIPT_DIR/build/_build.csproj"
13 | TEMP_DIRECTORY="$SCRIPT_DIR//.nuke/temp"
14 |
15 | DOTNET_GLOBAL_FILE="$SCRIPT_DIR//global.json"
16 | DOTNET_INSTALL_URL="https://dot.net/v1/dotnet-install.sh"
17 | DOTNET_CHANNEL="STS"
18 |
19 | export DOTNET_CLI_TELEMETRY_OPTOUT=1
20 | export DOTNET_NOLOGO=1
21 |
22 | ###########################################################################
23 | # EXECUTION
24 | ###########################################################################
25 |
26 | function FirstJsonValue {
27 | perl -nle 'print $1 if m{"'"$1"'": "([^"]+)",?}' <<< "${@:2}"
28 | }
29 |
30 | # If dotnet CLI is installed globally and it matches requested version, use for execution
31 | if [ -x "$(command -v dotnet)" ] && dotnet --version &>/dev/null; then
32 | export DOTNET_EXE="$(command -v dotnet)"
33 | else
34 | # Download install script
35 | DOTNET_INSTALL_FILE="$TEMP_DIRECTORY/dotnet-install.sh"
36 | mkdir -p "$TEMP_DIRECTORY"
37 | curl -Lsfo "$DOTNET_INSTALL_FILE" "$DOTNET_INSTALL_URL"
38 | chmod +x "$DOTNET_INSTALL_FILE"
39 |
40 | # If global.json exists, load expected version
41 | if [[ -f "$DOTNET_GLOBAL_FILE" ]]; then
42 | DOTNET_VERSION=$(FirstJsonValue "version" "$(cat "$DOTNET_GLOBAL_FILE")")
43 | if [[ "$DOTNET_VERSION" == "" ]]; then
44 | unset DOTNET_VERSION
45 | fi
46 | fi
47 |
48 | # Install by channel or version
49 | DOTNET_DIRECTORY="$TEMP_DIRECTORY/dotnet-unix"
50 | if [[ -z ${DOTNET_VERSION+x} ]]; then
51 | "$DOTNET_INSTALL_FILE" --install-dir "$DOTNET_DIRECTORY" --channel "$DOTNET_CHANNEL" --no-path
52 | else
53 | "$DOTNET_INSTALL_FILE" --install-dir "$DOTNET_DIRECTORY" --version "$DOTNET_VERSION" --no-path
54 | fi
55 | export DOTNET_EXE="$DOTNET_DIRECTORY/dotnet"
56 | export PATH="$DOTNET_DIRECTORY:$PATH"
57 | fi
58 |
59 | echo "Microsoft (R) .NET SDK version $("$DOTNET_EXE" --version)"
60 |
61 | if [[ ! -z ${NUKE_ENTERPRISE_TOKEN+x} && "$NUKE_ENTERPRISE_TOKEN" != "" ]]; then
62 | "$DOTNET_EXE" nuget remove source "nuke-enterprise" &>/dev/null || true
63 | "$DOTNET_EXE" nuget add source "https://f.feedz.io/nuke/enterprise/nuget" --name "nuke-enterprise" --username "PAT" --password "$NUKE_ENTERPRISE_TOKEN" --store-password-in-clear-text &>/dev/null || true
64 | fi
65 |
66 | "$DOTNET_EXE" build "$BUILD_PROJECT_FILE" /nodeReuse:false /p:UseSharedCompilation=false -nologo -clp:NoSummary --verbosity quiet
67 | "$DOTNET_EXE" run --project "$BUILD_PROJECT_FILE" --no-build -- "$@"
--------------------------------------------------------------------------------
/build/.editorconfig:
--------------------------------------------------------------------------------
1 | [*.cs]
2 | dotnet_style_qualification_for_field = false:warning
3 | dotnet_style_qualification_for_property = false:warning
4 | dotnet_style_qualification_for_method = false:warning
5 | dotnet_style_qualification_for_event = false:warning
6 | dotnet_style_require_accessibility_modifiers = never:warning
7 |
8 | csharp_style_expression_bodied_methods = true:silent
9 | csharp_style_expression_bodied_properties = true:warning
10 | csharp_style_expression_bodied_indexers = true:warning
11 | csharp_style_expression_bodied_accessors = true:warning
12 |
--------------------------------------------------------------------------------
/build/Build.cs:
--------------------------------------------------------------------------------
1 | using Nuke.Common;
2 | using Nuke.Common.CI;
3 | using Nuke.Common.CI.GitHubActions;
4 | using Nuke.Common.Execution;
5 | using Nuke.Common.IO;
6 | using Nuke.Common.ProjectModel;
7 | using Nuke.Common.Tooling;
8 | using Nuke.Common.Tools.DotNet;
9 | using Nuke.Common.Utilities.Collections;
10 | using static Nuke.Common.EnvironmentInfo;
11 | using static Nuke.Common.IO.FileSystemTasks;
12 | using static Nuke.Common.IO.PathConstruction;
13 | using static Nuke.Common.Tools.DotNet.DotNetTasks;
14 |
15 | [ShutdownDotNetAfterServerBuild]
16 | class Build : NukeBuild
17 | {
18 | /// Support plugins are available for:
19 | /// - JetBrains ReSharper https://nuke.build/resharper
20 | /// - JetBrains Rider https://nuke.build/rider
21 | /// - Microsoft VisualStudio https://nuke.build/visualstudio
22 | /// - Microsoft VSCode https://nuke.build/vscode
23 |
24 | public static int Main () => Execute(x => x.Compile);
25 |
26 | [Parameter("Configuration to build - Default is 'Debug' (local) or 'Release' (server)")]
27 | readonly Configuration Configuration = IsLocalBuild ? Configuration.Debug : Configuration.Release;
28 |
29 | [Solution(GenerateProjects = true)] readonly Solution Solution;
30 |
31 | AbsolutePath OutputDirectory => RootDirectory / "artifacts";
32 | AbsolutePath ArtifactsDirectory => OutputDirectory / "packages";
33 | AbsolutePath TestResultsDirectory => OutputDirectory / "results";
34 |
35 | [Parameter] readonly string NuGetToken;
36 | [Parameter] readonly AbsolutePath PackagesDirectory = RootDirectory / "packages";
37 |
38 | const string NugetOrgUrl = "https://api.nuget.org/v3/index.json";
39 | bool IsTag => GitHubActions.Instance?.Ref?.StartsWith("refs/tags/") ?? false;
40 |
41 | Target Clean => _ => _
42 | .Before(Restore)
43 | .Executes(() =>
44 | {
45 | OutputDirectory.CreateOrCleanDirectory();
46 | if (!string.IsNullOrEmpty(PackagesDirectory))
47 | {
48 | PackagesDirectory.CreateOrCleanDirectory();
49 | }
50 | });
51 |
52 | Target Restore => _ => _
53 | .Executes(() =>
54 | {
55 | DotNetRestore(s => s
56 | .When(!string.IsNullOrEmpty(PackagesDirectory), x=>x.SetPackageDirectory(PackagesDirectory))
57 | .SetProjectFile(Solution));
58 | });
59 |
60 | Target Compile => _ => _
61 | .DependsOn(Restore)
62 | .Executes(() =>
63 | {
64 | DotNetBuild(s => s
65 | .SetProjectFile(Solution)
66 | .SetConfiguration(Configuration)
67 | .When(IsServerBuild, x => x.SetProperty("ContinuousIntegrationBuild", "true"))
68 | .EnableNoRestore());
69 | });
70 |
71 | Target Test => _ => _
72 | .DependsOn(Compile)
73 | .Executes(() =>
74 | {
75 | DotNetTest(s => s
76 | .SetProjectFile(Solution)
77 | .SetConfiguration(Configuration)
78 | .When(IsServerBuild, x => x.SetProperty("ContinuousIntegrationBuild", "true"))
79 | .When(IsServerBuild, x => x
80 | .SetLoggers("trx")
81 | .SetResultsDirectory(TestResultsDirectory))
82 | .EnableNoBuild()
83 | .EnableNoRestore());
84 | });
85 |
86 | Target Pack => _ => _
87 | .DependsOn(Compile)
88 | .After(Test)
89 | .Produces(ArtifactsDirectory)
90 | .Executes(() =>
91 | {
92 | DotNetPack(s => s
93 | .SetConfiguration(Configuration)
94 | .SetOutputDirectory(ArtifactsDirectory)
95 | .EnableNoBuild()
96 | .EnableNoRestore()
97 | .When(IsServerBuild, x => x.SetProperty("ContinuousIntegrationBuild", "true"))
98 | .SetProject(Solution));
99 | });
100 |
101 | Target TestPackage => _ => _
102 | .DependsOn(Pack)
103 | .After(Test)
104 | .Produces(ArtifactsDirectory)
105 | .Executes(() =>
106 | {
107 | var projectFiles = new[]
108 | {
109 | Solution.tests.NetEscapades_EnumGenerators_Nuget_IntegrationTests.Path,
110 | Solution.tests.NetEscapades_EnumGenerators_Nuget_Attributes_IntegrationTests.Path,
111 | Solution.tests.NetEscapades_EnumGenerators_Nuget_Interceptors_IntegrationTests.Path,
112 | Solution.tests.NetEscapades_EnumGenerators_Nuget_NetStandard_Interceptors_IntegrationTests.Path,
113 | };
114 |
115 | if (!string.IsNullOrEmpty(PackagesDirectory))
116 | {
117 | (PackagesDirectory / "netescapades.enumgenerators").DeleteDirectory();
118 | (PackagesDirectory / "netescapades.enumgenerators.attributes").DeleteDirectory();
119 | (PackagesDirectory / "netescapades.enumgenerators.interceptors").DeleteDirectory();
120 | (PackagesDirectory / "netescapades.enumgenerators.interceptors.attributes").DeleteDirectory();
121 | }
122 |
123 | DotNetRestore(s => s
124 | .When(!string.IsNullOrEmpty(PackagesDirectory), x => x.SetPackageDirectory(PackagesDirectory))
125 | .SetConfigFile(RootDirectory / "NuGet.integration-tests.config")
126 | .CombineWith(projectFiles, (s, p) => s.SetProjectFile(p)));
127 |
128 | DotNetBuild(s => s
129 | .When(!string.IsNullOrEmpty(PackagesDirectory), x=>x.SetPackageDirectory(PackagesDirectory))
130 | .EnableNoRestore()
131 | .SetConfiguration(Configuration)
132 | .CombineWith(projectFiles, (s, p) => s.SetProjectFile(p)));
133 |
134 | DotNetTest(s => s
135 | .SetConfiguration(Configuration)
136 | .EnableNoBuild()
137 | .EnableNoRestore()
138 | .When(IsServerBuild, x => x
139 | .SetLoggers("trx")
140 | .SetResultsDirectory(TestResultsDirectory))
141 | .CombineWith(projectFiles, (s, p) => s.SetProjectFile(p)));
142 | });
143 |
144 | Target PushToNuGet => _ => _
145 | .DependsOn(Pack)
146 | .OnlyWhenStatic(() => IsTag && IsServerBuild && IsWin)
147 | .Requires(() => NuGetToken)
148 | .After(TestPackage)
149 | .Executes(() =>
150 | {
151 | var packages = ArtifactsDirectory.GlobFiles("*.nupkg");
152 | DotNetNuGetPush(s => s
153 | .SetApiKey(NuGetToken)
154 | .SetSource(NugetOrgUrl)
155 | .EnableSkipDuplicate()
156 | .CombineWith(packages, (x, package) => x
157 | .SetTargetPath(package)));
158 | });
159 | }
160 |
--------------------------------------------------------------------------------
/build/Configuration.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.ComponentModel;
3 | using System.Linq;
4 | using Nuke.Common.Tooling;
5 |
6 | [TypeConverter(typeof(TypeConverter))]
7 | public class Configuration : Enumeration
8 | {
9 | public static Configuration Debug = new Configuration { Value = nameof(Debug) };
10 | public static Configuration Release = new Configuration { Value = nameof(Release) };
11 |
12 | public static implicit operator string(Configuration configuration)
13 | {
14 | return configuration.Value;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/build/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/build/Directory.Build.targets:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/build/_build.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net8.0
6 |
7 | CS0649;CS0169
8 | ..
9 | ..
10 | 1
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | Directory.Build\tests\Directory.Build.props
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/build/_build.csproj.DotSettings:
--------------------------------------------------------------------------------
1 |
2 | DO_NOT_SHOW
3 | DO_NOT_SHOW
4 | DO_NOT_SHOW
5 | DO_NOT_SHOW
6 | Implicit
7 | Implicit
8 | ExpressionBody
9 | 0
10 | NEXT_LINE
11 | True
12 | False
13 | 120
14 | IF_OWNER_IS_SINGLE_LINE
15 | WRAP_IF_LONG
16 | False
17 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
18 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
19 | <Policy><Descriptor Staticness="Instance" AccessRightKinds="Private" Description="Instance fields (private)"><ElementKinds><Kind Name="FIELD" /><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></Policy>
20 | <Policy><Descriptor Staticness="Static" AccessRightKinds="Private" Description="Static fields (private)"><ElementKinds><Kind Name="FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></Policy>
21 | True
22 | True
23 | True
24 | True
25 | True
26 | True
27 | True
28 | True
29 | True
30 | True
31 |
--------------------------------------------------------------------------------
/global.json:
--------------------------------------------------------------------------------
1 | {
2 | "sdk": {
3 | "allowPrerelease": true
4 | }
5 | }
--------------------------------------------------------------------------------
/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/andrewlock/NetEscapades.EnumGenerators/c2bb4e0a8c27b82638ee396addb2190ef569c9b4/icon.png
--------------------------------------------------------------------------------
/icon_32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/andrewlock/NetEscapades.EnumGenerators/c2bb4e0a8c27b82638ee396addb2190ef569c9b4/icon_32.png
--------------------------------------------------------------------------------
/releasenotes.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
15 |
16 | $(PackageReleaseNotes)
17 | See $(PackageProjectUrl)/blob/main/CHANGELOG.md#v$(PackageVersion.Replace('.','')) for more details.
18 |
19 |
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 ""Extensions"" 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 | /// By default, when used with NetEscapades.EnumGenerators.Interceptors
26 | /// any interceptable usages of the enum will be replaced by usages of
27 | /// the extension method in this project. To disable interception of
28 | /// the enum in this project when used with the interceptable package,
29 | /// set to .
30 | ///
31 | public bool IsInterceptable { get; set; } = true;
32 | }
33 |
34 | ///
35 | /// Add to enums to indicate that extension methods should be generated for the type
36 | ///
37 | [System.AttributeUsage(System.AttributeTargets.Assembly, AllowMultiple = true)]
38 | [System.Diagnostics.Conditional("NETESCAPADES_ENUMGENERATORS_USAGES")]
39 | public class EnumExtensionsAttribute : System.Attribute
40 | where T: System.Enum
41 | {
42 | ///
43 | /// The namespace to generate the extension class.
44 | /// If not provided, the namespace of the enum will be used.
45 | ///
46 | public string? ExtensionClassNamespace { get; set; }
47 |
48 | ///
49 | /// The name to use for the extension class.
50 | /// If not provided, the enum name with an Extensions suffix will be used.
51 | /// For example for an Enum called StatusCodes, the default name
52 | /// will be StatusCodesExtensions.
53 | ///
54 | public string? ExtensionClassName { get; set; }
55 |
56 | ///
57 | /// By default, when used with NetEscapades.EnumGenerators.Interceptors
58 | /// any interceptable usages of the enum will be replaced by usages of
59 | /// the extension method in this project. To disable interception of
60 | /// the enum in this project when used with the interceptable package,
61 | /// set to .
62 | ///
63 | public bool IsInterceptable { get; set; } = true;
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/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.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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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.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 | }
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/src/NetEscapades.EnumGenerators.Interceptors/InterceptorGenerator.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Immutable;
2 | using System.Text;
3 | using Microsoft.CodeAnalysis;
4 | using Microsoft.CodeAnalysis.CSharp;
5 | using Microsoft.CodeAnalysis.CSharp.Syntax;
6 | using Microsoft.CodeAnalysis.Operations;
7 | using Microsoft.CodeAnalysis.Text;
8 |
9 | namespace NetEscapades.EnumGenerators.Interceptors;
10 |
11 | [Generator]
12 | public class InterceptorGenerator : IIncrementalGenerator
13 | {
14 | public void Initialize(IncrementalGeneratorInitializationContext context)
15 | {
16 | context.RegisterPostInitializationOutput(ctx => ctx.AddSource(
17 | "InterceptableAttribute.g.cs", SourceText.From(SourceGenerationHelper.Attribute, Encoding.UTF8)));
18 |
19 | IncrementalValuesProvider enumsToIntercept = context.SyntaxProvider
20 | .ForAttributeWithMetadataName(
21 | Attributes.EnumExtensionsAttribute,
22 | predicate: static (node, _) => node is EnumDeclarationSyntax,
23 | transform: GetInterceptableEnum)
24 | .WithTrackingName(TrackingNames.InitialExtraction)
25 | .Where(static m => m is not null)
26 | .Select((e, _) => e!.Value)
27 | .WithTrackingName(TrackingNames.RemovingNulls);
28 |
29 | IncrementalValuesProvider externalEnums = context
30 | .SyntaxProvider
31 | .ForAttributeWithMetadataName(
32 | Attributes.ExternalEnumExtensionsAttribute,
33 | predicate: static (node, _) => node is CompilationUnitSyntax,
34 | transform: static (ctx, ct) => GetEnumToInterceptFromAssemblyAttribute(
35 | ctx, ct, "EnumExtensionsAttribute", "EnumExtensions"))
36 | .Where(static m => m is not null)
37 | .SelectMany(static (m, _) => m!.Value)
38 | .WithTrackingName(TrackingNames.InitialExternalExtraction);
39 |
40 | var interceptionEnabledSetting = context.AnalyzerConfigOptionsProvider
41 | .Select((x, _) =>
42 | x.GlobalOptions.TryGetValue($"build_property.{Constants.EnabledPropertyName}", out var enableSwitch)
43 | && !enableSwitch.Equals("false", StringComparison.Ordinal));
44 |
45 | var csharpSufficient = context.CompilationProvider
46 | .Select((x,_) => x is CSharpCompilation { LanguageVersion: LanguageVersion.Default or >= LanguageVersion.CSharp11 });
47 |
48 | var settings = interceptionEnabledSetting
49 | .Combine(csharpSufficient)
50 | .WithTrackingName(TrackingNames.Settings);
51 |
52 | var interceptableEnumsAndLocations = context
53 | .SyntaxProvider
54 | .ForAttributeWithMetadataName(
55 | Constants.InterceptableAttribute,
56 | predicate: static (node, _) => node is CompilationUnitSyntax,
57 | transform: static (ctx, ct) =>
58 | {
59 | var enumToIntercept = GetEnumToInterceptFromAssemblyAttribute(
60 | ctx, ct, "InterceptableAttribute", "Interceptable");
61 | var location = LocationInfo.CreateFrom(ctx.TargetNode.GetLocation());
62 | return (enumToIntercept, location);
63 | })
64 | .WithTrackingName(TrackingNames.InitialInterceptable);
65 |
66 | var interceptableEnums = interceptableEnumsAndLocations
67 | .Where(static m => m.enumToIntercept is not null)
68 | .SelectMany(static (m, _) => m.enumToIntercept!.Value)
69 | .WithTrackingName(TrackingNames.InitialInterceptableOnly);
70 |
71 | var interceptionEnabled = settings
72 | .Select((x, _) => x.Left && x.Right);
73 |
74 | var locations = context.SyntaxProvider
75 | .CreateSyntaxProvider(InterceptorPredicate, InterceptorParser)
76 | .Combine(interceptionEnabled)
77 | .Where(x => x.Right && x.Left is not null)
78 | .Select((x, _) => x.Left!)
79 | .Collect()
80 | .WithTrackingName(TrackingNames.InterceptedLocations);
81 |
82 | var enumInterceptions = enumsToIntercept
83 | .Combine(locations)
84 | .Select(FilterInterceptorCandidates)
85 | .Where(x => x is not null)
86 | .WithTrackingName(TrackingNames.EnumInterceptions);
87 |
88 | var externalInterceptions = externalEnums
89 | .Combine(locations)
90 | .Select(FilterInterceptorCandidates!)
91 | .Where(x => x is not null)
92 | .WithTrackingName(TrackingNames.ExternalInterceptions);
93 |
94 | var additionalInterceptions = interceptableEnums
95 | .Combine(locations)
96 | .Select(FilterInterceptorCandidates!)
97 | .Where(x => x is not null)
98 | .WithTrackingName(TrackingNames.AdditionalInterceptions);
99 |
100 | context.RegisterSourceOutput(enumInterceptions,
101 | static (spc, toIntercept) => ExecuteInterceptors(toIntercept, spc));
102 |
103 | context.RegisterSourceOutput(externalInterceptions,
104 | static (spc, toIntercept) => ExecuteInterceptors(toIntercept, spc));
105 |
106 | context.RegisterSourceOutput(additionalInterceptions,
107 | static (spc, toIntercept) => ExecuteInterceptors(toIntercept, spc));
108 |
109 | context.RegisterImplementationSourceOutput(settings,
110 | static (spc, args) =>
111 | {
112 | var explicitlyEnabled = args.Left;
113 | var csharpSufficient = args.Right;
114 | if (explicitlyEnabled && !csharpSufficient)
115 | {
116 | spc.ReportDiagnostic(Diagnostic.Create(DiagnosticHelper.CsharpVersionLooLow, location: null));
117 | }
118 | });
119 | }
120 |
121 | static EquatableArray? GetEnumToInterceptFromAssemblyAttribute(
122 | GeneratorAttributeSyntaxContext context, CancellationToken ct, string fullAttributeName, string shortAttributeName)
123 | {
124 | List? enums = null;
125 | foreach (AttributeData attribute in context.Attributes)
126 | {
127 | if (!((attribute.AttributeClass?.Name == fullAttributeName ||
128 | attribute.AttributeClass?.Name == shortAttributeName) &&
129 | attribute.AttributeClass.IsGenericType &&
130 | attribute.AttributeClass.TypeArguments.Length == 1))
131 | {
132 | // wrong attribute
133 | continue;
134 | }
135 |
136 | var isInterceptable = true;
137 | string? name = null;
138 | string? nameSpace = null;
139 |
140 | foreach (KeyValuePair namedArgument in attribute.NamedArguments)
141 | {
142 | if (namedArgument.Key == "IsInterceptable"
143 | && namedArgument.Value.Value is false)
144 | {
145 | isInterceptable = false;
146 | break;
147 | }
148 |
149 | if (namedArgument.Key == "ExtensionClassNamespace"
150 | && namedArgument.Value.Value?.ToString() is { } ns)
151 | {
152 | nameSpace = ns;
153 | }
154 | else if (namedArgument.Key == "ExtensionClassName"
155 | && namedArgument.Value.Value?.ToString() is { } n)
156 | {
157 | name = n;
158 | }
159 | }
160 | if(isInterceptable && attribute.AttributeClass.TypeArguments[0] is INamedTypeSymbol enumSymbol)
161 | {
162 | enums ??= new();
163 | enums.Add(ToEnumToIntercept(enumSymbol, name, nameSpace));
164 | }
165 | }
166 |
167 | return enums is not null
168 | ? new EquatableArray(enums.ToArray())
169 | : null;
170 | }
171 |
172 | static EnumToIntercept? GetInterceptableEnum(GeneratorAttributeSyntaxContext context, CancellationToken ct)
173 | {
174 | var enumSymbol = context.TargetSymbol as INamedTypeSymbol;
175 | if (enumSymbol is null)
176 | {
177 | // nothing to do if this type isn't available
178 | return null;
179 | }
180 |
181 | ct.ThrowIfCancellationRequested();
182 |
183 | string? nameSpace = null;
184 | string? name = null;
185 |
186 | foreach (AttributeData attributeData in enumSymbol.GetAttributes())
187 | {
188 | if (attributeData.AttributeClass?.Name != "EnumExtensionsAttribute" ||
189 | attributeData.AttributeClass.ToDisplayString() != Attributes.EnumExtensionsAttribute)
190 | {
191 | continue;
192 | }
193 |
194 | foreach (KeyValuePair namedArgument in attributeData.NamedArguments)
195 | {
196 |
197 | if (namedArgument is { Key: "IsInterceptable", Value.Value: false })
198 | {
199 | // Not interceptable, can bail out
200 | return null;
201 | }
202 |
203 | if (namedArgument.Key == "ExtensionClassNamespace"
204 | && namedArgument.Value.Value?.ToString() is { } ns)
205 | {
206 | nameSpace = ns;
207 | }
208 | else if (namedArgument.Key == "ExtensionClassName"
209 | && namedArgument.Value.Value?.ToString() is { } n)
210 | {
211 | name = n;
212 | }
213 | }
214 | }
215 |
216 | return ToEnumToIntercept(enumSymbol, name, nameSpace);
217 | }
218 |
219 | static EnumToIntercept ToEnumToIntercept(INamedTypeSymbol enumSymbol, string? name, string? nameSpace)
220 | {
221 | name ??= enumSymbol.Name + "Extensions";
222 | nameSpace ??= enumSymbol.ContainingNamespace.IsGlobalNamespace ? string.Empty : enumSymbol.ContainingNamespace.ToString();
223 |
224 | return new(name, enumSymbol.ToString(), nameSpace);
225 | }
226 |
227 | private static bool InterceptorPredicate(SyntaxNode node, CancellationToken ct) =>
228 | node is InvocationExpressionSyntax {Expression: MemberAccessExpressionSyntax {Name.Identifier.ValueText: "ToString" or "HasFlag"}};
229 |
230 | private static CandidateInvocation? InterceptorParser(GeneratorSyntaxContext ctx, CancellationToken ct)
231 | {
232 | if (ctx.Node is InvocationExpressionSyntax {Expression: MemberAccessExpressionSyntax {Name: { } nameSyntax}} invocation
233 | && ctx.SemanticModel.GetOperation(ctx.Node, ct) is IInvocationOperation targetOperation
234 | && targetOperation.TargetMethod is {Name : "ToString" or "HasFlag", ContainingType: {Name: "Enum", ContainingNamespace: {Name: "System", ContainingNamespace.IsGlobalNamespace: true}}}
235 | && targetOperation.Instance?.Type is { } type
236 | #pragma warning disable RSEXPERIMENTAL002 // / Experimental interceptable location API
237 | && ctx.SemanticModel.GetInterceptableLocation(invocation) is { } location)
238 | #pragma warning restore RSEXPERIMENTAL002
239 | {
240 | var targetToIntercept = targetOperation.TargetMethod.Name switch
241 | {
242 | "ToString" => InterceptorTarget.ToString,
243 | "HasFlag" => InterceptorTarget.HasFlag,
244 | _ => default, // can't be reached
245 | };
246 | return new CandidateInvocation(location, type.ToString(), targetToIntercept);
247 | }
248 |
249 | return null;
250 | }
251 |
252 | private MethodToIntercept? FilterInterceptorCandidates(
253 | (EnumToIntercept Enum, ImmutableArray Candidates) arg1,
254 | CancellationToken ct)
255 | {
256 | if (arg1.Candidates.IsDefaultOrEmpty)
257 | {
258 | return default;
259 | }
260 |
261 | List? results = null;
262 | foreach (var candidate in arg1.Candidates)
263 | {
264 | if (arg1.Enum.FullyQualifiedName.Equals(candidate.EnumName, StringComparison.Ordinal))
265 | {
266 | results ??= new();
267 | results.Add(candidate);
268 | }
269 | }
270 |
271 | if (results is null)
272 | {
273 | return null;
274 | }
275 |
276 | return new(new(results.ToArray()), arg1.Enum);
277 | }
278 |
279 | private static void ExecuteInterceptors(MethodToIntercept? toIntercept, SourceProductionContext spc)
280 | {
281 | var (result, filename) = SourceGenerationHelper.GenerateInterceptorsClass(toIntercept!);
282 | spc.AddSource(filename, SourceText.From(result, Encoding.UTF8));
283 | }
284 | }
285 |
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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.Interceptors/NetEscapades.EnumGenerators.Interceptors.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/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.Interceptors/README.md:
--------------------------------------------------------------------------------
1 | #  NetEscapades.EnumGenerators.Interceptors
2 |
3 | A source generator interceptor for automatically intercepting calls to `ToString()` on enums, and replacing them with calls to `ToStringFast()` generated by [NetEscapades.EnumGenerators](https://www.nuget.org/packages/NetEscapades.EnumGenerators)
4 |
5 | > This source generator requires the .NET 8.0.400 SDK. You can target earlier frameworks like .NET Core 3.1 etc, but the _SDK_ must be at least 8.0.400
6 |
7 | ## Why use this package?
8 |
9 | Many methods that operate with enums, such as the `ToString()` or `HasFlag()` method, are surprisingly slow. The [NetEscapades.EnumGenerators](https://www.nuget.org/packages/NetEscapades.EnumGenerators) uses a source generator to provide _fast_ versions of these methods, such as `ToStringFast()` or `HasFlagFast()`.
10 |
11 | The main downside to the extension methods generated by [NetEscapades.EnumGenerators](https://www.nuget.org/packages/NetEscapades.EnumGenerators) is that you have to remember to use them. The [NetEscapades.EnumGenerators.Interceptors](https://www.nuget.org/packages/NetEscapades.EnumGenerators.Interceptors) package solves this problem by intercepting calls to `ToString()` and replacing them with calls `ToStringFast()` automatically using the .NET compiler feature called [interceptors](https://github.com/dotnet/roslyn/blob/main/docs/features/interceptors.md).
12 |
13 | > Interceptors were introduced as an experimental feature in C#12 with .NET 8, and are stable in .NET 9. They allow a source generator to "intercept" certain method calls, and replace the call with a different one.
14 |
15 | For example if you have this code:
16 |
17 | ```csharp
18 | var choice = Color.Red;
19 | Console.WriteLine("You chose: " + choice.ToString());
20 |
21 | public enum Color
22 | {
23 | Red = 0,
24 | Blue = 1,
25 | }
26 | ```
27 |
28 | When you use the [NetEscapades.EnumGenerators.Interceptors](https://www.nuget.org/packages/NetEscapades.EnumGenerators.Interceptors), the interceptor automatically replaces the call to `choice.ToString()` at compile-time with a call to `ToStringFast()`, as though you wrote the following:
29 |
30 | ```csharp
31 | // The compiler replaces the call with this 👇
32 | Console.WriteLine("You chose: " + choice.ToStringFast());
33 | ```
34 |
35 | There are many caveats to this behaviour, as described below, but any explicit calls to `ToString()` or `HasFlag()` on a supported enum are replaced automatically.
36 |
37 | ## Adding NetEscapades.EnumGenerators.Interceptors to your project
38 |
39 | Add the package to your application using:
40 |
41 | ```bash
42 | dotnet add package NetEscapades.EnumGenerators.Interceptors
43 | ```
44 |
45 | This adds a `` to your project. You can additionally mark the package as `PrivateAssets="all"` and `ExcludeAssets="runtime"`.
46 |
47 | > Setting `PrivateAssets="all"` means any projects referencing this one won't get a reference to the _NetEscapades.EnumGenerators.Interceptors_ package. Setting `ExcludeAssets="runtime"` ensures the _NetEscapades.EnumGenerators.Interceptors.Attributes.dll_ file is _not_ copied to your build output (it is not required at runtime).
48 |
49 | ```xml
50 |
51 |
52 |
53 | Exe
54 | net8.0
55 |
56 |
57 |
58 |
60 |
61 |
62 |
63 | ```
64 |
65 | ## Enabling interception for an enum
66 |
67 | By default, adding [NetEscapades.EnumGenerators.Interceptors](https://www.nuget.org/packages/NetEscapades.EnumGenerators.Interceptors) to a project enables interception for all enums _defined in that project_ that use the `[EnumExtensions]` or `[EnumExtensions]` attributes. If you wish to intercept calls made to enums with extensions defined in _other_ projects, you must add the `[Interceptable]` attribute in the project where you want the interception to happen, e.g.
68 |
69 | ```csharp
70 | [assembly:Interceptable]
71 | [assembly:Interceptable]
72 | ```
73 |
74 | If you don't want a specific enum to be intercepted, you can set the `IsInterceptable` property to `false`, e.g.
75 |
76 | ```csharp
77 | [EnumExtensions(IsInterceptable = false)]
78 | public enum Colour
79 | {
80 | Red = 0,
81 | Blue = 1,
82 | }
83 | ```
84 |
85 | Interception only works when the target type is unambiguously an interceptable enum, so it won't work
86 |
87 | - When `ToString()` is called in other source generated code.
88 | - When `ToString()` is called in already-compiled code.
89 | - If the `ToString()` call is _implicit_ (for example in `string` interpolation)
90 | - If the `ToString()` call is made on a base type, such as `System.Enum` or `object`
91 | - If the `ToString()` call is made on a generic type
92 |
93 | For example:
94 |
95 | ```csharp
96 | // All the examples in this method CAN be intercepted
97 | public void CanIntercept()
98 | {
99 | var ok1 = Color.Red.ToString(); // ✅
100 | var red = Color.Red;
101 | var ok2 = red.ToString(); // ✅
102 | var ok3 = "The colour is " + red.ToString(); // ✅
103 | var ok4 = $"The colour is {red.ToString()}"; // ✅
104 | }
105 |
106 | // The examples in this method can NOT be intercepted
107 | public void CantIntercept()
108 | {
109 | var bad1 = ((System.Enum)Color.Red).ToString(); // ❌ Base type
110 | var bad2 = ((object)Color.Red).ToString(); // ❌ Base type
111 |
112 | var bad3 = "The colour is " + red; // ❌ implicit
113 | var bad4 = $"The colour is {red}"; // ❌ implicit
114 |
115 | string Write(T val)
116 | where T : Enum
117 | {
118 | return val.ToString(); // ❌ generic
119 | }
120 | }
121 | ```
--------------------------------------------------------------------------------
/src/NetEscapades.EnumGenerators.Interceptors/SourceGenerationHelper.cs:
--------------------------------------------------------------------------------
1 | using System.Text;
2 |
3 | namespace NetEscapades.EnumGenerators.Interceptors;
4 |
5 | public static class SourceGenerationHelper
6 | {
7 | private const string Header =
8 | """
9 | //------------------------------------------------------------------------------
10 | //
11 | // This code was generated by the NetEscapades.EnumGenerators.Interceptors source generator
12 | //
13 | // Changes to this file may cause incorrect behavior and will be lost if
14 | // the code is regenerated.
15 | //
16 | //------------------------------------------------------------------------------
17 |
18 | #nullable enable
19 | """;
20 |
21 | public const string Attribute =
22 | $$"""
23 | {{Header}}
24 |
25 | #if NETESCAPADES_ENUMGENERATORS_EMBED_ATTRIBUTES
26 | namespace NetEscapades.EnumGenerators
27 | {
28 | ///
29 | /// Add to an assembly to indicate that usages of the enum should
30 | /// be automatically intercepted to use the extension methods
31 | /// generated by EnumExtensionsAttribute in this project.
32 | /// Note that the extension methods must be accessible from this project,
33 | /// otherwise you will receive compilation errors
34 | ///
35 | [global::System.AttributeUsage(global::System.AttributeTargets.Assembly, AllowMultiple = true)]
36 | [global::System.Diagnostics.Conditional("NETESCAPADES_ENUMGENERATORS_USAGES")]
37 | #if NET5_0_OR_GREATER
38 | [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage(Justification = "Generated by the NetEscapades.EnumGenerators.Interceptors source generator.")]
39 | #else
40 | [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
41 | #endif
42 | public class InterceptableAttribute : global::System.Attribute
43 | where T: global::System.Enum
44 | {
45 | ///
46 | /// The namespace generated for the extension class. If not provided,
47 | /// and the referenced enum is in a different project, the namespace
48 | /// of the extension methods are assumed to be the same as the enum.
49 | ///
50 | public string? ExtensionClassNamespace { get; set; }
51 |
52 | ///
53 | /// The name used for the extension class. If not provided,
54 | /// and the referenced enum is in a different project, the enum name
55 | /// with an Extensions suffix will be assumed. For example for
56 | /// an Enum called StatusCodes, the assumed name will be StatusCodesExtensions.
57 | ///
58 | public string? ExtensionClassName { get; set; }
59 | }
60 | }
61 | #endif
62 | """;
63 |
64 | public static (string Content, string Filename) GenerateInterceptorsClass(MethodToIntercept toIntercept)
65 | {
66 | var sb = new StringBuilder(
67 | $$"""
68 | {{Header}}
69 |
70 | namespace System.Runtime.CompilerServices
71 | {
72 | // this type is needed by the compiler to implement interceptors - it doesn't need to
73 | // come from the runtime itself, though
74 |
75 | [global::System.Diagnostics.Conditional("DEBUG")] // not needed post-build, so: evaporate
76 | [global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = true)]
77 | sealed file class InterceptsLocationAttribute : global::System.Attribute
78 | {
79 | public InterceptsLocationAttribute(int version, string data)
80 | {
81 | _ = version;
82 | _ = data;
83 | }
84 | }
85 | }
86 |
87 | #pragma warning disable CS0612 // Ignore usages of obsolete members or enums
88 | #pragma warning disable CS0618 // Ignore usages of obsolete members or enums
89 | namespace NetEscapades.EnumGenerators
90 | {
91 | static file class EnumInterceptors
92 | {
93 |
94 | """);
95 |
96 | bool toStringIntercepted = false;
97 | foreach (var location in toIntercept.Invocations)
98 | {
99 | if(location!.Target == InterceptorTarget.ToString)
100 | {
101 | toStringIntercepted = true;
102 | sb.AppendLine(GetInterceptorAttr(location));
103 | }
104 | }
105 |
106 | if(toStringIntercepted)
107 | {
108 | sb.AppendLine(
109 | $$"""
110 | public static string {{toIntercept.ExtensionTypeName}}ToString(this global::System.Enum value)
111 | => global::{{toIntercept.EnumNamespace}}{{(string.IsNullOrEmpty(toIntercept.EnumNamespace) ? "" : ".")}}{{toIntercept.ExtensionTypeName}}.ToStringFast((global::{{toIntercept.FullyQualifiedName}})value);
112 |
113 | """);
114 | }
115 |
116 | bool hasFlagIntercepted = false;
117 | foreach (var location in toIntercept.Invocations)
118 | {
119 | if(location!.Target == InterceptorTarget.HasFlag)
120 | {
121 | hasFlagIntercepted = true;
122 | sb.AppendLine(GetInterceptorAttr(location));
123 | }
124 | }
125 |
126 | if(hasFlagIntercepted)
127 | {
128 | sb.AppendLine(
129 | $$"""
130 | public static bool {{toIntercept.ExtensionTypeName}}HasFlag(this global::System.Enum value, global::System.Enum flag)
131 | => global::{{toIntercept.EnumNamespace}}{{(string.IsNullOrEmpty(toIntercept.EnumNamespace) ? "" : ".")}}{{toIntercept.ExtensionTypeName}}.HasFlagFast((global::{{toIntercept.FullyQualifiedName}})value, (global::{{toIntercept.FullyQualifiedName}})flag);
132 |
133 | """);
134 | }
135 |
136 | sb.AppendLine(
137 | """
138 | }
139 | }
140 | #pragma warning restore CS0612 // Ignore usages of obsolete members or enums
141 | #pragma warning restore CS0618 // Ignore usages of obsolete members or enums
142 | """);
143 | var content = sb.ToString();
144 | sb.Clear();
145 |
146 | var filename = sb
147 | .Append(toIntercept.FullyQualifiedName)
148 | .Append("_Interceptors.g.cs")
149 | .Replace('<', '_')
150 | .Replace('>', '_')
151 | .Replace(',', '.')
152 | .Replace(' ', '_')
153 | .ToString();
154 | return (content, filename);
155 |
156 | static string GetInterceptorAttr(CandidateInvocation location)
157 | {
158 | return $""" [global::System.Runtime.CompilerServices.InterceptsLocation({location.Location.Version}, "{location.Location.Data}")] // {location.Location.GetDisplayLocation()}""";
159 | }
160 | }
161 | }
--------------------------------------------------------------------------------
/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/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 EnumExtensionsAttribute = "NetEscapades.EnumGenerators.EnumExtensionsAttribute";
8 | public const string ExternalEnumExtensionsAttribute = "NetEscapades.EnumGenerators.EnumExtensionsAttribute`1";
9 | public const string FlagsAttribute = "System.FlagsAttribute";
10 | }
--------------------------------------------------------------------------------
/src/NetEscapades.EnumGenerators/Constants.cs:
--------------------------------------------------------------------------------
1 | namespace NetEscapades.EnumGenerators;
2 |
3 | public static class Constants
4 | {
5 | public const string Version = "1.0.0-beta13";
6 | }
--------------------------------------------------------------------------------
/src/NetEscapades.EnumGenerators/EnumGenerator.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Immutable;
2 | using System.Text;
3 | using Microsoft.CodeAnalysis;
4 | using Microsoft.CodeAnalysis.CSharp;
5 | using Microsoft.CodeAnalysis.CSharp.Syntax;
6 | using Microsoft.CodeAnalysis.Operations;
7 | using Microsoft.CodeAnalysis.Text;
8 |
9 | namespace NetEscapades.EnumGenerators;
10 |
11 | [Generator]
12 | public class EnumGenerator : IIncrementalGenerator
13 | {
14 | public void Initialize(IncrementalGeneratorInitializationContext context)
15 | {
16 | context.RegisterPostInitializationOutput(ctx => ctx.AddSource(
17 | "EnumExtensionsAttribute.g.cs", SourceText.From(SourceGenerationHelper.Attribute, Encoding.UTF8)));
18 |
19 | IncrementalValuesProvider enumsToGenerate = context.SyntaxProvider
20 | .ForAttributeWithMetadataName(Attributes.EnumExtensionsAttribute,
21 | predicate: static (node, _) => node is EnumDeclarationSyntax,
22 | transform: GetTypeToGenerate)
23 | .WithTrackingName(TrackingNames.InitialExtraction)
24 | .Where(static m => m is not null)
25 | .Select(static (m, _) => m!.Value)
26 | .WithTrackingName(TrackingNames.RemovingNulls);
27 |
28 | IncrementalValuesProvider externalEnums = context
29 | .SyntaxProvider
30 | .ForAttributeWithMetadataName(Attributes.ExternalEnumExtensionsAttribute,
31 | predicate: static (node, _) => node is CompilationUnitSyntax,
32 | transform: static (context1, ct) => GetEnumToGenerateFromGenericAssemblyAttribute(context1, ct, "EnumExtensionsAttribute", "EnumExtensions"))
33 | .Where(static m => m is not null)
34 | .SelectMany(static (m, _) => m!.Value)
35 | .WithTrackingName(TrackingNames.InitialExternalExtraction);
36 |
37 | context.RegisterSourceOutput(enumsToGenerate,
38 | static (spc, enumToGenerate) => Execute(in enumToGenerate, spc));
39 |
40 | context.RegisterSourceOutput(externalEnums,
41 | static (spc, enumToGenerate) => Execute(in enumToGenerate, spc));
42 | }
43 |
44 | static void Execute(in EnumToGenerate enumToGenerate, SourceProductionContext context)
45 | {
46 | var (result, filename) = SourceGenerationHelper.GenerateExtensionClass(in enumToGenerate);
47 | context.AddSource(filename, SourceText.From(result, Encoding.UTF8));
48 | }
49 |
50 | static EquatableArray? GetEnumToGenerateFromGenericAssemblyAttribute(
51 | GeneratorAttributeSyntaxContext context, CancellationToken ct, string fullAttributeName, string shortAttributeName)
52 | {
53 | List? enums = null;
54 | foreach (AttributeData attribute in context.Attributes)
55 | {
56 | if (!((attribute.AttributeClass?.Name == fullAttributeName ||
57 | attribute.AttributeClass?.Name == shortAttributeName) &&
58 | attribute.AttributeClass.IsGenericType &&
59 | attribute.AttributeClass.TypeArguments.Length == 1))
60 | {
61 | // wrong attribute
62 | continue;
63 | }
64 |
65 | var enumSymbol = attribute.AttributeClass.TypeArguments[0] as INamedTypeSymbol;
66 | if (enumSymbol is null)
67 | {
68 | continue;
69 | }
70 |
71 | bool hasFlags = false;
72 | string? name = null;
73 | string? nameSpace = null;
74 |
75 | foreach (KeyValuePair namedArgument in attribute.NamedArguments)
76 | {
77 | if (namedArgument.Key == "ExtensionClassNamespace"
78 | && namedArgument.Value.Value?.ToString() is { } ns)
79 | {
80 | nameSpace = ns;
81 | continue;
82 | }
83 |
84 | if (namedArgument.Key == "ExtensionClassName"
85 | && namedArgument.Value.Value?.ToString() is { } n)
86 | {
87 | name = n;
88 | }
89 | }
90 |
91 | foreach (var attrData in enumSymbol.GetAttributes())
92 | {
93 | if ((attrData.AttributeClass?.Name == "FlagsAttribute" ||
94 | attrData.AttributeClass?.Name == "Flags") &&
95 | attrData.AttributeClass.ToDisplayString() == Attributes.FlagsAttribute)
96 | {
97 | hasFlags = true;
98 | break;
99 | }
100 | }
101 |
102 | var enumToGenerate = TryExtractEnumSymbol(enumSymbol, name, nameSpace, hasFlags);
103 | if (enumToGenerate is not null)
104 | {
105 | enums ??= new();
106 | enums.Add(enumToGenerate.Value);
107 | }
108 | }
109 |
110 | return enums is not null
111 | ? new EquatableArray(enums.ToArray())
112 | : null;
113 | }
114 |
115 | static EnumToGenerate? GetTypeToGenerate(GeneratorAttributeSyntaxContext context, CancellationToken ct)
116 | {
117 | INamedTypeSymbol? enumSymbol = context.TargetSymbol as INamedTypeSymbol;
118 | if (enumSymbol is null)
119 | {
120 | // nothing to do if this type isn't available
121 | return null;
122 | }
123 |
124 | ct.ThrowIfCancellationRequested();
125 |
126 | var hasFlags = false;
127 | string? nameSpace = null;
128 | string? name = null;
129 |
130 | foreach (AttributeData attributeData in enumSymbol.GetAttributes())
131 | {
132 | if ((attributeData.AttributeClass?.Name == "FlagsAttribute" ||
133 | attributeData.AttributeClass?.Name == "Flags") &&
134 | attributeData.AttributeClass.ToDisplayString() == Attributes.FlagsAttribute)
135 | {
136 | hasFlags = true;
137 | continue;
138 | }
139 |
140 | if (attributeData.AttributeClass?.Name != "EnumExtensionsAttribute" ||
141 | attributeData.AttributeClass.ToDisplayString() != Attributes.EnumExtensionsAttribute)
142 | {
143 | continue;
144 | }
145 |
146 | foreach (KeyValuePair namedArgument in attributeData.NamedArguments)
147 | {
148 | if (namedArgument.Key == "ExtensionClassNamespace"
149 | && namedArgument.Value.Value?.ToString() is { } ns)
150 | {
151 | nameSpace = ns;
152 | continue;
153 | }
154 |
155 | if (namedArgument.Key == "ExtensionClassName"
156 | && namedArgument.Value.Value?.ToString() is { } n)
157 | {
158 | name = n;
159 | }
160 | }
161 | }
162 |
163 | return TryExtractEnumSymbol(enumSymbol, name, nameSpace, hasFlags);
164 | }
165 |
166 | static EnumToGenerate? TryExtractEnumSymbol(INamedTypeSymbol enumSymbol, string? name, string? nameSpace, bool hasFlags)
167 | {
168 | name ??= enumSymbol.Name + "Extensions";
169 | nameSpace ??= enumSymbol.ContainingNamespace.IsGlobalNamespace ? string.Empty : enumSymbol.ContainingNamespace.ToString();
170 |
171 | string fullyQualifiedName = enumSymbol.ToString();
172 | string underlyingType = enumSymbol.EnumUnderlyingType?.ToString() ?? "int";
173 |
174 | var enumMembers = enumSymbol.GetMembers();
175 | var members = new List<(string, EnumValueOption)>(enumMembers.Length);
176 | HashSet? displayNames = null;
177 | var isDisplayNameTheFirstPresence = false;
178 |
179 | foreach (var member in enumMembers)
180 | {
181 | if (member is not IFieldSymbol {ConstantValue: { } constantValue})
182 | {
183 | continue;
184 | }
185 |
186 | string? displayName = null;
187 | foreach (var attribute in member.GetAttributes())
188 | {
189 | if (attribute.AttributeClass?.Name == "DisplayAttribute" &&
190 | attribute.AttributeClass.ToDisplayString() == Attributes.DisplayAttribute)
191 | {
192 | foreach (var namedArgument in attribute.NamedArguments)
193 | {
194 | if (namedArgument.Key == "Name" && namedArgument.Value.Value?.ToString() is { } dn)
195 | {
196 | // found display attribute, all done
197 | displayName = dn;
198 | goto addDisplayName;
199 | }
200 | }
201 | }
202 |
203 | if (attribute.AttributeClass?.Name == "DescriptionAttribute"
204 | && attribute.AttributeClass.ToDisplayString() == Attributes.DescriptionAttribute
205 | && attribute.ConstructorArguments.Length == 1)
206 | {
207 | if (attribute.ConstructorArguments[0].Value?.ToString() is { } dn)
208 | {
209 | // found display attribute, all done
210 | // Handle cases where contains a quote or a backslash
211 | displayName = dn;
212 | goto addDisplayName;
213 | }
214 | }
215 | }
216 |
217 | addDisplayName:
218 | if (displayName is not null)
219 | {
220 | displayNames ??= new();
221 | isDisplayNameTheFirstPresence = displayNames.Add(displayName);
222 | }
223 |
224 | members.Add((member.Name, new EnumValueOption(displayName, isDisplayNameTheFirstPresence, constantValue)));
225 | }
226 |
227 | return new EnumToGenerate(
228 | name: name,
229 | fullyQualifiedName: fullyQualifiedName,
230 | ns: nameSpace,
231 | underlyingType: underlyingType,
232 | isPublic: enumSymbol.DeclaredAccessibility == Accessibility.Public,
233 | hasFlags: hasFlags,
234 | names: members,
235 | isDisplayAttributeUsed: displayNames?.Count > 0);
236 | }
237 | }
238 |
--------------------------------------------------------------------------------
/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 |
15 | ///
16 | /// Key is the enum name.
17 | ///
18 | public readonly EquatableArray<(string Key, EnumValueOption Value)> Names;
19 |
20 | public readonly bool IsDisplayAttributeUsed;
21 |
22 | public EnumToGenerate(
23 | string name,
24 | string ns,
25 | string fullyQualifiedName,
26 | string underlyingType,
27 | bool isPublic,
28 | List<(string Key, EnumValueOption Value)> names,
29 | bool hasFlags,
30 | bool isDisplayAttributeUsed)
31 | {
32 | Name = name;
33 | Namespace = ns;
34 | UnderlyingType = underlyingType;
35 | Names = new EquatableArray<(string, EnumValueOption)>(names.ToArray());
36 | HasFlags = hasFlags;
37 | IsPublic = isPublic;
38 | FullyQualifiedName = fullyQualifiedName;
39 | IsDisplayAttributeUsed = isDisplayAttributeUsed;
40 | }
41 | }
--------------------------------------------------------------------------------
/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 | public string? DisplayName { get; }
9 | public bool IsDisplayNameTheFirstPresence { get; }
10 | public object ConstantValue { get; }
11 |
12 | public EnumValueOption(string? displayName, bool isDisplayNameTheFirstPresence, object constantValue)
13 | {
14 | DisplayName = displayName;
15 | IsDisplayNameTheFirstPresence = isDisplayNameTheFirstPresence;
16 | ConstantValue = constantValue;
17 | }
18 |
19 | public bool Equals(EnumValueOption other)
20 | {
21 | return DisplayName == other.DisplayName &&
22 | IsDisplayNameTheFirstPresence == other.IsDisplayNameTheFirstPresence &&
23 | Equals(ConstantValue, other.ConstantValue);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/src/NetEscapades.EnumGenerators/HashCode.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel;
2 | using System.Runtime.CompilerServices;
3 |
4 | namespace NetEscapades.EnumGenerators;
5 |
6 | ///
7 | /// Polyfill for .NET 6 HashCode
8 | ///
9 | internal struct HashCode
10 | {
11 | private static readonly uint s_seed = GenerateGlobalSeed();
12 |
13 | private const uint Prime1 = 2654435761U;
14 | private const uint Prime2 = 2246822519U;
15 | private const uint Prime3 = 3266489917U;
16 | private const uint Prime4 = 668265263U;
17 | private const uint Prime5 = 374761393U;
18 |
19 | private uint _v1, _v2, _v3, _v4;
20 | private uint _queue1, _queue2, _queue3;
21 | private uint _length;
22 |
23 | private static uint GenerateGlobalSeed()
24 | {
25 | var buffer = new byte[sizeof(uint)];
26 | new Random().NextBytes(buffer);
27 | return BitConverter.ToUInt32(buffer, 0);
28 | }
29 |
30 | public static int Combine(T1 value1)
31 | {
32 | // Provide a way of diffusing bits from something with a limited
33 | // input hash space. For example, many enums only have a few
34 | // possible hashes, only using the bottom few bits of the code. Some
35 | // collections are built on the assumption that hashes are spread
36 | // over a larger space, so diffusing the bits may help the
37 | // collection work more efficiently.
38 |
39 | uint hc1 = (uint)(value1?.GetHashCode() ?? 0);
40 |
41 | uint hash = MixEmptyState();
42 | hash += 4;
43 |
44 | hash = QueueRound(hash, hc1);
45 |
46 | hash = MixFinal(hash);
47 | return (int)hash;
48 | }
49 |
50 | public static int Combine(T1 value1, T2 value2)
51 | {
52 | uint hc1 = (uint)(value1?.GetHashCode() ?? 0);
53 | uint hc2 = (uint)(value2?.GetHashCode() ?? 0);
54 |
55 | uint hash = MixEmptyState();
56 | hash += 8;
57 |
58 | hash = QueueRound(hash, hc1);
59 | hash = QueueRound(hash, hc2);
60 |
61 | hash = MixFinal(hash);
62 | return (int)hash;
63 | }
64 |
65 | public static int Combine(T1 value1, T2 value2, T3 value3)
66 | {
67 | uint hc1 = (uint)(value1?.GetHashCode() ?? 0);
68 | uint hc2 = (uint)(value2?.GetHashCode() ?? 0);
69 | uint hc3 = (uint)(value3?.GetHashCode() ?? 0);
70 |
71 | uint hash = MixEmptyState();
72 | hash += 12;
73 |
74 | hash = QueueRound(hash, hc1);
75 | hash = QueueRound(hash, hc2);
76 | hash = QueueRound(hash, hc3);
77 |
78 | hash = MixFinal(hash);
79 | return (int)hash;
80 | }
81 |
82 | public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4)
83 | {
84 | uint hc1 = (uint)(value1?.GetHashCode() ?? 0);
85 | uint hc2 = (uint)(value2?.GetHashCode() ?? 0);
86 | uint hc3 = (uint)(value3?.GetHashCode() ?? 0);
87 | uint hc4 = (uint)(value4?.GetHashCode() ?? 0);
88 |
89 | Initialize(out uint v1, out uint v2, out uint v3, out uint v4);
90 |
91 | v1 = Round(v1, hc1);
92 | v2 = Round(v2, hc2);
93 | v3 = Round(v3, hc3);
94 | v4 = Round(v4, hc4);
95 |
96 | uint hash = MixState(v1, v2, v3, v4);
97 | hash += 16;
98 |
99 | hash = MixFinal(hash);
100 | return (int)hash;
101 | }
102 |
103 | public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5)
104 | {
105 | uint hc1 = (uint)(value1?.GetHashCode() ?? 0);
106 | uint hc2 = (uint)(value2?.GetHashCode() ?? 0);
107 | uint hc3 = (uint)(value3?.GetHashCode() ?? 0);
108 | uint hc4 = (uint)(value4?.GetHashCode() ?? 0);
109 | uint hc5 = (uint)(value5?.GetHashCode() ?? 0);
110 |
111 | Initialize(out uint v1, out uint v2, out uint v3, out uint v4);
112 |
113 | v1 = Round(v1, hc1);
114 | v2 = Round(v2, hc2);
115 | v3 = Round(v3, hc3);
116 | v4 = Round(v4, hc4);
117 |
118 | uint hash = MixState(v1, v2, v3, v4);
119 | hash += 20;
120 |
121 | hash = QueueRound(hash, hc5);
122 |
123 | hash = MixFinal(hash);
124 | return (int)hash;
125 | }
126 |
127 | public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6)
128 | {
129 | uint hc1 = (uint)(value1?.GetHashCode() ?? 0);
130 | uint hc2 = (uint)(value2?.GetHashCode() ?? 0);
131 | uint hc3 = (uint)(value3?.GetHashCode() ?? 0);
132 | uint hc4 = (uint)(value4?.GetHashCode() ?? 0);
133 | uint hc5 = (uint)(value5?.GetHashCode() ?? 0);
134 | uint hc6 = (uint)(value6?.GetHashCode() ?? 0);
135 |
136 | Initialize(out uint v1, out uint v2, out uint v3, out uint v4);
137 |
138 | v1 = Round(v1, hc1);
139 | v2 = Round(v2, hc2);
140 | v3 = Round(v3, hc3);
141 | v4 = Round(v4, hc4);
142 |
143 | uint hash = MixState(v1, v2, v3, v4);
144 | hash += 24;
145 |
146 | hash = QueueRound(hash, hc5);
147 | hash = QueueRound(hash, hc6);
148 |
149 | hash = MixFinal(hash);
150 | return (int)hash;
151 | }
152 |
153 | public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6, T7 value7)
154 | {
155 | uint hc1 = (uint)(value1?.GetHashCode() ?? 0);
156 | uint hc2 = (uint)(value2?.GetHashCode() ?? 0);
157 | uint hc3 = (uint)(value3?.GetHashCode() ?? 0);
158 | uint hc4 = (uint)(value4?.GetHashCode() ?? 0);
159 | uint hc5 = (uint)(value5?.GetHashCode() ?? 0);
160 | uint hc6 = (uint)(value6?.GetHashCode() ?? 0);
161 | uint hc7 = (uint)(value7?.GetHashCode() ?? 0);
162 |
163 | Initialize(out uint v1, out uint v2, out uint v3, out uint v4);
164 |
165 | v1 = Round(v1, hc1);
166 | v2 = Round(v2, hc2);
167 | v3 = Round(v3, hc3);
168 | v4 = Round(v4, hc4);
169 |
170 | uint hash = MixState(v1, v2, v3, v4);
171 | hash += 28;
172 |
173 | hash = QueueRound(hash, hc5);
174 | hash = QueueRound(hash, hc6);
175 | hash = QueueRound(hash, hc7);
176 |
177 | hash = MixFinal(hash);
178 | return (int)hash;
179 | }
180 |
181 | public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6, T7 value7, T8 value8)
182 | {
183 | uint hc1 = (uint)(value1?.GetHashCode() ?? 0);
184 | uint hc2 = (uint)(value2?.GetHashCode() ?? 0);
185 | uint hc3 = (uint)(value3?.GetHashCode() ?? 0);
186 | uint hc4 = (uint)(value4?.GetHashCode() ?? 0);
187 | uint hc5 = (uint)(value5?.GetHashCode() ?? 0);
188 | uint hc6 = (uint)(value6?.GetHashCode() ?? 0);
189 | uint hc7 = (uint)(value7?.GetHashCode() ?? 0);
190 | uint hc8 = (uint)(value8?.GetHashCode() ?? 0);
191 |
192 | Initialize(out uint v1, out uint v2, out uint v3, out uint v4);
193 |
194 | v1 = Round(v1, hc1);
195 | v2 = Round(v2, hc2);
196 | v3 = Round(v3, hc3);
197 | v4 = Round(v4, hc4);
198 |
199 | v1 = Round(v1, hc5);
200 | v2 = Round(v2, hc6);
201 | v3 = Round(v3, hc7);
202 | v4 = Round(v4, hc8);
203 |
204 | uint hash = MixState(v1, v2, v3, v4);
205 | hash += 32;
206 |
207 | hash = MixFinal(hash);
208 | return (int)hash;
209 | }
210 |
211 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
212 | private static void Initialize(out uint v1, out uint v2, out uint v3, out uint v4)
213 | {
214 | v1 = s_seed + Prime1 + Prime2;
215 | v2 = s_seed + Prime2;
216 | v3 = s_seed;
217 | v4 = s_seed - Prime1;
218 | }
219 |
220 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
221 | private static uint Round(uint hash, uint input)
222 | {
223 | return RotateLeft(hash + input * Prime2, 13) * Prime1;
224 | }
225 |
226 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
227 | private static uint QueueRound(uint hash, uint queuedValue)
228 | {
229 | return RotateLeft(hash + queuedValue * Prime3, 17) * Prime4;
230 | }
231 |
232 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
233 | private static uint MixState(uint v1, uint v2, uint v3, uint v4)
234 | {
235 | return RotateLeft(v1, 1) + RotateLeft(v2, 7) + RotateLeft(v3, 12) + RotateLeft(v4, 18);
236 | }
237 |
238 | private static uint MixEmptyState()
239 | {
240 | return s_seed + Prime5;
241 | }
242 |
243 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
244 | private static uint MixFinal(uint hash)
245 | {
246 | hash ^= hash >> 15;
247 | hash *= Prime2;
248 | hash ^= hash >> 13;
249 | hash *= Prime3;
250 | hash ^= hash >> 16;
251 | return hash;
252 | }
253 |
254 | public void Add(T value)
255 | {
256 | Add(value?.GetHashCode() ?? 0);
257 | }
258 |
259 | public void Add(T value, IEqualityComparer? comparer)
260 | {
261 | Add(value is null ? 0 : (comparer?.GetHashCode(value) ?? value.GetHashCode()));
262 | }
263 |
264 | private void Add(int value)
265 | {
266 | // The original xxHash works as follows:
267 | // 0. Initialize immediately. We can't do this in a struct (no
268 | // default ctor).
269 | // 1. Accumulate blocks of length 16 (4 uints) into 4 accumulators.
270 | // 2. Accumulate remaining blocks of length 4 (1 uint) into the
271 | // hash.
272 | // 3. Accumulate remaining blocks of length 1 into the hash.
273 |
274 | // There is no need for #3 as this type only accepts ints. _queue1,
275 | // _queue2 and _queue3 are basically a buffer so that when
276 | // ToHashCode is called we can execute #2 correctly.
277 |
278 | // We need to initialize the xxHash32 state (_v1 to _v4) lazily (see
279 | // #0) nd the last place that can be done if you look at the
280 | // original code is just before the first block of 16 bytes is mixed
281 | // in. The xxHash32 state is never used for streams containing fewer
282 | // than 16 bytes.
283 |
284 | // To see what's really going on here, have a look at the Combine
285 | // methods.
286 |
287 | uint val = (uint)value;
288 |
289 | // Storing the value of _length locally shaves off quite a few bytes
290 | // in the resulting machine code.
291 | uint previousLength = _length++;
292 | uint position = previousLength % 4;
293 |
294 | // Switch can't be inlined.
295 |
296 | if (position == 0)
297 | _queue1 = val;
298 | else if (position == 1)
299 | _queue2 = val;
300 | else if (position == 2)
301 | _queue3 = val;
302 | else // position == 3
303 | {
304 | if (previousLength == 3)
305 | Initialize(out _v1, out _v2, out _v3, out _v4);
306 |
307 | _v1 = Round(_v1, _queue1);
308 | _v2 = Round(_v2, _queue2);
309 | _v3 = Round(_v3, _queue3);
310 | _v4 = Round(_v4, val);
311 | }
312 | }
313 |
314 | public int ToHashCode()
315 | {
316 | // Storing the value of _length locally shaves off quite a few bytes
317 | // in the resulting machine code.
318 | uint length = _length;
319 |
320 | // position refers to the *next* queue position in this method, so
321 | // position == 1 means that _queue1 is populated; _queue2 would have
322 | // been populated on the next call to Add.
323 | uint position = length % 4;
324 |
325 | // If the length is less than 4, _v1 to _v4 don't contain anything
326 | // yet. xxHash32 treats this differently.
327 |
328 | uint hash = length < 4 ? MixEmptyState() : MixState(_v1, _v2, _v3, _v4);
329 |
330 | // _length is incremented once per Add(Int32) and is therefore 4
331 | // times too small (xxHash length is in bytes, not ints).
332 |
333 | hash += length * 4;
334 |
335 | // Mix what remains in the queue
336 |
337 | // Switch can't be inlined right now, so use as few branches as
338 | // possible by manually excluding impossible scenarios (position > 1
339 | // is always false if position is not > 0).
340 | if (position > 0)
341 | {
342 | hash = QueueRound(hash, _queue1);
343 | if (position > 1)
344 | {
345 | hash = QueueRound(hash, _queue2);
346 | if (position > 2)
347 | hash = QueueRound(hash, _queue3);
348 | }
349 | }
350 |
351 | hash = MixFinal(hash);
352 | return (int)hash;
353 | }
354 |
355 | #pragma warning disable 0809
356 | // Obsolete member 'memberA' overrides non-obsolete member 'memberB'.
357 | // Disallowing GetHashCode and Equals is by design
358 |
359 | // * We decided to not override GetHashCode() to produce the hash code
360 | // as this would be weird, both naming-wise as well as from a
361 | // behavioral standpoint (GetHashCode() should return the object's
362 | // hash code, not the one being computed).
363 |
364 | // * Even though ToHashCode() can be called safely multiple times on
365 | // this implementation, it is not part of the contract. If the
366 | // implementation has to change in the future we don't want to worry
367 | // about people who might have incorrectly used this type.
368 |
369 | [Obsolete("HashCode is a mutable struct and should not be compared with other HashCodes. Use ToHashCode to retrieve the computed hash code.", error: true)]
370 | [EditorBrowsable(EditorBrowsableState.Never)]
371 | public override int GetHashCode() => throw new NotSupportedException("Hash code not supported");
372 |
373 | [Obsolete("HashCode is a mutable struct and should not be compared with other HashCodes.", error: true)]
374 | [EditorBrowsable(EditorBrowsableState.Never)]
375 | public override bool Equals(object? obj) => throw new NotSupportedException("Equality not supported");
376 | #pragma warning restore 0809
377 |
378 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
379 | private static uint RotateLeft(uint value, int offset)
380 | => (value << offset) | (value >> (32 - offset));
381 | }
382 |
--------------------------------------------------------------------------------
/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/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 | all
22 | runtime; build; native; contentfiles; analyzers; buildtransitive
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/tests/NetEscapades.EnumGenerators.Benchmarks/NetEscapades.EnumGenerators.Benchmarks.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net48;net6.0
6 | enable
7 | enable
8 | false
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/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 INTERCEPTOR_TESTS
10 | namespace NetEscapades.EnumGenerators.Interceptors.IntegrationTests;
11 | #elif NUGET_ATTRS_INTEGRATION_TESTS
12 | namespace NetEscapades.EnumGenerators.Nuget.Attributes.IntegrationTests;
13 | #elif NUGET_INTEGRATION_TESTS
14 | namespace NetEscapades.EnumGenerators.Nuget.IntegrationTests;
15 | #elif NUGET_INTERCEPTOR_TESTS
16 | namespace NetEscapades.EnumGenerators.Nuget.Interceptors.IntegrationTests;
17 | #else
18 | #error Unknown integration tests
19 | #endif
20 |
21 | public class EnumInFooExtensionsTests : ExtensionTests, ITestData
22 | {
23 | public TheoryData ValidEnumValues() => new()
24 | {
25 | EnumInFoo.First,
26 | EnumInFoo.Second,
27 | (EnumInFoo)3,
28 | };
29 |
30 | public TheoryData ValuesToParse() => new()
31 | {
32 | "First",
33 | "Second",
34 | "2nd",
35 | "2ND",
36 | "first",
37 | "SECOND",
38 | "3",
39 | "267",
40 | "-267",
41 | "2147483647",
42 | "3000000000",
43 | "Fourth",
44 | "Fifth",
45 | };
46 |
47 | protected override string[] GetNames() => EnumInFooExtensions.GetNames();
48 | protected override EnumInFoo[] GetValues() => EnumInFooExtensions.GetValues();
49 | protected override int[] GetValuesAsUnderlyingType() => EnumInFooExtensions.GetValuesAsUnderlyingType();
50 | protected override int AsUnderlyingValue(EnumInFoo value) => value.AsUnderlyingType();
51 |
52 | protected override string ToStringFast(EnumInFoo value) => value.ToStringFast();
53 | protected override string ToStringFast(EnumInFoo value, bool withMetadata) => value.ToStringFast(withMetadata);
54 | protected override bool IsDefined(EnumInFoo value) => EnumInFooExtensions.IsDefined(value);
55 | protected override bool IsDefined(string name, bool allowMatchingMetadataAttribute) => EnumInFooExtensions.IsDefined(name, allowMatchingMetadataAttribute);
56 | #if READONLYSPAN
57 | protected override bool IsDefined(in ReadOnlySpan name, bool allowMatchingMetadataAttribute) => EnumInFooExtensions.IsDefined(name, allowMatchingMetadataAttribute);
58 | #endif
59 | protected override bool TryParse(string name, out EnumInFoo parsed, bool ignoreCase, bool allowMatchingMetadataAttribute)
60 | => EnumInFooExtensions.TryParse(name, out parsed, ignoreCase);
61 | #if READONLYSPAN
62 | protected override bool TryParse(in ReadOnlySpan name, out EnumInFoo parsed, bool ignoreCase, bool allowMatchingMetadataAttribute)
63 | => EnumInFooExtensions.TryParse(name, out parsed, ignoreCase);
64 | #endif
65 |
66 | protected override EnumInFoo Parse(string name, bool ignoreCase, bool allowMatchingMetadataAttribute)
67 | => EnumInFooExtensions.Parse(name, ignoreCase);
68 | #if READONLYSPAN
69 | protected override EnumInFoo Parse(in ReadOnlySpan name, bool ignoreCase, bool allowMatchingMetadataAttribute)
70 | => EnumInFooExtensions.Parse(name, ignoreCase);
71 | #endif
72 | }
--------------------------------------------------------------------------------
/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 INTERCEPTOR_TESTS
9 | namespace NetEscapades.EnumGenerators.Interceptors.IntegrationTests;
10 | #elif NUGET_ATTRS_INTEGRATION_TESTS
11 | namespace NetEscapades.EnumGenerators.Nuget.Attributes.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 | #else
17 | #error Unknown integration tests
18 | #endif
19 |
20 | public class EnumInNamespaceExtensionsTests : ExtensionTests, ITestData
21 | {
22 | public TheoryData ValidEnumValues() => new()
23 | {
24 | EnumInNamespace.First,
25 | EnumInNamespace.Second,
26 | (EnumInNamespace)3,
27 | };
28 |
29 | public TheoryData ValuesToParse() => new()
30 | {
31 | "First",
32 | "Second",
33 | "2nd",
34 | "2ND",
35 | "first",
36 | "SECOND",
37 | "3",
38 | "267",
39 | "-267",
40 | "2147483647",
41 | "3000000000",
42 | "Fourth",
43 | "Fifth",
44 | };
45 |
46 | protected override string[] GetNames() => EnumInNamespaceExtensions.GetNames();
47 | protected override EnumInNamespace[] GetValues() => EnumInNamespaceExtensions.GetValues();
48 | protected override int[] GetValuesAsUnderlyingType() => EnumInNamespaceExtensions.GetValuesAsUnderlyingType();
49 | protected override int AsUnderlyingValue(EnumInNamespace value) => value.AsUnderlyingType();
50 |
51 | protected override string ToStringFast(EnumInNamespace value) => value.ToStringFast();
52 | protected override string ToStringFast(EnumInNamespace value, bool withMetadata) => value.ToStringFast(withMetadata);
53 | protected override bool IsDefined(EnumInNamespace value) => EnumInNamespaceExtensions.IsDefined(value);
54 | protected override bool IsDefined(string name, bool allowMatchingMetadataAttribute) => EnumInNamespaceExtensions.IsDefined(name, allowMatchingMetadataAttribute);
55 | #if READONLYSPAN
56 | protected override bool IsDefined(in ReadOnlySpan name, bool allowMatchingMetadataAttribute) => EnumInNamespaceExtensions.IsDefined(name, allowMatchingMetadataAttribute);
57 | #endif
58 | protected override bool TryParse(string name, out EnumInNamespace parsed, bool ignoreCase, bool allowMatchingMetadataAttribute)
59 | => EnumInNamespaceExtensions.TryParse(name, out parsed, ignoreCase);
60 | #if READONLYSPAN
61 | protected override bool TryParse(in ReadOnlySpan name, out EnumInNamespace parsed, bool ignoreCase, bool allowMatchingMetadataAttribute)
62 | => EnumInNamespaceExtensions.TryParse(name, out parsed, ignoreCase);
63 | #endif
64 |
65 | protected override EnumInNamespace Parse(string name, bool ignoreCase, bool allowMatchingMetadataAttribute)
66 | => EnumInNamespaceExtensions.Parse(name, ignoreCase);
67 | #if READONLYSPAN
68 | protected override EnumInNamespace Parse(in ReadOnlySpan name, bool ignoreCase, bool allowMatchingMetadataAttribute)
69 | => EnumInNamespaceExtensions.Parse(name, ignoreCase);
70 | #endif
71 | }
--------------------------------------------------------------------------------
/tests/NetEscapades.EnumGenerators.IntegrationTests/EnumWithDescriptionInNamespaceExtensionsTests.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 INTERCEPTOR_TESTS
9 | namespace NetEscapades.EnumGenerators.Interceptors.IntegrationTests;
10 | #elif NUGET_ATTRS_INTEGRATION_TESTS
11 | namespace NetEscapades.EnumGenerators.Nuget.Attributes.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 | #else
17 | #error Unknown integration tests
18 | #endif
19 |
20 | public class EnumWithDescriptionInNamespaceExtensionsTests : ExtensionTests, ITestData
21 | {
22 | public TheoryData ValidEnumValues() => new()
23 | {
24 | EnumWithDescriptionInNamespace.First,
25 | EnumWithDescriptionInNamespace.Second,
26 | (EnumWithDescriptionInNamespace)3,
27 | };
28 |
29 | public TheoryData ValuesToParse() => new()
30 | {
31 | "First",
32 | "Second",
33 | "2nd",
34 | "2ND",
35 | "first",
36 | "SECOND",
37 | "3",
38 | "267",
39 | "-267",
40 | "2147483647",
41 | "3000000000",
42 | "Fourth",
43 | "Fifth",
44 | };
45 |
46 | protected override string[] GetNames() => EnumWithDescriptionInNamespaceExtensions.GetNames();
47 | protected override EnumWithDescriptionInNamespace[] GetValues() => EnumWithDescriptionInNamespaceExtensions.GetValues();
48 | protected override int[] GetValuesAsUnderlyingType() => EnumWithDescriptionInNamespaceExtensions.GetValuesAsUnderlyingType();
49 | protected override int AsUnderlyingValue(EnumWithDescriptionInNamespace value) => value.AsUnderlyingType();
50 |
51 | protected override string ToStringFast(EnumWithDescriptionInNamespace value) => value.ToStringFast();
52 | protected override string ToStringFast(EnumWithDescriptionInNamespace value, bool withMetadata) => value.ToStringFast(withMetadata);
53 | protected override bool IsDefined(EnumWithDescriptionInNamespace value) => EnumWithDescriptionInNamespaceExtensions.IsDefined(value);
54 | protected override bool IsDefined(string name, bool allowMatchingMetadataAttribute) => EnumWithDescriptionInNamespaceExtensions.IsDefined(name, allowMatchingMetadataAttribute);
55 | #if READONLYSPAN
56 | protected override bool IsDefined(in ReadOnlySpan name, bool allowMatchingMetadataAttribute = false) => EnumWithDescriptionInNamespaceExtensions.IsDefined(name, allowMatchingMetadataAttribute);
57 | #endif
58 | protected override bool TryParse(string name, out EnumWithDescriptionInNamespace parsed, bool ignoreCase, bool allowMatchingMetadataAttribute)
59 | => EnumWithDescriptionInNamespaceExtensions.TryParse(name, out parsed, ignoreCase, allowMatchingMetadataAttribute);
60 | #if READONLYSPAN
61 | protected override bool TryParse(in ReadOnlySpan name, out EnumWithDescriptionInNamespace parsed, bool ignoreCase, bool allowMatchingMetadataAttribute)
62 | => EnumWithDescriptionInNamespaceExtensions.TryParse(name, out parsed, ignoreCase, allowMatchingMetadataAttribute);
63 | #endif
64 | protected override EnumWithDescriptionInNamespace Parse(string name, bool ignoreCase, bool allowMatchingMetadataAttribute)
65 | => EnumWithDescriptionInNamespaceExtensions.Parse(name, ignoreCase);
66 | #if READONLYSPAN
67 | protected override EnumWithDescriptionInNamespace Parse(in ReadOnlySpan name, bool ignoreCase, bool allowMatchingMetadataAttribute)
68 | => EnumWithDescriptionInNamespaceExtensions.Parse(name, ignoreCase);
69 | #endif
70 | }
--------------------------------------------------------------------------------
/tests/NetEscapades.EnumGenerators.IntegrationTests/EnumWithDisplayNameInNamespaceExtensionsTests.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 INTERCEPTOR_TESTS
9 | namespace NetEscapades.EnumGenerators.Interceptors.IntegrationTests;
10 | #elif NUGET_ATTRS_INTEGRATION_TESTS
11 | namespace NetEscapades.EnumGenerators.Nuget.Attributes.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 | #else
17 | #error Unknown integration tests
18 | #endif
19 |
20 | public class EnumWithDisplayNameInNamespaceExtensionsTests : ExtensionTests, ITestData
21 | {
22 | public TheoryData ValidEnumValues() => new()
23 | {
24 | EnumWithDisplayNameInNamespace.First,
25 | EnumWithDisplayNameInNamespace.Second,
26 | (EnumWithDisplayNameInNamespace)3,
27 | };
28 |
29 | public TheoryData ValuesToParse() => new()
30 | {
31 | "First",
32 | "Second",
33 | "2nd",
34 | "2ND",
35 | "first",
36 | "SECOND",
37 | "3",
38 | "267",
39 | "-267",
40 | "2147483647",
41 | "3000000000",
42 | "Fourth",
43 | "Fifth",
44 | };
45 |
46 | protected override string[] GetNames() => EnumWithDisplayNameInNamespaceExtensions.GetNames();
47 | protected override EnumWithDisplayNameInNamespace[] GetValues() => EnumWithDisplayNameInNamespaceExtensions.GetValues();
48 | protected override int[] GetValuesAsUnderlyingType() => EnumWithDisplayNameInNamespaceExtensions.GetValuesAsUnderlyingType();
49 | protected override int AsUnderlyingValue(EnumWithDisplayNameInNamespace value) => value.AsUnderlyingType();
50 |
51 | protected override string ToStringFast(EnumWithDisplayNameInNamespace value) => value.ToStringFast();
52 | protected override string ToStringFast(EnumWithDisplayNameInNamespace value, bool withMetadata) => value.ToStringFast(withMetadata);
53 | protected override bool IsDefined(EnumWithDisplayNameInNamespace value) => EnumWithDisplayNameInNamespaceExtensions.IsDefined(value);
54 | protected override bool IsDefined(string name, bool allowMatchingMetadataAttribute) => EnumWithDisplayNameInNamespaceExtensions.IsDefined(name, allowMatchingMetadataAttribute);
55 | #if READONLYSPAN
56 | protected override bool IsDefined(in ReadOnlySpan name, bool allowMatchingMetadataAttribute = false) => EnumWithDisplayNameInNamespaceExtensions.IsDefined(name, allowMatchingMetadataAttribute);
57 | #endif
58 | protected override bool TryParse(string name, out EnumWithDisplayNameInNamespace parsed, bool ignoreCase, bool allowMatchingMetadataAttribute)
59 | => EnumWithDisplayNameInNamespaceExtensions.TryParse(name, out parsed, ignoreCase, allowMatchingMetadataAttribute);
60 | #if READONLYSPAN
61 | protected override bool TryParse(in ReadOnlySpan name, out EnumWithDisplayNameInNamespace parsed, bool ignoreCase, bool allowMatchingMetadataAttribute)
62 | => EnumWithDisplayNameInNamespaceExtensions.TryParse(name, out parsed, ignoreCase, allowMatchingMetadataAttribute);
63 | #endif
64 | protected override EnumWithDisplayNameInNamespace Parse(string name, bool ignoreCase, bool allowMatchingMetadataAttribute)
65 | => EnumWithDisplayNameInNamespaceExtensions.Parse(name, ignoreCase);
66 | #if READONLYSPAN
67 | protected override EnumWithDisplayNameInNamespace Parse(in ReadOnlySpan name, bool ignoreCase, bool allowMatchingMetadataAttribute)
68 | => EnumWithDisplayNameInNamespaceExtensions.Parse(name, ignoreCase);
69 | #endif
70 | }
--------------------------------------------------------------------------------
/tests/NetEscapades.EnumGenerators.IntegrationTests/EnumWithSameDisplayNameExtensionsTests.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 INTERCEPTOR_TESTS
9 | namespace NetEscapades.EnumGenerators.Interceptors.IntegrationTests;
10 | #elif NUGET_ATTRS_INTEGRATION_TESTS
11 | namespace NetEscapades.EnumGenerators.Nuget.Attributes.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 | #else
17 | #error Unknown integration tests
18 | #endif
19 |
20 | public class EnumWithSameDisplayNameExtensionsTests : ExtensionTests, ITestData
21 | {
22 | public TheoryData ValidEnumValues() => new()
23 | {
24 | EnumWithSameDisplayName.First,
25 | EnumWithSameDisplayName.Second,
26 | (EnumWithSameDisplayName)3,
27 | };
28 |
29 | public TheoryData ValuesToParse() => new()
30 | {
31 | "First",
32 | "Second",
33 | "2nd",
34 | "2ND",
35 | "first",
36 | "SECOND",
37 | "3",
38 | "267",
39 | "-267",
40 | "2147483647",
41 | "3000000000",
42 | "Fourth",
43 | "Fifth",
44 | };
45 |
46 | protected override string[] GetNames() => EnumWithSameDisplayNameExtensions.GetNames();
47 | protected override EnumWithSameDisplayName[] GetValues() => EnumWithSameDisplayNameExtensions.GetValues();
48 | protected override int[] GetValuesAsUnderlyingType() => EnumWithSameDisplayNameExtensions.GetValuesAsUnderlyingType();
49 | protected override int AsUnderlyingValue(EnumWithSameDisplayName value) => value.AsUnderlyingType();
50 |
51 | protected override string ToStringFast(EnumWithSameDisplayName value) => value.ToStringFast();
52 | protected override string ToStringFast(EnumWithSameDisplayName value, bool withMetadata) => value.ToStringFast(withMetadata);
53 | protected override bool IsDefined(EnumWithSameDisplayName value) => EnumWithSameDisplayNameExtensions.IsDefined(value);
54 | protected override bool IsDefined(string name, bool allowMatchingMetadataAttribute) => EnumWithSameDisplayNameExtensions.IsDefined(name, allowMatchingMetadataAttribute);
55 | #if READONLYSPAN
56 | protected override bool IsDefined(in ReadOnlySpan name, bool allowMatchingMetadataAttribute) => EnumWithSameDisplayNameExtensions.IsDefined(name, allowMatchingMetadataAttribute);
57 | #endif
58 | protected override bool TryParse(string name, out EnumWithSameDisplayName parsed, bool ignoreCase, bool allowMatchingMetadataAttribute)
59 | => EnumWithSameDisplayNameExtensions.TryParse(name, out parsed, ignoreCase, allowMatchingMetadataAttribute);
60 | #if READONLYSPAN
61 | protected override bool TryParse(in ReadOnlySpan name, out EnumWithSameDisplayName parsed, bool ignoreCase, bool allowMatchingMetadataAttribute)
62 | => EnumWithSameDisplayNameExtensions.TryParse(name, out parsed, ignoreCase, allowMatchingMetadataAttribute);
63 | #endif
64 | protected override EnumWithSameDisplayName Parse(string name, bool ignoreCase, bool allowMatchingMetadataAttribute)
65 | => EnumWithSameDisplayNameExtensions.Parse(name, ignoreCase);
66 | #if READONLYSPAN
67 | protected override EnumWithSameDisplayName Parse(in ReadOnlySpan name, bool ignoreCase, bool allowMatchingMetadataAttribute)
68 | => EnumWithSameDisplayNameExtensions.Parse(name, ignoreCase);
69 | #endif
70 | }
--------------------------------------------------------------------------------
/tests/NetEscapades.EnumGenerators.IntegrationTests/Enums.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.ComponentModel;
3 | using System.ComponentModel.DataAnnotations;
4 |
5 | [assembly:NetEscapades.EnumGenerators.EnumExtensions()]
6 | [assembly:NetEscapades.EnumGenerators.EnumExtensions()]
7 |
8 | namespace System
9 | {
10 | using NetEscapades.EnumGenerators;
11 |
12 | [EnumExtensions]
13 | public enum EnumInSystem
14 | {
15 | First = 0,
16 | Second = 1,
17 | Third = 2,
18 | }
19 |
20 | [EnumExtensions(IsInterceptable = false)]
21 | public enum NonInterceptableEnum
22 | {
23 | First = 0,
24 | [Display(Name = "2nd")] Second = 1,
25 | Third = 2,
26 | }
27 | }
28 |
29 | namespace Foo
30 | {
31 | using NetEscapades.EnumGenerators;
32 |
33 | // causes Error CS0426 : The type name 'TestEnum' does not exist in the type 'Foo'.
34 | // workaround is to use global prefix
35 |
36 | public class Foo
37 | {
38 | }
39 |
40 | [EnumExtensions]
41 | public enum EnumInFoo
42 | {
43 | First = 0,
44 | [Display(Name = "2nd")]
45 | Second = 1,
46 | Third = 2,
47 | }
48 | }
49 |
50 |
51 | #if INTEGRATION_TESTS
52 | namespace NetEscapades.EnumGenerators.IntegrationTests
53 | #elif NETSTANDARD_INTEGRATION_TESTS
54 | namespace NetEscapades.EnumGenerators.NetStandard.IntegrationTests
55 | #elif INTERCEPTOR_TESTS
56 | namespace NetEscapades.EnumGenerators.Interceptors.IntegrationTests
57 | #elif NUGET_ATTRS_INTEGRATION_TESTS
58 | namespace NetEscapades.EnumGenerators.Nuget.Attributes.IntegrationTests
59 | #elif NUGET_INTEGRATION_TESTS
60 | namespace NetEscapades.EnumGenerators.Nuget.IntegrationTests
61 | #elif NUGET_INTERCEPTOR_TESTS
62 | namespace NetEscapades.EnumGenerators.Nuget.Interceptors.IntegrationTests
63 | #elif NUGET_NETSTANDARD_INTERCEPTOR_TESTS
64 | namespace NetEscapades.EnumGenerators.Nuget.NetStandard.Interceptors.IntegrationTests
65 | #else
66 | #error Unknown integration tests
67 | #endif
68 | {
69 | [EnumExtensions]
70 | public enum EnumInNamespace
71 | {
72 | First = 0,
73 | Second = 1,
74 | Third = 2,
75 | }
76 |
77 | [EnumExtensions]
78 | public enum EnumWithDisplayNameInNamespace
79 | {
80 | First = 0,
81 |
82 | [Display(Name = "2nd")]
83 | Second = 1,
84 |
85 | Third = 2,
86 | }
87 |
88 | [EnumExtensions]
89 | public enum EnumWithDescriptionInNamespace
90 | {
91 | First = 0,
92 |
93 | [Description("2nd")]
94 | Second = 1,
95 |
96 | Third = 2,
97 | }
98 |
99 | [EnumExtensions]
100 | public enum EnumWithSameDisplayName
101 | {
102 | First = 0,
103 |
104 | [Display(Name = "2nd")]
105 | Second = 1,
106 |
107 | [Display(Name = "2nd")]
108 | Third = 2,
109 | }
110 |
111 | [EnumExtensions]
112 | public enum LongEnum: long
113 | {
114 | Second = 1,
115 | First = 0,
116 | Third = 2,
117 | }
118 |
119 | [EnumExtensions]
120 | [Flags]
121 | public enum FlagsEnum
122 | {
123 | None = 0,
124 | Second = 1 << 1,
125 | First = 1 << 0,
126 | Third = 1 << 2,
127 | Fourth = 1 << 3,
128 | ThirdAndFourth = Third | Fourth,
129 | }
130 |
131 | [EnumExtensions]
132 | public enum StringTesting
133 | {
134 | [System.ComponentModel.Description("Quotes \"")] Quotes,
135 | [System.ComponentModel.Description(@"Literal Quotes """)] LiteralQuotes,
136 | [Obsolete]
137 | [System.ComponentModel.Description("Backslash \\")] Backslash,
138 | [System.ComponentModel.Description(@"LiteralBackslash \")] BackslashLiteral,
139 | [System.ComponentModel.Description("Line\nBreak")] LineBreak,
140 | }
141 |
142 | [EnumExtensions(ExtensionClassName="SomeExtension", ExtensionClassNamespace = "SomethingElse")]
143 | public enum EnumWithExtensionInOtherNamespace
144 | {
145 | First = 0,
146 |
147 | [Display(Name = "2nd")] Second = 1,
148 |
149 | Third = 2,
150 | }
151 | }
--------------------------------------------------------------------------------
/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 INTERCEPTOR_TESTS
11 | namespace NetEscapades.EnumGenerators.Interceptors.IntegrationTests;
12 | #elif NUGET_ATTRS_INTEGRATION_TESTS
13 | namespace NetEscapades.EnumGenerators.Nuget.Attributes.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 | #else
19 | #error Unknown integration tests
20 | #endif
21 |
22 | public class ExternalEnumExtensionsTests : ExtensionTests, ITestData
23 | {
24 | public TheoryData ValidEnumValues() => new()
25 | {
26 | DateTimeKind.Unspecified,
27 | DateTimeKind.Utc,
28 | (DateTimeKind)3, // not actually valid
29 | };
30 |
31 | public TheoryData ValuesToParse() => new()
32 | {
33 | "Unspecified",
34 | "Utc",
35 | "Local",
36 | "NotLocal",
37 | "SomethingElse",
38 | "utc",
39 | "UTC",
40 | "3",
41 | "267",
42 | "-267",
43 | "2147483647",
44 | "3000000000",
45 | "Fourth",
46 | "Fifth",
47 | };
48 |
49 | protected override string[] GetNames() => DateTimeKindExtensions.GetNames();
50 | protected override DateTimeKind[] GetValues() => DateTimeKindExtensions.GetValues();
51 | protected override int[] GetValuesAsUnderlyingType() => DateTimeKindExtensions.GetValuesAsUnderlyingType();
52 | protected override int AsUnderlyingValue(DateTimeKind value) => value.AsUnderlyingType();
53 |
54 | protected override string ToStringFast(DateTimeKind value) => value.ToStringFast();
55 | protected override string ToStringFast(DateTimeKind value, bool withMetadata) => value.ToStringFast(withMetadata);
56 | protected override bool IsDefined(DateTimeKind value) => DateTimeKindExtensions.IsDefined(value);
57 | protected override bool IsDefined(string name, bool allowMatchingMetadataAttribute) => DateTimeKindExtensions.IsDefined(name, allowMatchingMetadataAttribute: false);
58 | #if READONLYSPAN
59 | protected override bool IsDefined(in ReadOnlySpan name, bool allowMatchingMetadataAttribute) => DateTimeKindExtensions.IsDefined(name, allowMatchingMetadataAttribute: false);
60 | #endif
61 | protected override bool TryParse(string name, out DateTimeKind parsed, bool ignoreCase, bool allowMatchingMetadataAttribute)
62 | => DateTimeKindExtensions.TryParse(name, out parsed, ignoreCase);
63 | #if READONLYSPAN
64 | protected override bool TryParse(in ReadOnlySpan name, out DateTimeKind parsed, bool ignoreCase, bool allowMatchingMetadataAttribute)
65 | => DateTimeKindExtensions.TryParse(name, out parsed, ignoreCase);
66 | #endif
67 | protected override DateTimeKind Parse(string name, bool ignoreCase, bool allowMatchingMetadataAttribute)
68 | => DateTimeKindExtensions.Parse(name, ignoreCase);
69 | #if READONLYSPAN
70 | protected override DateTimeKind Parse(in ReadOnlySpan name, bool ignoreCase, bool allowMatchingMetadataAttribute)
71 | => DateTimeKindExtensions.Parse(name, ignoreCase);
72 | #endif
73 | }
--------------------------------------------------------------------------------
/tests/NetEscapades.EnumGenerators.IntegrationTests/ExternalFlagsEnumExtensionsTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using FluentAssertions;
6 | using Xunit;
7 |
8 | #if INTEGRATION_TESTS
9 | namespace NetEscapades.EnumGenerators.IntegrationTests;
10 | #elif NETSTANDARD_INTEGRATION_TESTS
11 | namespace NetEscapades.EnumGenerators.NetStandard.IntegrationTests;
12 | #elif INTERCEPTOR_TESTS
13 | namespace NetEscapades.EnumGenerators.Interceptors.IntegrationTests;
14 | #elif NUGET_ATTRS_INTEGRATION_TESTS
15 | namespace NetEscapades.EnumGenerators.Nuget.Attributes.IntegrationTests;
16 | #elif NUGET_INTEGRATION_TESTS
17 | namespace NetEscapades.EnumGenerators.Nuget.IntegrationTests;
18 | #elif NUGET_INTERCEPTOR_TESTS
19 | namespace NetEscapades.EnumGenerators.Nuget.Interceptors.IntegrationTests;
20 | #else
21 | #error Unknown integration tests
22 | #endif
23 |
24 | public class ExternalFileShareExtensionsTests : ExtensionTests, ITestData
25 | {
26 | public TheoryData ValidEnumValues() => new()
27 | {
28 | FileShare.Read,
29 | FileShare.Write,
30 | FileShare.Delete | FileShare.Read,
31 | (FileShare)3,
32 | };
33 |
34 | public TheoryData ValuesToParse() => new()
35 | {
36 | "Read",
37 | "Write",
38 | "BEEP",
39 | "Boop",
40 | "read",
41 | "WRITE",
42 | "3",
43 | "267",
44 | "-267",
45 | "2147483647",
46 | "3000000000",
47 | "Fourth",
48 | "Fifth",
49 | };
50 |
51 | protected override string[] GetNames() => FileShareExtensions.GetNames();
52 | protected override FileShare[] GetValues() => FileShareExtensions.GetValues();
53 | protected override int[] GetValuesAsUnderlyingType() => FileShareExtensions.GetValuesAsUnderlyingType();
54 | protected override int AsUnderlyingValue(FileShare value) => value.AsUnderlyingType();
55 |
56 | protected override string ToStringFast(FileShare value) => value.ToStringFast();
57 | protected override string ToStringFast(FileShare value, bool withMetadata) => value.ToStringFast(withMetadata);
58 | protected override bool IsDefined(FileShare value) => FileShareExtensions.IsDefined(value);
59 | protected override bool IsDefined(string name, bool allowMatchingMetadataAttribute) => FileShareExtensions.IsDefined(name, allowMatchingMetadataAttribute: false);
60 | #if READONLYSPAN
61 | protected override bool IsDefined(in ReadOnlySpan name, bool allowMatchingMetadataAttribute) => FileShareExtensions.IsDefined(name, allowMatchingMetadataAttribute: false);
62 | #endif
63 | protected override bool TryParse(string name, out FileShare parsed, bool ignoreCase, bool allowMatchingMetadataAttribute)
64 | => FileShareExtensions.TryParse(name, out parsed, ignoreCase);
65 | #if READONLYSPAN
66 | protected override bool TryParse(in ReadOnlySpan name, out FileShare parsed, bool ignoreCase, bool allowMatchingMetadataAttribute)
67 | => FileShareExtensions.TryParse(name, out parsed, ignoreCase);
68 | #endif
69 | protected override FileShare Parse(string name, bool ignoreCase, bool allowMatchingMetadataAttribute)
70 | => FileShareExtensions.Parse(name, ignoreCase);
71 | #if READONLYSPAN
72 | protected override FileShare Parse(in ReadOnlySpan name, bool ignoreCase, bool allowMatchingMetadataAttribute)
73 | => FileShareExtensions.Parse(name, ignoreCase);
74 | #endif
75 |
76 | public static IEnumerable