Feat/ts optimizer package - #8872
Open
scottweaver wants to merge 1225 commits into
Open
Conversation
…QwikDev#350) Post-merge wrap-up: main -> 5b301e7, full baseline 1160 -> 1172, three merged branches deleted, qds dist re-synced, OPTIMIZER.md audit clean (contract-preserving JSX partition fix). Progress log trimmed to 10. Claude-Session: https://claude.ai/code/session_01CJEPKkGz766mEd1Y7pHi8v Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…Dev#351) sync$ bodies stay inline as _qrlSync(<live fn>, "<serialized string>"). The live fn is TS-stripped by the downstream module/segment transform, but the serialized string is a JS string literal that pass never descends into — so annotations like (e: KeyboardEvent): void survived verbatim. At runtime the framework inlines that string into SSR HTML and eval's it, where a leaked annotation is a SyntaxError that silently kills the inline sync handler. Transpile the text used for the serialized string through oxc-transform (wrapped as a const-declaration, then sliced back out); the first, live argument stays raw so existing snapshot formatting is preserved. The strip is a proven no-op after minify for TS-free bodies, so it is always applied rather than gated on a flag. Verification: typecheck clean; convergence 203/9 (same failing set); full suite 0 new failures, +9 new tests; the three existing sync$ snapshots stay byte-identical. Claude-Session: https://claude.ai/code/session_01CJEPKkGz766mEd1Y7pHi8v Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…ion (QwikDev#352) The pre-transformed `_jsxDEV(type, props, key, isStatic, source, self)` path in `buildJsxSortedCall` unconditionally sliced `args[2]` as the key, so every element emitted the dev form's `undefined` key placeholder. The old `isNested` gate was dead code for dev-form input (always >= 3 args). Nested components with `undefined` keys break runtime reconciliation (`insertBefore` crash). New keying rule: a component element is always keyed; an HTML element is keyed only when it is not a direct jsx child of another jsx element — a render root, or an element reached through an expression boundary (`&&` / ternary / `.map` / an arrow or loop body). A direct HTML child takes `null`. An explicit key is reused only from the 3-argument `jsx(tag, props, key)` form; the 6-argument dev form's trailing key/source arguments are ignored. Replaced the tag-stack / nearest-HTML-ancestor machinery with a `markDirectJsxChildren` pass that records each call's direct jsx children at enter (before the reactive-binding early return, so purely-static components are covered). Renamed the `buildJsxSortedCall` `isNested` param to `isDirectJsxChild`. Convergence unchanged (203/9, same set; `example_qwik_react` and `example_parsed_inlined_qrls` still byte-identical). Adds 6 tests for the key-generation rule. No 7th dev-metadata arg is emitted (out of scope). Claude-Session: https://claude.ai/code/session_01CJEPKkGz766mEd1Y7pHi8v Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…/535 Done, OSS-532 parked (QwikDev#353) Claude-Session: https://claude.ai/code/session_01CJEPKkGz766mEd1Y7pHi8v Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…ect crash cleared (QwikDev#354) Claude-Session: https://claude.ai/code/session_01CJEPKkGz766mEd1Y7pHi8v Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…mode) (QwikDev#355) * fix(OSS-536): inject _useHmr into inline/hoist component bodies (hmr mode) In hmr mode the optimizer injected `_useHmr(devFile)` into `component$` bodies only on the segment (client) strategy path (post-process.ts). The inline/hoist strategy — which the dev SSR render uses — emitted the body with no HMR hook. `_useHmr` is a Qwik hook: its presence changes a component's serialized hook/vnode layout. The SSR render (inline/hoist, no hook) serialized ref offsets one way; the client (segment, with hook) re-rendered differently under interaction, so vnode ref offsets desynced → an intermittent `Missing refElement` assert under DOM churn on every ref-bound component (carousel, checkbox, checklist, collapsible, tree in qwik-design-system). TS-only; the SWC reference injects the hook in both paths and stays consistent. Inject `_useHmr` in the inline/hoist emission path (output-assembly.ts), gated identically to the segment path: `mode === 'hmr' && devFilePath && isAnyComponentCtx(ctxName)`. Extracted the block/expression body-injection core out of `injectUseHmr` and added `injectUseHmrIntoInlineBody` for the bare-body shape the inline/hoist path carries. Browser head-to-head (qwik-design-system dev SSR, TS :5200 vs SWC :5201): the refElement assert is gone across all 5 routes (carousel 3/3→0, checklist 4/4→0, checkbox 2/15→0, collapsible 3/3→0, tree 1/8→0); TS SSR HTML now emits q-d:q-hmr markers + q:template projection elements matching SWC. Convergence unchanged (203/9 — no inline/hoist fixture runs in hmr mode). Full baseline green. +5 regression tests. OPTIMIZER.md useHmr emission-site note updated. Closes OSS-536. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CJEPKkGz766mEd1Y7pHi8v * docs(state): record OSS-536 root-cause + fix Server/client HMR-hook desync fixed; refElement assert gone on all 5 ref-bound qds routes. Branch entry + measurements + progress log updated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CJEPKkGz766mEd1Y7pHi8v * refactor(OSS-536): drop what-comments from useHmr inline-injection path Remove the inline WHY comment on the inline/hoist _useHmr gate plus the two doc comments on injectHmrCallIntoFunctionBody / injectUseHmrIntoInlineBody added by the fix. The gate mirrors the uncommented segment-path gate; the function names state intent and the behavior is guarded by inline-hoist-usehmr.test.ts. The serialized-layout desync rationale stays in the fix commit message where it can't rot. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CJEPKkGz766mEd1Y7pHi8v * docs(state): record OSS-536 full post-fix qds re-sweep (clean) Full 29-route head-to-head re-sweep post-fix: 0 TS-only divergences across all 23 rendering routes; all 5 fixed routes crash->0; 9 prior-MATCH routes no regression; modal symmetric, 6 identical 404s. Refresh header, progress log (trim to 10), and the what-to-do-next section. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CJEPKkGz766mEd1Y7pHi8v --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…v#356) PR QwikDev#355 (OSS-536: inject _useHmr into inline/hoist component bodies in hmr mode) merged to main (c4091ea; baseline 2bdf578). Post-merge wrap-up: branch fix/oss-536-inline-hoist-usehmr deleted local+remote, qds dist rebuilt + re-synced, OSS-536 flipped to Done. STATE refresh: header PR-status + tail, branches table (main head SHA 2bdf578, OSS-536 row removed), measurements (main baseline 203/9 convergence + 1192 full, +5 absorbed), progress-log prepend + trim to 10, what-to-do-next OSS-536 marked Done. OPTIMIZER.md useHmr emission-site note already updated in the fix PR — no further audit. Claude-Session: https://claude.ai/code/session_01CJEPKkGz766mEd1Y7pHi8v Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…ves, wireMigration split (QwikDev#357) * refactor: remove dead code + an exact-dup helper (hygiene batch A) What: pure deletions + one import repoint across 7 files. - applyRawPropsTransformDetailed + RawPropsTransformResult — zero callers. - collectIdentifiersFromExpr — orphaned (collectSignalDeps uses the inlined fallbackCollectIdents); fixed its one stale doc reference. - findArrowIndex — whole chain dead: canonical in text-scanning.ts, its lone import + re-export in body-transforms.ts, no consumers repo-wide. - addBindingNamesToSet — one-line passthrough; call the already-imported addBindingNamesFromPatternToSet directly at all 4 sites. - partsHaveImport — import-collection.ts kept a byte-identical private clone of the body-transforms.ts export it already imports from. Isolation: removes silently-dead surface area and one duplicate that could drift from its source. No behavior change. Foundation: clears the "free wins" tranche ahead of the shared-primitive consolidation (batch B). Risk: deletions + one import repoint. typecheck clean; convergence 203/203 and full 1192/1192 baseline-passing tests unchanged. Net -91 LOC. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CJEPKkGz766mEd1Y7pHi8v * refactor: share edit/AST primitives across passes (hygiene batch B) What: replace hand-rolled duplicates with shared primitives. - 6 reverse-splice apply loops (dead-code, module-cleanup, const-propagation) → the existing applyReplacements; ranges are disjoint by construction at every site. - resolveConstLiterals / resolveConstLiteralsInClosure: extract the shared inner walk as collectConstLiteralValues(root, source, offset, …); the two now differ only by wrapper-offset vs source-absolute. - containsJsx / containsUnknownCall / containsImportedReference: fold the three deep-existential walks onto a new someAstDescendant primitive in ast/guards (sibling of someAstChild); drop containsJsx's vestigial array-input branch (no caller passes an array). - getPropertyName / staticPropName → shared memberStaticPropName; the two q_<sym>.w() capture-wrap arms (jsx.ts, jsx-props.ts) → shared isCaptureWrappingQrlCall. - inlineConstCaptures guard + isRealRef → the canonical isReplaceableIdentifierPosition (adds a params-position exclusion the hand-rolled copies lacked; params shadow, so it can only be more correct). Isolation: one home per operation instead of 2-6 drifting copies; the divergent-guard risk (each reverse-splice/position-guard copy could rot independently) is removed. Foundation: someAstDescendant + memberStaticPropName + the shared predicates are reused by later hygiene batches (C/D). Risk: behavior-preserving. typecheck clean; convergence 203/203 and full 1192/1192 baseline-passing tests unchanged. Net -97 LOC. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CJEPKkGz766mEd1Y7pHi8v * refactor: dedupe _noopQrl builders + dev-meta literal (hygiene batch C) What: the inline/hoist strategy had four near-identical _noopQrl string builders differing only in the LHS var name (q_<sym> vs sentinel q_qrl_<counter>), and the {file,lo,hi,displayName} dev-meta object literal was hand-emitted three times (twice here, once in dev-mode.ts). - Two private core builders buildNoopQrl(varName, symbolName) / buildNoopQrlDev(varName, symbolName, devMeta); the four public builders become one-line delegations. - NoopQrlDevMeta type + formatDevMeta() in dev-mode.ts, now the single source of the dev-meta literal for both _noopQrlDEV and qrlDEV. - Trimmed the redundant format-example doc blocks the delegations no longer need. Isolation: one place each defines the noop-QRL template and the dev-meta literal; output is byte-identical (snapshot-pinned). Foundation: formatDevMeta is the reuse point if a third dev-meta emitter appears. Risk: string builders are snapshot-pinned; convergence 203/203 and full 1192/1192 baseline-passing tests unchanged (byte-identical). Net -34 LOC. Deferred (NOT behavior-preserving as pure refactors — the "duplicates" have diverged): - Import-before-separator idiom (5 sites): guards genuinely differ (substring vs partsHaveImport-structured vs startsWith-import) and the canonical helper unshifts where these skip when no separator exists. - Same-file import ladder (2 sites): different data sources (reexportedNames set vs migrationDecisions lookup), skip conditions, and default-vs-reexport ordering. Unifying either would normalize behavior in edge cases the fixtures may not cover. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CJEPKkGz766mEd1Y7pHi8v * refactor: extract lib-mode marker predicate (hygiene batch D) What: the identical `name.length > 1 && name.endsWith('$')` lib-mode marker test appeared at 3 sites (rewrite/index, output-assembly, module-cleanup), each behind a paragraph-long comment explaining the same rationale. Extract isLibModePreservedMarker() in qwik/qrl-naming; the rationale now lives once on the predicate's doc, and the three site comments (which also referenced the reference implementation, against the comment convention) are gone. Isolation: one named predicate for "a $-suffixed marker other than the bare $"; the intent is stated once instead of re-explained per site. Foundation: the marker-name predicate is the reuse point if a fourth lib-mode gate appears. Risk: identical boolean, surrounding conditions unchanged — behavior provably preserved. convergence 203/203 and full 1192/1192 unchanged. Deferred (NOT behavior-preserving as pure refactors): - isHtmlTagName (4 copies): three genuinely different rules — any-lowercasing char vs strict ASCII a-z vs not-uppercase-letter. They disagree on digit/non-ASCII-initial tags; unifying normalizes behavior. - signal-result const/var classification (2 sites): diverge on param/in-loop handling — sharing would close a parity gap = change output. - createTransformSession adoption (5 sites): a parse-mechanism swap with wrapper prefix/suffix offset math to verify per site; too much risk layered on batch B's const-propagation changes for a boilerplate win. - marker->Qrl slice dup: the diagnostic caller may not want getQrlCalleeName's $/sync$ special-casing; needs verification, not a blind swap. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CJEPKkGz766mEd1Y7pHi8v * refactor: split wireMigration into a helper sequencer (hygiene batch E) What: wireMigration was a ~200-line function interleaving five concerns. Extract three named helpers, mirroring the generateAllSegmentModules sequencer pattern (OSS-343): - resolveMovedDeclImportDeps — a moved decl's import dependencies. - emitMovedDeclaration — marker-QRL vs plain-decl emission. - filterMigratedCaptures — drop migrated names from captures + reconcile the captures flag against paramNames. wireMigration is now a ~40-line orchestrator: auto-imports, pre-scan sets, the move loop (calling the two per-decl helpers), then the capture filter. Also folded the move-loop's reexport guard to an early-continue. Isolation: each concern is independently readable and named; the move loop reads as "resolve deps, emit" instead of 100 lines inline. The shared-mutation surface (captureInfo, movedQrlSymbols) is now threaded explicitly through helper parameters rather than closed over. Foundation: the per-decl helpers are the natural extension points for the deferred SWC transitive-ordering work (topological emit) noted in OPTIMIZER.md's migration deep dive. Risk: pure code motion. typecheck clean; convergence 203/203 and full 1192/1192 baseline-passing tests unchanged. Net -5 LOC. Also updates the OPTIMIZER.md line refs this move invalidated (wireMigration/buildDefaultStrategySegment/generateAllSegmentModules). The doc's other segment-generation.ts refs carry pre-existing cumulative drift and are left for a dedicated line-ref refresh. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CJEPKkGz766mEd1Y7pHi8v * refactor: drop self-evident docs on two batch-B helpers collectConstLiteralValues and isCaptureWrappingQrlCall each carried a doc block that only restated what the name + logic already say (the offset parameter's two modes are shown by its two call sites; the q_<sym>.w shape is exactly what the predicate checks, and the const-classification rationale belongs at the call site, not on the shape detector). Comment- only; no behavior change. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CJEPKkGz766mEd1Y7pHi8v --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Deletions (assert nothing / exact duplicate):
- extract.test.ts: two disambiguateExtractions stubs — one had zero
expect() calls, the other only asserted length on an input that by
construction produces no disambiguation. Both superseded by the working
"disambiguates multiple extractions with same display name" test.
- fnsignal-nonreactive-map.test.ts: a byte-identical duplicate of
fnsignal-computed-key.test.ts's computed-key test.
- segment-only-debug.test.ts: a debug harness ending in
expect(true).toBe(true) that ran a full-corpus transform each CI run to
console.log a report — an investigation artifact, not a test, and not
referenced by any doc.
Strengthened in place (kept the test IDs; each now fails on real
regression instead of passing vacuously):
- failure-families.test.ts: had zero assertions (a categorize-and-log
harness). Added corpus-present + complete-partition guards so a missing
or dropped snapshot surfaces. Kept as a test since docs reference it as
a secondary signal.
- transform.test.ts: two ctxKind tests asserted inside `if (seg)` guards
that vanish if extraction stops emitting the segment — now assert the
segment is found first. The const-vs-var test only checked a
`_jsxSorted("div"` call existed — now also asserts both prop values
survive. Dropped a reference-implementation comment.
- rewrite-parent.test.ts Test 9: named "parent-child relationship" but
only asserted extraction count — now asserts a nested extraction's
parent resolves to another extraction's symbol.
Baseline: the four deletions drop four passing IDs and the total. Mapped
.ci/baseline.json from the stored (CI-true) baseline — removed exactly
those four IDs, decremented full.total 1217 -> 1213 — rather than
regenerating locally (local totals carry +2 from the QWIK_HOME benchmark
skips). Verified: convergence 203/203, full 1188/1188, 1:1 with zero
unaccounted adds/removes.
Claude-Session: https://claude.ai/code/session_01CJEPKkGz766mEd1Y7pHi8v
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* refactor(OSS-540): extract plainQrlName primitive (finding QwikDev#6) What: extract the special-case-free `$`→`Qrl` slice into `plainQrlName`; `getQrlCalleeName` now layers its bare-`$`/`sync$` cases on top of it, and the C05 diagnostic export-name check uses `plainQrlName` directly instead of its own inline `slice(0,-1)+'Qrl'`. Why: removes the duplicated slice while preserving the diagnostic's exact current behavior — it intentionally does NOT want getQrlCalleeName's marker special-casing (a user `sync$`/`$` export must still be checked against `syncQrl`/`Qrl`, not `_qrlSync`/``). Naively sharing getQrlCalleeName would have changed that; the primitive avoids the change. Foundation: resolves the OSS-540 "marker→Qrl slice dup" finding as a safe extraction rather than a behavior-changing merge. Risk: none — pure extraction. Typecheck clean; +3 plainQrlName pins; 42/42 in rewrite-calls + diagnostics suites, unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CJEPKkGz766mEd1Y7pHi8v * refactor(OSS-540): unify raw-name HTML predicate on isHtmlElement (finding QwikDev#3) What: point segment-generation's local `isHtmlElementName` at the canonical `isHtmlElement`, and drop the redundant a-z re-check in jsx-elements-core's `tagIsHtml` (its `tag.startsWith('"')` already encodes processJsxTag's isHtmlElement decision). Left jsx-children's `isComponent` alone — it is a distinct uppercase check; expressing it as `!isHtmlElement` would flip member-expression children (empty tagStr → HTML). Why: the audit flagged "4 copies" of the HTML-tag test, but only one was a divergent copy of the raw-name predicate. segment-generation used a strict ASCII a-z rule while the rest of the pipeline uses Rule L (first char equals its lowercase); they disagreed on digit/non-ASCII-initial tags. Unifying removes the latent inconsistency and leaves one canonical predicate. Foundation: resolves the OSS-540 isHtmlTagName finding — single source of truth for "is this raw tag name an HTML element". Risk: none on real input. The two rules agree on every valid tag (a-z lowercase → HTML, A-Z → component); they diverge only on digit/non-ASCII initials, which are invalid/absent in the fixture corpus. Convergence 203/9 unchanged; +4 edge-case pins (hyphenated, caseless-initial, empty). The 3 unrelated failures in segment/jsx dirs are pre-existing on main. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CJEPKkGz766mEd1Y7pHi8v * refactor(OSS-540): adopt createTransformSession in 4 body-reparse sites (finding QwikDev#5) What: replace hand-rolled wrapper-prefix + parseSync + offset-math in four body-text reparse sites with the canonical `createTransformSession`: - const-propagation.ts ×3 (resolveConstLiterals, inlineConstCaptures, propagateConstLiteralsInBody) — read AST positions, edit the unwrapped body via applyReplacements; now use session.program + session.offset. - lib-mode-collapse.ts substituteInnerQVarsInText — edits a MagicString on the wrapped source; now uses session.edits + session.toSource(), which also removes the fragile manual `.slice(offset, len-1)` suffix trimming. Why: every one of these reimplemented the same wrap/parse/offset idiom with its own prefix (`__rl__`/`__ic__`/`__pb__`/`__body__`) and its own filename, so their wrapped-source strings never matched the canonical session's — no shared parse memo, and four subtly different offset computations to keep correct. Routing them through createTransformSession gives one wrapper idiom and lets repeated parses of the same body version share a cached parse. Foundation: resolves the OSS-540 createTransformSession-adoption finding for the clean-swap sites; single canonical body-reparse path. Deferred (documented): inline-body.ts's JSX-in-inline-body reparse keeps its hand-rolled wrapper — its prefix length escapes into the JSX transform's dev-info `sourcePosition.wrapperPrefixLen`, so adopting the session would have to thread session.wrapperPrefix.length through source-map positioning; bounded benefit, real source-map risk, left for a dedicated pass. Risk: none. parseWithRawTransfer (the session's parse) is exactly the parseSync(RAW_TRANSFER_PARSER_OPTIONS) these sites used; the offset shift (15/17 → 16) is applied consistently within each session. Typecheck clean; const-propagation + lib-mode suites 23/23; convergence 203/9 unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CJEPKkGz766mEd1Y7pHi8v * refactor(OSS-540): unify ensureCoreImports onto canonical import helpers (finding QwikDev#1) What: ensureCoreImports now uses `partsHaveImport` for its already-imported guard and `insertImportBeforeSeparator` for the insert, replacing its loose `startsWith('import') && includes(sym)` guard and inline `splice(sepIdx, 0, ...)`. The dead no-separator early-return is dropped. Why: the audit flagged three divergent guards for "is this symbol already imported"; ensureCoreImports carried the loosest (substring-on-import-line), the one most prone to false positives. The single caller guarantees a `//` separator right before the call, so the early-return branch was unreachable and the guard's precision was the only real difference. partsHaveImport is behavior-identical here because the core symbol set has no substring collisions. Foundation: resolves the OSS-540 import-before-separator finding for the provably-safe site; ensureCoreImports now shares one insert idiom with the rest of import-collection/segment-codegen. Deferred (documented): the segment-codegen raw-splice sites keep their own no-separator fallbacks (append-import-then-separator / append-import / skip), which are genuinely distinct from the canonical unshift and can't collapse without changing behavior. One of them also has a `, sym`-substring guard that false-positives on _noopQrl vs _noopQrlDEV; correcting it is a real behavior change, left out of this behavior-preserving pass. Risk: none on real input. Guard is identical for the core symbols; no-sep branch was dead; the insert switch flips core-import order from reverse to forward, which is not semantically checked (imports are order-independent) and regresses nothing. Convergence 203/9, full 1188 baseline all pass; +4 ensureCoreImports contract pins. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CJEPKkGz766mEd1Y7pHi8v * refactor(OSS-540): share the same-file import ladder (finding QwikDev#2) What: extract `resolveSameFileImportName` (+ `formatSameFileImport`) as the single same-file-symbol import resolver, and route both prior copies through it: import-collection's `addSameFileImport` (which emitted import strings) and segment-generation's `resolveMovedDeclImportDeps` (which builds MovedImportDep objects). Why: the audit flagged these two ladders as divergent — different data sources (a migration-decision lookup vs a precomputed reexportedNames set) and opposite reexport-vs-default evaluation order. On inspection they encode the same decision: the reexport predicate is identical (`reexport && !isExported` at both sites), and the reexport/default arms are mutually exclusive (a default export is always isExported, so it can never enter the reexport arm), which makes the order difference unobservable. Unifying makes that equivalence explicit and removes the drift risk of two ladders that must stay in lockstep by hand. Foundation: one canonical same-file import resolution; the two callers now differ only in how they format the shared result (string vs dep object). Risk: none. The resolver reproduces every arm of both ladders; the order switch is unobservable by the mutual-exclusivity argument above. Typecheck clean; convergence 203/9, full 1188 baseline all pass; +8 resolver/formatter pins covering all four arms. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CJEPKkGz766mEd1Y7pHi8v * fix(OSS-540): share _fnSignal deps-const classifier, close _jsxDEV param gap (finding QwikDev#4) What: extract `fnSignalDepsAllConst` (jsx.ts) as the single deps-const predicate for _fnSignal prop classification, and route both classifiers through it — the raw-JSX path (jsx-props `processProps`, two call sites) and the pre-transformed `_jsxDEV` path (jsx-call-transform `classifyProp`). Why (behavior change): `classifyProp` was missing the raw path's component-parameter arm — a dep that's a closure parameter counts as const-eligible on a *component* element (not on HTML). So on the `_jsxDEV` path a param-dependent _fnSignal prop on a component element was wrongly placed in the var-props bag instead of the const bag. Confirmed by byte-level probe: before: _jsxSorted(Inner, { title: _fnSignal(_hf0, [props], …) }, null, null, …) after: _jsxSorted(Inner, null, { title: _fnSignal(_hf0, [props], …) }, null, …) Now the `_jsxDEV` path classifies identically to the raw-JSX path. The raw path's other arm (`!inLoop`) is not portable — the `_jsxDEV` path has no loop tracking — so classifyProp gains only the param arm. Scope: convergence is unaffected (every fixture is raw JSX; the `_jsxDEV` path is never exercised there). This path is covered by the jsxdev unit tier and real bundler builds (qds/router). A qds head-to-head is the definitive validation — recommended follow-up before relying on the qds output shift. Also: dropped the SWC-reference comments from the edited processProps block (the param/component rationale now lives once on fnSignalDepsAllConst's doc). Risk: full suite + convergence green (203/9, 1188 baseline all pass); no existing test regressed. +2 jsxdev pins: component element → const bag (the fix), HTML element → var bag (param arm correctly HTML-gated, unchanged). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CJEPKkGz766mEd1Y7pHi8v * refactor(OSS-540): drop what-comments on small helpers (PR QwikDev#359 review) Remove WHAT-comments that restated what the name and body already say: - plainQrlName: doc deleted — name + the getQrlCalleeName body below are self-evident, and a test pins the special-case-free contract. - fnSignalDepsAllConst: trimmed to the one non-obvious WHY (params are const-eligible only on component elements). - ensureCoreImports: doc deleted — name + body say it. Also restructured processProps's _fnSignal block: the two empty guard branches (each carrying a fall-through comment) collapse into named `isRawWhenNonConst` / `excludedFromHoist` booleans, so the code speaks for itself with no comments. Behavior-preserving: typecheck clean; convergence 203/9, full 1188 baseline all pass; no test changed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CJEPKkGz766mEd1Y7pHi8v --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…SS-537 hygiene sweep complete (QwikDev#360) - main → 36417d2; OSS-538/539/540 Done (PRs QwikDev#357/QwikDev#358/QwikDev#359 merged). - Header rewritten to the current state: OSS-540's six consolidations, with QwikDev#4 flagged as a `_jsxDEV`-path parity change (not a convergence flip) whose definitive validation is a still-pending qds head-to-head. - Measurements: 203/9 convergence, 1188 baseline all pass (+20 absorbed). - Branches: main head bumped; merged OSS-540 branch removed. - Progress log: prepend QwikDev#359 + QwikDev#357/QwikDev#358 entries, trim oldest 2. - Regenerated .cursor/rules mirrors (STATE.mdc; OPTIMIZER.mdc drift corrected). Claude-Session: https://claude.ai/code/session_01CJEPKkGz766mEd1Y7pHi8v Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…Dev#361) * refactor: de-any jsx-transform.test.ts AST-extraction helpers What: replace the two `any`-returning parse helpers (`parseExpr` plus its dead byte-identical twin `parseJsx`) with four typed helpers — `parseExpr`, `parseExprStatement`, `parseJsxElement`, `parseJsxFragment` — and collapse the 18 inline `(program.body[0] as any).expression` node extractions into single typed-helper calls. Drop the `collectScopeAwareBindings(program as any)` cast (assignable directly). 20 `any`s and one dead helper removed. Why (isolation): the helpers now return `Expression` / `JSXElement` / `JSXFragment` and assert node structure, so a malformed parse throws a named error instead of silently propagating `undefined` into the assertion — a strictly stronger test. Upholds the hard "no `any`" type-discipline rule. Why (foundation): the typed-extraction pattern is the reusable template for the deferred `tests/optimizer` de-any effort (STATE.md "Held / deferred") — the same `(body[0] as any).expression` idiom recurs across sibling specs (capture-analysis, jsx-arrow-prop-value, …) and adopts these helpers next. Risk: test-only, one file, touches no src. Verified behavior-identical — 62 pass / 2 fail before and after (the 2 are pre-existing SWC-parity known-failures, not in the baseline passing set, untouched). typecheck clean; local gate green: convergence 203/203, full 1208/1208 baseline- passing, zero regressions. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CJEPKkGz766mEd1Y7pHi8v * refactor: extract shared typed AST-parse helpers for tests What: promote the four typed parse helpers (`parseExpr`, `parseExprWithSource`, `parseJsxElement`, `parseJsxFragment`) from jsx-transform.test.ts into a shared `tests/optimizer/helpers/parse-nodes.ts` module, and adopt them in signal-analysis.test.ts — removing its last two `any`s (its own `{ node, source }` parse helper duplicated the pattern). Why (isolation): a second consumer appeared, so the helpers move to one home instead of being copied. One typed definition of "parse a string into an AST node", asserting structure at the boundary, now serves both specs. Why (foundation): this is the reusable surface the deferred `tests/optimizer` de-any effort (STATE.md "Held / deferred") builds on — sibling specs that share the `(body[0] as any).expression` idiom adopt these helpers next instead of re-deriving them. Risk: test-only, touches no src; the new module is a non-test `.ts` (adds no tests). Verified behavior-identical — jsx-transform 62/2, signal-analysis 25/1, unchanged before/after (the 3 fails are pre-existing SWC-parity known-failures outside the baseline passing set). typecheck clean; gate green: convergence 203/203, full 1208/1208, zero regressions. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CJEPKkGz766mEd1Y7pHi8v --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…lla filed (QwikDev#362) PR QwikDev#361 (test-infrastructure de-any) merged to main (d30c4ab): jsx-transform.test.ts + signal-analysis.test.ts de-anied, shared tests/optimizer/helpers/parse-nodes.ts extracted. Test-only; gate green (convergence 203/9 unchanged, full 1208 baseline all pass). - Bump Last updated + main head SHA (36417d2 -> d30c4ab) + measurements. - Prepend progress entry; trim oldest (Stranded-QwikDev#348). - Correct the "Held / deferred" de-any note: now tracked as umbrella OSS-541 + subs OSS-542 (tests/optimizer + oxc-walker) / OSS-543 (ast-compare oracle narrowing -- NOT mechanical as previously billed). - Regenerate .cursor/rules/STATE.mdc mirror. OPTIMIZER.md audit: PR touched only tests/ -- no trigger-checklist file, no update needed. Claude-Session: https://claude.ai/code/session_01CJEPKkGz766mEd1Y7pHi8v Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
De-any the low-risk test-spec track of the OSS-541 umbrella (test-only). What changed: - Typed the oxc-walker pattern in capture-analysis.test.ts (~19 sites) and loop-hoisting.test.ts: AstNode/AstFunction callbacks, this.skip() via the walker's contextual this, dropped every `program as any`. - capture-analysis.test.ts: unified the two near-identical dollar-arg helpers behind typed collectors (asClosureArg/collectDollarArgs/collectParentScopeIds/ collectImportedNames/buildDollarArgFacts), removing the duplication. - Extracted the duplicated AST position-stripping normalizer from convergence-breakdown + failure-families into tests/optimizer/helpers/ ast-normalize.ts (unknown + isRecord narrowing, no casts). - Typed findFirstCall/findFirstNode to return CallExpression/AstNode and throw on absence (the parse-nodes.ts convention); every callsite supplies valid input. - transform.test.ts: `as any` -> `as SegmentMetadataInternal`, the sanctioned test idiom already used in stripped-qrl-cleanup.test.ts for reaching the internal captureNames field. Beneficial in isolation: removes the actionable `any` from tests/optimizer, upholding the "any is never the right answer" rule; kills two duplicated `strip` normalizers; typed walker callbacks surface AST-shape mistakes at compile time. Foundation: extends the parse-nodes.ts typed-helper pattern (PR QwikDev#361); ast-normalize.ts becomes the shared normalizer for breakdown-style tests. Completes Sub A of OSS-541, leaving OSS-543 (ast-compare.ts oracle) as the only remaining de-any. Risk: test-only, behavior-preserving. Casts are erased at runtime; refactored helpers are semantically identical. Convergence 203/9 and full 1208 baseline all pass, 0 regressions; the 6 pre-existing transform.test.ts failures are unchanged (verified against main). Refs: OSS-542 (sub of OSS-541). Claude-Session: https://claude.ai/code/session_01CJEPKkGz766mEd1Y7pHi8v Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…subs landed (QwikDev#365) OSS-542 (QwikDev#363) merged to main (0e56468); OSS-543 (QwikDev#364) In Review. Bumped main SHA + Last verified; prepended the umbrella progress entry and trimmed the log to ~10. Regenerated the .cursor/rules/STATE.mdc mirror. Claude-Session: https://claude.ai/code/session_01CJEPKkGz766mEd1Y7pHi8v Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…v#364) * refactor(OSS-543): de-any the ast-compare differential oracle De-any the higher-risk track of the OSS-541 umbrella: the AST differential-comparison oracle (`src/testing/ast-compare.ts`, ~164 `any` sites across 71 normalizer functions), consumed by both the convergence suite and production `output-assembly.ts`. What changed: - Every code-level `any` replaced with typed narrowing. Node params -> `AstCompatNode`; recursive-clone/walk params + returns -> `unknown`; local accumulators -> `Record<string, unknown>` / `unknown[]`. - Three shared narrowing primitives carry the per-site work: reused `isAstNode` from `optimizer/ast/guards.ts`, plus local `isRecord` and `asArray` (returns the SAME array reference so in-place push/splice/ sort/index-assign still mutate the real tree) and `asString`. - Strict-oxc-typed helpers that actually receive the loose post-strip tree were retyped to `AstCompatNode`/`unknown` (`isReorderableDeclaration`, `stripFrameworkHelperImports` + its closures); the now-unused `Directive`/`ImportDeclaration`/ `ModuleExportName`/`Program`/`Statement` imports were dropped. - Removed a provably-dead `=== 'AssignmentPattern'` check in `inlineDestructuredBindings` (the preceding `!== 'Identifier'` break already covers it) rather than keep a type-defeating `: unknown` dodge. No `as` casts introduced (the sanctioned one-cast-at-the-boundary lives inside `guards.ts`'s `isAstNode`); narrowing is guard-based throughout. Beneficial in isolation: closes the last `any` hole in the test infrastructure and makes the oracle's dynamic AST access compile-time checked. Foundation: completes OSS-541. Risk: this is the differential oracle — a weakened comparison would silently mask convergence regressions. Verified BIT-IDENTICAL: the full pass/fail set is unchanged (before pass=1208 fail=25, after pass=1208 fail=25; 0 regressed, 0 newly-passing, 0 gained-fail, 0 lost-fail), confirmed by re-running the whole suite and diffing test-id sets. `pnpm typecheck` clean. Closes OSS-543 (sub of OSS-541). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CJEPKkGz766mEd1Y7pHi8v * refactor(OSS-543): drop doc comments on trivial asArray/asString helpers Per review: the one-line JSDoc on `asArray`/`asString` only restated the name + body (WHAT-narration). The names and one-line bodies are self-evident. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CJEPKkGz766mEd1Y7pHi8v --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…lla closed (QwikDev#366) OSS-543 (QwikDev#364) merged to main (b7fa34c); OSS-541/542/543 all Done. Bumped main SHA + Last verified; prepended the close-out entry and trimmed the log to ~10. Regenerated the .cursor/rules/STATE.mdc mirror. Claude-Session: https://claude.ai/code/session_01CJEPKkGz766mEd1Y7pHi8v Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…SWC parity) (QwikDev#367) * fix(OSS-471): MOVE single-consumer server$ binding into its segment (SWC parity) A module-level `const testServer$ = server$(…)` referenced only inside one nested `onClick$` was re-exported (`_auto_`) instead of moved into its single consumer, diverging from the reference optimizer. Four coupled fixes, each verified byte-for-byte against the reference on the canonical route shape: - Capture over-attribution: a parent extraction dropped module-level captures whose every occurrence falls inside a nested child's byte range. Gated to file-emitting strategies — under inline/hoist the child body stays inline (`q_X.s(IDENT)`), so the parent still references the name and the reexport must survive (e.g. `useStyle$(STYLES)`). - Escaped-name match in `tryBuildMarkerDeclMove` and the parallel `movedMarkerSymbols` detection: a `$`-suffixed decl's displayName is escaped (`testServer$` → `…_testServer_server`), so a raw match missed it — leaving a broken raw-text move and an un-demoted parent QRL binding. - Marker-QRL callee imports from the marker's real source (`@qwik.dev/router`), not hardcoded core. - PURE annotation on the moved wrap is per-callee (`componentQrl` pure, `serverQrl` not), via `needsPureAnnotation`. Reachable (non-stripped) output now matches the reference: parent emits a bare QRL registration, the handler segment is a self-contained `serverQrl(qrl(…))` move. The stripped variant registers via `_noopQrl` (a `qrl(()=>import(strippedChunk))` would resolve to the chunk's `null` export); its local var name is a known cosmetic divergence on an unreachable path. Three baseline tests that pinned the reexport behavior updated to the MOVE (IDs kept for the name-based gate); one focused strip-aware test added. Convergence-neutral (203/203); full suite 1208 pass, 0 regressions, +1 new. Refs: OSS-471 (sub of OSS-456). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CJEPKkGz766mEd1Y7pHi8v * chore(OSS-471): trim inline docs per review Apply the comment ladder to the OSS-471 diff: delete WHAT-narration, lean on self-documenting locals (`moduleLevelCaptures`, `usedOutsideAnyChild`) and `expect(...)` message args, and drop comments the tests already guard (the inline/hoist exclusion gate is pinned by the hoist-regctxname STYLES test). Only genuinely-irrecoverable one-line WHYs remain (the module-level capture invariant, the escapeSymbol match, the retained test-ID note). No behavior change — typecheck clean, affected tests + convergence unchanged. Refs: OSS-471 (sub of OSS-456). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CJEPKkGz766mEd1Y7pHi8v * chore(OSS-471): drop remaining inline comments; rename tests to match contract Remove every inline `//` comment from the OSS-471 diff — the code, the self-documenting locals, and the `expect(...)` messages carry the intent, and the rationale lives in the commit/PR/Linear. The two router-integration tests whose names described the old reexport behavior are renamed to state the MOVE contract directly (no explanatory comment needed); the two baseline IDs are mapped through in `.ci/baseline.json` (REGRESSION.md deliberate-migration). No behavior change — typecheck clean, convergence 203/203, full 1208 pass, 0 regressions (verified against the mapped baseline). Refs: OSS-471 (sub of OSS-456). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CJEPKkGz766mEd1Y7pHi8v --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
… landed (QwikDev#368) OSS-471 (single-consumer server$ binding MOVE, SWC parity) merged via PR QwikDev#367 → main (02b2610); OSS-471 Done, closing an OSS-456 sub (OSS-473 is the last open one). Bump Last-updated + measurements (convergence 203/9 unchanged; full 1209 after baseline auto-update), main-row head SHA, drop OSS-471 from the Backlog list, prepend the progress entry + trim to 10. OPTIMIZER.md audited — no update (bug-fix/type-internal; cited refs <50-line drift). Claude-Session: https://claude.ai/code/session_01CJEPKkGz766mEd1Y7pHi8v Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…migration hygiene) (QwikDev#369) Prepares the tree for migration into the Qwik core monorepo, where the SWC optimizer ships as a sibling package and "matches SWC" comments become actively misleading. Comment-only across 163 files (plus 5 test-name rewordings); no behavior change. - Comment volume 9,918 -> 2,617 lines (-74%): deleted WHAT-narration, section banners, numbered step markers, JSDoc that restated signatures, and test preambles (tests self-document via describe/it names). Kept only irrecoverable one-line WHYs (invariants, external-tool workarounds). - Scrubbed every reference-optimizer mention from comments AND test descriptions (~300 across 73 files): SWC, *.rs file:line refs, Rust fn names, @qwik.dev/optimizer, "matches SWC"/"SWC parity". Behavioral notes reworded as this optimizer's own contract. NAPI retained only where it names the native-binding ABI the public types conform to. The benchmark's functional SWC references (it loads and times the native optimizer) stay. - Renamed 7 test names that editorialized "matches SWC"/"NAPI parity"; the 7 IDs are mapped 1:1 in .ci/baseline.json (deliberate migration). Verification: oxc-based skeleton proof shows 163/168 changed files code-identical and the other 5 differ only in the intentional test-name strings; full suite 1234/1209/25 unchanged from the pre-sweep baseline; typecheck clean; baseline gate green (203 convergence, 1209 full). Claude-Session: https://claude.ai/code/session_01CJEPKkGz766mEd1Y7pHi8v Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Remove TS-Optimizer-repo-specific infrastructure that is redundant inside the Qwik monorepo (which provides its own): the .ci baseline-regression system and its scripts/, the .github workflows, .claude/.cursor agent rules, .planning docs, the agent-sync tooling, the root-only pnpm-lock.yaml, and the swc-reference-only pinned copy of the optimizer (packages/optimizer is the source of truth here). Also drop the agent-facing AGENTS.md/CLAUDE.md and the point-in-time RCA writeup. Package build/test stays self-contained (tsdown + vitest). History for every removed file is preserved in the grafted commits. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CJEPKkGz766mEd1Y7pHi8v
Reconcile package.json for its new home: drop the baseline/CI scripts (whose files were removed with the standalone infra), repoint repository/homepage/bugs to QwikDev/qwik with a `directory` field, and remove the package-level `packageManager` pin (the root owns it; its version differed from root). Add the package's dependencies to the workspace lockfile via pnpm install. Verified in-tree: typecheck clean, `tsdown` build emits dist, and the full vitest suite runs with a byte-identical pass/fail set to standalone (1209/1234 passing; the 25 failures are the pre-existing convergence/parity gaps, unchanged by the move). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CJEPKkGz766mEd1Y7pHi8v
…timizer Match the monorepo convention (packages/optimizer -> @qwik.dev/optimizer): drop the now-redundant `qwik-` prefix and scope the package under @qwik.dev. Moves the directory to packages/ts-optimizer (history preserved via git rename detection), updates package name + repository/homepage/directory metadata, the README, and the index.ts header, and regenerates the workspace lockfile. Verified in the new location: typecheck clean, build emits dist, full suite 1209/1234 passing (25 pre-existing convergence/parity failures unchanged). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CJEPKkGz766mEd1Y7pHi8v
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 join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
This set of changes sees the experimental Typescript optimizer from https://github.com/thejackshelton/TS-Optimizer to a Qwik sub-package project.