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
2 changes: 1 addition & 1 deletion .config/dotnet-tools.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"rollForward": false
},
"purview.build": {
"version": "0.5.4",
"version": "0.7.0",
"commands": [
"purview-build"
],
Expand Down
29 changes: 23 additions & 6 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -1,3 +1,23 @@
# Purview.BuildSdk 1.1.0 - managed file, do not edit
#
# This file is written by Purview.BuildSdk and is replaced whenever the SDK version changes. The version
# on the first line is how the SDK recognises a file it owns: a .editorconfig without that marker is
# yours, and is never touched.
#
# To add your own rules, create a second .editorconfig in a subdirectory - `src/.editorconfig` is the
# usual choice - without `root = true`. EditorConfig applies the nearest file last, so your settings win
# over these, and nothing you write is lost when this file is updated.
#
# To stop the SDK updating this file, set one of:
# <PurviewEditorConfigAutoUpdate>false</PurviewEditorConfigAutoUpdate> keep this file as it is
# <PurviewRepoBootstrapMode>Never</PurviewRepoBootstrapMode> opt out of all bootstrapping
# <BootstrapEditorConfigToRepoRoot>false</BootstrapEditorConfigToRepoRoot>
#
# Deleting the first line also works: without the marker the SDK treats the file as yours. You then own
# every rule in it, including the ones a future SDK version would have fixed.
#
# See https://github.com/purview-dev/build-sdk#editorconfig for the full description.

root = true

# Base Configuration - General Settings, File Formatting, and Naming Conventions
Expand Down Expand Up @@ -360,12 +380,8 @@ dotnet_style_require_accessibility_modifiers = omit_if_default:warning
# Code-block preferences
csharp_prefer_braces = when_multiline:suggestion
csharp_prefer_simple_using_statement = false:silent
csharp_prefer_system_threading_lock = true
dotnet_style_prefer_collection_expression = true:suggestion

# Enable foreach explicit cast preference
dotnet_style_prefer_foreach_explicit_cast_in_source = when_strongly_typed:warning

# Prefer System.Threading.Lock
csharp_prefer_system_threading_lock = true:warning

Expand Down Expand Up @@ -497,7 +513,7 @@ dotnet_style_prefer_auto_properties = true:warning
dotnet_style_prefer_compound_assignment = true:warning
dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion
dotnet_style_prefer_conditional_expression_over_return = true:suggestion
dotnet_style_prefer_foreach_explicit_cast_in_source = when_strongly_typed
dotnet_style_prefer_foreach_explicit_cast_in_source = when_strongly_typed:warning
dotnet_style_prefer_inferred_anonymous_type_member_names = true:warning
dotnet_style_prefer_inferred_tuple_names = true:warning
dotnet_style_prefer_simplified_boolean_expressions = false:silent
Expand Down Expand Up @@ -2676,7 +2692,8 @@ dotnet_diagnostic.PDS0004.severity = none

[**/Extensions/**.{cs,vb}]
# Files under the Extensions/ namespace-reset convention must not force developers to add pragmas;
# suppress the namespace-conflict diagnostics that arise from declaring framework-style namespaces.
# suppress only the namespace-conflict diagnostics that arise from declaring framework-style
# namespaces. Accessibility/design rules (for example CA1034) are deliberately not suppressed here.
dotnet_diagnostic.IDE0130.severity = none
dotnet_diagnostic.CA1724.severity = none
dotnet_diagnostic.CS0436.severity = none
Expand Down
7 changes: 7 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@ on:
push:
branches: [main]

permissions:
contents: write
# The shared release workflow pushes the same packages to nuget.org and, when
# PURVIEW_PUBLISH_GITHUB_PACKAGES=true, to GitHub Packages. A reusable workflow cannot grant
# more than its caller, so the permission is declared here.
packages: write

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: false
Expand Down
140 changes: 140 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
# Changelog

All notable changes to `Purview.SourceGeneratorFramework` and its testing packages.

The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Versions come from the
`version` field in `package.json`, which is the single source of truth: a merge to `main` publishes
that version to nuget.org.

This file was reconstructed from the conventional-commit history at 1.1.1, so entries before that
release summarise what the commits recorded rather than what was written at the time.

## [1.2.0] - 2026-10-21

### Fixed

- `TypeLibraryModelLibrary` held an `INamedTypeSymbol` in its pipeline node value and combined it with
`CompilationProvider`. A symbol compares by reference, so the stage reported `Modified` on every new
compilation and rebuilt its target on every keystroke. The whole target is now built inside the
`ForAttributeWithMetadataName` transform, and the framework `PurviewTypeLibrary` shape is memoised per
compilation through a `ConditionalWeakTable` — preserving the once-per-compilation walk without a
pipeline node holding the compilation. The `GetFrameworkTypeLibraryTree` stage no longer exists.
- `AttributeDataModelLibrary`'s type-library spec provider returned the symbol and projected it in a
following `Select`; the read now happens inside the transform, so the node value is the
value-equatable `TypeLibrarySpecData`.
- `AvoidSymbolsInPipelineModelAnalyzer` (`PSGFR33`) matched banned types by exact display string, so
`INamedTypeSymbol` never matched `ISymbol` and `CSharpCompilation` never matched `Compilation`. It now
walks base types and implemented interfaces, which is what real pipeline code names.
- `AttributeDataModelGenerator` had three `_ => throw new InvalidOperationException(...)` arms in its emit
helpers — exactly the `PSGFR44` shape, and reachable by a consumer only as a `CS8785` that would cost them
every file the generator produces. All three were unreachable by validation (`ADM0008` is an error unless a
type-argument property is a `TypeIdentity`), so each now falls through to the equivalent defined arm with a
comment naming the validation that makes it unreachable. No behaviour changes for any defined input.
- Corrected the `Attirbutes` spelling in `GeneratorTypeLibrary` (internal).
- Removed a committed zero-byte `AttributeDataGenerationModel.cs`.

### Added

- **`ComponentGuard` / `ComponentFailure` — an escaping exception becomes a diagnostic.** Covers every
component kind the framework supports: source generators, analyzers, code fixes, refactorings and
diagnostic suppressors. `Run` reports the failure where there is a channel to report on (a generator's
`SourceProductionContext`, an analyzer's `ReportDiagnostic`); `TryRun` returns whether the body succeeded
where there is not (suppressors, code fixes — the correct behaviour there is to suppress nothing and offer
no fix). Cancellation is never caught: an `OperationCanceledException` raised while the supplied token is
cancelled propagates untouched, because Roslyn cancels analysis on every keystroke.

The reported diagnostic defaults entirely from the component, because it belongs to the consumer rather
than to the framework: the id is derived from the component's namespace (`Acme.Widgets.Generators` →
`AWG0001`), the title from the component kind, the category from the root namespace, and the severity is
`Warning`. Every part is overridable through `ComponentFailureOptions`, including an opt-in stack trace.
See [`docs/wiki/Component-Failures.md`](docs/wiki/Component-Failures.md).

The framework's own `RegisterSourceOutput` now runs the emit callback through the guard, so a generator
built on it is covered without changing a line. Its signature is unchanged — the overrides are declared on
the new `GenerationSettings.FailureOptions`, alongside everything else a generator says about itself.
Adding an optional parameter to `RegisterSourceOutput` instead would have removed the four-parameter
signature from metadata and broken every assembly already compiled against it; package validation caught
that before it shipped.
- **`PSGFR44` — source generators must not throw.** An exception escaping a generator is reported as
`CS8785` in every consuming build and IDE, and the generator produces no output at all for that
compilation, so the consumer also gets every "type or namespace not found" error that follows — with a
stack trace through the generator's internals rather than a diagnostic on their own code. The rule flags a
`throw` anywhere inside a type implementing `IIncrementalGenerator` (the `Initialize` body, the pipeline
lambdas, and the emit helpers hanging off it) and in any lambda handed to a pipeline method. Report a
diagnostic instead: `GeneratorResult<T>` with a `ReportableDiagnostic`, or
`SourceProductionContext.ReportDiagnostic`. Cancellation is unaffected, because
`CancellationToken.ThrowIfCancellationRequested()` is a method call rather than a `throw`.
- `PSGFR33` now also inspects `IncrementalValue(s)Provider<T>` type arguments, so a transform returning a
symbol or a tuple carrying one is reported. Previously the rule only walked the members of named model
types, which is a shape the framework's own generators did not use — so the rule could not see them.
Findings are collected during the compilation and reported at compilation end, because the descriptor
carries `WellKnownDiagnosticTags.CompilationEnd` and Roslyn drops such diagnostics when they are
reported from an operation action.
- `GeneratorCompilationRetentionTests`, which pins that a generator's pipeline nodes do not keep earlier
compilations reachable.

### Changed

- **Breaking for test authors.** `SourceGeneratorTestRunner.RunIncremental` now builds a fresh
`Compilation` for every run, matching what an IDE produces on every keystroke. It previously reused one
instance across runs with identical sources, which let a stage holding a symbol report `Cached` on
reference equality alone and hid exactly the regression these tests exist to catch. Set
`SourceGeneratorTestOptions.ReuseCompilationForIdenticalSources` to restore the old behaviour where a
scenario genuinely needs it. Assert the generator's own tracked stages with `StepIsCached` rather than
`AllStepsCachedOrUnchanged`, which also covers Roslyn's internal steps.

### Documentation

- `docs/wiki/Step-Cache-Tests.md` documents the fresh-compilation default and the opt-out.

## [1.1.1]

### Fixed

- Incorrect identification of a drive result.

### Changed

- Migrated authored agent skills to the repository-root `skills/` folder and the skills CLI.

## [1.1.0]

### Added

- `PSGFR42` for undisposed test-harness results.

### Performance

- Various caching and expensive-operation fixes across the framework.
- `MetadataReference` instances shared across test compilations.

### Fixed

- The logging sink is registered for incremental runs, and entries stay ordered.
- A scoped `CodeQuery` no longer repeats matches once per tree.
- The `TypeIdentity` symbol cache is keyed on the symbol.

## [1.0.0]

### Added

- ILRepack-based merge so a component ships a self-contained analyzer and two generators cannot collide
on assembly identity in `VBCSCompiler`.
- Expanded type library, `EnumValue`/`EnumValues` support, operator method names.

### Fixed

- A component's own internals grants survive the merge.
- Internaliser incorrectly matching tested types.
- Source-generator resolution on pack, and framework types leaking from a component.
- XML cref fixers.

### Changed

- Removed the separate `Shared` assembly; its sources are link-compiled into the framework.

[Unreleased]: https://github.com/purview-dev/sourcegenerator-framework/compare/v1.2.0...HEAD
[1.2.0]: https://github.com/purview-dev/sourcegenerator-framework/compare/v1.1.1...v1.2.0
[1.1.1]: https://github.com/purview-dev/sourcegenerator-framework/compare/v1.1.0...v1.1.1
[1.1.0]: https://github.com/purview-dev/sourcegenerator-framework/compare/v1.0.0...v1.1.0
[1.0.0]: https://github.com/purview-dev/sourcegenerator-framework/releases/tag/v1.0.0
7 changes: 2 additions & 5 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@
<CentralPackageTransitivePinningEnabled>true</CentralPackageTransitivePinningEnabled>
<RoslynCompilerVersion Condition="'$(RoslynCompilerVersion)' == ''">[5.9.0,)</RoslynCompilerVersion>
<RoslynAnalyzersVersion Condition="'$(RoslynAnalyzersVersion)' == ''">[5.9.0,)</RoslynAnalyzersVersion>
<!-- Floor raised to 1.75.0 because Purview.Skills.Testing 1.0.6 depends on TUnit.Assertions/TUnit.Core
1.75.0, and central transitive pinning would otherwise pin them back down. -->
<TUnitVersion>[1.75.0,)</TUnitVersion>
<TUnitVersion>[1.76.0,)</TUnitVersion>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Bogus" Version="[35.6.5,)" />
Expand All @@ -25,7 +23,6 @@
<PackageVersion Include="BenchmarkDotNet" Version="0.15.8" />
<PackageVersion Include="ILRepack.Lib" Version="2.0.48" />
<PackageVersion Include="Mono.Cecil" Version="0.11.6" />
<!-- The Agent Skills conformance rule set, shipped alongside Purview.BuildSdk. -->
<PackageVersion Include="Purview.Skills.Testing" Version="1.0.6" />
<PackageVersion Include="Purview.Skills.Testing" Version="1.1.0" />
</ItemGroup>
</Project>
1 change: 1 addition & 0 deletions docs/wiki/Analyzers.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ Opt out with `PurviewSourceGeneratorFrameworkAnalyzerValidation=false` (`PRSGD00
| `PSGFR40` | In Roslyn components (`IsRoslynComponent=true`), reference SGF types as inline code (`<c>Type</c>`) instead of a `cref`: copied documentation must not depend on cref resolution. |
| `PSGFR41` | In Roslyn components whose framework implementation is merged, a public member (or generic constraint) whose signature references an SGF type: the merge internalizes every SGF type, so the signature is left referring to an internal type. Make the member or its declaring type non-public. |
| `PSGFR42` | A disposable Testing-harness result (`DriverRunResult`) is produced and never disposed, leaking the collectible `AssemblyLoadContext` and `MetadataLoadContext` of a compiled run. Reported as a `warning` when the run syntactically compiles to an assembly (a `Compile` options member or `CompileToAssembly = true`), otherwise as `info`. Awaiting an assertion, or calling a helper that is handed the result — an extension on it, or a method taking it as a parameter — is not a production: both hand the same instance back, so only a task-like await (the shape the harness runners return) or a genuine producer is reported. |
| `PSGFR44` | A `throw` in code that runs during source generation. An exception escaping a generator is reported as `CS8785` in every consuming build and IDE, and the generator produces no output at all for that compilation — so the consumer also sees every "type or namespace not found" error that follows from the missing files, with a stack trace through the generator's internals rather than a diagnostic on their own code. Report a diagnostic instead: carry it on `GeneratorResult<T>` as a `ReportableDiagnostic`, or call `SourceProductionContext.ReportDiagnostic`. Two scopes are checked — anything inside a type implementing `IIncrementalGenerator` (the `Initialize` body, the pipeline lambdas, and the emit helpers hanging off it), and any lambda handed to a pipeline method wherever it is written. Cancellation is unaffected: `CancellationToken.ThrowIfCancellationRequested()` is a method call, not a `throw`, and Roslyn expects that exception to propagate. |

## Type-library and attribute-model diagnostics

Expand Down
Loading
Loading