feat: add BindCommand and BindInteraction source-generated bindings#13
Merged
Conversation
## New Features Add compile-time source generation for BindCommand and BindInteraction, completing the core ReactiveUI binding API surface. **BindCommand** generates optimized command wiring at compile time: - Detects ICommand/CommandParameter properties on target controls (WPF, WinForms, MAUI buttons) and generates direct property assignment - Falls back to runtime CommandBinderService plugin system when the target control lacks standard command properties, preserving compatibility with custom or platform-specific controls - Supports custom event binding via toEvent parameter - Supports command parameter binding via expression or observable - Handles Enabled property synchronization for WinForms-style controls - Deep property path support for nested command properties **BindInteraction** generates compile-time interaction handler wiring: - Type-safe handler registration with TInput/TOutput generics - Proper IDisposable lifecycle management - CallerFilePath/CallerLineNumber dispatch (same pattern as other APIs) **Runtime library additions:** - Interaction<TInput, TOutput> with IInteraction interface - InteractionContext<TInput, TOutput> for handler communication - ICreatesCommandBinding interface for runtime command binding plugins - CommandBinderService for fallback command binding resolution - SerialDisposable for lifecycle management in generated code ## Maintainers **Source generator restructuring:** - Extracted MetadataExtractor into focused helpers under Helpers/: ObservationExtractor, WhenAnyObservableExtractor, BindingExtractor, CommandExtractor, InteractionExtractor, TypeDetectionExtractor, ExtractorValidation, SyntaxHelpers, SymbolHelpers, EventHelpers - Moved observation generators to Generators/Observation/ subfolder - Eliminated phantom IL branches by extracting inline pattern matching into named RoslynHelpers predicates - Removed impossible null guards (replaced with null-forgiving operator) - InvalidOperationExceptionHelper for defensive guards in extractors **Testing:** - Added NSubstitute dependency for mocking Roslyn interfaces - 100% line, branch, and method coverage across all 4 assemblies (5585 lines, 2108 branches, 677 methods) - 6981 tests passing across net8.0/net9.0/net10.0 - New helper unit tests: ExtractorValidationTests, CommandExtractorHelperTests, SymbolHelpersTests, SyntaxHelpersTests, EventHelpersTests, TypeDetectionExtractorTests, RoslynHelpersTests, AnalyzerHelpersTests - Snapshot tests for all BindCommand/BindInteraction scenarios - Runtime execution tests for BindCommand and BindInteraction
ChrisPulman
approved these changes
Mar 8, 2026
Add WinForms-specific command binding tests that exercise the source generator's output against real System.Windows.Forms.Button and ToolStripButton controls. Tests run only on Windows CI where WinForms types are available. - Reference source generator as analyzer in WinForms test project - WinFormsCommandScenarios: BindCommand calls with Button, ToolStripButton, expression params, observable params, and custom MouseUp event - WinFormsCommandBindingTests: 12 tests covering click execution, CanExecute, disposal, command changes, parameter passing - Fix placeholder test to run on all platforms (was excluded on Windows)
… binding tests Remove the ReactiveUI.Binding.WinForms.Tests project which required Windows to run. Replace with cross-platform runtime tests using structural test doubles (WinFormsLikeButton, WpfLikeButton) that exercise all three binding plugins: EventEnabled, CommandProperty, and DefaultEvent — running on all platforms. Adds 14 new tests covering EventEnabled (Click+Enabled sync, CanExecute, dispose, expression/observable params) and CommandProperty (Command/ CommandParameter property setting, reactive updates, command changes).
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #13 +/- ##
===========================================
+ Coverage 97.82% 100.00% +2.17%
===========================================
Files 164 118 -46
Lines 5556 1949 -3607
Branches 995 364 -631
===========================================
- Hits 5435 1949 -3486
+ Misses 30 0 -30
+ Partials 91 0 -91 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
|
This pull request has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What's Changed
This PR adds compile-time source generation for BindCommand and BindInteraction, completing the core ReactiveUI binding API surface. Both APIs follow the same CallerFilePath/CallerLineNumber dispatch pattern used by the existing WhenChanged, WhenAnyValue, and Bind APIs.
BindCommand
Wire an
ICommandon your ViewModel to a control with a single call — the source generator handles event subscription, command property assignment, and parameter binding at compile time.How it works under the hood:
The generator inspects the target control type at compile time and selects the best binding strategy:
Command property binding — If the control has a settable
ICommand Commandproperty (WPFButtonBase, MAUIButton, etc.), the generator assigns the command directly and optionally setsCommandParameter. This is the most efficient path.Event + Enabled binding — If the control has a default event (like
Click) and a settablebool Enabledproperty (WinFormsButton, etc.), the generator subscribes to the event, invokesICommand.Execute(), and synchronizesEnabledwithICommand.CanExecute().Event-only binding — If the control has a default event but no
Enabledproperty, the generator subscribes to the event and invokes the command.Runtime fallback — If no event or command property is detected at compile time (custom controls, third-party libraries), the generated code delegates to
CommandBinderService, which resolves anICreatesCommandBindingplugin at runtime. This preserves compatibility with platform-specific controls that the generator can't introspect.BindInteraction
Type-safe interaction handler registration, generated at compile time:
The generator resolves the
Interaction<TInput, TOutput>property type at compile time and wires the handler with full generic type safety — no runtime type casting.Runtime Library Additions
Interaction<TInput, TOutput>— the interaction type withHandle()method and handler registrationInteractionContext<TInput, TOutput>— carries input to handlers, collects output viaSetOutput()ICreatesCommandBinding— plugin interface for runtime command binding resolutionCommandBinderService— resolves the bestICreatesCommandBindingfor a given control typeSerialDisposable— replaces previous subscription on re-binding (used in generated code)Source Generator Internals
Restructured Helpers
Broke apart the monolithic
MetadataExtractorinto focused, single-responsibility helpers underHelpers/:ObservationExtractorWhenAnyObservableExtractorBindingExtractorCommandExtractorInteractionExtractorTypeDetectionExtractorExtractorValidationSyntaxHelpersSymbolHelpersEventHelpersInvalidOperationExceptionHelperNew Code Generators
BindCommandCodeGenerator— generates command binding dispatch with plugin selectionBindInteractionCodeGenerator— generates interaction handler wiringObservation generators moved
Moved platform-specific observation generators to
Generators/Observation/subfolder for clearer organization.Eliminated phantom IL branches
Extracted inline pattern matching from invocation generator predicates into named
RoslynHelpersmethods, eliminating 18 phantom uncovered branches that were artifacts of the C# compiler's IL generation.How We Tested
IMethodSymbol,IParameterSymbol,INamedTypeSymbol) in unit testsExtractorValidationTests,CommandExtractorHelperTests,SymbolHelpersTests,SyntaxHelpersTests,EventHelpersTests,TypeDetectionExtractorTests,RoslynHelpersTests,AnalyzerHelpersTests