Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Internal Agent Instructions

Purpose: Helper source files (embedded) for .NET incremental generators; keep output deterministic and public API stable.

## 1. Scope
Chat = terse (summary + reason + minimal sample). Repo docs = rich (rationale, examples, edge cases, perf). Additive = new files/members only; no deletions, renames, or moves unless explicitly requested.

## 2. Platform & Stability
netstandard2.0 / C# 7.3. No newer BCL APIs. Preserve public type & member names.

## 3. Guards
Each helper file can be excluded from the build using a guard.
Nest or document dependencies using guards, depending on how closely related the helper file is to the dependency.
Closely related examples:
```
#if !DATACUTE_EXCLUDE_EQUATABLEIMMUTABLEARRAYEXTENSIONS // Feature: EquatableImmutableArrayExtensions
#if !DATACUTE_EXCLUDE_EQUATABLEIMMUTABLEARRAY // Dependency: EquatableImmutableArray
// contents
#endif // !DATACUTE_EXCLUDE_EQUATABLEIMMUTABLEARRAY
#endif // !DATACUTE_EXCLUDE_EQUATABLEIMMUTABLEARRAYEXTENSIONS
```
Not closely related examples:
```
#if !DATACUTE_EXCLUDE_ATTRIBUTECONTEXTANDDATA // Feature: AttributeContextAndData
#if DATACUTE_EXCLUDE_TYPECONTEXT
#error AttributeContextAndData requires TypeContext (remove DATACUTE_EXCLUDE_TYPECONTEXT or also exclude DATACUTE_EXCLUDE_ATTRIBUTECONTEXTANDDATA)
#endif
#if DATACUTE_EXCLUDE_EQUATABLEIMMUTABLEARRAY
#error AttributeContextAndData requires EquatableImmutableArray (remove DATACUTE_EXCLUDE_EQUATABLEIMMUTABLEARRAY or also exclude DATACUTE_EXCLUDE_ATTRIBUTECONTEXTANDDATA)
#endif
// contents
#endif // !DATACUTE_EXCLUDE_ATTRIBUTECONTEXTANDDATA
```

## 4. Editing & Samples
Show only example code snippets (no diffs) with ≤3 lines of surrounding context when needed. Fenced blocks for multi‑line examples. Use code samples to illustrate changes; do not use diff syntax in chat. Use language tags: csharp for C# code, xml for XML docs, and text for plain text. Short reason (perf / determinism / clarity / bug fix). Keep commentary in the chat. Code changes must omit commentary. Resulting code must look like it was always written that way.

When showing a change, prefer Before/After paired samples:
- Before: the minimal original snippet required for context.
- After: the revised snippet in full.

## 5. Public API Doc Comments
For every public type or member emit an XML doc summary (no placeholders). Include <param>/<typeparam>/<returns> when present; only add <remarks> if non-trivial behaviour (threading, allocation, ordering, invariants).

## 6. Documentation
UK English. XML doc first sentence = summary; follow with behaviour / perf / pitfalls if useful. READMEs may include: Overview, Why, Key APIs, Examples, Performance, Dependencies, Exclusion Flags, Instrumentation. Document public surface only (internal/private only if cross‑file contract). Chat brevity does not apply to repo docs.

## 7. Chat Response Format
1. Summary (1–2 lines)
2. Example code snippet(s) only (no diff format)
3. Optional next steps (label “Optional”).

## 8. Assumptions
If unspecified, state one reversible assumption in the chat and proceed.
54 changes: 54 additions & 0 deletions .junie/guidelines.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Internal Agent Instructions

Purpose: Helper source files (embedded) for .NET incremental generators; keep output deterministic and public API stable.

## 1. Scope
Chat = terse (summary + reason + minimal sample). Repo docs = rich (rationale, examples, edge cases, perf). Additive = new files/members only; no deletions, renames, or moves unless explicitly requested.

## 2. Platform & Stability
netstandard2.0 / C# 7.3. No newer BCL APIs. Preserve public type & member names.

