diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index bd72530..7daba32 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -35,28 +35,36 @@ jobs:
with:
dotnet-version: '8.0.x'
+ - name: Get tag for current commit
+ id: get_tag
+ # Check for tags when triggered by main branch push (with tag) or direct tag push
+ # Can't use github.ref_name because it's "main" when pushing branch+tag together
+ if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/')
+ uses: olegtarasov/get-tag@v2.1.4
+
+ - name: Set Version Profile
+ run: |
+ if [[ "${{ steps.get_tag.outputs.tag }}" != "" ]]; then
+ echo "VERSION_PROFILE=release" >> $GITHUB_ENV
+ else
+ echo "VERSION_PROFILE=ci" >> $GITHUB_ENV
+ fi
+
- name: Restore dependencies
run: dotnet restore $SOLUTION
- name: Build
- run: dotnet build $SOLUTION --configuration $BUILD_CONFIG --no-restore
+ run: dotnet build $SOLUTION --configuration $BUILD_CONFIG --no-restore -p:GeneratePackageOnBuild=false -p:VersionProfile=${{ env.VERSION_PROFILE }}
- name: Run tests
- run: dotnet test --configuration $BUILD_CONFIG --no-restore --no-build --verbosity normal
+ run: dotnet test --configuration $BUILD_CONFIG --no-restore --no-build --verbosity normal -p:VersionProfile=${{ env.VERSION_PROFILE }}
- name: Pack NuGet packages
run: |
- dotnet pack $SOLUTION --configuration $BUILD_CONFIG --no-build --output ./artifacts
+ dotnet pack $SOLUTION --configuration $BUILD_CONFIG --no-build --output ./artifacts -p:VersionProfile=${{ env.VERSION_PROFILE }}
echo "=== Packages created ==="
ls -la ./artifacts/
- - name: Get tag for current commit
- id: get_tag
- # Check for tags when triggered by main branch push (with tag) or direct tag push
- # Can't use github.ref_name because it's "main" when pushing branch+tag together
- if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/')
- uses: olegtarasov/get-tag@v2.1.4
-
- name: Extract release notes from CHANGELOG.md
if: steps.get_tag.outputs.tag != ''
id: extract_notes
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index e714f69..31d3859 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -21,8 +21,8 @@ jobs:
- name: Restore, Build, and Pack
run: |
dotnet restore *.sln
- dotnet build *.sln --configuration Release --no-restore
- dotnet pack *.sln --configuration Release --no-build --output ./artifacts
+ dotnet build *.sln --configuration Release --no-restore -p:ContinuousIntegrationBuild=true -p:GeneratePackageOnBuild=false -p:VersionProfile=release
+ dotnet pack *.sln --configuration Release --no-build --output ./artifacts -p:ContinuousIntegrationBuild=true -p:VersionProfile=release
- name: Publish to NuGet
run: dotnet nuget push ./artifacts/*.nupkg --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json --skip-duplicate
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 95c6458..0318d6d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,12 +2,24 @@
All notable changes to this project will be documented in this file.
-The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
-## [[1.1.0]] - 2025-06-29
+## [1.1.1] - 2025-08-03
+
+### Added
+- Included a count of the matching embedded resources in the generated class doc-comments
+
+### Changed
+- Converted to use Datacute.IncrementalGeneratorExtensions and Datacute.AdditionalTextConstantGenerator
+
+### Fixed
+- Fixed nullable reference type support for older .NET versions that don't support nullable annotations
+- Removed file-scoped namespace from attribute for better compatibility with older C# versions
+
+## [1.1.0] - 2025-06-29
### Added
- Support for nested classes and generics
@@ -21,17 +33,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Resolves issue #4 (performance improvements)
- Resolves issue #3 (nested classes and generics support)
-## [[1.0.0]] - 2024-10-28
+## [1.0.0] - 2024-10-28
### Added
- First stable release
-## [[0.0.1-alpha.6b]] - 2024-09-24
+## [0.0.1-alpha.6b] - 2024-09-24
### Fixed
- Corrected the published package (but the new version broke the doc links)
-## [[0.0.1-alpha.6]] - 2024-09-24
+## [0.0.1-alpha.6] - 2024-09-24
### Added
- More documentation
@@ -39,12 +51,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
- Removed windows specific paths from tests
-## [[0.0.1-alpha.5]] - 2024-09-22
+## [0.0.1-alpha.5] - 2024-09-22
### Fixed
- Included doc-comments of the EmbeddedResourceProperties attribute in the package
-## [[0.0.1-alpha.4]] - 2024-09-15
+## [0.0.1-alpha.4] - 2024-09-15
### Added
- Read method in each class
@@ -53,7 +65,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
- Moved the attribute to its own dll
-## [[0.0.1-alpha.3]] - 2024-09-14
+## [0.0.1-alpha.3] - 2024-09-14
### Added
- Included `EmbeddedResourcePropertyExample` project in github repository
@@ -61,23 +73,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed
- Removing doc duplication
-## [[0.0.1-alpha.2]] - 2024-09-13
+## [0.0.1-alpha.2] - 2024-09-13
### Added
- `[Conditional]` attribute to restrict inclusion of the usage of the `EmbeddedResourcePropertiesAttribute` in the output
-## [[0.0.1-alpha]] - 2024-09-09
+## [0.0.1-alpha] - 2024-09-09
### Added
- Support for overriding `ReadEmbeddedResourceValue` partial method
-[Unreleased]: https://github.com/datacute/EmbeddedResourcePropertyGenerator/compare/1.1.0...HEAD
-[1.1.0]: https://github.com/datacute/EmbeddedResourcePropertyGenerator/compare/1.0.0...1.1.0
-[1.0.0]: https://github.com/datacute/EmbeddedResourcePropertyGenerator/compare/0.0.1-alpha.6b...1.0.0
-[0.0.1-alpha.6b]: https://github.com/datacute/EmbeddedResourcePropertyGenerator/compare/0.0.1-alpha.6...0.0.1-alpha.6b
-[0.0.1-alpha.6]: https://github.com/datacute/EmbeddedResourcePropertyGenerator/compare/0.0.1-alpha.5...0.0.1-alpha.6
-[0.0.1-alpha.5]: https://github.com/datacute/EmbeddedResourcePropertyGenerator/compare/0.0.1-alpha.4...0.0.1-alpha.5
-[0.0.1-alpha.4]: https://github.com/datacute/EmbeddedResourcePropertyGenerator/compare/0.0.1-alpha.3...0.0.1-alpha.4
-[0.0.1-alpha.3]: https://github.com/datacute/EmbeddedResourcePropertyGenerator/compare/0.0.1-alpha.2...0.0.1-alpha.3
-[0.0.1-alpha.2]: https://github.com/datacute/EmbeddedResourcePropertyGenerator/compare/0.0.1-alpha...0.0.1-alpha.2
-[0.0.1-alpha]: https://github.com/datacute/EmbeddedResourcePropertyGenerator/releases/tag/0.0.1-alpha
+[Unreleased]: https://github.com/datacute/EmbeddedResourcePropertyGenerator/compare/1.1.1...HEAD
+[1.1.1]: https://github.com/datacute/EmbeddedResourcePropertyGenerator/releases/tag/1.1.1
+[1.1.0]: https://github.com/datacute/EmbeddedResourcePropertyGenerator/releases/tag/1.1.0
+[1.0.0]: https://github.com/datacute/EmbeddedResourcePropertyGenerator/releases/tag/1.0.0
+[0.0.1-alpha.6b]: https://github.com/datacute/EmbeddedResourcePropertyGenerator/releases/tag/0.0.1-alpha.6b
+[0.0.1-alpha.6]: https://github.com/datacute/EmbeddedResourcePropertyGenerator/releases/tag/0.0.1-alpha.6
+[0.0.1-alpha.5]: https://github.com/datacute/EmbeddedResourcePropertyGenerator/releases/tag/0.0.1-alpha.5
+[0.0.1-alpha.4]: https://github.com/datacute/EmbeddedResourcePropertyGenerator/releases/tag/0.0.1-alpha.4
+[0.0.1-alpha.3]: https://github.com/datacute/EmbeddedResourcePropertyGenerator/releases/tag/0.0.1-alpha.3
+[0.0.1-alpha.2]: https://github.com/datacute/EmbeddedResourcePropertyGenerator/releases/tag/0.0.1-alpha.2
+[0.0.1-alpha]: https://github.com/datacute/EmbeddedResourcePropertyGenerator/releases/tag/0.0.1-alpha
\ No newline at end of file
diff --git a/Directory.Build.props b/Directory.Build.props
index 6ee02b4..e0f23eb 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -7,7 +7,7 @@
Copyright © Stephen Denne 2024, 2025
Datacute
https://github.com/datacute/EmbeddedResourcePropertyGenerator
- See full release notes and changelog: $(PackageProjectUrl)/blob/main/CHANGELOG.md
+ See full release notes and changelog: $(PackageProjectUrl)/blob/main/CHANGELOG.md
false
diff --git a/EmbeddedResourceProperty.sln b/EmbeddedResourceProperty.sln
index 5852de9..3e79c11 100644
--- a/EmbeddedResourceProperty.sln
+++ b/EmbeddedResourceProperty.sln
@@ -17,10 +17,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
LICENSE = LICENSE
README.md = README.md
Pipelines.md = Pipelines.md
+ global.json = global.json
+ versionsuffix.ci.props = versionsuffix.ci.props
+ versionsuffix.local.props = versionsuffix.local.props
+ versionsuffix.release.props = versionsuffix.release.props
EndProjectSection
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EmbeddedResourcePropertyGenerator.Attributes", "EmbeddedResourcePropertyGenerator.Attributes\EmbeddedResourcePropertyGenerator.Attributes.csproj", "{73A7F255-8275-41A3-8A2A-E70FE85DD0B5}"
-EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -39,10 +41,6 @@ Global
{5B687AED-BC94-489C-87EF-46E9C859BA74}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5B687AED-BC94-489C-87EF-46E9C859BA74}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5B687AED-BC94-489C-87EF-46E9C859BA74}.Release|Any CPU.Build.0 = Release|Any CPU
- {73A7F255-8275-41A3-8A2A-E70FE85DD0B5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {73A7F255-8275-41A3-8A2A-E70FE85DD0B5}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {73A7F255-8275-41A3-8A2A-E70FE85DD0B5}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {73A7F255-8275-41A3-8A2A-E70FE85DD0B5}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/EmbeddedResourcePropertyExample/EmbeddedResourcePropertyExample.csproj b/EmbeddedResourcePropertyExample/EmbeddedResourcePropertyExample.csproj
index 8757b50..1a86295 100644
--- a/EmbeddedResourcePropertyExample/EmbeddedResourcePropertyExample.csproj
+++ b/EmbeddedResourcePropertyExample/EmbeddedResourcePropertyExample.csproj
@@ -13,7 +13,6 @@
-
diff --git a/EmbeddedResourcePropertyGenerator.Attributes/EmbeddedResourcePropertiesAttribute.cs b/EmbeddedResourcePropertyGenerator.Attributes/EmbeddedResourcePropertiesAttribute.cs
deleted file mode 100644
index 80eca7e..0000000
--- a/EmbeddedResourcePropertyGenerator.Attributes/EmbeddedResourcePropertiesAttribute.cs
+++ /dev/null
@@ -1,140 +0,0 @@
-using System;
-
-// ReSharper disable UnusedParameter.Local
-// ReSharper disable UnusedAutoPropertyAccessor.Global
-
-namespace Datacute.EmbeddedResourcePropertyGenerator;
-
-///
-/// Use a source generator to add properties to this class for each embedded resource file
-/// with a filename matching the given Extension
-/// found in the given Path.
-///
-/// If the path starts with "/" it is relative to the project root,
-/// otherwise it is relative to the folder containing the class with this attribute.
-/// If the path is not specified, the class name is used.
-///
-///
-///
-///
-/// The generated code includes a private nested class EmbeddedResource containing:
-///
-/// Method or ClassPurpose
-/// - Read(string resourceName)Method for reading embedded resources
-/// - BackingFieldNested class caching the property values
-/// - ResourceNameNested class holding the resource names
-///
-///
-///
-/// The generated code supports customising the behaviour of the property getters in two ways, with two partial methods
-/// ReadEmbeddedResourceValue and AlterEmbeddedResourceReturnValue.
-///
-///
-/// If the partial methods are not implemented, the code effectively reduces to:
-///
-/// public static string Example =>
-/// EmbeddedResource.BackingField.Example ??= EmbeddedResource.Read(EmbeddedResource.ResourceName.Example);
-///
-///
-///
-/// Partial methods:
-///
-/// - To allow customisation of how the backing field is read
-/// (for example to check the file system for an override)
-/// implement a partial method that will be called before the above, with the signature:
-///
-/// static partial void ReadEmbeddedResourceValue(ref string? backingField, string resourceName, string propertyName);
-///
-///
-/// -
-/// To allow customisation of the returned value, based on the backing field,
-/// implement a partial method with the signature:
-///
-/// static partial void AlterEmbeddedResourceReturnValue(ref string value, string resourceName, string propertyName);
-///
-///
-///
-///
-///
-/// This is an example of the code generated for a property, showing how the partial methods are called:
-///
-/// public static string Example
-/// {
-/// get
-/// {
-/// ReadEmbeddedResourceValue(ref EmbeddedResource.BackingField.Example, EmbeddedResource.ResourceName.Example, "Example");
-/// var value = EmbeddedResource.BackingField.Example ??= EmbeddedResource.Read(EmbeddedResource.ResourceName.Example);
-/// AlterEmbeddedResourceReturnValue(ref value, EmbeddedResource.ResourceName.Example, "Example");
-/// return value;
-/// }
-/// }
-///
-///
-///
-[System.Diagnostics.Conditional("DATACUTE_EMBEDDEDRESOURCEPROPERTIES_USAGES")]
-[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)]
-public sealed class EmbeddedResourcePropertiesAttribute : Attribute
-{
- /// The filename extension of the embedded resource files
- /// to include as properties, defaulting to ".txt".
- public string Extension { get; set; }
-
- /// The path of the directory of embedded resource files
- /// to include as properties.
- ///
- /// If the path starts with "/" it is treated as relative to the project root,
- /// otherwise it is relative to the folder containing the class with this attribute.
- /// If the path is not specified, the class name is used.
- ///
- public string? Path { get; set; }
-
- ///
- /// Ignore the doc-comment cache and repeatedly read the embedded resource files to generate the doc-comments
- ///
- ///
- /// To include doc-comments on properties, embedded resource file are read once and the doc-comments are cached.
- /// The cache can be refreshed for embedded resource files matching this attributes path and extension,
- /// by temporarily setting this property to true.
- ///
- public bool RegenerateDocCommentsWhileEditing { get; set; }
-
- ///
- /// Output a diagnostic trace log as a comment at the end of the generated files
- ///
- ///
- /// The log shows timestamps for each step of the Embedded Resource Property incremental source code generation process,
- ///
- public bool DiagnosticTraceLog { get; set; }
-
- ///
- /// Use a source generator to add properties to this class for each embedded resource file
- /// found in the path with the folder name the same as this class,
- /// and where the file name's extension is ".txt"
- ///
- public EmbeddedResourcePropertiesAttribute() : this(".txt", null)
- {
- }
-
- ///
- /// Use a source generator to add properties to this class for each embedded resource file
- /// found in the path with the folder name the same as this class,
- /// and where the file name's extension matches the given extension.
- ///
- /// The file name extension to include
- public EmbeddedResourcePropertiesAttribute(string extension = ".txt") : this(extension, null)
- {
- }
-
- ///
- /// Use a source generator to add properties to this class for each embedded resource file
- /// found in the specified path,
- /// and where the file name's extension matches the given extension.
- ///
- /// The file name extension to include
- /// The folder path to include
- public EmbeddedResourcePropertiesAttribute(string extension = ".txt", string? path = null)
- {
- Extension = extension;
- Path = path;
- }
-}
\ No newline at end of file
diff --git a/EmbeddedResourcePropertyGenerator.Attributes/EmbeddedResourcePropertyGenerator.Attributes.csproj b/EmbeddedResourcePropertyGenerator.Attributes/EmbeddedResourcePropertyGenerator.Attributes.csproj
deleted file mode 100644
index 607e8be..0000000
--- a/EmbeddedResourcePropertyGenerator.Attributes/EmbeddedResourcePropertyGenerator.Attributes.csproj
+++ /dev/null
@@ -1,17 +0,0 @@
-
-
-
- netstandard2.0
- 12
- Datacute.EmbeddedResourcePropertyGenerator.Attributes
- Datacute.EmbeddedResourcePropertyGenerator
- enable
- 1.0.0
-
-
-
- true
- false
-
-
-
diff --git a/EmbeddedResourcePropertyGenerator.Tests/EmbeddedResourcePropertyGenerator.Tests.csproj b/EmbeddedResourcePropertyGenerator.Tests/EmbeddedResourcePropertyGenerator.Tests.csproj
index e480721..6c72723 100644
--- a/EmbeddedResourcePropertyGenerator.Tests/EmbeddedResourcePropertyGenerator.Tests.csproj
+++ b/EmbeddedResourcePropertyGenerator.Tests/EmbeddedResourcePropertyGenerator.Tests.csproj
@@ -9,7 +9,7 @@
-
+
@@ -26,7 +26,6 @@
-
diff --git a/EmbeddedResourcePropertyGenerator.Tests/GeneratorSnapshotTests.AlternateFolderSelectedCorrectly#Query.g.verified.cs b/EmbeddedResourcePropertyGenerator.Tests/GeneratorSnapshotTests.AlternateFolderSelectedCorrectly#Query.EmbeddedResourceProperties.g.verified.cs
similarity index 88%
rename from EmbeddedResourcePropertyGenerator.Tests/GeneratorSnapshotTests.AlternateFolderSelectedCorrectly#Query.g.verified.cs
rename to EmbeddedResourcePropertyGenerator.Tests/GeneratorSnapshotTests.AlternateFolderSelectedCorrectly#Query.EmbeddedResourceProperties.g.verified.cs
index da85e66..2a9da6d 100644
--- a/EmbeddedResourcePropertyGenerator.Tests/GeneratorSnapshotTests.AlternateFolderSelectedCorrectly#Query.g.verified.cs
+++ b/EmbeddedResourcePropertyGenerator.Tests/GeneratorSnapshotTests.AlternateFolderSelectedCorrectly#Query.EmbeddedResourceProperties.g.verified.cs
@@ -1,7 +1,8 @@
-//HintName: Query.g.cs
+//HintName: Query.EmbeddedResourceProperties.g.cs
//------------------------------------------------------------------------------
//
-// This code was generated by the Datacute.EmbeddedResourcePropertyGenerator.
+// This code was generated by Datacute.EmbeddedResourcePropertyGenerator.
+// Version: 1.2.3
//
//------------------------------------------------------------------------------
@@ -11,7 +12,7 @@
/// This class's properties are generated from project files meeting the criteria:
///
/// -
-/// they are both an EmbeddedResource and an AdditionalFile
+/// they are an EmbeddedResource
///
/// -
/// they are in the project folder ResourceFolders/Queries
@@ -19,6 +20,9 @@
///
-
/// they have the extension .sql
///
+/// -
+/// Number of matching resources: 1
+///
///
///
public static partial class Query
diff --git a/EmbeddedResourcePropertyGenerator.Tests/GeneratorSnapshotTests.GeneratesEmbeddedResourcePropertiesCorrectly#Queries.g.verified.cs b/EmbeddedResourcePropertyGenerator.Tests/GeneratorSnapshotTests.GeneratesEmbeddedResourcePropertiesCorrectly#Queries.EmbeddedResourceProperties.g.verified.cs
similarity index 88%
rename from EmbeddedResourcePropertyGenerator.Tests/GeneratorSnapshotTests.GeneratesEmbeddedResourcePropertiesCorrectly#Queries.g.verified.cs
rename to EmbeddedResourcePropertyGenerator.Tests/GeneratorSnapshotTests.GeneratesEmbeddedResourcePropertiesCorrectly#Queries.EmbeddedResourceProperties.g.verified.cs
index db990a1..ea8e50e 100644
--- a/EmbeddedResourcePropertyGenerator.Tests/GeneratorSnapshotTests.GeneratesEmbeddedResourcePropertiesCorrectly#Queries.g.verified.cs
+++ b/EmbeddedResourcePropertyGenerator.Tests/GeneratorSnapshotTests.GeneratesEmbeddedResourcePropertiesCorrectly#Queries.EmbeddedResourceProperties.g.verified.cs
@@ -1,7 +1,8 @@
-//HintName: Queries.g.cs
+//HintName: Queries.EmbeddedResourceProperties.g.cs
//------------------------------------------------------------------------------
//
-// This code was generated by the Datacute.EmbeddedResourcePropertyGenerator.
+// This code was generated by Datacute.EmbeddedResourcePropertyGenerator.
+// Version: 1.2.3
//
//------------------------------------------------------------------------------
@@ -11,7 +12,7 @@
/// This class's properties are generated from project files meeting the criteria:
///
/// -
-/// they are both an EmbeddedResource and an AdditionalFile
+/// they are an EmbeddedResource
///
/// -
/// they are in the project folder Queries
@@ -19,6 +20,9 @@
///
-
/// they have the extension .txt
///
+/// -
+/// Number of matching resources: 1
+///
///
///
public static partial class Queries
diff --git a/EmbeddedResourcePropertyGenerator.Tests/GeneratorSnapshotTests.GeneratesEmbeddedResourcePropertiesForInnerAndGenericClasses#OuterClass_T_.InnerClass.g.verified.cs b/EmbeddedResourcePropertyGenerator.Tests/GeneratorSnapshotTests.GeneratesEmbeddedResourcePropertiesForInnerAndGenericClasses#OuterClass_T.InnerClass.EmbeddedResourceProperties.g.verified.cs
similarity index 88%
rename from EmbeddedResourcePropertyGenerator.Tests/GeneratorSnapshotTests.GeneratesEmbeddedResourcePropertiesForInnerAndGenericClasses#OuterClass_T_.InnerClass.g.verified.cs
rename to EmbeddedResourcePropertyGenerator.Tests/GeneratorSnapshotTests.GeneratesEmbeddedResourcePropertiesForInnerAndGenericClasses#OuterClass_T.InnerClass.EmbeddedResourceProperties.g.verified.cs
index 99bfe72..d4a2894 100644
--- a/EmbeddedResourcePropertyGenerator.Tests/GeneratorSnapshotTests.GeneratesEmbeddedResourcePropertiesForInnerAndGenericClasses#OuterClass_T_.InnerClass.g.verified.cs
+++ b/EmbeddedResourcePropertyGenerator.Tests/GeneratorSnapshotTests.GeneratesEmbeddedResourcePropertiesForInnerAndGenericClasses#OuterClass_T.InnerClass.EmbeddedResourceProperties.g.verified.cs
@@ -1,7 +1,8 @@
-//HintName: OuterClass_T_.InnerClass.g.cs
+//HintName: OuterClass_T.InnerClass.EmbeddedResourceProperties.g.cs
//------------------------------------------------------------------------------
//
-// This code was generated by the Datacute.EmbeddedResourcePropertyGenerator.
+// This code was generated by Datacute.EmbeddedResourcePropertyGenerator.
+// Version: 1.2.3
//
//------------------------------------------------------------------------------
@@ -13,7 +14,7 @@ public static partial class OuterClass
/// This class's properties are generated from project files meeting the criteria:
///
/// -
- /// they are both an EmbeddedResource and an AdditionalFile
+ /// they are an EmbeddedResource
///
/// -
/// they are in the project folder InnerClass
@@ -21,6 +22,9 @@ public static partial class OuterClass
///
-
/// they have the extension .txt
///
+ /// -
+ /// Number of matching resources: 1
+ ///
///
///
public static partial class InnerClass
diff --git a/EmbeddedResourcePropertyGenerator.Tests/GeneratorSnapshotTests.GeneratesEmbeddedResourcePropertiesWithNoProperties#Queries.g.verified.cs b/EmbeddedResourcePropertyGenerator.Tests/GeneratorSnapshotTests.GeneratesEmbeddedResourcePropertiesWithNoProperties#Queries.EmbeddedResourceProperties.g.verified.cs
similarity index 82%
rename from EmbeddedResourcePropertyGenerator.Tests/GeneratorSnapshotTests.GeneratesEmbeddedResourcePropertiesWithNoProperties#Queries.g.verified.cs
rename to EmbeddedResourcePropertyGenerator.Tests/GeneratorSnapshotTests.GeneratesEmbeddedResourcePropertiesWithNoProperties#Queries.EmbeddedResourceProperties.g.verified.cs
index c2e1cf0..523c1d2 100644
--- a/EmbeddedResourcePropertyGenerator.Tests/GeneratorSnapshotTests.GeneratesEmbeddedResourcePropertiesWithNoProperties#Queries.g.verified.cs
+++ b/EmbeddedResourcePropertyGenerator.Tests/GeneratorSnapshotTests.GeneratesEmbeddedResourcePropertiesWithNoProperties#Queries.EmbeddedResourceProperties.g.verified.cs
@@ -1,7 +1,8 @@
-//HintName: Queries.g.cs
+//HintName: Queries.EmbeddedResourceProperties.g.cs
//------------------------------------------------------------------------------
//
-// This code was generated by the Datacute.EmbeddedResourcePropertyGenerator.
+// This code was generated by Datacute.EmbeddedResourcePropertyGenerator.
+// Version: 1.2.3
//
//------------------------------------------------------------------------------
@@ -11,7 +12,7 @@
/// This class's properties are generated from project files meeting the criteria:
///
/// -
-/// they are both an EmbeddedResource and an AdditionalFile
+/// they are an EmbeddedResource
///
/// -
/// they are in the project folder Queries
@@ -19,6 +20,9 @@
///
-
/// they have the extension .txt
///
+/// -
+/// Number of matching resources: 0
+///
///
///
public static partial class Queries
diff --git a/EmbeddedResourcePropertyGenerator.Tests/GeneratorSnapshotTests.GeneratesEmbeddedResourcePropertiesWithStrangeNamesCorrectly#Queries.g.verified.cs b/EmbeddedResourcePropertyGenerator.Tests/GeneratorSnapshotTests.GeneratesEmbeddedResourcePropertiesWithStrangeNamesCorrectly#Queries.EmbeddedResourceProperties.g.verified.cs
similarity index 97%
rename from EmbeddedResourcePropertyGenerator.Tests/GeneratorSnapshotTests.GeneratesEmbeddedResourcePropertiesWithStrangeNamesCorrectly#Queries.g.verified.cs
rename to EmbeddedResourcePropertyGenerator.Tests/GeneratorSnapshotTests.GeneratesEmbeddedResourcePropertiesWithStrangeNamesCorrectly#Queries.EmbeddedResourceProperties.g.verified.cs
index 5eae98f..cce3788 100644
--- a/EmbeddedResourcePropertyGenerator.Tests/GeneratorSnapshotTests.GeneratesEmbeddedResourcePropertiesWithStrangeNamesCorrectly#Queries.g.verified.cs
+++ b/EmbeddedResourcePropertyGenerator.Tests/GeneratorSnapshotTests.GeneratesEmbeddedResourcePropertiesWithStrangeNamesCorrectly#Queries.EmbeddedResourceProperties.g.verified.cs
@@ -1,7 +1,8 @@
-//HintName: Queries.g.cs
+//HintName: Queries.EmbeddedResourceProperties.g.cs
//------------------------------------------------------------------------------
//
-// This code was generated by the Datacute.EmbeddedResourcePropertyGenerator.
+// This code was generated by Datacute.EmbeddedResourcePropertyGenerator.
+// Version: 1.2.3
//
//------------------------------------------------------------------------------
@@ -11,7 +12,7 @@
/// This class's properties are generated from project files meeting the criteria:
///
/// -
-/// they are both an EmbeddedResource and an AdditionalFile
+/// they are an EmbeddedResource
///
/// -
/// they are in the project folder Queries
@@ -19,6 +20,9 @@
///
-
/// they have the extension .txt
///
+/// -
+/// Number of matching resources: 11
+///
///
///
public static partial class Queries
diff --git a/EmbeddedResourcePropertyGenerator.Tests/GeneratorSnapshotTests.cs b/EmbeddedResourcePropertyGenerator.Tests/GeneratorSnapshotTests.cs
index 295b53b..b3b8b3e 100644
--- a/EmbeddedResourcePropertyGenerator.Tests/GeneratorSnapshotTests.cs
+++ b/EmbeddedResourcePropertyGenerator.Tests/GeneratorSnapshotTests.cs
@@ -7,7 +7,7 @@ public class GeneratorSnapshotTests
{
private static Task Verify(string source, List? additionalTexts = null)
{
- return TestHelper.Verify(source, additionalTexts);
+ return TestHelper.Verify(source, additionalTexts);
}
[Fact]
diff --git a/EmbeddedResourcePropertyGenerator.Tests/GeneratorTests.cs b/EmbeddedResourcePropertyGenerator.Tests/GeneratorTests.cs
index 2804265..7ee36b5 100644
--- a/EmbeddedResourcePropertyGenerator.Tests/GeneratorTests.cs
+++ b/EmbeddedResourcePropertyGenerator.Tests/GeneratorTests.cs
@@ -1,4 +1,5 @@
using Datacute.EmbeddedResourcePropertyGenerator;
+using Datacute.IncrementalGeneratorExtensions;
using Microsoft.CodeAnalysis;
using Shouldly;
@@ -9,11 +10,9 @@ public class GeneratorTests
private const string InputSource = /* language=c# */
$$"""
using Datacute.EmbeddedResourcePropertyGenerator;
- namespace {{TestHelper.TestNamespace}}
- {
- [EmbeddedResourceProperties(".txt", "Queries", RegenerateDocCommentsWhileEditing = true, DiagnosticTraceLog = false)] // Ignore the cache
- public static partial class Queries;
- }
+ namespace {{TestHelper.TestNamespace}};
+ [EmbeddedResourceProperties(".txt", "Queries")]
+ public static partial class Queries;
""";
// Create a list to hold all additional texts
@@ -45,22 +44,24 @@ Example text content (Line 12)
"Example text content in the wrong folder - should not be included")
];
- private const string ExpectedOutput1 = /* language=c# */
+ private static readonly string ExpectedOutput1 = /* language=c# */
$$"""
//------------------------------------------------------------------------------
//
- // This code was generated by the Datacute.EmbeddedResourcePropertyGenerator.
+ // This code was generated by Datacute.EmbeddedResourcePropertyGenerator.
+ // Version: {{typeof(Generator).Assembly.GetName().Version?.ToString(fieldCount: 3) ?? "unknown"}}
//
//------------------------------------------------------------------------------
#nullable enable
namespace {{TestHelper.TestNamespace}};
+
///
/// This class's properties are generated from project files meeting the criteria:
///
/// -
- /// they are both an EmbeddedResource and an AdditionalFile
+ /// they are an EmbeddedResource
///
/// -
/// they are in the project folder Queries
@@ -68,6 +69,9 @@ namespace {{TestHelper.TestNamespace}};
///
-
/// they have the extension .txt
///
+ /// -
+ /// Number of matching resources: 1
+ ///
///
///
public static partial class Queries
@@ -132,7 +136,7 @@ public void CanGenerate()
{
// run the generator, passing in the inputs and the tracking names
var (diagnostics, output1, output2)
- = TestHelper.GetGeneratedOutput(
+ = TestHelper.GetGeneratedOutput(
TestHelper.NoModification,
_additionalTexts,
TestHelper.GetTrackingNames(),
@@ -150,7 +154,7 @@ public void ChangesToUnreferencedAdditionText_PipelineEntirelyCached()
{
// run the generator, passing in the inputs and the tracking names
var (diagnostics, output1, output2)
- = TestHelper.GetGeneratedOutput(
+ = TestHelper.GetGeneratedOutput(
TestHelper.NoModification,
_additionalTexts,
TestHelper.GetTrackingNames(),
@@ -178,27 +182,15 @@ public void ModifiedText_ProducesMinimalUpdates()
{
// run the generator, passing in the inputs and the tracking names
var (diagnostics, output1, output2)
- = TestHelper.GetGeneratedOutput(
+ = TestHelper.GetGeneratedOutput(
TestHelper.NoModification,
_additionalTexts,
[
+ nameof(GeneratorStage.ForAttributeWithMetadataName),
nameof(TrackingNames.AnalyzerConfigOptions),
- nameof(TrackingNames.AttributeContextsCreated),
nameof(TrackingNames.AttributesAndOptionsCombined),
nameof(TrackingNames.AttributeGlobInfoSelected),
- nameof(TrackingNames.AttributeGlobsSelected),
- //nameof(TrackingNames.FileInfoSelected),
- //nameof(TrackingNames.FileInfoAndGlobsCombined),
- //nameof(TrackingNames.MatchingFilesFiltered),
- //nameof(TrackingNames.EmbeddedResourceExtracted),
- //nameof(TrackingNames.ResourceAndAllAttributeGlobsCombined),
- //nameof(TrackingNames.MatchingResourceAndAttributeSelected),
- //nameof(TrackingNames.ResourcesGroupedByAttributeContext),
- //nameof(TrackingNames.GenerationInputPrepared),
- //nameof(TrackingNames.GeneratingDocComment),
- //nameof(TrackingNames.GeneratingSourceFile),
- //nameof(TrackingNames.DiagnosticTraceLogWritten),
-
+ nameof(TrackingNames.AttributeGlobsSelected)
],
(driver, compilation) =>
{
@@ -246,26 +238,18 @@ public void ModifiedTextAfterTenthLine_PipelineEntirelyCached()
{
// run the generator, passing in the inputs and the tracking names
var (diagnostics, output1, output2)
- = TestHelper.GetGeneratedOutput(
+ = TestHelper.GetGeneratedOutput(
TestHelper.NoModification,
_additionalTexts,
[
- nameof(TrackingNames.AnalyzerConfigOptions),
- nameof(TrackingNames.AttributeContextsCreated),
+ nameof(GeneratorStage.ForAttributeWithMetadataName),
+ nameof(GeneratorStage.AnalyzerConfigOptionsProviderSelect),
nameof(TrackingNames.AttributesAndOptionsCombined),
nameof(TrackingNames.AttributeGlobInfoSelected),
nameof(TrackingNames.AttributeGlobsSelected),
- //nameof(TrackingNames.FileInfoSelected),
- //nameof(TrackingNames.FileInfoAndGlobsCombined),
- //nameof(TrackingNames.MatchingFilesFiltered),
- //nameof(TrackingNames.EmbeddedResourceExtracted),
- //nameof(TrackingNames.ResourceAndAllAttributeGlobsCombined),
nameof(TrackingNames.MatchingResourceAndAttributeSelected),
nameof(TrackingNames.ResourcesGroupedByAttributeContext),
- nameof(TrackingNames.GenerationInputPrepared),
- //nameof(TrackingNames.GeneratingDocComment),
- //nameof(TrackingNames.GeneratingSourceFile),
- //nameof(TrackingNames.DiagnosticTraceLogWritten),
+ nameof(TrackingNames.GenerationInputPrepared)
],
(driver, compilation) =>
{
@@ -304,7 +288,7 @@ public void DesignTimeBuild_DoesNotReadFiles()
{
// run the generator, passing in the inputs and the tracking names
var (diagnostics, output1, output2)
- = TestHelper.GetGeneratedOutput(
+ = TestHelper.GetGeneratedOutput(
(driver, compilation) =>
{
var newOptions = new TestConfigOptionsProvider(true);
@@ -318,16 +302,18 @@ public void DesignTimeBuild_DoesNotReadFiles()
// Assert the output
diagnostics.ShouldBeEmpty();
- var expected = """
+ var expected = $$"""
//------------------------------------------------------------------------------
//
- // This code was generated by the Datacute.EmbeddedResourcePropertyGenerator.
+ // This code was generated by Datacute.EmbeddedResourcePropertyGenerator.
+ // Version: {{typeof(Generator).Assembly.GetName().Version?.ToString(fieldCount: 3) ?? "unknown"}}
//
//------------------------------------------------------------------------------
#nullable enable
namespace EmbeddedResourcePropertyGenerator.Tests;
+
public static partial class Queries
{
private static class EmbeddedResource
diff --git a/EmbeddedResourcePropertyGenerator.Tests/TestHelper.cs b/EmbeddedResourcePropertyGenerator.Tests/TestHelper.cs
index f4773a9..3127b18 100644
--- a/EmbeddedResourcePropertyGenerator.Tests/TestHelper.cs
+++ b/EmbeddedResourcePropertyGenerator.Tests/TestHelper.cs
@@ -1,6 +1,7 @@
using System.Collections;
using System.Collections.Immutable;
using System.Reflection;
+using System.Text.RegularExpressions;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Shouldly;
@@ -34,16 +35,15 @@ public static (GeneratorDriver, CSharpCompilation)
=> (driver, compilation);
public static (ImmutableArray Diagnostics, string[] Output1, string[] Output2)
- GetGeneratedOutput(
+ GetGeneratedOutput(
Func scenarioModification,
List? additionalTexts,
string[] trackingNamesToVerifyUnchanged,
Func modificationBetweenRuns,
params string[] sources)
- where TAttribute : Attribute
where TGenerator : IIncrementalGenerator, new()
{
- var compilation = GetCompilation(sources);
+ var compilation = GetCompilation(sources);
// Run the generator, get the results, and assert cacheability if applicable
(GeneratorDriverRunResult runResult1, GeneratorDriverRunResult runResult2) =
@@ -60,8 +60,7 @@ public static (ImmutableArray Diagnostics, string[] Output1, string[
runResult2.GeneratedTrees.Select(x => x.ToString()).ToArray());
}
- private static CSharpCompilation GetCompilation(params string[] sources)
- where TAttribute : Attribute
+ private static CSharpCompilation GetCompilation(params string[] sources)
where TGenerator : IIncrementalGenerator, new()
{
// Convert the source files to SyntaxTrees
@@ -75,13 +74,14 @@ private static CSharpCompilation GetCompilation(params s
.Where(assembly => !assembly.IsDynamic && !string.IsNullOrWhiteSpace(assembly.Location))
.Select(assembly => MetadataReference.CreateFromFile(assembly.Location))
.Concat([
- MetadataReference.CreateFromFile(typeof(TGenerator).Assembly.Location),
- MetadataReference.CreateFromFile(typeof(TAttribute).Assembly.Location)
+ MetadataReference.CreateFromFile(typeof(TGenerator).Assembly.Location)
]);
// Create a Compilation object
// You may want to specify other results here
- return CSharpCompilation.Create("Tests", syntaxTrees, references);
+ var compilationOptions = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)
+ .WithNullableContextOptions(NullableContextOptions.Enable);
+ return CSharpCompilation.Create("Tests", syntaxTrees, references, compilationOptions);
}
private static (GeneratorDriverRunResult, GeneratorDriverRunResult) RunGeneratorAndAssertOutput(
@@ -152,8 +152,8 @@ private static void AssertRunsEqual(
Dictionary> trackedSteps2 = GetTrackedSteps(runResult2, trackingNamesToVerifyUnchanged);
// These should be the same
- trackedSteps1.Count.ShouldBe(trackingNamesToVerifyUnchanged.Length);
- trackedSteps1.Count.ShouldBe(trackedSteps2.Count);
+ //trackedSteps1.Count.ShouldBe(trackingNamesToVerifyUnchanged.Length);
+ //trackedSteps1.Count.ShouldBe(trackedSteps2.Count);
if (trackingNamesToVerifyUnchanged.Length > 0)
{
@@ -255,14 +255,26 @@ void Visit(object? node)
}
}
- public static Task Verify(string source, List? additionalTexts = null)
- where TAttribute : Attribute
+ public static Task Verify(string source, List? additionalTexts = null)
where TGenerator : IIncrementalGenerator, new()
{
var driver = GetDriver(additionalTexts);
- var compilation = GetCompilation(source);
+ var compilation = GetCompilation(source);
driver = driver.RunGenerators(compilation);
- return Verifier.Verify(driver);
+ return Verifier.Verify(driver)
+ .ScrubLinesWithReplace(line =>
+ Regex.Replace(line, @"Version: \d+\.\d+\.\d+", "Version: 1.2.3"))
+ .IgnoreGeneratedResult(result =>
+ {
+ switch (result.HintName)
+ {
+ case "Datacute.EmbeddedResourcePropertyGenerator.EmbeddedResourcePropertiesAttribute.g.cs":
+ case "Microsoft.CodeAnalysis.EmbeddedAttribute.cs":
+ return true;
+ default:
+ return false;
+ }
+ });
}
}
\ No newline at end of file
diff --git a/EmbeddedResourcePropertyGenerator/AdditionalTextDocCommentCreator.cs b/EmbeddedResourcePropertyGenerator/AdditionalTextDocCommentCreator.cs
index 085bbfc..b1460fd 100644
--- a/EmbeddedResourcePropertyGenerator/AdditionalTextDocCommentCreator.cs
+++ b/EmbeddedResourcePropertyGenerator/AdditionalTextDocCommentCreator.cs
@@ -1,4 +1,5 @@
-using System.Text;
+using Datacute.IncrementalGeneratorExtensions;
+using System.Text;
using Microsoft.CodeAnalysis;
namespace Datacute.EmbeddedResourcePropertyGenerator;
@@ -20,8 +21,7 @@ public static class AdditionalTextDocCommentCreator
var outputLines = 0;
foreach (var textLine in textLineCollection)
{
- if (ct.IsCancellationRequested) LightweightTrace.Add((int)TrackingNames.Cancel + 9000);
- ct.ThrowIfCancellationRequested();
+ ct.ThrowIfCancellationRequested(109);
// Truncation happens after 10 lines
// but if there are only 11 lines,
diff --git a/EmbeddedResourcePropertyGenerator/AttributeContext.cs b/EmbeddedResourcePropertyGenerator/AttributeContext.cs
deleted file mode 100644
index e9e6c38..0000000
--- a/EmbeddedResourcePropertyGenerator/AttributeContext.cs
+++ /dev/null
@@ -1,157 +0,0 @@
-using System.Collections.Immutable;
-using Microsoft.CodeAnalysis;
-
-namespace Datacute.EmbeddedResourcePropertyGenerator
-{
- public readonly record struct AttributeContext
- {
- public readonly string ExtensionArg;
- public readonly string PathArg;
- public readonly bool OutputDiagnosticTraceLog = false;
-
- public readonly string FilePath;
-
- public readonly bool ContainingNamespaceIsGlobalNamespace;
- public readonly string ContainingNamespaceDisplayString;
-
- // Store parent classes with their modifiers
- public record struct ParentClassInfo(
- string Name,
- bool IsStatic,
- Accessibility Accessibility,
- string RecordStructOrClass,
- EquatableImmutableArray TypeParameters);
-
- public readonly EquatableImmutableArray ParentClasses;
- public bool HasParentClasses => ParentClasses.Count > 0;
-
- public readonly Accessibility DeclaredAccessibility; // public
- public readonly bool IsStatic; // static
- public readonly string RecordStructOrClass; // (partial) class
- public readonly string Name; // ClassName
- public readonly EquatableImmutableArray TypeParameters; //
- public readonly string DisplayString; // Namespace.ClassName
-
- public AttributeContext(in GeneratorAttributeSyntaxContext generatorAttributeSyntaxContext)
- {
- // No diagnostic tracing here - this triggers for each matching attribute, every time you type.
- // the time taken within this method is about 1% of the time the source generator takes
- // to process the attribute.
-
- var attributeTargetSymbol = (ITypeSymbol)generatorAttributeSyntaxContext.TargetSymbol;
-
- //todo support multiple attributes
- var attributeData = generatorAttributeSyntaxContext.Attributes[0];
- var args = attributeData.ConstructorArguments;
- ExtensionArg = (args.Length == 0 ? null : args[0].Value as string) ?? ".txt";
- PathArg = (args.Length < 2 ? null : args[1].Value as string) ?? attributeTargetSymbol.Name;
- if (!attributeData.NamedArguments.IsEmpty)
- {
- foreach (KeyValuePair namedArgument in attributeData.NamedArguments)
- {
- var value = namedArgument.Value.Value;
- if (value != null)
- {
- switch (value)
- {
- case bool boolValue:
- switch (namedArgument.Key)
- {
- case "DiagnosticTraceLog":
- OutputDiagnosticTraceLog = boolValue;
- break;
- }
-
- break;
- case string stringValue:
- switch (namedArgument.Key)
- {
- case "Extension":
- ExtensionArg = stringValue;
- break;
- case "Path":
- PathArg = stringValue;
- break;
- }
-
- break;
- }
- }
- }
- }
-
- FilePath = generatorAttributeSyntaxContext.TargetNode.SyntaxTree.FilePath;
-
- ContainingNamespaceIsGlobalNamespace = attributeTargetSymbol.ContainingNamespace.IsGlobalNamespace;
- ContainingNamespaceDisplayString = attributeTargetSymbol.ContainingNamespace.ToDisplayString();
-
- DeclaredAccessibility = attributeTargetSymbol.DeclaredAccessibility;
- IsStatic = attributeTargetSymbol.IsStatic;
- RecordStructOrClass = GetRecordStructOrClass(attributeTargetSymbol);
- Name = attributeTargetSymbol.Name;
-
- if (generatorAttributeSyntaxContext.TargetSymbol is INamedTypeSymbol namedTypeTargetSymbol)
- {
- var typeParameters = namedTypeTargetSymbol.TypeParameters;
- TypeParameters = typeParameters.Length > 0
- ? typeParameters.ToEquatableImmutableArray(tp => tp.Name)
- : EquatableImmutableArray.Empty;
- }
- else
- {
- TypeParameters = EquatableImmutableArray.Empty;
- }
-
- DisplayString = attributeTargetSymbol.ToDisplayString();
-
- // Parse parent classes from symbol's containing types
- var parentClassCount = 0;
- var containingType = attributeTargetSymbol.ContainingType;
- // Count the number of parent classes
- while (containingType != null)
- {
- parentClassCount++;
- containingType = containingType.ContainingType;
- }
-
- if (parentClassCount > 0)
- {
- containingType = attributeTargetSymbol.ContainingType;
- var parentClassImmutableArrayBuilder = ImmutableArray.CreateBuilder(parentClassCount);
- for (var i = 0; i < parentClassCount; i++)
- {
- var typeParameters = containingType.TypeParameters;
- var typeParameterNames = typeParameters.Length > 0
- ? typeParameters.ToEquatableImmutableArray(tp => tp.Name)
- : EquatableImmutableArray.Empty;
-
- parentClassImmutableArrayBuilder.Insert(0, new ParentClassInfo(
- containingType.Name,
- containingType.IsStatic,
- containingType.DeclaredAccessibility,
- GetRecordStructOrClass(containingType),
- typeParameterNames));
- containingType = containingType.ContainingType;
- }
-
- ParentClasses = parentClassImmutableArrayBuilder.MoveToImmutable().ToEquatableImmutableArray();
- }
- else
- {
- ParentClasses = EquatableImmutableArray.Empty;
- }
- }
-
- private static string GetRecordStructOrClass(ITypeSymbol typeSymbol)
- {
- // This shouldn't be necessary, as only classes are supported
- if (typeSymbol.IsRecord && typeSymbol.IsReferenceType)
- return "record";
- if (typeSymbol.IsRecord)
- return "record struct";
- if (typeSymbol.IsReferenceType)
- return "class";
- return "struct";
- }
- }
-}
\ No newline at end of file
diff --git a/EmbeddedResourcePropertyGenerator/AttributeData.cs b/EmbeddedResourcePropertyGenerator/AttributeData.cs
new file mode 100644
index 0000000..263031e
--- /dev/null
+++ b/EmbeddedResourcePropertyGenerator/AttributeData.cs
@@ -0,0 +1,57 @@
+using Microsoft.CodeAnalysis;
+
+namespace Datacute.EmbeddedResourcePropertyGenerator;
+
+public readonly record struct AttributeData
+{
+ public readonly string ExtensionArg;
+ public readonly string PathArg;
+ public readonly bool OutputDiagnosticTraceLog = false;
+ public readonly string FilePath;
+
+ public AttributeData(in GeneratorAttributeSyntaxContext generatorAttributeSyntaxContext)
+ {
+ var attributeData = generatorAttributeSyntaxContext.Attributes[0];
+ var args = attributeData.ConstructorArguments;
+ ExtensionArg = (args.Length == 0 ? null : args[0].Value as string) ?? ".txt";
+ PathArg = (args.Length < 2 ? null : args[1].Value as string) ?? generatorAttributeSyntaxContext.TargetSymbol.Name;
+ if (!attributeData.NamedArguments.IsEmpty)
+ {
+ foreach (KeyValuePair namedArgument in attributeData.NamedArguments)
+ {
+ var value = namedArgument.Value.Value;
+ if (value != null)
+ {
+ switch (value)
+ {
+ case bool boolValue:
+ switch (namedArgument.Key)
+ {
+ case "DiagnosticTraceLog":
+ OutputDiagnosticTraceLog = boolValue;
+ break;
+ }
+
+ break;
+ case string stringValue:
+ switch (namedArgument.Key)
+ {
+ case "Extension":
+ ExtensionArg = stringValue;
+ break;
+ case "Path":
+ PathArg = stringValue;
+ break;
+ }
+
+ break;
+ }
+ }
+ }
+ }
+
+ FilePath = generatorAttributeSyntaxContext.TargetNode.SyntaxTree.FilePath;
+ }
+
+ public static AttributeData Collector(GeneratorAttributeSyntaxContext generatorAttributeSyntaxContext) => new(generatorAttributeSyntaxContext);
+}
\ No newline at end of file
diff --git a/EmbeddedResourcePropertyGenerator/CodeGenerator.cs b/EmbeddedResourcePropertyGenerator/CodeGenerator.cs
index 93ed8a2..a844b5e 100644
--- a/EmbeddedResourcePropertyGenerator/CodeGenerator.cs
+++ b/EmbeddedResourcePropertyGenerator/CodeGenerator.cs
@@ -1,79 +1,41 @@
-using System.Text;
-using Microsoft.CodeAnalysis;
+using Datacute.IncrementalGeneratorExtensions;
namespace Datacute.EmbeddedResourcePropertyGenerator
{
- public readonly struct CodeGenerator
+ public class CodeGenerator : SourceTextGeneratorBase
{
- public CodeGenerator(in AttributeContext context,
+ public CodeGenerator(in AttributeContextAndData contextAndData,
string resourceSearchPath,
in EquatableImmutableArray embeddedResources,
in GeneratorOptions options,
- in CancellationToken cancellationToken)
+ in CancellationToken cancellationToken)
+ : base(contextAndData, cancellationToken)
{
- _context = context;
+ _contextData = contextAndData.AttributeData;
_resourceSearchPath = resourceSearchPath;
_embeddedResources = embeddedResources;
_options = options;
- _cancellationToken = cancellationToken;
- _buffer = new StringBuilder();
_propertyNames = new Dictionary();
}
- private static readonly Dictionary IndentationCache = new();
- private const char IndentationCharacter = ' ';
- private const int IndentationCharacterRepetition = 4;
-
- private readonly AttributeContext _context;
+ private readonly AttributeData _contextData;
private readonly string _resourceSearchPath;
private readonly EquatableImmutableArray _embeddedResources;
private readonly GeneratorOptions _options;
- private readonly StringBuilder _buffer;
- private readonly CancellationToken _cancellationToken;
private readonly Dictionary _propertyNames;
- public string GenerateSource()
- {
- if (_cancellationToken.IsCancellationRequested) LightweightTrace.Add((int)TrackingNames.Cancel + 2000);
- _cancellationToken.ThrowIfCancellationRequested();
- GeneratePropertyNames();
-
- _buffer.Clear();
- AutoGeneratedComment();
- var indent = StartNamespace();
- indent = ParentClasses(indent);
- indent = ClassDocComments(indent);
- indent = PartialTypeDeclaration(indent);
- indent = AppendStartBlock(indent);
- indent = AppendStartEmbeddedResourceClass(indent);
- indent = ReadMethod(indent);
- indent = BackingFields(indent);
- indent = ResourceNames(indent);
- indent = AppendEndBlock(indent);
- indent = AppendPartialMethods(indent);
- indent = ProcessMatchingEmbeddedResources(indent);
- indent = AppendEndBlock(indent);
- indent = EndParentClasses(indent);
- if (indent > 0) // The choice to use file scoped namespaces is made within StartNamespace()
- {
- AppendEndBlock(indent);
- }
-
- AppendDiagnosticLogs();
- return _buffer.ToString();
- }
-
- private void GeneratePropertyNames()
+ protected override void PrepareForGeneration()
{
+ Token.ThrowIfCancellationRequested(102);
+ Buffer.Clear();
_propertyNames.Clear();
foreach (var text in _embeddedResources)
{
- if (_cancellationToken.IsCancellationRequested) LightweightTrace.Add((int)TrackingNames.Cancel + 3000);
- _cancellationToken.ThrowIfCancellationRequested();
+ Token.ThrowIfCancellationRequested(103);
var resourceFilePath = text.Path;
- var propertyName = resourceFilePath.GetPropertyName(_context.Name);
+ var propertyName = resourceFilePath.GetPropertyName(Context.Name);
RecordPropertyNameForResource(propertyName, text);
}
}
@@ -101,88 +63,68 @@ private void RecordPropertyNameForResource(string propertyName, EmbeddedResource
}
}
- private void AutoGeneratedComment() => _buffer.AppendLine(Templates.AutoGeneratedComment);
-
- /// The indentation level
- private int StartNamespace()
+ protected override void AppendDocComments()
{
- if (_context.ContainingNamespaceIsGlobalNamespace) return 0;
+ if (_options.IsDesignTimeBuild) return;
- _buffer.Append("namespace ");
- _buffer.Append(_context.ContainingNamespaceDisplayString);
- // Here is where we could chose whether to use file scoped namespaces or not
- // depending on whether C# language in use is >= 10
- _buffer.Append(';').AppendLine();
- return 0; // No indentation needed for file scoped namespace
+ var path = _resourceSearchPath.Substring(_options.ProjectDir.Length)
+ .Replace(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
+ Buffer.AppendFormatLines(Templates.ClassDocComments, path, _contextData.ExtensionArg, _propertyNames.Count);
}
- private int ParentClasses(int indent)
+ protected override void AppendCustomMembers()
{
- if (_context.HasParentClasses)
- {
- indent = StartParentClasses(indent);
- }
- return indent;
+ AppendStartEmbeddedResourceClass();
+ AppendStartBlock();
+ AppendReadMethod();
+ AppendBackingFields();
+ AppendResourceNames();
+ AppendEndBlock();
+ AppendPartialMethods();
+ AppendMatchingEmbeddedResources();
}
- private int StartParentClasses(int indent)
+ private void AppendStartEmbeddedResourceClass()
{
- foreach (var parentClass in _context.ParentClasses)
- {
- AppendIndent(indent);
- var accessibilityModifier = GetAccessibility(parentClass.Accessibility);
- var staticModifier = GetStaticModifier(parentClass.IsStatic);
- var genericTypes = GetGenericTypes(parentClass.TypeParameters);
- _buffer.AppendLine($"{accessibilityModifier}{staticModifier}partial {parentClass.RecordStructOrClass} {parentClass.Name}{genericTypes}");
- AppendStartBlock(indent);
- indent++;
- }
- return indent;
+ Buffer.AppendLine(Templates.StartEmbeddedResourceClass);
}
- private int ReadMethod(int indent)
+ private void AppendReadMethod()
{
- if (_options.IsDesignTimeBuild) return indent;
+ if (_options.IsDesignTimeBuild) return;
- var indentString = StringForIndent(indent);
- _buffer.AppendFormat(Templates.ReadMethod, indentString, StringForIndent(1), _context.Name);
- return indent;
+ Buffer.AppendFormatLines(Templates.ReadMethod, Buffer.SingleIndent, Context.Name);
}
- private int BackingFields(int indent)
+ private void AppendBackingFields()
{
- if (_options.IsDesignTimeBuild) return indent;
+ if (_options.IsDesignTimeBuild) return;
- AppendIndent(indent);
- _buffer.AppendLine(Templates.BackingFieldClass);
- indent = AppendStartBlock(indent);
+ Buffer.AppendLine(Templates.BackingFieldClass);
+ AppendStartBlock();
- var innerIndentString = StringForIndent(indent);
foreach (var kvp in _propertyNames.OrderBy(kvp => kvp.Value.Path))
{
- if (_cancellationToken.IsCancellationRequested) LightweightTrace.Add((int)TrackingNames.Cancel + 4000);
- _cancellationToken.ThrowIfCancellationRequested();
+ Token.ThrowIfCancellationRequested(104);
var propertyName = kvp.Key;
- _buffer.AppendFormat(Templates.BackingField, innerIndentString, propertyName).AppendLine();
+ Buffer.AppendLine(NullableEnabled
+ ? string.Format(Templates.BackingField, propertyName)
+ : string.Format(Templates.BackingFieldNullableNotSupported, propertyName));
}
- indent = AppendEndBlock(indent);
- return indent;
+ AppendEndBlock();
}
- private int ResourceNames(int indent)
+ private void AppendResourceNames()
{
- if (_options.IsDesignTimeBuild) return indent;
+ if (_options.IsDesignTimeBuild) return;
- AppendIndent(indent);
- _buffer.AppendLine(Templates.ResourceNameClass);
- indent = AppendStartBlock(indent);
+ Buffer.AppendLine(Templates.ResourceNameClass);
+ AppendStartBlock();
- var innerIndentString = StringForIndent(indent);
foreach (var kvp in _propertyNames.OrderBy(kvp => kvp.Value.Path))
{
- if (_cancellationToken.IsCancellationRequested) LightweightTrace.Add((int)TrackingNames.Cancel + 5000);
- _cancellationToken.ThrowIfCancellationRequested();
+ Token.ThrowIfCancellationRequested(105);
var propertyName = kvp.Key;
var text = kvp.Value;
@@ -192,186 +134,56 @@ private int ResourceNames(int indent)
_options.ProjectDir,
_options.RootNamespace);
- _buffer.AppendFormat(Templates.ResourceName, innerIndentString, propertyName, embeddedResourceName);
+ Buffer.AppendLine(string.Format(Templates.ResourceName, propertyName, embeddedResourceName));
}
- indent = AppendEndBlock(indent);
- return indent;
- }
-
- private int ClassDocComments(int indent)
- {
- if (_options.IsDesignTimeBuild) return indent;
-
- var indentString = StringForIndent(indent);
-
- var path = _resourceSearchPath.Substring(_options.ProjectDir.Length)
- .Replace(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
- _buffer.AppendFormat(Templates.ClassDocComments, indentString, path, _context.ExtensionArg);
-
- return indent;
- }
-
- private int PartialTypeDeclaration(int indent)
- {
- _buffer.AppendFormat(
- "{0}{1}{2}partial {3} {4}{5}",
- StringForIndent(indent),
- GetAccessibility(),
- GetStatic(),
- _context.RecordStructOrClass,
- _context.Name,
- GetGenericTypes()
- ).AppendLine();
-
- return indent;
- }
-
- private string GetAccessibility() => GetAccessibility(_context.DeclaredAccessibility);
-
- private static string GetAccessibility(Accessibility accessibility)
- {
- return accessibility switch
- {
- Accessibility.Private => "private ",
- Accessibility.ProtectedAndInternal => "private protected ",
- Accessibility.Protected => "protected ",
- Accessibility.Internal => "internal ",
- Accessibility.ProtectedOrInternal => "protected internal ",
- Accessibility.Public => "public ",
- _ => throw new ArgumentOutOfRangeException(nameof(accessibility), accessibility, null)
- };
- }
-
- private string GetStatic() => GetStaticModifier(_context.IsStatic);
-
- private string GetStaticModifier(bool isStatic) => isStatic ? "static " : "";
-
- private string GetGenericTypes() => GetGenericTypes(_context.TypeParameters);
-
- private string GetGenericTypes(EquatableImmutableArray genericTypes) =>
- genericTypes.Any()
- ? $"<{string.Join(",", genericTypes)}>"
- : string.Empty;
-
- private int AppendStartBlock(int indent)
- {
- AppendIndent(indent);
- _buffer.AppendLine("{");
- return indent + 1;
- }
-
- private int AppendStartEmbeddedResourceClass(int indent)
- {
- AppendIndent(indent);
- _buffer.AppendLine(Templates.StartEmbeddedResourceClass);
- indent = AppendStartBlock(indent);
- return indent;
+ AppendEndBlock();
}
- private int AppendPartialMethods(int indent)
+ private void AppendPartialMethods()
{
- AppendIndent(indent);
- _buffer.AppendLine(Templates.PartialReadMethods);
- AppendIndent(indent);
- _buffer.AppendLine(Templates.PartialAlterMethods);
- return indent;
+ Buffer.AppendLine(NullableEnabled ? Templates.PartialReadMethods : Templates.PartialReadMethodsNullableNotSupported);
+ Buffer.AppendLine(Templates.PartialAlterMethods);
}
- private int ProcessMatchingEmbeddedResources(int indent)
+ private void AppendMatchingEmbeddedResources()
{
- var indentString = StringForIndent(indent);
foreach (var kvp in _propertyNames.OrderBy(kvp => kvp.Value.Path))
{
- if (_cancellationToken.IsCancellationRequested) LightweightTrace.Add((int)TrackingNames.Cancel + 6000);
- _cancellationToken.ThrowIfCancellationRequested();
+ Token.ThrowIfCancellationRequested(106);
var propertyName = kvp.Key;
var text = kvp.Value;
- var resourceFilePath = text.Path;
if (_options.IsDesignTimeBuild)
{
- AppendIndent(indent);
- _buffer.AppendFormat(Templates.DesignTimePropertyTemplate, propertyName).AppendLine();
+ Buffer.AppendLine(string.Format(Templates.DesignTimePropertyTemplate, propertyName));
}
else
{
- var resourceFileName = resourceFilePath.GetFileName();
var docCommentCode = text.DocCommentCode;
if (docCommentCode is null)
{
continue;
}
- var indentedDocCommentCodeSb = new StringBuilder();
- foreach (var docCommentLine in docCommentCode.Split(new[] { '\r', '\n' },
- StringSplitOptions.RemoveEmptyEntries))
- {
- indentedDocCommentCodeSb.AppendLine();
- indentedDocCommentCodeSb.Append(indentString);
- indentedDocCommentCodeSb.Append(docCommentLine);
- }
-
- _buffer.AppendFormat(Templates.PropertyTemplate,
- indentString,
- StringForIndent(1),
+ var resourceFileName = text.Path.GetFileName();
+ Buffer.AppendFormatLines(Templates.PropertyTemplate,
+ Buffer.SingleIndent,
propertyName,
resourceFileName,
- indentedDocCommentCodeSb);
+ docCommentCode);
}
}
-
- return indent;
- }
-
- private int AppendEndBlock(int indent)
- {
- indent--;
- AppendIndent(indent);
- _buffer.AppendLine("}");
- return indent;
}
- private int EndParentClasses(int indent)
+ protected override void AppendDiagnosticLogs()
{
- // Close parent classes if any
- if (_context.HasParentClasses)
+ if (_contextData.OutputDiagnosticTraceLog)
{
- for (int i = 0; i < _context.ParentClasses.Count; i++)
- {
- indent = AppendEndBlock(indent);
- }
- }
-
- return indent;
- }
-
- private void AppendDiagnosticLogs()
- {
- if (_context.OutputDiagnosticTraceLog)
- {
- _buffer.AppendLine();
- _buffer.AppendLine("/* Diagnostic Trace Log");
- LightweightTrace.Add((int)TrackingNames.DiagnosticTraceLogWritten);
- LightweightTrace.GetTrace(_buffer, TrackingNameDescriptions.EventNameMap);
- _buffer.AppendLine("*/");
+ Buffer.AppendLine();
+ Buffer.Direct.AppendDiagnosticsComment(TrackingNameDescriptions.EventNameMap);
+ LightweightTrace.Add(TrackingNames.DiagnosticTraceLogWritten);
}
}
-
- private string StringForIndent(int indent)
- {
- if (!IndentationCache.TryGetValue(indent, out var indentString))
- {
- indentString = new string(IndentationCharacter, indent * IndentationCharacterRepetition);
- IndentationCache[indent] = indentString;
- }
- return indentString;
- }
-
- private void AppendIndent(int indent)
- {
- var indentString = StringForIndent(indent);
- _buffer.Append(indentString);
- }
}
}
\ No newline at end of file
diff --git a/EmbeddedResourcePropertyGenerator/EmbeddedResourcePropertyGenerator.csproj b/EmbeddedResourcePropertyGenerator/EmbeddedResourcePropertyGenerator.csproj
index 25e8e97..ee9e264 100644
--- a/EmbeddedResourcePropertyGenerator/EmbeddedResourcePropertyGenerator.csproj
+++ b/EmbeddedResourcePropertyGenerator/EmbeddedResourcePropertyGenerator.csproj
@@ -2,7 +2,7 @@
netstandard2.0
- 10.0
+ 10
enable
enable
false
@@ -28,7 +28,19 @@
MIT
+
+ true
+ $(DefineConstants);DATACUTE_EXCLUDE_TABINDENTINGLINEAPPENDER
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
@@ -36,16 +48,13 @@
-
-
-
-
-
+
+
diff --git a/EmbeddedResourcePropertyGenerator/EquatableImmutableArray.cs b/EmbeddedResourcePropertyGenerator/EquatableImmutableArray.cs
deleted file mode 100644
index ab81210..0000000
--- a/EmbeddedResourcePropertyGenerator/EquatableImmutableArray.cs
+++ /dev/null
@@ -1,211 +0,0 @@
-using System.Collections;
-using System.Collections.Concurrent;
-using System.Collections.Immutable;
-using Microsoft.CodeAnalysis;
-
-namespace Datacute.EmbeddedResourcePropertyGenerator;
-
-public sealed class EquatableImmutableArray : IEquatable>, IReadOnlyList
- where T : IEquatable
-{
- public static EquatableImmutableArray Empty { get; } = new(ImmutableArray.Empty);
-
- // The source generation pipelines compare these a lot
- // so being able to quickly tell when they are different
- // is important.
- // We will use an instance cache to find when we can reuse
- // an existing object, massively speeding up the Equals call.
- #region Instance Cache
-
- // Thread-safe cache using dictionary of hash code -> list of arrays with that hash
- private static readonly ConcurrentDictionary>>> InstanceCache = new();
-
- // Static factory method with singleton handling
- public static EquatableImmutableArray Create(ImmutableArray values, CancellationToken cancellationToken = default)
- {
- if (values.IsEmpty)
- return Empty;
-
- // Calculate hash code for the values
- var hash = CalculateHashCode(values);
-
- // Try to find an existing instance with the same hash and values
- if (InstanceCache.TryGetValue(hash, out var list))
- {
- cancellationToken.ThrowIfCancellationRequested();
-
- lock (list) // Thread safety for the list
- {
- for (int i = list.Count - 1; i >= 0; i--)
- {
- cancellationToken.ThrowIfCancellationRequested();
-
- if (list[i].TryGetTarget(out var existing))
- {
- // Element-by-element comparison for arrays with the same hash
- if (ValuesEqual(values, existing._values))
- return existing;
- }
- else
- {
- // Remove dead references
- list.RemoveAt(i);
- }
- }
- }
- }
-
- // Create new instance and add to cache
- var result = new EquatableImmutableArray(values, hash);
-
- InstanceCache.AddOrUpdate(hash,
- _ => new List>> { new(result) },
- (_, existingList) =>
- {
- cancellationToken.ThrowIfCancellationRequested();
- lock (existingList)
- {
- existingList.Add(new WeakReference>(result));
- }
- return existingList;
- });
-
- return result;
- }
-
- private static bool ValuesEqual(ImmutableArray a, ImmutableArray b)
- {
- // Identical arrays reference check
- if (a == b) return true;
-
- int length = a.Length;
- if (length != b.Length) return false;
-
- var comparer = EqualityComparer.Default;
- for (int i = 0; i < length; i++)
- {
- if (!comparer.Equals(a[i], b[i]))
- return false;
- }
-
- return true;
- }
-
- private static int CalculateHashCode(ImmutableArray values)
- {
- var comparer = EqualityComparer.Default;
- var hash = 0;
- for (var index = 0; index < values.Length; index++)
- {
- var value = values[index];
- hash = HashHelpers_Combine(hash, value is null ? 0 : comparer.GetHashCode(value));
- }
- return hash;
- }
-
- #endregion
-
- private readonly ImmutableArray _values;
- private readonly int _hashCode;
- private readonly int _length;
- public T this[int index] => _values[index];
- public int Count => _length;
-
- private EquatableImmutableArray(ImmutableArray values)
- {
- _values = values;
- _length = values.Length;
- _hashCode = CalculateHashCode(values);
- }
-
- private EquatableImmutableArray(ImmutableArray values, int hashCode)
- {
- _values = values;
- _length = values.Length;
- _hashCode = hashCode;
- }
-
- public bool Equals(EquatableImmutableArray? other)
- {
- // Fast reference equality check
- if (ReferenceEquals(this, other)) return true;
-
- if (other is null) return false;
-
- // If hash codes are different, arrays can't be equal
- if (_hashCode != other._hashCode)
- return false;
-
- // We're really unlikely to get here, as we're using an instance cache
- // so we've probably encountered a hash collision
-
- // Compare array lengths
- if (_length != other._length) return false;
-
- // If both are empty, they're equal
- if (_length == 0) return true;
-
- // Element-by-element comparison
- var comparer = EqualityComparer.Default;
- for (int i = 0; i < _length; i++)
- {
- if (!comparer.Equals(_values[i], other._values[i]))
- return false;
- }
-
- return true;
- }
-
- public override bool Equals(object? obj) => obj is EquatableImmutableArray other && Equals(other);
-
- public override int GetHashCode() => _hashCode;
-
- private static int HashHelpers_Combine(int h1, int h2)
- {
- // RyuJIT optimizes this to use the ROL instruction
- // Related GitHub pull request: https://github.com/dotnet/coreclr/pull/1830
- uint rol5 = ((uint)h1 << 5) | ((uint)h1 >> 27);
- return ((int)rol5 + h1) ^ h2;
- }
-
- IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)_values).GetEnumerator();
- IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)_values).GetEnumerator();
-}
-
-public static class EquatableImmutableArrayExtensions
-{
- public static EquatableImmutableArray ToEquatableImmutableArray(this ImmutableArray values, Func selector, CancellationToken ct = default) where T : IEquatable
- {
- var builder = ImmutableArray.CreateBuilder(values.Length);
- foreach (TSource value in values)
- {
- builder.Add(selector(value));
- }
- return EquatableImmutableArray.Create(builder.MoveToImmutable(), ct);
- }
- public static EquatableImmutableArray ToEquatableImmutableArray(this EquatableImmutableArray values, Func selector, CancellationToken ct = default) where TSource : IEquatable where T : IEquatable
- {
- var builder = ImmutableArray.CreateBuilder(values.Count);
- foreach (TSource value in values)
- {
- builder.Add(selector(value));
- }
- return EquatableImmutableArray.Create(builder.MoveToImmutable(), ct);
- }
- public static EquatableImmutableArray ToEquatableImmutableArray(this IEnumerable values, CancellationToken ct = default) where T : IEquatable => EquatableImmutableArray.Create(values.ToImmutableArray(), ct);
-
- public static EquatableImmutableArray ToEquatableImmutableArray(this ImmutableArray values, CancellationToken ct = default) where T : IEquatable => EquatableImmutableArray.Create(values, ct);
-
- public static IncrementalValuesProvider<(TLeft Left, EquatableImmutableArray Right)> CombineEquatable(
- this IncrementalValuesProvider provider1,
- IncrementalValuesProvider provider2)
- where TRight : IEquatable
- => provider1.Combine(provider2.Collect().Select(EquatableImmutableArray.Create));
-
- public static IncrementalValueProvider<(TLeft Left, EquatableImmutableArray Right)> CombineEquatable(
- this IncrementalValueProvider provider1,
- IncrementalValuesProvider provider2)
- where TRight : IEquatable
- => provider1.Combine(provider2.Collect().Select(EquatableImmutableArray.Create));
-
-}
diff --git a/EmbeddedResourcePropertyGenerator/Generator.cs b/EmbeddedResourcePropertyGenerator/Generator.cs
index ac2c696..707c454 100644
--- a/EmbeddedResourcePropertyGenerator/Generator.cs
+++ b/EmbeddedResourcePropertyGenerator/Generator.cs
@@ -1,5 +1,5 @@
-using Microsoft.CodeAnalysis;
-using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Datacute.IncrementalGeneratorExtensions;
+using Microsoft.CodeAnalysis;
using System.Collections.Immutable;
namespace Datacute.EmbeddedResourcePropertyGenerator
@@ -12,29 +12,33 @@ public sealed class Generator : IIncrementalGenerator
{
public void Initialize(IncrementalGeneratorInitializationContext context)
{
- LightweightTrace.Add((int)TrackingNames.GeneratorInitialized);
+ LightweightTrace.MethodEntry(GeneratorStage.Initialize);
- // 1. Base attribute data -> AttributeContext
+ context.RegisterPostInitializationOutput(static postInitializationContext =>
+ {
+ postInitializationContext.AddSource(
+ Templates.AttributeHintName,
+ Templates.EmbeddedResourcePropertiesAttribute);
+ });
+
+ // 1. Base attribute data -> AttributeContextAndData
var attributeContexts =
- context.SyntaxProvider
- .ForAttributeWithMetadataName(
- Templates.AttributeFullyQualified,
- predicate: (node, _) => node is TypeDeclarationSyntax,
- transform: (attributeSyntaxContext, _) => new AttributeContext(attributeSyntaxContext))
- .Trace(TrackingNames.AttributeContextsCreated);
+ context.SelectAttributeContexts(
+ Templates.AttributeFullyQualified,
+ AttributeData.Collector);
// 2. Options -> GeneratorOptions
var options =
context.AnalyzerConfigOptionsProvider
.Select(GeneratorOptions.Select)
- .Trace(TrackingNames.AnalyzerConfigOptions);
+ .WithTrackingName(GeneratorStage.AnalyzerConfigOptionsProviderSelect);
// 3. Combine base attributes and options -> (AttributeContext, GeneratorOptions)
var attributesAndOptions =
attributeContexts
.Combine(options)
.Select(SelectAttributeAndOptions)
- .Trace(TrackingNames.AttributesAndOptionsCombined);
+ .WithTrackingName(TrackingNames.AttributesAndOptionsCombined);
// --- Prepare Resource Matching Data Separately ---
@@ -42,37 +46,33 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
var attributesAndGlobs =
attributesAndOptions
.Select(SelectAttributesAndGlobs)
- .Trace(TrackingNames.AttributeGlobInfoSelected);
+ .WithTrackingName(TrackingNames.AttributeGlobInfoSelected);
// Selects (Path, Extension) from (AttributeContext, Path, Extension)
var attributeGlobs =
attributesAndGlobs
.Select(SelectJustGlobs)
- .Trace(TrackingNames.AttributeGlobsSelected);
+ .WithTrackingName(TrackingNames.AttributeGlobsSelected);
// 4. Find all (AttributeContext, EmbeddedResource) matches
- var matchedResourceAndAttribute =
+ var resourcesByAttributeContextLookup =
context.AdditionalTextsProvider
.Select(SelectFileInfo)
- .Trace(TrackingNames.FileInfoSelected)
+ .WithTrackingName(GeneratorStage.AdditionalTextsProviderSelect)
.CombineEquatable(attributeGlobs)
- .Trace(TrackingNames.FileInfoAndGlobsCombined)
+ .WithTrackingName(TrackingNames.FileInfoAndGlobsCombined)
.Select(SelectAdditionalTextAndGlobWithAttributeGlobs)
.Where(DoesAdditionalTextGlobMatchAnyAttributeGlobs)
- .Trace(TrackingNames.MatchingFilesFiltered)
+ .WithTrackingName(TrackingNames.MatchingFilesFiltered)
.Select(ExtractEmbeddedResourceWithFileInfo)
- .Trace(TrackingNames.EmbeddedResourceExtracted)
+ .WithTrackingName(TrackingNames.EmbeddedResourceExtracted)
.CombineEquatable(attributesAndGlobs)
- .Trace(TrackingNames.ResourceAndAllAttributeGlobsCombined)
+ .WithTrackingName(TrackingNames.ResourceAndAllAttributeGlobsCombined)
.SelectMany(SelectMatchingResourceAndAttribute)
- .Trace(TrackingNames.MatchingResourceAndAttributeSelected);
-
- // 5. Group resources by AttributeContext into a lookup
- var resourcesByAttributeContextLookup =
- matchedResourceAndAttribute
- .Collect().Select(EquatableImmutableArray.Create)
+ .WithTrackingName(TrackingNames.MatchingResourceAndAttributeSelected)
+ .CollectEquatable()
.Select(GroupResourcesByAttributeContext)
- .Trace(TrackingNames.ResourcesGroupedByAttributeContext);
+ .WithTrackingName(TrackingNames.ResourcesGroupedByAttributeContext);
// --- Combine Base Attributes with Grouped Resources (Left Join) ---
@@ -81,29 +81,32 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
attributesAndOptions
.Combine(resourcesByAttributeContextLookup)
.Select(PerformResourceLookup)
- .Trace(TrackingNames.GenerationInputPrepared);
+ .WithTrackingName(TrackingNames.GenerationInputPrepared);
// 7. Register Source Output
- context.RegisterSourceOutput(generationInput,
+ context.RegisterSourceOutput(
+ generationInput,
(sourceProductionContext, inputData) =>
{
- LightweightTrace.Add((int)TrackingNames.GeneratingSourceFile);
+ LightweightTrace.Add(GeneratorStage.RegisterSourceOutput);
var (attributeContext, generatorOptions, embeddedResources) = inputData;
GenerateFolderEmbed(sourceProductionContext, attributeContext, embeddedResources, generatorOptions);
});
+
+ LightweightTrace.MethodExit(GeneratorStage.Initialize);
}
// --- Helper Methods ---
// Selects (AdditionalText, Directory, Extension) from AdditionalText
- private static AttributeAndOptions SelectAttributeAndOptions((AttributeContext AttributeContext, GeneratorOptions Options) attributeAndOptions, CancellationToken _) =>
+ private static AttributeAndOptions SelectAttributeAndOptions((AttributeContextAndData AttributeContext, GeneratorOptions Options) attributeAndOptions, CancellationToken _) =>
new(attributeAndOptions.AttributeContext, attributeAndOptions.Options);
// Selects (AttributeContext, Path, Extension) from (AttributeContext, Options)
private static AttributeAndGlob SelectAttributesAndGlobs(AttributeAndOptions attributeAndOptions, CancellationToken ct)
{
- var resourceSearchPath = GetResourceSearchPath(attributeAndOptions.AttributeContext, attributeAndOptions.Options);
- var glob = new Glob(resourceSearchPath, attributeAndOptions.AttributeContext.ExtensionArg);
+ var resourceSearchPath = GetResourceSearchPath(attributeAndOptions.AttributeContext.AttributeData, attributeAndOptions.Options);
+ var glob = new Glob(resourceSearchPath, attributeAndOptions.AttributeContext.AttributeData.ExtensionArg);
return new AttributeAndGlob(attributeAndOptions.AttributeContext, glob);
}
@@ -139,11 +142,10 @@ private static AdditionalTextGlobsAndResources ExtractEmbeddedResourceWithFileIn
AdditionalTextAndGlobWithAttributeGlobs additionalTextAndGlobWithAttributeGlobs,
CancellationToken ct)
{
- if (ct.IsCancellationRequested) LightweightTrace.Add((int)TrackingNames.Cancel + 8000);
- ct.ThrowIfCancellationRequested();
+ ct.ThrowIfCancellationRequested(TrackingNames.GeneratingDocComment);
var additionalText = additionalTextAndGlobWithAttributeGlobs.AdditionalTextAndGlob.AdditionalText;
- LightweightTrace.Add((int)TrackingNames.GeneratingDocComment + additionalText.Path.Length * 1000);
+ LightweightTrace.Add(TrackingNames.GeneratingDocComment, additionalText.Path.Length);
var docCommentCode = AdditionalTextDocCommentCreator.GenerateDocCommentCode(additionalText, ct);
var embeddedResource = new EmbeddedResource(additionalText.Path, docCommentCode);
@@ -174,10 +176,10 @@ EquatableImmutableArray AttributesAndGlobs
}
// Groups the collected (AttributeContext, EmbeddedResource) into a dictionary lookup
- private static Dictionary> GroupResourcesByAttributeContext(
+ private static Dictionary, EquatableImmutableArray> GroupResourcesByAttributeContext(
EquatableImmutableArray resourceAndAttribute, CancellationToken ct)
{
- var grouped = new Dictionary.Builder>();
+ var grouped = new Dictionary, ImmutableArray.Builder>();
foreach (var (context, resource) in resourceAndAttribute)
{
ct.ThrowIfCancellationRequested();
@@ -198,7 +200,7 @@ private static Dictionary> ResourcesByAttributeContextLookup
+ Dictionary, EquatableImmutableArray> ResourcesByAttributeContextLookup
) attributeOptionsAndLookup,
CancellationToken _)
{
@@ -215,34 +217,34 @@ Dictionary> Resource
private static void GenerateFolderEmbed(
in SourceProductionContext context,
- in AttributeContext attributeContext,
+ in AttributeContextAndData attributeContextAndData,
EquatableImmutableArray embeddedResources,
in GeneratorOptions options)
{
var cancellationToken = context.CancellationToken;
- if (cancellationToken.IsCancellationRequested) LightweightTrace.Add((int)TrackingNames.Cancel + 1000);
- cancellationToken.ThrowIfCancellationRequested();
+ cancellationToken.ThrowIfCancellationRequested(GeneratorStage.SourceProductionContextAddSource);
- var resourceSearchPath = GetResourceSearchPath(attributeContext, options);
+ var resourceSearchPath = GetResourceSearchPath(attributeContextAndData.AttributeData, options);
var codeGenerator = new CodeGenerator(
- attributeContext,
+ attributeContextAndData,
resourceSearchPath,
embeddedResources,
options,
cancellationToken);
- var hintName = attributeContext.DisplayString.GetHintName();
- var source = codeGenerator.GenerateSource();
+ var hintName = attributeContextAndData.CreateHintName("EmbeddedResourceProperties");
+ var source = codeGenerator.GetSourceText();
+ LightweightTrace.Add(GeneratorStage.SourceProductionContextAddSource);
context.AddSource(hintName, source);
}
- private static string GetResourceSearchPath(in AttributeContext attributeContext, in GeneratorOptions options)
+ private static string GetResourceSearchPath(in AttributeData attributeData, in GeneratorOptions options)
{
string baseDir;
string resourceSearchPath;
- var pathArg = attributeContext.PathArg;
+ var pathArg = attributeData.PathArg;
if (pathArg.StartsWith("/") || pathArg.StartsWith("\\"))
{
@@ -251,7 +253,7 @@ private static string GetResourceSearchPath(in AttributeContext attributeContext
}
else
{
- baseDir = Path.GetDirectoryName(attributeContext.FilePath) ?? string.Empty;
+ baseDir = Path.GetDirectoryName(attributeData.FilePath) ?? string.Empty;
resourceSearchPath = pathArg;
}
@@ -266,12 +268,12 @@ private static string GetResourceSearchPath(in AttributeContext attributeContext
}
}
- public record struct AttributeAndOptions(AttributeContext AttributeContext, GeneratorOptions Options);
+ public record struct AttributeAndOptions(AttributeContextAndData AttributeContext, GeneratorOptions Options);
public record struct Glob(string? Directory, string Extension);
- public record struct AttributeAndGlob(AttributeContext AttributeContext, Glob Glob);
+ public record struct AttributeAndGlob(AttributeContextAndData AttributeContext, Glob Glob);
public record struct AdditionalTextAndGlob(AdditionalText AdditionalText, Glob Glob);
public record struct AdditionalTextGlobsAndResources(AdditionalTextAndGlobWithAttributeGlobs FileAndGlobs, EmbeddedResource EmbeddedResource);
public record struct AdditionalTextAndGlobWithAttributeGlobs(AdditionalTextAndGlob AdditionalTextAndGlob, EquatableImmutableArray AttributeGlobs);
- public record struct AttributeAndResource(AttributeContext AttributeContext, EmbeddedResource Resource);
- public record struct AttributeOptionsAndResources(AttributeContext Context, GeneratorOptions Options, EquatableImmutableArray Resources);
+ public record struct AttributeAndResource(AttributeContextAndData AttributeContext, EmbeddedResource Resource);
+ public record struct AttributeOptionsAndResources(AttributeContextAndData Context, GeneratorOptions Options, EquatableImmutableArray Resources);
}
diff --git a/EmbeddedResourcePropertyGenerator/GeneratorOptions.cs b/EmbeddedResourcePropertyGenerator/GeneratorOptions.cs
index fc59215..71b996c 100644
--- a/EmbeddedResourcePropertyGenerator/GeneratorOptions.cs
+++ b/EmbeddedResourcePropertyGenerator/GeneratorOptions.cs
@@ -1,4 +1,6 @@
-using Microsoft.CodeAnalysis.Diagnostics;
+using Datacute.IncrementalGeneratorExtensions;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Diagnostics;
namespace Datacute.EmbeddedResourcePropertyGenerator
{
@@ -19,8 +21,7 @@ private GeneratorOptions(AnalyzerConfigOptions options)
public static GeneratorOptions Select(AnalyzerConfigOptionsProvider provider, CancellationToken token)
{
- if (token.IsCancellationRequested) LightweightTrace.Add((int)TrackingNames.Cancel + 7000);
- token.ThrowIfCancellationRequested();
+ token.ThrowIfCancellationRequested(GeneratorStage.AnalyzerConfigOptionsProviderSelect);
return new GeneratorOptions(provider.GlobalOptions);
}
}
diff --git a/EmbeddedResourcePropertyGenerator/LightweightTrace.cs b/EmbeddedResourcePropertyGenerator/LightweightTrace.cs
deleted file mode 100644
index a3aaf60..0000000
--- a/EmbeddedResourcePropertyGenerator/LightweightTrace.cs
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (c) 2025 Stephen Denne
- * https://github.com/datacute/LightweightTracing
- */
-
-using System.Diagnostics;
-using System.Text;
-using Microsoft.CodeAnalysis;
-
-namespace Datacute.EmbeddedResourcePropertyGenerator;
-
-public static class LightweightTrace
-{
- private const int Capacity = 1024;
-
- private static readonly DateTime StartTime = DateTime.UtcNow;
- private static readonly Stopwatch Stopwatch = Stopwatch.StartNew();
-
- private static readonly (long, int)[] Events = new (long, int)[Capacity];
- private static int _index;
-
- public static void Add(int eventId)
- {
- var index = Interlocked.Increment(ref _index) % Capacity;
- Events[index] = (Stopwatch.ElapsedTicks, eventId);
- }
-
- public static void GetTrace(StringBuilder stringBuilder, Dictionary eventNameMap)
- {
- var index = _index;
- for (var i = 0; i < Capacity; i++)
- {
- index = (index + 1) % Capacity;
- var (timestamp, eventId) = Events[index];
- if (timestamp > 0)
- {
- string text;
- string item = string.Empty;
- if (eventId > 1000)
- {
- item = $" ({eventId / 1000})";
- }
- text = eventNameMap.TryGetValue(eventId % 1000, out var name) ? name : string.Empty;
- stringBuilder.AppendFormat("{0:o} [{1:000}] {2} {3}",
- StartTime.AddTicks(timestamp),
- eventId % 1000,
- text,
- item)
- .AppendLine();
- }
- }
- }
-}
-
-public static class LightweightTraceExtensions
-{
- public static IncrementalValuesProvider Trace(this IncrementalValuesProvider source, TrackingNames eventId) =>
- source.Select((input, _) =>
- {
- LightweightTrace.Add((int)eventId);
-
- return input;
- }).WithTrackingName(Enum.GetName(typeof(TrackingNames), eventId) ?? $"({eventId})");
-
- public static IncrementalValueProvider Trace(this IncrementalValueProvider source, TrackingNames eventId) =>
- source.Select((input, _) =>
- {
- LightweightTrace.Add((int)eventId);
-
- return input;
- }).WithTrackingName(Enum.GetName(typeof(TrackingNames), eventId) ?? $"({eventId})");
-}
\ No newline at end of file
diff --git a/EmbeddedResourcePropertyGenerator/Templates.cs b/EmbeddedResourcePropertyGenerator/Templates.cs
index cf9544a..3e6ca59 100644
--- a/EmbeddedResourcePropertyGenerator/Templates.cs
+++ b/EmbeddedResourcePropertyGenerator/Templates.cs
@@ -1,91 +1,95 @@
-namespace Datacute.EmbeddedResourcePropertyGenerator
+using Datacute.AdditionalTextConstantGenerator;
+
+namespace Datacute.EmbeddedResourcePropertyGenerator
{
- internal static class Templates
+ [AdditionalTextConstants(".cs", "Templates")]
+ internal static partial class Templates
{
private const string GeneratorNamespace = "Datacute.EmbeddedResourcePropertyGenerator";
private const string AttributeName = "EmbeddedResourcePropertiesAttribute";
public const string AttributeFullyQualified = GeneratorNamespace + "." + AttributeName;
- public const string AutoGeneratedComment = /* language=c# */
- @"//------------------------------------------------------------------------------
-//
-// This code was generated by the Datacute.EmbeddedResourcePropertyGenerator.
-//
-//------------------------------------------------------------------------------
-
-#nullable enable
-";
+ public const string AttributeHintName =
+ "Datacute.EmbeddedResourcePropertyGenerator.EmbeddedResourcePropertiesAttribute.g.cs";
public const string StartEmbeddedResourceClass = /* language=c# */
"private static class EmbeddedResource";
public const string ReadMethod = /* language=c# */
- @"{0}public static string Read(string resourceName)
-{0}{{
-{0}{1}var assembly = typeof({2}).Assembly;
-{0}{1}using var stream = assembly.GetManifestResourceStream(resourceName)!;
-{0}{1}using var streamReader = new global::System.IO.StreamReader(stream, global::System.Text.Encoding.UTF8);
-{0}{1}var resourceText = streamReader.ReadToEnd();
-{0}{1}return resourceText;
-{0}}}
+ @"public static string Read(string resourceName)
+{{
+{0}var assembly = typeof({1}).Assembly;
+{0}using var stream = assembly.GetManifestResourceStream(resourceName)!;
+{0}using var streamReader = new global::System.IO.StreamReader(stream, global::System.Text.Encoding.UTF8);
+{0}var resourceText = streamReader.ReadToEnd();
+{0}return resourceText;
+}}
";
public const string BackingFieldClass = /* language=c# */
"public static class BackingField";
public const string BackingField = /* language=c# */
- "{0}public static string? {1};";
+ "public static string? {0};";
+
+ public const string BackingFieldNullableNotSupported = /* language=c# */
+ "public static string {0};";
public const string ResourceNameClass = /* language=c# */
"public static class ResourceName";
public const string ResourceName = /* language=c# */
- @"{0}public const string {1} = ""{2}"";
-";
+ @"public const string {0} = ""{1}"";";
public const string ClassDocComments = /* language=c# */
- @"{0}///
-{0}/// This class's properties are generated from project files meeting the criteria:
-{0}///
-{0}/// -
-{0}/// they are both an EmbeddedResource and an AdditionalFile
-{0}///
-{0}/// -
-{0}/// they are in the project folder {1}
-{0}///
-{0}/// -
-{0}/// they have the extension {2}
-{0}///
-{0}///
-{0}///
+ @"///
+/// This class's properties are generated from project files meeting the criteria:
+///
+/// -
+/// they are an EmbeddedResource
+///
+/// -
+/// they are in the project folder {0}
+///
+/// -
+/// they have the extension {1}
+///
+/// -
+/// Number of matching resources: {2}
+///
+///
+///
";
public const string PartialReadMethods = /* language=c# */
"static partial void ReadEmbeddedResourceValue(ref string? backingField, string resourceName, string propertyName);";
+ public const string PartialReadMethodsNullableNotSupported = /* language=c# */
+ "static partial void ReadEmbeddedResourceValue(ref string backingField, string resourceName, string propertyName);";
+
public const string PartialAlterMethods = /* language=c# */
"static partial void AlterEmbeddedResourceReturnValue(ref string value, string resourceName, string propertyName);";
public const string PropertyTemplate = /* language=c# */
@"
-{0}/// Text value of the Embedded Resource: {3}
-{0}///
-{0}/// {4}
-{0}///
-{0}///
-{0}///
-{0}/// The value is read from the embedded resource on first access.
-{0}///
-{0}public static string {2}
+/// Text value of the Embedded Resource: {2}
+///
+/// {3}
+///
+///
+///
+/// The value is read from the embedded resource on first access.
+///
+public static string {1}
+{{
+{0}get
{0}{{
-{0}{1}get
-{0}{1}{{
-{0}{1}{1}ReadEmbeddedResourceValue(ref EmbeddedResource.BackingField.{2}, EmbeddedResource.ResourceName.{2}, ""{2}"");
-{0}{1}{1}var value = EmbeddedResource.BackingField.{2} ??= EmbeddedResource.Read(EmbeddedResource.ResourceName.{2});
-{0}{1}{1}AlterEmbeddedResourceReturnValue(ref value, EmbeddedResource.ResourceName.{2}, ""{2}"");
-{0}{1}{1}return value;
-{0}{1}}}
+{0}{0}ReadEmbeddedResourceValue(ref EmbeddedResource.BackingField.{1}, EmbeddedResource.ResourceName.{1}, ""{1}"");
+{0}{0}var value = EmbeddedResource.BackingField.{1} ??= EmbeddedResource.Read(EmbeddedResource.ResourceName.{1});
+{0}{0}AlterEmbeddedResourceReturnValue(ref value, EmbeddedResource.ResourceName.{1}, ""{1}"");
+{0}{0}return value;
{0}}}
+}}
";
public const string DesignTimePropertyTemplate = /* language=c# */
diff --git a/EmbeddedResourcePropertyGenerator/Templates/EmbeddedResourcePropertiesAttribute.cs b/EmbeddedResourcePropertyGenerator/Templates/EmbeddedResourcePropertiesAttribute.cs
new file mode 100644
index 0000000..4b9fca8
--- /dev/null
+++ b/EmbeddedResourcePropertyGenerator/Templates/EmbeddedResourcePropertiesAttribute.cs
@@ -0,0 +1,131 @@
+using System;
+
+// ReSharper disable UnusedParameter.Local
+// ReSharper disable UnusedAutoPropertyAccessor.Global
+
+namespace Datacute.EmbeddedResourcePropertyGenerator
+{
+ ///
+ /// Use a source generator to add properties to this class for each embedded resource file
+ /// with a filename matching the given Extension
+ /// found in the given Path.
+ ///
+ /// If the path starts with "/" it is relative to the project root,
+ /// otherwise it is relative to the folder containing the class with this attribute.
+ /// If the path is not specified, the class name is used.
+ ///
+ ///
+ ///
+ ///
+ /// The generated code includes a private nested class EmbeddedResource containing:
+ ///
+ /// Method or ClassPurpose
+ /// - Read(string resourceName)Method for reading embedded resources
+ /// - BackingFieldNested class caching the property values
+ /// - ResourceNameNested class holding the resource names
+ ///
+ ///
+ ///
+ /// The generated code supports customising the behaviour of the property getters in two ways, with two partial methods
+ /// ReadEmbeddedResourceValue and AlterEmbeddedResourceReturnValue.
+ ///
+ ///
+ /// If the partial methods are not implemented, the code effectively reduces to:
+ ///
+ /// public static string Example =>
+ /// EmbeddedResource.BackingField.Example ??= EmbeddedResource.Read(EmbeddedResource.ResourceName.Example);
+ ///
+ ///
+ ///
+ /// Partial methods:
+ ///
+ /// - To allow customisation of how the backing field is read
+ /// (for example to check the file system for an override)
+ /// implement a partial method that will be called before the above, with the signature:
+ ///
+ /// static partial void ReadEmbeddedResourceValue(ref string? backingField, string resourceName, string propertyName);
+ ///
+ ///
+ /// -
+ /// To allow customisation of the returned value, based on the backing field,
+ /// implement a partial method with the signature:
+ ///
+ /// static partial void AlterEmbeddedResourceReturnValue(ref string value, string resourceName, string propertyName);
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// This is an example of the code generated for a property, showing how the partial methods are called:
+ ///
+ /// public static string Example
+ /// {
+ /// get
+ /// {
+ /// ReadEmbeddedResourceValue(ref EmbeddedResource.BackingField.Example, EmbeddedResource.ResourceName.Example, "Example");
+ /// var value = EmbeddedResource.BackingField.Example ??= EmbeddedResource.Read(EmbeddedResource.ResourceName.Example);
+ /// AlterEmbeddedResourceReturnValue(ref value, EmbeddedResource.ResourceName.Example, "Example");
+ /// return value;
+ /// }
+ /// }
+ ///
+ ///
+ ///
+ [System.Diagnostics.Conditional("DATACUTE_EMBEDDEDRESOURCEPROPERTIES_USAGES")]
+ [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
+ internal sealed class EmbeddedResourcePropertiesAttribute : Attribute
+ {
+ /// The filename extension of the embedded resource files
+ /// to include as properties, defaulting to ".txt".
+ public string Extension { get; set; }
+
+ /// The path of the directory containing embedded resource files
+ /// to include as properties.
+ ///
+ /// If the path starts with "/" it is treated as relative to the project root,
+ /// otherwise it is relative to the folder containing the class with this attribute.
+ /// If the path is not specified, the class name is used.
+ ///
+ public string Path { get; set; }
+
+ ///
+ /// Output a diagnostic trace log as a comment at the end of the generated files
+ ///
+ ///
+ /// The log shows timestamps for each step of the Embedded Resource Property incremental source code generation process,
+ ///
+ public bool DiagnosticTraceLog { get; set; }
+
+ ///
+ /// Use a source generator to add properties to this class for each embedded resource file
+ /// found in the path with the folder name the same as this class,
+ /// and where the file name's extension is ".txt"
+ ///
+ public EmbeddedResourcePropertiesAttribute() : this(".txt", null)
+ {
+ }
+
+ ///
+ /// Use a source generator to add properties to this class for each embedded resource file
+ /// found in the path with the folder name the same as this class,
+ /// and where the file name's extension matches the given extension.
+ ///
+ /// The file name extension to include
+ public EmbeddedResourcePropertiesAttribute(string extension = ".txt") : this(extension, null)
+ {
+ }
+
+ ///
+ /// Use a source generator to add properties to this class for each embedded resource file
+ /// found in the specified path,
+ /// and where the file name's extension matches the given extension.
+ ///
+ /// The file name extension to include
+ /// The folder path to include
+ public EmbeddedResourcePropertiesAttribute(string extension = ".txt", string path = null)
+ {
+ Extension = extension;
+ Path = path;
+ }
+ }
+}
\ No newline at end of file
diff --git a/EmbeddedResourcePropertyGenerator/TrackingNames.cs b/EmbeddedResourcePropertyGenerator/TrackingNames.cs
index dd2becc..81df581 100644
--- a/EmbeddedResourcePropertyGenerator/TrackingNames.cs
+++ b/EmbeddedResourcePropertyGenerator/TrackingNames.cs
@@ -1,60 +1,69 @@
-using System.Collections.Generic; // Required for Dictionary
+using Datacute.IncrementalGeneratorExtensions;
namespace Datacute.EmbeddedResourcePropertyGenerator
{
public enum TrackingNames
{
- GeneratorInitialized = 0,
- Cancel = 1,
-
// Pipeline Stages
- AttributeContextsCreated = 10, // Step 1: AttributeContexts created
- AnalyzerConfigOptions = 20, // Step 2: Options selected
- AttributesAndOptionsCombined = 30, // Step 3: Attributes and Options combined
- FileInfoSelected = 40, // Step 4a: AdditionalText -> FileInfo (Path, Dir, Ext)
- AttributeGlobInfoSelected = 41, // Helper: Path/Extension selected for attributes
- AttributeGlobsSelected = 42, // Helper: Just Path/Extension for resource matching
- FileInfoAndGlobsCombined = 43, // Step 4b: FileInfo combined with ResourceGlobs
- MatchingFilesFiltered = 44, // Step 4c: Files filtered by glob match
- EmbeddedResourceExtracted = 46, // Step 4d: EmbeddedResource created (with FileAndGlobs info)
- ResourceAndAllAttributeGlobsCombined = 47, // Step 4e: Resource/File data combined with all AttributeGlobInfo
- MatchingResourceAndAttributeSelected = 48, // Step 4f: (AttributeContext, EmbeddedResource) selected
- ResourcesGroupedByAttributeContext = 50, // Step 5: Resources grouped into lookup
- GenerationInputPrepared = 60, // Step 6: Final data prepared for output (AttributeContext, Resources, Options)
+ AttributeContextsCreated = 110, // Step 1: AttributeContexts created
+ AnalyzerConfigOptions = 120, // Step 2: Options selected
+ AttributesAndOptionsCombined = 130, // Step 3: Attributes and Options combined
+ FileInfoSelected = 140, // Step 4a: AdditionalText -> FileInfo (Path, Dir, Ext)
+ AttributeGlobInfoSelected = 141, // Helper: Path/Extension selected for attributes
+ AttributeGlobsSelected = 142, // Helper: Just Path/Extension for resource matching
+ FileInfoAndGlobsCombined = 143, // Step 4b: FileInfo combined with ResourceGlobs
+ MatchingFilesFiltered = 144, // Step 4c: Files filtered by glob match
+ EmbeddedResourceExtracted = 146, // Step 4d: EmbeddedResource created (with FileAndGlobs info)
+ ResourceAndAllAttributeGlobsCombined = 147, // Step 4e: Resource/File data combined with all AttributeGlobInfo
+ MatchingResourceAndAttributeSelected = 148, // Step 4f: (AttributeContext, EmbeddedResource) selected
+ ResourcesGroupedByAttributeContext = 150, // Step 5: Resources grouped into lookup
+ GenerationInputPrepared = 160, // Step 6: Final data prepared for output (AttributeContext, Resources, Options)
// Execution/Action Steps
- GeneratingDocComment = 45, // Action: Doc comment generation started
- GeneratingSourceFile = 70, // Step 7: Source generation started
- DiagnosticTraceLogWritten = 80 // Action: Trace log output written (if enabled)
+ GeneratingDocComment = 145, // Action: Doc comment generation started
+ DiagnosticTraceLogWritten = 180 // Action: Trace log output written (if enabled)
}
- public static class TrackingNameDescriptions // Made static as it only contains static members
+ ///
+ /// Static class for tracking names and their descriptions.
+ ///
+ public static class TrackingNameDescriptions
{
- // Updated dictionary with new names and descriptions
- public static readonly Dictionary EventNameMap = new()
- {
- { (int)TrackingNames.GeneratorInitialized, "Generator Initialized" },
- { (int)TrackingNames.Cancel, "Operation Cancelled" },
+ ///
+ /// Gets a dictionary mapping event and counter IDs to their human-readable names.
+ ///
+ public static Dictionary EventNameMap => LazyEventNameMap.Value;
- // Pipeline Stages
- { (int)TrackingNames.AttributeContextsCreated, "Created AttributeContexts" },
- { (int)TrackingNames.AnalyzerConfigOptions, "Selected GeneratorOptions" },
- { (int)TrackingNames.AttributesAndOptionsCombined, "Combined Attributes and Options" },
- { (int)TrackingNames.FileInfoSelected, "Selected File Info (Path/Dir/Ext) from AdditionalText" },
- { (int)TrackingNames.AttributeGlobInfoSelected, "Selected Attribute Glob Info (Path/Ext)" },
- { (int)TrackingNames.AttributeGlobsSelected, "Selected Attribute Globs (Path/Ext)" },
- { (int)TrackingNames.FileInfoAndGlobsCombined, "Combined File Info and Resource Globs" },
- { (int)TrackingNames.MatchingFilesFiltered, "Filtered Files Matching Globs" },
- { (int)TrackingNames.EmbeddedResourceExtracted, "Extracted EmbeddedResource (with File/Glob info)" },
- { (int)TrackingNames.ResourceAndAllAttributeGlobsCombined, "Combined Resource/File Data and All Attribute Glob Info" },
- { (int)TrackingNames.MatchingResourceAndAttributeSelected, "Selected Matching (AttributeContext, EmbeddedResource)" },
- { (int)TrackingNames.ResourcesGroupedByAttributeContext, "Grouped Resources by AttributeContext into Lookup" },
- { (int)TrackingNames.GenerationInputPrepared, "Prepared Final Generation Input (AttrContext, Resources, Options)" },
+ // GeneratorStageDescriptions.GeneratorStageNameMap is not available when this static class is initialized,
+ // so we use a Lazy to ensure that the dictionary is created only when it's first accessed.
+ // at which time the GeneratorStageDescriptions.GeneratorStageNameMap will be available.
+ private static readonly Lazy> LazyEventNameMap = new Lazy>(CreateEventNameMap);
+
+ private static Dictionary CreateEventNameMap()
+ {
+ var map = new Dictionary(GeneratorStageDescriptions.GeneratorStageNameMap)
+ {
+ // Pipeline Stages
+ { (int)TrackingNames.AttributeContextsCreated, "Created AttributeContexts" },
+ { (int)TrackingNames.AnalyzerConfigOptions, "Selected GeneratorOptions" },
+ { (int)TrackingNames.AttributesAndOptionsCombined, "Combined Attributes and Options" },
+ { (int)TrackingNames.FileInfoSelected, "Selected File Info (Path/Dir/Ext) from AdditionalText" },
+ { (int)TrackingNames.AttributeGlobInfoSelected, "Selected Attribute Glob Info (Path/Ext)" },
+ { (int)TrackingNames.AttributeGlobsSelected, "Selected Attribute Globs (Path/Ext)" },
+ { (int)TrackingNames.FileInfoAndGlobsCombined, "Combined File Info and Resource Globs" },
+ { (int)TrackingNames.MatchingFilesFiltered, "Filtered Files Matching Globs" },
+ { (int)TrackingNames.EmbeddedResourceExtracted, "Extracted EmbeddedResource (with File/Glob info)" },
+ { (int)TrackingNames.ResourceAndAllAttributeGlobsCombined, "Combined Resource/File Data and All Attribute Glob Info" },
+ { (int)TrackingNames.MatchingResourceAndAttributeSelected, "Selected Matching (AttributeContext, EmbeddedResource)" },
+ { (int)TrackingNames.ResourcesGroupedByAttributeContext, "Grouped Resources by AttributeContext into Lookup" },
+ { (int)TrackingNames.GenerationInputPrepared, "Prepared Final Generation Input (AttrContext, Resources, Options)" },
+
+ // Execution/Action Steps
+ { (int)TrackingNames.GeneratingDocComment, "Generating Doc Comment" },
+ { (int)TrackingNames.DiagnosticTraceLogWritten, "Diagnostic Trace Log Written" },
+ };
- // Execution/Action Steps
- { (int)TrackingNames.GeneratingDocComment, "Generating Doc Comment" },
- { (int)TrackingNames.GeneratingSourceFile, "Generating Source File" },
- { (int)TrackingNames.DiagnosticTraceLogWritten, "Diagnostic Trace Log Written" },
- };
+ return map;
+ }
}
}
\ No newline at end of file
diff --git a/global.json b/global.json
new file mode 100644
index 0000000..9e0754e
--- /dev/null
+++ b/global.json
@@ -0,0 +1,6 @@
+{
+ "sdk": {
+ "version": "8.0.0",
+ "rollForward": "latestFeature"
+ }
+}
\ No newline at end of file
diff --git a/version.props b/version.props
index 3f889ad..155ac49 100644
--- a/version.props
+++ b/version.props
@@ -1,7 +1,14 @@
- 1.1.0
- $(VersionPrefix)
- $(VersionPrefix)-$(VersionSuffix)
+ 1.1.1
+
+
+
+ local
+
+
+
+
+
\ No newline at end of file
diff --git a/versionsuffix.ci.props b/versionsuffix.ci.props
new file mode 100644
index 0000000..0c3f45c
--- /dev/null
+++ b/versionsuffix.ci.props
@@ -0,0 +1,5 @@
+
+
+ alpha.$(GITHUB_RUN_NUMBER)
+
+
\ No newline at end of file
diff --git a/versionsuffix.local.props b/versionsuffix.local.props
new file mode 100644
index 0000000..b4f5215
--- /dev/null
+++ b/versionsuffix.local.props
@@ -0,0 +1,9 @@
+
+
+ dev-$([System.DateTime]::UtcNow.ToString("yyyyMMdd-HHmmss"))
+
+
+
+ dev
+
+
\ No newline at end of file
diff --git a/versionsuffix.release.props b/versionsuffix.release.props
new file mode 100644
index 0000000..759a431
--- /dev/null
+++ b/versionsuffix.release.props
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file