├── .gitattributes ├── .github └── workflows │ └── BuildAndPack.yml ├── .gitignore ├── .nuke ├── build.schema.json └── parameters.json ├── CHANGELOG.md ├── Directory.Build.props ├── LICENSE ├── NuGet.integration-tests.config ├── README.md ├── StronglyTypedId.sln ├── build.cmd ├── build.ps1 ├── build.sh ├── build ├── .editorconfig ├── Build.cs ├── Configuration.cs ├── Directory.Build.props ├── Directory.Build.targets ├── _build.csproj └── _build.csproj.DotSettings ├── docs ├── migration.md ├── strongly_typed_id.gif └── strongly_typed_id.mp4 ├── global.json ├── logo.png ├── logo.xcf ├── releasenotes.props ├── src ├── StronglyTypedIds.Attributes │ ├── StronglyTypedIdAttribute.cs │ ├── StronglyTypedIdDefaultsAttribute.cs │ ├── StronglyTypedIds.Attributes.csproj │ └── Template.cs ├── StronglyTypedIds.Templates │ ├── StronglyTypedId.Templates.props │ ├── StronglyTypedId.Templates.targets │ ├── StronglyTypedIds.Templates.csproj │ ├── guid-dapper.typedid │ ├── guid-efcore.typedid │ ├── guid-full.typedid │ ├── guid-newtonsoftjson.typedid │ ├── int-dapper.typedid │ ├── int-efcore.typedid │ ├── int-full.typedid │ ├── int-newtonsoftjson.typedid │ ├── long-dapper.typedid │ ├── long-efcore.typedid │ ├── long-full.typedid │ ├── long-newtonsoftjson.typedid │ ├── newid-full.typedid │ ├── nullablestring-full.typedid │ ├── string-dapper.typedid │ ├── string-efcore.typedid │ ├── string-full.typedid │ └── string-newtonsoftjson.typedid └── StronglyTypedIds │ ├── Constants.cs │ ├── DiagnosticInfo.cs │ ├── Diagnostics │ ├── DiagnosticHelper.cs │ ├── InvalidTemplateNameDiagnostic.cs │ ├── MultipleAssemblyAttributeDiagnostic.cs │ ├── NotPartialDiagnostic.cs │ ├── UnknownTemplateCodeFixProvider.cs │ └── UnknownTemplateDiagnostic.cs │ ├── EmbeddedSources.Guid.cs │ ├── EmbeddedSources.Int.cs │ ├── EmbeddedSources.Long.cs │ ├── EmbeddedSources.String.cs │ ├── EmbeddedSources.cs │ ├── EquatableArray.cs │ ├── HashCode.cs │ ├── Parser.cs │ ├── Properties │ └── AssemblyInfo.cs │ ├── SourceGenerationHelper.cs │ ├── StronglyTypedId.props │ ├── StronglyTypedIdGenerator.cs │ ├── StronglyTypedIds.csproj │ ├── StronglyTypedIds.csproj.DotSettings │ ├── StructToGenerate.cs │ └── Templates │ └── AutoGeneratedHeader.cs ├── test ├── Directory.Build.props ├── IntegrationLibraries.props ├── StronglyTypedIds.IntegrationTests.ExternalIds │ ├── StronglyTypedIds.IntegrationTests.ExternalIds.csproj │ └── xunit.runner.json ├── StronglyTypedIds.IntegrationTests.Types │ ├── Enums.cs │ ├── StronglyTypedIds.IntegrationTests.Types.csproj │ └── simple.typedid ├── StronglyTypedIds.IntegrationTests │ ├── DapperTypeHandlers.cs │ ├── DefaultIdTests.cs │ ├── Enums.cs │ ├── GuidIdTests.cs │ ├── IntIdTests.cs │ ├── LongIdTests.cs │ ├── MassTransitNewIdTests.cs │ ├── ModuleInitializerAttribute.cs │ ├── NestedIdTests.cs │ ├── NullableStringIdTests.cs │ ├── SimpleCustomIdTests.cs │ ├── StringIdTests.cs │ ├── StronglyTypedIds.IntegrationTests.csproj │ ├── SystemTextJsonSerializerContext.cs │ ├── simple.typedid │ └── xunit.runner.json ├── StronglyTypedIds.Nuget.Attributes.IntegrationTests │ ├── StronglyTypedIds.Nuget.Attributes.IntegrationTests.csproj │ └── simple.typedid ├── StronglyTypedIds.Nuget.IntegrationTests │ ├── StronglyTypedIds.Nuget.IntegrationTests.csproj │ └── simple.typedid └── StronglyTypedIds.Tests │ ├── DiagnosticsTests.cs │ ├── EmbeddedResourceTests.cs │ ├── EqualityTests.cs │ ├── Snapshots │ ├── EmbeddedResourceTests.EmittedResourceIsSameAsCompiledResource_resource=StronglyTypedIdAttribute.verified.txt │ ├── EmbeddedResourceTests.EmittedResourceIsSameAsCompiledResource_resource=StronglyTypedIdDefaultsAttribute.verified.txt │ ├── EmbeddedResourceTests.EmittedResourceIsSameAsCompiledResource_resource=Template.verified.txt │ ├── StronglyTypedIdGeneratorTests.CanGenerateDefaultIdInGlobalNamespace.verified.txt │ ├── StronglyTypedIdGeneratorTests.CanGenerateForCustomTemplate.verified.txt │ ├── StronglyTypedIdGeneratorTests.CanGenerateGenericVeryNestedIdInFileScopeNamespace.verified.txt │ ├── StronglyTypedIdGeneratorTests.CanGenerateIdInFileScopedNamespace.verified.txt │ ├── StronglyTypedIdGeneratorTests.CanGenerateIdInNamespace.verified.txt │ ├── StronglyTypedIdGeneratorTests.CanGenerateMultipleIdsWithSameName.verified.txt │ ├── StronglyTypedIdGeneratorTests.CanGenerateMultipleTemplatesWithBuiltIn.verified.txt │ ├── StronglyTypedIdGeneratorTests.CanGenerateMultipleTemplatesWithoutBuiltIn.verified.txt │ ├── StronglyTypedIdGeneratorTests.CanGenerateNestedIdInFileScopeNamespace.verified.txt │ ├── StronglyTypedIdGeneratorTests.CanGenerateNonDefaultIdInNamespace.verified.txt │ ├── StronglyTypedIdGeneratorTests.CanGenerateVeryNestedIdInFileScopeNamespace.verified.txt │ ├── StronglyTypedIdGeneratorTests.CanOverrideDefaultsWithCustomTemplateUsingGlobalAttribute.verified.txt │ ├── StronglyTypedIdGeneratorTests.CanOverrideDefaultsWithTemplateUsingGlobalAttribute.verified.txt │ ├── UnknownTemplateCodeFixProviderUnitTests.UsesBuiltInTemplates_Guid.verified.txt │ ├── UnknownTemplateCodeFixProviderUnitTests.UsesBuiltInTemplates_Int.verified.txt │ ├── UnknownTemplateCodeFixProviderUnitTests.UsesBuiltInTemplates_Long.verified.txt │ └── UnknownTemplateCodeFixProviderUnitTests.UsesBuiltInTemplates_String.verified.txt │ ├── SourceGenerationHelperSnapshotTests.cs │ ├── StronglyTypedIdGeneratorTests.cs │ ├── StronglyTypedIds.Tests.csproj │ └── 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-13 # latest is arm64, and it breaks a bunch of stuff 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 | 9.0.x 52 | 8.0.x 53 | 7.0.x 54 | 6.0.x 55 | 3.1.x 56 | 57 | - name: Cache .nuke/temp, ~/.nuget/packages 58 | uses: actions/cache@v3 59 | with: 60 | path: | 61 | .nuke/temp 62 | ~/.nuget/packages 63 | !~/.nuget/packages/stronglytypeid 64 | key: ${{ runner.os }}-${{ hashFiles('**/global.json', '**/*.csproj') }} 65 | 66 | - name: Run './build.cmd Clean Test TestPackages PushToNuGet' 67 | run: ./build.cmd Clean Test TestPackages PushToNuGet 68 | env: 69 | NuGetToken: ${{ secrets.NUGET_TOKEN || 'NOT_SET'}} 70 | 71 | - uses: actions/upload-artifact@v4 72 | with: 73 | name: packages-${{ matrix.os}} 74 | path: artifacts/packages 75 | - uses: actions/upload-artifact@v4 76 | with: 77 | name: results-${{ matrix.os}} 78 | path: artifacts/results 79 | 80 | publish-test-results: 81 | name: "Publish Tests Results" 82 | needs: build-and-test 83 | runs-on: ubuntu-latest 84 | permissions: 85 | checks: write 86 | pull-requests: write # needed unless run with comment_mode: off 87 | # contents: read # only needed for private repository 88 | # issues: read # only needed for private repository 89 | if: always() 90 | 91 | steps: 92 | - name: Download Artifacts 93 | uses: actions/download-artifact@v4 94 | with: 95 | path: artifacts/results 96 | 97 | - name: Publish Test Results 98 | uses: EnricoMi/publish-unit-test-result-action@v2 99 | with: 100 | files: "artifacts/**/*.trx" 101 | json_thousands_separator: "," 102 | check_run_annotations_branch: "*" -------------------------------------------------------------------------------- /.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 | # NuGet testing 250 | globalPackagesFolder -------------------------------------------------------------------------------- /.nuke/build.schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "definitions": { 4 | "Host": { 5 | "type": "string", 6 | "enum": [ 7 | "AppVeyor", 8 | "AzurePipelines", 9 | "Bamboo", 10 | "Bitbucket", 11 | "Bitrise", 12 | "GitHubActions", 13 | "GitLab", 14 | "Jenkins", 15 | "Rider", 16 | "SpaceAutomation", 17 | "TeamCity", 18 | "Terminal", 19 | "TravisCI", 20 | "VisualStudio", 21 | "VSCode" 22 | ] 23 | }, 24 | "ExecutableTarget": { 25 | "type": "string", 26 | "enum": [ 27 | "Clean", 28 | "Compile", 29 | "Pack", 30 | "PushToNuGet", 31 | "Restore", 32 | "Test", 33 | "TestPackages" 34 | ] 35 | }, 36 | "Verbosity": { 37 | "type": "string", 38 | "description": "", 39 | "enum": [ 40 | "Verbose", 41 | "Normal", 42 | "Minimal", 43 | "Quiet" 44 | ] 45 | }, 46 | "NukeBuild": { 47 | "properties": { 48 | "Continue": { 49 | "type": "boolean", 50 | "description": "Indicates to continue a previously failed build attempt" 51 | }, 52 | "Help": { 53 | "type": "boolean", 54 | "description": "Shows the help text for this build assembly" 55 | }, 56 | "Host": { 57 | "description": "Host for execution. Default is 'automatic'", 58 | "$ref": "#/definitions/Host" 59 | }, 60 | "NoLogo": { 61 | "type": "boolean", 62 | "description": "Disables displaying the NUKE logo" 63 | }, 64 | "Partition": { 65 | "type": "string", 66 | "description": "Partition to use on CI" 67 | }, 68 | "Plan": { 69 | "type": "boolean", 70 | "description": "Shows the execution plan (HTML)" 71 | }, 72 | "Profile": { 73 | "type": "array", 74 | "description": "Defines the profiles to load", 75 | "items": { 76 | "type": "string" 77 | } 78 | }, 79 | "Root": { 80 | "type": "string", 81 | "description": "Root directory during build execution" 82 | }, 83 | "Skip": { 84 | "type": "array", 85 | "description": "List of targets to be skipped. Empty list skips all dependencies", 86 | "items": { 87 | "$ref": "#/definitions/ExecutableTarget" 88 | } 89 | }, 90 | "Target": { 91 | "type": "array", 92 | "description": "List of targets to be invoked. Default is '{default_target}'", 93 | "items": { 94 | "$ref": "#/definitions/ExecutableTarget" 95 | } 96 | }, 97 | "Verbosity": { 98 | "description": "Logging verbosity during build execution. Default is 'Normal'", 99 | "$ref": "#/definitions/Verbosity" 100 | } 101 | } 102 | } 103 | }, 104 | "allOf": [ 105 | { 106 | "properties": { 107 | "Configuration": { 108 | "type": "string", 109 | "description": "Configuration to build - Default is 'Debug' (local) or 'Release' (server)", 110 | "enum": [ 111 | "Debug", 112 | "Release" 113 | ] 114 | }, 115 | "GithubToken": { 116 | "type": "string" 117 | }, 118 | "NuGetToken": { 119 | "type": "string" 120 | }, 121 | "PackagesDirectory": { 122 | "type": "string" 123 | }, 124 | "Solution": { 125 | "type": "string", 126 | "description": "Path to a solution file that is automatically loaded" 127 | } 128 | } 129 | }, 130 | { 131 | "$ref": "#/definitions/NukeBuild" 132 | } 133 | ] 134 | } 135 | -------------------------------------------------------------------------------- /.nuke/parameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./build.schema.json", 3 | "Solution": "StronglyTypedId.sln" 4 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [v1.0.0-beta08] 4 | 5 | Fixes 6 | * Fix bug in System.Text.Json converters that could cause errors when used with source generators 7 | 8 | ## [v1.0.0-beta07] 9 | 10 | Breaking Changes: 11 | * This release contains a fundamental change in how you define your IDs 12 | * For details see [the related issue](https://github.com/andrewlock/StronglyTypedId/issues/102), the [PR](https://github.com/andrewlock/StronglyTypedId/pull/117), or the [README](./README.md) 13 | * The "options" enums `StronglyTypedIdBackingType`, `StronglyTypedIdConverter`, and `StronglyTypedIdImplementations` have been removed. 14 | * Instead, you simply choose one of 4 different built-in templates, or use a custom template. 15 | 16 | Features 17 | * Adds `[GeneratedCode]` attribute to generated IDs (Fixes https://github.com/andrewlock/StronglyTypedId/issues/57) 18 | * Add support for parsing `SCOPE_IDENTITY()` and `@@IDENTITY` in MSSQL with `DapperTypeHandler` (Fixes https://github.com/andrewlock/StronglyTypedId/issues/118) 19 | * Fix exception being thrown when deserializing nullable strongly-typed id backed by string (Fixes https://github.com/andrewlock/StronglyTypedId/issues/83) 20 | * Allow creating multiple IDs with the same name in a project (Fixes https://github.com/andrewlock/StronglyTypedId/issues/74, [thanks @jo-goro](https://github.com/andrewlock/StronglyTypedId/pull/77)!) 21 | 22 | ## [v1.0.0-beta06] 23 | 24 | Features 25 | * Added support for Masstransit.NewId ([thanks @Khitiara](https://github.com/andrewlock/StronglyTypedId/pull/52)!) Fixes https://github.com/andrewlock/StronglyTypedId/issues/51 26 | * Added parameterless constructor to EF Core ValueConverts for compatibility with global conventions. Fixes https://github.com/andrewlock/StronglyTypedId/issues/50 27 | * Added `#pragma warning disable 1591` to generated code to avoid warning CS1591. Fixes https://github.com/andrewlock/StronglyTypedId/issues/47 28 | 29 | 30 | ## [v1.0.0-beta05] 31 | 32 | Breaking Changes: 33 | * Removed StronglyTypedId.Attributes NuGet package. 34 | * The attributes are no longer embed in your project by default, instead it will use the external dll. You can re-enable the embedding by setting `STRONGLY_TYPED_ID_EMBED_ATTRIBUTES`. 35 | 36 | New Features: 37 | 38 | * Improved approach to handling [InternalsVisibleTo] issues, by embedding the StronglyTypedId.Attributes.dll in the NuGet package directly. 39 | 40 | ## [v1.0.0-beta04] 41 | 42 | New Features: 43 | 44 | * Added support for IDs inside nested classes/records/structs (Fixes https://github.com/andrewlock/StronglyTypedId/issues/40) 45 | 46 | ## [v1.0.0-beta03] 47 | 48 | Breaking Changes: 49 | 50 | * Converted to use .NET 6's incremental source generators. This should provide performance improvements, but it requires using the .NET 6 SDK. 51 | 52 | Bug fixes: 53 | 54 | * Fixed problem deserializing nullable strongly-typed IDs with Newtonsoft.Json (https://github.com/andrewlock/StronglyTypedId/issues/36) 55 | 56 | New Features: 57 | 58 | * To support scenarios in which [InternalsVisibleTo] causes duplicate reference issues with the marker attributes, you can set the msbuild constant `STRONGLY_TYPED_ID_EXCLUDE_ATTRIBUTES` to exclude these from build output. You must then reference the StronglyTypedId.Attributes project as well, which contains the marker attributes. 59 | * By default, the marker attributes are decorated with the `[Conditional]` attribute, so they will not appear on your IDs. If you need these to persist, define the msbuild constant `STRONGLY_TYPED_ID_USAGES`. 60 | 61 | ## [v1.0.0-beta02] 62 | 63 | Bug fixes 64 | 65 | * Adds auto-generated attributes and enums as `internal` to help avoid referencing issues 66 | 67 | ## [v1.0.0-beta01] 68 | 69 | Version 0.x of this library used the helper library [CodeGeneration.Roslyn](https://github.com/AArnott/CodeGeneration.Roslyn) by [AArnott](https://github.com/AArnott), for build-time source generation. In version 1.x this approach has been completely replaced in favour of source generators, as these are explicitly supported in .NET 5+. As part of this change, there were a number of additional features added and breaking changes made. 70 | 71 | Breaking Changes 72 | 73 | * `StronglyTypedIds` namespace is required. In version 0.x of the library, the `[StronglyTypedId]` attribute was in the global namespace. In version 1.x, the attribute is in the `StronglyTypedIds` namespace, so you must add `namespace StronglyTypedIds;`. 74 | * The properties exposed by `StronglyTypedIds` have changed: there is no longer a `generateJsonConverter` property. Instead, this is infered based on the `StronglyTypedIdConverters` flags provided. 75 | * The `String` backing typed ID will throw if you call the constructor with a `null` value 76 | 77 | New Features 78 | 79 | * The attributes can now auto-generate additional converter types such as EF Core `ValueConverter` and Dapper `TypeHandler`, as described in [my blog posts](https://andrewlock.net/series/using-strongly-typed-entity-ids-to-avoid-primitive-obsession/). These are optional flags on the `converters` property. 80 | * Made interface implementations (`IEquatable` and `IComparable` currently) optional. This is to potentially support additional interfaces in future versions. 81 | * Added a `NullableString` backing type. Due to the behaviour of `struct`s in c#, the `String` backing type ID _may_ still be null, but you can't explicitly call the constructor with a null value. In contrast, you can do this with the `NullableString` backing type. 82 | * Added a `[StronglyTypedIdDefaults]` attribute to set default values for all `[StronglyTypedId]` attributes in your project. This is useful if you want to customise all the attributes, for example, if you want to generate additional converters by default. You can still override all the properties of a `[StronglyTypedId]` instance. 83 | 84 | Bug Fixes 85 | 86 | * Some converters had incorrect implementations, such as in ([#26](https://github.com/andrewlock/StronglyTypedId/issues/24)). These have been addressed in version 1.x. 87 | * Better null handling has been added for the `String` backing type, handling issues such as [#32](https://github.com/andrewlock/StronglyTypedId/issues/32). 88 | * The code is marked as auto generated, to avoid errors such as #CS1591 as described in [#27](https://github.com/andrewlock/StronglyTypedId/issues/27) 89 | 90 | ## [v0.2.1] 91 | 92 | Features: 93 | 94 | * Fix Package description 95 | 96 | ## [v0.2.0] 97 | 98 | Features: 99 | 100 | * Added support for .NET Core 3.1, and converted to using CodeGeneration.Roslyn.Tool instead of dotnet-codegen (thanks [Bartłomiej Oryszak 101 | ](https://github.com/vebbo2)) 102 | * Added support for generating System.Text.Json `JsonConverters` (thanks [Bartłomiej Oryszak 103 | ](https://github.com/vebbo2)) 104 | * Added support for long backing type (thanks [Bartłomiej Oryszak 105 | ](https://github.com/vebbo2)) 106 | 107 | ## [v0.1.0] 108 | 109 | Initial release 110 | 111 | [v0.2.0]: https://github.com/andrewlock/StronglyTypedId/compare/v0.1.0...v0.2.0 -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Andrew Lock 8 | Copyright © AndrewLock 9 | en-GB 10 | false 11 | MIT 12 | logo.png 13 | https://github.com/andrewlock/StronglyTypedId 14 | stronglytypedid attribute generator generation codegen codegenerator codegeneration netescapades 15 | https://github.com/andrewlock/StronglyTypedId 16 | git 17 | false 18 | true 19 | $(MSBuildThisFileDirectory)artifacts 20 | 21 | 22 | 23 | embedded 24 | true 25 | latest 26 | False 27 | true 28 | true 29 | $(MSBuildThisFileDirectory)\artifacts 30 | 31 | 32 | 33 | README.md 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /NuGet.integration-tests.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /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 } 75 | -------------------------------------------------------------------------------- /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 -- "$@" 68 | -------------------------------------------------------------------------------- /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.IO; 5 | using Nuke.Common.ProjectModel; 6 | using Nuke.Common.Tooling; 7 | using Nuke.Common.Tools.DotNet; 8 | using Nuke.Common.Utilities.Collections; 9 | using static Nuke.Common.EnvironmentInfo; 10 | using static Nuke.Common.Tools.DotNet.DotNetTasks; 11 | 12 | [ShutdownDotNetAfterServerBuild] 13 | class Build : NukeBuild 14 | { 15 | /// Support plugins are available for: 16 | /// - JetBrains ReSharper https://nuke.build/resharper 17 | /// - JetBrains Rider https://nuke.build/rider 18 | /// - Microsoft VisualStudio https://nuke.build/visualstudio 19 | /// - Microsoft VSCode https://nuke.build/vscode 20 | 21 | public static int Main () => Execute(x => x.Compile); 22 | 23 | [Parameter("Configuration to build - Default is 'Debug' (local) or 'Release' (server)")] 24 | readonly Configuration Configuration = IsLocalBuild ? Configuration.Debug : Configuration.Release; 25 | 26 | [Solution(GenerateProjects = true)] readonly Solution Solution; 27 | 28 | AbsolutePath SourceDirectory => RootDirectory / "src"; 29 | AbsolutePath TestsDirectory => RootDirectory / "test"; 30 | AbsolutePath ArtifactsDirectory => RootDirectory / "artifacts"; 31 | AbsolutePath TestResultsDirectory => ArtifactsDirectory / "results"; 32 | AbsolutePath OutputPackagesDirectory => ArtifactsDirectory / "packages"; 33 | 34 | [Parameter] readonly string GithubToken; 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 | SourceDirectory.GlobDirectories("**/bin", "**/obj").ForEach(x => x.DeleteDirectory()); 46 | TestsDirectory.GlobDirectories("**/bin", "**/obj").ForEach(x => x.DeleteDirectory()); 47 | PackagesDirectory.CreateOrCleanDirectory(); 48 | ArtifactsDirectory.CreateOrCleanDirectory(); 49 | }); 50 | 51 | Target Restore => _ => _ 52 | .Executes(() => 53 | { 54 | DotNetRestore(s => s 55 | .When(_ => !string.IsNullOrEmpty(PackagesDirectory), x=>x.SetPackageDirectory(PackagesDirectory)) 56 | .SetProjectFile(Solution)); 57 | }); 58 | 59 | Target Compile => _ => _ 60 | .DependsOn(Restore) 61 | .Executes(() => 62 | { 63 | DotNetBuild(s => s 64 | .SetProjectFile(Solution) 65 | .SetConfiguration(Configuration) 66 | .When(_ => IsServerBuild, x => x.SetProperty("ContinuousIntegrationBuild", "true")) 67 | .EnableNoRestore()); 68 | }); 69 | 70 | Target Test => _ => _ 71 | .DependsOn(Compile) 72 | .Executes(() => 73 | { 74 | DotNetTest(s => s 75 | .SetProjectFile(Solution) 76 | .SetConfiguration(Configuration) 77 | .When(_ => IsServerBuild, x => x.SetProperty("ContinuousIntegrationBuild", "true")) 78 | .When(_ => IsServerBuild, x => x 79 | .SetLoggers("trx") 80 | .SetResultsDirectory(TestResultsDirectory)) 81 | .EnableNoBuild() 82 | .EnableNoRestore()); 83 | }); 84 | 85 | Target Pack => _ => _ 86 | .DependsOn(Compile) 87 | .After(Test) 88 | .Produces(OutputPackagesDirectory) 89 | .Executes(() => 90 | { 91 | DotNetPack(s => s 92 | .SetConfiguration(Configuration) 93 | .SetOutputDirectory(OutputPackagesDirectory) 94 | .When(_ => IsServerBuild, x => x.SetProperty("ContinuousIntegrationBuild", "true")) 95 | .EnableNoBuild() 96 | .EnableNoRestore() 97 | .SetProject(Solution)); 98 | }); 99 | 100 | Target TestPackages => _ => _ 101 | .DependsOn(Pack) 102 | .After(Test) 103 | .Produces(OutputPackagesDirectory) 104 | .Executes(() => 105 | { 106 | var projectFiles = new[] 107 | { 108 | TestsDirectory / "StronglyTypedIds.Nuget.IntegrationTests", 109 | TestsDirectory / "StronglyTypedIds.Nuget.Attributes.IntegrationTests", 110 | }; 111 | 112 | if (!string.IsNullOrEmpty(PackagesDirectory)) 113 | { 114 | (PackagesDirectory / "stronglytypedid").DeleteDirectory(); 115 | (PackagesDirectory / "stronglytypedid.attributes").DeleteDirectory(); 116 | } 117 | 118 | DotNetRestore(s => s 119 | .When(_ => !string.IsNullOrEmpty(PackagesDirectory), x => x.SetPackageDirectory(PackagesDirectory)) 120 | .SetConfigFile(RootDirectory / "NuGet.integration-tests.config") 121 | .CombineWith(projectFiles, (s, p) => s.SetProjectFile(p))); 122 | 123 | DotNetBuild(s => s 124 | .When(_ => !string.IsNullOrEmpty(PackagesDirectory), x=>x.SetPackageDirectory(PackagesDirectory)) 125 | .EnableNoRestore() 126 | .SetConfiguration(Configuration) 127 | .CombineWith(projectFiles, (s, p) => s.SetProjectFile(p))); 128 | 129 | DotNetTest(s => s 130 | .SetConfiguration(Configuration) 131 | .EnableNoBuild() 132 | .EnableNoRestore() 133 | .When(_ => IsServerBuild, x => x 134 | .SetLoggers("trx") 135 | .SetResultsDirectory(TestResultsDirectory)) 136 | .CombineWith(projectFiles, (s, p) => s.SetProjectFile(p))); 137 | 138 | }); 139 | 140 | Target PushToNuGet => _ => _ 141 | .DependsOn(Compile) 142 | .OnlyWhenStatic(() => IsTag && IsServerBuild && IsWin) 143 | .Requires(() => NuGetToken) 144 | .After(Pack) 145 | .Executes(() => 146 | { 147 | var packages = OutputPackagesDirectory.GlobFiles("*.nupkg"); 148 | DotNetNuGetPush(s => s 149 | .SetApiKey(NuGetToken) 150 | .SetSource(NugetOrgUrl) 151 | .EnableSkipDuplicate() 152 | .CombineWith(packages, (x, package) => x 153 | .SetTargetPath(package))); 154 | }); 155 | } 156 | -------------------------------------------------------------------------------- /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 | net9.0 6 | 7 | CS0649;CS0169 8 | .. 9 | .. 10 | 1 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /docs/migration.md: -------------------------------------------------------------------------------- 1 | # Migration Guide 2 | 3 | This page describes how to migrate between specific versions in which major updates were made. 4 | 5 | ## Migrating from 0.x.x -> 1.x.x 6 | 7 | Version 0.x of this library used the helper library [CodeGeneration.Roslyn](https://github.com/AArnott/CodeGeneration.Roslyn) by [AArnott](https://github.com/AArnott), for build-time source generation. In version 1.0.0 this approach has been completely replaced in favour of source generators, as these are explicitly supported in .NET 6+. As part of this change, there were a number of additional features added and breaking changes made. 8 | 9 | ### Breaking Changes 10 | 11 | * `StronglyTypedIds` namespace is required. In version 0.x of the library, the `[StronglyTypedId]` attribute was in the global namespace. In version 1.x, the attribute is in the `StronglyTypedIds` namespace, so you must add `namespace StronglyTypedIds;`. 12 | * The properties exposed by `StronglyTypedIds` have changed: there is no longer a `generateJsonConverter` property. Instead, this is infered based on the `StronglyTypedIdConverters` flags provided. 13 | * The `String` backing typed ID will throw if you call the constructor with a `null` value 14 | 15 | ### New Features 16 | 17 | * The attributes can now auto-generate additional converter types such as EF Core `ValueConverter` and Dapper `TypeHandler`, as described in [my blog posts](https://andrewlock.net/series/using-strongly-typed-entity-ids-to-avoid-primitive-obsession/). These are optional flags on the `converters` property. 18 | * Made interface implementations (`IEquatable` and `IComparable` currently) optional. This is to potentially support additional interfaces in future versions. 19 | * Added a `NullableString` backing type. Due to the behaviour of `struct`s in c#, the `String` backing type ID _may_ still be null, but you can't explicitly call the constructor with a null value. In contrast, you can do this with the `NullableString` backing type. 20 | * Added a `[StronglyTypedIdDefaults]` attribute to set default values for all `[StronglyTypedId]` attributes in your project. This is useful if you want to customise all the attributes, for example, if you want to generate additional converters by default. You can still override all the properties of a `[StronglyTypedId]` instance. 21 | 22 | ### Bug Fixes 23 | 24 | * Some converters had incorrect implementations, such as in ([#24](https://github.com/andrewlock/StronglyTypedId/issues/24)). These have been addressed in version 1.x. 25 | * Better null handling has been added for the `String` backing type, handling issues such as [#32](https://github.com/andrewlock/StronglyTypedId/issues/32). 26 | * The code is marked as auto generated, to avoid errors such as #CS1591 as described in [#27](https://github.com/andrewlock/StronglyTypedId/issues/27) 27 | * An error deserializing nullable StronglyTypedIds with Newtonsoft.Json [#36](https://github.com/andrewlock/StronglyTypedId/issues/36) -------------------------------------------------------------------------------- /docs/strongly_typed_id.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrewlock/StronglyTypedId/6bd17db4a4b700eaad9e209baf41478cc3f0bbe9/docs/strongly_typed_id.gif -------------------------------------------------------------------------------- /docs/strongly_typed_id.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrewlock/StronglyTypedId/6bd17db4a4b700eaad9e209baf41478cc3f0bbe9/docs/strongly_typed_id.mp4 -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "allowPrerelease": true 4 | } 5 | } -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrewlock/StronglyTypedId/6bd17db4a4b700eaad9e209baf41478cc3f0bbe9/logo.png -------------------------------------------------------------------------------- /logo.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrewlock/StronglyTypedId/6bd17db4a4b700eaad9e209baf41478cc3f0bbe9/logo.xcf -------------------------------------------------------------------------------- /releasenotes.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ` and `IComparable` currently) optional. This is to potentially support additional interfaces in future versions. 81 | * Added a `NullableString` backing type. Due to the behaviour of `struct`s in c#, the `String` backing type ID _may_ still be null, but you can't explicitly call the constructor with a null value. In contrast, you can do this with the `NullableString` backing type. 82 | * Added a `[StronglyTypedIdDefaults]` attribute to set default values for all `[StronglyTypedId]` attributes in your project. This is useful if you want to customise all the attributes, for example, if you want to generate additional converters by default. You can still override all the properties of a `[StronglyTypedId]` instance. 83 | 84 | ## Bug Fixes 85 | 86 | * Some converters had incorrect implementations, such as in ([#26](https://github.com/andrewlock/StronglyTypedId/issues/24)). These have been addressed in version 1.x. 87 | * Better null handling has been added for the `String` backing type, handling issues such as [#32](https://github.com/andrewlock/StronglyTypedId/issues/32). 88 | * The code is marked as auto generated, to avoid errors such as #CS1591 as described in [#27](https://github.com/andrewlock/StronglyTypedId/issues/27) 89 | 90 | ]]> 91 | 92 | $(PackageReleaseNotes) 93 | See $(PackageProjectUrl)/blob/master/CHANGELOG.md#v$(VersionPrefix.Replace('.','')) for more details. 94 | 95 | -------------------------------------------------------------------------------- /src/StronglyTypedIds.Attributes/StronglyTypedIdAttribute.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by the StronglyTypedId source generator 4 | // 5 | // Changes to this file may cause incorrect behavior and will be lost if 6 | // the code is regenerated. 7 | // 8 | //------------------------------------------------------------------------------ 9 | 10 | #pragma warning disable 1591 // publicly visible type or member must be documented 11 | 12 | #nullable enable 13 | 14 | namespace StronglyTypedIds 15 | { 16 | /// 17 | /// Place on partial structs to make the type a strongly-typed ID 18 | /// 19 | [global::System.AttributeUsage(global::System.AttributeTargets.Struct, Inherited = false, AllowMultiple = false)] 20 | [global::System.Diagnostics.Conditional("STRONGLY_TYPED_ID_USAGES")] 21 | public sealed class StronglyTypedIdAttribute : global::System.Attribute 22 | { 23 | /// 24 | /// Make the struct a strongly typed ID. 25 | /// 26 | /// The built-in template to use to generate the ID. 27 | /// The names of additional custom templates to use to generate the ID. 28 | /// Templates must be added to the project using the format NAME.typedid, 29 | /// where NAME is the name of the template passed in . 30 | /// 31 | public StronglyTypedIdAttribute(global::StronglyTypedIds.Template template, params string[] templateNames) 32 | { 33 | } 34 | 35 | /// 36 | /// Make the struct a strongly typed ID. 37 | /// 38 | /// The names of the template to use to generate the ID. 39 | /// Templates must be added to the project using the format NAME.typedid, 40 | /// where NAME is the name of the template passed in . 41 | /// If no templates are provided, the default value is used, as specified by 42 | /// , or alternatively the 43 | /// template. 44 | /// 45 | public StronglyTypedIdAttribute(params string[] templateNames) 46 | { 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /src/StronglyTypedIds.Attributes/StronglyTypedIdDefaultsAttribute.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by the StronglyTypedId source generator 4 | // 5 | // Changes to this file may cause incorrect behavior and will be lost if 6 | // the code is regenerated. 7 | // 8 | //------------------------------------------------------------------------------ 9 | 10 | #pragma warning disable 1591 // publicly visible type or member must be documented 11 | 12 | #nullable enable 13 | 14 | namespace StronglyTypedIds 15 | { 16 | /// 17 | /// Used to control the default strongly typed ID values. Apply to an assembly using 18 | /// [assembly:StronglyTypedIdDefaults(Template.Int)] for example 19 | /// 20 | [global::System.AttributeUsage(global::System.AttributeTargets.Assembly, Inherited = false, AllowMultiple = false)] 21 | [global::System.Diagnostics.Conditional("STRONGLY_TYPED_ID_USAGES")] 22 | public sealed class StronglyTypedIdDefaultsAttribute : global::System.Attribute 23 | { 24 | /// 25 | /// Set the default template to use for strongly typed IDs 26 | /// 27 | /// The built-in template to use to generate the ID. 28 | /// The names of additional custom templates to use to generate the ID. 29 | /// Templates must be added to the project using the format NAME.typedid, 30 | /// where NAME is the name of the template passed in . 31 | /// 32 | public StronglyTypedIdDefaultsAttribute(global::StronglyTypedIds.Template template, params string[] templateNames) 33 | { 34 | } 35 | 36 | /// 37 | /// Set the default template to use for strongly typed IDs 38 | /// 39 | /// The name of the template to use to generate the ID. 40 | /// Templates must be added to the project using the format NAME.typedid, 41 | /// where NAME is the name of the template passed in . 42 | /// 43 | public StronglyTypedIdDefaultsAttribute(string templateName) 44 | { 45 | } 46 | 47 | /// 48 | /// Set the default template to use for strongly typed IDs 49 | /// 50 | /// The name of the template to use to generate the ID. 51 | /// Templates must be added to the project using the format NAME.typedid, 52 | /// where NAME is the name of the template passed in . 53 | /// 54 | public StronglyTypedIdDefaultsAttribute(string templateName, params string[] templateNames) 55 | { 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /src/StronglyTypedIds.Attributes/StronglyTypedIds.Attributes.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | enable 6 | StronglyTypedIds 7 | false 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/StronglyTypedIds.Attributes/Template.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by the StronglyTypedId source generator 4 | // 5 | // Changes to this file may cause incorrect behavior and will be lost if 6 | // the code is regenerated. 7 | // 8 | //------------------------------------------------------------------------------ 9 | 10 | #pragma warning disable 1591 // publicly visible type or member must be documented 11 | 12 | #nullable enable 13 | 14 | using System; 15 | 16 | namespace StronglyTypedIds 17 | { 18 | /// 19 | /// The built-in template to use to generate the strongly-typed ID 20 | /// 21 | public enum Template 22 | { 23 | Guid, 24 | Int, 25 | String, 26 | Long, 27 | } 28 | } -------------------------------------------------------------------------------- /src/StronglyTypedIds.Templates/StronglyTypedId.Templates.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | false 4 | 5 | -------------------------------------------------------------------------------- /src/StronglyTypedIds.Templates/StronglyTypedId.Templates.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | $(ShowStronglyTypedIdTemplates) 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/StronglyTypedIds.Templates/StronglyTypedIds.Templates.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | false 6 | enable 7 | StronglyTypedId.Templates 8 | A collection of templates for generating strongly-typed IDs by decorating with a [StronglyTypedId] attribute 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/StronglyTypedIds.Templates/guid-dapper.typedid: -------------------------------------------------------------------------------- 1 | partial struct PLACEHOLDERID 2 | { 3 | public partial class DapperTypeHandler : global::Dapper.SqlMapper.TypeHandler 4 | { 5 | public override void SetValue(global::System.Data.IDbDataParameter parameter, PLACEHOLDERID value) 6 | { 7 | parameter.Value = value.Value; 8 | } 9 | 10 | public override PLACEHOLDERID Parse(object value) 11 | { 12 | return value switch 13 | { 14 | global::System.Guid guidValue => new PLACEHOLDERID(guidValue), 15 | string stringValue when !string.IsNullOrEmpty(stringValue) && global::System.Guid.TryParse(stringValue, out var result) => new PLACEHOLDERID(result), 16 | _ => throw new global::System.InvalidCastException($"Unable to cast object of type {value.GetType()} to PLACEHOLDERID"), 17 | }; 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /src/StronglyTypedIds.Templates/guid-efcore.typedid: -------------------------------------------------------------------------------- 1 | partial struct PLACEHOLDERID 2 | { 3 | public partial class EfCoreValueConverter : global::Microsoft.EntityFrameworkCore.Storage.ValueConversion.ValueConverter 4 | { 5 | public EfCoreValueConverter() : this(null) { } 6 | public EfCoreValueConverter(global::Microsoft.EntityFrameworkCore.Storage.ValueConversion.ConverterMappingHints? mappingHints = null) 7 | : base( 8 | id => id.Value, 9 | value => new PLACEHOLDERID(value), 10 | mappingHints 11 | ) { } 12 | } 13 | } -------------------------------------------------------------------------------- /src/StronglyTypedIds.Templates/guid-newtonsoftjson.typedid: -------------------------------------------------------------------------------- 1 | [global::Newtonsoft.Json.JsonConverter(typeof(PLACEHOLDERIDNewtonsoftJsonConverter))] 2 | partial struct PLACEHOLDERID 3 | { 4 | public partial class PLACEHOLDERIDNewtonsoftJsonConverter : global::Newtonsoft.Json.JsonConverter 5 | { 6 | public override bool CanConvert(global::System.Type objectType) 7 | { 8 | return objectType == typeof(PLACEHOLDERID); 9 | } 10 | 11 | public override void WriteJson(global::Newtonsoft.Json.JsonWriter writer, object? value, global::Newtonsoft.Json.JsonSerializer serializer) 12 | { 13 | serializer.Serialize(writer, value is PLACEHOLDERID id ? id.Value : null); 14 | } 15 | 16 | public override object? ReadJson(global::Newtonsoft.Json.JsonReader reader, global::System.Type objectType, object? existingValue, global::Newtonsoft.Json.JsonSerializer serializer) 17 | { 18 | var guid = serializer.Deserialize(reader); 19 | return guid.HasValue ? new PLACEHOLDERID(guid.Value) : null; 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /src/StronglyTypedIds.Templates/int-dapper.typedid: -------------------------------------------------------------------------------- 1 | partial struct PLACEHOLDERID 2 | { 3 | public partial class DapperTypeHandler : global::Dapper.SqlMapper.TypeHandler 4 | { 5 | public override void SetValue(global::System.Data.IDbDataParameter parameter, PLACEHOLDERID value) 6 | { 7 | parameter.Value = value.Value; 8 | } 9 | 10 | public override PLACEHOLDERID Parse(object value) 11 | { 12 | return value switch 13 | { 14 | int intValue => new PLACEHOLDERID(intValue), 15 | short shortValue => new PLACEHOLDERID(shortValue), 16 | long longValue and < int.MaxValue and > int.MinValue => new PLACEHOLDERID((int)longValue), 17 | decimal decimalValue and < int.MaxValue and > int.MinValue => new PLACEHOLDERID((int)decimalValue), 18 | string stringValue when !string.IsNullOrEmpty(stringValue) && int.TryParse(stringValue, out var result) => new PLACEHOLDERID(result), 19 | _ => throw new global::System.InvalidCastException($"Unable to cast object of type {value.GetType()} to PLACEHOLDERID"), 20 | }; 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /src/StronglyTypedIds.Templates/int-efcore.typedid: -------------------------------------------------------------------------------- 1 | partial struct PLACEHOLDERID 2 | { 3 | public partial class EfCoreValueConverter : global::Microsoft.EntityFrameworkCore.Storage.ValueConversion.ValueConverter 4 | { 5 | public EfCoreValueConverter() : this(null) { } 6 | public EfCoreValueConverter(global::Microsoft.EntityFrameworkCore.Storage.ValueConversion.ConverterMappingHints? mappingHints = null) 7 | : base( 8 | id => id.Value, 9 | value => new PLACEHOLDERID(value), 10 | mappingHints 11 | ) { } 12 | } 13 | } -------------------------------------------------------------------------------- /src/StronglyTypedIds.Templates/int-newtonsoftjson.typedid: -------------------------------------------------------------------------------- 1 | [global::Newtonsoft.Json.JsonConverter(typeof(PLACEHOLDERIDNewtonsoftJsonConverter))] 2 | partial struct PLACEHOLDERID 3 | { 4 | public partial class PLACEHOLDERIDNewtonsoftJsonConverter : global::Newtonsoft.Json.JsonConverter 5 | { 6 | public override bool CanConvert(global::System.Type objectType) 7 | { 8 | return objectType == typeof(PLACEHOLDERID); 9 | } 10 | 11 | public override void WriteJson(global::Newtonsoft.Json.JsonWriter writer, object? value, global::Newtonsoft.Json.JsonSerializer serializer) 12 | { 13 | serializer.Serialize(writer, value is PLACEHOLDERID id ? id.Value : null); 14 | } 15 | 16 | public override object? ReadJson(global::Newtonsoft.Json.JsonReader reader, global::System.Type objectType, object? existingValue, global::Newtonsoft.Json.JsonSerializer serializer) 17 | { 18 | var result = serializer.Deserialize(reader); 19 | return result.HasValue ? new PLACEHOLDERID(result.Value) : null; 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /src/StronglyTypedIds.Templates/long-dapper.typedid: -------------------------------------------------------------------------------- 1 | partial struct PLACEHOLDERID 2 | { 3 | public partial class DapperTypeHandler : global::Dapper.SqlMapper.TypeHandler 4 | { 5 | public override void SetValue(global::System.Data.IDbDataParameter parameter, PLACEHOLDERID value) 6 | { 7 | parameter.Value = value.Value; 8 | } 9 | 10 | public override PLACEHOLDERID Parse(object value) 11 | { 12 | return value switch 13 | { 14 | long longValue => new PLACEHOLDERID(longValue), 15 | int intValue => new PLACEHOLDERID(intValue), 16 | short shortValue => new PLACEHOLDERID(shortValue), 17 | decimal decimalValue and < long.MaxValue and > long.MinValue => new PLACEHOLDERID((long)decimalValue), 18 | string stringValue when !string.IsNullOrEmpty(stringValue) && long.TryParse(stringValue, out var result) => new PLACEHOLDERID(result), 19 | _ => throw new global::System.InvalidCastException($"Unable to cast object of type {value.GetType()} to PLACEHOLDERID"), 20 | }; 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /src/StronglyTypedIds.Templates/long-efcore.typedid: -------------------------------------------------------------------------------- 1 | partial struct PLACEHOLDERID 2 | { 3 | public partial class EfCoreValueConverter : global::Microsoft.EntityFrameworkCore.Storage.ValueConversion.ValueConverter 4 | { 5 | public EfCoreValueConverter() : this(null) { } 6 | public EfCoreValueConverter(global::Microsoft.EntityFrameworkCore.Storage.ValueConversion.ConverterMappingHints? mappingHints = null) 7 | : base( 8 | id => id.Value, 9 | value => new PLACEHOLDERID(value), 10 | mappingHints 11 | ) { } 12 | } 13 | } -------------------------------------------------------------------------------- /src/StronglyTypedIds.Templates/long-newtonsoftjson.typedid: -------------------------------------------------------------------------------- 1 | [global::Newtonsoft.Json.JsonConverter(typeof(PLACEHOLDERIDNewtonsoftJsonConverter))] 2 | partial struct PLACEHOLDERID 3 | { 4 | public partial class PLACEHOLDERIDNewtonsoftJsonConverter : global::Newtonsoft.Json.JsonConverter 5 | { 6 | public override bool CanConvert(global::System.Type objectType) 7 | { 8 | return objectType == typeof(PLACEHOLDERID); 9 | } 10 | 11 | public override void WriteJson(global::Newtonsoft.Json.JsonWriter writer, object? value, global::Newtonsoft.Json.JsonSerializer serializer) 12 | { 13 | serializer.Serialize(writer, value is PLACEHOLDERID id ? id.Value : null); 14 | } 15 | 16 | public override object? ReadJson(global::Newtonsoft.Json.JsonReader reader, global::System.Type objectType, object? existingValue, global::Newtonsoft.Json.JsonSerializer serializer) 17 | { 18 | var result = serializer.Deserialize(reader); 19 | return result.HasValue ? new PLACEHOLDERID(result.Value) : null; 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /src/StronglyTypedIds.Templates/string-dapper.typedid: -------------------------------------------------------------------------------- 1 | partial struct PLACEHOLDERID 2 | { 3 | public partial class DapperTypeHandler : global::Dapper.SqlMapper.TypeHandler 4 | { 5 | public override void SetValue(global::System.Data.IDbDataParameter parameter, PLACEHOLDERID value) 6 | { 7 | parameter.Value = value.Value; 8 | } 9 | 10 | public override PLACEHOLDERID Parse(object value) 11 | { 12 | return value switch 13 | { 14 | string stringValue => new PLACEHOLDERID(stringValue), 15 | _ => throw new global::System.InvalidCastException($"Unable to cast object of type {value.GetType()} to PLACEHOLDERID"), 16 | }; 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /src/StronglyTypedIds.Templates/string-efcore.typedid: -------------------------------------------------------------------------------- 1 | partial struct PLACEHOLDERID 2 | { 3 | public partial class EfCoreValueConverter : global::Microsoft.EntityFrameworkCore.Storage.ValueConversion.ValueConverter 4 | { 5 | public EfCoreValueConverter() : this(null) { } 6 | public EfCoreValueConverter(global::Microsoft.EntityFrameworkCore.Storage.ValueConversion.ConverterMappingHints? mappingHints = null) 7 | : base( 8 | id => id.Value, 9 | value => new PLACEHOLDERID(value), 10 | mappingHints 11 | ) { } 12 | } 13 | } -------------------------------------------------------------------------------- /src/StronglyTypedIds.Templates/string-newtonsoftjson.typedid: -------------------------------------------------------------------------------- 1 | [global::Newtonsoft.Json.JsonConverter(typeof(PLACEHOLDERIDNewtonsoftJsonConverter))] 2 | partial struct PLACEHOLDERID 3 | { 4 | public partial class PLACEHOLDERIDNewtonsoftJsonConverter : global::Newtonsoft.Json.JsonConverter 5 | { 6 | public override bool CanConvert(global::System.Type objectType) 7 | { 8 | return objectType == typeof(PLACEHOLDERID); 9 | } 10 | 11 | public override void WriteJson(global::Newtonsoft.Json.JsonWriter writer, object? value, global::Newtonsoft.Json.JsonSerializer serializer) 12 | { 13 | serializer.Serialize(writer, value is PLACEHOLDERID id ? id.Value : null); 14 | } 15 | 16 | public override object? ReadJson(global::Newtonsoft.Json.JsonReader reader, global::System.Type objectType, object? existingValue, global::Newtonsoft.Json.JsonSerializer serializer) 17 | { 18 | if (objectType == typeof(PLACEHOLDERID?)) 19 | { 20 | var value = serializer.Deserialize(reader); 21 | 22 | return value is null ? null : new PLACEHOLDERID(value); 23 | } 24 | 25 | return new PLACEHOLDERID(serializer.Deserialize(reader)!); 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /src/StronglyTypedIds/Constants.cs: -------------------------------------------------------------------------------- 1 | namespace StronglyTypedIds 2 | { 3 | internal static class Constants 4 | { 5 | public const string StronglyTypedIdsVersion = "1.0.0-beta08"; 6 | public const string Usage = nameof(Usage); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/StronglyTypedIds/DiagnosticInfo.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | 3 | namespace StronglyTypedIds; 4 | 5 | internal sealed record DiagnosticInfo 6 | { 7 | public DiagnosticInfo(DiagnosticDescriptor descriptor, Location location, string? messageArg = null, EquatableArray<(string, string)> properties = default) 8 | { 9 | Descriptor = descriptor; 10 | Location = location; 11 | MessageArg = messageArg; 12 | Properties = properties; 13 | } 14 | 15 | public DiagnosticDescriptor Descriptor { get; } 16 | public Location Location { get; } 17 | public string? MessageArg { get; } 18 | public EquatableArray<(string, string)> Properties { get; } 19 | } -------------------------------------------------------------------------------- /src/StronglyTypedIds/Diagnostics/DiagnosticHelper.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Immutable; 2 | using Microsoft.CodeAnalysis; 3 | 4 | namespace StronglyTypedIds.Diagnostics; 5 | 6 | internal static class DiagnosticHelper 7 | { 8 | public static void ReportDiagnostic(this SourceProductionContext context, DiagnosticInfo info) 9 | { 10 | var diagnostic = CreateDiagnostic(info); 11 | 12 | context.ReportDiagnostic(diagnostic); 13 | } 14 | 15 | public static Diagnostic CreateDiagnostic(DiagnosticInfo info) 16 | { 17 | var messageArgs = info.MessageArg is { } arg 18 | ? new object[] {arg} 19 | : null; 20 | 21 | ImmutableDictionary? properties = null; 22 | if (info.Properties is {Count: > 0} props) 23 | { 24 | var dict = ImmutableDictionary.CreateBuilder(); 25 | foreach (var prop in props.GetArray()!) 26 | { 27 | dict.Add(prop.Item1, prop.Item2); 28 | } 29 | 30 | properties = dict.ToImmutable()!; 31 | } 32 | 33 | 34 | var diagnostic = Diagnostic.Create( 35 | descriptor: info.Descriptor, 36 | location: info.Location, 37 | messageArgs: messageArgs, 38 | properties: properties); 39 | return diagnostic; 40 | } 41 | } -------------------------------------------------------------------------------- /src/StronglyTypedIds/Diagnostics/InvalidTemplateNameDiagnostic.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | 3 | namespace StronglyTypedIds.Diagnostics; 4 | 5 | internal static class InvalidTemplateNameDiagnostic 6 | { 7 | internal const string Id = "STRONGID001"; 8 | internal const string Message = "The template name must not be null or whitespace."; 9 | internal const string Title = "Invalid template name"; 10 | 11 | public static DiagnosticInfo CreateInfo(LocationInfo location) 12 | => new(new DiagnosticDescriptor( 13 | Id, Title, Message, category: Constants.Usage, defaultSeverity: DiagnosticSeverity.Error, isEnabledByDefault: true), 14 | location.ToLocation()); 15 | } -------------------------------------------------------------------------------- /src/StronglyTypedIds/Diagnostics/MultipleAssemblyAttributeDiagnostic.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | 3 | namespace StronglyTypedIds.Diagnostics; 4 | 5 | internal static class MultipleAssemblyAttributeDiagnostic 6 | { 7 | internal const string Id = "STRONGID004"; 8 | internal const string Message = "You may only have one instance of the StronglyTypedIdDefaults assembly attribute"; 9 | internal const string Title = "Multiple assembly attributes"; 10 | 11 | public static DiagnosticInfo CreateInfo(SyntaxNode currentNode) => 12 | new(new DiagnosticDescriptor( 13 | Id, Title, Message, category: Constants.Usage, defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: true), 14 | currentNode.GetLocation()); 15 | } -------------------------------------------------------------------------------- /src/StronglyTypedIds/Diagnostics/NotPartialDiagnostic.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | 3 | namespace StronglyTypedIds.Diagnostics 4 | { 5 | internal static class NotPartialDiagnostic 6 | { 7 | internal const string Id = "STRONGID003"; 8 | internal const string Message = "The target of the StronglyTypedId attribute must be declared as partial."; 9 | internal const string Title = "Must be partial"; 10 | 11 | public static Diagnostic Create(SyntaxNode currentNode) => 12 | Diagnostic.Create( 13 | new DiagnosticDescriptor( 14 | Id, Title, Message, category: Constants.Usage, defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: true), 15 | currentNode.GetLocation()); 16 | 17 | public static DiagnosticInfo CreateInfo(SyntaxNode currentNode) 18 | => new(new DiagnosticDescriptor( 19 | Id, Title, Message, category: Constants.Usage, defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: true), 20 | currentNode.GetLocation()); 21 | } 22 | } -------------------------------------------------------------------------------- /src/StronglyTypedIds/Diagnostics/UnknownTemplateCodeFixProvider.cs: -------------------------------------------------------------------------------- 1 | // Based on https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/1dd5ced072d7d870f6dd698a6c02ad509a122452/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/Settings/UnknownTemplateCodeFixProvider.cs 2 | 3 | #nullable disable 4 | 5 | using System; 6 | using System.Collections.Immutable; 7 | using System.Composition; 8 | using System.Threading; 9 | using System.Threading.Tasks; 10 | using Microsoft.CodeAnalysis; 11 | using Microsoft.CodeAnalysis.CodeActions; 12 | using Microsoft.CodeAnalysis.CodeFixes; 13 | 14 | namespace StronglyTypedIds.Diagnostics; 15 | 16 | [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(UnknownTemplateCodeFixProvider)), Shared] 17 | internal class UnknownTemplateCodeFixProvider : CodeFixProvider 18 | { 19 | internal const string DefaultHeader = 20 | """" 21 | // ACTION REQUIRED: This file was automatically added to your project, but it 22 | // will not take effect until additional steps are taken to enable it. See 23 | // https://github.com/dotnet/roslyn/issues/4655 for more details. 24 | // 25 | // To enable the template, in Visual Studio 2017, 2019, and 2022: 26 | // 1. Select the file in Solution Explorer. 27 | // 2. In the 'Properties' window, set the value for 'Build Action' 28 | // to one of the following (whichever is available): 29 | // - For .NET Core and .NET Standard projects: 'C# analyzer additional file' 30 | // - For other projects: 'AdditionalFiles' 31 | // 32 | // Any instances of PLACEHOLDERID will be replaced with the target ID name 33 | // when generating code from this template. 34 | 35 | 36 | """"; 37 | 38 | /// 39 | public override ImmutableArray FixableDiagnosticIds { get; } = 40 | ImmutableArray.Create(Diagnostics.UnknownTemplateDiagnostic.Id); 41 | 42 | /// 43 | public override Task RegisterCodeFixesAsync(CodeFixContext context) 44 | { 45 | var project = context.Document.Project; 46 | var workspace = project.Solution.Workspace; 47 | 48 | // if we can't add extra documents, there's nothing we can do 49 | if (!workspace.CanApplyChange(ApplyChangesKind.AddAdditionalDocument)) 50 | { 51 | return Task.CompletedTask; 52 | } 53 | 54 | foreach (var diagnostic in context.Diagnostics) 55 | { 56 | if(!diagnostic.Properties.TryGetValue(UnknownTemplateDiagnostic.TemplateName, out var templateName)) 57 | { 58 | // This shouldn't happen, but play it safe 59 | continue; 60 | } 61 | 62 | // check if the template file already exists 63 | var alreadyAdded = false; 64 | foreach (var document in project.AdditionalDocuments) 65 | { 66 | if (document.Name.Equals(templateName, StringComparison.OrdinalIgnoreCase)) 67 | { 68 | alreadyAdded = true; 69 | break; 70 | } 71 | } 72 | 73 | if (alreadyAdded) 74 | { 75 | continue; 76 | } 77 | 78 | context.RegisterCodeFix( 79 | CodeAction.Create( 80 | $"Add '{templateName}.typedid' template to the project", 81 | cancellationToken => GetTransformedSolutionAsync(context.Document, templateName, cancellationToken), 82 | nameof(UnknownTemplateCodeFixProvider)), 83 | diagnostic); 84 | } 85 | 86 | return Task.CompletedTask; 87 | } 88 | 89 | /// 90 | public override FixAllProvider GetFixAllProvider() 91 | { 92 | // This code fix does not support fix all actions. 93 | return null; 94 | } 95 | 96 | private static Task GetTransformedSolutionAsync(Document document, string templateName, CancellationToken cancellationToken) 97 | { 98 | // Currently unused 99 | _ = cancellationToken; 100 | 101 | var project = document.Project; 102 | var solution = project.Solution; 103 | 104 | var newDocumentId = DocumentId.CreateNewId(project.Id); 105 | 106 | var templateContent = GetTemplateContent(templateName); 107 | 108 | var newSolution = solution.AddAdditionalDocument(newDocumentId, $"{templateName}.typedid", templateContent); 109 | 110 | return Task.FromResult(newSolution); 111 | } 112 | 113 | internal static string GetTemplateContent(string templateName) 114 | { 115 | var templateContent = templateName switch 116 | { 117 | { } x when x.Contains("int", StringComparison.OrdinalIgnoreCase) => EmbeddedSources.LoadEmbeddedTypedId("int-full.typedid"), 118 | { } x when x.Contains("long", StringComparison.OrdinalIgnoreCase) => EmbeddedSources.LoadEmbeddedTypedId("long-full.typedid"), 119 | { } x when x.Contains("string", StringComparison.OrdinalIgnoreCase) => EmbeddedSources.LoadEmbeddedTypedId("string-full.typedid"), 120 | _ => EmbeddedSources.LoadEmbeddedTypedId("guid-full.typedid"), 121 | }; 122 | 123 | return DefaultHeader + templateContent; 124 | } 125 | } -------------------------------------------------------------------------------- /src/StronglyTypedIds/Diagnostics/UnknownTemplateDiagnostic.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | 3 | namespace StronglyTypedIds.Diagnostics; 4 | 5 | internal static class UnknownTemplateDiagnostic 6 | { 7 | internal const string Id = "STRONGID002"; 8 | internal const string Title = "Unknown .typedid template"; 9 | internal const string TemplateName = nameof(TemplateName); 10 | 11 | public static DiagnosticInfo CreateInfo(LocationInfo location, string name) 12 | => new(CreateDescriptor(), 13 | location.ToLocation(), 14 | name, 15 | new EquatableArray<(string, string)>(new[] {(TemplateName, name)})); 16 | 17 | public static DiagnosticDescriptor CreateDescriptor() 18 | => new( 19 | Id, Title, "Could not find '{0}.typedid' template. Ensure the template exists in the project and has a build action of 'Additional Files'.", 20 | category: Constants.Usage, defaultSeverity: DiagnosticSeverity.Error, isEnabledByDefault: true); 21 | } -------------------------------------------------------------------------------- /src/StronglyTypedIds/EmbeddedSources.String.cs: -------------------------------------------------------------------------------- 1 | namespace StronglyTypedIds; 2 | 3 | internal static partial class EmbeddedSources 4 | { 5 | private const string StringTemplate = """ 6 | partial struct PLACEHOLDERID : 7 | #if NET6_0_OR_GREATER 8 | global::System.ISpanFormattable, 9 | #endif 10 | #if NET7_0_OR_GREATER 11 | global::System.IParsable, global::System.ISpanParsable, 12 | #endif 13 | global::System.IComparable, global::System.IEquatable, global::System.IFormattable 14 | { 15 | public string Value { get; } 16 | 17 | public PLACEHOLDERID(string value) 18 | { 19 | #if NET7_0_OR_GREATER 20 | global::System.ArgumentNullException.ThrowIfNull(value); 21 | Value = value; 22 | #else 23 | Value = value ?? throw new global::System.ArgumentNullException(nameof(value)); 24 | #endif 25 | } 26 | 27 | public static readonly PLACEHOLDERID Empty = new PLACEHOLDERID(string.Empty); 28 | 29 | /// 30 | public bool Equals(PLACEHOLDERID other) 31 | => (Value, other.Value) switch 32 | { 33 | (null, null) => true, 34 | (null, _) => false, 35 | (_, null) => false, 36 | (_, _) => Value.Equals(other.Value, global::System.StringComparison.Ordinal), 37 | }; 38 | 39 | public override bool Equals(object? obj) 40 | { 41 | if (ReferenceEquals(null, obj)) return false; 42 | return obj is PLACEHOLDERID other && Equals(other); 43 | } 44 | 45 | public override int GetHashCode() => Value.GetHashCode(); 46 | 47 | public override string ToString() => Value; 48 | 49 | public static bool operator ==(PLACEHOLDERID a, PLACEHOLDERID b) => a.Equals(b); 50 | public static bool operator !=(PLACEHOLDERID a, PLACEHOLDERID b) => !(a == b); 51 | public static bool operator > (PLACEHOLDERID a, PLACEHOLDERID b) => a.CompareTo(b) > 0; 52 | public static bool operator < (PLACEHOLDERID a, PLACEHOLDERID b) => a.CompareTo(b) < 0; 53 | public static bool operator >= (PLACEHOLDERID a, PLACEHOLDERID b) => a.CompareTo(b) >= 0; 54 | public static bool operator <= (PLACEHOLDERID a, PLACEHOLDERID b) => a.CompareTo(b) <= 0; 55 | 56 | /// 57 | public int CompareTo(PLACEHOLDERID other) 58 | => (Value, other.Value) switch 59 | { 60 | (null, null) => 0, 61 | (null, _) => -1, 62 | (_, null) => 1, 63 | (_, _) => string.CompareOrdinal(Value, other.Value), 64 | }; 65 | 66 | public partial class PLACEHOLDERIDTypeConverter : global::System.ComponentModel.TypeConverter 67 | { 68 | public override bool CanConvertFrom(global::System.ComponentModel.ITypeDescriptorContext? context, global::System.Type sourceType) 69 | { 70 | return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType); 71 | } 72 | 73 | public override object? ConvertFrom(global::System.ComponentModel.ITypeDescriptorContext? context, global::System.Globalization.CultureInfo? culture, object value) 74 | { 75 | if (value is string stringValue) 76 | { 77 | return new PLACEHOLDERID(stringValue); 78 | } 79 | 80 | return base.ConvertFrom(context, culture, value); 81 | } 82 | 83 | public override bool CanConvertTo(global::System.ComponentModel.ITypeDescriptorContext? context, global::System.Type? sourceType) 84 | { 85 | return sourceType == typeof(string) || base.CanConvertTo(context, sourceType); 86 | } 87 | 88 | public override object? ConvertTo(global::System.ComponentModel.ITypeDescriptorContext? context, global::System.Globalization.CultureInfo? culture, object? value, global::System.Type destinationType) 89 | { 90 | if (value is PLACEHOLDERID idValue) 91 | { 92 | if (destinationType == typeof(string)) 93 | { 94 | return idValue.Value; 95 | } 96 | } 97 | 98 | return base.ConvertTo(context, culture, value, destinationType); 99 | } 100 | } 101 | 102 | public partial class PLACEHOLDERIDSystemTextJsonConverter : global::System.Text.Json.Serialization.JsonConverter 103 | { 104 | public override PLACEHOLDERID Read(ref global::System.Text.Json.Utf8JsonReader reader, global::System.Type typeToConvert, global::System.Text.Json.JsonSerializerOptions options) 105 | => new (reader.GetString()!); 106 | 107 | public override void Write(global::System.Text.Json.Utf8JsonWriter writer, PLACEHOLDERID value, global::System.Text.Json.JsonSerializerOptions options) 108 | => writer.WriteStringValue(value.Value); 109 | 110 | #if NET6_0_OR_GREATER 111 | public override PLACEHOLDERID ReadAsPropertyName(ref global::System.Text.Json.Utf8JsonReader reader, global::System.Type typeToConvert, global::System.Text.Json.JsonSerializerOptions options) 112 | => new(reader.GetString() ?? throw new global::System.FormatException("The string for the PLACEHOLDERID property was null")); 113 | 114 | public override void WriteAsPropertyName(global::System.Text.Json.Utf8JsonWriter writer, PLACEHOLDERID value, global::System.Text.Json.JsonSerializerOptions options) 115 | => writer.WritePropertyName(value.Value); 116 | #endif 117 | } 118 | 119 | public static PLACEHOLDERID Parse(string input) 120 | => new(input); 121 | 122 | #if NET7_0_OR_GREATER 123 | /// 124 | public static PLACEHOLDERID Parse(string input, global::System.IFormatProvider? provider) 125 | => new(input); 126 | 127 | /// 128 | public static bool TryParse( 129 | [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] string? input, 130 | global::System.IFormatProvider? provider, 131 | out PLACEHOLDERID result) 132 | { 133 | if (input is null) 134 | { 135 | result = default; 136 | return false; 137 | } 138 | 139 | result = new(input); 140 | return true; 141 | } 142 | #endif 143 | 144 | /// 145 | public string ToString(string? format, global::System.IFormatProvider? formatProvider) 146 | => Value; 147 | 148 | #if NETCOREAPP2_1_OR_GREATER 149 | public static PLACEHOLDERID Parse(global::System.ReadOnlySpan input) 150 | => new(input.ToString()); 151 | #endif 152 | 153 | #if NET6_0_OR_GREATER 154 | #if NET7_0_OR_GREATER 155 | /// 156 | #endif 157 | public static PLACEHOLDERID Parse(global::System.ReadOnlySpan input, global::System.IFormatProvider? provider) 158 | => new(input.ToString()); 159 | 160 | #if NET7_0_OR_GREATER 161 | /// 162 | #endif 163 | public static bool TryParse(global::System.ReadOnlySpan input, global::System.IFormatProvider? provider, out PLACEHOLDERID result) 164 | { 165 | result = new(input.ToString()); 166 | return true; 167 | } 168 | 169 | /// 170 | public bool TryFormat( 171 | global::System.Span destination, 172 | out int charsWritten, 173 | global::System.ReadOnlySpan format, 174 | global::System.IFormatProvider? provider) 175 | => TryFormat(destination, out charsWritten, format); 176 | 177 | /// 178 | public bool TryFormat( 179 | global::System.Span destination, 180 | out int charsWritten, 181 | global::System.ReadOnlySpan format = default) 182 | { 183 | if (destination.Length > Value.Length) 184 | { 185 | global::System.MemoryExtensions.AsSpan(Value).CopyTo(destination); 186 | charsWritten = Value.Length; 187 | return true; 188 | } 189 | 190 | charsWritten = default; 191 | return false; 192 | } 193 | #endif 194 | } 195 | """; 196 | } -------------------------------------------------------------------------------- /src/StronglyTypedIds/EmbeddedSources.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Reflection; 4 | using System.Text; 5 | 6 | namespace StronglyTypedIds; 7 | 8 | internal static partial class EmbeddedSources 9 | { 10 | private static readonly Assembly ThisAssembly = typeof(EmbeddedSources).Assembly; 11 | internal static readonly string StronglyTypedIdAttributeSource = LoadAttributeTemplateForEmitting("StronglyTypedIdAttribute"); 12 | internal static readonly string StronglyTypedIdDefaultsAttributeSource = LoadAttributeTemplateForEmitting("StronglyTypedIdDefaultsAttribute"); 13 | internal static readonly string TemplateSource = LoadAttributeTemplateForEmitting("Template"); 14 | 15 | internal static readonly string AutoGeneratedHeader = LoadEmbeddedResource("StronglyTypedIds.Templates.AutoGeneratedHeader.cs"); 16 | 17 | internal static string GetTemplate(Template template) 18 | => template switch 19 | { 20 | Template.Guid => GuidTemplate, 21 | Template.Int => IntTemplate, 22 | Template.Long => LongTemplate, 23 | Template.String => StringTemplate, 24 | _ => string.Empty, 25 | }; 26 | 27 | internal static string LoadEmbeddedTypedId(string templateName) 28 | => LoadEmbeddedResource($"StronglyTypedIds.Templates.{templateName}"); 29 | 30 | internal static string LoadEmbeddedResource(string resourceName) 31 | { 32 | var resourceStream = ThisAssembly.GetManifestResourceStream(resourceName); 33 | if (resourceStream is null) 34 | { 35 | var existingResources = ThisAssembly.GetManifestResourceNames(); 36 | throw new ArgumentException($"Could not find embedded resource {resourceName}. Available names: {string.Join(", ", existingResources)}"); 37 | } 38 | 39 | using var reader = new StreamReader(resourceStream, Encoding.UTF8); 40 | 41 | return reader.ReadToEnd(); 42 | } 43 | 44 | internal static string LoadAttributeTemplateForEmitting(string resourceName) 45 | { 46 | var resource = LoadEmbeddedResource($"StronglyTypedIds.Templates.Sources.{resourceName}.cs"); 47 | // AutoGeneratedHeader is included directly in the file 48 | // so that it's there if you're directly referencing the attributes package too 49 | return resource 50 | .Replace("#nullable enable", """ 51 | #nullable enable 52 | 53 | #if STRONGLY_TYPED_ID_EMBED_ATTRIBUTES 54 | """) 55 | .Replace("public sealed", "internal sealed") 56 | .Replace("public enum", "internal enum") 57 | + """ 58 | 59 | #endif 60 | """; 61 | } 62 | } -------------------------------------------------------------------------------- /src/StronglyTypedIds/EquatableArray.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Collections.Immutable; 5 | 6 | namespace StronglyTypedIds; 7 | 8 | /// 9 | /// An immutable, equatable array. This is equivalent to but with value equality support. 10 | /// 11 | /// The type of values in the array. 12 | internal readonly struct EquatableArray : IEquatable>, IEnumerable 13 | where T : IEquatable 14 | { 15 | public static readonly EquatableArray Empty = new(Array.Empty()); 16 | 17 | /// 18 | /// The underlying array. 19 | /// 20 | private readonly T[]? _array; 21 | 22 | /// 23 | /// Creates a new instance. 24 | /// 25 | /// The input to wrap. 26 | public EquatableArray(T[] array) 27 | { 28 | _array = array; 29 | } 30 | 31 | /// 32 | public bool Equals(EquatableArray array) 33 | { 34 | return AsSpan().SequenceEqual(array.AsSpan()); 35 | } 36 | 37 | /// 38 | public override bool Equals(object? obj) 39 | { 40 | return obj is EquatableArray array && this.Equals(array); 41 | } 42 | 43 | /// 44 | public override int GetHashCode() 45 | { 46 | if (_array is not T[] array) 47 | { 48 | return 0; 49 | } 50 | 51 | HashCode hashCode = default; 52 | 53 | foreach (T item in array) 54 | { 55 | hashCode.Add(item); 56 | } 57 | 58 | return hashCode.ToHashCode(); 59 | } 60 | 61 | /// 62 | /// Returns a wrapping the current items. 63 | /// 64 | /// A wrapping the current items. 65 | public ReadOnlySpan AsSpan() 66 | { 67 | return _array.AsSpan(); 68 | } 69 | 70 | /// 71 | /// Gets the underlying array if there is one 72 | /// 73 | public T[]? GetArray() => _array; 74 | 75 | /// 76 | IEnumerator IEnumerable.GetEnumerator() 77 | { 78 | return ((IEnumerable)(_array ?? Array.Empty())).GetEnumerator(); 79 | } 80 | 81 | /// 82 | IEnumerator IEnumerable.GetEnumerator() 83 | { 84 | return ((IEnumerable)(_array ?? Array.Empty())).GetEnumerator(); 85 | } 86 | 87 | public int Count => _array?.Length ?? 0; 88 | 89 | /// 90 | /// Checks whether two values are the same. 91 | /// 92 | /// The first value. 93 | /// The second value. 94 | /// Whether and are equal. 95 | public static bool operator ==(EquatableArray left, EquatableArray right) 96 | { 97 | return left.Equals(right); 98 | } 99 | 100 | /// 101 | /// Checks whether two values are not the same. 102 | /// 103 | /// The first value. 104 | /// The second value. 105 | /// Whether and are not equal. 106 | public static bool operator !=(EquatableArray left, EquatableArray right) 107 | { 108 | return !left.Equals(right); 109 | } 110 | } -------------------------------------------------------------------------------- /src/StronglyTypedIds/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | [assembly: System.Runtime.CompilerServices.InternalsVisibleToAttribute("StronglyTypedIds.Tests")] -------------------------------------------------------------------------------- /src/StronglyTypedIds/SourceGenerationHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | 4 | namespace StronglyTypedIds 5 | { 6 | internal static class SourceGenerationHelper 7 | { 8 | public static string CreateId( 9 | string idNamespace, 10 | string idName, 11 | ParentClass? parentClass, 12 | string template, 13 | bool addDefaultAttributes, 14 | bool addGeneratedCodeAttribute, 15 | StringBuilder? sb) 16 | { 17 | if (string.IsNullOrEmpty(idName)) 18 | { 19 | throw new ArgumentException("Value cannot be null or empty.", nameof(idName)); 20 | } 21 | 22 | var hasNamespace = !string.IsNullOrEmpty(idNamespace); 23 | 24 | var parentsCount = 0; 25 | 26 | if (sb is null) 27 | { 28 | sb = new StringBuilder(); 29 | } 30 | else 31 | { 32 | sb.Clear(); 33 | } 34 | 35 | sb.Append(EmbeddedSources.AutoGeneratedHeader); 36 | 37 | if (hasNamespace) 38 | { 39 | sb 40 | .Append("namespace ") 41 | .Append(idNamespace) 42 | .AppendLine(@" 43 | {"); 44 | } 45 | 46 | var hasGenericParent = false; 47 | 48 | while (parentClass is { } parent) 49 | { 50 | sb.Append(" "); 51 | 52 | if (!string.IsNullOrEmpty(parent.Modifiers)) 53 | { 54 | sb.Append(parent.Modifiers).Append(' '); 55 | } 56 | 57 | if (parent.Modifiers.IndexOf("partial", StringComparison.Ordinal) == -1) 58 | { 59 | sb.Append("partial "); 60 | } 61 | 62 | sb 63 | .Append(parent.Keyword) 64 | .Append(' ') 65 | .Append(parent.Name) 66 | .Append(' ') 67 | .Append(parent.Constraints) 68 | .AppendLine(@" 69 | {"); 70 | parentsCount++; 71 | hasGenericParent |= parent.IsGeneric; 72 | parentClass = parent.Child; 73 | } 74 | 75 | if (addDefaultAttributes && !hasGenericParent) 76 | { 77 | sb.AppendLine(" [global::System.ComponentModel.TypeConverter(typeof(PLACEHOLDERIDTypeConverter))]"); 78 | sb.AppendLine(" [global::System.Text.Json.Serialization.JsonConverter(typeof(PLACEHOLDERIDSystemTextJsonConverter))]"); 79 | } 80 | 81 | if (addGeneratedCodeAttribute) 82 | { 83 | sb.AppendLine($""" [global::System.CodeDom.Compiler.GeneratedCodeAttribute("StronglyTypedId", "{Constants.StronglyTypedIdsVersion}")]"""); 84 | } 85 | 86 | sb.AppendLine(template); 87 | 88 | sb.Replace("PLACEHOLDERID", idName); 89 | 90 | for (int i = 0; i < parentsCount; i++) 91 | { 92 | sb.AppendLine(@" }"); 93 | } 94 | 95 | if (hasNamespace) 96 | { 97 | sb.Append('}').AppendLine(); 98 | } 99 | 100 | return sb.ToString(); 101 | } 102 | 103 | internal static string CreateSourceName(StringBuilder sb, string nameSpace, ParentClass? parent, string name, string template) 104 | { 105 | sb.Clear(); 106 | sb.Append(nameSpace).Append('.'); 107 | while (parent is { } p) 108 | { 109 | var s = p.Name 110 | .Replace(" ", "") 111 | .Replace(",", "") 112 | .Replace("<", "__") 113 | .Replace(">", ""); 114 | sb.Append(s).Append('.'); 115 | parent = p.Child; 116 | } 117 | 118 | sb.Append(name); 119 | if (!string.IsNullOrEmpty(template)) 120 | { 121 | sb.Append('.').Append(template); 122 | } 123 | 124 | return sb.Append(".g.cs").ToString(); 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/StronglyTypedIds/StronglyTypedId.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/StronglyTypedIds/StronglyTypedIds.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | false 6 | enable 7 | StronglyTypedId 8 | A source generator for creating strongly-typed IDs by decorating with a [StronglyTypedId] attribute 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/StronglyTypedIds/StronglyTypedIds.csproj.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | False 3 | True 4 | True -------------------------------------------------------------------------------- /src/StronglyTypedIds/StructToGenerate.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.CodeAnalysis; 3 | using Microsoft.CodeAnalysis.Text; 4 | 5 | namespace StronglyTypedIds; 6 | 7 | internal readonly record struct StructToGenerate 8 | { 9 | public StructToGenerate(string name, string nameSpace, Template? template, string[]? templateNames, ParentClass? parent, LocationInfo templateLocation) 10 | { 11 | Name = name; 12 | NameSpace = nameSpace; 13 | TemplateNames = templateNames is null ? EquatableArray.Empty : new EquatableArray(templateNames); 14 | Template = template; 15 | Parent = parent; 16 | TemplateLocation = templateLocation; 17 | } 18 | 19 | public string Name { get; } 20 | public string NameSpace { get; } 21 | public EquatableArray TemplateNames { get; } 22 | public Template? Template { get; } 23 | public ParentClass? Parent { get; } 24 | public LocationInfo? TemplateLocation { get; } 25 | } 26 | 27 | internal sealed record Result(TValue Value, EquatableArray Errors) 28 | where TValue : IEquatable? 29 | { 30 | public static Result<(TValue, bool)> Fail() 31 | => new((default!, false), EquatableArray.Empty); 32 | } 33 | 34 | 35 | internal readonly record struct Defaults 36 | { 37 | public Defaults(Template? template, string[]? templateNames, LocationInfo location, bool hasMultiple) 38 | { 39 | TemplateNames = templateNames is null ? EquatableArray.Empty : new EquatableArray(templateNames); 40 | HasMultiple = hasMultiple; 41 | Template = template; 42 | TemplateLocation = location; 43 | } 44 | 45 | public EquatableArray TemplateNames { get; } 46 | public Template? Template { get; } 47 | public LocationInfo? TemplateLocation { get; } 48 | public bool HasMultiple { get; } 49 | } 50 | 51 | internal record ParentClass(string Modifiers, string Keyword, string Name, string Constraints, ParentClass? Child, bool IsGeneric); 52 | 53 | internal record LocationInfo(string FilePath, TextSpan TextSpan, LinePositionSpan LineSpan) 54 | { 55 | public Location ToLocation() 56 | => Location.Create(FilePath, TextSpan, LineSpan); 57 | 58 | public static LocationInfo CreateFrom(SyntaxNode node) 59 | { 60 | var location = node.GetLocation(); 61 | // assuming that source tree is always non-null here... hopefully that's the case 62 | return new LocationInfo(location.SourceTree!.FilePath, location.SourceSpan, location.GetLineSpan().Span); 63 | } 64 | } -------------------------------------------------------------------------------- /src/StronglyTypedIds/Templates/AutoGeneratedHeader.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by the StronglyTypedId source generator 4 | // 5 | // Changes to this file may cause incorrect behavior and will be lost if 6 | // the code is regenerated. 7 | // 8 | //------------------------------------------------------------------------------ 9 | 10 | #pragma warning disable 1591 // publicly visible type or member must be documented 11 | 12 | #nullable enable 13 | -------------------------------------------------------------------------------- /test/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | net6.0;net7.0;net8.0;net9.0 6 | net48;netcoreapp3.1;$(TargetFrameworks) 7 | false 8 | true 9 | 10 | 11 | 12 | 13 | 14 | 15 | all 16 | runtime; build; native; contentfiles; analyzers; buildtransitive 17 | 18 | 19 | runtime; build; native; contentfiles; analyzers; buildtransitive 20 | all 21 | 22 | 23 | -------------------------------------------------------------------------------- /test/IntegrationLibraries.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /test/StronglyTypedIds.IntegrationTests.ExternalIds/StronglyTypedIds.IntegrationTests.ExternalIds.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /test/StronglyTypedIds.IntegrationTests.ExternalIds/xunit.runner.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://xunit.net/schema/current/xunit.runner.schema.json", 3 | "shadowCopy": false 4 | } -------------------------------------------------------------------------------- /test/StronglyTypedIds.IntegrationTests.Types/Enums.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | using System; 3 | 4 | namespace StronglyTypedIds.IntegrationTests.Types; 5 | 6 | 7 | [StronglyTypedId] 8 | public partial struct DefaultId1 { } 9 | 10 | [StronglyTypedId] 11 | public partial struct DefaultId2 { } 12 | 13 | [StronglyTypedId(Template.Guid)] 14 | public partial struct GuidId1 { } 15 | 16 | [StronglyTypedId("guid-full")] 17 | public partial struct ConvertersGuidId { } 18 | 19 | [StronglyTypedId(Template.Guid, "guid-efcore", "guid-dapper", "guid-newtonsoftjson")] 20 | public partial struct ConvertersGuidId2 { } 21 | 22 | [StronglyTypedId(Template.Guid)] 23 | public partial struct GuidId2 { } 24 | 25 | [StronglyTypedId(Template.Int)] 26 | public partial struct IntId { } 27 | 28 | [StronglyTypedId("int-full")] 29 | public partial struct ConvertersIntId { } 30 | 31 | [StronglyTypedId(Template.Int, "int-efcore", "int-dapper", "int-newtonsoftjson")] 32 | public partial struct ConvertersIntId2 { } 33 | 34 | [StronglyTypedId(Template.Long)] 35 | public partial struct LongId { } 36 | 37 | [StronglyTypedId("long-full")] 38 | public partial struct ConvertersLongId { } 39 | 40 | [StronglyTypedId(Template.Long, "long-efcore", "long-dapper", "long-newtonsoftjson")] 41 | public partial struct ConvertersLongId2 { } 42 | 43 | [StronglyTypedId("newid-full")] 44 | public partial struct NewIdId1 { } 45 | 46 | [StronglyTypedId("newid-full")] 47 | public partial struct NewIdId2 { } 48 | 49 | [StronglyTypedId(Template.String)] 50 | public partial struct StringId { } 51 | 52 | [StronglyTypedId("string-full")] 53 | public partial struct ConvertersStringId { } 54 | 55 | [StronglyTypedId(Template.String, "string-efcore", "string-dapper", "string-newtonsoftjson")] 56 | public partial struct ConvertersStringId2 { } 57 | 58 | [StronglyTypedId("nullablestring-full")] 59 | public partial struct NullableStringId { } 60 | 61 | [StronglyTypedId("simple")] 62 | public partial struct SimpleCustomId { } 63 | 64 | public partial class SomeType where T : new() 65 | { 66 | public partial record struct NestedType 67 | { 68 | public partial struct MoreNesting 69 | { 70 | [StronglyTypedId] 71 | public readonly partial struct VeryNestedId 72 | { 73 | } 74 | } 75 | } 76 | } 77 | 78 | -------------------------------------------------------------------------------- /test/StronglyTypedIds.IntegrationTests.Types/StronglyTypedIds.IntegrationTests.Types.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /test/StronglyTypedIds.IntegrationTests.Types/simple.typedid: -------------------------------------------------------------------------------- 1 | partial struct PLACEHOLDERID 2 | { 3 | public int Value { get; } 4 | 5 | public PLACEHOLDERID(int value) 6 | { 7 | Value = value; 8 | } 9 | } -------------------------------------------------------------------------------- /test/StronglyTypedIds.IntegrationTests/DapperTypeHandlers.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | using Dapper; 3 | using StronglyTypedIds.IntegrationTests.Types; 4 | 5 | namespace StronglyTypedIds.IntegrationTests 6 | { 7 | public static class DapperTypeHandlers 8 | { 9 | [ModuleInitializer] 10 | public static void AddHandlers() 11 | { 12 | SqlMapper.AddTypeHandler(new ConvertersGuidId.DapperTypeHandler()); 13 | SqlMapper.AddTypeHandler(new ConvertersGuidId2.DapperTypeHandler()); 14 | SqlMapper.AddTypeHandler(new ConvertersIntId.DapperTypeHandler()); 15 | SqlMapper.AddTypeHandler(new ConvertersIntId2.DapperTypeHandler()); 16 | SqlMapper.AddTypeHandler(new ConvertersLongId.DapperTypeHandler()); 17 | SqlMapper.AddTypeHandler(new ConvertersLongId2.DapperTypeHandler()); 18 | SqlMapper.AddTypeHandler(new ConvertersStringId.DapperTypeHandler()); 19 | SqlMapper.AddTypeHandler(new ConvertersStringId2.DapperTypeHandler()); 20 | SqlMapper.AddTypeHandler(new NullableStringId.DapperTypeHandler()); 21 | SqlMapper.AddTypeHandler(new NewIdId1.DapperTypeHandler()); 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /test/StronglyTypedIds.IntegrationTests/DefaultIdTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Linq; 4 | using Microsoft.Data.Sqlite; 5 | using Microsoft.EntityFrameworkCore; 6 | using StronglyTypedIds.IntegrationTests.Types; 7 | using Xunit; 8 | using NewtonsoftJsonSerializer = Newtonsoft.Json.JsonConvert; 9 | using SystemTextJsonSerializer = System.Text.Json.JsonSerializer; 10 | 11 | namespace StronglyTypedIds.IntegrationTests 12 | { 13 | public class DefaultIdTests 14 | { 15 | [Fact] 16 | public void SameValuesAreEqual() 17 | { 18 | var id = Guid.NewGuid(); 19 | var foo1 = new DefaultId1(id); 20 | var foo2 = new DefaultId1(id); 21 | 22 | Assert.Equal(foo1, foo2); 23 | } 24 | 25 | [Fact] 26 | public void EmptyValueIsEmpty() 27 | { 28 | Assert.Equal(DefaultId1.Empty.Value, Guid.Empty); 29 | } 30 | 31 | [Fact] 32 | public void DifferentValuesAreUnequal() 33 | { 34 | var foo1 = DefaultId1.New(); 35 | var foo2 = DefaultId1.New(); 36 | 37 | Assert.NotEqual(foo1, foo2); 38 | } 39 | 40 | [Fact] 41 | public void OverloadsWorkCorrectly() 42 | { 43 | var id = Guid.NewGuid(); 44 | var same1 = new DefaultId1(id); 45 | var same2 = new DefaultId1(id); 46 | var different = DefaultId1.New(); 47 | 48 | Assert.True(same1 == same2); 49 | Assert.False(same1 == different); 50 | Assert.False(same1 != same2); 51 | Assert.True(same1 != different); 52 | } 53 | 54 | [Fact] 55 | public void DifferentTypesAreUnequal() 56 | { 57 | var bar = DefaultId2.New(); 58 | var foo = DefaultId1.New(); 59 | 60 | //Assert.NotEqual(bar, foo); // does not compile 61 | Assert.NotEqual((object)bar, (object)foo); 62 | } 63 | 64 | [Fact] 65 | public void CantCreateEmptyGeneratedId1() 66 | { 67 | var foo = new DefaultId1(); 68 | var bar = new DefaultId2(); 69 | 70 | //Assert.NotEqual(bar, foo); // does not compile 71 | Assert.NotEqual((object)bar, (object)foo); 72 | } 73 | 74 | [Theory] 75 | [InlineData("78104553-f1cd-41ec-bcb6-d3a8ff8d994d")] 76 | public void TypeConverter_CanConvertToAndFrom(string value) 77 | { 78 | var converter = TypeDescriptor.GetConverter(typeof(DefaultId1)); 79 | var id = converter.ConvertFrom(value); 80 | Assert.IsType(id); 81 | Assert.Equal(new DefaultId1(Guid.Parse(value)), id); 82 | 83 | var reconverted = converter.ConvertTo(id, value.GetType()); 84 | Assert.Equal(value, reconverted); 85 | } 86 | } 87 | } -------------------------------------------------------------------------------- /test/StronglyTypedIds.IntegrationTests/Enums.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | using System; 3 | 4 | namespace StronglyTypedIds.IntegrationTests.Types; 5 | 6 | 7 | [StronglyTypedId] 8 | internal partial struct DefaultId1 { } 9 | 10 | [StronglyTypedId] 11 | internal partial struct DefaultId2 { } 12 | 13 | [StronglyTypedId(Template.Guid)] 14 | internal partial struct GuidId1 { } 15 | 16 | [StronglyTypedId("guid-full")] 17 | internal partial struct ConvertersGuidId { } 18 | 19 | [StronglyTypedId(Template.Guid, "guid-efcore", "guid-dapper", "guid-newtonsoftjson")] 20 | internal partial struct ConvertersGuidId2 { } 21 | 22 | [StronglyTypedId(Template.Guid)] 23 | internal partial struct GuidId2 { } 24 | 25 | [StronglyTypedId(Template.Int)] 26 | internal partial struct IntId { } 27 | 28 | [StronglyTypedId("int-full")] 29 | internal partial struct ConvertersIntId { } 30 | 31 | [StronglyTypedId(Template.Int, "int-efcore", "int-dapper", "int-newtonsoftjson")] 32 | internal partial struct ConvertersIntId2 { } 33 | 34 | [StronglyTypedId(Template.Long)] 35 | internal partial struct LongId { } 36 | 37 | [StronglyTypedId("long-full")] 38 | internal partial struct ConvertersLongId { } 39 | 40 | [StronglyTypedId(Template.Long, "long-efcore", "long-dapper", "long-newtonsoftjson")] 41 | internal partial struct ConvertersLongId2 { } 42 | 43 | [StronglyTypedId("newid-full")] 44 | internal partial struct NewIdId1 { } 45 | 46 | [StronglyTypedId("newid-full")] 47 | internal partial struct NewIdId2 { } 48 | 49 | [StronglyTypedId(Template.String)] 50 | internal partial struct StringId { } 51 | 52 | [StronglyTypedId("string-full")] 53 | internal partial struct ConvertersStringId { } 54 | 55 | [StronglyTypedId(Template.String, "string-efcore", "string-dapper", "string-newtonsoftjson")] 56 | internal partial struct ConvertersStringId2 { } 57 | 58 | [StronglyTypedId("nullablestring-full")] 59 | internal partial struct NullableStringId { } 60 | 61 | [StronglyTypedId("simple")] 62 | internal partial struct SimpleCustomId { } 63 | 64 | internal partial class SomeType where T : new() 65 | { 66 | internal partial record struct NestedType 67 | { 68 | internal partial struct MoreNesting 69 | { 70 | [StronglyTypedId] 71 | internal readonly partial struct VeryNestedId 72 | { 73 | } 74 | } 75 | } 76 | } 77 | 78 | -------------------------------------------------------------------------------- /test/StronglyTypedIds.IntegrationTests/ModuleInitializerAttribute.cs: -------------------------------------------------------------------------------- 1 | #if !NET5_0_OR_GREATER 2 | // ReSharper disable once CheckNamespace 3 | namespace System.Runtime.CompilerServices 4 | { 5 | // This is a C#9 feature, but requires this attribute to be defined 6 | // Only .NET 5 defines it though, it only exists in .NET 5 7 | [AttributeUsage(AttributeTargets.Method, Inherited = false)] 8 | internal sealed class ModuleInitializerAttribute : Attribute 9 | { 10 | } 11 | } 12 | #endif -------------------------------------------------------------------------------- /test/StronglyTypedIds.IntegrationTests/NestedIdTests.cs: -------------------------------------------------------------------------------- 1 | using StronglyTypedIds.IntegrationTests.Types; 2 | using Xunit; 3 | 4 | namespace StronglyTypedIds.IntegrationTests; 5 | 6 | public class NestedIdTests 7 | { 8 | [Fact] 9 | public void CanCreateNestedId() 10 | { 11 | var id = SomeType.NestedType.MoreNesting.VeryNestedId.New(); 12 | } 13 | } -------------------------------------------------------------------------------- /test/StronglyTypedIds.IntegrationTests/SimpleCustomIdTests.cs: -------------------------------------------------------------------------------- 1 | using StronglyTypedIds.IntegrationTests.Types; 2 | using Xunit; 3 | 4 | namespace StronglyTypedIds.IntegrationTests; 5 | 6 | public class SimpleCustomIdTests 7 | { 8 | [Fact] 9 | public void SameValuesAreEqual() 10 | { 11 | var id = 123; 12 | var foo1 = new SimpleCustomId(id); 13 | var foo2 = new SimpleCustomId(id); 14 | 15 | Assert.Equal(foo1, foo2); 16 | } 17 | 18 | [Fact] 19 | public void DifferentValuesAreUnequal() 20 | { 21 | var foo1 = new SimpleCustomId(1); 22 | var foo2 = new SimpleCustomId(2); 23 | 24 | Assert.NotEqual(foo1, foo2); 25 | } 26 | } -------------------------------------------------------------------------------- /test/StronglyTypedIds.IntegrationTests/StronglyTypedIds.IntegrationTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /test/StronglyTypedIds.IntegrationTests/SystemTextJsonSerializerContext.cs: -------------------------------------------------------------------------------- 1 | #if NET6_0_OR_GREATER 2 | using System.Text.Json; 3 | using System.Text.Json.Serialization; 4 | using StronglyTypedIds.IntegrationTests.Types; 5 | 6 | namespace StronglyTypedIds.IntegrationTests; 7 | 8 | [JsonSerializable(typeof(GuidId1))] 9 | [JsonSerializable(typeof(ConvertersGuidId))] 10 | [JsonSerializable(typeof(ConvertersGuidId2))] 11 | [JsonSerializable(typeof(GuidIdTests.TypeWithDictionaryKeys))] 12 | [JsonSerializable(typeof(GuidIdTests.ToSerialize), TypeInfoPropertyName = "GuidIdTests")] 13 | [JsonSerializable(typeof(IntIdTests.ToSerialize), TypeInfoPropertyName = "IntIdTests")] 14 | [JsonSerializable(typeof(LongIdTests.ToSerialize), TypeInfoPropertyName = "LongIdTests")] 15 | [JsonSerializable(typeof(StringIdTests.ToSerialize), TypeInfoPropertyName = "StringIdTests")] 16 | internal partial class SystemTextJsonSerializerContext : JsonSerializerContext 17 | { 18 | internal static SystemTextJsonSerializerContext Custom 19 | => new(new JsonSerializerOptions 20 | { 21 | Converters = 22 | { 23 | new GuidId1.GuidId1SystemTextJsonConverter(), 24 | new ConvertersGuidId.ConvertersGuidIdSystemTextJsonConverter(), 25 | new ConvertersGuidId2.ConvertersGuidId2SystemTextJsonConverter(), 26 | new ConvertersIntId.ConvertersIntIdSystemTextJsonConverter(), 27 | new ConvertersLongId.ConvertersLongIdSystemTextJsonConverter(), 28 | new ConvertersStringId.ConvertersStringIdSystemTextJsonConverter(), 29 | } 30 | }); 31 | 32 | internal static SystemTextJsonSerializerContext Web 33 | => new(new JsonSerializerOptions() 34 | { 35 | WriteIndented = true, 36 | PropertyNamingPolicy = JsonNamingPolicy.CamelCase, 37 | Converters = 38 | { 39 | new GuidId1.GuidId1SystemTextJsonConverter(), 40 | new ConvertersGuidId.ConvertersGuidIdSystemTextJsonConverter(), 41 | new ConvertersGuidId2.ConvertersGuidId2SystemTextJsonConverter(), 42 | new ConvertersIntId.ConvertersIntIdSystemTextJsonConverter(), 43 | new ConvertersLongId.ConvertersLongIdSystemTextJsonConverter(), 44 | new ConvertersStringId.ConvertersStringIdSystemTextJsonConverter(), 45 | } 46 | }); 47 | } 48 | #endif 49 | -------------------------------------------------------------------------------- /test/StronglyTypedIds.IntegrationTests/simple.typedid: -------------------------------------------------------------------------------- 1 | partial struct PLACEHOLDERID 2 | { 3 | public int Value { get; } 4 | 5 | public PLACEHOLDERID(int value) 6 | { 7 | Value = value; 8 | } 9 | } -------------------------------------------------------------------------------- /test/StronglyTypedIds.IntegrationTests/xunit.runner.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://xunit.net/schema/current/xunit.runner.schema.json", 3 | "shadowCopy": false 4 | } -------------------------------------------------------------------------------- /test/StronglyTypedIds.Nuget.Attributes.IntegrationTests/StronglyTypedIds.Nuget.Attributes.IntegrationTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | STRONGLY_TYPED_ID_EMBED_ATTRIBUTES 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /test/StronglyTypedIds.Nuget.Attributes.IntegrationTests/simple.typedid: -------------------------------------------------------------------------------- 1 | partial struct PLACEHOLDERID 2 | { 3 | public int Value { get; } 4 | 5 | public PLACEHOLDERID(int value) 6 | { 7 | Value = value; 8 | } 9 | } -------------------------------------------------------------------------------- /test/StronglyTypedIds.Nuget.IntegrationTests/StronglyTypedIds.Nuget.IntegrationTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /test/StronglyTypedIds.Nuget.IntegrationTests/simple.typedid: -------------------------------------------------------------------------------- 1 | partial struct PLACEHOLDERID 2 | { 3 | public int Value { get; } 4 | 5 | public PLACEHOLDERID(int value) 6 | { 7 | Value = value; 8 | } 9 | } -------------------------------------------------------------------------------- /test/StronglyTypedIds.Tests/DiagnosticsTests.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using StronglyTypedIds.Diagnostics; 3 | using VerifyTests; 4 | using VerifyXunit; 5 | using Xunit; 6 | 7 | namespace StronglyTypedIds.Tests.Diagnostics; 8 | 9 | [UsesVerify] 10 | public class DiagnosticsTests 11 | { 12 | public const string NoIdGenerationSnapshotName = "NoGeneratedIds"; 13 | 14 | [Theory] 15 | [InlineData("\"\"")] 16 | [InlineData("\" \"")] 17 | public void EmptyTemplate_GivesInvalidTemplateNameDiagnostic_AndDoesntGenerate(string template) 18 | { 19 | var input = $$""" 20 | using StronglyTypedIds; 21 | 22 | [StronglyTypedId({{template}})] 23 | public partial struct MyId {} 24 | """; 25 | var (diagnostics, output) = TestHelpers.GetGeneratedOutput(input, includeAttributes: false); 26 | 27 | Assert.Contains(diagnostics, diagnostic => diagnostic.Id == InvalidTemplateNameDiagnostic.Id); 28 | Assert.Empty(output); 29 | } 30 | 31 | [Fact] 32 | public void MultipleAssemblyAttributes_GivesMultipleAttributeDiagnostic_AndDoesntGenerate() 33 | { 34 | const string input = """ 35 | using StronglyTypedIds; 36 | [assembly:StronglyTypedIdDefaults(Template.Int)] 37 | [assembly:StronglyTypedIdDefaults(Template.Long)] 38 | 39 | [StronglyTypedId] 40 | public partial struct MyId {} 41 | """; 42 | var (diagnostics, output) = TestHelpers.GetGeneratedOutput(input, includeAttributes: false); 43 | 44 | Assert.Contains(diagnostics, diagnostic => diagnostic.Id == MultipleAssemblyAttributeDiagnostic.Id); 45 | Assert.Empty(output); 46 | } 47 | 48 | [Fact] 49 | public void InvalidTemplate_GivesDiagnostic_AndDoesntGenerate() 50 | { 51 | const string input = """ 52 | using StronglyTypedIds; 53 | 54 | [StronglyTypedId("some-template")] 55 | public partial struct MyId {} 56 | """; 57 | var (diagnostics, output) = TestHelpers.GetGeneratedOutput(input, includeAttributes: false); 58 | 59 | Assert.Contains(diagnostics, diagnostic => diagnostic.Id == UnknownTemplateDiagnostic.Id); 60 | Assert.Empty(output); 61 | } 62 | 63 | [Fact] 64 | public void InvalidTemplateInDefaultsAttribute_GivesDiagnostic_AndDoesntGenerate() 65 | { 66 | const string input = """ 67 | using StronglyTypedIds; 68 | [assembly:StronglyTypedIdDefaults("some-template")] 69 | 70 | [StronglyTypedId] 71 | public partial struct MyId {} 72 | """; 73 | var (diagnostics, output) = TestHelpers.GetGeneratedOutput(input, includeAttributes: false); 74 | 75 | Assert.Contains(diagnostics, diagnostic => diagnostic.Id == UnknownTemplateDiagnostic.Id); 76 | 77 | Assert.Empty(output); 78 | } 79 | } -------------------------------------------------------------------------------- /test/StronglyTypedIds.Tests/EmbeddedResourceTests.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using VerifyXunit; 3 | using Xunit; 4 | 5 | namespace StronglyTypedIds.Tests 6 | { 7 | [UsesVerify] 8 | public class EmbeddedResourceTests 9 | { 10 | public static TheoryData EmbeddedResources { get; } = new() 11 | { 12 | "StronglyTypedIdAttribute", 13 | "StronglyTypedIdDefaultsAttribute", 14 | "Template", 15 | }; 16 | 17 | [Theory] 18 | [MemberData(nameof(EmbeddedResources))] 19 | public Task EmittedResourceIsSameAsCompiledResource(string resource) 20 | { 21 | var embedded = EmbeddedSources.LoadAttributeTemplateForEmitting(resource); 22 | 23 | return Verifier.Verify(embedded) 24 | .UseDirectory("Snapshots") 25 | .UseParameters(resource); 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /test/StronglyTypedIds.Tests/EqualityTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | using Microsoft.CodeAnalysis.Text; 3 | using StronglyTypedIds.Diagnostics; 4 | using Xunit; 5 | 6 | namespace StronglyTypedIds.Tests; 7 | 8 | public class EqualityTests 9 | { 10 | private static LocationInfo _templateLocation = new("Some path", new TextSpan(0, 100), new LinePositionSpan(new LinePosition(23, 2), new LinePosition(23, 15))); 11 | 12 | [Fact] 13 | public void ParentClassHasExpectedEqualityBehaviour() 14 | { 15 | var instance1 = GetParentClass(); 16 | var instance2 = GetParentClass(); 17 | 18 | Assert.Equal(instance1, instance2); 19 | Assert.True(instance1.Equals(instance2)); 20 | Assert.True(instance1 == instance2); 21 | 22 | ParentClass GetParentClass() => new(null, "struct", "TestName", "where T : class", null, false); 23 | } 24 | 25 | [Fact] 26 | public void ParentClassWithParentHasExpectedEqualityBehaviour() 27 | { 28 | var instance1 = GetParentClass(); 29 | var instance2 = GetParentClass(); 30 | 31 | Assert.Equal(instance1, instance2); 32 | Assert.True(instance1.Equals(instance2)); 33 | Assert.True(instance1 == instance2); 34 | 35 | ParentClass GetParentClass() => new(null, "struct", "TestName", "where T : class", new ParentClass(null, "class", "b", "", null, false), false); 36 | } 37 | 38 | [Fact] 39 | public void StructToGenerateHasExpectedEqualityBehaviour() 40 | { 41 | var instance1 = GetStruct(); 42 | var instance2 = GetStruct(); 43 | 44 | Assert.Equal(instance1, instance2); 45 | Assert.True(instance1.Equals(instance2)); 46 | Assert.True(instance1 == instance2); 47 | 48 | StructToGenerate GetStruct() => 49 | new( 50 | name: "MyStruct", 51 | nameSpace: "MyNamespace", 52 | template: Template.Guid, 53 | null, 54 | parent: null, 55 | _templateLocation); 56 | } 57 | 58 | [Fact] 59 | public void StructToGenerateWithTemplateAndLocationHasExpectedEqualityBehaviour() 60 | { 61 | var instance1 = GetStruct(); 62 | var instance2 = GetStruct(); 63 | 64 | Assert.Equal(instance1, instance2); 65 | Assert.True(instance1.Equals(instance2)); 66 | Assert.True(instance1 == instance2); 67 | 68 | StructToGenerate GetStruct() => 69 | new( 70 | name: "MyStruct", 71 | nameSpace: "MyNamespace", 72 | template: Template.Int, 73 | templateNames: new[] {"Guid"}, 74 | templateLocation: _templateLocation, 75 | parent: null); 76 | } 77 | 78 | [Fact] 79 | public void StructToGenerateWithParentHasExpectedEqualityBehaviour() 80 | { 81 | var instance1 = GetStruct(); 82 | var instance2 = GetStruct(); 83 | 84 | Assert.Equal(instance1, instance2); 85 | Assert.True(instance1.Equals(instance2)); 86 | Assert.True(instance1 == instance2); 87 | 88 | StructToGenerate GetStruct() 89 | { 90 | return new StructToGenerate( 91 | name: "MyStruct", 92 | nameSpace: "MyNamespace", 93 | template: Template.Guid, 94 | templateNames: null, 95 | parent: new ParentClass(null, "class", "b", "", null, false), 96 | _templateLocation); 97 | } 98 | } 99 | 100 | [Fact] 101 | public void ResultWithoutDiagnosticHasExpectedEqualityBehaviour() 102 | { 103 | var instance1 = GetResult(); 104 | var instance2 = GetResult(); 105 | 106 | Assert.Equal(instance1, instance2); 107 | Assert.True(instance1.Equals(instance2)); 108 | Assert.True(instance1 == instance2); 109 | 110 | static Result<(StructToGenerate, bool)> GetResult() 111 | { 112 | var instance = new StructToGenerate( 113 | name: "MyStruct", 114 | nameSpace: "MyNamespace", 115 | template: Template.Guid, 116 | templateNames: null, 117 | parent: new ParentClass(null, "class", "b", "", null, false), 118 | _templateLocation); 119 | 120 | return new Result<(StructToGenerate, bool)>((instance, true), new EquatableArray()); 121 | } 122 | } 123 | 124 | [Fact] 125 | public void ResultWithDiagnosticHasExpectedEqualityBehaviour() 126 | { 127 | var instance1 = GetResult(); 128 | var instance2 = GetResult(); 129 | 130 | Assert.Equal(instance1, instance2); 131 | Assert.True(instance1.Equals(instance2)); 132 | Assert.True(instance1 == instance2); 133 | 134 | static Result<(StructToGenerate, bool)> GetResult() 135 | { 136 | var instance = new StructToGenerate( 137 | name: "MyStruct", 138 | nameSpace: "MyNamespace", 139 | template: Template.Guid, 140 | templateNames: null, 141 | parent: new ParentClass(null, "class", "b", "", null, false), 142 | _templateLocation); 143 | var diagnostics = new DiagnosticInfo(new DiagnosticDescriptor( 144 | NotPartialDiagnostic.Id, NotPartialDiagnostic.Title, NotPartialDiagnostic.Message, category: Constants.Usage, 145 | defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: true), 146 | Location.Create("somepath.cs", new TextSpan(), new LinePositionSpan(LinePosition.Zero, LinePosition.Zero))); 147 | 148 | var errors = new EquatableArray(new[] { diagnostics }); 149 | return new Result<(StructToGenerate, bool)>((instance, true), errors); 150 | } 151 | } 152 | 153 | [Fact] 154 | public void EquatableArray_PrimitiveComparison() 155 | { 156 | int[] val1 = [1, 2, 3, 4, 5]; 157 | int[] val2 = [1, 2, 3, 4, 5]; 158 | 159 | var arr1 = new EquatableArray(val1); 160 | var arr2 = new EquatableArray(val2); 161 | 162 | Assert.True(arr1.Equals(arr2)); 163 | } 164 | 165 | [Fact] 166 | public void EquatableArray_RecordComparison() 167 | { 168 | Record[] val1 = [new(1), new(2), new(3), new(4), new(5)]; 169 | Record[] val2 = [new(1), new(2), new(3), new(4), new(5)]; 170 | 171 | var arr1 = new EquatableArray(val1); 172 | var arr2 = new EquatableArray(val2); 173 | 174 | Assert.True(arr1.Equals(arr2)); 175 | } 176 | 177 | [Fact] 178 | public void EquatableArray_NestedEquatableArrayComparison() 179 | { 180 | EquatableArray[] val1 = [new([1]), new([2]), new([3]), new([4]), new([5])]; 181 | EquatableArray[] val2 = [new([1]), new([2]), new([3]), new([4]), new([5])]; 182 | 183 | var arr1 = new EquatableArray>(val1); 184 | var arr2 = new EquatableArray>(val2); 185 | 186 | Assert.True(arr1.Equals(arr2)); 187 | } 188 | 189 | [Fact] 190 | public void EquatableArray_BoxedNestedEquatableArrayComparison() 191 | { 192 | EquatableArray[] val1 = [new([1]), new([2]), new([3]), new([4]), new([5])]; 193 | EquatableArray[] val2 = [new([1]), new([2]), new([3]), new([4]), new([5])]; 194 | 195 | object arr1 = new EquatableArray>(val1); 196 | var arr2 = new EquatableArray>(val2); 197 | 198 | Assert.True(arr1.Equals(arr2)); 199 | Assert.True(arr2.Equals(arr1)); 200 | } 201 | 202 | public record Record 203 | { 204 | public Record(int value) 205 | { 206 | Value = value; 207 | } 208 | 209 | public int Value { get; } 210 | } 211 | } -------------------------------------------------------------------------------- /test/StronglyTypedIds.Tests/Snapshots/EmbeddedResourceTests.EmittedResourceIsSameAsCompiledResource_resource=StronglyTypedIdAttribute.verified.txt: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by the StronglyTypedId source generator 4 | // 5 | // Changes to this file may cause incorrect behavior and will be lost if 6 | // the code is regenerated. 7 | // 8 | //------------------------------------------------------------------------------ 9 | 10 | #pragma warning disable 1591 // publicly visible type or member must be documented 11 | 12 | #nullable enable 13 | 14 | #if STRONGLY_TYPED_ID_EMBED_ATTRIBUTES 15 | 16 | namespace StronglyTypedIds 17 | { 18 | /// 19 | /// Place on partial structs to make the type a strongly-typed ID 20 | /// 21 | [global::System.AttributeUsage(global::System.AttributeTargets.Struct, Inherited = false, AllowMultiple = false)] 22 | [global::System.Diagnostics.Conditional("STRONGLY_TYPED_ID_USAGES")] 23 | internal sealed class StronglyTypedIdAttribute : global::System.Attribute 24 | { 25 | /// 26 | /// Make the struct a strongly typed ID. 27 | /// 28 | /// The built-in template to use to generate the ID. 29 | /// The names of additional custom templates to use to generate the ID. 30 | /// Templates must be added to the project using the format NAME.typedid, 31 | /// where NAME is the name of the template passed in . 32 | /// 33 | public StronglyTypedIdAttribute(global::StronglyTypedIds.Template template, params string[] templateNames) 34 | { 35 | } 36 | 37 | /// 38 | /// Make the struct a strongly typed ID. 39 | /// 40 | /// The names of the template to use to generate the ID. 41 | /// Templates must be added to the project using the format NAME.typedid, 42 | /// where NAME is the name of the template passed in . 43 | /// If no templates are provided, the default value is used, as specified by 44 | /// , or alternatively the 45 | /// template. 46 | /// 47 | public StronglyTypedIdAttribute(params string[] templateNames) 48 | { 49 | } 50 | } 51 | } 52 | #endif -------------------------------------------------------------------------------- /test/StronglyTypedIds.Tests/Snapshots/EmbeddedResourceTests.EmittedResourceIsSameAsCompiledResource_resource=StronglyTypedIdDefaultsAttribute.verified.txt: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by the StronglyTypedId source generator 4 | // 5 | // Changes to this file may cause incorrect behavior and will be lost if 6 | // the code is regenerated. 7 | // 8 | //------------------------------------------------------------------------------ 9 | 10 | #pragma warning disable 1591 // publicly visible type or member must be documented 11 | 12 | #nullable enable 13 | 14 | #if STRONGLY_TYPED_ID_EMBED_ATTRIBUTES 15 | 16 | namespace StronglyTypedIds 17 | { 18 | /// 19 | /// Used to control the default strongly typed ID values. Apply to an assembly using 20 | /// [assembly:StronglyTypedIdDefaults(Template.Int)] for example 21 | /// 22 | [global::System.AttributeUsage(global::System.AttributeTargets.Assembly, Inherited = false, AllowMultiple = false)] 23 | [global::System.Diagnostics.Conditional("STRONGLY_TYPED_ID_USAGES")] 24 | internal sealed class StronglyTypedIdDefaultsAttribute : global::System.Attribute 25 | { 26 | /// 27 | /// Set the default template to use for strongly typed IDs 28 | /// 29 | /// The built-in template to use to generate the ID. 30 | /// The names of additional custom templates to use to generate the ID. 31 | /// Templates must be added to the project using the format NAME.typedid, 32 | /// where NAME is the name of the template passed in . 33 | /// 34 | public StronglyTypedIdDefaultsAttribute(global::StronglyTypedIds.Template template, params string[] templateNames) 35 | { 36 | } 37 | 38 | /// 39 | /// Set the default template to use for strongly typed IDs 40 | /// 41 | /// The name of the template to use to generate the ID. 42 | /// Templates must be added to the project using the format NAME.typedid, 43 | /// where NAME is the name of the template passed in . 44 | /// 45 | public StronglyTypedIdDefaultsAttribute(string templateName) 46 | { 47 | } 48 | 49 | /// 50 | /// Set the default template to use for strongly typed IDs 51 | /// 52 | /// The name of the template to use to generate the ID. 53 | /// Templates must be added to the project using the format NAME.typedid, 54 | /// where NAME is the name of the template passed in . 55 | /// 56 | public StronglyTypedIdDefaultsAttribute(string templateName, params string[] templateNames) 57 | { 58 | } 59 | } 60 | } 61 | #endif -------------------------------------------------------------------------------- /test/StronglyTypedIds.Tests/Snapshots/EmbeddedResourceTests.EmittedResourceIsSameAsCompiledResource_resource=Template.verified.txt: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by the StronglyTypedId source generator 4 | // 5 | // Changes to this file may cause incorrect behavior and will be lost if 6 | // the code is regenerated. 7 | // 8 | //------------------------------------------------------------------------------ 9 | 10 | #pragma warning disable 1591 // publicly visible type or member must be documented 11 | 12 | #nullable enable 13 | 14 | #if STRONGLY_TYPED_ID_EMBED_ATTRIBUTES 15 | 16 | using System; 17 | 18 | namespace StronglyTypedIds 19 | { 20 | /// 21 | /// The built-in template to use to generate the strongly-typed ID 22 | /// 23 | internal enum Template 24 | { 25 | Guid, 26 | Int, 27 | String, 28 | Long, 29 | } 30 | } 31 | #endif -------------------------------------------------------------------------------- /test/StronglyTypedIds.Tests/Snapshots/StronglyTypedIdGeneratorTests.CanGenerateDefaultIdInGlobalNamespace.verified.txt: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by the StronglyTypedId source generator 4 | // 5 | // Changes to this file may cause incorrect behavior and will be lost if 6 | // the code is regenerated. 7 | // 8 | //------------------------------------------------------------------------------ 9 | 10 | #pragma warning disable 1591 // publicly visible type or member must be documented 11 | 12 | #nullable enable 13 | [global::System.ComponentModel.TypeConverter(typeof(MyIdTypeConverter))] 14 | [global::System.Text.Json.Serialization.JsonConverter(typeof(MyIdSystemTextJsonConverter))] 15 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("StronglyTypedId", "1.0.0-beta08")] 16 | partial struct MyId : 17 | #if NET6_0_OR_GREATER 18 | global::System.ISpanFormattable, 19 | #endif 20 | #if NET7_0_OR_GREATER 21 | global::System.IParsable, global::System.ISpanParsable, 22 | #endif 23 | #if NET8_0_OR_GREATER 24 | global::System.IUtf8SpanFormattable, 25 | #endif 26 | global::System.IComparable, global::System.IEquatable, global::System.IFormattable 27 | { 28 | public global::System.Guid Value { get; } 29 | 30 | public MyId(global::System.Guid value) 31 | { 32 | Value = value; 33 | } 34 | 35 | public static MyId New() => new MyId(global::System.Guid.NewGuid()); 36 | public static readonly MyId Empty = new MyId(global::System.Guid.Empty); 37 | 38 | /// 39 | public bool Equals(MyId other) => this.Value.Equals(other.Value); 40 | public override bool Equals(object? obj) 41 | { 42 | if (ReferenceEquals(null, obj)) return false; 43 | return obj is MyId other && Equals(other); 44 | } 45 | 46 | public override int GetHashCode() => Value.GetHashCode(); 47 | 48 | public override string ToString() => Value.ToString(); 49 | 50 | public static bool operator ==(MyId a, MyId b) => a.Equals(b); 51 | public static bool operator !=(MyId a, MyId b) => !(a == b); 52 | public static bool operator > (MyId a, MyId b) => a.CompareTo(b) > 0; 53 | public static bool operator < (MyId a, MyId b) => a.CompareTo(b) < 0; 54 | public static bool operator >= (MyId a, MyId b) => a.CompareTo(b) >= 0; 55 | public static bool operator <= (MyId a, MyId b) => a.CompareTo(b) <= 0; 56 | 57 | /// 58 | public int CompareTo(MyId other) => Value.CompareTo(other.Value); 59 | 60 | public partial class MyIdTypeConverter : global::System.ComponentModel.TypeConverter 61 | { 62 | public override bool CanConvertFrom(global::System.ComponentModel.ITypeDescriptorContext? context, global::System.Type sourceType) 63 | { 64 | return sourceType == typeof(global::System.Guid) || sourceType == typeof(string) || base.CanConvertFrom(context, sourceType); 65 | } 66 | 67 | public override object? ConvertFrom(global::System.ComponentModel.ITypeDescriptorContext? context, global::System.Globalization.CultureInfo? culture, object value) 68 | { 69 | return value switch 70 | { 71 | global::System.Guid guidValue => new MyId(guidValue), 72 | string stringValue when !string.IsNullOrEmpty(stringValue) && global::System.Guid.TryParse(stringValue, out var result) => new MyId(result), 73 | _ => base.ConvertFrom(context, culture, value), 74 | }; 75 | } 76 | 77 | public override bool CanConvertTo(global::System.ComponentModel.ITypeDescriptorContext? context, global::System.Type? sourceType) 78 | { 79 | return sourceType == typeof(global::System.Guid) || sourceType == typeof(string) || base.CanConvertTo(context, sourceType); 80 | } 81 | 82 | public override object? ConvertTo(global::System.ComponentModel.ITypeDescriptorContext? context, global::System.Globalization.CultureInfo? culture, object? value, global::System.Type destinationType) 83 | { 84 | if (value is MyId idValue) 85 | { 86 | if (destinationType == typeof(global::System.Guid)) 87 | { 88 | return idValue.Value; 89 | } 90 | 91 | if (destinationType == typeof(string)) 92 | { 93 | return idValue.Value.ToString(); 94 | } 95 | } 96 | 97 | return base.ConvertTo(context, culture, value, destinationType); 98 | } 99 | } 100 | 101 | public partial class MyIdSystemTextJsonConverter : global::System.Text.Json.Serialization.JsonConverter 102 | { 103 | public override MyId Read(ref global::System.Text.Json.Utf8JsonReader reader, global::System.Type typeToConvert, global::System.Text.Json.JsonSerializerOptions options) 104 | => new (reader.GetGuid()); 105 | 106 | public override void Write(global::System.Text.Json.Utf8JsonWriter writer, MyId value, global::System.Text.Json.JsonSerializerOptions options) 107 | => writer.WriteStringValue(value.Value); 108 | 109 | #if NET6_0_OR_GREATER 110 | public override MyId ReadAsPropertyName(ref global::System.Text.Json.Utf8JsonReader reader, global::System.Type typeToConvert, global::System.Text.Json.JsonSerializerOptions options) 111 | => new(global::System.Guid.Parse(reader.GetString() ?? throw new global::System.FormatException("The string for the MyId property was null"))); 112 | 113 | public override void WriteAsPropertyName(global::System.Text.Json.Utf8JsonWriter writer, MyId value, global::System.Text.Json.JsonSerializerOptions options) 114 | => writer.WritePropertyName(value.Value.ToString()); 115 | #endif 116 | } 117 | 118 | public static MyId Parse(string input) 119 | => new(global::System.Guid.Parse(input)); 120 | 121 | #if NET7_0_OR_GREATER 122 | /// 123 | public static MyId Parse(string input, global::System.IFormatProvider? provider) 124 | => new(global::System.Guid.Parse(input, provider)); 125 | 126 | /// 127 | public static bool TryParse( 128 | [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] string? input, 129 | global::System.IFormatProvider? provider, 130 | out MyId result) 131 | { 132 | if (input is null) 133 | { 134 | result = default; 135 | return false; 136 | } 137 | 138 | if (global::System.Guid.TryParse(input, provider, out var guid)) 139 | { 140 | result = new(guid); 141 | return true; 142 | } 143 | else 144 | { 145 | result = default; 146 | return false; 147 | } 148 | } 149 | #endif 150 | 151 | /// 152 | public string ToString( 153 | #if NET7_0_OR_GREATER 154 | [global::System.Diagnostics.CodeAnalysis.StringSyntax(global::System.Diagnostics.CodeAnalysis.StringSyntaxAttribute.GuidFormat)] 155 | #endif 156 | string? format, 157 | global::System.IFormatProvider? formatProvider) 158 | => Value.ToString(format, formatProvider); 159 | 160 | #if NETCOREAPP2_1_OR_GREATER 161 | public static MyId Parse(global::System.ReadOnlySpan input) 162 | => new(global::System.Guid.Parse(input)); 163 | #endif 164 | 165 | #if NET6_0_OR_GREATER 166 | #if NET7_0_OR_GREATER 167 | /// 168 | #endif 169 | public static MyId Parse(global::System.ReadOnlySpan input, global::System.IFormatProvider? provider) 170 | #if NET7_0_OR_GREATER 171 | => new(global::System.Guid.Parse(input, provider)); 172 | #else 173 | => new(global::System.Guid.Parse(input)); 174 | #endif 175 | 176 | #if NET7_0_OR_GREATER 177 | /// 178 | #endif 179 | public static bool TryParse(global::System.ReadOnlySpan input, global::System.IFormatProvider? provider, out MyId result) 180 | { 181 | #if NET7_0_OR_GREATER 182 | if (global::System.Guid.TryParse(input, provider, out var guid)) 183 | #else 184 | if (global::System.Guid.TryParse(input, out var guid)) 185 | #endif 186 | { 187 | result = new(guid); 188 | return true; 189 | } 190 | else 191 | { 192 | result = default; 193 | return false; 194 | } 195 | } 196 | 197 | /// 198 | public bool TryFormat( 199 | global::System.Span destination, 200 | out int charsWritten, 201 | #if NET7_0_OR_GREATER 202 | [global::System.Diagnostics.CodeAnalysis.StringSyntax(global::System.Diagnostics.CodeAnalysis.StringSyntaxAttribute.GuidFormat)] 203 | #endif 204 | global::System.ReadOnlySpan format, 205 | global::System.IFormatProvider? provider) 206 | => Value.TryFormat(destination, out charsWritten, format); 207 | 208 | /// 209 | public bool TryFormat( 210 | global::System.Span destination, 211 | out int charsWritten, 212 | #if NET7_0_OR_GREATER 213 | [global::System.Diagnostics.CodeAnalysis.StringSyntax(global::System.Diagnostics.CodeAnalysis.StringSyntaxAttribute.GuidFormat)] 214 | #endif 215 | global::System.ReadOnlySpan format = default) 216 | => Value.TryFormat(destination, out charsWritten, format); 217 | #endif 218 | #if NET8_0_OR_GREATER 219 | /// 220 | public bool TryFormat( 221 | global::System.Span utf8Destination, 222 | out int bytesWritten, 223 | [global::System.Diagnostics.CodeAnalysis.StringSyntax(global::System.Diagnostics.CodeAnalysis.StringSyntaxAttribute.GuidFormat)] 224 | global::System.ReadOnlySpan format, 225 | global::System.IFormatProvider? provider) 226 | => Value.TryFormat(utf8Destination, out bytesWritten, format); 227 | #endif 228 | } 229 | -------------------------------------------------------------------------------- /test/StronglyTypedIds.Tests/Snapshots/StronglyTypedIdGeneratorTests.CanGenerateGenericVeryNestedIdInFileScopeNamespace.verified.txt: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by the StronglyTypedId source generator 4 | // 5 | // Changes to this file may cause incorrect behavior and will be lost if 6 | // the code is regenerated. 7 | // 8 | //------------------------------------------------------------------------------ 9 | 10 | #pragma warning disable 1591 // publicly visible type or member must be documented 11 | 12 | #nullable enable 13 | namespace SomeNamespace 14 | { 15 | public partial class ParentClass where T: new() 16 | { 17 | internal partial record InnerClass 18 | { 19 | public partial struct InnerStruct 20 | { 21 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("StronglyTypedId", "1.0.0-beta08")] 22 | partial struct MyId : 23 | #if NET6_0_OR_GREATER 24 | global::System.ISpanFormattable, 25 | #endif 26 | #if NET7_0_OR_GREATER 27 | global::System.IParsable, global::System.ISpanParsable, 28 | #endif 29 | #if NET8_0_OR_GREATER 30 | global::System.IUtf8SpanFormattable, 31 | #endif 32 | global::System.IComparable, global::System.IEquatable, global::System.IFormattable 33 | { 34 | public global::System.Guid Value { get; } 35 | 36 | public MyId(global::System.Guid value) 37 | { 38 | Value = value; 39 | } 40 | 41 | public static MyId New() => new MyId(global::System.Guid.NewGuid()); 42 | public static readonly MyId Empty = new MyId(global::System.Guid.Empty); 43 | 44 | /// 45 | public bool Equals(MyId other) => this.Value.Equals(other.Value); 46 | public override bool Equals(object? obj) 47 | { 48 | if (ReferenceEquals(null, obj)) return false; 49 | return obj is MyId other && Equals(other); 50 | } 51 | 52 | public override int GetHashCode() => Value.GetHashCode(); 53 | 54 | public override string ToString() => Value.ToString(); 55 | 56 | public static bool operator ==(MyId a, MyId b) => a.Equals(b); 57 | public static bool operator !=(MyId a, MyId b) => !(a == b); 58 | public static bool operator > (MyId a, MyId b) => a.CompareTo(b) > 0; 59 | public static bool operator < (MyId a, MyId b) => a.CompareTo(b) < 0; 60 | public static bool operator >= (MyId a, MyId b) => a.CompareTo(b) >= 0; 61 | public static bool operator <= (MyId a, MyId b) => a.CompareTo(b) <= 0; 62 | 63 | /// 64 | public int CompareTo(MyId other) => Value.CompareTo(other.Value); 65 | 66 | public partial class MyIdTypeConverter : global::System.ComponentModel.TypeConverter 67 | { 68 | public override bool CanConvertFrom(global::System.ComponentModel.ITypeDescriptorContext? context, global::System.Type sourceType) 69 | { 70 | return sourceType == typeof(global::System.Guid) || sourceType == typeof(string) || base.CanConvertFrom(context, sourceType); 71 | } 72 | 73 | public override object? ConvertFrom(global::System.ComponentModel.ITypeDescriptorContext? context, global::System.Globalization.CultureInfo? culture, object value) 74 | { 75 | return value switch 76 | { 77 | global::System.Guid guidValue => new MyId(guidValue), 78 | string stringValue when !string.IsNullOrEmpty(stringValue) && global::System.Guid.TryParse(stringValue, out var result) => new MyId(result), 79 | _ => base.ConvertFrom(context, culture, value), 80 | }; 81 | } 82 | 83 | public override bool CanConvertTo(global::System.ComponentModel.ITypeDescriptorContext? context, global::System.Type? sourceType) 84 | { 85 | return sourceType == typeof(global::System.Guid) || sourceType == typeof(string) || base.CanConvertTo(context, sourceType); 86 | } 87 | 88 | public override object? ConvertTo(global::System.ComponentModel.ITypeDescriptorContext? context, global::System.Globalization.CultureInfo? culture, object? value, global::System.Type destinationType) 89 | { 90 | if (value is MyId idValue) 91 | { 92 | if (destinationType == typeof(global::System.Guid)) 93 | { 94 | return idValue.Value; 95 | } 96 | 97 | if (destinationType == typeof(string)) 98 | { 99 | return idValue.Value.ToString(); 100 | } 101 | } 102 | 103 | return base.ConvertTo(context, culture, value, destinationType); 104 | } 105 | } 106 | 107 | public partial class MyIdSystemTextJsonConverter : global::System.Text.Json.Serialization.JsonConverter 108 | { 109 | public override MyId Read(ref global::System.Text.Json.Utf8JsonReader reader, global::System.Type typeToConvert, global::System.Text.Json.JsonSerializerOptions options) 110 | => new (reader.GetGuid()); 111 | 112 | public override void Write(global::System.Text.Json.Utf8JsonWriter writer, MyId value, global::System.Text.Json.JsonSerializerOptions options) 113 | => writer.WriteStringValue(value.Value); 114 | 115 | #if NET6_0_OR_GREATER 116 | public override MyId ReadAsPropertyName(ref global::System.Text.Json.Utf8JsonReader reader, global::System.Type typeToConvert, global::System.Text.Json.JsonSerializerOptions options) 117 | => new(global::System.Guid.Parse(reader.GetString() ?? throw new global::System.FormatException("The string for the MyId property was null"))); 118 | 119 | public override void WriteAsPropertyName(global::System.Text.Json.Utf8JsonWriter writer, MyId value, global::System.Text.Json.JsonSerializerOptions options) 120 | => writer.WritePropertyName(value.Value.ToString()); 121 | #endif 122 | } 123 | 124 | public static MyId Parse(string input) 125 | => new(global::System.Guid.Parse(input)); 126 | 127 | #if NET7_0_OR_GREATER 128 | /// 129 | public static MyId Parse(string input, global::System.IFormatProvider? provider) 130 | => new(global::System.Guid.Parse(input, provider)); 131 | 132 | /// 133 | public static bool TryParse( 134 | [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] string? input, 135 | global::System.IFormatProvider? provider, 136 | out MyId result) 137 | { 138 | if (input is null) 139 | { 140 | result = default; 141 | return false; 142 | } 143 | 144 | if (global::System.Guid.TryParse(input, provider, out var guid)) 145 | { 146 | result = new(guid); 147 | return true; 148 | } 149 | else 150 | { 151 | result = default; 152 | return false; 153 | } 154 | } 155 | #endif 156 | 157 | /// 158 | public string ToString( 159 | #if NET7_0_OR_GREATER 160 | [global::System.Diagnostics.CodeAnalysis.StringSyntax(global::System.Diagnostics.CodeAnalysis.StringSyntaxAttribute.GuidFormat)] 161 | #endif 162 | string? format, 163 | global::System.IFormatProvider? formatProvider) 164 | => Value.ToString(format, formatProvider); 165 | 166 | #if NETCOREAPP2_1_OR_GREATER 167 | public static MyId Parse(global::System.ReadOnlySpan input) 168 | => new(global::System.Guid.Parse(input)); 169 | #endif 170 | 171 | #if NET6_0_OR_GREATER 172 | #if NET7_0_OR_GREATER 173 | /// 174 | #endif 175 | public static MyId Parse(global::System.ReadOnlySpan input, global::System.IFormatProvider? provider) 176 | #if NET7_0_OR_GREATER 177 | => new(global::System.Guid.Parse(input, provider)); 178 | #else 179 | => new(global::System.Guid.Parse(input)); 180 | #endif 181 | 182 | #if NET7_0_OR_GREATER 183 | /// 184 | #endif 185 | public static bool TryParse(global::System.ReadOnlySpan input, global::System.IFormatProvider? provider, out MyId result) 186 | { 187 | #if NET7_0_OR_GREATER 188 | if (global::System.Guid.TryParse(input, provider, out var guid)) 189 | #else 190 | if (global::System.Guid.TryParse(input, out var guid)) 191 | #endif 192 | { 193 | result = new(guid); 194 | return true; 195 | } 196 | else 197 | { 198 | result = default; 199 | return false; 200 | } 201 | } 202 | 203 | /// 204 | public bool TryFormat( 205 | global::System.Span destination, 206 | out int charsWritten, 207 | #if NET7_0_OR_GREATER 208 | [global::System.Diagnostics.CodeAnalysis.StringSyntax(global::System.Diagnostics.CodeAnalysis.StringSyntaxAttribute.GuidFormat)] 209 | #endif 210 | global::System.ReadOnlySpan format, 211 | global::System.IFormatProvider? provider) 212 | => Value.TryFormat(destination, out charsWritten, format); 213 | 214 | /// 215 | public bool TryFormat( 216 | global::System.Span destination, 217 | out int charsWritten, 218 | #if NET7_0_OR_GREATER 219 | [global::System.Diagnostics.CodeAnalysis.StringSyntax(global::System.Diagnostics.CodeAnalysis.StringSyntaxAttribute.GuidFormat)] 220 | #endif 221 | global::System.ReadOnlySpan format = default) 222 | => Value.TryFormat(destination, out charsWritten, format); 223 | #endif 224 | #if NET8_0_OR_GREATER 225 | /// 226 | public bool TryFormat( 227 | global::System.Span utf8Destination, 228 | out int bytesWritten, 229 | [global::System.Diagnostics.CodeAnalysis.StringSyntax(global::System.Diagnostics.CodeAnalysis.StringSyntaxAttribute.GuidFormat)] 230 | global::System.ReadOnlySpan format, 231 | global::System.IFormatProvider? provider) 232 | => Value.TryFormat(utf8Destination, out bytesWritten, format); 233 | #endif 234 | } 235 | } 236 | } 237 | } 238 | } 239 | -------------------------------------------------------------------------------- /test/StronglyTypedIds.Tests/Snapshots/StronglyTypedIdGeneratorTests.CanGenerateIdInFileScopedNamespace.verified.txt: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by the StronglyTypedId source generator 4 | // 5 | // Changes to this file may cause incorrect behavior and will be lost if 6 | // the code is regenerated. 7 | // 8 | //------------------------------------------------------------------------------ 9 | 10 | #pragma warning disable 1591 // publicly visible type or member must be documented 11 | 12 | #nullable enable 13 | namespace SomeNamespace 14 | { 15 | [global::System.ComponentModel.TypeConverter(typeof(MyIdTypeConverter))] 16 | [global::System.Text.Json.Serialization.JsonConverter(typeof(MyIdSystemTextJsonConverter))] 17 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("StronglyTypedId", "1.0.0-beta08")] 18 | partial struct MyId : 19 | #if NET6_0_OR_GREATER 20 | global::System.ISpanFormattable, 21 | #endif 22 | #if NET7_0_OR_GREATER 23 | global::System.IParsable, global::System.ISpanParsable, 24 | #endif 25 | #if NET8_0_OR_GREATER 26 | global::System.IUtf8SpanFormattable, 27 | #endif 28 | global::System.IComparable, global::System.IEquatable, global::System.IFormattable 29 | { 30 | public global::System.Guid Value { get; } 31 | 32 | public MyId(global::System.Guid value) 33 | { 34 | Value = value; 35 | } 36 | 37 | public static MyId New() => new MyId(global::System.Guid.NewGuid()); 38 | public static readonly MyId Empty = new MyId(global::System.Guid.Empty); 39 | 40 | /// 41 | public bool Equals(MyId other) => this.Value.Equals(other.Value); 42 | public override bool Equals(object? obj) 43 | { 44 | if (ReferenceEquals(null, obj)) return false; 45 | return obj is MyId other && Equals(other); 46 | } 47 | 48 | public override int GetHashCode() => Value.GetHashCode(); 49 | 50 | public override string ToString() => Value.ToString(); 51 | 52 | public static bool operator ==(MyId a, MyId b) => a.Equals(b); 53 | public static bool operator !=(MyId a, MyId b) => !(a == b); 54 | public static bool operator > (MyId a, MyId b) => a.CompareTo(b) > 0; 55 | public static bool operator < (MyId a, MyId b) => a.CompareTo(b) < 0; 56 | public static bool operator >= (MyId a, MyId b) => a.CompareTo(b) >= 0; 57 | public static bool operator <= (MyId a, MyId b) => a.CompareTo(b) <= 0; 58 | 59 | /// 60 | public int CompareTo(MyId other) => Value.CompareTo(other.Value); 61 | 62 | public partial class MyIdTypeConverter : global::System.ComponentModel.TypeConverter 63 | { 64 | public override bool CanConvertFrom(global::System.ComponentModel.ITypeDescriptorContext? context, global::System.Type sourceType) 65 | { 66 | return sourceType == typeof(global::System.Guid) || sourceType == typeof(string) || base.CanConvertFrom(context, sourceType); 67 | } 68 | 69 | public override object? ConvertFrom(global::System.ComponentModel.ITypeDescriptorContext? context, global::System.Globalization.CultureInfo? culture, object value) 70 | { 71 | return value switch 72 | { 73 | global::System.Guid guidValue => new MyId(guidValue), 74 | string stringValue when !string.IsNullOrEmpty(stringValue) && global::System.Guid.TryParse(stringValue, out var result) => new MyId(result), 75 | _ => base.ConvertFrom(context, culture, value), 76 | }; 77 | } 78 | 79 | public override bool CanConvertTo(global::System.ComponentModel.ITypeDescriptorContext? context, global::System.Type? sourceType) 80 | { 81 | return sourceType == typeof(global::System.Guid) || sourceType == typeof(string) || base.CanConvertTo(context, sourceType); 82 | } 83 | 84 | public override object? ConvertTo(global::System.ComponentModel.ITypeDescriptorContext? context, global::System.Globalization.CultureInfo? culture, object? value, global::System.Type destinationType) 85 | { 86 | if (value is MyId idValue) 87 | { 88 | if (destinationType == typeof(global::System.Guid)) 89 | { 90 | return idValue.Value; 91 | } 92 | 93 | if (destinationType == typeof(string)) 94 | { 95 | return idValue.Value.ToString(); 96 | } 97 | } 98 | 99 | return base.ConvertTo(context, culture, value, destinationType); 100 | } 101 | } 102 | 103 | public partial class MyIdSystemTextJsonConverter : global::System.Text.Json.Serialization.JsonConverter 104 | { 105 | public override MyId Read(ref global::System.Text.Json.Utf8JsonReader reader, global::System.Type typeToConvert, global::System.Text.Json.JsonSerializerOptions options) 106 | => new (reader.GetGuid()); 107 | 108 | public override void Write(global::System.Text.Json.Utf8JsonWriter writer, MyId value, global::System.Text.Json.JsonSerializerOptions options) 109 | => writer.WriteStringValue(value.Value); 110 | 111 | #if NET6_0_OR_GREATER 112 | public override MyId ReadAsPropertyName(ref global::System.Text.Json.Utf8JsonReader reader, global::System.Type typeToConvert, global::System.Text.Json.JsonSerializerOptions options) 113 | => new(global::System.Guid.Parse(reader.GetString() ?? throw new global::System.FormatException("The string for the MyId property was null"))); 114 | 115 | public override void WriteAsPropertyName(global::System.Text.Json.Utf8JsonWriter writer, MyId value, global::System.Text.Json.JsonSerializerOptions options) 116 | => writer.WritePropertyName(value.Value.ToString()); 117 | #endif 118 | } 119 | 120 | public static MyId Parse(string input) 121 | => new(global::System.Guid.Parse(input)); 122 | 123 | #if NET7_0_OR_GREATER 124 | /// 125 | public static MyId Parse(string input, global::System.IFormatProvider? provider) 126 | => new(global::System.Guid.Parse(input, provider)); 127 | 128 | /// 129 | public static bool TryParse( 130 | [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] string? input, 131 | global::System.IFormatProvider? provider, 132 | out MyId result) 133 | { 134 | if (input is null) 135 | { 136 | result = default; 137 | return false; 138 | } 139 | 140 | if (global::System.Guid.TryParse(input, provider, out var guid)) 141 | { 142 | result = new(guid); 143 | return true; 144 | } 145 | else 146 | { 147 | result = default; 148 | return false; 149 | } 150 | } 151 | #endif 152 | 153 | /// 154 | public string ToString( 155 | #if NET7_0_OR_GREATER 156 | [global::System.Diagnostics.CodeAnalysis.StringSyntax(global::System.Diagnostics.CodeAnalysis.StringSyntaxAttribute.GuidFormat)] 157 | #endif 158 | string? format, 159 | global::System.IFormatProvider? formatProvider) 160 | => Value.ToString(format, formatProvider); 161 | 162 | #if NETCOREAPP2_1_OR_GREATER 163 | public static MyId Parse(global::System.ReadOnlySpan input) 164 | => new(global::System.Guid.Parse(input)); 165 | #endif 166 | 167 | #if NET6_0_OR_GREATER 168 | #if NET7_0_OR_GREATER 169 | /// 170 | #endif 171 | public static MyId Parse(global::System.ReadOnlySpan input, global::System.IFormatProvider? provider) 172 | #if NET7_0_OR_GREATER 173 | => new(global::System.Guid.Parse(input, provider)); 174 | #else 175 | => new(global::System.Guid.Parse(input)); 176 | #endif 177 | 178 | #if NET7_0_OR_GREATER 179 | /// 180 | #endif 181 | public static bool TryParse(global::System.ReadOnlySpan input, global::System.IFormatProvider? provider, out MyId result) 182 | { 183 | #if NET7_0_OR_GREATER 184 | if (global::System.Guid.TryParse(input, provider, out var guid)) 185 | #else 186 | if (global::System.Guid.TryParse(input, out var guid)) 187 | #endif 188 | { 189 | result = new(guid); 190 | return true; 191 | } 192 | else 193 | { 194 | result = default; 195 | return false; 196 | } 197 | } 198 | 199 | /// 200 | public bool TryFormat( 201 | global::System.Span destination, 202 | out int charsWritten, 203 | #if NET7_0_OR_GREATER 204 | [global::System.Diagnostics.CodeAnalysis.StringSyntax(global::System.Diagnostics.CodeAnalysis.StringSyntaxAttribute.GuidFormat)] 205 | #endif 206 | global::System.ReadOnlySpan format, 207 | global::System.IFormatProvider? provider) 208 | => Value.TryFormat(destination, out charsWritten, format); 209 | 210 | /// 211 | public bool TryFormat( 212 | global::System.Span destination, 213 | out int charsWritten, 214 | #if NET7_0_OR_GREATER 215 | [global::System.Diagnostics.CodeAnalysis.StringSyntax(global::System.Diagnostics.CodeAnalysis.StringSyntaxAttribute.GuidFormat)] 216 | #endif 217 | global::System.ReadOnlySpan format = default) 218 | => Value.TryFormat(destination, out charsWritten, format); 219 | #endif 220 | #if NET8_0_OR_GREATER 221 | /// 222 | public bool TryFormat( 223 | global::System.Span utf8Destination, 224 | out int bytesWritten, 225 | [global::System.Diagnostics.CodeAnalysis.StringSyntax(global::System.Diagnostics.CodeAnalysis.StringSyntaxAttribute.GuidFormat)] 226 | global::System.ReadOnlySpan format, 227 | global::System.IFormatProvider? provider) 228 | => Value.TryFormat(utf8Destination, out bytesWritten, format); 229 | #endif 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /test/StronglyTypedIds.Tests/Snapshots/StronglyTypedIdGeneratorTests.CanGenerateIdInNamespace.verified.txt: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by the StronglyTypedId source generator 4 | // 5 | // Changes to this file may cause incorrect behavior and will be lost if 6 | // the code is regenerated. 7 | // 8 | //------------------------------------------------------------------------------ 9 | 10 | #pragma warning disable 1591 // publicly visible type or member must be documented 11 | 12 | #nullable enable 13 | namespace SomeNamespace 14 | { 15 | [global::System.ComponentModel.TypeConverter(typeof(MyIdTypeConverter))] 16 | [global::System.Text.Json.Serialization.JsonConverter(typeof(MyIdSystemTextJsonConverter))] 17 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("StronglyTypedId", "1.0.0-beta08")] 18 | partial struct MyId : 19 | #if NET6_0_OR_GREATER 20 | global::System.ISpanFormattable, 21 | #endif 22 | #if NET7_0_OR_GREATER 23 | global::System.IParsable, global::System.ISpanParsable, 24 | #endif 25 | #if NET8_0_OR_GREATER 26 | global::System.IUtf8SpanFormattable, 27 | #endif 28 | global::System.IComparable, global::System.IEquatable, global::System.IFormattable 29 | { 30 | public global::System.Guid Value { get; } 31 | 32 | public MyId(global::System.Guid value) 33 | { 34 | Value = value; 35 | } 36 | 37 | public static MyId New() => new MyId(global::System.Guid.NewGuid()); 38 | public static readonly MyId Empty = new MyId(global::System.Guid.Empty); 39 | 40 | /// 41 | public bool Equals(MyId other) => this.Value.Equals(other.Value); 42 | public override bool Equals(object? obj) 43 | { 44 | if (ReferenceEquals(null, obj)) return false; 45 | return obj is MyId other && Equals(other); 46 | } 47 | 48 | public override int GetHashCode() => Value.GetHashCode(); 49 | 50 | public override string ToString() => Value.ToString(); 51 | 52 | public static bool operator ==(MyId a, MyId b) => a.Equals(b); 53 | public static bool operator !=(MyId a, MyId b) => !(a == b); 54 | public static bool operator > (MyId a, MyId b) => a.CompareTo(b) > 0; 55 | public static bool operator < (MyId a, MyId b) => a.CompareTo(b) < 0; 56 | public static bool operator >= (MyId a, MyId b) => a.CompareTo(b) >= 0; 57 | public static bool operator <= (MyId a, MyId b) => a.CompareTo(b) <= 0; 58 | 59 | /// 60 | public int CompareTo(MyId other) => Value.CompareTo(other.Value); 61 | 62 | public partial class MyIdTypeConverter : global::System.ComponentModel.TypeConverter 63 | { 64 | public override bool CanConvertFrom(global::System.ComponentModel.ITypeDescriptorContext? context, global::System.Type sourceType) 65 | { 66 | return sourceType == typeof(global::System.Guid) || sourceType == typeof(string) || base.CanConvertFrom(context, sourceType); 67 | } 68 | 69 | public override object? ConvertFrom(global::System.ComponentModel.ITypeDescriptorContext? context, global::System.Globalization.CultureInfo? culture, object value) 70 | { 71 | return value switch 72 | { 73 | global::System.Guid guidValue => new MyId(guidValue), 74 | string stringValue when !string.IsNullOrEmpty(stringValue) && global::System.Guid.TryParse(stringValue, out var result) => new MyId(result), 75 | _ => base.ConvertFrom(context, culture, value), 76 | }; 77 | } 78 | 79 | public override bool CanConvertTo(global::System.ComponentModel.ITypeDescriptorContext? context, global::System.Type? sourceType) 80 | { 81 | return sourceType == typeof(global::System.Guid) || sourceType == typeof(string) || base.CanConvertTo(context, sourceType); 82 | } 83 | 84 | public override object? ConvertTo(global::System.ComponentModel.ITypeDescriptorContext? context, global::System.Globalization.CultureInfo? culture, object? value, global::System.Type destinationType) 85 | { 86 | if (value is MyId idValue) 87 | { 88 | if (destinationType == typeof(global::System.Guid)) 89 | { 90 | return idValue.Value; 91 | } 92 | 93 | if (destinationType == typeof(string)) 94 | { 95 | return idValue.Value.ToString(); 96 | } 97 | } 98 | 99 | return base.ConvertTo(context, culture, value, destinationType); 100 | } 101 | } 102 | 103 | public partial class MyIdSystemTextJsonConverter : global::System.Text.Json.Serialization.JsonConverter 104 | { 105 | public override MyId Read(ref global::System.Text.Json.Utf8JsonReader reader, global::System.Type typeToConvert, global::System.Text.Json.JsonSerializerOptions options) 106 | => new (reader.GetGuid()); 107 | 108 | public override void Write(global::System.Text.Json.Utf8JsonWriter writer, MyId value, global::System.Text.Json.JsonSerializerOptions options) 109 | => writer.WriteStringValue(value.Value); 110 | 111 | #if NET6_0_OR_GREATER 112 | public override MyId ReadAsPropertyName(ref global::System.Text.Json.Utf8JsonReader reader, global::System.Type typeToConvert, global::System.Text.Json.JsonSerializerOptions options) 113 | => new(global::System.Guid.Parse(reader.GetString() ?? throw new global::System.FormatException("The string for the MyId property was null"))); 114 | 115 | public override void WriteAsPropertyName(global::System.Text.Json.Utf8JsonWriter writer, MyId value, global::System.Text.Json.JsonSerializerOptions options) 116 | => writer.WritePropertyName(value.Value.ToString()); 117 | #endif 118 | } 119 | 120 | public static MyId Parse(string input) 121 | => new(global::System.Guid.Parse(input)); 122 | 123 | #if NET7_0_OR_GREATER 124 | /// 125 | public static MyId Parse(string input, global::System.IFormatProvider? provider) 126 | => new(global::System.Guid.Parse(input, provider)); 127 | 128 | /// 129 | public static bool TryParse( 130 | [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] string? input, 131 | global::System.IFormatProvider? provider, 132 | out MyId result) 133 | { 134 | if (input is null) 135 | { 136 | result = default; 137 | return false; 138 | } 139 | 140 | if (global::System.Guid.TryParse(input, provider, out var guid)) 141 | { 142 | result = new(guid); 143 | return true; 144 | } 145 | else 146 | { 147 | result = default; 148 | return false; 149 | } 150 | } 151 | #endif 152 | 153 | /// 154 | public string ToString( 155 | #if NET7_0_OR_GREATER 156 | [global::System.Diagnostics.CodeAnalysis.StringSyntax(global::System.Diagnostics.CodeAnalysis.StringSyntaxAttribute.GuidFormat)] 157 | #endif 158 | string? format, 159 | global::System.IFormatProvider? formatProvider) 160 | => Value.ToString(format, formatProvider); 161 | 162 | #if NETCOREAPP2_1_OR_GREATER 163 | public static MyId Parse(global::System.ReadOnlySpan input) 164 | => new(global::System.Guid.Parse(input)); 165 | #endif 166 | 167 | #if NET6_0_OR_GREATER 168 | #if NET7_0_OR_GREATER 169 | /// 170 | #endif 171 | public static MyId Parse(global::System.ReadOnlySpan input, global::System.IFormatProvider? provider) 172 | #if NET7_0_OR_GREATER 173 | => new(global::System.Guid.Parse(input, provider)); 174 | #else 175 | => new(global::System.Guid.Parse(input)); 176 | #endif 177 | 178 | #if NET7_0_OR_GREATER 179 | /// 180 | #endif 181 | public static bool TryParse(global::System.ReadOnlySpan input, global::System.IFormatProvider? provider, out MyId result) 182 | { 183 | #if NET7_0_OR_GREATER 184 | if (global::System.Guid.TryParse(input, provider, out var guid)) 185 | #else 186 | if (global::System.Guid.TryParse(input, out var guid)) 187 | #endif 188 | { 189 | result = new(guid); 190 | return true; 191 | } 192 | else 193 | { 194 | result = default; 195 | return false; 196 | } 197 | } 198 | 199 | /// 200 | public bool TryFormat( 201 | global::System.Span destination, 202 | out int charsWritten, 203 | #if NET7_0_OR_GREATER 204 | [global::System.Diagnostics.CodeAnalysis.StringSyntax(global::System.Diagnostics.CodeAnalysis.StringSyntaxAttribute.GuidFormat)] 205 | #endif 206 | global::System.ReadOnlySpan format, 207 | global::System.IFormatProvider? provider) 208 | => Value.TryFormat(destination, out charsWritten, format); 209 | 210 | /// 211 | public bool TryFormat( 212 | global::System.Span destination, 213 | out int charsWritten, 214 | #if NET7_0_OR_GREATER 215 | [global::System.Diagnostics.CodeAnalysis.StringSyntax(global::System.Diagnostics.CodeAnalysis.StringSyntaxAttribute.GuidFormat)] 216 | #endif 217 | global::System.ReadOnlySpan format = default) 218 | => Value.TryFormat(destination, out charsWritten, format); 219 | #endif 220 | #if NET8_0_OR_GREATER 221 | /// 222 | public bool TryFormat( 223 | global::System.Span utf8Destination, 224 | out int bytesWritten, 225 | [global::System.Diagnostics.CodeAnalysis.StringSyntax(global::System.Diagnostics.CodeAnalysis.StringSyntaxAttribute.GuidFormat)] 226 | global::System.ReadOnlySpan format, 227 | global::System.IFormatProvider? provider) 228 | => Value.TryFormat(utf8Destination, out bytesWritten, format); 229 | #endif 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /test/StronglyTypedIds.Tests/Snapshots/StronglyTypedIdGeneratorTests.CanGenerateMultipleTemplatesWithoutBuiltIn.verified.txt: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by the StronglyTypedId source generator 4 | // 5 | // Changes to this file may cause incorrect behavior and will be lost if 6 | // the code is regenerated. 7 | // 8 | //------------------------------------------------------------------------------ 9 | 10 | #pragma warning disable 1591 // publicly visible type or member must be documented 11 | 12 | #nullable enable 13 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("StronglyTypedId", "1.0.0-beta08")] 14 | partial struct MyId 15 | { 16 | public partial class EfCoreValueConverter : global::Microsoft.EntityFrameworkCore.Storage.ValueConversion.ValueConverter 17 | { 18 | public EfCoreValueConverter() : this(null) { } 19 | public EfCoreValueConverter(global::Microsoft.EntityFrameworkCore.Storage.ValueConversion.ConverterMappingHints? mappingHints = null) 20 | : base( 21 | id => id.Value, 22 | value => new MyId(value), 23 | mappingHints 24 | ) { } 25 | } 26 | } 27 | 28 | //------------------------------------------------------------------------------ 29 | // 30 | // This code was generated by the StronglyTypedId source generator 31 | // 32 | // Changes to this file may cause incorrect behavior and will be lost if 33 | // the code is regenerated. 34 | // 35 | //------------------------------------------------------------------------------ 36 | 37 | #pragma warning disable 1591 // publicly visible type or member must be documented 38 | 39 | #nullable enable 40 | partial struct MyId 41 | { 42 | public partial class DapperTypeHandler : global::Dapper.SqlMapper.TypeHandler 43 | { 44 | public override void SetValue(global::System.Data.IDbDataParameter parameter, MyId value) 45 | { 46 | parameter.Value = value.Value; 47 | } 48 | 49 | public override MyId Parse(object value) 50 | { 51 | return value switch 52 | { 53 | global::System.Guid guidValue => new MyId(guidValue), 54 | string stringValue when !string.IsNullOrEmpty(stringValue) && global::System.Guid.TryParse(stringValue, out var result) => new MyId(result), 55 | _ => throw new global::System.InvalidCastException($"Unable to cast object of type {value.GetType()} to MyId"), 56 | }; 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /test/StronglyTypedIds.Tests/SourceGenerationHelperSnapshotTests.cs: -------------------------------------------------------------------------------- 1 | // using System; 2 | // using System.Collections.Generic; 3 | // using System.Linq; 4 | // using System.Threading.Tasks; 5 | // using VerifyXunit; 6 | // using Xunit; 7 | // 8 | // namespace StronglyTypedIds.Tests 9 | // { 10 | // [UsesVerify] 11 | // public class SourceGenerationHelperSnapshotTests 12 | // { 13 | // private const string IdNamespace = "Some.Namespace"; 14 | // 15 | // [Theory] 16 | // [InlineData(null)] 17 | // [InlineData("")] 18 | // public void ThrowsWhenClassNameIsNullOrEmpty(string idName) 19 | // { 20 | // Assert.Throws(() => SourceGenerationHelper.CreateId( 21 | // idName: idName, 22 | // idNamespace: IdNamespace, 23 | // parentClass: null, 24 | // template: "N/A" 25 | // )); 26 | // } 27 | // 28 | // [Theory] 29 | // [MemberData(nameof(Parameters))] 30 | // public Task GeneratesIdCorrectly( 31 | // StronglyTypedIdBackingType type, 32 | // StronglyTypedIdConverter c, 33 | // StronglyTypedIdImplementations i) 34 | // { 35 | // const string idName = "MyTestId"; 36 | // var result = SourceGenerationHelper.CreateId( 37 | // idName: idName, 38 | // idNamespace: "", 39 | // parentClass: null, 40 | // converters: c, 41 | // backingType: type, 42 | // implementations: i 43 | // ); 44 | // 45 | // return Verifier.Verify(result) 46 | // .UseDirectory("Snapshots") 47 | // .UseParameters(type, c, i); 48 | // } 49 | // 50 | // [Theory] 51 | // [MemberData(nameof(BackingTypes))] 52 | // public Task GeneratesFullIdCorrectly(StronglyTypedIdBackingType type) 53 | // { 54 | // // combine them all 55 | // var combinedConverter = EnumHelper.AllConverters(includeDefault: false) 56 | // .Aggregate(StronglyTypedIdConverter.None, (prev, current) => prev | current); 57 | // 58 | // // combine them all 59 | // var combinedImplementation = EnumHelper.AllImplementations(includeDefault: false) 60 | // .Aggregate(StronglyTypedIdImplementations.None, (prev, current) => prev | current); 61 | // 62 | // const string idName = "MyTestId"; 63 | // 64 | // var result = SourceGenerationHelper.CreateId( 65 | // idName: idName, 66 | // idNamespace: "", 67 | // parentClass: null, 68 | // converters: combinedConverter, 69 | // backingType: type, 70 | // implementations: combinedImplementation 71 | // ); 72 | // 73 | // return Verifier.Verify(result) 74 | // .UseDirectory("Snapshots") 75 | // .UseParameters(type); 76 | // } 77 | // 78 | // [Theory] 79 | // [InlineData(0)] 80 | // [InlineData(1)] 81 | // [InlineData(2)] 82 | // public Task GeneratesIdWithNestedClassCorrectly(int nestedClassCount) 83 | // { 84 | // var parent = new ParentClass("record", "InnerMost", string.Empty, child: null); 85 | // for (int i = 0; i < nestedClassCount; i++) 86 | // { 87 | // parent = new ParentClass("class", "OuterLayer" + i, string.Empty, parent); 88 | // } 89 | // 90 | // var result = SourceGenerationHelper.CreateId( 91 | // idName: "MyTestId", 92 | // idNamespace: "MyTestNamespace", 93 | // parentClass: parent, 94 | // converters: StronglyTypedIdConverter.SystemTextJson, 95 | // backingType: StronglyTypedIdBackingType.Guid, 96 | // implementations: StronglyTypedIdImplementations.None 97 | // ); 98 | // 99 | // return Verifier.Verify(result) 100 | // .UseDirectory("Snapshots") 101 | // .UseParameters(nestedClassCount); 102 | // } 103 | // 104 | // public static IEnumerable BackingTypes() 105 | // => EnumHelper.AllBackingTypes(includeDefault: false) 106 | // .Select(x => new object[] { x }); 107 | // 108 | // public static IEnumerable Parameters() 109 | // { 110 | // foreach (var backingType in EnumHelper.AllBackingTypes(includeDefault: false)) 111 | // { 112 | // // All individual convert types 113 | // foreach (var converter in EnumHelper.AllConverters(includeDefault: false)) 114 | // { 115 | // yield return new object[] { backingType, converter, StronglyTypedIdImplementations.None }; 116 | // } 117 | // 118 | // // All individual implementations 119 | // foreach (var implementation in EnumHelper.AllImplementations(includeDefault: false)) 120 | // { 121 | // if (implementation is StronglyTypedIdImplementations.None) 122 | // { 123 | // // We've already covered this one in the previous loop 124 | // continue; 125 | // } 126 | // yield return new object[] { backingType, StronglyTypedIdConverter.None, implementation }; 127 | // } 128 | // } 129 | // } 130 | // } 131 | // } -------------------------------------------------------------------------------- /test/StronglyTypedIds.Tests/StronglyTypedIds.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /test/StronglyTypedIds.Tests/TestHelpers.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.Immutable; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Reflection; 7 | using System.Text; 8 | using System.Threading; 9 | using Microsoft.CodeAnalysis; 10 | using Microsoft.CodeAnalysis.CSharp; 11 | using Microsoft.CodeAnalysis.Text; 12 | 13 | namespace StronglyTypedIds.Tests 14 | { 15 | internal static class TestHelpers 16 | { 17 | public static (ImmutableArray Diagnostics, string Output) GetGeneratedOutput(string source, bool includeAttributes = true) 18 | where T : IIncrementalGenerator, new() 19 | { 20 | var syntaxTree = CSharpSyntaxTree.ParseText(source); 21 | var references = AppDomain.CurrentDomain.GetAssemblies() 22 | .Where(_ => !_.IsDynamic && !string.IsNullOrWhiteSpace(_.Location)) 23 | .Select(_ => MetadataReference.CreateFromFile(_.Location)) 24 | .Concat(new[] {MetadataReference.CreateFromFile(typeof(T).Assembly.Location)}); 25 | var compilation = CSharpCompilation.Create( 26 | "generator", 27 | new[] {syntaxTree}, 28 | references, 29 | new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); 30 | 31 | var originalTreeCount = compilation.SyntaxTrees.Length; 32 | 33 | CSharpGeneratorDriver 34 | .Create(new T()) 35 | .AddAdditionalTexts(LoadEmbeddedResourcesAsAdditionalText()) 36 | .RunGeneratorsAndUpdateCompilation(compilation, out var outputCompilation, out var diagnostics); 37 | 38 | // If we don't want the attributes, we try to get all the potential resource streams and exclude them 39 | var countsToExclude = includeAttributes 40 | ? originalTreeCount 41 | : originalTreeCount + typeof(T).Assembly.GetManifestResourceNames().Count(x => x.StartsWith("StronglyTypedIds.Templates.Sources.")); 42 | 43 | var output = string.Join("\n", outputCompilation.SyntaxTrees.Skip(countsToExclude).Select(t => t.ToString())); 44 | 45 | return (diagnostics, output); 46 | } 47 | 48 | private static ImmutableArray LoadEmbeddedResourcesAsAdditionalText() 49 | { 50 | var texts = ImmutableArray.CreateBuilder(); 51 | var assembly = typeof(TestHelpers).Assembly; 52 | 53 | texts.AddRange(assembly.GetManifestResourceNames() 54 | .Select(name => new TestAdditionalText( 55 | text: LoadEmbeddedResource(assembly, name), 56 | path: Path.Combine("c:", "test", "Templates", Path.GetExtension(Path.GetFileNameWithoutExtension(name)).Substring(1) + ".typedid")))); 57 | 58 | return texts.ToImmutable(); 59 | } 60 | private static string LoadEmbeddedResource(Assembly assembly, string resourceName) 61 | { 62 | var resourceStream = assembly.GetManifestResourceStream(resourceName); 63 | if (resourceStream is null) 64 | { 65 | var existingResources = assembly.GetManifestResourceNames(); 66 | throw new ArgumentException($"Could not find embedded resource {resourceName}. Available names: {string.Join(", ", existingResources)}"); 67 | } 68 | 69 | using var reader = new StreamReader(resourceStream, Encoding.UTF8); 70 | 71 | return reader.ReadToEnd(); 72 | } 73 | 74 | private sealed class TestAdditionalText : AdditionalText 75 | { 76 | private readonly SourceText _text; 77 | 78 | public TestAdditionalText(string path, SourceText text) 79 | { 80 | Path = path; 81 | _text = text; 82 | } 83 | 84 | public TestAdditionalText(string text = "", Encoding encoding = null, string path = "dummy", SourceHashAlgorithm checksumAlgorithm = SourceHashAlgorithm.Sha1) 85 | : this(path, new StringText(text, encoding, checksumAlgorithm: checksumAlgorithm)) 86 | { 87 | } 88 | 89 | public override string Path { get; } 90 | 91 | public override SourceText GetText(CancellationToken cancellationToken = default) => _text; 92 | } 93 | 94 | /// 95 | /// Implementation of SourceText based on a input 96 | /// 97 | internal sealed class StringText : SourceText 98 | { 99 | private readonly string _source; 100 | private readonly Encoding _encodingOpt; 101 | 102 | internal StringText( 103 | string source, 104 | Encoding encodingOpt, 105 | ImmutableArray checksum = default(ImmutableArray), 106 | SourceHashAlgorithm checksumAlgorithm = SourceHashAlgorithm.Sha1) 107 | : base(checksum, checksumAlgorithm) 108 | { 109 | _source = source; 110 | _encodingOpt = encodingOpt; 111 | } 112 | 113 | public override Encoding Encoding => _encodingOpt; 114 | 115 | /// 116 | /// Underlying string which is the source of this instance 117 | /// 118 | public string Source => _source; 119 | 120 | /// 121 | /// The length of the text represented by . 122 | /// 123 | public override int Length => _source.Length; 124 | 125 | /// 126 | /// Returns a character at given position. 127 | /// 128 | /// The position to get the character from. 129 | /// The character. 130 | /// When position is negative or 131 | /// greater than . 132 | public override char this[int position] 133 | { 134 | get 135 | { 136 | // NOTE: we are not validating position here as that would not 137 | // add any value to the range check that string accessor performs anyways. 138 | 139 | return _source[position]; 140 | } 141 | } 142 | 143 | /// 144 | /// Provides a string representation of the StringText located within given span. 145 | /// 146 | /// When given span is outside of the text range. 147 | public override string ToString(TextSpan span) 148 | { 149 | if (span.End > this.Source.Length) 150 | { 151 | throw new ArgumentOutOfRangeException(nameof(span)); 152 | } 153 | 154 | if (span.Start == 0 && span.Length == this.Length) 155 | { 156 | return this.Source; 157 | } 158 | 159 | return this.Source.Substring(span.Start, span.Length); 160 | } 161 | 162 | public override void CopyTo(int sourceIndex, char[] destination, int destinationIndex, int count) 163 | { 164 | this.Source.CopyTo(sourceIndex, destination, destinationIndex, count); 165 | } 166 | 167 | public override void Write(TextWriter textWriter, TextSpan span, CancellationToken cancellationToken = default(CancellationToken)) 168 | { 169 | if (span.Start == 0 && span.End == this.Length) 170 | { 171 | textWriter.Write(this.Source); 172 | } 173 | else 174 | { 175 | base.Write(textWriter, span, cancellationToken); 176 | } 177 | } 178 | } 179 | } 180 | } -------------------------------------------------------------------------------- /version.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 1.0.0 4 | beta08 5 | $(VersionPrefix) 6 | $(VersionPrefix)-$(VersionSuffix) 7 | 8 | 9 | --------------------------------------------------------------------------------