@archie/devtools 0.0.27 → 0.0.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/collection-binding.d.mts +103 -8
- package/dist/collection-binding.d.ts +103 -8
- package/dist/collection-binding.js +136 -31
- package/dist/collection-binding.mjs +136 -31
- package/package.json +1 -1
|
@@ -21,8 +21,12 @@ import { types, PluginObj } from '@babel/core';
|
|
|
21
21
|
* data-agent-row {String(param?.id)}, on the row root
|
|
22
22
|
* data-agent-field static string OR a runtime expression
|
|
23
23
|
* (??/ternary field selection, D5.5)
|
|
24
|
-
* data-agent-field-state "mixed" | "unresolved"
|
|
25
|
-
*
|
|
24
|
+
* data-agent-field-state "mixed" | "unresolved" | "rendered"
|
|
25
|
+
* ("rendered": a formatter-CALL-wrapped
|
|
26
|
+
* value — the DOM only ever holds the
|
|
27
|
+
* FORMATTED string, ARCH-1065 M-COV FIX)
|
|
28
|
+
* data-agent-field-parts comma-separated field list (mixed leaves,
|
|
29
|
+
* or the single field of a "rendered" leaf)
|
|
26
30
|
* data-agent-field-slot text-node index of the dynamic part
|
|
27
31
|
* (label+value leaves, D6)
|
|
28
32
|
* data-agent-collection-order "stable" | "derived" (D18), on the row root
|
|
@@ -35,8 +39,12 @@ import { types, PluginObj } from '@babel/core';
|
|
|
35
39
|
* `.filter()/.slice()/.sort()/.map()` chain calls, through single-assignment
|
|
36
40
|
* (`const`, never reassigned) local-variable hops (bounded to
|
|
37
41
|
* `MAX_SOURCE_CHAIN_HOPS`), and through a `useMemo(() => …, deps)` /
|
|
38
|
-
* `React.useMemo(() => …, deps)` unwrap
|
|
39
|
-
*
|
|
42
|
+
* `React.useMemo(() => …, deps)` unwrap — EITHER the arrow-EXPRESSION-body
|
|
43
|
+
* shape (`() => expr`) OR a block body with exactly ONE return statement
|
|
44
|
+
* (`() => { …; return expr; }`, ARCH-1065 M-COV FIX, devtools 0.0.29); a
|
|
45
|
+
* block body with zero or 2+ returns (ambiguous control flow) is left
|
|
46
|
+
* unresolved, never guessed at (see `resolveSliceSource` /
|
|
47
|
+
* `findSingleReturnArgument`). A `.sort(` ANYWHERE in the chain marks the
|
|
40
48
|
* collection `data-agent-collection-order="derived"`; otherwise `"stable"`
|
|
41
49
|
* (D18). The identifier root must be exactly `slice` — this mirrors the
|
|
42
50
|
* runtime's `window.__slice` (M3); the codemod does not trace how a `slice`
|
|
@@ -72,6 +80,50 @@ import { types, PluginObj } from '@babel/core';
|
|
|
72
80
|
* specific field OR several), because no row identity is at stake — only
|
|
73
81
|
* further field leaves.
|
|
74
82
|
*
|
|
83
|
+
* ── Forwarding-component allowlist descent (ARCH-1065 M-COV fix) ──────────
|
|
84
|
+
*
|
|
85
|
+
* D5.4 only ever sees THROUGH a component whose own source is available
|
|
86
|
+
* (module-local, co-located in the same file). Kit/imported components
|
|
87
|
+
* (`Card`, `CardContent`, `StatusBadge`, …) are opaque to the AST — the
|
|
88
|
+
* plugin has no body to project into — but many of them forward unrecognized
|
|
89
|
+
* JSX attributes onto their own root DOM element (typically via
|
|
90
|
+
* `{...rest}`), which makes them valid attribute CARRIERS even though
|
|
91
|
+
* they're not valid PROJECTION targets. `forwardingComponents` (a plugin
|
|
92
|
+
* option, see `CollectionBindingPluginOptions`) names exactly which ones.
|
|
93
|
+
*
|
|
94
|
+
* Two independent mechanisms use that allowlist:
|
|
95
|
+
*
|
|
96
|
+
* - (a2) ROW ROOT: when the `.map()` callback's own returned JSX element
|
|
97
|
+
* is an allowlisted component (`<Card key={r.id}>…</Card>` as the row
|
|
98
|
+
* root itself), the row-identity attributes stamp directly onto that
|
|
99
|
+
* component usage — NOT a projection into a separate definition, since
|
|
100
|
+
* there is none to project into. The field walk continues into the
|
|
101
|
+
* component's own JSX children with the SAME binding scope the row
|
|
102
|
+
* callback already established.
|
|
103
|
+
* - (c2-ii) LEAF: when a component usage directly hugs a field expression
|
|
104
|
+
* with no host element beneath it (`<StatusBadge>{w.status}</StatusBadge>`),
|
|
105
|
+
* the field attribute stamps onto the component itself IFF its name is
|
|
106
|
+
* allowlisted; otherwise the leaf is left unstamped — a leaf is never
|
|
107
|
+
* given an invented carrier.
|
|
108
|
+
*
|
|
109
|
+
* Separately, and regardless of allowlist membership at all — walking a
|
|
110
|
+
* component's own JSX children costs nothing, since no attribute lands
|
|
111
|
+
* anywhere until a valid carrier (host, or allowlisted leaf) is actually
|
|
112
|
+
* found — (a1) DESCENT: the field walk always continues INTO a structural
|
|
113
|
+
* child that is a component usage, as long as that usage has its own JSX
|
|
114
|
+
* children in source. `<Card><span>{w.name}</span></Card>` reaches the
|
|
115
|
+
* `<span>` (a host, stamped unconditionally) even though `Card` itself is
|
|
116
|
+
* never a stamp target unless allowlisted. This is what makes (c2-ii) safe
|
|
117
|
+
* to leave allowlist-gated: a wrapper with a host descendant never needs the
|
|
118
|
+
* wrapper itself to carry anything.
|
|
119
|
+
*
|
|
120
|
+
* The allowlist is ALWAYS an explicit option, never a built-in default list
|
|
121
|
+
* baked into this plugin — kit-manifest is the single source of truth for
|
|
122
|
+
* which components forward attributes (`forwardsDataAttributes`), and the
|
|
123
|
+
* kit build (`build-d.mjs`) is what threads that list in. An absent/empty
|
|
124
|
+
* option is exactly today's fail-closed behavior: zero component-carried
|
|
125
|
+
* stamps, byte-identical to the plugin before this mechanism existed.
|
|
126
|
+
*
|
|
75
127
|
* ── Field resolution ────────────────────────────────────────────────────
|
|
76
128
|
*
|
|
77
129
|
* Reuses `meaningfulChildren` from `babel-plugin-editor-meta.ts` (the exact
|
|
@@ -95,10 +147,25 @@ import { types, PluginObj } from '@babel/core';
|
|
|
95
147
|
* the duplicated test would double-evaluate it across SSR and hydration,
|
|
96
148
|
* so the whole leaf is left unstamped instead;
|
|
97
149
|
* - `cond ? 'Literal' : 'Other'` where `cond` is itself a bare field
|
|
98
|
-
* member (a boolean-typed field driving two literal branches)
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
150
|
+
* member (a boolean-typed field driving two literal branches), OR a
|
|
151
|
+
* CALL-FREE comparison (`===`/`!==`/`==`/`!=`/`</<=`/`>`/`>=`) naming a
|
|
152
|
+
* field on either side (`r.status === 'Active' ? 'Pause' : 'Enable'`,
|
|
153
|
+
* D5.5 widening, ARCH-1065 M-COV FIX) → static field named by the
|
|
154
|
+
* resolved test field — the FE's typed controls (D7, a different
|
|
155
|
+
* mechanism, M3) read the boolean's/status's true/false or vocabulary
|
|
156
|
+
* rendering from `window.__slice`, not from this attribute. Nothing
|
|
157
|
+
* about the test is ever duplicated into the DOM here (only the field
|
|
158
|
+
* NAME is emitted), so there is no C3 double-evaluation concern for this
|
|
159
|
+
* shape — `containsEvaluation(test)` is still checked defensively;
|
|
160
|
+
* - a formatter CALL with exactly one argument that is itself a bare
|
|
161
|
+
* member/optional-member field (`Kit.date(r.updated_at)`,
|
|
162
|
+
* `money(auction.currentPrice)`, ARCH-1065 M-COV FIX) → the resolved
|
|
163
|
+
* field name, PLUS `data-agent-field-state="rendered"` and
|
|
164
|
+
* `data-agent-field-parts` (the same field name) — the DOM only ever
|
|
165
|
+
* holds the FORMATTED string, never the raw value, so this is marked
|
|
166
|
+
* the same "honest, non-invertible" way a `??`/boolean fallback is (D8);
|
|
167
|
+
* a multi-arg call, or a call whose sole argument isn't a bare field
|
|
168
|
+
* member, is left unstamped rather than guessed at;
|
|
102
169
|
* - a COMPUTED member (`a[expr]`, `expr` not a string literal) → no field
|
|
103
170
|
* name, `data-agent-field-state="unresolved"` (a positive "this is
|
|
104
171
|
* row-scoped data, but which field is unknown" signal — never silent).
|
|
@@ -127,6 +194,13 @@ import { types, PluginObj } from '@babel/core';
|
|
|
127
194
|
* that is itself slice-rooted gets discovered and stamped independently by
|
|
128
195
|
* the same Program-wide `.map()` scan, with its own entity/row identity.
|
|
129
196
|
*
|
|
197
|
+
* A JSXExpressionContainer wrapping `cond && <JSX/>` / `cond || <JSX/>` / a
|
|
198
|
+
* ternary IS a structural child, though (ARCH-1065 M-COV FIX, devtools
|
|
199
|
+
* 0.0.29, see `collectConditionalJsxBranchPaths`): the walker descends into
|
|
200
|
+
* whichever branch(es) are themselves a JSXElement, with the SAME scope —
|
|
201
|
+
* purely structural, the guard/test is never duplicated or stamped, only the
|
|
202
|
+
* JSX it conditionally renders is walked for further fields.
|
|
203
|
+
*
|
|
130
204
|
* ── Failure posture ─────────────────────────────────────────────────────
|
|
131
205
|
*
|
|
132
206
|
* Every resolution step is an explicit allowlist; anything the resolver is
|
|
@@ -148,6 +222,27 @@ import { types, PluginObj } from '@babel/core';
|
|
|
148
222
|
interface CollectionBindingPluginOptions {
|
|
149
223
|
/** Skip files matching this pattern. @default /node_modules/ */
|
|
150
224
|
fileExclude?: RegExp;
|
|
225
|
+
/**
|
|
226
|
+
* Component names known to forward unrecognized JSX attributes onto their
|
|
227
|
+
* own root DOM element (e.g. via `{...rest}`) — the exact set that makes
|
|
228
|
+
* a component usage a valid attribute CARRIER (row-root or leaf) even
|
|
229
|
+
* though it's opaque to the AST (see the "Forwarding-component allowlist
|
|
230
|
+
* descent" section above).
|
|
231
|
+
*
|
|
232
|
+
* Sourced from kit-manifest's `forwardsDataAttributes` flags by the kit
|
|
233
|
+
* build (`build-d.mjs` passes it) — this plugin NEVER hardcodes a default
|
|
234
|
+
* list here; that would be a shadow copy of kit-manifest's own truth, and
|
|
235
|
+
* would drift the moment a kit component's forwarding behavior changed
|
|
236
|
+
* without this plugin's knowledge. Synthetic lists are fine in tests, to
|
|
237
|
+
* keep the plugin testable standalone.
|
|
238
|
+
*
|
|
239
|
+
* Absent or empty = today's fail-closed behavior: no component ever
|
|
240
|
+
* carries a stamp, byte-identical to the plugin before this option
|
|
241
|
+
* existed.
|
|
242
|
+
*
|
|
243
|
+
* @default []
|
|
244
|
+
*/
|
|
245
|
+
forwardingComponents?: string[];
|
|
151
246
|
}
|
|
152
247
|
declare function babelPluginCollectionBinding({ types: t }: {
|
|
153
248
|
types: typeof types;
|
|
@@ -21,8 +21,12 @@ import { types, PluginObj } from '@babel/core';
|
|
|
21
21
|
* data-agent-row {String(param?.id)}, on the row root
|
|
22
22
|
* data-agent-field static string OR a runtime expression
|
|
23
23
|
* (??/ternary field selection, D5.5)
|
|
24
|
-
* data-agent-field-state "mixed" | "unresolved"
|
|
25
|
-
*
|
|
24
|
+
* data-agent-field-state "mixed" | "unresolved" | "rendered"
|
|
25
|
+
* ("rendered": a formatter-CALL-wrapped
|
|
26
|
+
* value — the DOM only ever holds the
|
|
27
|
+
* FORMATTED string, ARCH-1065 M-COV FIX)
|
|
28
|
+
* data-agent-field-parts comma-separated field list (mixed leaves,
|
|
29
|
+
* or the single field of a "rendered" leaf)
|
|
26
30
|
* data-agent-field-slot text-node index of the dynamic part
|
|
27
31
|
* (label+value leaves, D6)
|
|
28
32
|
* data-agent-collection-order "stable" | "derived" (D18), on the row root
|
|
@@ -35,8 +39,12 @@ import { types, PluginObj } from '@babel/core';
|
|
|
35
39
|
* `.filter()/.slice()/.sort()/.map()` chain calls, through single-assignment
|
|
36
40
|
* (`const`, never reassigned) local-variable hops (bounded to
|
|
37
41
|
* `MAX_SOURCE_CHAIN_HOPS`), and through a `useMemo(() => …, deps)` /
|
|
38
|
-
* `React.useMemo(() => …, deps)` unwrap
|
|
39
|
-
*
|
|
42
|
+
* `React.useMemo(() => …, deps)` unwrap — EITHER the arrow-EXPRESSION-body
|
|
43
|
+
* shape (`() => expr`) OR a block body with exactly ONE return statement
|
|
44
|
+
* (`() => { …; return expr; }`, ARCH-1065 M-COV FIX, devtools 0.0.29); a
|
|
45
|
+
* block body with zero or 2+ returns (ambiguous control flow) is left
|
|
46
|
+
* unresolved, never guessed at (see `resolveSliceSource` /
|
|
47
|
+
* `findSingleReturnArgument`). A `.sort(` ANYWHERE in the chain marks the
|
|
40
48
|
* collection `data-agent-collection-order="derived"`; otherwise `"stable"`
|
|
41
49
|
* (D18). The identifier root must be exactly `slice` — this mirrors the
|
|
42
50
|
* runtime's `window.__slice` (M3); the codemod does not trace how a `slice`
|
|
@@ -72,6 +80,50 @@ import { types, PluginObj } from '@babel/core';
|
|
|
72
80
|
* specific field OR several), because no row identity is at stake — only
|
|
73
81
|
* further field leaves.
|
|
74
82
|
*
|
|
83
|
+
* ── Forwarding-component allowlist descent (ARCH-1065 M-COV fix) ──────────
|
|
84
|
+
*
|
|
85
|
+
* D5.4 only ever sees THROUGH a component whose own source is available
|
|
86
|
+
* (module-local, co-located in the same file). Kit/imported components
|
|
87
|
+
* (`Card`, `CardContent`, `StatusBadge`, …) are opaque to the AST — the
|
|
88
|
+
* plugin has no body to project into — but many of them forward unrecognized
|
|
89
|
+
* JSX attributes onto their own root DOM element (typically via
|
|
90
|
+
* `{...rest}`), which makes them valid attribute CARRIERS even though
|
|
91
|
+
* they're not valid PROJECTION targets. `forwardingComponents` (a plugin
|
|
92
|
+
* option, see `CollectionBindingPluginOptions`) names exactly which ones.
|
|
93
|
+
*
|
|
94
|
+
* Two independent mechanisms use that allowlist:
|
|
95
|
+
*
|
|
96
|
+
* - (a2) ROW ROOT: when the `.map()` callback's own returned JSX element
|
|
97
|
+
* is an allowlisted component (`<Card key={r.id}>…</Card>` as the row
|
|
98
|
+
* root itself), the row-identity attributes stamp directly onto that
|
|
99
|
+
* component usage — NOT a projection into a separate definition, since
|
|
100
|
+
* there is none to project into. The field walk continues into the
|
|
101
|
+
* component's own JSX children with the SAME binding scope the row
|
|
102
|
+
* callback already established.
|
|
103
|
+
* - (c2-ii) LEAF: when a component usage directly hugs a field expression
|
|
104
|
+
* with no host element beneath it (`<StatusBadge>{w.status}</StatusBadge>`),
|
|
105
|
+
* the field attribute stamps onto the component itself IFF its name is
|
|
106
|
+
* allowlisted; otherwise the leaf is left unstamped — a leaf is never
|
|
107
|
+
* given an invented carrier.
|
|
108
|
+
*
|
|
109
|
+
* Separately, and regardless of allowlist membership at all — walking a
|
|
110
|
+
* component's own JSX children costs nothing, since no attribute lands
|
|
111
|
+
* anywhere until a valid carrier (host, or allowlisted leaf) is actually
|
|
112
|
+
* found — (a1) DESCENT: the field walk always continues INTO a structural
|
|
113
|
+
* child that is a component usage, as long as that usage has its own JSX
|
|
114
|
+
* children in source. `<Card><span>{w.name}</span></Card>` reaches the
|
|
115
|
+
* `<span>` (a host, stamped unconditionally) even though `Card` itself is
|
|
116
|
+
* never a stamp target unless allowlisted. This is what makes (c2-ii) safe
|
|
117
|
+
* to leave allowlist-gated: a wrapper with a host descendant never needs the
|
|
118
|
+
* wrapper itself to carry anything.
|
|
119
|
+
*
|
|
120
|
+
* The allowlist is ALWAYS an explicit option, never a built-in default list
|
|
121
|
+
* baked into this plugin — kit-manifest is the single source of truth for
|
|
122
|
+
* which components forward attributes (`forwardsDataAttributes`), and the
|
|
123
|
+
* kit build (`build-d.mjs`) is what threads that list in. An absent/empty
|
|
124
|
+
* option is exactly today's fail-closed behavior: zero component-carried
|
|
125
|
+
* stamps, byte-identical to the plugin before this mechanism existed.
|
|
126
|
+
*
|
|
75
127
|
* ── Field resolution ────────────────────────────────────────────────────
|
|
76
128
|
*
|
|
77
129
|
* Reuses `meaningfulChildren` from `babel-plugin-editor-meta.ts` (the exact
|
|
@@ -95,10 +147,25 @@ import { types, PluginObj } from '@babel/core';
|
|
|
95
147
|
* the duplicated test would double-evaluate it across SSR and hydration,
|
|
96
148
|
* so the whole leaf is left unstamped instead;
|
|
97
149
|
* - `cond ? 'Literal' : 'Other'` where `cond` is itself a bare field
|
|
98
|
-
* member (a boolean-typed field driving two literal branches)
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
150
|
+
* member (a boolean-typed field driving two literal branches), OR a
|
|
151
|
+
* CALL-FREE comparison (`===`/`!==`/`==`/`!=`/`</<=`/`>`/`>=`) naming a
|
|
152
|
+
* field on either side (`r.status === 'Active' ? 'Pause' : 'Enable'`,
|
|
153
|
+
* D5.5 widening, ARCH-1065 M-COV FIX) → static field named by the
|
|
154
|
+
* resolved test field — the FE's typed controls (D7, a different
|
|
155
|
+
* mechanism, M3) read the boolean's/status's true/false or vocabulary
|
|
156
|
+
* rendering from `window.__slice`, not from this attribute. Nothing
|
|
157
|
+
* about the test is ever duplicated into the DOM here (only the field
|
|
158
|
+
* NAME is emitted), so there is no C3 double-evaluation concern for this
|
|
159
|
+
* shape — `containsEvaluation(test)` is still checked defensively;
|
|
160
|
+
* - a formatter CALL with exactly one argument that is itself a bare
|
|
161
|
+
* member/optional-member field (`Kit.date(r.updated_at)`,
|
|
162
|
+
* `money(auction.currentPrice)`, ARCH-1065 M-COV FIX) → the resolved
|
|
163
|
+
* field name, PLUS `data-agent-field-state="rendered"` and
|
|
164
|
+
* `data-agent-field-parts` (the same field name) — the DOM only ever
|
|
165
|
+
* holds the FORMATTED string, never the raw value, so this is marked
|
|
166
|
+
* the same "honest, non-invertible" way a `??`/boolean fallback is (D8);
|
|
167
|
+
* a multi-arg call, or a call whose sole argument isn't a bare field
|
|
168
|
+
* member, is left unstamped rather than guessed at;
|
|
102
169
|
* - a COMPUTED member (`a[expr]`, `expr` not a string literal) → no field
|
|
103
170
|
* name, `data-agent-field-state="unresolved"` (a positive "this is
|
|
104
171
|
* row-scoped data, but which field is unknown" signal — never silent).
|
|
@@ -127,6 +194,13 @@ import { types, PluginObj } from '@babel/core';
|
|
|
127
194
|
* that is itself slice-rooted gets discovered and stamped independently by
|
|
128
195
|
* the same Program-wide `.map()` scan, with its own entity/row identity.
|
|
129
196
|
*
|
|
197
|
+
* A JSXExpressionContainer wrapping `cond && <JSX/>` / `cond || <JSX/>` / a
|
|
198
|
+
* ternary IS a structural child, though (ARCH-1065 M-COV FIX, devtools
|
|
199
|
+
* 0.0.29, see `collectConditionalJsxBranchPaths`): the walker descends into
|
|
200
|
+
* whichever branch(es) are themselves a JSXElement, with the SAME scope —
|
|
201
|
+
* purely structural, the guard/test is never duplicated or stamped, only the
|
|
202
|
+
* JSX it conditionally renders is walked for further fields.
|
|
203
|
+
*
|
|
130
204
|
* ── Failure posture ─────────────────────────────────────────────────────
|
|
131
205
|
*
|
|
132
206
|
* Every resolution step is an explicit allowlist; anything the resolver is
|
|
@@ -148,6 +222,27 @@ import { types, PluginObj } from '@babel/core';
|
|
|
148
222
|
interface CollectionBindingPluginOptions {
|
|
149
223
|
/** Skip files matching this pattern. @default /node_modules/ */
|
|
150
224
|
fileExclude?: RegExp;
|
|
225
|
+
/**
|
|
226
|
+
* Component names known to forward unrecognized JSX attributes onto their
|
|
227
|
+
* own root DOM element (e.g. via `{...rest}`) — the exact set that makes
|
|
228
|
+
* a component usage a valid attribute CARRIER (row-root or leaf) even
|
|
229
|
+
* though it's opaque to the AST (see the "Forwarding-component allowlist
|
|
230
|
+
* descent" section above).
|
|
231
|
+
*
|
|
232
|
+
* Sourced from kit-manifest's `forwardsDataAttributes` flags by the kit
|
|
233
|
+
* build (`build-d.mjs` passes it) — this plugin NEVER hardcodes a default
|
|
234
|
+
* list here; that would be a shadow copy of kit-manifest's own truth, and
|
|
235
|
+
* would drift the moment a kit component's forwarding behavior changed
|
|
236
|
+
* without this plugin's knowledge. Synthetic lists are fine in tests, to
|
|
237
|
+
* keep the plugin testable standalone.
|
|
238
|
+
*
|
|
239
|
+
* Absent or empty = today's fail-closed behavior: no component ever
|
|
240
|
+
* carries a stamp, byte-identical to the plugin before this option
|
|
241
|
+
* existed.
|
|
242
|
+
*
|
|
243
|
+
* @default []
|
|
244
|
+
*/
|
|
245
|
+
forwardingComponents?: string[];
|
|
151
246
|
}
|
|
152
247
|
declare function babelPluginCollectionBinding({ types: t }: {
|
|
153
248
|
types: typeof types;
|
|
@@ -45,9 +45,11 @@ var ATTR_COLLECTION_ORDER = "data-agent-collection-order";
|
|
|
45
45
|
var SLICE_ROOT_NAME = "slice";
|
|
46
46
|
var CHAIN_METHODS = /* @__PURE__ */ new Set(["filter", "slice", "sort", "map"]);
|
|
47
47
|
var MAX_SOURCE_CHAIN_HOPS = 4;
|
|
48
|
+
var COMPARISON_OPERATORS = /* @__PURE__ */ new Set(["==", "!=", "===", "!==", "<", "<=", ">", ">="]);
|
|
48
49
|
var MAX_PROJECTION_DEPTH = 3;
|
|
49
50
|
function babelPluginCollectionBinding({ types: t }, opts = {}) {
|
|
50
|
-
const { fileExclude = /node_modules
|
|
51
|
+
const { fileExclude = /node_modules/, forwardingComponents = [] } = opts;
|
|
52
|
+
const forwardingComponentSet = new Set(forwardingComponents);
|
|
51
53
|
function hasOwnAttr(elementPath, name) {
|
|
52
54
|
return elementPath.node.openingElement.attributes.some(
|
|
53
55
|
(a) => t.isJSXAttribute(a) && t.isJSXIdentifier(a.name, { name })
|
|
@@ -75,6 +77,10 @@ function babelPluginCollectionBinding({ types: t }, opts = {}) {
|
|
|
75
77
|
function isLowercaseTag(tag) {
|
|
76
78
|
return tag.length > 0 && tag[0] === tag[0].toLowerCase();
|
|
77
79
|
}
|
|
80
|
+
function canCarryFieldStamp(elementPath) {
|
|
81
|
+
const tag = getTagName(elementPath.node.openingElement);
|
|
82
|
+
return isLowercaseTag(tag) || forwardingComponentSet.has(tag);
|
|
83
|
+
}
|
|
78
84
|
function getAttrName(attr) {
|
|
79
85
|
return t.isJSXIdentifier(attr.name) ? attr.name.name : `${attr.name.namespace.name}:${attr.name.name.name}`;
|
|
80
86
|
}
|
|
@@ -87,44 +93,72 @@ function babelPluginCollectionBinding({ types: t }, opts = {}) {
|
|
|
87
93
|
if (t.isIdentifier(callee, { name: "useMemo" })) return true;
|
|
88
94
|
return t.isMemberExpression(callee) && !callee.computed && t.isIdentifier(callee.property, { name: "useMemo" });
|
|
89
95
|
}
|
|
90
|
-
function
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
return resolveSliceSource(factory.body, scope, hops, hasSort);
|
|
96
|
+
function findSingleReturnArgument(blockPath) {
|
|
97
|
+
const returnPaths = [];
|
|
98
|
+
blockPath.traverse({
|
|
99
|
+
Function(inner) {
|
|
100
|
+
inner.skip();
|
|
101
|
+
},
|
|
102
|
+
ReturnStatement(rp) {
|
|
103
|
+
returnPaths.push(rp);
|
|
99
104
|
}
|
|
100
|
-
|
|
101
|
-
|
|
105
|
+
});
|
|
106
|
+
if (returnPaths.length !== 1) return null;
|
|
107
|
+
const argPath = returnPaths[0].get("argument");
|
|
108
|
+
if (Array.isArray(argPath) || !argPath.node || !argPath.isExpression()) return null;
|
|
109
|
+
return argPath;
|
|
110
|
+
}
|
|
111
|
+
function resolveSliceSource(path, hops, hasSort) {
|
|
112
|
+
if (path.isCallExpression()) {
|
|
113
|
+
const calleePath = path.get("callee");
|
|
114
|
+
if (Array.isArray(calleePath) || !calleePath.isExpression()) return null;
|
|
115
|
+
if (isUseMemoCallee(calleePath.node)) {
|
|
116
|
+
const argPaths = path.get("arguments");
|
|
117
|
+
const factoryPath = argPaths[0];
|
|
118
|
+
if (!factoryPath || !factoryPath.isArrowFunctionExpression()) return null;
|
|
119
|
+
const bodyPath = factoryPath.get("body");
|
|
120
|
+
if (Array.isArray(bodyPath)) return null;
|
|
121
|
+
if (bodyPath.isExpression()) {
|
|
122
|
+
return resolveSliceSource(bodyPath, hops, hasSort);
|
|
123
|
+
}
|
|
124
|
+
if (bodyPath.isBlockStatement()) {
|
|
125
|
+
const returnArgPath = findSingleReturnArgument(bodyPath);
|
|
126
|
+
if (!returnArgPath) return null;
|
|
127
|
+
return resolveSliceSource(returnArgPath, hops, hasSort);
|
|
128
|
+
}
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
const calleeNode = calleePath.node;
|
|
132
|
+
if (t.isMemberExpression(calleeNode) && !calleeNode.computed && t.isIdentifier(calleeNode.property)) {
|
|
133
|
+
const method = calleeNode.property.name;
|
|
102
134
|
if (!CHAIN_METHODS.has(method)) return null;
|
|
103
|
-
|
|
104
|
-
|
|
135
|
+
const objectPath = calleePath.get("object");
|
|
136
|
+
if (Array.isArray(objectPath) || !objectPath.isExpression()) return null;
|
|
137
|
+
return resolveSliceSource(objectPath, hops, hasSort || method === "sort");
|
|
105
138
|
}
|
|
106
139
|
return null;
|
|
107
140
|
}
|
|
108
|
-
if (
|
|
109
|
-
|
|
110
|
-
if (!
|
|
111
|
-
if (!
|
|
112
|
-
|
|
141
|
+
if (path.isMemberExpression()) {
|
|
142
|
+
const { object, property, computed } = path.node;
|
|
143
|
+
if (!t.isIdentifier(object, { name: SLICE_ROOT_NAME })) return null;
|
|
144
|
+
if (!isPageSliceBinding(path.scope)) return null;
|
|
145
|
+
if (!computed && t.isIdentifier(property)) {
|
|
146
|
+
return { entity: property.name, hasSort };
|
|
113
147
|
}
|
|
114
|
-
if (
|
|
115
|
-
return { entity:
|
|
148
|
+
if (computed && t.isStringLiteral(property)) {
|
|
149
|
+
return { entity: property.value, hasSort };
|
|
116
150
|
}
|
|
117
151
|
return null;
|
|
118
152
|
}
|
|
119
|
-
if (
|
|
153
|
+
if (path.isIdentifier()) {
|
|
120
154
|
if (hops >= MAX_SOURCE_CHAIN_HOPS) return null;
|
|
121
|
-
const binding = scope.getBinding(
|
|
155
|
+
const binding = path.scope.getBinding(path.node.name);
|
|
122
156
|
if (!binding || !binding.constant) return null;
|
|
123
157
|
const declarator = binding.path;
|
|
124
158
|
if (!declarator.isVariableDeclarator()) return null;
|
|
125
|
-
const
|
|
126
|
-
if (!
|
|
127
|
-
return resolveSliceSource(
|
|
159
|
+
const initPath = declarator.get("init");
|
|
160
|
+
if (Array.isArray(initPath) || !initPath.node || !initPath.isExpression()) return null;
|
|
161
|
+
return resolveSliceSource(initPath, hops + 1, hasSort);
|
|
128
162
|
}
|
|
129
163
|
return null;
|
|
130
164
|
}
|
|
@@ -262,11 +296,27 @@ function babelPluginCollectionBinding({ types: t }, opts = {}) {
|
|
|
262
296
|
const field = fieldNameForBranch(expr.left, scope);
|
|
263
297
|
return field ? { kind: "static", field } : null;
|
|
264
298
|
}
|
|
299
|
+
function fieldNameForLiteralBranchTest(expr, scope) {
|
|
300
|
+
const direct = fieldNameForBranch(expr, scope);
|
|
301
|
+
if (direct) return direct;
|
|
302
|
+
if (t.isBinaryExpression(expr) && COMPARISON_OPERATORS.has(expr.operator)) {
|
|
303
|
+
if (t.isExpression(expr.left)) {
|
|
304
|
+
const leftField = fieldNameForBranch(expr.left, scope);
|
|
305
|
+
if (leftField) return leftField;
|
|
306
|
+
}
|
|
307
|
+
if (t.isExpression(expr.right)) {
|
|
308
|
+
const rightField = fieldNameForBranch(expr.right, scope);
|
|
309
|
+
if (rightField) return rightField;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
return null;
|
|
313
|
+
}
|
|
265
314
|
function classifyConditional(expr, scope) {
|
|
266
315
|
const { test, consequent, alternate } = expr;
|
|
267
316
|
if (!t.isExpression(test) || !t.isExpression(consequent) || !t.isExpression(alternate)) return null;
|
|
268
317
|
if (isStaticLiteralExpr(consequent) && isStaticLiteralExpr(alternate)) {
|
|
269
|
-
|
|
318
|
+
if (containsEvaluation(test)) return null;
|
|
319
|
+
const testField = fieldNameForLiteralBranchTest(test, scope);
|
|
270
320
|
return testField ? { kind: "static", field: testField } : null;
|
|
271
321
|
}
|
|
272
322
|
const consField = fieldNameForBranch(consequent, scope);
|
|
@@ -279,6 +329,14 @@ function babelPluginCollectionBinding({ types: t }, opts = {}) {
|
|
|
279
329
|
expr: t.conditionalExpression(t.cloneNode(test), t.stringLiteral(consField), t.stringLiteral(altField))
|
|
280
330
|
};
|
|
281
331
|
}
|
|
332
|
+
function classifyFormatterCall(expr, scope) {
|
|
333
|
+
if (expr.arguments.length !== 1) return null;
|
|
334
|
+
const [arg] = expr.arguments;
|
|
335
|
+
if (!t.isMemberExpression(arg) && !t.isOptionalMemberExpression(arg)) return null;
|
|
336
|
+
const verdict = classifyMemberField(arg, scope);
|
|
337
|
+
if (!verdict || verdict.kind !== "static") return null;
|
|
338
|
+
return { kind: "formatted", field: verdict.field };
|
|
339
|
+
}
|
|
282
340
|
function classifyFieldExpr(expr, scope) {
|
|
283
341
|
if (t.isIdentifier(expr)) {
|
|
284
342
|
const binding = scope.identifiers.get(expr.name);
|
|
@@ -290,6 +348,7 @@ function babelPluginCollectionBinding({ types: t }, opts = {}) {
|
|
|
290
348
|
if (t.isLogicalExpression(expr) && expr.operator === "??") return classifyNullishChain(expr, scope);
|
|
291
349
|
if (t.isLogicalExpression(expr) && expr.operator === "||") return classifySimpleLogicalOr(expr, scope);
|
|
292
350
|
if (t.isConditionalExpression(expr)) return classifyConditional(expr, scope);
|
|
351
|
+
if (t.isCallExpression(expr)) return classifyFormatterCall(expr, scope);
|
|
293
352
|
return null;
|
|
294
353
|
}
|
|
295
354
|
function applyFieldVerdict(elementPath, verdict) {
|
|
@@ -302,6 +361,12 @@ function babelPluginCollectionBinding({ types: t }, opts = {}) {
|
|
|
302
361
|
addAttr(elementPath, ATTR_FIELD, t.stringLiteral(verdict.field));
|
|
303
362
|
return;
|
|
304
363
|
}
|
|
364
|
+
if (verdict.kind === "formatted") {
|
|
365
|
+
addAttr(elementPath, ATTR_FIELD, t.stringLiteral(verdict.field));
|
|
366
|
+
addAttr(elementPath, ATTR_FIELD_STATE, t.stringLiteral("rendered"));
|
|
367
|
+
addAttr(elementPath, ATTR_FIELD_PARTS, t.stringLiteral(verdict.field));
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
305
370
|
addAttr(elementPath, ATTR_FIELD, t.jsxExpressionContainer(verdict.expr));
|
|
306
371
|
}
|
|
307
372
|
function meaningfulChildPaths(elementPath) {
|
|
@@ -324,11 +389,13 @@ function babelPluginCollectionBinding({ types: t }, opts = {}) {
|
|
|
324
389
|
}
|
|
325
390
|
function stampPureLeaf(elementPath, expr, scope) {
|
|
326
391
|
if (hasOwnAttr(elementPath, ATTR_FIELD) || hasOwnAttr(elementPath, ATTR_FIELD_STATE)) return;
|
|
392
|
+
if (!canCarryFieldStamp(elementPath)) return;
|
|
327
393
|
if (t.isJSXEmptyExpression(expr) || !t.isExpression(expr)) return;
|
|
328
394
|
applyFieldVerdict(elementPath, classifyFieldExpr(expr, scope));
|
|
329
395
|
}
|
|
330
396
|
function stampLabelValueOrMixed(elementPath, childPaths, scope) {
|
|
331
397
|
if (hasOwnAttr(elementPath, ATTR_FIELD) || hasOwnAttr(elementPath, ATTR_FIELD_STATE)) return;
|
|
398
|
+
if (!canCarryFieldStamp(elementPath)) return;
|
|
332
399
|
const hasInvisibleSibling = childPaths.some(
|
|
333
400
|
(p) => p.isJSXExpressionContainer() && t.isExpression(p.node.expression) && isInvisibleRenderExpr(p.node.expression)
|
|
334
401
|
);
|
|
@@ -432,6 +499,7 @@ function babelPluginCollectionBinding({ types: t }, opts = {}) {
|
|
|
432
499
|
function resolveRowRoot(jsxPath, scope, depth) {
|
|
433
500
|
const tag = getTagName(jsxPath.node.openingElement);
|
|
434
501
|
if (isLowercaseTag(tag)) return { hostPath: jsxPath, scope };
|
|
502
|
+
if (forwardingComponentSet.has(tag)) return { hostPath: jsxPath, scope };
|
|
435
503
|
if (depth >= MAX_PROJECTION_DEPTH) return null;
|
|
436
504
|
const fnPath = resolveComponentDefinition(tag, jsxPath);
|
|
437
505
|
if (!fnPath) return null;
|
|
@@ -477,13 +545,42 @@ function babelPluginCollectionBinding({ types: t }, opts = {}) {
|
|
|
477
545
|
if (!innerReturn) return null;
|
|
478
546
|
return resolveFieldProjection(innerReturn, { identifiers }, depth + 1);
|
|
479
547
|
}
|
|
548
|
+
function collectConditionalJsxBranchPaths(containerPath) {
|
|
549
|
+
const exprPath = containerPath.get("expression");
|
|
550
|
+
if (Array.isArray(exprPath)) return [];
|
|
551
|
+
if (exprPath.isLogicalExpression() && (exprPath.node.operator === "&&" || exprPath.node.operator === "||")) {
|
|
552
|
+
const rightPath = exprPath.get("right");
|
|
553
|
+
return !Array.isArray(rightPath) && rightPath.isJSXElement() ? [rightPath] : [];
|
|
554
|
+
}
|
|
555
|
+
if (exprPath.isConditionalExpression()) {
|
|
556
|
+
const branches = [];
|
|
557
|
+
const consPath = exprPath.get("consequent");
|
|
558
|
+
const altPath = exprPath.get("alternate");
|
|
559
|
+
if (!Array.isArray(consPath) && consPath.isJSXElement()) branches.push(consPath);
|
|
560
|
+
if (!Array.isArray(altPath) && altPath.isJSXElement()) branches.push(altPath);
|
|
561
|
+
return branches;
|
|
562
|
+
}
|
|
563
|
+
return [];
|
|
564
|
+
}
|
|
480
565
|
function walkFieldsWithinRow(elementPath, scope) {
|
|
481
566
|
const childPaths = meaningfulChildPaths(elementPath);
|
|
482
|
-
const
|
|
567
|
+
const conditionalBranchesByChild = /* @__PURE__ */ new Map();
|
|
568
|
+
for (const p of childPaths) {
|
|
569
|
+
if (!p.isJSXExpressionContainer()) continue;
|
|
570
|
+
const branches = collectConditionalJsxBranchPaths(p);
|
|
571
|
+
if (branches.length > 0) conditionalBranchesByChild.set(p, branches);
|
|
572
|
+
}
|
|
573
|
+
const hasElementChild = childPaths.some((p) => p.isJSXElement() || conditionalBranchesByChild.has(p));
|
|
483
574
|
if (hasElementChild) {
|
|
484
575
|
for (const childPath of childPaths) {
|
|
485
|
-
if (
|
|
486
|
-
|
|
576
|
+
if (childPath.isJSXElement()) {
|
|
577
|
+
handleStructuralChild(childPath, scope);
|
|
578
|
+
continue;
|
|
579
|
+
}
|
|
580
|
+
const branches = conditionalBranchesByChild.get(childPath);
|
|
581
|
+
if (branches) {
|
|
582
|
+
for (const branchPath of branches) handleStructuralChild(branchPath, scope);
|
|
583
|
+
}
|
|
487
584
|
}
|
|
488
585
|
return;
|
|
489
586
|
}
|
|
@@ -503,6 +600,10 @@ function babelPluginCollectionBinding({ types: t }, opts = {}) {
|
|
|
503
600
|
walkFieldsWithinRow(childPath, scope);
|
|
504
601
|
return;
|
|
505
602
|
}
|
|
603
|
+
if (meaningfulChildPaths(childPath).length > 0) {
|
|
604
|
+
walkFieldsWithinRow(childPath, scope);
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
506
607
|
const projected = resolveFieldProjection(childPath, scope, 0);
|
|
507
608
|
if (!projected) return;
|
|
508
609
|
walkFieldsWithinRow(projected.hostPath, projected.scope);
|
|
@@ -513,7 +614,11 @@ function babelPluginCollectionBinding({ types: t }, opts = {}) {
|
|
|
513
614
|
if (!t.isMemberExpression(callee) || callee.computed) return;
|
|
514
615
|
if (!t.isIdentifier(callee.property, { name: "map" })) return;
|
|
515
616
|
if (!t.isExpression(callee.object)) return;
|
|
516
|
-
const
|
|
617
|
+
const calleePath = callPath.get("callee");
|
|
618
|
+
if (Array.isArray(calleePath) || !calleePath.isMemberExpression()) return;
|
|
619
|
+
const objectPath = calleePath.get("object");
|
|
620
|
+
if (Array.isArray(objectPath) || !objectPath.isExpression()) return;
|
|
621
|
+
const source = resolveSliceSource(objectPath, 0, false);
|
|
517
622
|
if (!source) return;
|
|
518
623
|
const [callback] = node.arguments;
|
|
519
624
|
if (!callback || !t.isArrowFunctionExpression(callback) && !t.isFunctionExpression(callback)) return;
|
|
@@ -14,9 +14,11 @@ var ATTR_COLLECTION_ORDER = "data-agent-collection-order";
|
|
|
14
14
|
var SLICE_ROOT_NAME = "slice";
|
|
15
15
|
var CHAIN_METHODS = /* @__PURE__ */ new Set(["filter", "slice", "sort", "map"]);
|
|
16
16
|
var MAX_SOURCE_CHAIN_HOPS = 4;
|
|
17
|
+
var COMPARISON_OPERATORS = /* @__PURE__ */ new Set(["==", "!=", "===", "!==", "<", "<=", ">", ">="]);
|
|
17
18
|
var MAX_PROJECTION_DEPTH = 3;
|
|
18
19
|
function babelPluginCollectionBinding({ types: t }, opts = {}) {
|
|
19
|
-
const { fileExclude = /node_modules
|
|
20
|
+
const { fileExclude = /node_modules/, forwardingComponents = [] } = opts;
|
|
21
|
+
const forwardingComponentSet = new Set(forwardingComponents);
|
|
20
22
|
function hasOwnAttr(elementPath, name) {
|
|
21
23
|
return elementPath.node.openingElement.attributes.some(
|
|
22
24
|
(a) => t.isJSXAttribute(a) && t.isJSXIdentifier(a.name, { name })
|
|
@@ -44,6 +46,10 @@ function babelPluginCollectionBinding({ types: t }, opts = {}) {
|
|
|
44
46
|
function isLowercaseTag(tag) {
|
|
45
47
|
return tag.length > 0 && tag[0] === tag[0].toLowerCase();
|
|
46
48
|
}
|
|
49
|
+
function canCarryFieldStamp(elementPath) {
|
|
50
|
+
const tag = getTagName(elementPath.node.openingElement);
|
|
51
|
+
return isLowercaseTag(tag) || forwardingComponentSet.has(tag);
|
|
52
|
+
}
|
|
47
53
|
function getAttrName(attr) {
|
|
48
54
|
return t.isJSXIdentifier(attr.name) ? attr.name.name : `${attr.name.namespace.name}:${attr.name.name.name}`;
|
|
49
55
|
}
|
|
@@ -56,44 +62,72 @@ function babelPluginCollectionBinding({ types: t }, opts = {}) {
|
|
|
56
62
|
if (t.isIdentifier(callee, { name: "useMemo" })) return true;
|
|
57
63
|
return t.isMemberExpression(callee) && !callee.computed && t.isIdentifier(callee.property, { name: "useMemo" });
|
|
58
64
|
}
|
|
59
|
-
function
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
return resolveSliceSource(factory.body, scope, hops, hasSort);
|
|
65
|
+
function findSingleReturnArgument(blockPath) {
|
|
66
|
+
const returnPaths = [];
|
|
67
|
+
blockPath.traverse({
|
|
68
|
+
Function(inner) {
|
|
69
|
+
inner.skip();
|
|
70
|
+
},
|
|
71
|
+
ReturnStatement(rp) {
|
|
72
|
+
returnPaths.push(rp);
|
|
68
73
|
}
|
|
69
|
-
|
|
70
|
-
|
|
74
|
+
});
|
|
75
|
+
if (returnPaths.length !== 1) return null;
|
|
76
|
+
const argPath = returnPaths[0].get("argument");
|
|
77
|
+
if (Array.isArray(argPath) || !argPath.node || !argPath.isExpression()) return null;
|
|
78
|
+
return argPath;
|
|
79
|
+
}
|
|
80
|
+
function resolveSliceSource(path, hops, hasSort) {
|
|
81
|
+
if (path.isCallExpression()) {
|
|
82
|
+
const calleePath = path.get("callee");
|
|
83
|
+
if (Array.isArray(calleePath) || !calleePath.isExpression()) return null;
|
|
84
|
+
if (isUseMemoCallee(calleePath.node)) {
|
|
85
|
+
const argPaths = path.get("arguments");
|
|
86
|
+
const factoryPath = argPaths[0];
|
|
87
|
+
if (!factoryPath || !factoryPath.isArrowFunctionExpression()) return null;
|
|
88
|
+
const bodyPath = factoryPath.get("body");
|
|
89
|
+
if (Array.isArray(bodyPath)) return null;
|
|
90
|
+
if (bodyPath.isExpression()) {
|
|
91
|
+
return resolveSliceSource(bodyPath, hops, hasSort);
|
|
92
|
+
}
|
|
93
|
+
if (bodyPath.isBlockStatement()) {
|
|
94
|
+
const returnArgPath = findSingleReturnArgument(bodyPath);
|
|
95
|
+
if (!returnArgPath) return null;
|
|
96
|
+
return resolveSliceSource(returnArgPath, hops, hasSort);
|
|
97
|
+
}
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
const calleeNode = calleePath.node;
|
|
101
|
+
if (t.isMemberExpression(calleeNode) && !calleeNode.computed && t.isIdentifier(calleeNode.property)) {
|
|
102
|
+
const method = calleeNode.property.name;
|
|
71
103
|
if (!CHAIN_METHODS.has(method)) return null;
|
|
72
|
-
|
|
73
|
-
|
|
104
|
+
const objectPath = calleePath.get("object");
|
|
105
|
+
if (Array.isArray(objectPath) || !objectPath.isExpression()) return null;
|
|
106
|
+
return resolveSliceSource(objectPath, hops, hasSort || method === "sort");
|
|
74
107
|
}
|
|
75
108
|
return null;
|
|
76
109
|
}
|
|
77
|
-
if (
|
|
78
|
-
|
|
79
|
-
if (!
|
|
80
|
-
if (!
|
|
81
|
-
|
|
110
|
+
if (path.isMemberExpression()) {
|
|
111
|
+
const { object, property, computed } = path.node;
|
|
112
|
+
if (!t.isIdentifier(object, { name: SLICE_ROOT_NAME })) return null;
|
|
113
|
+
if (!isPageSliceBinding(path.scope)) return null;
|
|
114
|
+
if (!computed && t.isIdentifier(property)) {
|
|
115
|
+
return { entity: property.name, hasSort };
|
|
82
116
|
}
|
|
83
|
-
if (
|
|
84
|
-
return { entity:
|
|
117
|
+
if (computed && t.isStringLiteral(property)) {
|
|
118
|
+
return { entity: property.value, hasSort };
|
|
85
119
|
}
|
|
86
120
|
return null;
|
|
87
121
|
}
|
|
88
|
-
if (
|
|
122
|
+
if (path.isIdentifier()) {
|
|
89
123
|
if (hops >= MAX_SOURCE_CHAIN_HOPS) return null;
|
|
90
|
-
const binding = scope.getBinding(
|
|
124
|
+
const binding = path.scope.getBinding(path.node.name);
|
|
91
125
|
if (!binding || !binding.constant) return null;
|
|
92
126
|
const declarator = binding.path;
|
|
93
127
|
if (!declarator.isVariableDeclarator()) return null;
|
|
94
|
-
const
|
|
95
|
-
if (!
|
|
96
|
-
return resolveSliceSource(
|
|
128
|
+
const initPath = declarator.get("init");
|
|
129
|
+
if (Array.isArray(initPath) || !initPath.node || !initPath.isExpression()) return null;
|
|
130
|
+
return resolveSliceSource(initPath, hops + 1, hasSort);
|
|
97
131
|
}
|
|
98
132
|
return null;
|
|
99
133
|
}
|
|
@@ -231,11 +265,27 @@ function babelPluginCollectionBinding({ types: t }, opts = {}) {
|
|
|
231
265
|
const field = fieldNameForBranch(expr.left, scope);
|
|
232
266
|
return field ? { kind: "static", field } : null;
|
|
233
267
|
}
|
|
268
|
+
function fieldNameForLiteralBranchTest(expr, scope) {
|
|
269
|
+
const direct = fieldNameForBranch(expr, scope);
|
|
270
|
+
if (direct) return direct;
|
|
271
|
+
if (t.isBinaryExpression(expr) && COMPARISON_OPERATORS.has(expr.operator)) {
|
|
272
|
+
if (t.isExpression(expr.left)) {
|
|
273
|
+
const leftField = fieldNameForBranch(expr.left, scope);
|
|
274
|
+
if (leftField) return leftField;
|
|
275
|
+
}
|
|
276
|
+
if (t.isExpression(expr.right)) {
|
|
277
|
+
const rightField = fieldNameForBranch(expr.right, scope);
|
|
278
|
+
if (rightField) return rightField;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
return null;
|
|
282
|
+
}
|
|
234
283
|
function classifyConditional(expr, scope) {
|
|
235
284
|
const { test, consequent, alternate } = expr;
|
|
236
285
|
if (!t.isExpression(test) || !t.isExpression(consequent) || !t.isExpression(alternate)) return null;
|
|
237
286
|
if (isStaticLiteralExpr(consequent) && isStaticLiteralExpr(alternate)) {
|
|
238
|
-
|
|
287
|
+
if (containsEvaluation(test)) return null;
|
|
288
|
+
const testField = fieldNameForLiteralBranchTest(test, scope);
|
|
239
289
|
return testField ? { kind: "static", field: testField } : null;
|
|
240
290
|
}
|
|
241
291
|
const consField = fieldNameForBranch(consequent, scope);
|
|
@@ -248,6 +298,14 @@ function babelPluginCollectionBinding({ types: t }, opts = {}) {
|
|
|
248
298
|
expr: t.conditionalExpression(t.cloneNode(test), t.stringLiteral(consField), t.stringLiteral(altField))
|
|
249
299
|
};
|
|
250
300
|
}
|
|
301
|
+
function classifyFormatterCall(expr, scope) {
|
|
302
|
+
if (expr.arguments.length !== 1) return null;
|
|
303
|
+
const [arg] = expr.arguments;
|
|
304
|
+
if (!t.isMemberExpression(arg) && !t.isOptionalMemberExpression(arg)) return null;
|
|
305
|
+
const verdict = classifyMemberField(arg, scope);
|
|
306
|
+
if (!verdict || verdict.kind !== "static") return null;
|
|
307
|
+
return { kind: "formatted", field: verdict.field };
|
|
308
|
+
}
|
|
251
309
|
function classifyFieldExpr(expr, scope) {
|
|
252
310
|
if (t.isIdentifier(expr)) {
|
|
253
311
|
const binding = scope.identifiers.get(expr.name);
|
|
@@ -259,6 +317,7 @@ function babelPluginCollectionBinding({ types: t }, opts = {}) {
|
|
|
259
317
|
if (t.isLogicalExpression(expr) && expr.operator === "??") return classifyNullishChain(expr, scope);
|
|
260
318
|
if (t.isLogicalExpression(expr) && expr.operator === "||") return classifySimpleLogicalOr(expr, scope);
|
|
261
319
|
if (t.isConditionalExpression(expr)) return classifyConditional(expr, scope);
|
|
320
|
+
if (t.isCallExpression(expr)) return classifyFormatterCall(expr, scope);
|
|
262
321
|
return null;
|
|
263
322
|
}
|
|
264
323
|
function applyFieldVerdict(elementPath, verdict) {
|
|
@@ -271,6 +330,12 @@ function babelPluginCollectionBinding({ types: t }, opts = {}) {
|
|
|
271
330
|
addAttr(elementPath, ATTR_FIELD, t.stringLiteral(verdict.field));
|
|
272
331
|
return;
|
|
273
332
|
}
|
|
333
|
+
if (verdict.kind === "formatted") {
|
|
334
|
+
addAttr(elementPath, ATTR_FIELD, t.stringLiteral(verdict.field));
|
|
335
|
+
addAttr(elementPath, ATTR_FIELD_STATE, t.stringLiteral("rendered"));
|
|
336
|
+
addAttr(elementPath, ATTR_FIELD_PARTS, t.stringLiteral(verdict.field));
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
274
339
|
addAttr(elementPath, ATTR_FIELD, t.jsxExpressionContainer(verdict.expr));
|
|
275
340
|
}
|
|
276
341
|
function meaningfulChildPaths(elementPath) {
|
|
@@ -293,11 +358,13 @@ function babelPluginCollectionBinding({ types: t }, opts = {}) {
|
|
|
293
358
|
}
|
|
294
359
|
function stampPureLeaf(elementPath, expr, scope) {
|
|
295
360
|
if (hasOwnAttr(elementPath, ATTR_FIELD) || hasOwnAttr(elementPath, ATTR_FIELD_STATE)) return;
|
|
361
|
+
if (!canCarryFieldStamp(elementPath)) return;
|
|
296
362
|
if (t.isJSXEmptyExpression(expr) || !t.isExpression(expr)) return;
|
|
297
363
|
applyFieldVerdict(elementPath, classifyFieldExpr(expr, scope));
|
|
298
364
|
}
|
|
299
365
|
function stampLabelValueOrMixed(elementPath, childPaths, scope) {
|
|
300
366
|
if (hasOwnAttr(elementPath, ATTR_FIELD) || hasOwnAttr(elementPath, ATTR_FIELD_STATE)) return;
|
|
367
|
+
if (!canCarryFieldStamp(elementPath)) return;
|
|
301
368
|
const hasInvisibleSibling = childPaths.some(
|
|
302
369
|
(p) => p.isJSXExpressionContainer() && t.isExpression(p.node.expression) && isInvisibleRenderExpr(p.node.expression)
|
|
303
370
|
);
|
|
@@ -401,6 +468,7 @@ function babelPluginCollectionBinding({ types: t }, opts = {}) {
|
|
|
401
468
|
function resolveRowRoot(jsxPath, scope, depth) {
|
|
402
469
|
const tag = getTagName(jsxPath.node.openingElement);
|
|
403
470
|
if (isLowercaseTag(tag)) return { hostPath: jsxPath, scope };
|
|
471
|
+
if (forwardingComponentSet.has(tag)) return { hostPath: jsxPath, scope };
|
|
404
472
|
if (depth >= MAX_PROJECTION_DEPTH) return null;
|
|
405
473
|
const fnPath = resolveComponentDefinition(tag, jsxPath);
|
|
406
474
|
if (!fnPath) return null;
|
|
@@ -446,13 +514,42 @@ function babelPluginCollectionBinding({ types: t }, opts = {}) {
|
|
|
446
514
|
if (!innerReturn) return null;
|
|
447
515
|
return resolveFieldProjection(innerReturn, { identifiers }, depth + 1);
|
|
448
516
|
}
|
|
517
|
+
function collectConditionalJsxBranchPaths(containerPath) {
|
|
518
|
+
const exprPath = containerPath.get("expression");
|
|
519
|
+
if (Array.isArray(exprPath)) return [];
|
|
520
|
+
if (exprPath.isLogicalExpression() && (exprPath.node.operator === "&&" || exprPath.node.operator === "||")) {
|
|
521
|
+
const rightPath = exprPath.get("right");
|
|
522
|
+
return !Array.isArray(rightPath) && rightPath.isJSXElement() ? [rightPath] : [];
|
|
523
|
+
}
|
|
524
|
+
if (exprPath.isConditionalExpression()) {
|
|
525
|
+
const branches = [];
|
|
526
|
+
const consPath = exprPath.get("consequent");
|
|
527
|
+
const altPath = exprPath.get("alternate");
|
|
528
|
+
if (!Array.isArray(consPath) && consPath.isJSXElement()) branches.push(consPath);
|
|
529
|
+
if (!Array.isArray(altPath) && altPath.isJSXElement()) branches.push(altPath);
|
|
530
|
+
return branches;
|
|
531
|
+
}
|
|
532
|
+
return [];
|
|
533
|
+
}
|
|
449
534
|
function walkFieldsWithinRow(elementPath, scope) {
|
|
450
535
|
const childPaths = meaningfulChildPaths(elementPath);
|
|
451
|
-
const
|
|
536
|
+
const conditionalBranchesByChild = /* @__PURE__ */ new Map();
|
|
537
|
+
for (const p of childPaths) {
|
|
538
|
+
if (!p.isJSXExpressionContainer()) continue;
|
|
539
|
+
const branches = collectConditionalJsxBranchPaths(p);
|
|
540
|
+
if (branches.length > 0) conditionalBranchesByChild.set(p, branches);
|
|
541
|
+
}
|
|
542
|
+
const hasElementChild = childPaths.some((p) => p.isJSXElement() || conditionalBranchesByChild.has(p));
|
|
452
543
|
if (hasElementChild) {
|
|
453
544
|
for (const childPath of childPaths) {
|
|
454
|
-
if (
|
|
455
|
-
|
|
545
|
+
if (childPath.isJSXElement()) {
|
|
546
|
+
handleStructuralChild(childPath, scope);
|
|
547
|
+
continue;
|
|
548
|
+
}
|
|
549
|
+
const branches = conditionalBranchesByChild.get(childPath);
|
|
550
|
+
if (branches) {
|
|
551
|
+
for (const branchPath of branches) handleStructuralChild(branchPath, scope);
|
|
552
|
+
}
|
|
456
553
|
}
|
|
457
554
|
return;
|
|
458
555
|
}
|
|
@@ -472,6 +569,10 @@ function babelPluginCollectionBinding({ types: t }, opts = {}) {
|
|
|
472
569
|
walkFieldsWithinRow(childPath, scope);
|
|
473
570
|
return;
|
|
474
571
|
}
|
|
572
|
+
if (meaningfulChildPaths(childPath).length > 0) {
|
|
573
|
+
walkFieldsWithinRow(childPath, scope);
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
475
576
|
const projected = resolveFieldProjection(childPath, scope, 0);
|
|
476
577
|
if (!projected) return;
|
|
477
578
|
walkFieldsWithinRow(projected.hostPath, projected.scope);
|
|
@@ -482,7 +583,11 @@ function babelPluginCollectionBinding({ types: t }, opts = {}) {
|
|
|
482
583
|
if (!t.isMemberExpression(callee) || callee.computed) return;
|
|
483
584
|
if (!t.isIdentifier(callee.property, { name: "map" })) return;
|
|
484
585
|
if (!t.isExpression(callee.object)) return;
|
|
485
|
-
const
|
|
586
|
+
const calleePath = callPath.get("callee");
|
|
587
|
+
if (Array.isArray(calleePath) || !calleePath.isMemberExpression()) return;
|
|
588
|
+
const objectPath = calleePath.get("object");
|
|
589
|
+
if (Array.isArray(objectPath) || !objectPath.isExpression()) return;
|
|
590
|
+
const source = resolveSliceSource(objectPath, 0, false);
|
|
486
591
|
if (!source) return;
|
|
487
592
|
const [callback] = node.arguments;
|
|
488
593
|
if (!callback || !t.isArrowFunctionExpression(callback) && !t.isFunctionExpression(callback)) return;
|