diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..487ec02
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,5 @@
+root = true
+
+[*]
+# This only shows when calling AnalyzerConfigOptionsProvider.GetOptions(additionalText)
+example_editorconfig_value = true
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index d016234..ca9f810 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -23,7 +23,7 @@ jobs:
env:
BUILD_CONFIG: 'Release'
- SOLUTION: 'SourceGeneratorContext.sln'
+ SOLUTION: '*.sln'
runs-on: ubuntu-latest
@@ -33,7 +33,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
- dotnet-version: '8.0.x'
+ dotnet-version: '9.0.x'
- name: Restore dependencies
run: dotnet restore $SOLUTION
@@ -44,27 +44,32 @@ jobs:
- name: Run tests
run: dotnet test --configuration $BUILD_CONFIG --no-restore --no-build --verbosity normal
+ - name: Pack NuGet packages
+ run: |
+ dotnet pack $SOLUTION --configuration $BUILD_CONFIG --no-build --output ./artifacts
+ 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/')
- run: |
- TAG=$(git tag --points-at HEAD | grep -E '^[0-9]+\.[0-9]+\.[0-9]+$' | head -n 1)
- echo "tag=$TAG" >> $GITHUB_OUTPUT
- echo "Found tag: $TAG"
-
- - name: Pack NuGet package
- if: steps.get_tag.outputs.tag != ''
- run: dotnet pack $SOLUTION --configuration $BUILD_CONFIG --no-build --output ./artifacts
+ uses: olegtarasov/get-tag@v2.1.4
- - name: Publish to NuGet
+ - name: Extract release notes from CHANGELOG.md
if: steps.get_tag.outputs.tag != ''
- run: dotnet nuget push ./artifacts/*.nupkg --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json --skip-duplicate
+ id: extract_notes
+ uses: mindsers/changelog-reader-action@v2
+ with:
+ version: ${{ steps.get_tag.outputs.tag }}
+ path: ./CHANGELOG.md
- name: Create GitHub Release
if: steps.get_tag.outputs.tag != ''
uses: softprops/action-gh-release@v1
with:
tag_name: ${{ steps.get_tag.outputs.tag }}
- generate_release_notes: true
+ body: ${{ steps.extract_notes.outputs.changes }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
new file mode 100644
index 0000000..76f135a
--- /dev/null
+++ b/.github/workflows/publish.yml
@@ -0,0 +1,28 @@
+name: Publish to NuGet
+
+on:
+ workflow_dispatch:
+
+permissions:
+ contents: read
+
+jobs:
+ publish:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: '9.0.x'
+
+ - name: Restore, Build, and Pack
+ run: |
+ dotnet restore *.sln
+ dotnet build *.sln --configuration Release --no-restore
+ dotnet pack *.sln --configuration Release --no-build --output ./artifacts
+
+ - 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/.gitignore b/.gitignore
index 35063fc..66a5b5b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -51,4 +51,13 @@ CodeCoverage/
# NUnit
*.VisualState.xml
TestResult.xml
-nunit-*.xml
\ No newline at end of file
+nunit-*.xml
+
+# Visual Studio Code
+.vscode/
+
+# Rider
+.idea/
+
+# Visual Studio
+.vs/
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..fae3586
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,52 @@
+# Changelog
+
+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/),
+and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+
+## [Unreleased]
+
+### Added
+- `SourceGeneratorContext` library to help creators of source generators.
+- `[SourceGeneratorContext]` attribute to mark partial classes for generation.
+- Generate doc-comments to partial classes showing different portions of the context available to source generators.
+ - IncludeAll
+ - include all available details.
+ - IncludeAttributeContextTargetSymbol
+ - GeneratorAttributeSyntaxContext.TargetSymbol details
+ - IncludeAttributeContextTypeSymbol
+ - GeneratorAttributeSyntaxContext.TargetSymbol as ITypeSymbol details
+ - IncludeAttributeContextNamedTypeSymbol
+ - GeneratorAttributeSyntaxContext.TargetSymbol as INamedTypeSymbol details
+ - IncludeAttributeContextTargetNode
+ - GeneratorAttributeSyntaxContext.TargetNode details
+ - IncludeAttributeContextAttributes
+ - GeneratorAttributeSyntaxContext.Attributes details
+ - IncludeAttributeContextAllAttributes
+ - GeneratorAttributeSyntaxContext.GetAttributes() details
+ - IncludeGlobalOptions
+ - AnalyzerConfigOptionsProvider's GlobalOptions details
+ - IncludeCompilation
+ - CompilationProvider's Compilation details
+ - IncludeCompilationOptions
+ - CompilationProvider's Compilation.Options details
+ - IncludeCompilationAssembly
+ - CompilationProvider's Compilation.Assembly details
+ - IncludeCompilationReferences
+ - Counts of CompilationProvider's:
+ - Compilation.References
+ - Compilation.DirectiveReferences
+ - Compilation.ExternalReferences
+ - Compilation.ReferencedAssemblyNames
+ - IncludeParseOptions
+ - ParseOptionsProvider's ParseOptions details
+ - IncludeAdditionalTexts
+ - AdditionalTextsProvider's AdditionalText details
+ - IncludeAdditionalTextsOptions
+ - AdditionalTextsProvider's AdditionalText details combined with AnalyzerConfigOptionsProvider's AnalyzerConfigOptions for the AdditionalText
+ - IncludeMetadataReferences
+ - MetadataReferencesProvider's MetadataReference details
+- Diagnostic log of the source generation process and timing included.
+
+[Unreleased]: https://github.com/datacute/SourceGeneratorContext/compare/main...develop
diff --git a/Directory.Build.props b/Directory.Build.props
new file mode 100644
index 0000000..9954b4c
--- /dev/null
+++ b/Directory.Build.props
@@ -0,0 +1,19 @@
+
+
+
+
+
+ Stephen Denne
+ Copyright © Stephen Denne 2024, 2025
+ Datacute
+ https://github.com/datacute/SourceGeneratorContext
+ See full release notes and changelog: $(PackageProjectUrl)/blob/main/CHANGELOG.md
+ false
+
+
+
+ true
+ $(MSBuildThisFileDirectory)\artifacts
+
+
+
\ No newline at end of file
diff --git a/SourceGeneratorContext.Attribute/SourceGeneratorContext.Attribute.csproj b/SourceGeneratorContext.Attribute/SourceGeneratorContext.Attribute.csproj
new file mode 100644
index 0000000..7465efd
--- /dev/null
+++ b/SourceGeneratorContext.Attribute/SourceGeneratorContext.Attribute.csproj
@@ -0,0 +1,16 @@
+
+
+
+ netstandard2.0
+ 12
+ Datacute.SourceGeneratorContext.Attribute
+ Datacute.SourceGeneratorContext
+ enable
+ 1.0.0
+
+
+
+ true
+
+
+
diff --git a/SourceGeneratorContext.Attribute/SourceGeneratorContextAttribute.cs b/SourceGeneratorContext.Attribute/SourceGeneratorContextAttribute.cs
new file mode 100644
index 0000000..a207bc5
--- /dev/null
+++ b/SourceGeneratorContext.Attribute/SourceGeneratorContextAttribute.cs
@@ -0,0 +1,134 @@
+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
new file mode 100644
index 0000000..2f93387
--- /dev/null
+++ b/SourceGeneratorContext.sln
@@ -0,0 +1,42 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+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}"
+ ProjectSection(SolutionItems) = preProject
+ CHANGELOG.md = CHANGELOG.md
+ Directory.Build.props = Directory.Build.props
+ LICENSE = LICENSE
+ version.props = version.props
+ EndProjectSection
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {DB6CCC3F-D197-48F7-B166-E9194233939D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {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
+ {56E79762-6BB4-4042-9EAB-2D819C2FDAB0}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/SourceGeneratorContext.sln.DotSettings.user b/SourceGeneratorContext.sln.DotSettings.user
new file mode 100644
index 0000000..ad15185
--- /dev/null
+++ b/SourceGeneratorContext.sln.DotSettings.user
@@ -0,0 +1,47 @@
+
+ ForceIncluded
+ ForceIncluded
+ ForceIncluded
+ ForceIncluded
+ ForceIncluded
+ ForceIncluded
+ ForceIncluded
+ ForceIncluded
+ ForceIncluded
+ ForceIncluded
+ ForceIncluded
+ ForceIncluded
+ ForceIncluded
+ ForceIncluded
+ ForceIncluded
+ ForceIncluded
+ ForceIncluded
+ ForceIncluded
+ ForceIncluded
+ ForceIncluded
+ ForceIncluded
+ ForceIncluded
+ ForceIncluded
+ ForceIncluded
+ ForceIncluded
+ ForceIncluded
+ ForceIncluded
+ ForceIncluded
+ ForceIncluded
+ ForceIncluded
+ ForceIncluded
+ ForceIncluded
+ ForceIncluded
+ ForceIncluded
+ ForceIncluded
+ ForceIncluded
+ ForceIncluded
+ ForceIncluded
+ ForceIncluded
+ ForceIncluded
+ ForceIncluded
+ ForceIncluded
+ ForceIncluded
+ ForceIncluded
+ ForceIncluded
+
\ No newline at end of file
diff --git a/SourceGeneratorContext/AdditionalTextDescription.cs b/SourceGeneratorContext/AdditionalTextDescription.cs
new file mode 100644
index 0000000..974fa5d
--- /dev/null
+++ b/SourceGeneratorContext/AdditionalTextDescription.cs
@@ -0,0 +1,53 @@
+using System.Text;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Diagnostics;
+
+namespace Datacute.SourceGeneratorContext;
+
+public readonly struct AdditionalTextDescription
+{
+ public readonly string DocComments;
+ public readonly string OptionsComments;
+
+ public AdditionalTextDescription(AdditionalText additionalText, AnalyzerConfigOptions? options = null)
+ {
+ var sb = new StringBuilder();
+ sb.AddComment("Path", additionalText.Path);
+ var sourceText = additionalText.GetText();
+ sb.AddComment("Length", sourceText?.Length);
+ sb.AddComment("Encoding", sourceText?.Encoding?.EncodingName);
+ sb.AddComment("Lines", sourceText?.Lines.Count);
+ sb.AddComment("ChecksumAlgorithm", sourceText?.ChecksumAlgorithm);
+ sb.AddComment("CanBeEmbedded", sourceText?.CanBeEmbedded);
+
+ DocComments = sb.ToString();
+
+ sb.Clear();
+
+ if (options != null)
+ {
+ foreach (var key in options.Keys)
+ {
+ var v = options.TryGetValue(key, out var value) ? value : string.Empty;
+ sb.AddComment(key, v);
+ }
+ }
+
+ OptionsComments = sb.ToString();
+ }
+
+ public static AdditionalTextDescription Select(AdditionalText additionalText, CancellationToken token)
+ {
+ token.ThrowIfCancellationRequested();
+ return new AdditionalTextDescription(additionalText);
+ }
+
+ public static AdditionalTextDescription Select((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);
+ }
+}
\ No newline at end of file
diff --git a/SourceGeneratorContext/AnalyzerConfigOptionsDescription.cs b/SourceGeneratorContext/AnalyzerConfigOptionsDescription.cs
new file mode 100644
index 0000000..f6087df
--- /dev/null
+++ b/SourceGeneratorContext/AnalyzerConfigOptionsDescription.cs
@@ -0,0 +1,28 @@
+using System.Text;
+using Microsoft.CodeAnalysis.Diagnostics;
+
+namespace Datacute.SourceGeneratorContext;
+
+public readonly struct AnalyzerConfigOptionsDescription
+{
+ public readonly string DocComments;
+
+ public AnalyzerConfigOptionsDescription(AnalyzerConfigOptions options)
+ {
+ var sb = new StringBuilder();
+ foreach (var key in options.Keys)
+ {
+ var v = options.TryGetValue(key, out var value) ? value : string.Empty;
+ sb.AddComment(key, v);
+ }
+ DocComments = sb.ToString();
+ }
+
+ public static AnalyzerConfigOptionsDescription Select(AnalyzerConfigOptionsProvider provider, CancellationToken token)
+ {
+ LightweightTrace.Add(TrackingNames.AnalyzerConfigOptionsDescription_Select);
+
+ token.ThrowIfCancellationRequested();
+ return new AnalyzerConfigOptionsDescription(provider.GlobalOptions);
+ }
+}
\ No newline at end of file
diff --git a/SourceGeneratorContext/AttributeContext.cs b/SourceGeneratorContext/AttributeContext.cs
new file mode 100644
index 0000000..0a354eb
--- /dev/null
+++ b/SourceGeneratorContext/AttributeContext.cs
@@ -0,0 +1,427 @@
+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/CodeGenerator.cs b/SourceGeneratorContext/CodeGenerator.cs
new file mode 100644
index 0000000..7ea8893
--- /dev/null
+++ b/SourceGeneratorContext/CodeGenerator.cs
@@ -0,0 +1,318 @@
+using System.Collections.Immutable;
+using System.Text;
+using Microsoft.CodeAnalysis;
+
+namespace Datacute.SourceGeneratorContext;
+
+public readonly struct CodeGenerator
+{
+ public CodeGenerator(
+ in GeneratorSourceData source,
+ in CancellationToken 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();
+ }
+
+ private readonly AttributeContext _attributeContext;
+ 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 void StartNamespace()
+ {
+ if (_attributeContext.ContainingNamespaceIsGlobalNamespace) return;
+
+ _buffer.Append("namespace ");
+ _buffer.Append(_attributeContext.ContainingNamespaceDisplayString);
+ _buffer.Append(';').AppendLine();
+ }
+
+ private int ParentClasses()
+ {
+ var indentLevel = 0;
+ if (_attributeContext.HasParentClasses)
+ {
+ indentLevel = StartParentClasses();
+ }
+ return indentLevel;
+ }
+
+ private int StartParentClasses()
+ {
+ var indent = 0;
+ foreach (var parentClass in _attributeContext.ParentClasses)
+ {
+ _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;
+ }
+ 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))
+ {
+ _buffer.AppendFormat(Templates.ClassDocCommentsSectionBegin, indentString, "GeneratorAttributeSyntaxContext TargetSymbol as INamedTypeSymbol");
+ AppendIndentedLines(indent, _attributeContext.NamedTypeSymbolDocComments);
+ _buffer.AppendFormat(Templates.ClassDocCommentsSectionEnd, indentString);
+ }
+
+ if (_attributeContext.IncludeAttributeContextTargetNode || _attributeContext.IncludeAll)
+ {
+ _buffer.AppendFormat(Templates.ClassDocCommentsSectionBegin, indentString, "GeneratorAttributeSyntaxContext TargetNode");
+ AppendIndentedLines(indent, _attributeContext.TargetNodeDocComments);
+ _buffer.AppendFormat(Templates.ClassDocCommentsSectionEnd, indentString);
+ }
+
+ if (_attributeContext.IncludeAttributeContextAttributes || _attributeContext.IncludeAll)
+ {
+ _buffer.AppendFormat(Templates.ClassDocCommentsSectionBegin, indentString, "GeneratorAttributeSyntaxContext Attributes");
+ AppendIndentedLines(indent, _attributeContext.AttributesDocComments);
+ _buffer.AppendFormat(Templates.ClassDocCommentsSectionEnd, indentString);
+ }
+
+ if (_attributeContext.IncludeAttributeContextAllAttributes || _attributeContext.IncludeAll)
+ {
+ _buffer.AppendFormat(Templates.ClassDocCommentsSectionBegin, indentString, "GeneratorAttributeSyntaxContext TargetSymbol AllAttributes()");
+ AppendIndentedLines(indent, _attributeContext.AllAttributesDocComments);
+ _buffer.AppendFormat(Templates.ClassDocCommentsSectionEnd, indentString);
+ }
+
+ if (_attributeContext.IncludeGlobalOptions || _attributeContext.IncludeAll)
+ {
+ _buffer.AppendFormat(Templates.ClassDocCommentsSectionBegin, indentString, "AnalyzerConfigOptionsProvider GlobalOptions");
+ AppendIndentedLines(indent, _globalOptionsDescription.DocComments);
+ _buffer.AppendFormat(Templates.ClassDocCommentsSectionEnd, indentString);
+ }
+
+ if (_attributeContext.IncludeCompilation || _attributeContext.IncludeAll)
+ {
+ _buffer.AppendFormat(Templates.ClassDocCommentsSectionBegin, indentString, "Compilation");
+ AppendIndentedLines(indent, _compilationDescription.DocComments);
+ _buffer.AppendFormat(Templates.ClassDocCommentsSectionEnd, indentString);
+ }
+
+ if (_attributeContext.IncludeCompilationOptions || _attributeContext.IncludeAll)
+ {
+ _buffer.AppendFormat(Templates.ClassDocCommentsSectionBegin, indentString, "Compilation Options");
+ AppendIndentedLines(indent, _compilationDescription.OptionsDocComments);
+ _buffer.AppendFormat(Templates.ClassDocCommentsSectionEnd, indentString);
+ }
+
+ if (_attributeContext.IncludeCompilationAssembly || _attributeContext.IncludeAll)
+ {
+ _buffer.AppendFormat(Templates.ClassDocCommentsSectionBegin, indentString, "Compilation Assembly");
+ AppendIndentedLines(indent, _compilationDescription.AssemblyDocComments);
+ _buffer.AppendFormat(Templates.ClassDocCommentsSectionEnd, indentString);
+ }
+
+ if (_attributeContext.IncludeCompilationReferences || _attributeContext.IncludeAll)
+ {
+ _buffer.AppendFormat(Templates.ClassDocCommentsSectionBegin, indentString, "Compilation References");
+ AppendIndentedLines(indent, _compilationDescription.ReferencesDocComments);
+ _buffer.AppendFormat(Templates.ClassDocCommentsSectionEnd, indentString);
+ }
+
+ if (_attributeContext.IncludeParseOptions || _attributeContext.IncludeAll)
+ {
+ _buffer.AppendFormat(Templates.ClassDocCommentsSectionBegin, indentString, "Parse Options");
+ AppendIndentedLines(indent, _parseOptionsDescription.DocComments);
+ _buffer.AppendFormat(Templates.ClassDocCommentsSectionEnd, indentString);
+ }
+
+ if (_attributeContext.IncludeAdditionalTexts || _attributeContext.IncludeAll)
+ {
+ _buffer.AppendFormat(Templates.ClassDocCommentsSectionBegin, indentString, "Additional Texts");
+ // todo: indent
+ _buffer.AddComment("Number of Additional Texts", _additionalTextDescriptions.Length);
+ foreach (var additionalTextDescription in _additionalTextDescriptions)
+ {
+ AppendIndentedLines(indent, additionalTextDescription.DocComments);
+ if (_attributeContext.IncludeAdditionalTextsOptions)
+ {
+ AppendIndentedLines(indent, additionalTextDescription.OptionsComments);
+ }
+ }
+ _buffer.AppendFormat(Templates.ClassDocCommentsSectionEnd, indentString);
+ }
+
+ if (_attributeContext.IncludeMetadataReferences || _attributeContext.IncludeAll)
+ {
+ _buffer.AppendFormat(Templates.ClassDocCommentsSectionBegin, indentString, "Metadata References");
+ // todo: indent
+ _buffer.AddComment("Number of Metadata References", _metadataReferenceDescriptions.Length);
+ foreach (var metadataReferenceDescription in _metadataReferenceDescriptions)
+ {
+ AppendIndentedLines(indent, metadataReferenceDescription.DocComments);
+ }
+ _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++)
+ {
+ AppendEndClass(indent);
+ indent -= 4;
+ }
+ }
+ }
+
+ private void AppendDiagnosticLogs()
+ {
+ _buffer.AppendLine();
+ _buffer.AppendLine("/* Diagnostic Log");
+ LightweightTrace.Add(TrackingNames.DiagnosticLog_Written);
+ LightweightTrace.GetTrace(_buffer, TrackingNames.TracingNames);
+ _buffer.AppendLine("*/");
+ }
+
+ private static readonly Dictionary IndentationCache = new();
+ private string StringForIndent(int indent)
+ {
+ 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;
+ }
+ }
+}
diff --git a/SourceGeneratorContext/CompilationDescription.cs b/SourceGeneratorContext/CompilationDescription.cs
new file mode 100644
index 0000000..66b21c0
--- /dev/null
+++ b/SourceGeneratorContext/CompilationDescription.cs
@@ -0,0 +1,137 @@
+using System.Text;
+using Microsoft.CodeAnalysis;
+
+namespace Datacute.SourceGeneratorContext;
+
+public readonly struct CompilationDescription
+{
+ public readonly string DocComments;
+ public readonly string OptionsDocComments;
+ public readonly string AssemblyDocComments;
+ public readonly string ReferencesDocComments;
+
+ public CompilationDescription(Compilation compilation)
+ {
+ var sb = new StringBuilder();
+ sb.AddComment("AssemblyName", compilation.AssemblyName);
+ sb.AddComment("Language", compilation.Language);
+ sb.AddComment("IsCaseSensitive", compilation.IsCaseSensitive);
+ sb.AddComment("DynamicType", compilation.DynamicType);
+ sb.AddComment("GlobalNamespace", compilation.GlobalNamespace);
+ sb.AddComment("ObjectType", compilation.ObjectType);
+ sb.AddComment("ScriptClass", compilation.ScriptClass);
+ sb.AddComment("SourceModule", compilation.SourceModule);
+ sb.AddComment("ScriptCompilationInfo", compilation.ScriptCompilationInfo);
+ sb.AddComment("SyntaxTrees Count", compilation.SyntaxTrees.Count());
+
+ DocComments = sb.ToString();
+
+ sb.Clear();
+ var options = compilation.Options;
+ sb.AddComment("Language", options.Language);
+ sb.AddComment("OutputKind", options.OutputKind);
+ sb.AddComment("ModuleName", options.ModuleName);
+ sb.AddComment("MainTypeName", options.MainTypeName);
+ sb.AddComment("ScriptClassName", options.ScriptClassName);
+ sb.AddComment("CryptoKeyContainer", options.CryptoKeyContainer);
+ sb.AddComment("CryptoKeyFile", options.CryptoKeyFile);
+ sb.AddComment("CryptoPublicKey Length", options.CryptoPublicKey.Length);
+ sb.AddComment("DelaySign", options.DelaySign);
+ sb.AddComment("CheckOverflow", options.CheckOverflow);
+ sb.AddComment("Platform", options.Platform);
+ sb.AddComment("GeneralDiagnosticOption", options.GeneralDiagnosticOption);
+ sb.AddComment("WarningLevel", options.WarningLevel);
+ sb.AddComment("ReportSuppressedDiagnostics", options.ReportSuppressedDiagnostics);
+ sb.AddComment("OptimizationLevel", options.OptimizationLevel);
+ sb.AddComment("ConcurrentBuild", options.ConcurrentBuild);
+ sb.AddComment("Deterministic", options.Deterministic);
+ //sb.AddComment("XmlReferenceResolver", options.XmlReferenceResolver);
+ //sb.AddComment("SourceReferenceResolver", options.SourceReferenceResolver);
+ //sb.AddComment("SyntaxTreeOptionsProvider", options.SyntaxTreeOptionsProvider);
+ //sb.AddComment("MetadataReferenceResolver", options.MetadataReferenceResolver);
+ //sb.AddComment("StrongNameProvider", options.StrongNameProvider);
+ //sb.AddComment("AssemblyIdentityComparer", options.AssemblyIdentityComparer);
+ sb.AddComment("MetadataImportOptions", options.MetadataImportOptions);
+ sb.AddComment("PublicSign", options.PublicSign);
+ sb.AddComment("NullableContextOptions", options.NullableContextOptions);
+ sb.AddComment("SpecificDiagnosticOptions Count", options.SpecificDiagnosticOptions.Count);
+ foreach (var kvp in options.SpecificDiagnosticOptions)
+ {
+ sb.AddComment($"SpecificDiagnosticOptions '{kvp.Key}'", kvp.Value);
+ }
+ OptionsDocComments = sb.ToString();
+
+ sb.Clear();
+ var assembly = compilation.Assembly;
+ sb.AddComment("Identity Name", assembly.Identity.Name);
+ sb.AddComment("Identity Version", assembly.Identity.Version);
+ sb.AddComment("Identity CultureName", assembly.Identity.CultureName);
+ sb.AddComment("Identity Flags", assembly.Identity.Flags);
+ sb.AddComment("Identity ContentType", assembly.Identity.ContentType);
+ sb.AddComment("Identity HasPublicKey", assembly.Identity.HasPublicKey);
+ sb.AddComment("Identity PublicKey Length", assembly.Identity.PublicKey.Length);
+ //sb.AddComment("Identity PublicKeyToken", string.Join(",", assembly.Identity.PublicKeyToken));
+ sb.AddComment("Identity IsStrongName", assembly.Identity.IsStrongName);
+ sb.AddComment("Identity IsRetargetable", assembly.Identity.IsRetargetable);
+ sb.AddComment("IsInteractive", assembly.IsInteractive);
+ //sb.AddComment("GlobalNamespace", assembly.GlobalNamespace);
+ sb.AddComment("Modules Count", assembly.Modules.Count());
+ sb.AddComment("TypeNames Count", assembly.TypeNames.Count);
+ sb.AddComment("NamespaceNames Count", assembly.NamespaceNames.Count);
+ sb.AddComment("MightContainExtensionMethods", assembly.MightContainExtensionMethods);
+ //sb.AddComment("Kind", assembly.Kind);
+ sb.AddComment("Language", assembly.Language);
+ sb.AddComment("Name", assembly.Name);
+ sb.AddComment("MetadataName", assembly.MetadataName);
+ sb.AddComment("MetadataToken", assembly.MetadataToken);
+ //sb.AddComment("IsDefinition", assembly.IsDefinition);
+ //sb.AddComment("IsStatic", assembly.IsStatic);
+ //sb.AddComment("IsVirtual", assembly.IsVirtual);
+ //sb.AddComment("IsOverride", assembly.IsOverride);
+ //sb.AddComment("IsAbstract", assembly.IsAbstract);
+ //sb.AddComment("IsSealed", assembly.IsSealed);
+ //sb.AddComment("IsExtern", assembly.IsExtern);
+ //sb.AddComment("IsImplicitlyDeclared", assembly.IsImplicitlyDeclared);
+ //sb.AddComment("CanBeReferencedByName", assembly.CanBeReferencedByName);
+ sb.AddComment("Locations Length", assembly.Locations.Length);
+ //sb.AddComment("DeclaringSyntaxReferences Length", assembly.DeclaringSyntaxReferences.Length);
+ sb.AddComment("DeclaredAccessibility", assembly.DeclaredAccessibility);
+ //sb.AddComment("OriginalDefinition", assembly.OriginalDefinition);
+ AssemblyDocComments = sb.ToString();
+
+ sb.Clear();
+ sb.AddComment("References Count", compilation.References.Count());
+ sb.AddComment("DirectiveReferences Count", compilation.DirectiveReferences.Count());
+ sb.AddComment("ExternalReferences Count", compilation.ExternalReferences.Count());
+ sb.AddComment("ReferencedAssemblyNames Count", compilation.ReferencedAssemblyNames.Count());
+ //foreach (var reference in compilation.References)
+ // {
+ // sb.AppendFormat(Templates.OptionsLine, "Display", reference.Display);
+ // sb.AppendFormat(Templates.OptionsLine, "Properties.EmbedInteropTypes", reference.Properties.EmbedInteropTypes);
+ // sb.AppendFormat(Templates.OptionsLine, "Properties.Kind", reference.Properties.Kind);
+ // foreach (var alias in reference.Properties.Aliases)
+ // {
+ // sb.AppendFormat(Templates.OptionsLine, "Alias", alias);
+ // }
+ // }
+ ReferencesDocComments = sb.ToString();
+
+ // sb.Clear();
+ // foreach (var syntaxTree in compilation.SyntaxTrees)
+ // {
+ // sb.AppendFormat(Templates.OptionsLine, "FilePath", syntaxTree.FilePath);
+ // sb.AppendFormat(Templates.OptionsLine, "Length", syntaxTree.Length);
+ // sb.AppendFormat(Templates.OptionsLine, "Encoding", syntaxTree.Encoding);
+ // sb.AppendFormat(Templates.OptionsLine, "HasCompilationUnitRoot", syntaxTree.HasCompilationUnitRoot);
+ // }
+ // SyntaxTreesDocComments = sb.ToString();
+ }
+
+ public static CompilationDescription Select(Compilation compilation, CancellationToken token)
+ {
+ LightweightTrace.Add(TrackingNames.CompilationDescription_Select);
+
+ token.ThrowIfCancellationRequested();
+ return new CompilationDescription(compilation);
+ }
+}
\ No newline at end of file
diff --git a/SourceGeneratorContext/DocCommentDescriptionExtensions.cs b/SourceGeneratorContext/DocCommentDescriptionExtensions.cs
new file mode 100644
index 0000000..001b5b3
--- /dev/null
+++ b/SourceGeneratorContext/DocCommentDescriptionExtensions.cs
@@ -0,0 +1,13 @@
+using System.Text;
+
+namespace Datacute.SourceGeneratorContext;
+
+public static class DocCommentDescriptionExtensions
+{
+ public static void AddComment(this StringBuilder sb, string propertyName, object? value)
+ {
+ if (value == null) return;
+ var valueString = value.ToString();
+ sb.AppendFormat(Templates.OptionsLine, propertyName, Templates.EscapeStringForDocComments(valueString));
+ }
+}
\ No newline at end of file
diff --git a/SourceGeneratorContext/EquatableImmutableArray.cs b/SourceGeneratorContext/EquatableImmutableArray.cs
new file mode 100644
index 0000000..4b01eb9
--- /dev/null
+++ b/SourceGeneratorContext/EquatableImmutableArray.cs
@@ -0,0 +1,211 @@
+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
new file mode 100644
index 0000000..98cc02d
--- /dev/null
+++ b/SourceGeneratorContext/Generator.cs
@@ -0,0 +1,69 @@
+using System.Collections.Immutable;
+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);
+
+ var attributeContexts =
+ context.SyntaxProvider.ForAttributeWithMetadataName(
+ fullyQualifiedMetadataName: Templates.AttributeFullyQualified,
+ predicate: AttributeContext.Predicate,
+ transform: AttributeContext.Transform)
+ .WithTrackingName(TrackingNames.InitialExtraction);
+
+ var globalOptionsDescriptionValueProvider = context.AnalyzerConfigOptionsProvider.Select(AnalyzerConfigOptionsDescription.Select);
+ var compilationDescriptionValueProvider = context.CompilationProvider.Select(CompilationDescription.Select);
+ var parseOptionsDescriptionValueProvider = context.ParseOptionsProvider.Select(ParseOptionsDescription.Select);
+
+ // 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 source = attributeContexts
+ .Combine(globalOptionsDescriptionValueProvider)
+ .Select((x, _) => new AttributeAndOptions(x.Left, x.Right))
+ .Combine(compilationDescriptionValueProvider)
+ .Select((x, _) => new AttributeOptionsAndCompilation(x.Left, x.Right))
+ .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);
+
+ context.RegisterSourceOutput(source, Action);
+ }
+
+ private void Action(SourceProductionContext sourceProductionContext, GeneratorSourceData source)
+ {
+ LightweightTrace.Add(TrackingNames.Generator_Action);
+
+ var attributeContext = source.Core.AttributeOptionsCompilationAndParseOptions.AttributeOptionsAndCompilation.AttributeAndOptions.AttributeContext;
+
+ var cancellationToken = sourceProductionContext.CancellationToken;
+ cancellationToken.ThrowIfCancellationRequested();
+
+ var codeGenerator = new CodeGenerator(source, cancellationToken);
+
+ var hintName = attributeContext.DisplayString.GetHintName();
+ var generatedSource = codeGenerator.GenerateSource();
+ sourceProductionContext.AddSource(hintName, generatedSource);
+ }
+
+
+}
\ No newline at end of file
diff --git a/SourceGeneratorContext/LightweightTracing/LightweightTrace.cs b/SourceGeneratorContext/LightweightTracing/LightweightTrace.cs
new file mode 100644
index 0000000..12eb47b
--- /dev/null
+++ b/SourceGeneratorContext/LightweightTracing/LightweightTrace.cs
@@ -0,0 +1,45 @@
+/*
+ * 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
new file mode 100644
index 0000000..a20a932
--- /dev/null
+++ b/SourceGeneratorContext/MetadataReferenceDescription.cs
@@ -0,0 +1,27 @@
+using System.Text;
+using Microsoft.CodeAnalysis;
+
+namespace Datacute.SourceGeneratorContext;
+
+public readonly struct MetadataReferenceDescription
+{
+ public readonly string DocComments;
+ public MetadataReferenceDescription(MetadataReference metadataReference)
+ {
+ var sb = new StringBuilder();
+ sb.AddComment("Display", metadataReference.Display);
+ //sb.AddComment("Properties Kind", metadataReference.Properties.Kind);
+ //sb.AddComment("Properties EmbedInteropTypes", metadataReference.Properties.EmbedInteropTypes);
+ //sb.AddComment("Properties Aliases Length", metadataReference.Properties.Aliases.Length);
+
+ DocComments = sb.ToString();
+ }
+
+ public static MetadataReferenceDescription Select(MetadataReference metadataReference, CancellationToken token)
+ {
+ //LightweightTrace.Add(TrackingNames.MetadataReferenceDescription_Select);
+
+ token.ThrowIfCancellationRequested();
+ return new MetadataReferenceDescription(metadataReference);
+ }
+}
\ No newline at end of file
diff --git a/SourceGeneratorContext/NameGenerators.cs b/SourceGeneratorContext/NameGenerators.cs
new file mode 100644
index 0000000..fbd7ddd
--- /dev/null
+++ b/SourceGeneratorContext/NameGenerators.cs
@@ -0,0 +1,7 @@
+namespace Datacute.SourceGeneratorContext;
+
+internal static class NameGenerators
+{
+ public static string GetHintName(this string typeDisplayName) =>
+ $"{typeDisplayName.Replace('<', '_').Replace('>', '_')}.g.cs";
+}
\ No newline at end of file
diff --git a/SourceGeneratorContext/ParseOptionsDescription.cs b/SourceGeneratorContext/ParseOptionsDescription.cs
new file mode 100644
index 0000000..8f90729
--- /dev/null
+++ b/SourceGeneratorContext/ParseOptionsDescription.cs
@@ -0,0 +1,40 @@
+using System.Text;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+
+namespace Datacute.SourceGeneratorContext;
+
+public readonly struct ParseOptionsDescription
+{
+ public readonly string DocComments;
+
+ public ParseOptionsDescription(ParseOptions parseOptions)
+ {
+ var sb = new StringBuilder();
+ sb.AddComment("Kind", parseOptions.Kind);
+ sb.AddComment("SpecifiedKind", parseOptions.SpecifiedKind);
+ sb.AddComment("DocumentationMode", parseOptions.DocumentationMode);
+ sb.AddComment("Language", parseOptions.Language);
+ if (parseOptions is CSharpParseOptions cSharpParseOptions)
+ {
+ sb.AddComment("CSharp SpecifiedLanguageVersion", cSharpParseOptions.SpecifiedLanguageVersion);
+ sb.AddComment("CSharp LanguageVersion", cSharpParseOptions.LanguageVersion);
+ }
+ sb.AddComment("Features Count", parseOptions.Features.Count);
+ sb.AddComment("PreprocessorSymbolNames Count", parseOptions.PreprocessorSymbolNames.Count());
+ foreach (var preprocessorSymbolName in parseOptions.PreprocessorSymbolNames)
+ {
+ sb.AddComment($"PreprocessorSymbolName", preprocessorSymbolName);
+ }
+
+ DocComments = sb.ToString();
+ }
+
+ public static ParseOptionsDescription Select(ParseOptions parseOptions, CancellationToken token)
+ {
+ LightweightTrace.Add(TrackingNames.ParseOptionsDescription_Select);
+
+ token.ThrowIfCancellationRequested();
+ return new ParseOptionsDescription(parseOptions);
+ }
+}
\ No newline at end of file
diff --git a/SourceGeneratorContext/SourceGeneratorContext.csproj b/SourceGeneratorContext/SourceGeneratorContext.csproj
new file mode 100644
index 0000000..c3b5395
--- /dev/null
+++ b/SourceGeneratorContext/SourceGeneratorContext.csproj
@@ -0,0 +1,83 @@
+
+
+
+ netstandard2.0
+ 10.0
+ enable
+ enable
+ true
+ Datacute.SourceGeneratorContext
+ Datacute.SourceGeneratorContext
+
+
+
+ true
+
+
+
+ false
+
+ true
+ true
+ true
+
+
+
+ Datacute.SourceGeneratorContext
+ Datacute Source Generator Context
+ A source generator to help creators of source generators, by creating doc-comments showing the available generation context.
+ SourceGenerator
+ README.md
+ false
+ MIT
+
+
+
+
+
+
+ $(NoWarn);NU5128
+
+
+
+
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SourceGeneratorContext/Templates.cs b/SourceGeneratorContext/Templates.cs
new file mode 100644
index 0000000..3b58285
--- /dev/null
+++ b/SourceGeneratorContext/Templates.cs
@@ -0,0 +1,46 @@
+namespace Datacute.SourceGeneratorContext;
+
+internal static 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 ClassDocCommentsBegin = /* language=c# */
+ @"{0}///
+{0}/// When this item is examined by a source generator...
+";
+
+ public const string ClassDocCommentsSectionBegin = /* language=c# */
+ @"{0}/// {1} contains:
+{0}///
+{0}/// KeyValue
+";
+
+ public const string OptionsLine = /* language=c# */
+ @"/// - {0}{1}
+";
+
+ public const string ClassDocCommentsSectionEnd = /* language=c# */
+ @"{0}///
+";
+
+ public const string ClassDocCommentsEnd = /* language=c# */
+ @"{0}///
+";
+
+ public static string EscapeStringForDocComments(string input) =>
+ input.Replace("&", "&")
+ .Replace("<", "<")
+ .Replace(">", ">")
+ .Replace("\"", """)
+ .Replace("'", "'");
+}
\ No newline at end of file
diff --git a/SourceGeneratorContext/TrackingNames.cs b/SourceGeneratorContext/TrackingNames.cs
new file mode 100644
index 0000000..0512364
--- /dev/null
+++ b/SourceGeneratorContext/TrackingNames.cs
@@ -0,0 +1,30 @@
+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/Example Text File.txt b/SourceGeneratorContextExample/Example Text File.txt
new file mode 100644
index 0000000..e727da2
--- /dev/null
+++ b/SourceGeneratorContextExample/Example Text File.txt
@@ -0,0 +1 @@
+This file include a UTF-8 BOM
\ No newline at end of file
diff --git a/SourceGeneratorContextExample/Program.cs b/SourceGeneratorContextExample/Program.cs
new file mode 100644
index 0000000..4c2cd52
--- /dev/null
+++ b/SourceGeneratorContextExample/Program.cs
@@ -0,0 +1,190 @@
+using Datacute.SourceGeneratorContext;
+
+Console.WriteLine("View the doc-comments for the items with the [SourceGeneratorContext] attribute");
+
+// In order to demonstrate various scenarios, we're creating a lot of unused code, so...
+// ReSharper disable UnusedType.Global
+// ReSharper disable UnusedType.Local
+// ReSharper disable RedundantExtendsListEntry
+// ReSharper disable PartialTypeWithSinglePart
+// ReSharper disable NotAccessedPositionalProperty.Global
+// ReSharper disable UnusedTypeParameter
+#pragma warning disable CA1050 // Declare types in namespaces
+
+#region Simple example
+
+// class needs to be partial to allow for the source generator to extend it (adding doc comments in this case)
+[SourceGeneratorContext]
+public static partial class ClassInGlobalNamespace;
+
+#endregion
+
+#region All 'include' options
+
+[SourceGeneratorContext(IncludeAttributeContextTargetSymbol = true)]
+public partial class ViewClassDocsToSeeGeneratorAttributeSyntaxContextTargetSymbol;
+
+[SourceGeneratorContext(IncludeAttributeContextTypeSymbol = true)]
+public partial class ViewClassDocsToSeeGeneratorAttributeSyntaxContextTypeSymbol;
+
+[SourceGeneratorContext(IncludeAttributeContextNamedTypeSymbol = true)]
+public partial class ViewClassDocsToSeeGeneratorAttributeSyntaxContextNamedTypeSymbol;
+
+[SourceGeneratorContext(IncludeAttributeContextTargetNode = true)]
+public partial class ViewClassDocsToSeeGeneratorAttributeSyntaxContextTargetNode;
+
+[SourceGeneratorContext(IncludeAttributeContextAttributes = true)]
+public partial class ViewClassDocsToSeeGeneratorAttributeSyntaxContextAttributes;
+
+[SourceGeneratorContext(IncludeAttributeContextAllAttributes = true)]
+public partial class ViewClassDocsToSeeGeneratorAttributeSyntaxContextAllAttributes;
+
+[SourceGeneratorContext(IncludeGlobalOptions = true)]
+public partial class ViewClassDocsToSeeAnalyzerConfigOptionsProviderGlobalOptions;
+
+[SourceGeneratorContext(IncludeCompilation = true)]
+public partial class ViewClassDocsToSeeCompilation;
+
+[SourceGeneratorContext(IncludeCompilationOptions = true)]
+public partial class ViewClassDocsToSeeCompilationOptions;
+
+[SourceGeneratorContext(IncludeCompilationAssembly = true)]
+public partial class ViewClassDocsToSeeCompilationAssembly;
+
+[SourceGeneratorContext(IncludeCompilationReferences = true)]
+public partial class ViewClassDocsToSeeCompilationReferences;
+
+[SourceGeneratorContext(IncludeParseOptions = true)]
+public partial class ViewClassDocsToSeeParseOptions;
+
+[SourceGeneratorContext(IncludeAdditionalTexts = true)]
+public partial class ViewClassDocsToSeeAdditionalTexts;
+
+[SourceGeneratorContext(IncludeMetadataReferences = true)]
+public partial class ViewClassDocsToSeeMetadataReferences;
+
+// IncludeAdditionalTextsOptions seems really repetitive, so is not included in "includeAll"
+[SourceGeneratorContext(IncludeAdditionalTexts = true, IncludeAdditionalTextsOptions = true)]
+public partial class ViewClassDocsToSeeAdditionalTextsWithOptions;
+
+[SourceGeneratorContext(IncludeAll = true)]
+public partial class ViewClassDocsToSeeAllAvailableDetails;
+
+#endregion
+
+#region Inner classes
+
+[SourceGeneratorContext]
+public partial class ParentClassInGlobalNamespace: IFormattable
+{
+ public string ToString(string? format, IFormatProvider? formatProvider) => string.Empty;
+
+ public override string ToString() => string.Empty;
+
+ [SourceGeneratorContext]
+ public static partial class InnerStaticClassWithinParentClass
+ {
+ [SourceGeneratorContext]
+ private static partial class InnerClassWithinMultipleParents;
+
+ [SourceGeneratorContext]
+ internal partial class InnerNonStaticClassWithinMultipleParents;
+ }
+
+ // parent class needs to be partial to allow the child classes to be partial
+ internal partial class InnerNonStaticClassWithinParentClass
+ {
+ [SourceGeneratorContext]
+ private static partial class InnerClassWithinMultipleParents;
+
+ [SourceGeneratorContext]
+ private partial class InnerNonStaticClassWithinMultipleParents;
+ }
+}
+
+#endregion
+
+#region Inheritance scenarios
+
+// Visual Studio only shows these below comments, not the generated comments.
+// Jetbrains Rider shows both the generated comments then the below comments.
+///
+/// This is an example of doc comments on both the user written class, and the generated class.
+///
+/// Example remarks
+[SourceGeneratorContext]
+public partial class ClassWithOwnDocComments;
+
+[SourceGeneratorContext]
+public partial class SubclassExample : ParentClassInGlobalNamespace;
+
+// Uncommenting this attribute on a repeated class will break the source generation, due to a duplicate hint
+// The source generator is supposed to be able to handle repeated hint values,
+// so perhaps this is IDE and/or build tool dependent
+//[SourceGeneratorContext]
+public partial class SubclassExample: ParentClassInGlobalNamespace;
+
+// even though the attribute is inherited,
+// the generator will still only generate the doc-comments for the class with the attribute
+// this is due to the implementation of the context.SyntaxProvider.ForAttributeWithMetadataName method
+public partial class OverriddenClassWithoutAttribute: ParentClassInGlobalNamespace;
+
+// This inheritdoc DOES NOT show the doc-comments from the generated ClassWithOwnDocComments,
+// but only the doc-comments from ClassWithOwnDocComments above
+///
+public partial class OverriddenClassDemonstratingInheritDocWithoutGeneratedComments: ClassWithOwnDocComments;
+
+// This inheritdoc DOES show the doc-comments from ParentClassInGlobalNamespace,
+// because ParentClassInGlobalNamespace does not have its own docs above.
+// Note though that the generated comments are for the context of the attribute on the ParentClassInGlobalNamespace class
+// not the context for this class
+///
+public partial class OverriddenClassDemonstratingInheritDocWithGeneratedComments : ParentClassInGlobalNamespace;
+
+#endregion
+
+#region Various example scenarios
+
+namespace ExampleNamespace
+{
+ [SourceGeneratorContext]
+ internal static partial class ClassInNamespace;
+
+ [SourceGeneratorContext(IncludeAttributeContextTypeSymbol = true)]
+ internal partial record RecordClass(string Example)
+ {
+ [SourceGeneratorContext]
+ internal partial class InnerClassWithinRecord;
+ }
+
+ [SourceGeneratorContext]
+ public static partial class ParentClass
+ {
+ [SourceGeneratorContext]
+ internal static partial class InnerClassWithinParentClass
+ {
+ [Obsolete("Included in `TargetSymbol.AllAttributes()`")]
+ [SourceGeneratorContext("Included in `Attributes` and `TargetSymbol.AllAttributes()`")]
+ [SourceGeneratorContext]
+ private static partial class InnerClassWithinMultipleParents;
+ }
+
+ [SourceGeneratorContext(exampleOptionalParameter: "Example of a supplied parameter")]
+ internal partial class InnerGenericClass
+ {
+ [SourceGeneratorContext(ExampleNamedParameter = "Example of a named parameter")]
+ internal partial class InnerClassOfGenericClass;
+ }
+ }
+}
+
+[SourceGeneratorContext(IncludeAttributeContextTypeSymbol = true)]
+public partial struct ExampleStructure;
+
+[SourceGeneratorContext(IncludeAttributeContextTypeSymbol = true)]
+public partial interface IExampleInterface;
+
+
+#endregion
+
+#pragma warning restore CA1050 // Declare types in namespaces
diff --git a/SourceGeneratorContextExample/README.md b/SourceGeneratorContextExample/README.md
new file mode 100644
index 0000000..e1d5489
--- /dev/null
+++ b/SourceGeneratorContextExample/README.md
@@ -0,0 +1,4 @@
+# Example project
+
+This project demonstrates how the `[SourceGeneratorContext]` can be added to partial classes,
+to produce doc comments listing a huge amount of information about the class and compilation.
diff --git a/SourceGeneratorContextExample/SecondClass.cs b/SourceGeneratorContextExample/SecondClass.cs
new file mode 100644
index 0000000..9336a0f
--- /dev/null
+++ b/SourceGeneratorContextExample/SecondClass.cs
@@ -0,0 +1,9 @@
+using Datacute.SourceGeneratorContext;
+
+namespace ExampleNamespace;
+
+[SourceGeneratorContext]
+public partial class SecondClass
+{
+
+}
\ No newline at end of file
diff --git a/SourceGeneratorContextExample/SourceGeneratorContextExample.csproj b/SourceGeneratorContextExample/SourceGeneratorContextExample.csproj
new file mode 100644
index 0000000..5de5785
--- /dev/null
+++ b/SourceGeneratorContextExample/SourceGeneratorContextExample.csproj
@@ -0,0 +1,41 @@
+
+
+
+ enable
+ enable
+ Exe
+ Datacute.SourceGeneratorContextExample
+ Datacute.SourceGeneratorContextExample
+ en-NZ
+ preview
+ net9.0
+
+
+
+ DemoSourceGeneratorPropertyValue
+
+
+
+
+
+
+
+
+
+ PreserveNewest
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SourceGeneratorContextExample/SourceGeneratorContextExample.sln b/SourceGeneratorContextExample/SourceGeneratorContextExample.sln
new file mode 100644
index 0000000..c5fea54
--- /dev/null
+++ b/SourceGeneratorContextExample/SourceGeneratorContextExample.sln
@@ -0,0 +1,24 @@
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.5.2.0
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SourceGeneratorContextExample", "SourceGeneratorContextExample.csproj", "{1464453B-83DE-1F1D-58FB-2B9196BCE4C9}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {1464453B-83DE-1F1D-58FB-2B9196BCE4C9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {1464453B-83DE-1F1D-58FB-2B9196BCE4C9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1464453B-83DE-1F1D-58FB-2B9196BCE4C9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {1464453B-83DE-1F1D-58FB-2B9196BCE4C9}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {F587DC78-4E3B-459B-A138-C16B2B7A314D}
+ EndGlobalSection
+EndGlobal
diff --git a/global.json b/global.json
new file mode 100644
index 0000000..f4fd385
--- /dev/null
+++ b/global.json
@@ -0,0 +1,7 @@
+{
+ "sdk": {
+ "version": "9.0.0",
+ "rollForward": "latestMajor",
+ "allowPrerelease": true
+ }
+}
\ No newline at end of file
diff --git a/version.props b/version.props
new file mode 100644
index 0000000..ce771c6
--- /dev/null
+++ b/version.props
@@ -0,0 +1,6 @@
+
+
+ 0.0.1
+ alpha
+
+
\ No newline at end of file