Skip to content

Commit 540806f

Browse files
committed
Merge branch 'release/0.1.0-alpha'
2 parents cc5225c + 8dc4d65 commit 540806f

35 files changed

+1959
-332
lines changed

CHANGELOG.md

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## [0.1.0-alpha] - 2025-07-26
11+
12+
### Changed
13+
14+
- Converted to a source generator
15+
- Renamed Trace to be an overload of WithTrackingName
16+
- Renumbered the GeneratorStage Enum values
17+
18+
### Added
19+
20+
- GeneratorStage Enum and Descriptions for use with Lightweight Tracing
21+
- CancellationToken.ThrowIfCancellationRequested overload
22+
- Events (and counters) now support id & instance value
23+
- AttributeContextAndData and TypeContext to simplify the collection of pertinent attribute context data
24+
- Auto-Indenting StringBuilder wrappers to aid with indentation in generated source code
25+
1026
## [0.0.3-alpha] - 2025-07-19
1127

1228
### Fixed
@@ -28,7 +44,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2844

2945
---
3046

31-
[Unreleased]: https://github.com/datacute/IncrementalGeneratorExtensions/compare/release/0.0.3-alpha...develop
47+
[Unreleased]: https://github.com/datacute/IncrementalGeneratorExtensions/compare/0.1.0-alpha...develop
48+
[0.1.0-alpha]: https://github.com/datacute/IncrementalGeneratorExtensions/releases/tag/0.1.0-alpha
3249
[0.0.3-alpha]: https://github.com/datacute/IncrementalGeneratorExtensions/releases/tag/0.0.3-alpha
3350
[0.0.2-alpha]: https://github.com/datacute/IncrementalGeneratorExtensions/releases/tag/0.0.2-alpha
3451
[0.0.1-alpha]: https://github.com/datacute/IncrementalGeneratorExtensions/releases/tag/0.0.1-alpha

