Skip to content

feat: combine the binding and attribute source generators - #524

Open
glennawatson wants to merge 214 commits into
mainfrom
feat/combine-binding-source-generators
Open

glennawatson wants to merge 214 commits into
mainfrom
feat/combine-binding-source-generators

Conversation

@glennawatson

@glennawatson glennawatson commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Combines ReactiveUI.Binding.SourceGenerators into this repository, so the attribute generators and the binding generators build, test and ship together. The binding family's single-file generator structure is the model for both.

  • Both families share one SourceWriter and one array-backed EquatableArray<T>. Internal code uses arrays, lists and sets. ImmutableArray<T> stays only where Roslyn requires it.
  • The attribute generators lay out like the binding projects: one class and one models folder per generator, and one solution, solution filters and CI.
  • [IViewFor], [IViewFor<T>] and [IViewFor("TypeName")] are back, with the view registration the binding family reads.
  • Every shipped analyzer and diagnostic is kept, with its tests and a benchmark.
  • Every generator, analyzer and suppressor has a benchmark. The benchmark workflows run all of them.
  • Fixes found in the old repositories: a generic ReactiveObject owner now generates ToProperty; class is no longer repeated beside a class type constraint (CS0450); MAUI and Avalonia XAML in the 2009 namespace is read; an [ObservableAsProperty] value is readable from the notifications its helper raises as it starts.
  • The README is a short landing page with tables. The binding reference moved to docs, and each example has a README.

Why

The two families are coupled: the attribute generators write members the binding generators observe, and both replace the same ReactiveUI runtime work. Keeping them apart meant two sets of infrastructure and two sets of fixes.

Refs reactiveui/ReactiveUI.Binding.SourceGenerators#182, #184 and #185. Refs reactiveui/ReactiveUI#4538 and #4541.

Breaking changes

  • The ReactiveUI.Binding.SourceGenerators solution and project files are renamed into this repository's layout. Package IDs are unchanged.
  • The attribute packages and the ReactiveUI.Binding* packages keep separate version lines (v and binding-v tags), so tagging one family never moves the other.
  • ObservableAsPropertyHelper<T> gains Create overloads that take an ObservablePropertyTarget, and PendingValue. Existing overloads are unchanged.

How this was verified

Generator, analyzer and runtime tests for both families, including new tests for each fix above. The packed attribute package was inspected for its bands and README. The MAUI Android and wasm projects could not be built here because those workloads are not installed; CI builds them.

Notes for the reviewer

  • Start with src/ReactiveUI.SourceGenerators.Shared and CLAUDE.md, which describe the layout and the rules both families follow.
  • The history of the old repository is merged in, so the diff is large. The mechanical parts are the renames and the snapshot updates.

Checklist

  • I have read the Contribute guide
  • The PR title follows Conventional Commits
  • Tests cover this change, or the summary says why they do not
  • New or changed public API has XML documentation

glennawatson and others added 30 commits February 7, 2026 19:10
Clean up the shared scenarios files
…#4)

* Fix .NET Framework build errors and MAUI Visibility ambiguity

- Add RequiresUnreferencedCodeAttribute polyfill for net462/net472/net481
  targets where the attribute is inaccessible (only public in .NET 5+)
- Add ModuleInitializerAttribute polyfill for benchmark net462 target
- Fix CS0104 Visibility ambiguity in MAUI project on Windows targets by
  using type aliases instead of namespace imports

* Fix remaining .NET Framework polyfill and visibility issues

- Add InternalsVisibleTo from ReactiveUI.Binding to Wpf, WinForms, and
  Maui projects so they can access internal polyfill attributes
  (RequiresUnreferencedCodeAttribute, NotNullWhenAttribute, etc.)
- Add missing using for POCOObservableForProperty in MAUI's
  DependencyObjectObservableForProperty.cs
- Emit ModuleInitializerAttribute polyfill from source generator for
  consumer projects targeting .NET Framework (same pattern as existing
  CallerArgumentExpressionAttribute polyfill)

* Remove all polyfill emission from source generator

The generator should never emit polyfills into consumer projects.
Instead it detects platform capabilities and generates appropriate code:

- Remove CallerArgumentExpressionAttribute polyfill emission; the
  supportsCallerArgExpr flag now checks both C# 10+ language version
  AND that the attribute exists in the compilation, falling back to
  file/line dispatch when unavailable
- Remove [ModuleInitializer] from generated registration code entirely
- Replace [ModuleInitializer] in ReactiveUI benchmarks with explicit
  static constructor calls using an atomic bool guard pattern
- Update snapshot tests
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
* chore: increase the amount of unit testing

* Fix stack overflow in tests

* remove tests from code coverage by exclusion

* Update claude file

* Further adding tests

* Further testing

* Fix complete names of tests

Improve coverage tests
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
* feat: add AOT-safe ViewLocator with source-generated view dispatch

