├── .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 | # ![](https://raw.githubusercontent.com/andrewlock/NetEscapades.EnumGenerators/refs/heads/main/icon_32.png) 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 AllFlags() 77 | { 78 | var values = new[] 79 | { 80 | FileShare.Read, 81 | FileShare.Write, 82 | FileShare.ReadWrite, 83 | FileShare.Inheritable, 84 | FileShare.Delete | FileShare.Read, 85 | (FileShare)65, 86 | (FileShare)0, 87 | }; 88 | 89 | return from v1 in values 90 | from v2 in values 91 | select new object[] { v1, v2 }; 92 | } 93 | 94 | [Theory] 95 | [MemberData(nameof(AllFlags))] 96 | public void HasFlags(FileShare value, FileShare flag) 97 | { 98 | var isDefined = value.HasFlagFast(flag); 99 | 100 | isDefined.Should().Be(value.HasFlag(flag)); 101 | } 102 | } -------------------------------------------------------------------------------- /tests/NetEscapades.EnumGenerators.IntegrationTests/FlagsEnumExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using Xunit; 6 | 7 | #if INTEGRATION_TESTS 8 | namespace NetEscapades.EnumGenerators.IntegrationTests; 9 | #elif NETSTANDARD_INTEGRATION_TESTS 10 | namespace NetEscapades.EnumGenerators.NetStandard.IntegrationTests; 11 | #elif INTERCEPTOR_TESTS 12 | namespace NetEscapades.EnumGenerators.Interceptors.IntegrationTests; 13 | #elif NUGET_ATTRS_INTEGRATION_TESTS 14 | namespace NetEscapades.EnumGenerators.Nuget.Attributes.IntegrationTests; 15 | #elif NUGET_INTEGRATION_TESTS 16 | namespace NetEscapades.EnumGenerators.Nuget.IntegrationTests; 17 | #elif NUGET_INTERCEPTOR_TESTS 18 | namespace NetEscapades.EnumGenerators.Nuget.Interceptors.IntegrationTests; 19 | #else 20 | #error Unknown integration tests 21 | #endif 22 | 23 | public class FlagsEnumExtensionsTests : ExtensionTests, ITestData 24 | { 25 | public TheoryData ValidEnumValues() => new() 26 | { 27 | FlagsEnum.First, 28 | FlagsEnum.Second, 29 | FlagsEnum.ThirdAndFourth, 30 | (FlagsEnum)3, 31 | }; 32 | 33 | public TheoryData ValuesToParse() => new() 34 | { 35 | "First", 36 | "Second", 37 | "2nd", 38 | "2ND", 39 | "first", 40 | "SECOND", 41 | "3", 42 | "267", 43 | "-267", 44 | "2147483647", 45 | "3000000000", 46 | "Fourth", 47 | "Fifth", 48 | }; 49 | 50 | protected override string[] GetNames() => FlagsEnumExtensions.GetNames(); 51 | protected override FlagsEnum[] GetValues() => FlagsEnumExtensions.GetValues(); 52 | protected override int[] GetValuesAsUnderlyingType() => FlagsEnumExtensions.GetValuesAsUnderlyingType(); 53 | protected override int AsUnderlyingValue(FlagsEnum value) => value.AsUnderlyingType(); 54 | 55 | protected override string ToStringFast(FlagsEnum value) => value.ToStringFast(); 56 | protected override string ToStringFast(FlagsEnum value, bool withMetadata) => value.ToStringFast(withMetadata); 57 | protected override bool IsDefined(FlagsEnum value) => FlagsEnumExtensions.IsDefined(value); 58 | protected override bool IsDefined(string name, bool allowMatchingMetadataAttribute) => FlagsEnumExtensions.IsDefined(name, allowMatchingMetadataAttribute: false); 59 | #if READONLYSPAN 60 | protected override bool IsDefined(in ReadOnlySpan name, bool allowMatchingMetadataAttribute) => FlagsEnumExtensions.IsDefined(name, allowMatchingMetadataAttribute: false); 61 | #endif 62 | protected override bool TryParse(string name, out FlagsEnum parsed, bool ignoreCase, bool allowMatchingMetadataAttribute) 63 | => FlagsEnumExtensions.TryParse(name, out parsed, ignoreCase); 64 | #if READONLYSPAN 65 | protected override bool TryParse(in ReadOnlySpan name, out FlagsEnum parsed, bool ignoreCase, bool allowMatchingMetadataAttribute) 66 | => FlagsEnumExtensions.TryParse(name, out parsed, ignoreCase); 67 | #endif 68 | protected override FlagsEnum Parse(string name, bool ignoreCase, bool allowMatchingMetadataAttribute) 69 | => FlagsEnumExtensions.Parse(name, ignoreCase); 70 | #if READONLYSPAN 71 | protected override FlagsEnum Parse(in ReadOnlySpan name, bool ignoreCase, bool allowMatchingMetadataAttribute) 72 | => FlagsEnumExtensions.Parse(name, ignoreCase); 73 | #endif 74 | 75 | public static IEnumerable AllFlags() 76 | { 77 | var values = new[] 78 | { 79 | FlagsEnum.First, 80 | FlagsEnum.Second, 81 | FlagsEnum.Third, 82 | FlagsEnum.ThirdAndFourth, 83 | FlagsEnum.First | FlagsEnum.Second, 84 | (FlagsEnum)65, 85 | (FlagsEnum)0, 86 | }; 87 | 88 | return from v1 in values 89 | from v2 in values 90 | select new object[] { v1, v2 }; 91 | } 92 | 93 | [Theory] 94 | [MemberData(nameof(AllFlags))] 95 | public void HasFlags(FlagsEnum value, FlagsEnum flag) 96 | { 97 | var isDefined = value.HasFlagFast(flag); 98 | 99 | isDefined.Should().Be(value.HasFlag(flag)); 100 | } 101 | } -------------------------------------------------------------------------------- /tests/NetEscapades.EnumGenerators.IntegrationTests/LongEnumExtensionsTests.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 LongEnumExtensionsTests : ExtensionTests, ITestData 21 | { 22 | public TheoryData ValidEnumValues() => new() 23 | { 24 | LongEnum.First, 25 | LongEnum.Second, 26 | (LongEnum)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() => LongEnumExtensions.GetNames(); 47 | protected override LongEnum[] GetValues() => LongEnumExtensions.GetValues(); 48 | protected override long[] GetValuesAsUnderlyingType() => LongEnumExtensions.GetValuesAsUnderlyingType(); 49 | protected override long AsUnderlyingValue(LongEnum value) => value.AsUnderlyingType(); 50 | 51 | protected override string ToStringFast(LongEnum value) => value.ToStringFast(); 52 | protected override string ToStringFast(LongEnum value, bool withMetadata) => value.ToStringFast(withMetadata); 53 | protected override bool IsDefined(LongEnum value) => LongEnumExtensions.IsDefined(value); 54 | protected override bool IsDefined(string name, bool allowMatchingMetadataAttribute) => LongEnumExtensions.IsDefined(name, allowMatchingMetadataAttribute: false); 55 | #if READONLYSPAN 56 | protected override bool IsDefined(in ReadOnlySpan name, bool allowMatchingMetadataAttribute) => LongEnumExtensions.IsDefined(name, allowMatchingMetadataAttribute: false); 57 | #endif 58 | protected override bool TryParse(string name, out LongEnum parsed, bool ignoreCase, bool allowMatchingMetadataAttribute) 59 | => LongEnumExtensions.TryParse(name, out parsed, ignoreCase); 60 | #if READONLYSPAN 61 | protected override bool TryParse(in ReadOnlySpan name, out LongEnum parsed, bool ignoreCase, bool allowMatchingMetadataAttribute) 62 | => LongEnumExtensions.TryParse(name, out parsed, ignoreCase); 63 | #endif 64 | protected override LongEnum Parse(string name, bool ignoreCase, bool allowMatchingMetadataAttribute) 65 | => LongEnumExtensions.Parse(name, ignoreCase); 66 | #if READONLYSPAN 67 | protected override LongEnum Parse(in ReadOnlySpan name, bool ignoreCase, bool allowMatchingMetadataAttribute) 68 | => LongEnumExtensions.Parse(name, ignoreCase); 69 | #endif 70 | } -------------------------------------------------------------------------------- /tests/NetEscapades.EnumGenerators.IntegrationTests/NetEscapades.EnumGenerators.IntegrationTests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | false 5 | $(DefineConstants);INTEGRATION_TESTS 6 | $(DefineConstants);READONLYSPAN; 7 | true 8 | 9 | 10 | 11 | 12 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | runtime; build; native; contentfiles; analyzers; buildtransitive 29 | all 30 | 31 | 32 | runtime; build; native; contentfiles; analyzers; buildtransitive 33 | all 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /tests/NetEscapades.EnumGenerators.IntegrationTests/xunit.runner.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://xunit.net/schema/current/xunit.runner.schema.json", 3 | "shadowCopy": false 4 | } -------------------------------------------------------------------------------- /tests/NetEscapades.EnumGenerators.Interceptors.IntegrationTests/Enums.cs: -------------------------------------------------------------------------------- 1 | using Foo; 2 | using System; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.IO; 5 | using NetEscapades.EnumGenerators; 6 | using Xunit; 7 | 8 | [assembly:NetEscapades.EnumGenerators.EnumExtensions()] 9 | [assembly:NetEscapades.EnumGenerators.EnumExtensions()] 10 | 11 | namespace Foo 12 | { 13 | // causes Error CS0426 : The type name 'TestEnum' does not exist in the type 'Foo'. 14 | // workaround is to use global prefix 15 | public class Foo { } 16 | 17 | [EnumExtensions] 18 | public enum EnumInFoo 19 | { 20 | First = 0, 21 | [Display(Name = "2nd")] 22 | Second = 1, 23 | Third = 2, 24 | } 25 | } 26 | 27 | #if NUGET_INTERCEPTOR_TESTS 28 | namespace NetEscapades.EnumGenerators.Nuget.Interceptors.IntegrationTests 29 | #elif INTERCEPTOR_TESTS 30 | namespace NetEscapades.EnumGenerators.Interceptors.IntegrationTests 31 | #else 32 | #error Unknown project combination 33 | #endif 34 | { 35 | [EnumExtensions] 36 | public enum EnumWithDisplayNameInNamespace 37 | { 38 | First = 0, 39 | 40 | [System.ComponentModel.Description("2nd")] Second = 1, 41 | 42 | Third = 2, 43 | } 44 | 45 | [EnumExtensions(ExtensionClassName="SomeExtension", ExtensionClassNamespace = "SomethingElse")] 46 | public enum EnumWithExtensionInOtherNamespace 47 | { 48 | First = 0, 49 | 50 | [Display(Name = "2nd")] Second = 1, 51 | 52 | Third = 2, 53 | } 54 | 55 | [EnumExtensions] 56 | [Flags] 57 | public enum FlagsEnum 58 | { 59 | None = 0, 60 | First = 1 << 0, 61 | Second = 1 << 1, 62 | Third = 1 << 2, 63 | Fourth = 1 << 3, 64 | ThirdAndFourth = Third | Fourth, 65 | } 66 | 67 | [EnumExtensions] 68 | public enum StringTesting 69 | { 70 | [System.ComponentModel.Description("Quotes \"")] 71 | Quotes, 72 | 73 | [System.ComponentModel.Description(@"Literal Quotes """)] 74 | LiteralQuotes, 75 | 76 | [System.ComponentModel.Description("Backslash \\")] 77 | Backslash, 78 | 79 | [System.ComponentModel.Description(@"LiteralBackslash \")] 80 | BackslashLiteral, 81 | 82 | [System.ComponentModel.Description("Line\nBreak")] 83 | LineBreak, 84 | } 85 | 86 | [EnumExtensions(IsInterceptable = false)] 87 | public enum NonInterceptableEnum 88 | { 89 | First = 0, 90 | [Display(Name = "2nd")] Second = 1, 91 | Third = 2, 92 | } 93 | } -------------------------------------------------------------------------------- /tests/NetEscapades.EnumGenerators.Interceptors.IntegrationTests/InterceptorTests.cs: -------------------------------------------------------------------------------- 1 | using Foo; 2 | using System; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.IO; 5 | using NetEscapades.EnumGenerators; 6 | using Xunit; 7 | 8 | #if NUGET_INTERCEPTOR_TESTS 9 | namespace NetEscapades.EnumGenerators.Nuget.Interceptors.IntegrationTests; 10 | #elif INTERCEPTOR_TESTS 11 | namespace NetEscapades.EnumGenerators.Interceptors.IntegrationTests; 12 | #elif NETSTANDARD_INTERCEPTOR_TESTS 13 | using NetEscapades.EnumGenerators.NetStandard.IntegrationTests; 14 | 15 | namespace NetEscapades.EnumGenerators.NetStandard.Interceptors.IntegrationTests; 16 | #elif NUGET_NETSTANDARD_INTERCEPTOR_TESTS 17 | namespace NetEscapades.EnumGenerators.Nuget.NetStandard.Interceptors.IntegrationTests; 18 | #else 19 | #error Unknown project combination 20 | #endif 21 | 22 | public class InterceptorTests 23 | { 24 | [Fact] 25 | public void CallingToStringIsIntercepted() 26 | { 27 | AssertValue(EnumWithDisplayNameInNamespace.First); 28 | AssertValue(EnumWithDisplayNameInNamespace.Second); 29 | AssertValue(EnumWithDisplayNameInNamespace.Third); 30 | 31 | void AssertValue(EnumWithDisplayNameInNamespace value) 32 | { 33 | // This doesn't _actually_ test interception, because can't 34 | // differentiate with built-in version, it's only really verifying the generated code compiles 35 | var toString = value.ToString(); 36 | var fast = value.ToStringFast(); 37 | Assert.Equal(fast, toString); 38 | } 39 | } 40 | 41 | [Fact] 42 | public void CallingToStringIsIntercepted_StringTesting() 43 | { 44 | #pragma warning disable CS0612 45 | var result1 = StringTesting.Backslash.ToString(); 46 | var result2 = StringTesting.Backslash.ToStringFast(); 47 | #pragma warning restore CS0612 48 | Assert.Equal(result1, result2); 49 | } 50 | 51 | [Fact] 52 | public void CallingToStringIsIntercepted_EnumInFoo() 53 | { 54 | // This doesn't _actually_ test interception, because can't 55 | // differentiate with built-in version, it's only really verifying the generated code compiles 56 | var result1 = EnumInFoo.Second.ToString(); 57 | var result2 = EnumInFoo.Second.ToStringFast(); 58 | Assert.Equal(result1, result2); 59 | } 60 | 61 | [Fact] 62 | public void CallingToStringIsIntercepted_EnumWithExtensionInOtherNamespace() 63 | { 64 | // This doesn't _actually_ test interception, because can't 65 | // differentiate with built-in version, it's only really verifying the generated code compiles 66 | var result1 = EnumWithExtensionInOtherNamespace.Second.ToString(); 67 | var result2 = SomethingElse.SomeExtension.ToStringFast(EnumWithExtensionInOtherNamespace.Second); 68 | Assert.Equal(result1, result2); 69 | } 70 | 71 | [Fact] 72 | public void CallingToStringIsIntercepted_ExternalEnum() 73 | { 74 | // This doesn't _actually_ test interception, because can't 75 | // differentiate with built-in version, it's only really verifying the generated code compiles 76 | var result1 = DateTimeKind.Local.ToString(); 77 | var result2 = DateTimeKind.Local.ToStringFast(); 78 | Assert.Equal(result1, result2); 79 | } 80 | 81 | [Fact] 82 | public void CallingHasFlagIsIntercepted() 83 | { 84 | // This doesn't _actually_ test interception, because can't 85 | // differentiate with built-in version, it's only really verifying the generated code compiles 86 | var value1 = FlagsEnum.First; 87 | var result2 = FlagsEnum.Second.HasFlag(value1); 88 | Assert.False(result2); 89 | Assert.True(value1.HasFlag(FlagsEnum.None)); 90 | 91 | var combined = FlagsEnum.First | FlagsEnum.Second; 92 | Assert.True(combined.HasFlag(FlagsEnum.First)); 93 | Assert.False(FlagsEnum.First.HasFlag(combined)); 94 | } 95 | 96 | [Fact] 97 | public void CallingHasFlagIsIntercepted_ExternalEnumFlags() 98 | { 99 | // This doesn't _actually_ test interception, because can't 100 | // differentiate with built-in version, it's only really verifying the generated code compiles 101 | var value1 = FileShare.Read; 102 | var result2 = FileShare.Write.HasFlag(value1); 103 | Assert.False(result2); 104 | Assert.True(value1.HasFlag(FileShare.None)); 105 | 106 | var combined = FileShare.Read | FileShare.Write; 107 | Assert.True(combined.HasFlag(FileShare.Read)); 108 | Assert.False(FileShare.Read.HasFlag(combined)); 109 | } 110 | 111 | [Fact(Skip = "Can't actually verify it's not interceptable")] 112 | public void CallingNonInterceptableEnumIsNotIntercepted() 113 | { 114 | var result1 = NonInterceptableEnum.Second.ToString(); 115 | var result2 = NonInterceptableEnum.Second.ToStringFast(); 116 | Assert.NotEqual(result1, result2); 117 | } 118 | } -------------------------------------------------------------------------------- /tests/NetEscapades.EnumGenerators.Interceptors.IntegrationTests/NetEscapades.EnumGenerators.Interceptors.IntegrationTests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | false 5 | $(DefineConstants);INTERCEPTOR_TESTS 6 | $(DefineConstants);READONLYSPAN 7 | true 8 | true 9 | $(InterceptorsPreviewNamespaces);NetEscapades.EnumGenerators 10 | $(InterceptorsNamespaces);NetEscapades.EnumGenerators 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 26 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | runtime; build; native; contentfiles; analyzers; buildtransitive 40 | all 41 | 42 | 43 | runtime; build; native; contentfiles; analyzers; buildtransitive 44 | all 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /tests/NetEscapades.EnumGenerators.Interceptors.IntegrationTests/xunit.runner.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://xunit.net/schema/current/xunit.runner.schema.json", 3 | "shadowCopy": false 4 | } -------------------------------------------------------------------------------- /tests/NetEscapades.EnumGenerators.NetStandard.IntegrationTests/NetEscapades.EnumGenerators.NetStandard.IntegrationTests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | false 5 | enable 6 | $(DefineConstants);NETSTANDARD_INTEGRATION_TESTS 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | runtime; build; native; contentfiles; analyzers; buildtransitive 29 | all 30 | 31 | 32 | runtime; build; native; contentfiles; analyzers; buildtransitive 33 | all 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /tests/NetEscapades.EnumGenerators.NetStandard.Interceptors.IntegrationTests/InterceptionAttributes.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using Foo; 4 | using NetEscapades.EnumGenerators; 5 | using NetEscapades.EnumGenerators.NetStandard.IntegrationTests; 6 | 7 | [assembly:Interceptable] 8 | [assembly:Interceptable] 9 | [assembly:Interceptable] 10 | [assembly:Interceptable] 11 | [assembly:Interceptable] 12 | [assembly:Interceptable(ExtensionClassName="SomeExtension", ExtensionClassNamespace = "SomethingElse")] 13 | [assembly:Interceptable] 14 | [assembly:Interceptable] 15 | [assembly:Interceptable] 16 | -------------------------------------------------------------------------------- /tests/NetEscapades.EnumGenerators.NetStandard.Interceptors.IntegrationTests/NetEscapades.EnumGenerators.NetStandard.Interceptors.IntegrationTests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | false 5 | enable 6 | $(DefineConstants);NETSTANDARD_INTERCEPTOR_TESTS 7 | true 8 | $(InterceptorsPreviewNamespaces);NetEscapades.EnumGenerators 9 | $(InterceptorsNamespaces);NetEscapades.EnumGenerators 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 27 | 29 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | runtime; build; native; contentfiles; analyzers; buildtransitive 45 | all 46 | 47 | 48 | runtime; build; native; contentfiles; analyzers; buildtransitive 49 | all 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /tests/NetEscapades.EnumGenerators.NetStandard/NetEscapades.EnumGenerators.NetStandard.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | false 6 | enable 7 | $(DefineConstants);NETSTANDARD_INTEGRATION_TESTS 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /tests/NetEscapades.EnumGenerators.Nuget.Attributes.IntegrationTests/NetEscapades.EnumGenerators.Nuget.Attributes.IntegrationTests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | false 5 | enable 6 | $(DefineConstants);NETESCAPADES_ENUMGENERATORS_EMBED_ATTRIBUTES;NUGET_ATTRS_INTEGRATION_TESTS 7 | $(DefineConstants);READONLYSPAN 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | runtime; build; native; contentfiles; analyzers; buildtransitive 29 | all 30 | 31 | 32 | runtime; build; native; contentfiles; analyzers; buildtransitive 33 | all 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /tests/NetEscapades.EnumGenerators.Nuget.IntegrationTests/NetEscapades.EnumGenerators.Nuget.IntegrationTests.Project.targets: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp2.1;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0 5 | net48;$(TargetFrameworks) 6 | false 7 | enable 8 | $(DefineConstants);READONLYSPAN 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | runtime; build; native; contentfiles; analyzers; buildtransitive 30 | all 31 | 32 | 33 | runtime; build; native; contentfiles; analyzers; buildtransitive 34 | all 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /tests/NetEscapades.EnumGenerators.Nuget.IntegrationTests/NetEscapades.EnumGenerators.Nuget.IntegrationTests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | false 5 | enable 6 | $(DefineConstants);NUGET_INTEGRATION_TESTS 7 | $(DefineConstants);READONLYSPAN 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | runtime; build; native; contentfiles; analyzers; buildtransitive 29 | all 30 | 31 | 32 | runtime; build; native; contentfiles; analyzers; buildtransitive 33 | all 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /tests/NetEscapades.EnumGenerators.Nuget.Interceptors.IntegrationTests/NetEscapades.EnumGenerators.Nuget.Interceptors.IntegrationTests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | false 5 | enable 6 | $(DefineConstants);NUGET_INTERCEPTOR_TESTS 7 | $(DefineConstants);READONLYSPAN 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | runtime; build; native; contentfiles; analyzers; buildtransitive 28 | all 29 | 30 | 31 | runtime; build; native; contentfiles; analyzers; buildtransitive 32 | all 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /tests/NetEscapades.EnumGenerators.Nuget.NetStandard.Interceptors.IntegrationTests/InterceptionAttributes.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using Foo; 4 | using NetEscapades.EnumGenerators; 5 | #if NETSTANDARD_INTERCEPTOR_TESTS 6 | using NetEscapades.EnumGenerators.NetStandard.IntegrationTests; 7 | #elif NUGET_NETSTANDARD_INTERCEPTOR_TESTS 8 | using NetEscapades.EnumGenerators.Nuget.NetStandard.Interceptors.IntegrationTests; 9 | #else 10 | #error Unknown integration tests 11 | #endif 12 | 13 | [assembly:Interceptable] 14 | [assembly:Interceptable] 15 | [assembly:Interceptable] 16 | [assembly:Interceptable] 17 | [assembly:Interceptable] 18 | [assembly:Interceptable(ExtensionClassName="SomeExtension", ExtensionClassNamespace = "SomethingElse")] 19 | [assembly:Interceptable] 20 | [assembly:Interceptable] 21 | [assembly:Interceptable] 22 | -------------------------------------------------------------------------------- /tests/NetEscapades.EnumGenerators.Nuget.NetStandard.Interceptors.IntegrationTests/NetEscapades.EnumGenerators.Nuget.NetStandard.Interceptors.IntegrationTests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | false 5 | enable 6 | $(DefineConstants);NUGET_NETSTANDARD_INTERCEPTOR_TESTS 7 | $(DefineConstants);READONLYSPAN 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | runtime; build; native; contentfiles; analyzers; buildtransitive 32 | all 33 | 34 | 35 | runtime; build; native; contentfiles; analyzers; buildtransitive 36 | all 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /tests/NetEscapades.EnumGenerators.Nuget.NetStandard/NetEscapades.EnumGenerators.Nuget.NetStandard.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | false 6 | enable 7 | $(DefineConstants);NUGET_NETSTANDARD_INTERCEPTOR_TESTS 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /tests/NetEscapades.EnumGenerators.Tests/EquatableArrayTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using Xunit; 3 | 4 | namespace NetEscapades.EnumGenerators.Tests; 5 | 6 | public class EquatableArrayTests 7 | { 8 | [Fact] 9 | public void PrimitiveComparison() 10 | { 11 | int[] val1 = [1, 2, 3, 4, 5]; 12 | int[] val2 = [1, 2, 3, 4, 5]; 13 | 14 | var arr1 = new EquatableArray(val1); 15 | var arr2 = new EquatableArray(val2); 16 | 17 | arr1.Equals(arr2).Should().BeTrue(); 18 | } 19 | 20 | [Fact] 21 | public void RecordComparison() 22 | { 23 | Record[] val1 = [new(1), new(2), new(3), new(4), new(5)]; 24 | Record[] val2 = [new(1), new(2), new(3), new(4), new(5)]; 25 | 26 | var arr1 = new EquatableArray(val1); 27 | var arr2 = new EquatableArray(val2); 28 | 29 | arr1.Equals(arr2).Should().BeTrue(); 30 | } 31 | 32 | [Fact] 33 | public void NestedEquatableArrayComparison() 34 | { 35 | EquatableArray[] val1 = [new([1]), new([2]), new([3]), new([4]), new([5])]; 36 | EquatableArray[] val2 = [new([1]), new([2]), new([3]), new([4]), new([5])]; 37 | 38 | var arr1 = new EquatableArray>(val1); 39 | var arr2 = new EquatableArray>(val2); 40 | 41 | arr1.Equals(arr2).Should().BeTrue(); 42 | } 43 | 44 | [Fact] 45 | public void BoxedNestedEquatableArrayComparison() 46 | { 47 | EquatableArray[] val1 = [new([1]), new([2]), new([3]), new([4]), new([5])]; 48 | EquatableArray[] val2 = [new([1]), new([2]), new([3]), new([4]), new([5])]; 49 | 50 | object arr1 = new EquatableArray>(val1); 51 | var arr2 = new EquatableArray>(val2); 52 | 53 | arr1.Equals(arr2).Should().BeTrue(); 54 | arr2.Equals(arr1).Should().BeTrue(); 55 | } 56 | 57 | public record Record 58 | { 59 | public Record(int value) 60 | { 61 | Value = value; 62 | } 63 | 64 | public int Value { get; } 65 | } 66 | } -------------------------------------------------------------------------------- /tests/NetEscapades.EnumGenerators.Tests/NetEscapades.EnumGenerators.Tests.Project.targets: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | NetEscapades.EnumGenerators.Tests 5 | $(AssemblyName) 6 | enable 7 | false 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | runtime; build; native; contentfiles; analyzers; buildtransitive 18 | all 19 | 20 | 21 | runtime; build; native; contentfiles; analyzers; buildtransitive 22 | all 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /tests/NetEscapades.EnumGenerators.Tests/NetEscapades.EnumGenerators.Tests.Roslyn4_04.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 4.04.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /tests/NetEscapades.EnumGenerators.Tests/NetEscapades.EnumGenerators.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | NetEscapades.EnumGenerators.Tests 5 | $(AssemblyName) 6 | enable 7 | false 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | runtime; build; native; contentfiles; analyzers; buildtransitive 18 | all 19 | 20 | 21 | runtime; build; native; contentfiles; analyzers; buildtransitive 22 | all 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /tests/NetEscapades.EnumGenerators.Tests/SourceGenerationHelperSnapshotTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Text; 3 | using System.Threading.Tasks; 4 | using VerifyXunit; 5 | using Xunit; 6 | 7 | namespace NetEscapades.EnumGenerators.Tests; 8 | 9 | [UsesVerify] 10 | public class SourceGenerationHelperSnapshotTests 11 | { 12 | [Fact] 13 | public Task GeneratesEnumCorrectly() 14 | { 15 | var value = new EnumToGenerate( 16 | "ShortName", 17 | "Something.Blah", 18 | "Something.Blah.ShortName", 19 | "int", 20 | isPublic: true, 21 | new List<(string Key, EnumValueOption Value)> 22 | { 23 | ("First", new EnumValueOption(null, false, 0)), 24 | ("Second", new EnumValueOption(null, false, 1)), 25 | }, 26 | hasFlags: false, 27 | isDisplayAttributeUsed: false); 28 | 29 | var result = SourceGenerationHelper.GenerateExtensionClass(value).Content; 30 | 31 | return Verifier.Verify(result) 32 | .ScrubExpectedChanges() 33 | .UseDirectory("Snapshots"); 34 | } 35 | 36 | [Fact] 37 | public Task GeneratesFlagsEnumCorrectly() 38 | { 39 | var value = new EnumToGenerate( 40 | "ShortName", 41 | "Something.Blah", 42 | "Something.Blah.ShortName", 43 | "int", 44 | isPublic: true, 45 | new List<(string, EnumValueOption)> 46 | { 47 | ("First", new EnumValueOption(null, false, 0)), 48 | ("Second", new EnumValueOption(null, false, 1)), 49 | }, 50 | hasFlags: true, 51 | isDisplayAttributeUsed: false); 52 | 53 | var result = SourceGenerationHelper.GenerateExtensionClass(value).Content; 54 | 55 | return Verifier.Verify(result) 56 | .ScrubExpectedChanges() 57 | .UseDirectory("Snapshots"); 58 | } 59 | } -------------------------------------------------------------------------------- /version.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 1.0.0 4 | beta13 5 | $(VersionPrefix) 6 | $(VersionPrefix)-$(VersionSuffix) 7 | 8 | 9 | --------------------------------------------------------------------------------