@archie/devtools 0.0.27 → 0.0.28
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.
|
@@ -72,6 +72,50 @@ import { types, PluginObj } from '@babel/core';
|
|
|
72
72
|
* specific field OR several), because no row identity is at stake — only
|
|
73
73
|
* further field leaves.
|
|
74
74
|
*
|
|
75
|
+
* ── Forwarding-component allowlist descent (ARCH-1065 M-COV fix) ──────────
|
|
76
|
+
*
|
|
77
|
+
* D5.4 only ever sees THROUGH a component whose own source is available
|
|
78
|
+
* (module-local, co-located in the same file). Kit/imported components
|
|
79
|
+
* (`Card`, `CardContent`, `StatusBadge`, …) are opaque to the AST — the
|
|
80
|
+
* plugin has no body to project into — but many of them forward unrecognized
|
|
81
|
+
* JSX attributes onto their own root DOM element (typically via
|
|
82
|
+
* `{...rest}`), which makes them valid attribute CARRIERS even though
|
|
83
|
+
* they're not valid PROJECTION targets. `forwardingComponents` (a plugin
|
|
84
|
+
* option, see `CollectionBindingPluginOptions`) names exactly which ones.
|
|
85
|
+
*
|
|
86
|
+
* Two independent mechanisms use that allowlist:
|
|
87
|
+
*
|
|
88
|
+
* - (a2) ROW ROOT: when the `.map()` callback's own returned JSX element
|
|
89
|
+
* is an allowlisted component (`<Card key={r.id}>…</Card>` as the row
|
|
90
|
+
* root itself), the row-identity attributes stamp directly onto that
|
|
91
|
+
* component usage — NOT a projection into a separate definition, since
|
|
92
|
+
* there is none to project into. The field walk continues into the
|
|
93
|
+
* component's own JSX children with the SAME binding scope the row
|
|
94
|
+
* callback already established.
|
|
95
|
+
* - (c2-ii) LEAF: when a component usage directly hugs a field expression
|
|
96
|
+
* with no host element beneath it (`<StatusBadge>{w.status}</StatusBadge>`),
|
|
97
|
+
* the field attribute stamps onto the component itself IFF its name is
|
|
98
|
+
* allowlisted; otherwise the leaf is left unstamped — a leaf is never
|
|
99
|
+
* given an invented carrier.
|
|
100
|
+
*
|
|
101
|
+
* Separately, and regardless of allowlist membership at all — walking a
|
|
102
|
+
* component's own JSX children costs nothing, since no attribute lands
|
|
103
|
+
* anywhere until a valid carrier (host, or allowlisted leaf) is actually
|
|
104
|
+
* found — (a1) DESCENT: the field walk always continues INTO a structural
|
|
105
|
+
* child that is a component usage, as long as that usage has its own JSX
|
|
106
|
+
* children in source. `<Card><span>{w.name}</span></Card>` reaches the
|
|
107
|
+
* `<span>` (a host, stamped unconditionally) even though `Card` itself is
|
|
108
|
+
* never a stamp target unless allowlisted. This is what makes (c2-ii) safe
|
|
109
|
+
* to leave allowlist-gated: a wrapper with a host descendant never needs the
|
|
110
|
+
* wrapper itself to carry anything.
|
|
111
|
+
*
|
|
112
|
+
* The allowlist is ALWAYS an explicit option, never a built-in default list
|
|
113
|
+
* baked into this plugin — kit-manifest is the single source of truth for
|
|
114
|
+
* which components forward attributes (`forwardsDataAttributes`), and the
|
|
115
|
+
* kit build (`build-d.mjs`) is what threads that list in. An absent/empty
|
|
116
|
+
* option is exactly today's fail-closed behavior: zero component-carried
|
|
117
|
+
* stamps, byte-identical to the plugin before this mechanism existed.
|
|
118
|
+
*
|
|
75
119
|
* ── Field resolution ────────────────────────────────────────────────────
|
|
76
120
|
*
|
|
77
121
|
* Reuses `meaningfulChildren` from `babel-plugin-editor-meta.ts` (the exact
|
|
@@ -148,6 +192,27 @@ import { types, PluginObj } from '@babel/core';
|
|
|
148
192
|
interface CollectionBindingPluginOptions {
|
|
149
193
|
/** Skip files matching this pattern. @default /node_modules/ */
|
|
150
194
|
fileExclude?: RegExp;
|
|
195
|
+
/**
|
|
196
|
+
* Component names known to forward unrecognized JSX attributes onto their
|
|
197
|
+
* own root DOM element (e.g. via `{...rest}`) — the exact set that makes
|
|
198
|
+
* a component usage a valid attribute CARRIER (row-root or leaf) even
|
|
199
|
+
* though it's opaque to the AST (see the "Forwarding-component allowlist
|
|
200
|
+
* descent" section above).
|
|
201
|
+
*
|
|
202
|
+
* Sourced from kit-manifest's `forwardsDataAttributes` flags by the kit
|
|
203
|
+
* build (`build-d.mjs` passes it) — this plugin NEVER hardcodes a default
|
|
204
|
+
* list here; that would be a shadow copy of kit-manifest's own truth, and
|
|
205
|
+
* would drift the moment a kit component's forwarding behavior changed
|
|
206
|
+
* without this plugin's knowledge. Synthetic lists are fine in tests, to
|
|
207
|
+
* keep the plugin testable standalone.
|
|
208
|
+
*
|
|
209
|
+
* Absent or empty = today's fail-closed behavior: no component ever
|
|
210
|
+
* carries a stamp, byte-identical to the plugin before this option
|
|
211
|
+
* existed.
|
|
212
|
+
*
|
|
213
|
+
* @default []
|
|
214
|
+
*/
|
|
215
|
+
forwardingComponents?: string[];
|
|
151
216
|
}
|
|
152
217
|
declare function babelPluginCollectionBinding({ types: t }: {
|
|
153
218
|
types: typeof types;
|
|
@@ -72,6 +72,50 @@ import { types, PluginObj } from '@babel/core';
|
|
|
72
72
|
* specific field OR several), because no row identity is at stake — only
|
|
73
73
|
* further field leaves.
|
|
74
74
|
*
|
|
75
|
+
* ── Forwarding-component allowlist descent (ARCH-1065 M-COV fix) ──────────
|
|
76
|
+
*
|
|
77
|
+
* D5.4 only ever sees THROUGH a component whose own source is available
|
|
78
|
+
* (module-local, co-located in the same file). Kit/imported components
|
|
79
|
+
* (`Card`, `CardContent`, `StatusBadge`, …) are opaque to the AST — the
|
|
80
|
+
* plugin has no body to project into — but many of them forward unrecognized
|
|
81
|
+
* JSX attributes onto their own root DOM element (typically via
|
|
82
|
+
* `{...rest}`), which makes them valid attribute CARRIERS even though
|
|
83
|
+
* they're not valid PROJECTION targets. `forwardingComponents` (a plugin
|
|
84
|
+
* option, see `CollectionBindingPluginOptions`) names exactly which ones.
|
|
85
|
+
*
|
|
86
|
+
* Two independent mechanisms use that allowlist:
|
|
87
|
+
*
|
|
88
|
+
* - (a2) ROW ROOT: when the `.map()` callback's own returned JSX element
|
|
89
|
+
* is an allowlisted component (`<Card key={r.id}>…</Card>` as the row
|
|
90
|
+
* root itself), the row-identity attributes stamp directly onto that
|
|
91
|
+
* component usage — NOT a projection into a separate definition, since
|
|
92
|
+
* there is none to project into. The field walk continues into the
|
|
93
|
+
* component's own JSX children with the SAME binding scope the row
|
|
94
|
+
* callback already established.
|
|
95
|
+
* - (c2-ii) LEAF: when a component usage directly hugs a field expression
|
|
96
|
+
* with no host element beneath it (`<StatusBadge>{w.status}</StatusBadge>`),
|
|
97
|
+
* the field attribute stamps onto the component itself IFF its name is
|
|
98
|
+
* allowlisted; otherwise the leaf is left unstamped — a leaf is never
|
|
99
|
+
* given an invented carrier.
|
|
100
|
+
*
|
|
101
|
+
* Separately, and regardless of allowlist membership at all — walking a
|
|
102
|
+
* component's own JSX children costs nothing, since no attribute lands
|
|
103
|
+
* anywhere until a valid carrier (host, or allowlisted leaf) is actually
|
|
104
|
+
* found — (a1) DESCENT: the field walk always continues INTO a structural
|
|
105
|
+
* child that is a component usage, as long as that usage has its own JSX
|
|
106
|
+
* children in source. `<Card><span>{w.name}</span></Card>` reaches the
|
|
107
|
+
* `<span>` (a host, stamped unconditionally) even though `Card` itself is
|
|
108
|
+
* never a stamp target unless allowlisted. This is what makes (c2-ii) safe
|
|
109
|
+
* to leave allowlist-gated: a wrapper with a host descendant never needs the
|
|
110
|
+
* wrapper itself to carry anything.
|
|
111
|
+
*
|
|
112
|
+
* The allowlist is ALWAYS an explicit option, never a built-in default list
|
|
113
|
+
* baked into this plugin — kit-manifest is the single source of truth for
|
|
114
|
+
* which components forward attributes (`forwardsDataAttributes`), and the
|
|
115
|
+
* kit build (`build-d.mjs`) is what threads that list in. An absent/empty
|
|
116
|
+
* option is exactly today's fail-closed behavior: zero component-carried
|
|
117
|
+
* stamps, byte-identical to the plugin before this mechanism existed.
|
|
118
|
+
*
|
|
75
119
|
* ── Field resolution ────────────────────────────────────────────────────
|
|
76
120
|
*
|
|
77
121
|
* Reuses `meaningfulChildren` from `babel-plugin-editor-meta.ts` (the exact
|
|
@@ -148,6 +192,27 @@ import { types, PluginObj } from '@babel/core';
|
|
|
148
192
|
interface CollectionBindingPluginOptions {
|
|
149
193
|
/** Skip files matching this pattern. @default /node_modules/ */
|
|
150
194
|
fileExclude?: RegExp;
|
|
195
|
+
/**
|
|
196
|
+
* Component names known to forward unrecognized JSX attributes onto their
|
|
197
|
+
* own root DOM element (e.g. via `{...rest}`) — the exact set that makes
|
|
198
|
+
* a component usage a valid attribute CARRIER (row-root or leaf) even
|
|
199
|
+
* though it's opaque to the AST (see the "Forwarding-component allowlist
|
|
200
|
+
* descent" section above).
|
|
201
|
+
*
|
|
202
|
+
* Sourced from kit-manifest's `forwardsDataAttributes` flags by the kit
|
|
203
|
+
* build (`build-d.mjs` passes it) — this plugin NEVER hardcodes a default
|
|
204
|
+
* list here; that would be a shadow copy of kit-manifest's own truth, and
|
|
205
|
+
* would drift the moment a kit component's forwarding behavior changed
|
|
206
|
+
* without this plugin's knowledge. Synthetic lists are fine in tests, to
|
|
207
|
+
* keep the plugin testable standalone.
|
|
208
|
+
*
|
|
209
|
+
* Absent or empty = today's fail-closed behavior: no component ever
|
|
210
|
+
* carries a stamp, byte-identical to the plugin before this option
|
|
211
|
+
* existed.
|
|
212
|
+
*
|
|
213
|
+
* @default []
|
|
214
|
+
*/
|
|
215
|
+
forwardingComponents?: string[];
|
|
151
216
|
}
|
|
152
217
|
declare function babelPluginCollectionBinding({ types: t }: {
|
|
153
218
|
types: typeof types;
|
|
@@ -47,7 +47,8 @@ var CHAIN_METHODS = /* @__PURE__ */ new Set(["filter", "slice", "sort", "map"]);
|
|
|
47
47
|
var MAX_SOURCE_CHAIN_HOPS = 4;
|
|
48
48
|
var MAX_PROJECTION_DEPTH = 3;
|
|
49
49
|
function babelPluginCollectionBinding({ types: t }, opts = {}) {
|
|
50
|
-
const { fileExclude = /node_modules
|
|
50
|
+
const { fileExclude = /node_modules/, forwardingComponents = [] } = opts;
|
|
51
|
+
const forwardingComponentSet = new Set(forwardingComponents);
|
|
51
52
|
function hasOwnAttr(elementPath, name) {
|
|
52
53
|
return elementPath.node.openingElement.attributes.some(
|
|
53
54
|
(a) => t.isJSXAttribute(a) && t.isJSXIdentifier(a.name, { name })
|
|
@@ -75,6 +76,10 @@ function babelPluginCollectionBinding({ types: t }, opts = {}) {
|
|
|
75
76
|
function isLowercaseTag(tag) {
|
|
76
77
|
return tag.length > 0 && tag[0] === tag[0].toLowerCase();
|
|
77
78
|
}
|
|
79
|
+
function canCarryFieldStamp(elementPath) {
|
|
80
|
+
const tag = getTagName(elementPath.node.openingElement);
|
|
81
|
+
return isLowercaseTag(tag) || forwardingComponentSet.has(tag);
|
|
82
|
+
}
|
|
78
83
|
function getAttrName(attr) {
|
|
79
84
|
return t.isJSXIdentifier(attr.name) ? attr.name.name : `${attr.name.namespace.name}:${attr.name.name.name}`;
|
|
80
85
|
}
|
|
@@ -324,11 +329,13 @@ function babelPluginCollectionBinding({ types: t }, opts = {}) {
|
|
|
324
329
|
}
|
|
325
330
|
function stampPureLeaf(elementPath, expr, scope) {
|
|
326
331
|
if (hasOwnAttr(elementPath, ATTR_FIELD) || hasOwnAttr(elementPath, ATTR_FIELD_STATE)) return;
|
|
332
|
+
if (!canCarryFieldStamp(elementPath)) return;
|
|
327
333
|
if (t.isJSXEmptyExpression(expr) || !t.isExpression(expr)) return;
|
|
328
334
|
applyFieldVerdict(elementPath, classifyFieldExpr(expr, scope));
|
|
329
335
|
}
|
|
330
336
|
function stampLabelValueOrMixed(elementPath, childPaths, scope) {
|
|
331
337
|
if (hasOwnAttr(elementPath, ATTR_FIELD) || hasOwnAttr(elementPath, ATTR_FIELD_STATE)) return;
|
|
338
|
+
if (!canCarryFieldStamp(elementPath)) return;
|
|
332
339
|
const hasInvisibleSibling = childPaths.some(
|
|
333
340
|
(p) => p.isJSXExpressionContainer() && t.isExpression(p.node.expression) && isInvisibleRenderExpr(p.node.expression)
|
|
334
341
|
);
|
|
@@ -432,6 +439,7 @@ function babelPluginCollectionBinding({ types: t }, opts = {}) {
|
|
|
432
439
|
function resolveRowRoot(jsxPath, scope, depth) {
|
|
433
440
|
const tag = getTagName(jsxPath.node.openingElement);
|
|
434
441
|
if (isLowercaseTag(tag)) return { hostPath: jsxPath, scope };
|
|
442
|
+
if (forwardingComponentSet.has(tag)) return { hostPath: jsxPath, scope };
|
|
435
443
|
if (depth >= MAX_PROJECTION_DEPTH) return null;
|
|
436
444
|
const fnPath = resolveComponentDefinition(tag, jsxPath);
|
|
437
445
|
if (!fnPath) return null;
|
|
@@ -503,6 +511,10 @@ function babelPluginCollectionBinding({ types: t }, opts = {}) {
|
|
|
503
511
|
walkFieldsWithinRow(childPath, scope);
|
|
504
512
|
return;
|
|
505
513
|
}
|
|
514
|
+
if (meaningfulChildPaths(childPath).length > 0) {
|
|
515
|
+
walkFieldsWithinRow(childPath, scope);
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
506
518
|
const projected = resolveFieldProjection(childPath, scope, 0);
|
|
507
519
|
if (!projected) return;
|
|
508
520
|
walkFieldsWithinRow(projected.hostPath, projected.scope);
|
|
@@ -16,7 +16,8 @@ var CHAIN_METHODS = /* @__PURE__ */ new Set(["filter", "slice", "sort", "map"]);
|
|
|
16
16
|
var MAX_SOURCE_CHAIN_HOPS = 4;
|
|
17
17
|
var MAX_PROJECTION_DEPTH = 3;
|
|
18
18
|
function babelPluginCollectionBinding({ types: t }, opts = {}) {
|
|
19
|
-
const { fileExclude = /node_modules
|
|
19
|
+
const { fileExclude = /node_modules/, forwardingComponents = [] } = opts;
|
|
20
|
+
const forwardingComponentSet = new Set(forwardingComponents);
|
|
20
21
|
function hasOwnAttr(elementPath, name) {
|
|
21
22
|
return elementPath.node.openingElement.attributes.some(
|
|
22
23
|
(a) => t.isJSXAttribute(a) && t.isJSXIdentifier(a.name, { name })
|
|
@@ -44,6 +45,10 @@ function babelPluginCollectionBinding({ types: t }, opts = {}) {
|
|
|
44
45
|
function isLowercaseTag(tag) {
|
|
45
46
|
return tag.length > 0 && tag[0] === tag[0].toLowerCase();
|
|
46
47
|
}
|
|
48
|
+
function canCarryFieldStamp(elementPath) {
|
|
49
|
+
const tag = getTagName(elementPath.node.openingElement);
|
|
50
|
+
return isLowercaseTag(tag) || forwardingComponentSet.has(tag);
|
|
51
|
+
}
|
|
47
52
|
function getAttrName(attr) {
|
|
48
53
|
return t.isJSXIdentifier(attr.name) ? attr.name.name : `${attr.name.namespace.name}:${attr.name.name.name}`;
|
|
49
54
|
}
|
|
@@ -293,11 +298,13 @@ function babelPluginCollectionBinding({ types: t }, opts = {}) {
|
|
|
293
298
|
}
|
|
294
299
|
function stampPureLeaf(elementPath, expr, scope) {
|
|
295
300
|
if (hasOwnAttr(elementPath, ATTR_FIELD) || hasOwnAttr(elementPath, ATTR_FIELD_STATE)) return;
|
|
301
|
+
if (!canCarryFieldStamp(elementPath)) return;
|
|
296
302
|
if (t.isJSXEmptyExpression(expr) || !t.isExpression(expr)) return;
|
|
297
303
|
applyFieldVerdict(elementPath, classifyFieldExpr(expr, scope));
|
|
298
304
|
}
|
|
299
305
|
function stampLabelValueOrMixed(elementPath, childPaths, scope) {
|
|
300
306
|
if (hasOwnAttr(elementPath, ATTR_FIELD) || hasOwnAttr(elementPath, ATTR_FIELD_STATE)) return;
|
|
307
|
+
if (!canCarryFieldStamp(elementPath)) return;
|
|
301
308
|
const hasInvisibleSibling = childPaths.some(
|
|
302
309
|
(p) => p.isJSXExpressionContainer() && t.isExpression(p.node.expression) && isInvisibleRenderExpr(p.node.expression)
|
|
303
310
|
);
|
|
@@ -401,6 +408,7 @@ function babelPluginCollectionBinding({ types: t }, opts = {}) {
|
|
|
401
408
|
function resolveRowRoot(jsxPath, scope, depth) {
|
|
402
409
|
const tag = getTagName(jsxPath.node.openingElement);
|
|
403
410
|
if (isLowercaseTag(tag)) return { hostPath: jsxPath, scope };
|
|
411
|
+
if (forwardingComponentSet.has(tag)) return { hostPath: jsxPath, scope };
|
|
404
412
|
if (depth >= MAX_PROJECTION_DEPTH) return null;
|
|
405
413
|
const fnPath = resolveComponentDefinition(tag, jsxPath);
|
|
406
414
|
if (!fnPath) return null;
|
|
@@ -472,6 +480,10 @@ function babelPluginCollectionBinding({ types: t }, opts = {}) {
|
|
|
472
480
|
walkFieldsWithinRow(childPath, scope);
|
|
473
481
|
return;
|
|
474
482
|
}
|
|
483
|
+
if (meaningfulChildPaths(childPath).length > 0) {
|
|
484
|
+
walkFieldsWithinRow(childPath, scope);
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
475
487
|
const projected = resolveFieldProjection(childPath, scope, 0);
|
|
476
488
|
if (!projected) return;
|
|
477
489
|
walkFieldsWithinRow(projected.hostPath, projected.scope);
|