Directory.Build.props

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,8 @@
66
<Authors>Stephen Denne</Authors>
77
<Copyright>Copyright © Stephen Denne 2025</Copyright>
88
<Company>Datacute</Company>
9-
<PackageProjectUrl>https://github.com/datacute/IncrementalGeneratorExtensions</PackageProjectUrl>
10-
<PackageReleaseNotes>See full release notes and changelog: $(PackageProjectUrl)/blob/main/CHANGELOG.md</PackageReleaseNotes>
9+
<RepositoryUrl>https://github.com/datacute/IncrementalGeneratorExtensions</RepositoryUrl>
10+
<RepositoryType>git</RepositoryType>
1111
<CheckEolTargetFramework>false</CheckEolTargetFramework>
1212
</PropertyGroup>
13-
14-
<PropertyGroup>
15-
<NoPackageAnalysis>true</NoPackageAnalysis>
16-
<PackageOutputPath>$(MSBuildThisFileDirectory)artifacts</PackageOutputPath>
17-
</PropertyGroup>
1813
</Project>
Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
// <auto-generated>
2+
// This file is part of the Datacute.IncrementalGeneratorExtensions package.
3+
// It is included as a source file and should not be modified.
4+
// </auto-generated>
5+
6+
#if !DATACUTE_EXCLUDE_ATTRIBUTECONTEXTANDDATA && !DATACUTE_EXCLUDE_TYPECONTEXT
7+
using System;
8+
using System.Collections.Immutable;
9+
using System.Threading;
10+
using Microsoft.CodeAnalysis;
11+
using Microsoft.CodeAnalysis.CSharp.Syntax;
12+
13+
namespace Datacute.IncrementalGeneratorExtensions
14+
{
15+
/// <summary>
16+
/// Represents the context and data of an attribute in a source generator.
17+
/// </summary>
18+
/// <typeparam name="T">The type of the attribute data, which must implement <see cref="IEquatable{T}"/>.</typeparam>
19+
public readonly struct AttributeContextAndData<T> : IEquatable<AttributeContextAndData<T>>
20+
where T : IEquatable<T>
21+
{
22+
/// <summary>
23+
/// Indicates whether the containing namespace is the global namespace.
24+
/// </summary>
25+
public readonly bool ContainingNamespaceIsGlobalNamespace;
26+
/// <summary>
27+
/// The display string of the containing namespace.
28+
/// </summary>
29+
public readonly string ContainingNamespaceDisplayString;
30+
/// <summary>
31+
/// The context of the type to which the attribute is applied.
32+
/// </summary>
33+
public readonly TypeContext Context;
34+
/// <summary>
35+
/// A collection of contexts for the containing types of the attribute's target symbol.
36+
/// </summary>
37+
public readonly EquatableImmutableArray<TypeContext> ContainingTypes;
38+
39+
/// <summary>
40+
/// The display string of the attribute's target symbol, formatted as Namespace.ClassName.
41+
/// </summary>
42+
/// <remarks>
43+
/// This string is useful for generating a hint name for the attribute, as it provides a
44+
/// fully qualified name that includes the namespace and class name.
45+
/// </remarks>
46+
public readonly string DisplayString;
47+
48+
/// <summary>
49+
/// Indicates whether the attribute's target symbol has any containing types.
50+
/// </summary>
51+
public bool HasContainingTypes => ContainingTypes.Length > 0;
52+
53+
/// <summary>
54+
/// The data associated with the attribute, which is typically collected from the attribute's syntax context.
55+
/// </summary>
56+
public readonly T AttributeData;
57+
58+
/// <summary>
59+
/// Initializes a new instance of the <see cref="AttributeContextAndData{T}"/> struct.
60+
/// </summary>
61+
/// <param name="generatorAttributeSyntaxContext">The context of the attribute syntax, which includes information about the target symbol and the attribute itself.</param>
62+
/// <param name="attributeData">The data associated with the attribute, typically collected from the attribute's syntax context.</param>
63+
public AttributeContextAndData(in GeneratorAttributeSyntaxContext generatorAttributeSyntaxContext, in T attributeData)
64+
{
65+
AttributeData = attributeData;
66+
67+
// No diagnostic tracing here - this triggers for each matching attribute, every time you type.
68+
// the time taken within this method is about 1% of the time the source generator takes
69+
// to process the attribute.
70+
71+
var attributeTargetSymbol = (ITypeSymbol)generatorAttributeSyntaxContext.TargetSymbol;
72+
73+
ContainingNamespaceIsGlobalNamespace = attributeTargetSymbol.ContainingNamespace.IsGlobalNamespace;
74+
ContainingNamespaceDisplayString = attributeTargetSymbol.ContainingNamespace.ToDisplayString();
75+
76+
EquatableImmutableArray<string> typeParameterNames;
77+
if (generatorAttributeSyntaxContext.TargetSymbol is INamedTypeSymbol namedTypeTargetSymbol)
78+
{
79+
ImmutableArray<ITypeParameterSymbol> typeParameters = namedTypeTargetSymbol.TypeParameters;
80+
typeParameterNames = typeParameters.Length > 0
81+
? typeParameters.ToEquatableImmutableArray(tp => tp.Name)
82+
: EquatableImmutableArray<string>.Empty;
83+
}
84+
else
85+
{
86+
typeParameterNames = EquatableImmutableArray<string>.Empty;
87+
}
88+
89+
Context = new TypeContext(
90+
attributeTargetSymbol.Name,
91+
attributeTargetSymbol.IsStatic,
92+
attributeTargetSymbol.DeclaredAccessibility,
93+
TypeContext.GetRecordStructOrClass(attributeTargetSymbol),
94+
typeParameterNames);
95+
96+
DisplayString = attributeTargetSymbol.ToDisplayString();
97+
98+
// Parse parent classes from symbol's containing types
99+
var parentClassCount = 0;
100+
var containingType = attributeTargetSymbol.ContainingType;
101+
// Count the number of parent classes
102+
while (containingType != null)
103+
{
104+
parentClassCount++;
105+
containingType = containingType.ContainingType;
106+
}
107+
108+
if (parentClassCount > 0)
109+
{
110+
containingType = attributeTargetSymbol.ContainingType;
111+
var containingTypesImmutableArrayBuilder = ImmutableArray.CreateBuilder<TypeContext>(parentClassCount);
112+
for (var i = 0; i < parentClassCount; i++)
113+
{
114+
var typeParameters = containingType.TypeParameters;
115+
var containingTypeTypeParameterNames = typeParameters.Length > 0
116+
? typeParameters.ToEquatableImmutableArray(tp => tp.Name)
117+
: EquatableImmutableArray<string>.Empty;
118+
119+
containingTypesImmutableArrayBuilder.Insert(0, new TypeContext(
120+
containingType.Name,
121+
containingType.IsStatic,
122+
containingType.DeclaredAccessibility,
123+
TypeContext.GetRecordStructOrClass(containingType),
124+
containingTypeTypeParameterNames));
125+
containingType = containingType.ContainingType;
126+
}
127+
128+
ContainingTypes = containingTypesImmutableArrayBuilder.MoveToImmutable().ToEquatableImmutableArray();
129+
}
130+
else
131+
{
132+
ContainingTypes = EquatableImmutableArray<TypeContext>.Empty;
133+
}
134+
}
135+
136+
/// <inheritdoc />
137+
public bool Equals(AttributeContextAndData<T> other)
138+
{
139+
return ContainingNamespaceIsGlobalNamespace == other.ContainingNamespaceIsGlobalNamespace && ContainingNamespaceDisplayString == other.ContainingNamespaceDisplayString && Context.Equals(other.Context) && Equals(ContainingTypes, other.ContainingTypes) && DisplayString == other.DisplayString && AttributeData.Equals(other.AttributeData);
140+
}
141+
142+
/// <inheritdoc />
143+
public override bool Equals(object obj)
144+
{
145+
return obj is AttributeContextAndData<T> other && Equals(other);
146+
}
147+
148+
/// <inheritdoc />
149+
public override int GetHashCode()
150+
{
151+
unchecked
152+
{
153+
var hashCode = ContainingNamespaceIsGlobalNamespace.GetHashCode();
154+
hashCode = (hashCode * 397) ^ (ContainingNamespaceDisplayString != null ? ContainingNamespaceDisplayString.GetHashCode() : 0);
155+
hashCode = (hashCode * 397) ^ Context.GetHashCode();
156+
hashCode = (hashCode * 397) ^ (ContainingTypes != null ? ContainingTypes.GetHashCode() : 0);
157+
hashCode = (hashCode * 397) ^ (DisplayString != null ? DisplayString.GetHashCode() : 0);
158+
hashCode = (hashCode * 397) ^ (AttributeData != null ? AttributeData.GetHashCode() : 0);
159+
return hashCode;
160+
}
161+
}
162+
163+
/// <summary>
164+
/// Predicate to determine if the syntax node is a type declaration syntax.
165+
/// </summary>
166+
/// <param name="syntaxNode">The syntax node to check.</param>
167+
/// <param name="token">The cancellation token to observe for cancellation requests.</param>
168+
/// <returns> True if the syntax node is a type declaration syntax, otherwise false.</returns>
169+
/// <example>
170+
/// <code lang="csharp">
171+
/// context.SyntaxProvider
172+
/// .ForAttributeWithMetadataName(
173+
/// "YourFullyQualifiedMetadataName",
174+
/// predicate: AttributeContextAndData&lt;YourAttributeDataType&gt;.Predicate,
175+
/// transform: (syntaxContext, token) =>
176+
/// AttributeContextAndData&lt;YourAttributeDataType&gt;.Transform(syntaxContext, yourAttributeDataCollector, token))
177+
/// .WithTrackingName(GeneratorStage.ForAttributeWithMetadataName);
178+
/// </code>
179+
/// </example>
180+
/// <seealso cref="AttributeContextAndDataExtensions.SelectAttributeContexts{T}"/>
181+
public static bool Predicate(SyntaxNode syntaxNode, CancellationToken token)
182+
{
183+
#if !DATACUTE_EXCLUDE_GENERATORSTAGE && !DATACUTE_EXCLUDE_LIGHTWEIGHTTRACE
184+
LightweightTrace.IncrementCount(GeneratorStage.ForAttributeWithMetadataNamePredicate);
185+
#if !DATACUTE_EXCLUDE_LIGHTWEIGHTTRACEEXTENSIONS
186+
token.ThrowIfCancellationRequested(GeneratorStage.ForAttributeWithMetadataNamePredicate);
187+
#else
188+
token.ThrowIfCancellationRequested();
189+
#endif
190+
#endif
191+
return syntaxNode is TypeDeclarationSyntax;
192+
}
193+
194+
/// <summary>
195+
/// Transforms the <see cref="GeneratorAttributeSyntaxContext"/> into an <see cref="AttributeContextAndData{T}"/> instance.
196+
/// </summary>
197+
/// <param name="generatorAttributeSyntaxContext">The context of the attribute syntax, which includes information about the target symbol and the attribute itself.</param>
198+
/// <param name="attributeDataCollector">A function that collects the attribute data from the <see cref="GeneratorAttributeSyntaxContext"/>.</param>
199+
/// <param name="token">The cancellation token to observe for cancellation requests.</param>
200+
/// <returns>An instance of <see cref="AttributeContextAndData{T}"/> containing the attribute context and data.</returns>
201+
/// <example>
202+
/// <code lang="csharp">
203+
/// context.SyntaxProvider
204+
/// .ForAttributeWithMetadataName(
205+
/// "YourFullyQualifiedMetadataName",
206+
/// predicate: AttributeContextAndData&lt;YourAttributeDataType&gt;.Predicate,
207+
/// transform: (syntaxContext, token) =>
208+
/// AttributeContextAndData&lt;YourAttributeDataType&gt;.Transform(syntaxContext, yourAttributeDataCollector, token))
209+
/// .WithTrackingName(GeneratorStage.ForAttributeWithMetadataName);
210+
/// </code>
211+
/// </example>
212+
/// <seealso cref="AttributeContextAndDataExtensions.SelectAttributeContexts{T}"/>
213+
public static AttributeContextAndData<T> Transform(GeneratorAttributeSyntaxContext generatorAttributeSyntaxContext, Func<GeneratorAttributeSyntaxContext, T> attributeDataCollector, CancellationToken token)
214+
{
215+
#if !DATACUTE_EXCLUDE_GENERATORSTAGE && !DATACUTE_EXCLUDE_LIGHTWEIGHTTRACE
216+
LightweightTrace.IncrementCount(GeneratorStage.ForAttributeWithMetadataNameTransform);
217+
#if !DATACUTE_EXCLUDE_LIGHTWEIGHTTRACEEXTENSIONS
218+
token.ThrowIfCancellationRequested(GeneratorStage.ForAttributeWithMetadataNameTransform);
219+
#else
220+
token.ThrowIfCancellationRequested();
221+
#endif
222+
#endif
223+
T attributeData = attributeDataCollector(generatorAttributeSyntaxContext);
224+
var attributeContextAndData = new AttributeContextAndData<T>(generatorAttributeSyntaxContext, attributeData);
225+
return attributeContextAndData;
226+
}
227+
}
228+
}
229+
#endif
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// <auto-generated>
2+
// This file is part of the Datacute.IncrementalGeneratorExtensions package.
3+
// It is included as a source file and should not be modified.
4+
// </auto-generated>
5+
6+
#if !DATACUTE_EXCLUDE_ATTRIBUTECONTEXTANDDATAEXTENSIONS && !DATACUTE_EXCLUDE_ATTRIBUTECONTEXTANDDATA && !DATACUTE_EXCLUDE_TYPECONTEXT
7+
using System;
8+
using Microsoft.CodeAnalysis;
9+
10+
namespace Datacute.IncrementalGeneratorExtensions
11+
{
12+
public static class AttributeContextAndDataExtensions
13+
{
14+
/// <summary>
15+
/// Selects attribute contexts and their associated data from the syntax tree.
16+
/// </summary>
17+
/// <param name="context">The <see cref="IncrementalGeneratorInitializationContext"/></param>
18+
/// <param name="fullyQualifiedMetadataName">The fully qualified name of the marker attribute to select.</param>
19+
/// <param name="attributeDataCollector">A function that collects the attribute data from the <see cref="GeneratorAttributeSyntaxContext"/> of the context's SyntaxProvider.</param>
20+
/// <typeparam name="T">Type of the data associated with the attribute context.</typeparam>
21+
/// <returns>An <see cref="IncrementalValuesProvider{T}"/> that provides <see cref="AttributeContextAndData{T}"/>.</returns>
22+
/// <remarks>
23+
/// This method is a shortcut for using <see cref="AttributeContextAndData{T}"/>
24+
/// with the <see cref=" SyntaxValueProvider.ForAttributeWithMetadataName{T}"/> method.
25+
/// </remarks>
26+
/// <example>
27+
/// <code lang="csharp">
28+
/// var attributeContexts =
29+
/// context.SelectAttributeContexts(
30+
/// Templates.AttributeFullyQualified,
31+
/// c => new AttributeData(c));
32+
/// </code>
33+
/// </example>
34+
public static IncrementalValuesProvider<AttributeContextAndData<T>> SelectAttributeContexts<T>(
35+
this IncrementalGeneratorInitializationContext context,
36+
string fullyQualifiedMetadataName,
37+
Func<GeneratorAttributeSyntaxContext, T> attributeDataCollector)
38+
where T : IEquatable<T>
39+
=> context.SyntaxProvider
40+
.ForAttributeWithMetadataName(
41+
fullyQualifiedMetadataName,
42+
predicate: AttributeContextAndData<T>.Predicate,
43+
transform: (syntaxContext, token) =>
44+
AttributeContextAndData<T>.Transform(syntaxContext, attributeDataCollector, token))
45+
#if !DATACUTE_EXCLUDE_LIGHTWEIGHTTRACEEXTENSIONS && !DATACUTE_EXCLUDE_LIGHTWEIGHT && !DATACUTE_EXCLUDE_GENERATORSTAGE
46+
.WithTrackingName(GeneratorStage.ForAttributeWithMetadataName);
47+
#else
48+
;
49+
#endif
50+
}
51+
}
52+
#endif

IncrementalGeneratorExtensions/EmbeddedAttributeExtensions.cs renamed to IncrementalGeneratorExtensions.Content/Datacute/IncrementalGeneratorExtensions/EmbeddedAttributeExtensions.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
#if !DATACUTE_EXCLUDE_EMBEDDEDATTRIBUTEEXTENSIONS
1+
// <auto-generated>
2+
// This file is part of the Datacute.IncrementalGeneratorExtensions package.
3+
// It is included as a source file and should not be modified.
4+
// </auto-generated>
5+
6+
#if !DATACUTE_EXCLUDE_EMBEDDEDATTRIBUTEEXTENSIONS
27

38
// ReSharper disable RedundantNameQualifier including global:: qualifier is good practice when supplying sourcecode to an unknown context
49
// ReSharper disable UnusedMember.Global project source is used in other projects

0 commit comments

Comments
 (0)