## 3. Guards
Each helper file can be excluded from the build using a guard.
Nest or document dependencies using guards, depending on how closely related the helper file is to the dependency.
Closely related examples:
```
#if !DATACUTE_EXCLUDE_EQUATABLEIMMUTABLEARRAYEXTENSIONS // Feature: EquatableImmutableArrayExtensions
#if !DATACUTE_EXCLUDE_EQUATABLEIMMUTABLEARRAY // Dependency: EquatableImmutableArray
// contents
#endif // !DATACUTE_EXCLUDE_EQUATABLEIMMUTABLEARRAY
#endif // !DATACUTE_EXCLUDE_EQUATABLEIMMUTABLEARRAYEXTENSIONS
```
Not closely related examples:
```
#if !DATACUTE_EXCLUDE_ATTRIBUTECONTEXTANDDATA // Feature: AttributeContextAndData
#if DATACUTE_EXCLUDE_TYPECONTEXT
#error AttributeContextAndData requires TypeContext (remove DATACUTE_EXCLUDE_TYPECONTEXT or also exclude DATACUTE_EXCLUDE_ATTRIBUTECONTEXTANDDATA)
#endif
#if DATACUTE_EXCLUDE_EQUATABLEIMMUTABLEARRAY
#error AttributeContextAndData requires EquatableImmutableArray (remove DATACUTE_EXCLUDE_EQUATABLEIMMUTABLEARRAY or also exclude DATACUTE_EXCLUDE_ATTRIBUTECONTEXTANDDATA)
#endif
// contents
#endif // !DATACUTE_EXCLUDE_ATTRIBUTECONTEXTANDDATA
```

## 4. Editing & Samples
Show only example code snippets (no diffs) with ≤3 lines of surrounding context when needed. Fenced blocks for multi‑line examples. Use code samples to illustrate changes; do not use diff syntax in chat. Use language tags: csharp for C# code, xml for XML docs, and text for plain text. Short reason (perf / determinism / clarity / bug fix). Keep commentary in the chat. Code changes must omit commentary. Resulting code must look like it was always written that way.

When showing a change, prefer Before/After paired samples:
- Before: the minimal original snippet required for context.
- After: the revised snippet in full.

## 5. Public API Doc Comments
For every public type or member emit an XML doc summary (no placeholders). Include <param>/<typeparam>/<returns> when present; only add <remarks> if non-trivial behaviour (threading, allocation, ordering, invariants).

## 6. Documentation
UK English. XML doc first sentence = summary; follow with behaviour / perf / pitfalls if useful. READMEs may include: Overview, Why, Key APIs, Examples, Performance, Dependencies, Exclusion Flags, Instrumentation. Document public surface only (internal/private only if cross‑file contract). Chat brevity does not apply to repo docs.

## 7. Chat Response Format
1. Summary (1–2 lines)
2. Example code snippet(s) only (no diff format)
3. Optional next steps (label “Optional”).

## 8. Assumptions
If unspecified, state one reversible assumption in the chat and proceed.
13 changes: 12 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [1.0.6] - 2025-08-16

### Added

- Lots more documentation.

### Changed

- Include version in auto-generated comments added by this generator.

## [1.0.5] - 2025-08-03

### Fixed
Expand Down Expand Up @@ -96,7 +106,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

---

[Unreleased]: https://github.com/datacute/IncrementalGeneratorExtensions/compare/1.0.5...develop
[Unreleased]: https://github.com/datacute/IncrementalGeneratorExtensions/compare/1.0.6...develop
[1.0.6]: https://github.com/datacute/IncrementalGeneratorExtensions/releases/tag/1.0.6
[1.0.5]: https://github.com/datacute/IncrementalGeneratorExtensions/releases/tag/1.0.5
[1.0.4]: https://github.com/datacute/IncrementalGeneratorExtensions/releases/tag/1.0.4
[1.0.3]: https://github.com/datacute/IncrementalGeneratorExtensions/releases/tag/1.0.3
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
// <auto-generated>
// This file is part of the Datacute.IncrementalGeneratorExtensions package.
// It is included as a source file and should not be modified.
// </auto-generated>

#if !DATACUTE_EXCLUDE_ATTRIBUTECONTEXTANDDATA && !DATACUTE_EXCLUDE_TYPECONTEXT
#if !DATACUTE_EXCLUDE_ATTRIBUTECONTEXTANDDATA // Feature: AttributeContextAndData
#if DATACUTE_EXCLUDE_TYPECONTEXT
#error AttributeContextAndData requires TypeContext (remove DATACUTE_EXCLUDE_TYPECONTEXT or also exclude DATACUTE_EXCLUDE_ATTRIBUTECONTEXTANDDATA)
#endif
#if DATACUTE_EXCLUDE_EQUATABLEIMMUTABLEARRAY
#error AttributeContextAndData requires EquatableImmutableArray (remove DATACUTE_EXCLUDE_EQUATABLEIMMUTABLEARRAY or also exclude DATACUTE_EXCLUDE_ATTRIBUTECONTEXTANDDATA)
#endif
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Text;
using System.Threading;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;

