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