## New Features
- Add IViewLocator, DefaultViewLocator, ViewLocator, and ViewMappingBuilder runtime types supporting three-tier view resolution: generated dispatch → explicit mappings → service locator fallback.
- Add source generator that scans IViewFor<T> implementations at compile time and generates a type-switch dispatch function for AOT-safe view resolution.
- Add ViewRegistrationExtractor and ViewRegistrationInfo model for extracting view/view-model pairs from the compilation.
- Integrate ConfigureViewLocator into the builder pattern via IReactiveUIBindingBuilder, ReactiveUIBindingBuilder, and BuilderMixins.

## Tests
- Add 7 snapshot tests for ViewLocatorDispatchGenerator covering single/multiple views, abstract exclusion, private/missing constructors, deduplication, and non-IViewFor classes.
- Add 31 runtime tests for DefaultViewLocator, ViewLocator, ViewMappingBuilder, ViewLocatorNotFoundException, and builder integration.
- Add TestExecutor infrastructure following ReactiveUI's executor pattern for clean test isolation.

* fix: minor corrections to ViewLocator implementation

- Update BindingGenerator XML doc to mention all three pipelines (A, B, C)
- Make DefaultViewLocator _lock instance-scoped instead of static since it only guards the per-instance _mappings dictionary
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
)

## View Registration Attributes
- Add ExcludeFromViewRegistration, SingleInstanceView, and ViewContract attributes to ReactiveUI.Binding
- Source generator reads attributes to skip excluded views, emit singleton caching, and generate contract-aware dispatch
- Add CreateMappingBuilder() factory method on DefaultViewLocator

## Generator Improvements
- Rewrite ViewLocatorDispatchGenerator to use raw string literals ($$""" pattern)
- Deduplicate by (ViewModel FQN, Contract) pair instead of ViewModel FQN alone
- Remove redundant IEquatable<Self> from all sealed record model types

## Test Coverage
- 100% line and branch coverage maintained

* fix: ViewLocatorDispatch contract ordering, thread safety, and defensive improvements

- Group dispatch branches by ViewModel type so contract-specific checks
  are emitted before the default branch (prevents shadowing)
- Escape contract strings with SymbolDisplay.FormatLiteral for safe codegen
- Use Interlocked.CompareExchange for thread-safe singleton view caching
- Fix strategyDoc for SingleInstanceView without parameterless constructor
- Move [ExcludeFromViewRegistration] check inside IViewFor<T> guard to
  prevent EnsureNotNull crash in compilations without ReactiveUI.Binding
- Add tests for grouped dispatch and contract-only dispatch (100% coverage)
#18)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
#27)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
@codecov

codecov Bot commented Oct 26, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 99.56750% with 14 lines in your changes missing coverage. Please review.
✅ Project coverage is 98.94%. Comparing base (7b1644c) to head (323092f).

Files with missing lines Patch % Lines
...ding.Shared/Bindings/ObservableAsPropertyHelper.cs 94.21% 11 Missing ⚠️
...alyzer/Analyzers/ToPropertyInitialValueAnalyzer.cs 92.10% 3 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #524      +/-   ##
==========================================
+ Coverage   96.75%   98.94%   +2.18%     
==========================================
  Files          76      470     +394     
  Lines        3824    18808   +14984     
  Branches      544     2777    +2233     
==========================================
+ Hits         3700    18609   +14909     
- Misses        124      199      +75     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

…tor benchmark

- Add WhenAny, WhenAnyObservable, InvokeCommand and BindInteraction call sites to the generation benchmark.
- Update the adapter benchmark to the helpers the generator now references.
- Emitters and the view and property generators take arrays; Collect() results are read with RoslynArray.Unwrap.
- Stages that cache collected members hold EquatableArray.
- ImmutableArray stays only for the arrays Roslyn's symbol APIs return.
- Remove the SONAR_ENABLED gate and keep the existing ReactiveUI.Binding.SourceGenerators project key.
…benchmarks

- Benchmark harness: async trace copy, no forced collection, BenchmarkDotNet logger instead of Console.
- Samples: report through Trace, and make the members CA1822 flagged static.
- Drop the NoWarn entries and editorconfig relaxations for them.
- restore RXUIBIND002, RXUIBIND005 and RXUIBIND020 with their tests and benchmarks
- restore RXUISG0016 (analyzer and code fix) and RXUISG0022 with their tests
- benchmark the two restored attribute analyzers
- write the MAUI sample's output through Trace.TraceInformation
- update CLAUDE.md: shipped diagnostics are never removed without a request
- move the repeated Dispose into a helper so the example keeps its output
- the generator finds the public field by name, so WhenAnyValue on it observes the dependency property
- test the delivery gate under contention by holding it through a blocking comparison
- test the all-options constructor and a callback that throws with nothing queued
- drop the unreachable queue branch when the initial value is published
- test the ToProperty initial-value analyzer's candidate matching directly
- show shared disposal through a second owner instead of a repeated Dispose call
- drop the binding-v tag stream that no release step created
- one v* stream and a 10.0 floor give both families the same computed version
- the release workflow's override reaches every project again
- restore the binding-v tag stream with its own release workflow and solution filters
- give the attribute packages their own filter and release workflow
- each release computes its version from its own tags, so nothing is tagged by hand
@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
0.0% Coverage on New Code (required ≥ 80%)

See analysis details on SonarQube Cloud

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants