diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ca9f810..ae826a8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,28 +35,36 @@ jobs: with: dotnet-version: '9.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 352071f..bcbf610 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 -p:ContinuousIntegrationBuild=true - dotnet pack *.sln --configuration Release --no-build --output ./artifacts -p:ContinuousIntegrationBuild=true + 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 9d5d360..c36a412 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,11 +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.0.0] - 2025-08-03 + +### Changed + +- Massive simplification by using Datacute.IncrementalGeneratorExtensions +- Changed to use a builder style pattern for the pipeline Combines. +- Replaced Numerous Attribute "Include x" Properties with a single flags enum. +- Generate flags enum and attribute rather than including them in the package. (This results in more consistent package treatment as a source generator.) + +### Added + +- More docs + ## [0.0.2-alpha] - 2025-07-12 ### Fixed @@ -58,5 +71,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - MetadataReferencesProvider's MetadataReference details - Diagnostic log of the source generation process and timing included. -[Unreleased]: https://github.com/datacute/SourceGeneratorContext/compare/0.0.1-alpha...develop -[0.0.1-alpha]: https://github.com/datacute/SourceGeneratorContext/releases/0.0.1-alpha \ No newline at end of file +[Unreleased]: https://github.com/datacute/SourceGeneratorContext/compare/1.0.0...develop +[1.0.0]: https://github.com/datacute/SourceGeneratorContext/releases/tag/1.0.0 +[0.0.2-alpha]: https://github.com/datacute/SourceGeneratorContext/releases/tag/0.0.2-alpha +[0.0.1-alpha]: https://github.com/datacute/SourceGeneratorContext/releases/tag/0.0.1-alpha diff --git a/README.md b/README.md index e7a622e..3896051 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,331 @@ -# SourceGeneratorContext -A source generator to help creators of source generators, by creating doc-comments showing the available generation context. +# Datacute Source Generator Context +A source generator to help creators of source generators learn what information is available, +by creating doc-comments showing the available generation context. + +# Audience + +This source generator is intended for developers creating source generators, +who want to understand the context available to them during the generation process. + +# Installation + +Add the NuGet package `Datacute.SourceGeneratorContext` + +The package includes both the source generator and a marker attribute dll. +The marker attribute is used to indicate whereabouts in your project that the +source generator context should be generated. + +The marker attribute does not need to be included in your project's output assembly, +so the package reference in you `.csproj` file can be marked as `PrivateAssets="all" ExcludeAssets="runtime"` as follows: + +```xml + + + +``` + +# Usage + +To use the source generator, create a partial class in your project and decorate it with the `[SourceGeneratorContext]` attribute. + +```csharp +using Datacute.SourceGeneratorContext; + +[SourceGeneratorContext] +public partial class MySourceGeneratorContext +{ + // This class will be populated with doc-comments showing the available context for source generation. +} +``` + +# Attribute Flags + +The `SourceGeneratorContext` attribute can be configured with flags to control which parts of the context are included in the generated output. + +If any value is null or empty, the row is not included in the generated output. + +### No flags or `IncludeFlags.Summary` (default) +If no other specific flags are set, `IncludeFlags.AttributeContextTargetSymbol` is used. + +(This will change in a future version to be a small selection of various properties.) + +### `IncludeFlags.AttributeContextTargetSymbol` +Includes details about the GeneratorAttributeSyntaxContext.TargetSymbol. This is the default information provided if no flags are specified (see `Summary`). + +| Property | Value | +|---------------------------------------|---------------------------------------------------------------| +| Type | The runtime Type of this symbol | +| Kind | The kind of this symbol | +| Language | The language (C# or VB) that this symbol was declared in | +| DeclaredAccessibility | The declared accessibility of the symbol | +| ContainingSymbol Kind | The kind of the containing symbol | +| ContainingSymbol Name | The name of the containing symbol | +| ContainingAssembly Name | The name of the containing assembly | +| ContainingModule Name | The name of the containing module | +| ContainingType Name | The name of the containing type | +| ContainingType Generic Types | The number of generic type parameters for the containing type | +| ContainingType Generic Type n | The display string of the generic type parameter | +| ContainingNamespace Name | The name of the containing namespace | +| ContainingNamespace IsGlobalNamespace | True if the containing namespace is the global namespace | +| Name | The name of the symbol | +| MetadataName | The name of the symbol as it appears in metadata | +| MetadataToken | The metadata token of the symbol | +| IsDefinition | True if this symbol is a definition | +| IsStatic | True if this symbol is static | +| IsVirtual | True if this symbol is virtual | +| IsOverride | True if this symbol is an override | +| IsAbstract | True if this symbol is abstract (or an interface) | +| IsSealed | True if this symbol is sealed (or a struct) | +| IsExtern | True if this symbol is extern | +| IsImplicitlyDeclared | True if this symbol was implicitly declared | +| CanBeReferencedByName | True if this symbol can be referenced by name | + +### `IncludeFlags.AttributeContextTypeSymbol` +Includes details about the GeneratorAttributeSyntaxContext.TargetSymbol when cast to an ITypeSymbol. + +| Property | Value | +|----------------------|-------------------------------------------------| +| TypeKind | The kind of the type | +| BaseType Name | The name of the base type | +| Interfaces Length | The number of directly implemented interfaces | +| Interface n | The display string of the implemented interface | +| AllInterfaces Length | The number of all implemented interfaces | +| AllInterfaces n | The display string of the implemented interface | +| IsReferenceType | True if the type is a reference type | +| IsValueType | True if the type is a value type | +| IsAnonymousType | True if the type is an anonymous type | +| IsTupleType | True if the type is a tuple type | +| IsNativeIntegerType | True if the type is a native integer type | +| SpecialType | The special type of the type | +| IsRefLikeType | True if the type is a ref-like type | +| IsUnmanagedType | True if the type is an unmanaged type | +| IsReadOnly | True if the type is read-only | +| IsRecord | True if the type is a record | +| NullableAnnotation | The nullable annotation of the type | + +### `IncludeFlags.AttributeContextNamedTypeSymbol` +Includes details about the GeneratorAttributeSyntaxContext.TargetSymbol when cast to an INamedTypeSymbol. + +| Property | Value | +|------------------------------|--------------------------------------------------| +| Arity | The number of type parameters | +| IsGenericType | True if the type is generic | +| IsUnboundGenericType | True if the type is an unbound generic type | +| IsScriptClass | True if the type is a script class | +| IsImplicitClass | True if the type is an implicit class | +| IsComImport | True if the type is a COM import | +| IsFileLocal | True if the type is a file-local type | +| MemberNames | The number of member names | +| TypeParameters | The number of type parameters | +| TypeParameter n | The name of the type parameter | +| InstanceConstructors | The number of instance constructors | +| StaticConstructors | The number of static constructors | +| MightContainExtensionMethods | True if the type might contain extension methods | +| IsSerializable | True if the type is serializable | + +### `IncludeFlags.AttributeContextTargetNode` +Includes details about the GeneratorAttributeSyntaxContext.TargetNode. + +| Property | Value | +|---------------------|---------------------------------------------------| +| Type | The runtime Type of this node | +| RawKind | The raw kind of the node | +| Kind | The kind of the node | +| Language | The language of the node | +| Span.Start | The start position of the node in the source text | +| Span.Length | The length of the node in the source text | +| ContainsAnnotations | True if the node contains annotations | +| ContainsDiagnostics | True if the node contains diagnostics | +| ContainsDirectives | True if the node contains directives | +| ContainsSkippedText | True if the node contains skipped text | +| IsMissing | True if the node is missing | +| HasLeadingTrivia | True if the node has leading trivia | +| HasStructuredTrivia | True if the node has structured trivia | +| HasTrailingTrivia | True if the node has trailing trivia | +| IsStructuredTrivia | True if the node is structured trivia | + +### `IncludeFlags.AttributeContextAttributes` +Includes details about the GeneratorAttributeSyntaxContext.Attributes. + +| Property | Value | +|--------------------------------------------|-------------------------------------------| +| Attribute Count | The number of attributes | +| [n] AttributeClass | The display string of the attribute class | +| [n] ConstructorArguments Count | The number of constructor arguments | +| [n] AttributeConstructor Parameters x Name | The name of the constructor parameter | +| [n] ConstructorArgument x Kind | The kind of the constructor argument | +| [n] ConstructorArgument x Type | The name of the constructor argument type | +| [n] ConstructorArgument x Value | The value of the constructor argument | +| [n] NamedArguments Count | The number of named arguments | +| [n] NamedArgument x Key | The name of the named argument | +| [n] NamedArgument x Value Kind | The kind of the named argument value | +| [n] NamedArgument x Value Type | The name of the named argument value type | +| [n] NamedArgument x Value Value | The value of the named argument | + +### `IncludeFlags.AttributeContextAllAttributes` +Includes details about all attributes applied to the target symbol (using GetAttributes()). + +| Property | Value | +|--------------------------------------------|-------------------------------------------| +| Attribute Count | The number of attributes | +| [n] AttributeClass | The display string of the attribute class | +| [n] ConstructorArguments Count | The number of constructor arguments | +| [n] AttributeConstructor Parameters x Name | The name of the constructor parameter | +| [n] ConstructorArgument x Kind | The kind of the constructor argument | +| [n] ConstructorArgument x Type | The name of the constructor argument type | +| [n] ConstructorArgument x Value | The value of the constructor argument | +| [n] NamedArguments Count | The number of named arguments | +| [n] NamedArgument x Key | The name of the named argument | +| [n] NamedArgument x Value Kind | The kind of the named argument value | +| [n] NamedArgument x Value Type | The name of the named argument value type | +| [n] NamedArgument x Value Value | The value of the named argument | + +### `IncludeFlags.GlobalOptions` +Includes details from the `AnalyzerConfigOptionsProvider`'s `GlobalOptions`. Each key-value pair in the options will be added to the generated output. + +### `IncludeFlags.Compilation` +Includes general details about the Compilation. + +| Property | Value | +|-----------------------|-------------------------------------------| +| AssemblyName | The name of the assembly | +| Language | The language of the compilation | +| IsCaseSensitive | True if the compilation is case-sensitive | +| DynamicType | The dynamic type | +| GlobalNamespace | The global namespace | +| ObjectType | The object type | +| ScriptClass | The script class | +| SourceModule | The source module | +| ScriptCompilationInfo | The script compilation info | +| SyntaxTrees Count | The number of syntax trees | + +### `IncludeFlags.CompilationOptions` +Includes details about the Compilation's options. + +| Property | Value | +|-----------------------------------|---------------------------------------------------| +| Language | The language of the options | +| OutputKind | The output kind of the compilation | +| ModuleName | The name of the module | +| MainTypeName | The name of the main type | +| ScriptClassName | The name of the script class | +| CryptoKeyContainer | The crypto key container | +| CryptoKeyFile | The crypto key file | +| CryptoPublicKey Length | The length of the crypto public key | +| DelaySign | True if delay signing is enabled | +| CheckOverflow | True if overflow checking is enabled | +| Platform | The platform of the compilation | +| GeneralDiagnosticOption | The general diagnostic option | +| WarningLevel | The warning level | +| ReportSuppressedDiagnostics | True if suppressed diagnostics should be reported | +| OptimizationLevel | The optimization level | +| ConcurrentBuild | True if concurrent build is enabled | +| Deterministic | True if the build is deterministic | +| MetadataImportOptions | The metadata import options | +| PublicSign | True if public signing is enabled | +| NullableContextOptions | The nullable context options | +| SpecificDiagnosticOptions Count | The number of specific diagnostic options | +| SpecificDiagnosticOptions '{key}' | The value of the specific diagnostic option | + +### `IncludeFlags.CompilationAssembly` +Includes details about the Compilation's assembly. + +| Property | Value | +|------------------------------|------------------------------------------------------| +| Identity Name | The name of the assembly identity | +| Identity Version | The version of the assembly identity | +| Identity CultureName | The culture name of the assembly identity | +| Identity Flags | The flags of the assembly identity | +| Identity ContentType | The content type of the assembly identity | +| Identity HasPublicKey | True if the assembly identity has a public key | +| Identity PublicKey Length | The length of the public key | +| Identity IsStrongName | True if the assembly identity has a strong name | +| Identity IsRetargetable | True if the assembly identity is retargetable | +| IsInteractive | True if the assembly is interactive | +| Modules Count | The number of modules | +| TypeNames Count | The number of type names | +| NamespaceNames Count | The number of namespace names | +| MightContainExtensionMethods | True if the assembly might contain extension methods | +| Language | The language of the assembly | +| Name | The name of the assembly | +| MetadataName | The metadata name of the assembly | +| MetadataToken | The metadata token of the assembly | +| Locations Length | The number of locations | +| DeclaredAccessibility | The declared accessibility of the assembly | + +### `IncludeFlags.CompilationReferences` +Includes counts and names of Compilation's references (e.g., References, DirectiveReferences). + +| Property | Value | +|-------------------------------|-----------------------------------------| +| References Count | The number of references | +| DirectiveReferences Count | The number of directive references | +| ExternalReferences Count | The number of external references | +| ReferencedAssemblyNames Count | The number of referenced assembly names | + +### `IncludeFlags.ParseOptions` +Includes details about the ParseOptionsProvider's ParseOptions. + +| Property | Value | +|---------------------------------|-----------------------------------------| +| Kind | The kind of the parse options | +| SpecifiedKind | The specified kind of the parse options | +| DocumentationMode | The documentation mode | +| Language | The language of the parse options | +| CSharp SpecifiedLanguageVersion | The specified C# language version | +| CSharp LanguageVersion | The C# language version | +| Features Count | The number of features | +| PreprocessorSymbolNames Count | The number of preprocessor symbol names | +| PreprocessorSymbolName | A preprocessor symbol name | + +### `IncludeFlags.AdditionalTexts` +Includes details about AdditionalTextsProvider's AdditionalText entries. + +| Property | Value | +|-------------------|-------------------------------------------| +| Path | The path to the additional text file | +| Length | The length of the source text | +| Encoding | The encoding of the source text | +| Lines | The number of lines in the source text | +| ChecksumAlgorithm | The checksum algorithm of the source text | +| CanBeEmbedded | True if the source text can be embedded | + +### `IncludeFlags.AdditionalTextsOptions` +Includes details about `AdditionalTextsProvider`'s `AdditionalText` options (from `AnalyzerConfigOptions`). +For each additional text file, each key-value pair in the options will be added to the generated output. + +### `IncludeFlags.MetadataReferences` +Includes details about `MetadataReferencesProvider`'s `MetadataReference` entries. +A `MetadataReference` represents a reference to a metadata assembly, such as a .NET assembly. +This allows a source generator to inspect the types and members defined in referenced assemblies, +which can be useful for generating code that interacts with existing libraries. + +Due to the potentially large number of metadata references, +the generated output is limited to the display string of each reference. + +| Property | Value | +|-------------------------------|------------------------------------------| +| Display | The display string of the metadata reference | + +### `IncludeFlags.All` +Includes all available details about the source generation context, combining the output of all other flags. + +### Not all available properties are included + +While ths lists above are extensive, there are more properties and 'getter' methods available in the context objects, +and not all the information is included in the generated output. + +Additionally, new versions of the Roslyn compiler may add new properties +to the context, which this source generator will not show. + +--- + + + + +###### Datacute - Acute Information Revelation Tools + + + + diff --git a/SourceGeneratorContext.Attribute/SourceGeneratorContext.Attribute.csproj b/SourceGeneratorContext.Attribute/SourceGeneratorContext.Attribute.csproj deleted file mode 100644 index 3fe0366..0000000 --- a/SourceGeneratorContext.Attribute/SourceGeneratorContext.Attribute.csproj +++ /dev/null @@ -1,17 +0,0 @@ - - - - netstandard2.0 - 12 - Datacute.SourceGeneratorContext.Attribute - Datacute.SourceGeneratorContext - false - enable - 1.0.0 - - - - true - - - diff --git a/SourceGeneratorContext.Attribute/SourceGeneratorContextAttribute.cs b/SourceGeneratorContext.Attribute/SourceGeneratorContextAttribute.cs deleted file mode 100644 index a207bc5..0000000 --- a/SourceGeneratorContext.Attribute/SourceGeneratorContextAttribute.cs +++ /dev/null @@ -1,134 +0,0 @@ -using System; -// ReSharper disable UnusedAutoPropertyAccessor.Global Properties getters are not used as the source generator reads the source code. -// ReSharper disable UnusedParameter.Local Unused parameters are used to demonstrate behaviour. - -namespace Datacute.SourceGeneratorContext; - -/// -/// Add this attribute to a partial class to generate doc-comments detailing the source generation context. -/// -[System.Diagnostics.Conditional("DATACUTE_SOURCEGENERATORCONTEXTATTRIBUTE_USAGES")] -[AttributeUsage( - validOn: AttributeTargets.Class | - AttributeTargets.Interface | - AttributeTargets.Struct, // Method and Property should be allowed too - Inherited = true, // Inherited to show how SyntaxProvider.ForAttributeWithMetadataName doesn't support inheritance - AllowMultiple = true)] // AllowMultiple to show the differences when multiple attributes are applied -public class SourceGeneratorContextAttribute : Attribute -{ - /// - /// There is a huge amount of information available, but Visual Studio does not scroll doc-comments. - /// So either IncludeAll and view the generated source, or set one of the named parameters to control what gets output: - /// - /// [SourceGeneratorContext(IncludeAll = true)] - /// internal partial class Example; - /// - /// - public SourceGeneratorContextAttribute() - { - } - - /// - /// Set to true to include all available details. - /// - public bool IncludeAll { get; set; } - - /// - /// Set to true to include the GeneratorAttributeSyntaxContext.TargetSymbol details. - /// - public bool IncludeAttributeContextTargetSymbol { get; set; } - - /// - /// Set to true to include the GeneratorAttributeSyntaxContext.TargetSymbol as ITypeSymbol details. - /// - public bool IncludeAttributeContextTypeSymbol { get; set; } - - /// - /// Set to true to include the GeneratorAttributeSyntaxContext.TargetSymbol as INamedTypeSymbol details. - /// - public bool IncludeAttributeContextNamedTypeSymbol { get; set; } - - /// - /// Set to true to include the GeneratorAttributeSyntaxContext.TargetNode details. - /// - public bool IncludeAttributeContextTargetNode { get; set; } - - /// - /// Set to true to include the GeneratorAttributeSyntaxContext.Attributes details. - /// - public bool IncludeAttributeContextAttributes { get; set; } - - /// - /// Set to true to include the GeneratorAttributeSyntaxContext.GetAttributes() details. - /// - public bool IncludeAttributeContextAllAttributes { get; set; } - - /// - /// Set to true to include the AnalyzerConfigOptionsProvider's GlobalOptions details. - /// - public bool IncludeGlobalOptions { get; set; } - - /// - /// Set to true to include the CompilationProvider's Compilation details. - /// - public bool IncludeCompilation { get; set; } - - /// - /// Set to true to include the CompilationProvider's Compilation.Options details. - /// - public bool IncludeCompilationOptions { get; set; } - - /// - /// Set to true to include the CompilationProvider's Compilation.Assembly details. - /// - public bool IncludeCompilationAssembly { get; set; } - - /// - /// Set to true to include the Counts of CompilationProvider's Compilation.References, Compilation.DirectiveReferences, Compilation.ExternalReferences, and Compilation.ReferencedAssemblyNames. - /// - public bool IncludeCompilationReferences { get; set; } - - /// - /// Set to true to include the ParseOptionsProvider's ParseOptions details. - /// - public bool IncludeParseOptions { get; set; } - - /// - /// Set to true to include the AdditionalTextsProvider's AdditionalText details. - /// - public bool IncludeAdditionalTexts { get; set; } - - /// - /// Set to true to include the AdditionalTextsProvider's AdditionalText details combined with AnalyzerConfigOptionsProvider's AnalyzerConfigOptions for the AdditionalText. - /// - public bool IncludeAdditionalTextsOptions { get; set; } - - /// - /// Set to true to include the MetadataReferencesProvider's MetadataReference details. - /// - public bool IncludeMetadataReferences { get; set; } - - - #region Demonstration purposes only - - /// - /// Example of a named parameter. - /// - public string ExampleNamedParameter { get; set; } = - string.Empty; // only used for demonstrating working with Named Parameters - - /// - /// Example of an optional parameter. - /// - /// - public - SourceGeneratorContextAttribute( - string? exampleOptionalParameter = - null) // only used for demonstrating working with Constructor Arguments - { - // The constructor arguments do not need to be assigned to fields or properties - // as the source of the supplied values is what is available to the source generator - } - - #endregion -} \ No newline at end of file diff --git a/SourceGeneratorContext.sln b/SourceGeneratorContext.sln index 2f93387..caea35f 100644 --- a/SourceGeneratorContext.sln +++ b/SourceGeneratorContext.sln @@ -5,8 +5,6 @@ VisualStudioVersion = 17.13.35825.156 d17.13 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SourceGeneratorContext", "SourceGeneratorContext\SourceGeneratorContext.csproj", "{DB6CCC3F-D197-48F7-B166-E9194233939D}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SourceGeneratorContext.Attribute", "SourceGeneratorContext.Attribute\SourceGeneratorContext.Attribute.csproj", "{394AEA19-BF98-4427-BE95-BBD90A7D65A5}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SourceGeneratorContextExample", "SourceGeneratorContextExample\SourceGeneratorContextExample.csproj", "{56E79762-6BB4-4042-9EAB-2D819C2FDAB0}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{8EC462FD-D22E-90A8-E5CE-7E832BA40C5D}" @@ -15,6 +13,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Directory.Build.props = Directory.Build.props LICENSE = LICENSE version.props = version.props + global.json = global.json + versionsuffix.ci.props = versionsuffix.ci.props + versionsuffix.local.props = versionsuffix.local.props + versionsuffix.release.props = versionsuffix.release.props EndProjectSection EndProject Global @@ -27,10 +29,6 @@ Global {DB6CCC3F-D197-48F7-B166-E9194233939D}.Debug|Any CPU.Build.0 = Debug|Any CPU {DB6CCC3F-D197-48F7-B166-E9194233939D}.Release|Any CPU.ActiveCfg = Release|Any CPU {DB6CCC3F-D197-48F7-B166-E9194233939D}.Release|Any CPU.Build.0 = Release|Any CPU - {394AEA19-BF98-4427-BE95-BBD90A7D65A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {394AEA19-BF98-4427-BE95-BBD90A7D65A5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {394AEA19-BF98-4427-BE95-BBD90A7D65A5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {394AEA19-BF98-4427-BE95-BBD90A7D65A5}.Release|Any CPU.Build.0 = Release|Any CPU {56E79762-6BB4-4042-9EAB-2D819C2FDAB0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {56E79762-6BB4-4042-9EAB-2D819C2FDAB0}.Debug|Any CPU.Build.0 = Debug|Any CPU {56E79762-6BB4-4042-9EAB-2D819C2FDAB0}.Release|Any CPU.ActiveCfg = Release|Any CPU diff --git a/SourceGeneratorContext/AdditionalTextDescription.cs b/SourceGeneratorContext/AdditionalTextDescription.cs index 974fa5d..4091bbb 100644 --- a/SourceGeneratorContext/AdditionalTextDescription.cs +++ b/SourceGeneratorContext/AdditionalTextDescription.cs @@ -4,12 +4,12 @@ namespace Datacute.SourceGeneratorContext; -public readonly struct AdditionalTextDescription +public readonly record struct AdditionalTextDescription { public readonly string DocComments; public readonly string OptionsComments; - public AdditionalTextDescription(AdditionalText additionalText, AnalyzerConfigOptions? options = null) + private AdditionalTextDescription(AdditionalText additionalText, AnalyzerConfigOptions? options = null) { var sb = new StringBuilder(); sb.AddComment("Path", additionalText.Path); @@ -36,16 +36,14 @@ public AdditionalTextDescription(AdditionalText additionalText, AnalyzerConfigOp OptionsComments = sb.ToString(); } - public static AdditionalTextDescription Select(AdditionalText additionalText, CancellationToken token) + public static AdditionalTextDescription Selector(AdditionalText additionalText, CancellationToken token) { token.ThrowIfCancellationRequested(); return new AdditionalTextDescription(additionalText); } - public static AdditionalTextDescription Select((AdditionalText additionalText, AnalyzerConfigOptionsProvider optionsProvider) args, CancellationToken token) + public static AdditionalTextDescription Selector((AdditionalText additionalText, AnalyzerConfigOptionsProvider optionsProvider) args, CancellationToken token) { - LightweightTrace.Add(TrackingNames.AdditionalTextDescription_Select); - token.ThrowIfCancellationRequested(); var options = args.optionsProvider.GetOptions(args.additionalText); return new AdditionalTextDescription(args.additionalText, options); diff --git a/SourceGeneratorContext/AnalyzerConfigOptionsDescription.cs b/SourceGeneratorContext/AnalyzerConfigOptionsDescription.cs index f6087df..021cdcf 100644 --- a/SourceGeneratorContext/AnalyzerConfigOptionsDescription.cs +++ b/SourceGeneratorContext/AnalyzerConfigOptionsDescription.cs @@ -18,10 +18,8 @@ public AnalyzerConfigOptionsDescription(AnalyzerConfigOptions options) DocComments = sb.ToString(); } - public static AnalyzerConfigOptionsDescription Select(AnalyzerConfigOptionsProvider provider, CancellationToken token) + public static AnalyzerConfigOptionsDescription Selector(AnalyzerConfigOptionsProvider provider, CancellationToken token) { - LightweightTrace.Add(TrackingNames.AnalyzerConfigOptionsDescription_Select); - token.ThrowIfCancellationRequested(); return new AnalyzerConfigOptionsDescription(provider.GlobalOptions); } diff --git a/SourceGeneratorContext/AttributeContext.cs b/SourceGeneratorContext/AttributeContext.cs deleted file mode 100644 index 0a354eb..0000000 --- a/SourceGeneratorContext/AttributeContext.cs +++ /dev/null @@ -1,427 +0,0 @@ -using System.Collections.Immutable; -using System.Text; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; - -namespace Datacute.SourceGeneratorContext; - -public readonly struct AttributeContext -{ - // Store parent classes with their modifiers - public record struct ParentClassInfo( - string Name, - bool IsStatic, - Accessibility Accessibility, - string RecordStructOrClass, - string[] TypeParameters); - public readonly ParentClassInfo[] ParentClasses { get; } - public bool HasParentClasses => ParentClasses.Length > 0; - - public readonly bool IncludeSummary; - public readonly bool IncludeAll; - - public readonly bool IncludeAttributeContextTargetSymbol; - public readonly bool IncludeAttributeContextTypeSymbol; - public readonly bool IncludeAttributeContextNamedTypeSymbol; - public readonly bool IncludeAttributeContextTargetNode; - public readonly bool IncludeAttributeContextAttributes; - public readonly bool IncludeAttributeContextAllAttributes; - - public readonly bool IncludeGlobalOptions; - public readonly bool IncludeCompilation; - public readonly bool IncludeCompilationOptions; - public readonly bool IncludeCompilationAssembly; - public readonly bool IncludeCompilationReferences; - public readonly bool IncludeParseOptions; - public readonly bool IncludeAdditionalTexts; - public readonly bool IncludeAdditionalTextsOptions; - public readonly bool IncludeMetadataReferences; - - public readonly string TargetSymbolDocComments; - public readonly string TypeSymbolDocComments; - public readonly string NamedTypeSymbolDocComments; - public readonly string TargetNodeDocComments; - public readonly string AttributesDocComments; - public readonly string AllAttributesDocComments; - - public readonly bool ContainingNamespaceIsGlobalNamespace; - public readonly string ContainingNamespaceDisplayString; - - public readonly Accessibility DeclaredAccessibility; // public - public readonly bool IsStatic; // static - public readonly string RecordStructOrClass; // (partial) class - public readonly string Name; // ClassName - public readonly string[] TypeParameters; // - public readonly string DisplayString; // Namespace.ClassName - - public AttributeContext(in GeneratorAttributeSyntaxContext generatorAttributeSyntaxContext) - { - var sb = new StringBuilder(); - var targetSymbol = generatorAttributeSyntaxContext.TargetSymbol; - sb.AddComment("Type", targetSymbol.GetType().Name); - sb.AddComment("Kind", targetSymbol.Kind); - sb.AddComment("Language", targetSymbol.Language); - sb.AddComment("DeclaredAccessibility", targetSymbol.DeclaredAccessibility); - sb.AddComment("ContainingSymbol Kind", targetSymbol.ContainingSymbol?.Kind); - sb.AddComment("ContainingSymbol Name", targetSymbol.ContainingSymbol?.Name); - sb.AddComment("ContainingAssembly Name", targetSymbol.ContainingAssembly?.Name); - sb.AddComment("ContainingModule Name", targetSymbol.ContainingModule?.Name); - sb.AddComment("ContainingType Name", targetSymbol.ContainingType?.Name); - var containingTypeTypeParameters = targetSymbol.ContainingType?.TypeParameters; - sb.AddComment("ContainingType Generic Types", containingTypeTypeParameters?.Length); - if (containingTypeTypeParameters != null) - { - for (var n = 0; n < containingTypeTypeParameters.Value.Length; n++) - { - var typeParameter = containingTypeTypeParameters.Value[n]; - sb.AddComment($"ContainingType Generic Type {n+1}", typeParameter.ToDisplayString()); - } - } - sb.AddComment("ContainingNamespace Name", targetSymbol.ContainingNamespace.Name); - sb.AddComment("ContainingNamespace IsGlobalNamespace", targetSymbol.ContainingNamespace.IsGlobalNamespace); - sb.AddComment("Name", targetSymbol.Name); - sb.AddComment("MetadataName", targetSymbol.MetadataName); - sb.AddComment("MetadataToken", targetSymbol.MetadataToken); - sb.AddComment("IsDefinition", targetSymbol.IsDefinition); - sb.AddComment("IsStatic", targetSymbol.IsStatic); - sb.AddComment("IsVirtual", targetSymbol.IsVirtual); - sb.AddComment("IsOverride", targetSymbol.IsOverride); - sb.AddComment("IsAbstract", targetSymbol.IsAbstract); - sb.AddComment("IsSealed", targetSymbol.IsSealed); - sb.AddComment("IsExtern", targetSymbol.IsExtern); - sb.AddComment("IsImplicitlyDeclared", targetSymbol.IsImplicitlyDeclared); - sb.AddComment("CanBeReferencedByName", targetSymbol.CanBeReferencedByName); - - TargetSymbolDocComments = sb.ToString(); - - sb.Clear(); - - if (generatorAttributeSyntaxContext.TargetSymbol is ITypeSymbol typeTargetSymbol) - { - sb.AddComment("TypeKind", typeTargetSymbol.TypeKind); - sb.AddComment("BaseType Name", typeTargetSymbol.BaseType?.Name ?? string.Empty); - var interfaces = typeTargetSymbol.Interfaces; - sb.AddComment("Interfaces Length", interfaces.Length); - for (var n = 0; n < interfaces.Length; n++) - { - var directInterface = interfaces[n]; - sb.AddComment($"Interface {n+1}", directInterface.ToDisplayString()); - } - var allInterfaces = typeTargetSymbol.AllInterfaces; - sb.AddComment("AllInterfaces Length", allInterfaces.Length); - for (var n = 0; n < allInterfaces.Length; n++) - { - var implementedInterface = allInterfaces[n]; - sb.AddComment($"Interface {n+1}", implementedInterface.ToDisplayString()); - } - sb.AddComment("IsReferenceType", typeTargetSymbol.IsReferenceType); - sb.AddComment("IsValueType", typeTargetSymbol.IsValueType); - sb.AddComment("IsAnonymousType", typeTargetSymbol.IsAnonymousType); - sb.AddComment("IsTupleType", typeTargetSymbol.IsTupleType); - sb.AddComment("IsNativeIntegerType", typeTargetSymbol.IsNativeIntegerType); - sb.AddComment("SpecialType", typeTargetSymbol.SpecialType); - sb.AddComment("IsRefLikeType", typeTargetSymbol.IsRefLikeType); - sb.AddComment("IsUnmanagedType", typeTargetSymbol.IsUnmanagedType); - sb.AddComment("IsReadOnly", typeTargetSymbol.IsReadOnly); - sb.AddComment("IsRecord", typeTargetSymbol.IsRecord); - sb.AddComment("NullableAnnotation", typeTargetSymbol.NullableAnnotation); - - TypeSymbolDocComments = sb.ToString(); - - sb.Clear(); - } - else - { - TypeSymbolDocComments = string.Empty; - } - - if (generatorAttributeSyntaxContext.TargetSymbol is INamedTypeSymbol namedTypeTargetSymbol) - { - TypeParameters = namedTypeTargetSymbol.TypeParameters.Select(tp => tp.Name).ToArray(); - sb.AddComment("Arity", namedTypeTargetSymbol.Arity); - sb.AddComment("IsGenericType", namedTypeTargetSymbol.IsGenericType); - sb.AddComment("IsUnboundGenericType", namedTypeTargetSymbol.IsUnboundGenericType); - sb.AddComment("IsScriptClass", namedTypeTargetSymbol.IsScriptClass); - sb.AddComment("IsImplicitClass", namedTypeTargetSymbol.IsImplicitClass); - sb.AddComment("IsComImport", namedTypeTargetSymbol.IsComImport); - sb.AddComment("IsFileLocal", namedTypeTargetSymbol.IsFileLocal); - sb.AddComment("MemberNames", namedTypeTargetSymbol.MemberNames.Count()); - sb.AddComment("TypeParameters", TypeParameters.Length); - for (var n = 0; n < TypeParameters.Length; n++) - { - var typeParameter = TypeParameters[n]; - sb.AddComment($"TypeParameter {n+1}", typeParameter); - } - sb.AddComment("InstanceConstructors", namedTypeTargetSymbol.InstanceConstructors.Length); - sb.AddComment("StaticConstructors", namedTypeTargetSymbol.StaticConstructors.Length); - sb.AddComment("MightContainExtensionMethods", namedTypeTargetSymbol.MightContainExtensionMethods); - sb.AddComment("IsSerializable", namedTypeTargetSymbol.IsSerializable); - - NamedTypeSymbolDocComments = sb.ToString(); - - sb.Clear(); - } - else - { - TypeParameters = Array.Empty(); - NamedTypeSymbolDocComments = string.Empty; - } - - var targetNode = generatorAttributeSyntaxContext.TargetNode; - sb.AddComment("Type", targetNode.GetType().Name); - sb.AddComment("RawKind", targetNode.RawKind); - sb.AddComment("Kind", targetNode.Kind()); - sb.AddComment("Language", targetNode.Language); - sb.AddComment("Span.Start", targetNode.Span.Start); - sb.AddComment("Span.Length", targetNode.Span.Length); - sb.AddComment("ContainsAnnotations", targetNode.ContainsAnnotations); - sb.AddComment("ContainsDiagnostics", targetNode.ContainsDiagnostics); - sb.AddComment("ContainsDirectives", targetNode.ContainsDirectives); - sb.AddComment("ContainsSkippedText", targetNode.ContainsSkippedText); - sb.AddComment("IsMissing", targetNode.IsMissing); - sb.AddComment("HasLeadingTrivia", targetNode.HasLeadingTrivia); - sb.AddComment("HasStructuredTrivia", targetNode.HasStructuredTrivia); - sb.AddComment("HasTrailingTrivia", targetNode.HasTrailingTrivia); - sb.AddComment("IsStructuredTrivia", targetNode.IsStructuredTrivia); - - - TargetNodeDocComments = sb.ToString(); - - sb.Clear(); - - ( - IncludeSummary, - IncludeAll, - IncludeAttributeContextTargetSymbol, - IncludeAttributeContextTypeSymbol, - IncludeAttributeContextNamedTypeSymbol, - IncludeAttributeContextTargetNode, - IncludeAttributeContextAttributes, - IncludeAttributeContextAllAttributes, - IncludeGlobalOptions, - IncludeCompilation, - IncludeCompilationOptions, - IncludeCompilationAssembly, - IncludeCompilationReferences, - IncludeParseOptions, - IncludeAdditionalTexts, - IncludeAdditionalTextsOptions, - IncludeMetadataReferences) = AddAttributes(sb, generatorAttributeSyntaxContext.Attributes, true); - - AttributesDocComments = sb.ToString(); - - sb.Clear(); - - AddAttributes(sb, targetSymbol.GetAttributes(), false); - - AllAttributesDocComments = sb.ToString(); - - sb.Clear(); - - // Repeated above, but pulled out for ease of code generation - var attributeTargetSymbol = (ITypeSymbol)generatorAttributeSyntaxContext.TargetSymbol; - - ContainingNamespaceIsGlobalNamespace = attributeTargetSymbol.ContainingNamespace.IsGlobalNamespace; - ContainingNamespaceDisplayString = attributeTargetSymbol.ContainingNamespace.ToDisplayString(); - - DeclaredAccessibility = attributeTargetSymbol.DeclaredAccessibility; - IsStatic = attributeTargetSymbol.IsStatic; - RecordStructOrClass = GetRecordStructOrClass(attributeTargetSymbol); - Name = attributeTargetSymbol.Name; - DisplayString = attributeTargetSymbol.ToDisplayString(); - - // Parse parent classes from symbol's containing types - var parentClasses = new List(); - var containingType = attributeTargetSymbol.ContainingType; - while (containingType != null) - { - var typeParams = containingType.TypeParameters.Select(tp => tp.Name).ToArray(); - - parentClasses.Insert(0, new ParentClassInfo( - containingType.Name, - containingType.IsStatic, - containingType.DeclaredAccessibility, - GetRecordStructOrClass(containingType), - typeParams)); - containingType = containingType.ContainingType; - } - - ParentClasses = parentClasses.ToArray(); - } - - private static ( - bool includeSummary, - bool includeAll, - bool includeAttributeContextTargetSymbol, - bool includeAttributeContextTypeSymbol, - bool includeAttributeContextNamedTypeSymbol, - bool includeAttributeContextTargetNode, - bool includeAttributeContextAttributes, - bool includeAttributeContextAllAttributes, - bool includeGlobalOptions, - bool includeCompilation, - bool includeCompilationOptions, - bool includeCompilationAssembly, - bool includeCompilationReferences, - bool includeParseOptions, - bool includeAdditionalTexts, - bool includeAdditionalTextsOptions, - bool includeMetadataReferences) - AddAttributes(StringBuilder sb, ImmutableArray attributes, bool capture) - { - bool includeAll = false; - bool includeSummary = true; - bool includeAttributeContextTargetSymbol = false; - bool includeAttributeContextTypeSymbol = false; - bool includeAttributeContextNamedTypeSymbol = false; - bool includeAttributeContextTargetNode = false; - bool includeAttributeContextAttributes = false; - bool includeAttributeContextAllAttributes = false; - bool includeGlobalOptions = false; - bool includeCompilation = false; - bool includeCompilationOptions = false; - bool includeCompilationAssembly = false; - bool includeCompilationReferences = false; - bool includeParseOptions = false; - bool includeAdditionalTexts = false; - bool includeAdditionalTextsOptions = false; - bool includeMetadataReferences = false; - - sb.AddComment("Attribute Count", attributes.Length); - for (var i = 0; i < attributes.Length; i++) - { - var attribute = attributes[i]; - sb.AddComment($"[{i}] AttributeClass", attribute.AttributeClass?.ToDisplayString()); - - var constructorArguments = attribute.ConstructorArguments; - var constructorArgumentsLength = constructorArguments.Length; - sb.AddComment($"[{i}] ConstructorArguments Count", constructorArgumentsLength); - for (var c = 0; c < constructorArgumentsLength; c++) - { - var constructorArgument = constructorArguments[c]; - var attributeName = attribute.AttributeConstructor?.Parameters[c].Name; - sb.AddComment($"[{i}] AttributeConstructor Parameters {c+1} Name", attributeName); - sb.AddComment($"[{i}] ConstructorArgument {c+1} Kind", constructorArgument.Kind); - sb.AddComment($"[{i}] ConstructorArgument {c+1} Type", constructorArgument.Type?.Name); - sb.AddComment($"[{i}] ConstructorArgument {c+1} Value", constructorArgument.Value); - - includeSummary = false; - includeAttributeContextAttributes = true; - } - - var namedArguments = attribute.NamedArguments; - var namedArgumentsLength = namedArguments.Length; - sb.AddComment($"[{i}] NamedArguments Count", namedArgumentsLength); - for (var n = 0; n < namedArgumentsLength; n++) - { - var kvp = namedArguments[n]; - var argName = kvp.Key; - var namedArgument = kvp.Value; - sb.AddComment($"[{i}] NamedArgument {n+1} Key", argName); - sb.AddComment($"[{i}] NamedArgument {n+1} Value Kind", namedArgument.Kind); - sb.AddComment($"[{i}] NamedArgument {n+1} Value Type", namedArgument.Type?.Name); - sb.AddComment($"[{i}] NamedArgument {n+1} Value Value", namedArgument.Value); - if (capture) - { - includeSummary = false; - - var argumentValue = namedArgument.Value is true; - switch (argName) - { - case "IncludeAll": - includeAll = argumentValue; - break; - case "IncludeAttributeContextTargetSymbol": - includeAttributeContextTargetSymbol |= argumentValue; - break; - case "IncludeAttributeContextTypeSymbol": - includeAttributeContextTypeSymbol |= argumentValue; - break; - case "IncludeAttributeContextNamedTypeSymbol": - includeAttributeContextNamedTypeSymbol |= argumentValue; - break; - case "IncludeAttributeContextTargetNode": - includeAttributeContextTargetNode |= argumentValue; - break; - case "IncludeAttributeContextAttributes": - includeAttributeContextAttributes |= argumentValue; - break; - case "IncludeAttributeContextAllAttributes": - includeAttributeContextAllAttributes |= argumentValue; - break; - case "IncludeGlobalOptions": - includeGlobalOptions |= argumentValue; - break; - case "IncludeCompilation": - includeCompilation |= argumentValue; - break; - case "IncludeCompilationOptions": - includeCompilationOptions |= argumentValue; - break; - case "IncludeCompilationAssembly": - includeCompilationAssembly |= argumentValue; - break; - case "IncludeCompilationReferences": - includeCompilationReferences |= argumentValue; - break; - case "IncludeParseOptions": - includeParseOptions |= argumentValue; - break; - case "IncludeAdditionalTexts": - includeAdditionalTexts |= argumentValue; - break; - case "IncludeAdditionalTextsOptions": - includeAdditionalTextsOptions |= argumentValue; - break; - case "IncludeMetadataReferences": - includeMetadataReferences |= argumentValue; - break; - default: - includeAttributeContextAttributes = true; - break; - } - } - } - } - - return ( - includeSummary, - includeAll, - includeAttributeContextTargetSymbol, - includeAttributeContextTypeSymbol, - includeAttributeContextNamedTypeSymbol, - includeAttributeContextTargetNode, - includeAttributeContextAttributes, - includeAttributeContextAllAttributes, - includeGlobalOptions, - includeCompilation, - includeCompilationOptions, - includeCompilationAssembly, - includeCompilationReferences, - includeParseOptions, - includeAdditionalTexts, - includeAdditionalTextsOptions, - includeMetadataReferences); - } - - private static string GetRecordStructOrClass(ITypeSymbol typeSymbol) - { - if (typeSymbol.IsRecord && typeSymbol.IsReferenceType) - return "record"; - if (typeSymbol.IsRecord) - return "record struct"; - if (typeSymbol.TypeKind == TypeKind.Interface) - return "interface"; - if (typeSymbol.IsReferenceType) - return "class"; - return "struct"; - } - - public static bool Predicate(SyntaxNode syntaxNode, CancellationToken token) => true; //syntaxNode is TypeDeclarationSyntax, - - public static AttributeContext Transform(GeneratorAttributeSyntaxContext generatorAttributeSyntaxContext, CancellationToken token) - { - token.ThrowIfCancellationRequested(); - return new AttributeContext(generatorAttributeSyntaxContext); - } -} \ No newline at end of file diff --git a/SourceGeneratorContext/AttributeData.cs b/SourceGeneratorContext/AttributeData.cs new file mode 100644 index 0000000..61568b8 --- /dev/null +++ b/SourceGeneratorContext/AttributeData.cs @@ -0,0 +1,247 @@ +using System.Collections.Immutable; +using System.Text; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; + +namespace Datacute.SourceGeneratorContext; + +public readonly record struct AttributeData +{ + public readonly IncludeFlags Flags; + + public bool IncludeSummary => Flags == IncludeFlags.Summary; + + public bool IncludeAttributeContextTargetSymbol => Flags.HasFlag(IncludeFlags.AttributeContextTargetSymbol); + public bool IncludeAttributeContextTypeSymbol => Flags.HasFlag(IncludeFlags.AttributeContextTypeSymbol); + public bool IncludeAttributeContextNamedTypeSymbol => Flags.HasFlag(IncludeFlags.AttributeContextNamedTypeSymbol); + public bool IncludeAttributeContextTargetNode => Flags.HasFlag(IncludeFlags.AttributeContextTargetNode); + public bool IncludeAttributeContextAttributes => Flags.HasFlag(IncludeFlags.AttributeContextAttributes); + public bool IncludeAttributeContextAllAttributes => Flags.HasFlag(IncludeFlags.AttributeContextAllAttributes); + + public bool IncludeGlobalOptions => Flags.HasFlag(IncludeFlags.GlobalOptions); + public bool IncludeCompilation => Flags.HasFlag(IncludeFlags.Compilation); + public bool IncludeCompilationOptions => Flags.HasFlag(IncludeFlags.CompilationOptions); + public bool IncludeCompilationAssembly => Flags.HasFlag(IncludeFlags.CompilationAssembly); + public bool IncludeCompilationReferences => Flags.HasFlag(IncludeFlags.CompilationReferences); + public bool IncludeParseOptions => Flags.HasFlag(IncludeFlags.ParseOptions); + public bool IncludeAdditionalTexts => Flags.HasFlag(IncludeFlags.AdditionalTexts); + public bool IncludeAdditionalTextsOptions => Flags.HasFlag(IncludeFlags.AdditionalTextsOptions); + public bool IncludeMetadataReferences => Flags.HasFlag(IncludeFlags.MetadataReferences); + + public readonly string TargetSymbolDocComments; + public readonly string TypeSymbolDocComments; + public readonly string NamedTypeSymbolDocComments; + public readonly string TargetNodeDocComments; + public readonly string AttributesDocComments; + public readonly string AllAttributesDocComments; + + + public AttributeData(in GeneratorAttributeSyntaxContext generatorAttributeSyntaxContext) + { + var sb = new StringBuilder(); + var targetSymbol = generatorAttributeSyntaxContext.TargetSymbol; + sb.AddComment("Type", targetSymbol.GetType().Name); + sb.AddComment("Kind", targetSymbol.Kind); + sb.AddComment("Language", targetSymbol.Language); + sb.AddComment("DeclaredAccessibility", targetSymbol.DeclaredAccessibility); + sb.AddComment("ContainingSymbol Kind", targetSymbol.ContainingSymbol?.Kind); + sb.AddComment("ContainingSymbol Name", targetSymbol.ContainingSymbol?.Name); + sb.AddComment("ContainingAssembly Name", targetSymbol.ContainingAssembly?.Name); + sb.AddComment("ContainingModule Name", targetSymbol.ContainingModule?.Name); + sb.AddComment("ContainingType Name", targetSymbol.ContainingType?.Name); + var containingTypeTypeParameters = targetSymbol.ContainingType?.TypeParameters; + sb.AddComment("ContainingType Generic Types", containingTypeTypeParameters?.Length); + if (containingTypeTypeParameters != null) + { + for (var n = 0; n < containingTypeTypeParameters.Value.Length; n++) + { + var typeParameter = containingTypeTypeParameters.Value[n]; + sb.AddComment($"ContainingType Generic Type {n+1}", typeParameter.ToDisplayString()); + } + } + sb.AddComment("ContainingNamespace Name", targetSymbol.ContainingNamespace.Name); + sb.AddComment("ContainingNamespace IsGlobalNamespace", targetSymbol.ContainingNamespace.IsGlobalNamespace); + sb.AddComment("Name", targetSymbol.Name); + sb.AddComment("MetadataName", targetSymbol.MetadataName); + sb.AddComment("MetadataToken", targetSymbol.MetadataToken); + sb.AddComment("IsDefinition", targetSymbol.IsDefinition); + sb.AddComment("IsStatic", targetSymbol.IsStatic); + sb.AddComment("IsVirtual", targetSymbol.IsVirtual); + sb.AddComment("IsOverride", targetSymbol.IsOverride); + sb.AddComment("IsAbstract", targetSymbol.IsAbstract); + sb.AddComment("IsSealed", targetSymbol.IsSealed); + sb.AddComment("IsExtern", targetSymbol.IsExtern); + sb.AddComment("IsImplicitlyDeclared", targetSymbol.IsImplicitlyDeclared); + sb.AddComment("CanBeReferencedByName", targetSymbol.CanBeReferencedByName); + + TargetSymbolDocComments = sb.ToString(); + + sb.Clear(); + + if (generatorAttributeSyntaxContext.TargetSymbol is ITypeSymbol typeTargetSymbol) + { + sb.AddComment("TypeKind", typeTargetSymbol.TypeKind); + sb.AddComment("BaseType Name", typeTargetSymbol.BaseType?.Name ?? string.Empty); + var interfaces = typeTargetSymbol.Interfaces; + sb.AddComment("Interfaces Length", interfaces.Length); + for (var n = 0; n < interfaces.Length; n++) + { + var directInterface = interfaces[n]; + sb.AddComment($"Interface {n+1}", directInterface.ToDisplayString()); + } + var allInterfaces = typeTargetSymbol.AllInterfaces; + sb.AddComment("AllInterfaces Length", allInterfaces.Length); + for (var n = 0; n < allInterfaces.Length; n++) + { + var implementedInterface = allInterfaces[n]; + sb.AddComment($"Interface {n+1}", implementedInterface.ToDisplayString()); + } + sb.AddComment("IsReferenceType", typeTargetSymbol.IsReferenceType); + sb.AddComment("IsValueType", typeTargetSymbol.IsValueType); + sb.AddComment("IsAnonymousType", typeTargetSymbol.IsAnonymousType); + sb.AddComment("IsTupleType", typeTargetSymbol.IsTupleType); + sb.AddComment("IsNativeIntegerType", typeTargetSymbol.IsNativeIntegerType); + sb.AddComment("SpecialType", typeTargetSymbol.SpecialType); + sb.AddComment("IsRefLikeType", typeTargetSymbol.IsRefLikeType); + sb.AddComment("IsUnmanagedType", typeTargetSymbol.IsUnmanagedType); + sb.AddComment("IsReadOnly", typeTargetSymbol.IsReadOnly); + sb.AddComment("IsRecord", typeTargetSymbol.IsRecord); + sb.AddComment("NullableAnnotation", typeTargetSymbol.NullableAnnotation); + + TypeSymbolDocComments = sb.ToString(); + + sb.Clear(); + } + else + { + TypeSymbolDocComments = string.Empty; + } + + if (generatorAttributeSyntaxContext.TargetSymbol is INamedTypeSymbol namedTypeTargetSymbol) + { + var typeParameters = namedTypeTargetSymbol.TypeParameters.Select(tp => tp.Name).ToArray(); + sb.AddComment("Arity", namedTypeTargetSymbol.Arity); + sb.AddComment("IsGenericType", namedTypeTargetSymbol.IsGenericType); + sb.AddComment("IsUnboundGenericType", namedTypeTargetSymbol.IsUnboundGenericType); + sb.AddComment("IsScriptClass", namedTypeTargetSymbol.IsScriptClass); + sb.AddComment("IsImplicitClass", namedTypeTargetSymbol.IsImplicitClass); + sb.AddComment("IsComImport", namedTypeTargetSymbol.IsComImport); + sb.AddComment("IsFileLocal", namedTypeTargetSymbol.IsFileLocal); + sb.AddComment("MemberNames", namedTypeTargetSymbol.MemberNames.Count()); + sb.AddComment("TypeParameters", typeParameters.Length); + for (var n = 0; n < typeParameters.Length; n++) + { + var typeParameter = typeParameters[n]; + sb.AddComment($"TypeParameter {n+1}", typeParameter); + } + sb.AddComment("InstanceConstructors", namedTypeTargetSymbol.InstanceConstructors.Length); + sb.AddComment("StaticConstructors", namedTypeTargetSymbol.StaticConstructors.Length); + sb.AddComment("MightContainExtensionMethods", namedTypeTargetSymbol.MightContainExtensionMethods); + sb.AddComment("IsSerializable", namedTypeTargetSymbol.IsSerializable); + + NamedTypeSymbolDocComments = sb.ToString(); + + sb.Clear(); + } + else + { + NamedTypeSymbolDocComments = string.Empty; + } + + var targetNode = generatorAttributeSyntaxContext.TargetNode; + sb.AddComment("Type", targetNode.GetType().Name); + sb.AddComment("RawKind", targetNode.RawKind); + sb.AddComment("Kind", targetNode.Kind()); + sb.AddComment("Language", targetNode.Language); + sb.AddComment("Span.Start", targetNode.Span.Start); + sb.AddComment("Span.Length", targetNode.Span.Length); + sb.AddComment("ContainsAnnotations", targetNode.ContainsAnnotations); + sb.AddComment("ContainsDiagnostics", targetNode.ContainsDiagnostics); + sb.AddComment("ContainsDirectives", targetNode.ContainsDirectives); + sb.AddComment("ContainsSkippedText", targetNode.ContainsSkippedText); + sb.AddComment("IsMissing", targetNode.IsMissing); + sb.AddComment("HasLeadingTrivia", targetNode.HasLeadingTrivia); + sb.AddComment("HasStructuredTrivia", targetNode.HasStructuredTrivia); + sb.AddComment("HasTrailingTrivia", targetNode.HasTrailingTrivia); + sb.AddComment("IsStructuredTrivia", targetNode.IsStructuredTrivia); + + + TargetNodeDocComments = sb.ToString(); + + sb.Clear(); + + Flags = AddAttributes(sb, generatorAttributeSyntaxContext.Attributes, true); + + AttributesDocComments = sb.ToString(); + + sb.Clear(); + + AddAttributes(sb, targetSymbol.GetAttributes(), false); + + AllAttributesDocComments = sb.ToString(); + + sb.Clear(); + } + + private static IncludeFlags AddAttributes(StringBuilder sb, ImmutableArray attributes, bool capture) + { + var flags = IncludeFlags.Summary; + + sb.AddComment("Attribute Count", attributes.Length); + for (var i = 0; i < attributes.Length; i++) + { + var attribute = attributes[i]; + sb.AddComment($"[{i}] AttributeClass", attribute.AttributeClass?.ToDisplayString()); + + var constructorArguments = attribute.ConstructorArguments; + var constructorArgumentsLength = constructorArguments.Length; + sb.AddComment($"[{i}] ConstructorArguments Count", constructorArgumentsLength); + for (var c = 0; c < constructorArgumentsLength; c++) + { + var constructorArgument = constructorArguments[c]; + var attributeName = attribute.AttributeConstructor?.Parameters[c].Name; + sb.AddComment($"[{i}] AttributeConstructor Parameters {c+1} Name", attributeName); + sb.AddComment($"[{i}] ConstructorArgument {c+1} Kind", constructorArgument.Kind); + sb.AddComment($"[{i}] ConstructorArgument {c+1} Type", constructorArgument.Type?.Name); + sb.AddComment($"[{i}] ConstructorArgument {c+1} Value", constructorArgument.Value); + + if (capture) + { + if (constructorArgument.Type?.Name == "IncludeFlags" && constructorArgument.Value != null) + { + flags |= (IncludeFlags)constructorArgument.Value; + } + else + { + // if we're looking at the example of named arguments, include the attribute details + flags = IncludeFlags.AttributeContextAttributes; + } + } + } + + var namedArguments = attribute.NamedArguments; + var namedArgumentsLength = namedArguments.Length; + sb.AddComment($"[{i}] NamedArguments Count", namedArgumentsLength); + for (var n = 0; n < namedArgumentsLength; n++) + { + var kvp = namedArguments[n]; + var argName = kvp.Key; + var namedArgument = kvp.Value; + sb.AddComment($"[{i}] NamedArgument {n+1} Key", argName); + sb.AddComment($"[{i}] NamedArgument {n+1} Value Kind", namedArgument.Kind); + sb.AddComment($"[{i}] NamedArgument {n+1} Value Type", namedArgument.Type?.Name); + sb.AddComment($"[{i}] NamedArgument {n+1} Value Value", namedArgument.Value); + if (capture) + { + // if we're looking at the example of named arguments, include the attribute details + flags = IncludeFlags.AttributeContextAttributes; + } + } + } + + return flags; + } + + + public static AttributeData Collector(GeneratorAttributeSyntaxContext generatorAttributeSyntaxContext) => new(generatorAttributeSyntaxContext); +} \ No newline at end of file diff --git a/SourceGeneratorContext/CodeGenerator.cs b/SourceGeneratorContext/CodeGenerator.cs index 7ea8893..3a59aa9 100644 --- a/SourceGeneratorContext/CodeGenerator.cs +++ b/SourceGeneratorContext/CodeGenerator.cs @@ -1,318 +1,159 @@ -using System.Collections.Immutable; -using System.Text; -using Microsoft.CodeAnalysis; +using Datacute.IncrementalGeneratorExtensions; namespace Datacute.SourceGeneratorContext; -public readonly struct CodeGenerator +public class CodeGenerator : SourceTextGeneratorBase { public CodeGenerator( in GeneratorSourceData source, in CancellationToken cancellationToken) + : base(source.AttributeContext, cancellationToken) { - _attributeContext = source.Core.AttributeOptionsCompilationAndParseOptions.AttributeOptionsAndCompilation.AttributeAndOptions.AttributeContext; - _globalOptionsDescription = source.Core.AttributeOptionsCompilationAndParseOptions.AttributeOptionsAndCompilation.AttributeAndOptions.Options; - _compilationDescription = source.Core.AttributeOptionsCompilationAndParseOptions.AttributeOptionsAndCompilation.Compilation; - _parseOptionsDescription = source.Core.AttributeOptionsCompilationAndParseOptions.ParseOptions; - _additionalTextDescriptions = source.Core.AdditionalTexts; - _metadataReferenceDescriptions = source.MetadataReferences; - _cancellationToken = cancellationToken; - _buffer = new StringBuilder(); + // deconstruct the source data + ( + var contextAndData, + _globalOptionsDescription, + _compilationDescription, + _parseOptionsDescription, + _additionalTextDescriptions, + _metadataReferenceDescriptions + ) = source; + + _contextData = contextAndData.AttributeData; } - private readonly AttributeContext _attributeContext; + private readonly AttributeData _contextData; + private readonly AnalyzerConfigOptionsDescription _globalOptionsDescription; private readonly CompilationDescription _compilationDescription; private readonly ParseOptionsDescription _parseOptionsDescription; - private readonly ImmutableArray _additionalTextDescriptions; - private readonly ImmutableArray _metadataReferenceDescriptions; - private readonly CancellationToken _cancellationToken; - - private readonly StringBuilder _buffer; - - public string GenerateSource() - { - _cancellationToken.ThrowIfCancellationRequested(); - _buffer.Clear(); - AutoGeneratedComment(); - StartNamespace(); - var indentLevel = ParentClasses(); - ClassDocComments(indentLevel); - PartialTypeDeclaration(indentLevel); - AppendStartClass(indentLevel); - AppendEndClass(indentLevel); - EndParentClasses(); - AppendDiagnosticLogs(); - return _buffer.ToString(); - } - - private void AutoGeneratedComment() - { - _buffer.AppendLine(Templates.AutoGeneratedComment); - } + private readonly EquatableImmutableArray _additionalTextDescriptions; + private readonly EquatableImmutableArray _metadataReferenceDescriptions; - private void StartNamespace() + protected override void AppendDocComments() { - if (_attributeContext.ContainingNamespaceIsGlobalNamespace) return; + Buffer.AppendLines(Templates.ClassDocCommentsBegin); - _buffer.Append("namespace "); - _buffer.Append(_attributeContext.ContainingNamespaceDisplayString); - _buffer.Append(';').AppendLine(); - } - - private int ParentClasses() - { - var indentLevel = 0; - if (_attributeContext.HasParentClasses) + if (_contextData.IncludeAttributeContextTargetSymbol || _contextData.IncludeSummary) { - indentLevel = StartParentClasses(); + Buffer.AppendFormatLines(Templates.ClassDocCommentsSectionBegin, "GeneratorAttributeSyntaxContext TargetSymbol"); + Buffer.AppendLines(_contextData.TargetSymbolDocComments); + Buffer.AppendLine(Templates.ClassDocCommentsSectionEnd); } - return indentLevel; - } - private int StartParentClasses() - { - var indent = 0; - foreach (var parentClass in _attributeContext.ParentClasses) + if (_contextData.IncludeAttributeContextTypeSymbol && !string.IsNullOrEmpty(_contextData.TypeSymbolDocComments)) { - _buffer.Append(' ', indent); - - // Use the parent's actual accessibility - var accessibilityModifier = GetAccessibility(parentClass.Accessibility); - // Include static modifier if the parent class is static - var staticModifier = parentClass.IsStatic ? "static " : ""; - var genericTypes = parentClass.TypeParameters.Any() - ? $"<{string.Join(",", parentClass.TypeParameters)}>" - : string.Empty; - _buffer.AppendLine($"{accessibilityModifier}{staticModifier}partial {parentClass.RecordStructOrClass} {parentClass.Name}{genericTypes}"); - - _buffer.Append(' ', indent); - _buffer.AppendLine("{"); - indent += 4; + Buffer.AppendFormatLines(Templates.ClassDocCommentsSectionBegin, "GeneratorAttributeSyntaxContext TargetSymbol as ITypeSymbol"); + Buffer.AppendLines(_contextData.TypeSymbolDocComments); + Buffer.AppendLine(Templates.ClassDocCommentsSectionEnd); } - return indent; - } - private void ClassDocComments(int indent = 0) - { - var indentString = StringForIndent(indent); - _buffer.AppendFormat(Templates.ClassDocCommentsBegin, indentString); - - if (_attributeContext.IncludeAttributeContextTargetSymbol || _attributeContext.IncludeAll || _attributeContext.IncludeSummary) - { - _buffer.AppendFormat(Templates.ClassDocCommentsSectionBegin, indentString, "GeneratorAttributeSyntaxContext TargetSymbol"); - AppendIndentedLines(indent, _attributeContext.TargetSymbolDocComments); - _buffer.AppendFormat(Templates.ClassDocCommentsSectionEnd, indentString); - } - - if ((_attributeContext.IncludeAttributeContextTypeSymbol || _attributeContext.IncludeAll) && !string.IsNullOrEmpty(_attributeContext.TypeSymbolDocComments)) - { - _buffer.AppendFormat(Templates.ClassDocCommentsSectionBegin, indentString, "GeneratorAttributeSyntaxContext TargetSymbol as ITypeSymbol"); - AppendIndentedLines(indent,_attributeContext.TypeSymbolDocComments); - _buffer.AppendFormat(Templates.ClassDocCommentsSectionEnd, indentString); - } - - if ((_attributeContext.IncludeAttributeContextNamedTypeSymbol || _attributeContext.IncludeAll) && !string.IsNullOrEmpty(_attributeContext.NamedTypeSymbolDocComments)) + if (_contextData.IncludeAttributeContextNamedTypeSymbol && !string.IsNullOrEmpty(_contextData.NamedTypeSymbolDocComments)) { - _buffer.AppendFormat(Templates.ClassDocCommentsSectionBegin, indentString, "GeneratorAttributeSyntaxContext TargetSymbol as INamedTypeSymbol"); - AppendIndentedLines(indent, _attributeContext.NamedTypeSymbolDocComments); - _buffer.AppendFormat(Templates.ClassDocCommentsSectionEnd, indentString); + Buffer.AppendFormatLines(Templates.ClassDocCommentsSectionBegin, "GeneratorAttributeSyntaxContext TargetSymbol as INamedTypeSymbol"); + Buffer.AppendLines(_contextData.NamedTypeSymbolDocComments); + Buffer.AppendLine(Templates.ClassDocCommentsSectionEnd); } - if (_attributeContext.IncludeAttributeContextTargetNode || _attributeContext.IncludeAll) + if (_contextData.IncludeAttributeContextTargetNode) { - _buffer.AppendFormat(Templates.ClassDocCommentsSectionBegin, indentString, "GeneratorAttributeSyntaxContext TargetNode"); - AppendIndentedLines(indent, _attributeContext.TargetNodeDocComments); - _buffer.AppendFormat(Templates.ClassDocCommentsSectionEnd, indentString); + Buffer.AppendFormatLines(Templates.ClassDocCommentsSectionBegin, "GeneratorAttributeSyntaxContext TargetNode"); + Buffer.AppendLines(_contextData.TargetNodeDocComments); + Buffer.AppendLine(Templates.ClassDocCommentsSectionEnd); } - if (_attributeContext.IncludeAttributeContextAttributes || _attributeContext.IncludeAll) + if (_contextData.IncludeAttributeContextAttributes) { - _buffer.AppendFormat(Templates.ClassDocCommentsSectionBegin, indentString, "GeneratorAttributeSyntaxContext Attributes"); - AppendIndentedLines(indent, _attributeContext.AttributesDocComments); - _buffer.AppendFormat(Templates.ClassDocCommentsSectionEnd, indentString); + Buffer.AppendFormatLines(Templates.ClassDocCommentsSectionBegin, "GeneratorAttributeSyntaxContext Attributes"); + Buffer.AppendLines(_contextData.AttributesDocComments); + Buffer.AppendLine(Templates.ClassDocCommentsSectionEnd); } - if (_attributeContext.IncludeAttributeContextAllAttributes || _attributeContext.IncludeAll) + if (_contextData.IncludeAttributeContextAllAttributes) { - _buffer.AppendFormat(Templates.ClassDocCommentsSectionBegin, indentString, "GeneratorAttributeSyntaxContext TargetSymbol AllAttributes()"); - AppendIndentedLines(indent, _attributeContext.AllAttributesDocComments); - _buffer.AppendFormat(Templates.ClassDocCommentsSectionEnd, indentString); + Buffer.AppendFormatLines(Templates.ClassDocCommentsSectionBegin, "GeneratorAttributeSyntaxContext TargetSymbol AllAttributes()"); + Buffer.AppendLines(_contextData.AllAttributesDocComments); + Buffer.AppendLine(Templates.ClassDocCommentsSectionEnd); } - if (_attributeContext.IncludeGlobalOptions || _attributeContext.IncludeAll) + if (_contextData.IncludeGlobalOptions) { - _buffer.AppendFormat(Templates.ClassDocCommentsSectionBegin, indentString, "AnalyzerConfigOptionsProvider GlobalOptions"); - AppendIndentedLines(indent, _globalOptionsDescription.DocComments); - _buffer.AppendFormat(Templates.ClassDocCommentsSectionEnd, indentString); + Buffer.AppendFormatLines(Templates.ClassDocCommentsSectionBegin, "AnalyzerConfigOptionsProvider GlobalOptions"); + Buffer.AppendLines(_globalOptionsDescription.DocComments); + Buffer.AppendLine(Templates.ClassDocCommentsSectionEnd); } - if (_attributeContext.IncludeCompilation || _attributeContext.IncludeAll) + if (_contextData.IncludeCompilation) { - _buffer.AppendFormat(Templates.ClassDocCommentsSectionBegin, indentString, "Compilation"); - AppendIndentedLines(indent, _compilationDescription.DocComments); - _buffer.AppendFormat(Templates.ClassDocCommentsSectionEnd, indentString); + Buffer.AppendFormatLines(Templates.ClassDocCommentsSectionBegin, "Compilation"); + Buffer.AppendLines(_compilationDescription.DocComments); + Buffer.AppendLine(Templates.ClassDocCommentsSectionEnd); } - if (_attributeContext.IncludeCompilationOptions || _attributeContext.IncludeAll) + if (_contextData.IncludeCompilationOptions) { - _buffer.AppendFormat(Templates.ClassDocCommentsSectionBegin, indentString, "Compilation Options"); - AppendIndentedLines(indent, _compilationDescription.OptionsDocComments); - _buffer.AppendFormat(Templates.ClassDocCommentsSectionEnd, indentString); + Buffer.AppendFormatLines(Templates.ClassDocCommentsSectionBegin, "Compilation Options"); + Buffer.AppendLines(_compilationDescription.OptionsDocComments); + Buffer.AppendLine(Templates.ClassDocCommentsSectionEnd); } - if (_attributeContext.IncludeCompilationAssembly || _attributeContext.IncludeAll) + if (_contextData.IncludeCompilationAssembly) { - _buffer.AppendFormat(Templates.ClassDocCommentsSectionBegin, indentString, "Compilation Assembly"); - AppendIndentedLines(indent, _compilationDescription.AssemblyDocComments); - _buffer.AppendFormat(Templates.ClassDocCommentsSectionEnd, indentString); + Buffer.AppendFormatLines(Templates.ClassDocCommentsSectionBegin, "Compilation Assembly"); + Buffer.AppendLines(_compilationDescription.AssemblyDocComments); + Buffer.AppendLine(Templates.ClassDocCommentsSectionEnd); } - if (_attributeContext.IncludeCompilationReferences || _attributeContext.IncludeAll) + if (_contextData.IncludeCompilationReferences) { - _buffer.AppendFormat(Templates.ClassDocCommentsSectionBegin, indentString, "Compilation References"); - AppendIndentedLines(indent, _compilationDescription.ReferencesDocComments); - _buffer.AppendFormat(Templates.ClassDocCommentsSectionEnd, indentString); + Buffer.AppendFormatLines(Templates.ClassDocCommentsSectionBegin, "Compilation References"); + Buffer.AppendLines(_compilationDescription.ReferencesDocComments); + Buffer.AppendLine(Templates.ClassDocCommentsSectionEnd); } - if (_attributeContext.IncludeParseOptions || _attributeContext.IncludeAll) + if (_contextData.IncludeParseOptions) { - _buffer.AppendFormat(Templates.ClassDocCommentsSectionBegin, indentString, "Parse Options"); - AppendIndentedLines(indent, _parseOptionsDescription.DocComments); - _buffer.AppendFormat(Templates.ClassDocCommentsSectionEnd, indentString); + Buffer.AppendFormatLines(Templates.ClassDocCommentsSectionBegin, "Parse Options"); + Buffer.AppendLines(_parseOptionsDescription.DocComments); + Buffer.AppendLine(Templates.ClassDocCommentsSectionEnd); } - if (_attributeContext.IncludeAdditionalTexts || _attributeContext.IncludeAll) + if (_contextData.IncludeAdditionalTexts) { - _buffer.AppendFormat(Templates.ClassDocCommentsSectionBegin, indentString, "Additional Texts"); - // todo: indent - _buffer.AddComment("Number of Additional Texts", _additionalTextDescriptions.Length); + Buffer.AppendFormatLines(Templates.ClassDocCommentsSectionBegin, "Additional Texts"); + Buffer.AddComment("Number of Additional Texts", _additionalTextDescriptions.Length); foreach (var additionalTextDescription in _additionalTextDescriptions) { - AppendIndentedLines(indent, additionalTextDescription.DocComments); - if (_attributeContext.IncludeAdditionalTextsOptions) + Buffer.AppendLines(additionalTextDescription.DocComments); + if (_contextData.IncludeAdditionalTextsOptions) { - AppendIndentedLines(indent, additionalTextDescription.OptionsComments); + Buffer.AppendLines(additionalTextDescription.OptionsComments); } } - _buffer.AppendFormat(Templates.ClassDocCommentsSectionEnd, indentString); + Buffer.AppendLine(Templates.ClassDocCommentsSectionEnd); } - if (_attributeContext.IncludeMetadataReferences || _attributeContext.IncludeAll) + if (_contextData.IncludeMetadataReferences) { - _buffer.AppendFormat(Templates.ClassDocCommentsSectionBegin, indentString, "Metadata References"); - // todo: indent - _buffer.AddComment("Number of Metadata References", _metadataReferenceDescriptions.Length); - foreach (var metadataReferenceDescription in _metadataReferenceDescriptions) + Buffer.AppendFormatLines(Templates.ClassDocCommentsSectionBegin, "Metadata References"); + Buffer.AddComment("Number of Metadata References", _metadataReferenceDescriptions.Length); + if (_metadataReferenceDescriptions.Length > 0) { - AppendIndentedLines(indent, metadataReferenceDescription.DocComments); + Buffer.AddComment("Properties Kind, EmbedInteropTypes, Aliases", "[Skipped for brevity]"); } - _buffer.AppendFormat(Templates.ClassDocCommentsSectionEnd, indentString); - } - - _buffer.AppendFormat(Templates.ClassDocCommentsEnd, indentString); - } - - private void PartialTypeDeclaration(int indent = 0) - { - var genericTypes = _attributeContext.TypeParameters.Any() - ? $"<{string.Join(",", _attributeContext.TypeParameters)}>" - : string.Empty; - _buffer.Append(' ', indent); - _buffer.AppendFormat( - "{0}{1}partial {2} {3}{4}", - GetAccessibility(), - GetStatic(), - _attributeContext.RecordStructOrClass, - _attributeContext.Name, - genericTypes - ).AppendLine(); - } - - private string GetAccessibility() - { - var accessibility = _attributeContext.DeclaredAccessibility; - return GetAccessibility(accessibility); - } - - 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() => _attributeContext.IsStatic ? "static " : ""; - - private void AppendStartClass(int indent = 0) - { - var indentString = StringForIndent(indent); - _buffer.Append(indentString); - _buffer.AppendLine("{"); - } - - - private void AppendEndClass(int indent = 0) - { - var indentString = StringForIndent(indent); - _buffer.Append(indentString); - _buffer.AppendLine("}"); - } - - private void EndParentClasses() - { - // Close parent classes if any - if (_attributeContext.HasParentClasses) - { - var indent = (_attributeContext.ParentClasses.Length - 1) * 4; - for (int i = 0; i < _attributeContext.ParentClasses.Length; i++) + foreach (var metadataReferenceDescription in _metadataReferenceDescriptions) { - AppendEndClass(indent); - indent -= 4; + Buffer.AppendLines(metadataReferenceDescription.DocComments); } + Buffer.AppendLine(Templates.ClassDocCommentsSectionEnd); } - } - private void AppendDiagnosticLogs() - { - _buffer.AppendLine(); - _buffer.AppendLine("/* Diagnostic Log"); - LightweightTrace.Add(TrackingNames.DiagnosticLog_Written); - LightweightTrace.GetTrace(_buffer, TrackingNames.TracingNames); - _buffer.AppendLine("*/"); + Buffer.AppendLine(Templates.ClassDocCommentsEnd); } - - private static readonly Dictionary IndentationCache = new(); - private string StringForIndent(int indent) + + protected override void AppendDiagnosticLogs() { - if (!IndentationCache.TryGetValue(indent, out var indentString)) - { - indentString = new string(' ', indent); - IndentationCache[indent] = indentString; - } - return indentString; - } - - private void AppendIndentedLines(int indent, string lines) - { - var indentString = StringForIndent(indent); - bool includeLineBreak = false; - foreach (var line in lines.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)) - { - if (includeLineBreak) - { - _buffer.AppendLine(); - } - _buffer.Append(indentString); - _buffer.Append(line); - includeLineBreak = true; - } + Buffer.AppendLine(); + Buffer.Direct.AppendDiagnosticsComment(GeneratorStageDescriptions.GeneratorStageNameMap); } } diff --git a/SourceGeneratorContext/CompilationDescription.cs b/SourceGeneratorContext/CompilationDescription.cs index 66b21c0..ea0db0a 100644 --- a/SourceGeneratorContext/CompilationDescription.cs +++ b/SourceGeneratorContext/CompilationDescription.cs @@ -127,10 +127,8 @@ public CompilationDescription(Compilation compilation) // SyntaxTreesDocComments = sb.ToString(); } - public static CompilationDescription Select(Compilation compilation, CancellationToken token) + public static CompilationDescription Selector(Compilation compilation, CancellationToken token) { - LightweightTrace.Add(TrackingNames.CompilationDescription_Select); - token.ThrowIfCancellationRequested(); return new CompilationDescription(compilation); } diff --git a/SourceGeneratorContext/DocCommentDescriptionExtensions.cs b/SourceGeneratorContext/DocCommentDescriptionExtensions.cs index 001b5b3..b25f258 100644 --- a/SourceGeneratorContext/DocCommentDescriptionExtensions.cs +++ b/SourceGeneratorContext/DocCommentDescriptionExtensions.cs @@ -1,4 +1,5 @@ using System.Text; +using Datacute.IncrementalGeneratorExtensions; namespace Datacute.SourceGeneratorContext; @@ -10,4 +11,11 @@ public static void AddComment(this StringBuilder sb, string propertyName, object var valueString = value.ToString(); sb.AppendFormat(Templates.OptionsLine, propertyName, Templates.EscapeStringForDocComments(valueString)); } + + public static void AddComment(this IndentingLineAppender sb, string propertyName, object? value) + { + if (value == null) return; + var valueString = value.ToString(); + sb.AppendFormatLines(Templates.OptionsLine, propertyName, Templates.EscapeStringForDocComments(valueString)); + } } \ No newline at end of file diff --git a/SourceGeneratorContext/EquatableImmutableArray.cs b/SourceGeneratorContext/EquatableImmutableArray.cs deleted file mode 100644 index 4b01eb9..0000000 --- a/SourceGeneratorContext/EquatableImmutableArray.cs +++ /dev/null @@ -1,211 +0,0 @@ -using System.Collections; -using System.Collections.Concurrent; -using System.Collections.Immutable; -using Microsoft.CodeAnalysis; - -namespace Datacute.SourceGeneratorContext; - -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/SourceGeneratorContext/Generator.cs b/SourceGeneratorContext/Generator.cs index 98cc02d..57b2544 100644 --- a/SourceGeneratorContext/Generator.cs +++ b/SourceGeneratorContext/Generator.cs @@ -1,69 +1,88 @@ -using System.Collections.Immutable; +using Datacute.IncrementalGeneratorExtensions; using Microsoft.CodeAnalysis; namespace Datacute.SourceGeneratorContext; -// Record structs to replace complex tuple types -public record struct AttributeAndOptions(AttributeContext AttributeContext, AnalyzerConfigOptionsDescription Options); -public record struct AttributeOptionsAndCompilation(AttributeAndOptions AttributeAndOptions, CompilationDescription Compilation); -public record struct AttributeOptionsCompilationAndParseOptions(AttributeOptionsAndCompilation AttributeOptionsAndCompilation, ParseOptionsDescription ParseOptions); -public record struct AttributeOptionsCompilationParseAndAdditionalTexts(AttributeOptionsCompilationAndParseOptions AttributeOptionsCompilationAndParseOptions, ImmutableArray AdditionalTexts); -public record struct GeneratorSourceData(AttributeOptionsCompilationParseAndAdditionalTexts Core, ImmutableArray MetadataReferences); - [Generator(LanguageNames.CSharp)] public sealed class Generator : IIncrementalGenerator { public void Initialize(IncrementalGeneratorInitializationContext context) { - LightweightTrace.Add(TrackingNames.Generator_Initialize); + LightweightTrace.Add(GeneratorStage.Initialize); + + context.RegisterPostInitializationOutput(static postInitializationContext => + { + LightweightTrace.Add(GeneratorStage.RegisterPostInitializationOutput); + + postInitializationContext.AddSource( + Templates.IncludeFlagsHintName, + Templates.IncludeFlags); + postInitializationContext.AddSource( + Templates.AttributeHintName, + Templates.SourceGeneratorContextAttribute); + }); var attributeContexts = - context.SyntaxProvider.ForAttributeWithMetadataName( - fullyQualifiedMetadataName: Templates.AttributeFullyQualified, - predicate: AttributeContext.Predicate, - transform: AttributeContext.Transform) - .WithTrackingName(TrackingNames.InitialExtraction); + context.SelectAttributeContexts( + Templates.AttributeFullyQualified, + AttributeData.Collector); + + var globalOptionsDescriptionValueProvider = + context.AnalyzerConfigOptionsProvider + .Select(AnalyzerConfigOptionsDescription.Selector) + .WithTrackingName(GeneratorStage.AnalyzerConfigOptionsProviderSelect); - var globalOptionsDescriptionValueProvider = context.AnalyzerConfigOptionsProvider.Select(AnalyzerConfigOptionsDescription.Select); - var compilationDescriptionValueProvider = context.CompilationProvider.Select(CompilationDescription.Select); - var parseOptionsDescriptionValueProvider = context.ParseOptionsProvider.Select(ParseOptionsDescription.Select); + var compilationDescriptionValueProvider = + context.CompilationProvider + .Select(CompilationDescription.Selector) + .WithTrackingName(GeneratorStage.CompilationProviderSelect); - // It is possible to get config options FOR each additional text, but showing them is very repetitive - var additionalTextDescriptionsValuesProvider = context.AdditionalTextsProvider.Combine(context.AnalyzerConfigOptionsProvider).Select(AdditionalTextDescription.Select); - // var additionalTextDescriptionsValuesProvider = context.AdditionalTextsProvider.Select(AdditionalTextDescription.Select); - var metadataReferenceDescriptionsValuesProvider = context.MetadataReferencesProvider.Select(MetadataReferenceDescription.Select); + var parseOptionsDescriptionValueProvider = + context.ParseOptionsProvider + .Select(ParseOptionsDescription.Selector) + .WithTrackingName(GeneratorStage.ParseOptionsProviderSelect); + + var additionalTextDescriptionsValuesProvider = + context.AdditionalTextsProvider + .Combine(context.AnalyzerConfigOptionsProvider) + .Select(AdditionalTextDescription.Selector) + .WithTrackingName(GeneratorStage.AdditionalTextsProviderSelect); + + var metadataReferenceDescriptionsValuesProvider = + context.MetadataReferencesProvider + .Select(MetadataReferenceDescription.Selector) + .WithTrackingName(GeneratorStage.MetadataReferencesProviderSelect); var source = attributeContexts .Combine(globalOptionsDescriptionValueProvider) - .Select((x, _) => new AttributeAndOptions(x.Left, x.Right)) + .Select(SourceBuilder.WithOptions) .Combine(compilationDescriptionValueProvider) - .Select((x, _) => new AttributeOptionsAndCompilation(x.Left, x.Right)) + .Select(SourceBuilder.WithCompilation) .Combine(parseOptionsDescriptionValueProvider) - .Select((x, _) => new AttributeOptionsCompilationAndParseOptions(x.Left, x.Right)) - .Combine(additionalTextDescriptionsValuesProvider.Collect()) - .Select((x, _) => new AttributeOptionsCompilationParseAndAdditionalTexts(x.Left, x.Right)) - .Combine(metadataReferenceDescriptionsValuesProvider.Collect()) - .Select((x, _) => new GeneratorSourceData(x.Left, x.Right)) - .WithTrackingName(TrackingNames.Combine); - + .Select(SourceBuilder.WithParseOptions) + .CombineEquatable(additionalTextDescriptionsValuesProvider) + .Select(SourceBuilder.WithAdditionalTexts) + .CombineEquatable(metadataReferenceDescriptionsValuesProvider) + .Select(SourceBuilder.WithMetadataReferences); + context.RegisterSourceOutput(source, Action); } - private void Action(SourceProductionContext sourceProductionContext, GeneratorSourceData source) + private void Action(SourceProductionContext sourceProductionContext, SourceBuilder source) { - LightweightTrace.Add(TrackingNames.Generator_Action); + LightweightTrace.Add(GeneratorStage.RegisterSourceOutput); - var attributeContext = source.Core.AttributeOptionsCompilationAndParseOptions.AttributeOptionsAndCompilation.AttributeAndOptions.AttributeContext; + var attributeContext = source.AttributeContext; var cancellationToken = sourceProductionContext.CancellationToken; - cancellationToken.ThrowIfCancellationRequested(); + cancellationToken.ThrowIfCancellationRequested(0); + + var codeGenerator = new CodeGenerator(source.Build(), cancellationToken); - var codeGenerator = new CodeGenerator(source, cancellationToken); + var hintName = attributeContext.CreateHintName("SourceGeneratorContext"); + var generatedSource = codeGenerator.GetSourceText(); - var hintName = attributeContext.DisplayString.GetHintName(); - var generatedSource = codeGenerator.GenerateSource(); + LightweightTrace.Add(GeneratorStage.SourceProductionContextAddSource); sourceProductionContext.AddSource(hintName, generatedSource); } - - } \ No newline at end of file diff --git a/SourceGeneratorContext/GeneratorSourceData.cs b/SourceGeneratorContext/GeneratorSourceData.cs new file mode 100644 index 0000000..1875847 --- /dev/null +++ b/SourceGeneratorContext/GeneratorSourceData.cs @@ -0,0 +1,11 @@ +using Datacute.IncrementalGeneratorExtensions; + +namespace Datacute.SourceGeneratorContext; + +public readonly record struct GeneratorSourceData( + AttributeContextAndData AttributeContext, + AnalyzerConfigOptionsDescription Options, + CompilationDescription Compilation, + ParseOptionsDescription ParseOptions, + EquatableImmutableArray AdditionalTexts, + EquatableImmutableArray MetadataReferences); \ No newline at end of file diff --git a/SourceGeneratorContext/IsExternalInit.cs b/SourceGeneratorContext/IsExternalInit.cs new file mode 100644 index 0000000..94c788f --- /dev/null +++ b/SourceGeneratorContext/IsExternalInit.cs @@ -0,0 +1,7 @@ +// ReSharper disable once CheckNamespace +namespace System.Runtime.CompilerServices +{ + internal static class IsExternalInit + { + } +} \ No newline at end of file diff --git a/SourceGeneratorContext/LightweightTracing/LightweightTrace.cs b/SourceGeneratorContext/LightweightTracing/LightweightTrace.cs deleted file mode 100644 index 12eb47b..0000000 --- a/SourceGeneratorContext/LightweightTracing/LightweightTrace.cs +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (c) 2025 Stephen Denne - * https://github.com/datacute/LightweightTracing - */ - -using System.Diagnostics; -using System.Text; - -namespace Datacute.SourceGeneratorContext; - -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) - { - Events[_index] = (Stopwatch.ElapsedTicks, eventId); - _index = (_index + 1) % Capacity; - } - - public static void GetTrace(StringBuilder stringBuilder, Dictionary eventNameMap) - { - var index = _index; - for (var i = 0; i < Capacity; i++) - { - var (timestamp, eventId) = Events[index]; - if (timestamp > 0) - { - stringBuilder.AppendFormat("{0:o} [{1:000}] {2}", - StartTime.AddTicks(timestamp), - eventId, - eventNameMap.TryGetValue(eventId, out var name) ? name : string.Empty) - .AppendLine(); - } - - index = (index + 1) % Capacity; - } - } -} \ No newline at end of file diff --git a/SourceGeneratorContext/MetadataReferenceDescription.cs b/SourceGeneratorContext/MetadataReferenceDescription.cs index a20a932..a0a2b53 100644 --- a/SourceGeneratorContext/MetadataReferenceDescription.cs +++ b/SourceGeneratorContext/MetadataReferenceDescription.cs @@ -3,10 +3,10 @@ namespace Datacute.SourceGeneratorContext; -public readonly struct MetadataReferenceDescription +public readonly record struct MetadataReferenceDescription { public readonly string DocComments; - public MetadataReferenceDescription(MetadataReference metadataReference) + private MetadataReferenceDescription(MetadataReference metadataReference) { var sb = new StringBuilder(); sb.AddComment("Display", metadataReference.Display); @@ -17,10 +17,8 @@ public MetadataReferenceDescription(MetadataReference metadataReference) DocComments = sb.ToString(); } - public static MetadataReferenceDescription Select(MetadataReference metadataReference, CancellationToken token) + public static MetadataReferenceDescription Selector(MetadataReference metadataReference, CancellationToken token) { - //LightweightTrace.Add(TrackingNames.MetadataReferenceDescription_Select); - token.ThrowIfCancellationRequested(); return new MetadataReferenceDescription(metadataReference); } diff --git a/SourceGeneratorContext/ParseOptionsDescription.cs b/SourceGeneratorContext/ParseOptionsDescription.cs index 8f90729..856ca36 100644 --- a/SourceGeneratorContext/ParseOptionsDescription.cs +++ b/SourceGeneratorContext/ParseOptionsDescription.cs @@ -30,10 +30,8 @@ public ParseOptionsDescription(ParseOptions parseOptions) DocComments = sb.ToString(); } - public static ParseOptionsDescription Select(ParseOptions parseOptions, CancellationToken token) + public static ParseOptionsDescription Selector(ParseOptions parseOptions, CancellationToken token) { - LightweightTrace.Add(TrackingNames.ParseOptionsDescription_Select); - token.ThrowIfCancellationRequested(); return new ParseOptionsDescription(parseOptions); } diff --git a/SourceGeneratorContext/SourceBuilder.cs b/SourceGeneratorContext/SourceBuilder.cs new file mode 100644 index 0000000..84133a7 --- /dev/null +++ b/SourceGeneratorContext/SourceBuilder.cs @@ -0,0 +1,48 @@ +using Datacute.IncrementalGeneratorExtensions; + +namespace Datacute.SourceGeneratorContext; + +public readonly record struct SourceBuilder( + AttributeContextAndData AttributeContext, + AnalyzerConfigOptionsDescription? Options, + CompilationDescription? Compilation, + ParseOptionsDescription? ParseOptions, + EquatableImmutableArray? AdditionalTexts, + EquatableImmutableArray? MetadataReferences) +{ + public static SourceBuilder WithOptions( + (AttributeContextAndData AttributeContext, AnalyzerConfigOptionsDescription Options) data, + CancellationToken _) + => new(data.AttributeContext, data.Options, null, null, null, null); + + public static SourceBuilder WithCompilation( + (SourceBuilder previous, CompilationDescription Compilation) data, + CancellationToken _) + => data.previous with { Compilation = data.Compilation }; + + public static SourceBuilder WithParseOptions( + (SourceBuilder previous, ParseOptionsDescription ParseOptions) data, + CancellationToken _) + => data.previous with { ParseOptions = data.ParseOptions }; + + public static SourceBuilder WithAdditionalTexts( + (SourceBuilder previous, EquatableImmutableArray AdditionalTexts) data, + CancellationToken _) + => data.previous with { AdditionalTexts = data.AdditionalTexts }; + + public static SourceBuilder WithMetadataReferences( + (SourceBuilder previous, EquatableImmutableArray MetadataReferences) data, + CancellationToken _) + => data.previous with { MetadataReferences = data.MetadataReferences }; + + /// + /// This returns essentially the same data, but no longer nullable. + /// + public GeneratorSourceData Build() => new( + AttributeContext, + Options!.Value, + Compilation!.Value, + ParseOptions!.Value, + AdditionalTexts!, + MetadataReferences!); +} \ No newline at end of file diff --git a/SourceGeneratorContext/SourceGeneratorContext.csproj b/SourceGeneratorContext/SourceGeneratorContext.csproj index f992cbe..27139df 100644 --- a/SourceGeneratorContext/SourceGeneratorContext.csproj +++ b/SourceGeneratorContext/SourceGeneratorContext.csproj @@ -51,7 +51,19 @@ + + true + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + @@ -61,22 +73,18 @@ - - - - - - - - + + + + diff --git a/SourceGeneratorContext/SourceGeneratorContext.csproj.DotSettings b/SourceGeneratorContext/SourceGeneratorContext.csproj.DotSettings new file mode 100644 index 0000000..2d05e85 --- /dev/null +++ b/SourceGeneratorContext/SourceGeneratorContext.csproj.DotSettings @@ -0,0 +1,2 @@ + + True \ No newline at end of file diff --git a/SourceGeneratorContext/Templates.cs b/SourceGeneratorContext/Templates.cs index 3b58285..f38a00d 100644 --- a/SourceGeneratorContext/Templates.cs +++ b/SourceGeneratorContext/Templates.cs @@ -1,28 +1,29 @@ -namespace Datacute.SourceGeneratorContext; +using Datacute.AdditionalTextConstantGenerator; -internal static class Templates +namespace Datacute.SourceGeneratorContext; + +[AdditionalTextConstants(".cs", "Templates")] +internal static partial class Templates { private const string GeneratorNamespace = "Datacute.SourceGeneratorContext"; private const string AttributeName = "SourceGeneratorContextAttribute"; public const string AttributeFullyQualified = GeneratorNamespace + "." + AttributeName; - public const string AutoGeneratedComment = /* language=c# */ - @"//------------------------------------------------------------------------------ -// -// This code was generated by the Datacute.SourceGeneratorContext. -// -//------------------------------------------------------------------------------ -"; + public const string AttributeHintName = + "Datacute.SourceGeneratorContext.SourceGeneratorContextAttribute.g.cs"; + + public const string IncludeFlagsHintName = + "Datacute.SourceGeneratorContext.IncludeFlags.g.cs"; public const string ClassDocCommentsBegin = /* language=c# */ - @"{0}/// -{0}/// When this item is examined by a source generator...