namespace Datacute.IncrementalGeneratorExtensions
{
Expand Down Expand Up @@ -78,25 +80,25 @@ public AttributeContextAndData(
}

/// <inheritdoc />
public bool Equals(AttributeContextAndData<T> other)
{
return Context.Equals(other.Context) && Equals(ContainingTypes, other.ContainingTypes) && AttributeData.Equals(other.AttributeData);
}
public bool Equals(AttributeContextAndData<T> other) =>
Context.Equals(other.Context) &&
Equals(ContainingTypes, other.ContainingTypes) &&
EqualityComparer<T>.Default.Equals(AttributeData, other.AttributeData) &&
IsInFileScopedNamespace == other.IsInFileScopedNamespace;

/// <inheritdoc />
public override bool Equals(object obj)
{
return obj is AttributeContextAndData<T> other && Equals(other);
}
public override bool Equals(object obj) =>
obj is AttributeContextAndData<T> other && Equals(other);

/// <inheritdoc />
public override int GetHashCode()
{
unchecked
{
var hashCode = Context.GetHashCode();
hashCode = (hashCode * 397) ^ (ContainingTypes != null ? ContainingTypes.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ (AttributeData != null ? AttributeData.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ ContainingTypes.GetHashCode();
hashCode = (hashCode * 397) ^ EqualityComparer<T>.Default.GetHashCode(AttributeData);
hashCode = (hashCode * 397) ^ (IsInFileScopedNamespace ? 1 : 0);
return hashCode;
}
}
Expand Down Expand Up @@ -154,11 +156,11 @@ public static bool Predicate(SyntaxNode syntaxNode, CancellationToken token)
LightweightTrace.IncrementCount(GeneratorStage.ForAttributeWithMetadataNamePredicate);
#endif
// We are only interested in partial type declarations
var typeDeclaration = syntaxNode as TypeDeclarationSyntax;
if (typeDeclaration == null || !typeDeclaration.Modifiers.Any(SyntaxKind.PartialKeyword))
{
return false;
}
if (!(syntaxNode is TypeDeclarationSyntax typeDeclaration))
return false; // Not a type declaration

if (!typeDeclaration.Modifiers.Any(SyntaxKind.PartialKeyword))
return false; // Must be partial

// Now, ensure all containing types are also partial.
// This is necessary to be able to generate code for partial types correctly.
Expand Down Expand Up @@ -212,19 +214,8 @@ public static AttributeContextAndData<T> Transform(
var attributeTargetSymbol = (ITypeSymbol)generatorAttributeSyntaxContext.TargetSymbol;
var typeDeclaration = (TypeDeclarationSyntax)generatorAttributeSyntaxContext.TargetNode;

var isInFileScopedNamespace = false;
if (typeDeclaration.SyntaxTree.GetRoot(token) is CompilationUnitSyntax compilationUnit)
{
foreach (var member in compilationUnit.Members)
{
if (member.GetType().Name == "FileScopedNamespaceDeclarationSyntax")
{
isInFileScopedNamespace = true;
break;
}
}
}

var isInFileScopedNamespace = HasFileScopedNamespace(typeDeclaration.SyntaxTree, token);

var isNullableContextEnabled = GetIsNullableContextEnabled(generatorAttributeSyntaxContext.SemanticModel, typeDeclaration.SpanStart);

EquatableImmutableArray<string> typeParameterNames;
Expand All @@ -240,16 +231,7 @@ public static AttributeContextAndData<T> Transform(
typeParameterNames = EquatableImmutableArray<string>.Empty;
}

var typeContext = new TypeContext(
TypeContext.GetNamespaceDisplayString(attributeTargetSymbol.ContainingNamespace),
attributeTargetSymbol.Name,
attributeTargetSymbol.IsStatic,
isPartial: true, // Known to be true because of the predicate
attributeTargetSymbol.IsAbstract,
attributeTargetSymbol.IsSealed,
attributeTargetSymbol.DeclaredAccessibility,
TypeContext.GetTypeDeclarationKeyword(attributeTargetSymbol),
typeParameterNames);
var typeContext = CreateTypeContext(attributeTargetSymbol, isPartial: true, typeParameterNames);

// Parse parent classes from symbol's containing types
var parentClassCount = 0;
Expand All @@ -274,18 +256,9 @@ public static AttributeContextAndData<T> Transform(
: EquatableImmutableArray<string>.Empty;

// The predicate has already confirmed that this type is partial.
var containingTypeIsPartial = true;

containingTypesImmutableArrayBuilder.Insert(0, new TypeContext(
TypeContext.GetNamespaceDisplayString(containingType.ContainingNamespace),
containingType.Name,
containingType.IsStatic,
containingTypeIsPartial,
containingType.IsAbstract,
containingType.IsSealed,
containingType.DeclaredAccessibility,
TypeContext.GetTypeDeclarationKeyword(containingType),
containingTypeTypeParameterNames));
const bool containingTypeIsPartial = true;
var containingTypeContext = CreateTypeContext(containingType, containingTypeIsPartial, containingTypeTypeParameterNames);
containingTypesImmutableArrayBuilder.Insert(0, containingTypeContext);
containingType = containingType.ContainingType;
}

Expand All @@ -296,19 +269,48 @@ public static AttributeContextAndData<T> Transform(
containingTypes = EquatableImmutableArray<TypeContext>.Empty;
}

var attributeContextAndData = new AttributeContextAndData<T>(
return new AttributeContextAndData<T>(
typeContext,
containingTypes,
attributeData,
isInFileScopedNamespace,
isNullableContextEnabled);
}
private static TypeContext CreateTypeContext(
ITypeSymbol symbol,
bool isPartial,
EquatableImmutableArray<string> typeParameterNames)
{
return new TypeContext(
TypeContext.GetNamespaceDisplayString(symbol.ContainingNamespace),
symbol.Name,
symbol.IsStatic,
isPartial,
symbol.IsAbstract,
symbol.IsSealed,
symbol.DeclaredAccessibility,
TypeContext.GetTypeDeclarationKeyword(symbol),
typeParameterNames);
}

return attributeContextAndData;
private static bool HasFileScopedNamespace(SyntaxTree syntaxTree, CancellationToken token)
{
if (!(syntaxTree.GetRoot(token) is CompilationUnitSyntax root))
return false;

foreach (var member in root.Members)
{
if (member.GetType().Name == "FileScopedNamespaceDeclarationSyntax")
return true;
}

return false;
}



// This delegate is initialized to point to the bootstrap method.
// After the first run, it will point to the final, efficient implementation.
// ReSharper disable once StaticMemberInGenericType There's typically only one instance.
// ReSharper disable once InconsistentNaming purposely named like a method
private static Func<SemanticModel, int, bool> GetIsNullableContextEnabled = BootstrapGetIsNullableContextEnabled;

private static bool BootstrapGetIsNullableContextEnabled(SemanticModel semanticModel, int position)
Expand Down Expand Up @@ -338,4 +340,4 @@ private static bool BootstrapGetIsNullableContextEnabled(SemanticModel semanticM
}
}
}
#endif
#endif // !DATACUTE_EXCLUDE_ATTRIBUTECONTEXTANDDATA
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
// <auto-generated>
// This file is part of the Datacute.IncrementalGeneratorExtensions package.
// It is included as a source file and should not be modified.
// </auto-generated>

#if !DATACUTE_EXCLUDE_ATTRIBUTECONTEXTANDDATAEXTENSIONS && !DATACUTE_EXCLUDE_ATTRIBUTECONTEXTANDDATA && !DATACUTE_EXCLUDE_TYPECONTEXT
#if !DATACUTE_EXCLUDE_ATTRIBUTECONTEXTANDDATAEXTENSIONS // Feature: AttributeContextAndDataExtensions
#if !DATACUTE_EXCLUDE_ATTRIBUTECONTEXTANDDATA // Dependency: AttributeContextAndData
using System;
using Microsoft.CodeAnalysis;

namespace Datacute.IncrementalGeneratorExtensions
{
/// <summary>
/// Extension methods for wiring up <see cref="AttributeContextAndData{T}"/> collection into an incremental generator pipeline.
/// </summary>
public static class AttributeContextAndDataExtensions
{
/// <summary>
Expand Down Expand Up @@ -42,11 +41,12 @@ public static IncrementalValuesProvider<AttributeContextAndData<T>> SelectAttrib
predicate: AttributeContextAndData<T>.Predicate,
transform: (syntaxContext, token) =>
AttributeContextAndData<T>.Transform(syntaxContext, attributeDataCollector, token))
#if !DATACUTE_EXCLUDE_LIGHTWEIGHTTRACEEXTENSIONS && !DATACUTE_EXCLUDE_LIGHTWEIGHT && !DATACUTE_EXCLUDE_GENERATORSTAGE
#if !DATACUTE_EXCLUDE_LIGHTWEIGHTTRACEEXTENSIONS && !DATACUTE_EXCLUDE_LIGHTWEIGHTTRACE && !DATACUTE_EXCLUDE_GENERATORSTAGE
.WithTrackingName(GeneratorStage.ForAttributeWithMetadataName);
#else
;
#endif
}
}
#endif
#endif // !DATACUTE_EXCLUDE_ATTRIBUTECONTEXTANDDATA
#endif // !DATACUTE_EXCLUDE_ATTRIBUTECONTEXTANDDATAEXTENSIONS
Loading