solid_generator 3.0.0-dev.4
solid_generator: ^3.0.0-dev.4 copied to clipboard
Solid source-to-lib code generator for Flutter reactive state.
3.0.0-dev.4 #
- FIX: The builder's fast bailout for files with no
@Solid*annotation and noProvider(...)/.environment<T>()call site now runs the same cheap cross-file registry seeding_populateCrossFileTypesalready implements (#104/#105) against the file's UNRESOLVED parsed unit before giving up. A PURE CONSUMER — a file that holds a@SolidState-bearing class through plain constructor injection or an instance field, but carries no@Solid*annotation and no provider call site of its own — previously matched neither hint text, so the file was never even parsed and the cross-file seeding never ran for it: every read through the injected field (_authRepository.session) stayed silently un-lowered. Reproduced in a real app as a generated authentication bypass, wheredart fix'sdead_code/unnecessary_null_comparisonpasses collapsed a router guard's null check because the un-lowered field looked always-non-null (#106). Honest cost accounting: a hint-free file now always pays the syntactic parse (previously skipped entirely for such files); only a file with no custom-typed fields/params at all (just core-SDK/self-declared types) avoids the import walk and any resolver call, and that narrower path is what stays zero-cost — not "no work at all". A file whose field/param DOES name an externally-declared type pays a bounded import walk (bounded by the file's own import count), and a same-named-but-non-reactive match along the way (thecross_file_constructor_injected_no_stateshape) does not stop that walk early — the probe's registries are threaded into the pipeline's own cross-file pass so the walk still only runs once per file. Lowering itself is applied by a new, narrower pass (collectPureConsumerCrossFileEdits) that only rewrites.valuereads on plain-class members — it does not addimplements Disposableor synthesizedispose(), since a pure consumer owns no reactive member of its own to dispose. The builder runs this lowering BEFORE theProvider/.environmentauto-dispose pass, not after: lowering depends onvalue_rewriter.dart's tier-1Expression.staticTyperesolution to type receivers the AST-only tiers can't (e.g. a for-in loop variable), so it needs the still-valid resolved unit the dispose pass's text edits would otherwise invalidate; the dispose pass, by contrast, degrades gracefully to its AST-only tiers on a re-parsed, unresolved unit. Both shapes originally named here as residual gaps are now closed. A pure-consumerStatelessWidget/State<X>gets the same.valuelowering plus reactive rebuild through a sibling pass,collectPureConsumerWidgetEdits: it reusesrewriteBuildMethodverbatim on the class'sbuild()method — the exact SignalBuilder-wrap-placement machinery an@SolidEnvironment-consuming widget'sbuild()already goes through — with no widget-class Stateless→Stateful lift, since a plain constructor-injected field needs noBuildContext(unlike@SolidEnvironment, which needscontext.read<T>()). The plain-class pass and the widget pass are unified bylowerPureConsumers: both collect their edits against the SAME pristine source text and resolved unit, and the two edit lists are merged and applied together in a single text transformation — safe because the two passes visit disjoint class-kind sets (plainClassvsstatelessWidget/stateClass), so their edit ranges never overlap. (An earlier draft of this fix ran the two passes sequentially, re-parsing the widget pass's OUTPUT for the plain-class pass whenever the widget pass had edited anything — which silently dropped resolved-unit-dependent receiver resolution, e.g. a static-holder read, for a plain-class guard sharing a file with a widget pure consumer; fixed before release by collecting both edit sets up front instead of reordering.) Static-field-mediated DI (static final AuthRepository instance = …;, consumed elsewhere asHolder.instance.session) is also now recognized: the_populateCrossFileTypesDI-seeding loop'sif (member.isStatic) continue;guard carried no stated rationale anywhere in the #104/#105 history — it simply mirrored@SolidState's own unrelated instance-only restriction — and is removed. Receiver resolution needed no new code at all:Holder.instance.sessionparses as aPropertyAccesswhose target (Holder.instance) already resolves through the existing tier-1Expression.staticTypecheck toAuthRepositoryonce the type is seeded, verified empirically againstpackage:analyzerdirectly. Theflutter_solidartimport needed for a widget pure consumer'sSignalBuilderwrap is spliced in — or, if the file already importsflutter_solidartwith ashow/hidecombinator that doesn't exposeSignalBuilder(e.g. a pre-existingshow Signalfor an unrelated hand-rolled signal), REPAIRED in place — based on the lowering pass's own report of whether it emitted a wrap, not a substring scan forSignalBuilder(in the assembled output (which both risked a false positive on a preserved source comment and couldn't distinguish an import that exposes the name from one that doesn't).
Two narrower, adjacent gaps close partially in the same pass. The bare-super.x gap from dev.3 (below) is now closed on the RESOLVED path only: a file that already enters the main lowering pipeline for another reason (its own @Solid* annotation, or a Provider/.environment<T>() call site) consults SuperFormalParameter.declaredFragment.element.type when the AST carries no explicit type annotation, seeding wantedTypes the same as if the type had been written out — verified empirically that the resolved element's type IS populated even without a source-level annotation. This is moot for a PURE consumer whose only link to the cross-file class is a bare super.x: such a file carries no annotation and no provider hint, so it takes the no-annotation fast path's UNRESOLVED syntactic probe first, finds nothing to seed, and short-circuits to a verbatim copy before any resolved unit is ever requested — that narrower shape remains a known, accepted gap. Separately, value_rewriter.dart's cross-class receiver resolution gained a fourth fallback tier: when an instance field is declared with no type annotation at all (final _service; — infers dynamic, since a same-named constructor field-formal parameter's explicit type does NOT propagate back onto the field's inferred type, confirmed empirically against a real resolved unit), the resolver now falls back to a matching FieldFormalParameter's explicit type, or an initializer-list-assigned SimpleFormalParameter's explicit type; multiple constructors supplying conflicting types bail to unresolved rather than guess.
3.0.0-dev.3 #
- FIX: The cross-file class registry (
_populateCrossFileTypes) now seedswantedTypesfrom the declared type names of every class's instance fields and constructor parameters, not just@SolidEnvironmentfield types andProvider(...)/.environment<T>()call sites. A file that only constructor-receives a@SolidState-bearing class — the plain DI shape, e.g.CustomersRepository({required AuthRepository authRepository})storingfinal AuthRepository _authRepository;, with no@SolidEnvironmentfield and no same-file.environment()/Provider()call site — previously leftclassRegistryempty for that type, so cross-class.valuereads through it (_authRepository.session) were silently un-lowered: no compile error, just an always-non-nullSignalobject thatdart fix'sunnecessary_null_comparisoncould collapse into dead code (#104). - FIX: The seeding above (and, defensively, every other seeding path into
wantedTypes) now mirrors Dart's own name-resolution rules instead of blindly matching on simple class name. A simple name that the CURRENT file itself declares as a class/enum/mixin is dropped from the wanted set before the cross-file import walk starts — a local top-level declaration always shadows a same-name import, so attributing an unrelated imported class's reactive members to it was provably wrong (e.g. a file with its own plainclass Addressplus an unrelated, unconnected@SolidState-annotatedclass Addressimported from elsewhere previously emitted a non-compilingaddress.line1.valueagainst the local class). Each import'sshow/hidecombinators are also now honored: an import that hides the wanted name, orshows a list that excludes it, can no longer be credited as that name's source, closing a second, narrower collision window. - FIX: Constructor parameters using the explicit-typed
super.shorthand (Foo(AuthRepository super.repo)) now seedwantedTypesthe same as a plain or field-formal parameter. The far more common baresuper.repo(no type written) is a known, accepted gap: the type isn't present in the source at that position at all — recovering it would require resolvingrepoagainst the superclass's matching field/parameter, which this syntactic AST walk does not do — so a bare-shorthandsuper.parameter still isn't a seeding source. - FIX: A field or constructor parameter declared with a generic collection type (
final List<AuthRepository> repos;) now seedswantedTypesfrom every type argument, at every nesting level, not just the outer container name (List). This lets the existing resolved-static-type rewrite tiers recognize collection-derived receivers whose element type is Solid-lowered — covered by golden fixtures for afor (final r in repos) { r.field }loop variable and arepos.first.fieldreceiver (same resolved-type mechanism); deeper nesting (Map<K, List<T>>and beyond) is mechanically identical but likewise untested. Container names themselves (List,Map,String, and otherdart:core/dart:asyncSDK types) are now filtered out of every seed in the new constructor-injection/field loop before being added, which also fixes a performance regression where annotation-blind seeding of primitive types on nearly every field defeated thewantedTypes.isEmptyfast-path.
3.0.0-dev.2 #
- FIX: Cross-class
.valuerewrite now resolves constructor-injected instance fields (final AuthRepository _authRepository;), not just method/function parameters and@SolidEnvironmentfields. Previously a bare instance field receiver silently kept its unlowered form, producing always-true null checks and compile errors against the unboxedSignalpayload. Also covers athis.-prefixed receiver (this._authRepository.session) —this.<field>parses as a distinct AST shape from the bare_authRepository.sessionform and previously fell through unrewritten. - FIX:
.environment()/Provider(...)dispose auto-injection is now type-aware —dispose: (context, provider) => provider.dispose()is injected per a four-tier decision: (1) the created type provably hasdispose()(own declaration, or inherited — including transitively through a same-file base-class chain); (2) the created type is@Solid*-annotated, same-file OR cross-file (every Solid-lowered class synthesizesdispose()); (3) the type's declaration is visible and shows neither → skip, no injection; (4) the declaration isn't visible anywhere this check looked (typically a cross-file, non-@Solid*type) → inject anyway, preserving the pre-type-aware default so a wrong guess fails loudly at compile time rather than silently leaking a resource, withdispose: nullas the explicit opt-out. Previously the injection was unconditional and made source-layer typechecking fail with a compile-timeundefined_methoderror on.dispose()for any type with nodispose()method; omittingdispose:for such a type now injects nothing (same as an explicitdispose: null) instead of a load-bearingdispose: nullworkaround. This changes generated output for existing call sites that omitdispose:on a dispose-less type — the compile error goes away. - FIX: The dispose auto-injection above now also recognizes a cross-file
@Solid*-annotated type provided via.environment<T>()/Provider<T>(...)even when nothing in the providing file consumesTthrough an@SolidEnvironmentfield. Previously such a controller — the dominant real-world shape, e.g. a top-levelmain()that provides a controller it never itself consumes — got NO dispose injection at all: its synthesizeddispose()only exists after lowering, which was invisible to every check this rewriter had. This was a silent resource leak, not a compile error, because the call site was already valid Dart (dispose:simply absent).
3.0.0-dev.1 #
- BREAKING: Raise the Dart SDK lower bound to
^3.10.0to target the solidart v3 ecosystem. - CHORE: Upgrade
analyzerto^12.0.0and adapt to its reshaped class/enum declaration AST (name and members moved ontonamePart/bodyfor primary constructors). - CHORE: Bump
solid_annotationsto^3.0.0-dev.1,dart_styleto^3.1.8, andbuild/build_runner/build_test.
2.0.0+1 #
- DOCS: Update README installation.
2.0.0 #
- FEAT: SignalBuilder placement,
.valuerewrite, dispose synthesis, StatelessWidget→StatefulWidget split. - FEAT: Computed synthesis from getter form of
@SolidState. - FEAT: Fine-grained reactivity with untracked-read semantics (
.untracked). - FEAT: Support the
untracked(() => …)function form for untracked writes inside reactive bodies (e.g. writing a collection signal in a@SolidEffectwithout a cyclic reaction). The call passes through toflutter_solidart'suntracked; inner reads still receive.valuebut are not tracked. Previously this form was rejected. - FEAT: Effect lowering with
initStatematerialization for State and plain-class targets. - FEAT: Resource lowering for Future/Stream with
.when()/.refresh()call-site preservation. - FEAT: Environment field synthesis with Provider-backed DI and cross-class chain rewrites.
1.0.3 #
- FIX: Missing
flutter_solidartimport in generatedmain.dartfile, if no reactive annotations are used.
1.0.2 #
- FIX: Generator not transpiling code correctly in some cases.
1.0.1 #
- FIX: Remove Flutter SDK.
1.0.0+2 #
- CHORE: Add
fluttersdk to resolve score on pub.dev.
1.0.0 #
- Initial version.