+ @"/// +/// When this item is examined by a source generator...

"; public const string ClassDocCommentsSectionBegin = /* language=c# */ - @"{0}/// {1} contains: -{0}/// -{0}/// KeyValue + @"/// {0} contains: +/// +/// KeyValue "; public const string OptionsLine = /* language=c# */ @@ -30,11 +31,11 @@ internal static class Templates "; public const string ClassDocCommentsSectionEnd = /* language=c# */ - @"{0}///
+ @"///

"; public const string ClassDocCommentsEnd = /* language=c# */ - @"{0}///
+ @"///
"; public static string EscapeStringForDocComments(string input) => diff --git a/SourceGeneratorContext/Templates/IncludeFlags.cs b/SourceGeneratorContext/Templates/IncludeFlags.cs new file mode 100644 index 0000000..e39e41f --- /dev/null +++ b/SourceGeneratorContext/Templates/IncludeFlags.cs @@ -0,0 +1,98 @@ +using System; + +namespace Datacute.SourceGeneratorContext +{ + /// + /// Specifies the types of information to include in the generated source context documentation. + /// + [Flags] + public enum IncludeFlags : uint + { + /// + /// If no other specific flags are set, a basic summary of the context is included. + /// This flag is often managed internally by the generator to provide a default output. + /// + Summary = 0, + + /// + /// Includes details about the GeneratorAttributeSyntaxContext.TargetSymbol. + /// + AttributeContextTargetSymbol = 1U << 0, + + /// + /// Includes details about the GeneratorAttributeSyntaxContext.TargetSymbol when cast to an ITypeSymbol. + /// + AttributeContextTypeSymbol = 1U << 1, + + /// + /// Includes details about the GeneratorAttributeSyntaxContext.TargetSymbol when cast to an INamedTypeSymbol. + /// + AttributeContextNamedTypeSymbol = 1U << 2, + + /// + /// Includes details about the GeneratorAttributeSyntaxContext.TargetNode. + /// + AttributeContextTargetNode = 1U << 3, + + /// + /// Includes details about the GeneratorAttributeSyntaxContext.Attributes. + /// + AttributeContextAttributes = 1U << 4, + + /// + /// Includes details about all attributes applied to the target symbol (using GetAttributes()). + /// + AttributeContextAllAttributes = 1U << 5, + + /// + /// Includes details from the AnalyzerConfigOptionsProvider's GlobalOptions. + /// + GlobalOptions = 1U << 6, + + /// + /// Includes general details about the Compilation. + /// + Compilation = 1U << 7, + + /// + /// Includes details about the Compilation's options. + /// + CompilationOptions = 1U << 8, + + /// + /// Includes details about the Compilation's assembly. + /// + CompilationAssembly = 1U << 9, + + /// + /// Includes counts and names of Compilation's references (e.g., References, DirectiveReferences). + /// + CompilationReferences = 1U << 10, + + /// + /// Includes details about the ParseOptionsProvider's ParseOptions. + /// + ParseOptions = 1U << 11, + + /// + /// Includes details about AdditionalTextsProvider's AdditionalText entries. + /// + AdditionalTexts = 1U << 12, + + /// + /// Includes details about AdditionalTextsProvider's AdditionalText options (from AnalyzerConfigOptions). + /// + AdditionalTextsOptions = 1U << 13, + + /// + /// Includes details about MetadataReferencesProvider's MetadataReference entries. + /// + MetadataReferences = 1U << 14, + + /// + /// Includes all available details about the source generation context. + /// + All = ~0U + + } +} diff --git a/SourceGeneratorContext/Templates/SourceGeneratorContextAttribute.cs b/SourceGeneratorContext/Templates/SourceGeneratorContextAttribute.cs new file mode 100644 index 0000000..4a04ac0 --- /dev/null +++ b/SourceGeneratorContext/Templates/SourceGeneratorContextAttribute.cs @@ -0,0 +1,58 @@ +using System; +// ReSharper disable UnusedAutoPropertyAccessor.Global Properties getters are not used as the source generator reads the source code. +// ReSharper disable UnusedParameter.Local Unused parameters are used to demonstrate behaviour. + +namespace Datacute.SourceGeneratorContext; + +/// +/// Add this attribute to a partial class to generate doc-comments detailing the source generation context. +/// +[System.Diagnostics.Conditional("DATACUTE_SOURCEGENERATORCONTEXTATTRIBUTE_USAGES")] +[AttributeUsage( + validOn: AttributeTargets.Class | + AttributeTargets.Interface | + AttributeTargets.Struct, // Method and Property should be allowed too + Inherited = true, // Inherited to show how SyntaxProvider.ForAttributeWithMetadataName doesn't support inheritance + AllowMultiple = true)] // AllowMultiple to show the differences when multiple attributes are applied +public class SourceGeneratorContextAttribute : Attribute +{ + /// + /// There is a huge amount of information available, but Visual Studio does not scroll doc-comments. + /// So either IncludeAll and view the generated source, or set one of the named parameters to control what gets output: + /// + /// [SourceGeneratorContext(IncludeFlags.All)] + /// internal partial class Example; + /// + /// + public SourceGeneratorContextAttribute(IncludeFlags flags = IncludeFlags.Summary) + { + IncludeFlags = flags; + } + + /// + /// Flags to control what information is included in the generated doc-comments. + /// + public IncludeFlags IncludeFlags { get; set; } + + #region Demonstration purposes only + + /// + /// Example of a named parameter. + /// + public string ExampleNamedParameter { get; set; } = + string.Empty; // only used for demonstrating working with Named Parameters + + /// + /// Example of an optional parameter. + /// + /// + public + SourceGeneratorContextAttribute( + string exampleOptionalParameter) // only used for demonstrating working with Constructor Arguments + { + // The constructor arguments do not need to be assigned to fields or properties + // as the source of the supplied values is what is available to the source generator + } + + #endregion +} \ No newline at end of file diff --git a/SourceGeneratorContext/TrackingNames.cs b/SourceGeneratorContext/TrackingNames.cs deleted file mode 100644 index 0512364..0000000 --- a/SourceGeneratorContext/TrackingNames.cs +++ /dev/null @@ -1,30 +0,0 @@ -namespace Datacute.SourceGeneratorContext; - -public static class TrackingNames -{ - public const string InitialExtraction = nameof(InitialExtraction); - public const string Combine = nameof(Combine); - - public const int Generator_Initialize = 0; - public const int AttributeContext_Transform = 1; // too noisy to include - public const int AnalyzerConfigOptionsDescription_Select = 2; - public const int CompilationDescription_Select = 3; - public const int ParseOptionsDescription_Select = 4; - public const int AdditionalTextDescription_Select = 5; - public const int MetadataReferenceDescription_Select = 6; - public const int Generator_Action = 7; - public const int DiagnosticLog_Written = 8; - - public static readonly Dictionary TracingNames = new() - { - { Generator_Initialize, nameof(Generator_Initialize) }, - { AttributeContext_Transform, nameof(AttributeContext_Transform) }, - { AnalyzerConfigOptionsDescription_Select, nameof(AnalyzerConfigOptionsDescription_Select) }, - { CompilationDescription_Select, nameof(CompilationDescription_Select) }, - { ParseOptionsDescription_Select, nameof(ParseOptionsDescription_Select) }, - { AdditionalTextDescription_Select, nameof(AdditionalTextDescription_Select) }, - { MetadataReferenceDescription_Select, nameof(MetadataReferenceDescription_Select) }, - { Generator_Action, nameof(Generator_Action) }, - { DiagnosticLog_Written, nameof(DiagnosticLog_Written) }, - }; -} \ No newline at end of file diff --git a/SourceGeneratorContextExample/Program.cs b/SourceGeneratorContextExample/Program.cs index 4c2cd52..1421cc2 100644 --- a/SourceGeneratorContextExample/Program.cs +++ b/SourceGeneratorContextExample/Program.cs @@ -21,53 +21,53 @@ public static partial class ClassInGlobalNamespace; #region All 'include' options -[SourceGeneratorContext(IncludeAttributeContextTargetSymbol = true)] +[SourceGeneratorContext(IncludeFlags.AttributeContextTargetSymbol)] public partial class ViewClassDocsToSeeGeneratorAttributeSyntaxContextTargetSymbol; -[SourceGeneratorContext(IncludeAttributeContextTypeSymbol = true)] +[SourceGeneratorContext(IncludeFlags.AttributeContextTypeSymbol)] public partial class ViewClassDocsToSeeGeneratorAttributeSyntaxContextTypeSymbol; -[SourceGeneratorContext(IncludeAttributeContextNamedTypeSymbol = true)] +[SourceGeneratorContext(IncludeFlags.AttributeContextNamedTypeSymbol)] public partial class ViewClassDocsToSeeGeneratorAttributeSyntaxContextNamedTypeSymbol; -[SourceGeneratorContext(IncludeAttributeContextTargetNode = true)] +[SourceGeneratorContext(IncludeFlags.AttributeContextTargetNode)] public partial class ViewClassDocsToSeeGeneratorAttributeSyntaxContextTargetNode; -[SourceGeneratorContext(IncludeAttributeContextAttributes = true)] +[SourceGeneratorContext(IncludeFlags.AttributeContextAttributes)] public partial class ViewClassDocsToSeeGeneratorAttributeSyntaxContextAttributes; -[SourceGeneratorContext(IncludeAttributeContextAllAttributes = true)] +[SourceGeneratorContext(IncludeFlags.AttributeContextAllAttributes)] public partial class ViewClassDocsToSeeGeneratorAttributeSyntaxContextAllAttributes; -[SourceGeneratorContext(IncludeGlobalOptions = true)] +[SourceGeneratorContext(IncludeFlags.GlobalOptions)] public partial class ViewClassDocsToSeeAnalyzerConfigOptionsProviderGlobalOptions; -[SourceGeneratorContext(IncludeCompilation = true)] +[SourceGeneratorContext(IncludeFlags.Compilation)] public partial class ViewClassDocsToSeeCompilation; -[SourceGeneratorContext(IncludeCompilationOptions = true)] +[SourceGeneratorContext(IncludeFlags.CompilationOptions)] public partial class ViewClassDocsToSeeCompilationOptions; -[SourceGeneratorContext(IncludeCompilationAssembly = true)] +[SourceGeneratorContext(IncludeFlags.CompilationAssembly)] public partial class ViewClassDocsToSeeCompilationAssembly; -[SourceGeneratorContext(IncludeCompilationReferences = true)] +[SourceGeneratorContext(IncludeFlags.CompilationReferences)] public partial class ViewClassDocsToSeeCompilationReferences; -[SourceGeneratorContext(IncludeParseOptions = true)] +[SourceGeneratorContext(IncludeFlags.ParseOptions)] public partial class ViewClassDocsToSeeParseOptions; -[SourceGeneratorContext(IncludeAdditionalTexts = true)] +[SourceGeneratorContext(IncludeFlags.AdditionalTexts)] public partial class ViewClassDocsToSeeAdditionalTexts; -[SourceGeneratorContext(IncludeMetadataReferences = true)] +[SourceGeneratorContext(IncludeFlags.MetadataReferences)] public partial class ViewClassDocsToSeeMetadataReferences; // IncludeAdditionalTextsOptions seems really repetitive, so is not included in "includeAll" -[SourceGeneratorContext(IncludeAdditionalTexts = true, IncludeAdditionalTextsOptions = true)] +[SourceGeneratorContext(IncludeFlags.AdditionalTexts | IncludeFlags.AdditionalTextsOptions)] public partial class ViewClassDocsToSeeAdditionalTextsWithOptions; -[SourceGeneratorContext(IncludeAll = true)] +[SourceGeneratorContext(IncludeFlags.All)] public partial class ViewClassDocsToSeeAllAvailableDetails; #endregion @@ -150,7 +150,7 @@ namespace ExampleNamespace [SourceGeneratorContext] internal static partial class ClassInNamespace; - [SourceGeneratorContext(IncludeAttributeContextTypeSymbol = true)] + [SourceGeneratorContext(IncludeFlags.AttributeContextTypeSymbol)] internal partial record RecordClass(string Example) { [SourceGeneratorContext] @@ -165,7 +165,7 @@ internal static partial class InnerClassWithinParentClass { [Obsolete("Included in `TargetSymbol.AllAttributes()`")] [SourceGeneratorContext("Included in `Attributes` and `TargetSymbol.AllAttributes()`")] - [SourceGeneratorContext] + [SourceGeneratorContext(IncludeFlags.AttributeContextAllAttributes)] private static partial class InnerClassWithinMultipleParents; } @@ -178,10 +178,10 @@ internal partial class InnerClassOfGenericClass; } } -[SourceGeneratorContext(IncludeAttributeContextTypeSymbol = true)] +[SourceGeneratorContext(IncludeFlags.AttributeContextTypeSymbol)] public partial struct ExampleStructure; -[SourceGeneratorContext(IncludeAttributeContextTypeSymbol = true)] +[SourceGeneratorContext(IncludeFlags.AttributeContextTypeSymbol)] public partial interface IExampleInterface; diff --git a/SourceGeneratorContextExample/SourceGeneratorContextExample.csproj b/SourceGeneratorContextExample/SourceGeneratorContextExample.csproj index 4d54c55..cbecd2c 100644 --- a/SourceGeneratorContextExample/SourceGeneratorContextExample.csproj +++ b/SourceGeneratorContextExample/SourceGeneratorContextExample.csproj @@ -30,13 +30,8 @@ - - - - - \ No newline at end of file diff --git a/global.json b/global.json index f4fd385..a24b744 100644 --- a/global.json +++ b/global.json @@ -1,7 +1,6 @@ { "sdk": { "version": "9.0.0", - "rollForward": "latestMajor", - "allowPrerelease": true + "rollForward": "latestFeature" } } \ No newline at end of file diff --git a/version.props b/version.props index 356ac8b..71bc521 100644 --- a/version.props +++ b/version.props @@ -1,6 +1,14 @@ - 0.0.2 - alpha + 1.0.0 + + + + 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