feat(mix_generator): add @MixWidget annotation for Styler-driven widgets#909
feat(mix_generator): add @MixWidget annotation for Styler-driven widgets#909tilucasoli wants to merge 15 commits intomainfrom
Conversation
Design doc for a new @MixWidget annotation and generator that turns top-level Stylers (variable or function form) into StatelessWidget wrappers by reusing each Styler's hand-written call() method as the widget entry point. Includes stylable: true toggle for injectable Stylers. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Task-by-task TDD plan covering the annotation addition, pure-Dart param model + merge logic, string emitter, analyzer-facing generator, validation cases, and build.yaml wiring for mix_widget_generator. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds Card, CardV, CardH, H1, and Badge wrappers in box_widget.dart that exercise the variable form, function form, positional `call(...)`, and `stylable: true` paths of the new generator end-to-end against real Mix Stylers.
|
To view this pull requests documentation preview, visit the following URL: Documentation is deployed and generated using docs.page. |
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
tilucasoli
left a comment
There was a problem hiding this comment.
Code review
Clean separation between the pure-Dart MixWidgetParam / MixWidgetBuilder and the thin analyzer shim, with proportional unit + integration coverage. A few correctness concerns worth addressing before merge — left inline.
Not blocking, worth considering
- Build scope in
packages/mix/build.yaml:mix_generatoris narrowed tolib/src/specs/**but there's no analogous entry formix_widget_generator, so once downstream usages appear it will scan all oflib/**on every build. No correctness impact; adding a matching scope keeps incremental builds predictable. - Annotation target:
MixWidgethas no@Target({...}). You enforce top-level variable/function at generate time, but declaring@Target({TargetKind.topLevelVariable, TargetKind.function})would also surface misuse at the call site. - Missing test cases: inherited
call(...)from a base Styler, source function declaring akeyparameter, generic Styler type parameters.
Already addressed
The demo @MixWidget usage in box_widget.dart and the barrel-import swap were reverted in bb0db2ce9 — good call, those were my biggest concerns.
| '@MixWidget Styler ${styler.name} must declare a `call(...)` method.', | ||
| element: annotated, | ||
| ); | ||
| } |
There was a problem hiding this comment.
_findCall only inspects declared methods.
InterfaceElement.methods returns declared members only, not inherited ones. A user Styler that inherits call from a base class — e.g. class MyCard extends BoxStyler {} with no override — will fail validation with "must declare a call(...) method" even though invoking it works fine at runtime.
Fix: walk the superclass chain for call, or use an inherited-member lookup (styler.lookUpInheritedMethod('call', ...) / equivalent). Worth a regression test alongside.
| } | ||
| InterfaceType? current = type; | ||
| while (current != null) { | ||
| if (current.element.name == 'Style') { |
There was a problem hiding this comment.
Matching Style by plain name is too loose.
Any class named Style in any library will satisfy this check. Low-likelihood collision in practice, but trivial to tighten — compare current.element.library.uri against the mix package, or ask the type system whether the type is assignable to mix's Style<dynamic>.
|
|
||
| MixWidgetParam _convertFormalParam(FormalParameterElement p) { | ||
| return MixWidgetParam( | ||
| name: p.name!, |
There was a problem hiding this comment.
Minor: analyzer ≥7 allows null names on some synthetic/wildcard parameter paths. A bang works for typical user-authored code but consider throwing InvalidGenerationSource with a clearer message if p.name == null — otherwise a hit here surfaces as an opaque null-check error.
|
|
||
| final callMethod = _findCall(stylerClass, element); | ||
| final callParams = _convertCallParams(callMethod); | ||
| const sourceParams = <MixWidgetParam>[]; |
There was a problem hiding this comment.
sourceParams is hardcoded to empty for the variable form, which makes the stylable collision guard below a call-params-only check. It's correct (a variable has no params), but a one-line comment saying why would save a future reader a grep.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Walks the superclass chain in _findCall so a Styler that inherits
`call` from a base class (e.g. `class MyCard extends BoxStyler {}`)
is accepted instead of failing with "must declare a call(...) method".
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Previously any class named `Style` in any library would satisfy the superclass walk in `_resolveStyleClass`. Require the matched element to live under `package:mix/` so unrelated `Style` types don't spuriously qualify. Tests now provide a synthetic `mix|lib/mix.dart` asset declaring `class Style<T>` and import it via `package:mix/mix.dart`. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Related issue
None.
Description
Adds a new
@MixWidget('Name', {stylable})annotation that generates aStatelessWidgetwrapper around a top-level Styler — either a variable holding a Styler, or a function that returns one. The generated widget's constructor mirrors the source's parameters merged with the Styler's hand-writtencall(...)signature (per-param positional/named/required preserved verbatim), and itsbuildmethod invokes thatcall(...)with the widget's fields. Whenstylable: true, the wrapper also accepts an optionalstyleparameter and merges it into the annotated Styler before invokingcall(...).Generated output examples
1. Variable form
Input:
Generated:
2. Function form (source params merged with
call(...)params)Input:
Generated:
3.
stylable: true(injectsstylefield andmerge(style))Input:
Generated:
4. Positional
call(...)keeps positional on the widget constructorInput:
Generated:
Changes
MixWidgetannotation inpackages/mix_annotations/lib/src/annotations.dart.MixWidgetGeneratorbuilder inmix_generator(analyzer-facing class + pure-DartMixWidgetParammodel +MixWidgetBuilderstring emitter), wired intobuild.yamlas a user-facing builder (auto_apply: dependents, nolib/src/specs/**scope restriction).Style<T>targets, Stylers withoutcall(...), andstylable: truewith astyleparam on either source or call signature.stylable/ positionalcall(...)/ required-nullable cases, and validation error-path tests.Card,CardV,CardH,H1,Badgewrappers added tobox_widget.dartexercising the annotation end-to-end.docs/superpowers/.Review Checklist
mix_generatorsuite passes (192/192) andmelos run ciis green.Additional Information (optional)
`melos run analyze` reports a pre-existing collision in `mix_tailwinds` involving the demo `H1` symbol from `box_widget.dart`; the new generator code itself analyzes clean. Worth a follow-up to either rename the demo or scope it before merge.