bestax-migrate 2.2.2 → 2.3.1

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.
Files changed (54) hide show
  1. package/README.md +12 -10
  2. package/dist/sources/_shared/imports.d.ts +28 -0
  3. package/dist/sources/_shared/imports.d.ts.map +1 -1
  4. package/dist/sources/_shared/imports.js +62 -0
  5. package/dist/sources/_shared/jsx-utils.d.ts +17 -0
  6. package/dist/sources/_shared/jsx-utils.d.ts.map +1 -1
  7. package/dist/sources/_shared/jsx-utils.js +49 -1
  8. package/dist/sources/_shared/props.d.ts +17 -0
  9. package/dist/sources/_shared/props.d.ts.map +1 -1
  10. package/dist/sources/_shared/props.js +72 -3
  11. package/dist/sources/_shared/specials-utils.d.ts +49 -3
  12. package/dist/sources/_shared/specials-utils.d.ts.map +1 -1
  13. package/dist/sources/_shared/specials-utils.js +165 -5
  14. package/dist/sources/_shared/viewports.d.ts +17 -0
  15. package/dist/sources/_shared/viewports.d.ts.map +1 -0
  16. package/dist/sources/_shared/viewports.js +32 -0
  17. package/dist/sources/bloomer/deps.d.ts +17 -0
  18. package/dist/sources/bloomer/deps.d.ts.map +1 -0
  19. package/dist/sources/bloomer/deps.js +119 -0
  20. package/dist/sources/bloomer/index.d.ts +3 -0
  21. package/dist/sources/bloomer/index.d.ts.map +1 -0
  22. package/dist/sources/bloomer/index.js +10 -0
  23. package/dist/sources/bloomer/mapping.d.ts +84 -0
  24. package/dist/sources/bloomer/mapping.d.ts.map +1 -0
  25. package/dist/sources/bloomer/mapping.js +988 -0
  26. package/dist/sources/bloomer/responsive.d.ts +39 -0
  27. package/dist/sources/bloomer/responsive.d.ts.map +1 -0
  28. package/dist/sources/bloomer/responsive.js +259 -0
  29. package/dist/sources/bloomer/specials.d.ts +19 -0
  30. package/dist/sources/bloomer/specials.d.ts.map +1 -0
  31. package/dist/sources/bloomer/specials.js +774 -0
  32. package/dist/sources/bloomer/styles.d.ts +13 -0
  33. package/dist/sources/bloomer/styles.d.ts.map +1 -0
  34. package/dist/sources/bloomer/styles.js +17 -0
  35. package/dist/sources/bloomer/transform.d.ts +27 -0
  36. package/dist/sources/bloomer/transform.d.ts.map +1 -0
  37. package/dist/sources/bloomer/transform.js +826 -0
  38. package/dist/sources/rbx/responsive.d.ts.map +1 -1
  39. package/dist/sources/rbx/responsive.js +1 -31
  40. package/dist/sources/rbx/specials.d.ts.map +1 -1
  41. package/dist/sources/rbx/specials.js +14 -91
  42. package/dist/sources/rbx/transform.d.ts.map +1 -1
  43. package/dist/sources/rbx/transform.js +168 -78
  44. package/dist/sources/react-bulma-components/responsive.d.ts.map +1 -1
  45. package/dist/sources/react-bulma-components/responsive.js +2 -14
  46. package/dist/sources/react-bulma-components/specials.d.ts.map +1 -1
  47. package/dist/sources/react-bulma-components/specials.js +33 -17
  48. package/dist/sources/react-bulma-components/transform.d.ts.map +1 -1
  49. package/dist/sources/react-bulma-components/transform.js +168 -78
  50. package/dist/sources/registry.d.ts.map +1 -1
  51. package/dist/sources/registry.js +2 -0
  52. package/dist/types.d.ts +11 -0
  53. package/dist/types.d.ts.map +1 -1
  54. package/package.json +6 -5
@@ -1 +1 @@
1
- {"version":3,"file":"responsive.d.ts","sourceRoot":"","sources":["../../../src/sources/rbx/responsive.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAE3C,OAAO,EAML,KAAK,gBAAgB,EACtB,MAAM,yBAAyB,CAAC;AAIjC,MAAM,MAAM,cAAc,GAAG,SAAS,GAAG,QAAQ,GAAG,cAAc,CAAC;AAEnE,yEAAyE;AACzE,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAG3D,CAAC;AAwVF,8DAA8D;AAC9D,wBAAgB,sBAAsB,CACpC,GAAG,EAAE,gBAAgB,EACrB,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC,EAClB,OAAO,EAAE,GAAG,EACZ,IAAI,EAAE,cAAc,GACnB,IAAI,CAKN"}
1
+ {"version":3,"file":"responsive.d.ts","sourceRoot":"","sources":["../../../src/sources/rbx/responsive.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAE3C,OAAO,EAUL,KAAK,gBAAgB,EACtB,MAAM,yBAAyB,CAAC;AAIjC,MAAM,MAAM,cAAc,GAAG,SAAS,GAAG,QAAQ,GAAG,cAAc,CAAC;AAEnE,yEAAyE;AACzE,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAG3D,CAAC;AA0TF,8DAA8D;AAC9D,wBAAgB,sBAAsB,CACpC,GAAG,EAAE,gBAAgB,EACrB,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC,EAClB,OAAO,EAAE,GAAG,EACZ,IAAI,EAAE,cAAc,GACnB,IAAI,CAKN"}
@@ -21,42 +21,12 @@
21
21
  * here — only the shape changes.
22
22
  */
23
23
  import { RESPONSIVE_BREAKPOINTS } from './mapping.js';
24
- import { addAttr, addTodo, attributesOf, makeAttr, removeAttr, } from '../_shared/jsx-utils.js';
24
+ import { addAttr, addTodo, attributesOf, attrValue, literalOf, makeAttr, objectExpressionOf, propKey, removeAttr, } from '../_shared/jsx-utils.js';
25
25
  /** Which specials opt their element into the extra breakpoint shapes. */
26
26
  export const RESPONSIVE_KINDS = {
27
27
  column: 'column',
28
28
  'column-group': 'column-group',
29
29
  };
30
- function literalOf(node) {
31
- if (!node)
32
- return undefined;
33
- if (node.type === 'StringLiteral')
34
- return node.value;
35
- if (node.type === 'NumericLiteral')
36
- return node.value;
37
- if (node.type === 'BooleanLiteral')
38
- return node.value;
39
- return undefined;
40
- }
41
- /**
42
- * Reuse a value node as a JSX attribute value: a string literal can sit bare
43
- * (`size="one-third"`), anything else needs an expression container.
44
- */
45
- function attrValue(j, node) {
46
- return node?.type === 'StringLiteral'
47
- ? j.stringLiteral(node.value)
48
- : j.jsxExpressionContainer(node);
49
- }
50
- function propKey(prop) {
51
- const key = prop.key?.name ?? prop.key?.value;
52
- return typeof key === 'string' ? key : undefined;
53
- }
54
- function objectExpressionOf(value) {
55
- return value?.type === 'JSXExpressionContainer' &&
56
- value.expression?.type === 'ObjectExpression'
57
- ? value.expression
58
- : null;
59
- }
60
30
  /**
61
31
  * One `{ value, only }` cell of the universal `responsive` prop. Returns the
62
32
  * inner `value` node, or undefined when the cell isn't the expected shape.
@@ -1 +1 @@
1
- {"version":3,"file":"specials.d.ts","sourceRoot":"","sources":["../../../src/sources/rbx/specials.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAO3C,OAAO,EAYL,KAAK,gBAAgB,EACtB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAML,KAAK,aAAa,EACnB,MAAM,8BAA8B,CAAC;AAEtC,YAAY,EAAE,aAAa,EAAE,CAAC;AAw9B9B,wBAAgB,UAAU,CACxB,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,gBAAgB,EACrB,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC,EAClB,OAAO,EAAE,GAAG,GACX,aAAa,CAIf"}
1
+ {"version":3,"file":"specials.d.ts","sourceRoot":"","sources":["../../../src/sources/rbx/specials.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAO3C,OAAO,EAYL,KAAK,gBAAgB,EACtB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAUL,KAAK,aAAa,EACnB,MAAM,8BAA8B,CAAC;AAEtC,YAAY,EAAE,aAAa,EAAE,CAAC;AAs2B9B,wBAAgB,UAAU,CACxB,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,gBAAgB,EACrB,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC,EAClB,OAAO,EAAE,GAAG,GACX,aAAa,CAIf"}
@@ -15,26 +15,9 @@
15
15
  */
16
16
  import { BADGE_PROPS, RESPONSIVE_BREAKPOINTS, TOOLTIP_PROPS, UNIVERSAL_PROPS, } from './mapping.js';
17
17
  import { addAttr, addTodo, buildJsxName, attributesOf, findAttr, jsxNameParts, literalValueOf, makeAttr, plainElement, removeAttr, resolveBooleanish, } from '../_shared/jsx-utils.js';
18
- import { alignTarget, makeStripModifierProps, mergeClassName, parseIconClasses, } from '../_shared/specials-utils.js';
18
+ import { alignTarget, applyIconProps, makeStripModifierProps, makeStructuralHelpers, mergeClassName, modifierClass, parseIconClasses, restrictAsToTargets, } from '../_shared/specials-utils.js';
19
19
  /* eslint-disable @typescript-eslint/no-explicit-any */
20
20
  const stripModifierProps = makeStripModifierProps(UNIVERSAL_PROPS, RESPONSIVE_BREAKPOINTS);
21
- /**
22
- * Turn a literal modifier prop into an `is-*` class fragment for an element
23
- * on its way to becoming plain HTML. Returns the class (or undefined) and
24
- * always removes the attribute.
25
- */
26
- function modifierClass(ctx, path, element, prop, base, where) {
27
- const attr = findAttr(element, prop);
28
- if (!attr)
29
- return base;
30
- const literal = literalValueOf(attr);
31
- removeAttr(element, attr);
32
- if (literal.kind === 'string' || literal.kind === 'number') {
33
- return `${base} is-${literal.value}`;
34
- }
35
- addTodo(ctx, path, `prop:${prop}`, `dynamic ${where} \`${prop}\`; add the matching is-* class by hand`);
36
- return base;
37
- }
38
21
  /**
39
22
  * Also strips the universal `responsive` object: responsive.ts would have
40
23
  * consumed it, but a `replaced: true` special skips that pass too, and it is
@@ -68,66 +51,11 @@ function stripHelperComponentProps(ctx, path, attrs, where) {
68
51
  return kept;
69
52
  }
70
53
  /**
71
- * rbx puts `as` on every component, but a handler that chooses its target from
72
- * a prop value can land on a bestax component that has no `as` — Media.Item
73
- * resolves to Left/Content/Right where only Left declares one, Level.Item to
74
- * Left/Right/Item where only Item does, Navbar.Item to Item/Dropdown where
75
- * only Item does. Opting into `as` in the mapping table cannot express that,
76
- * so the target has to be checked after it is picked.
54
+ * rbx's plain-element rewrites strip both the universal helper props and the
55
+ * badge/tooltip/responsive props (which would otherwise ride onto the HTML
56
+ * tag as unknown DOM attributes — see stripHelperComponentProps).
77
57
  */
78
- function restrictAsToTargets(ctx, path, element, target, allowed) {
79
- const asAttr = findAttr(element, 'as');
80
- if (!asAttr || (target && allowed.includes(target)))
81
- return;
82
- removeAttr(element, asAttr);
83
- addTodo(ctx, path, 'prop:as', `bestax's \`${target}\` has no \`as\` prop (of the targets this can resolve to, only ${allowed
84
- .map(a => `\`${a}\``)
85
- .join(' / ')} does); render the tag directly or restructure`);
86
- ctx.dirty = true;
87
- }
88
- /** Replace the element with a plain HTML tag carrying `className`. */
89
- function replaceWithPlain(ctx, path, element, tag, className, where) {
90
- const merged = mergeClassName(ctx, path, element, className, where);
91
- const rest = stripHelperComponentProps(ctx, path, stripModifierProps(ctx, path, attributesOf(element), where), where);
92
- path.replace(plainElement(ctx.j, tag, merged, rest, element.children ?? []));
93
- ctx.dirty = true;
94
- return { replaced: true };
95
- }
96
- /**
97
- * rbx wraps several components in a `*.Container` that owns the modifiers,
98
- * where bestax's component renders that wrapper itself. Move the container's
99
- * (already-converted) attributes onto its single child and replace the
100
- * container with it — otherwise both would rename to the same bestax
101
- * component and nest, which is invalid.
102
- *
103
- * Returns null when the element isn't the single-JSX-child shape, so the
104
- * caller can fall back to keeping the container.
105
- */
106
- function collapseOntoChild(ctx, path, element, where, expected) {
107
- const children = (element.children ?? []).filter((c) => !(c.type === 'JSXText' && c.value.trim() === ''));
108
- if (children.length !== 1 || children[0].type !== 'JSXElement')
109
- return null;
110
- const child = children[0];
111
- // The child must be the component this container exists to wrap. rbx lets
112
- // both of these containers hold something else — an `<iframe>` for a ratio
113
- // box, a native `<select>` — and folding onto that put Bulma modifier props
114
- // on an intrinsic element and dropped the wrapper the markup needs.
115
- const childPath = ctx.resolve?.(child.openingElement?.name);
116
- if (!childPath || childPath.join('.') !== expected)
117
- return null;
118
- for (const attr of [...attributesOf(element)]) {
119
- const name = attr.name.name;
120
- if (findAttr(child, name)) {
121
- addTodo(ctx, path, `prop:${name}`, `\`${where}\` folded into its child, but both set \`${name}\`; the child's value was kept — reconcile by hand`);
122
- continue;
123
- }
124
- removeAttr(element, attr);
125
- addAttr(child, attr);
126
- }
127
- path.replace(child);
128
- ctx.dirty = true;
129
- return { replaced: true };
130
- }
58
+ const { replaceWithPlain, collapseOntoChild } = makeStructuralHelpers((ctx, path, attrs, where) => stripHelperComponentProps(ctx, path, stripModifierProps(ctx, path, attrs, where), where));
131
59
  const SPECIALS = {
132
60
  /**
133
61
  * rbx's `Heading` is Bulma's `.heading` label (small caps), not a title —
@@ -171,6 +99,11 @@ const SPECIALS = {
171
99
  else if (resolved === 'falsy') {
172
100
  removeAttr(element, spacedAttr);
173
101
  }
102
+ else if (findAttr(element, 'isSpaced')) {
103
+ // `isSpaced` is already set (rbx passes unknown props through), so
104
+ // renaming onto it would emit the attribute twice.
105
+ addTodo(ctx, path, 'prop:spaced', 'dynamic `spaced` alongside an `isSpaced` already on this element; bestax has one prop for both — reconcile by hand');
106
+ }
174
107
  else {
175
108
  // Dynamic: keep the expression, just rename the prop.
176
109
  spacedAttr.name = ctx.j.jsxIdentifier('isSpaced');
@@ -195,20 +128,10 @@ const SPECIALS = {
195
128
  const literal = classAttr ? literalValueOf(classAttr) : undefined;
196
129
  if (literal?.kind === 'string') {
197
130
  const parsed = parseIconClasses(literal.value);
198
- if (parsed) {
199
- addAttr(element, makeAttr(ctx.j, 'name', parsed.name));
200
- if (parsed.library) {
201
- addAttr(element, makeAttr(ctx.j, 'library', parsed.library));
202
- }
203
- if (parsed.variant) {
204
- addAttr(element, makeAttr(ctx.j, 'variant', parsed.variant));
205
- }
206
- element.children = [];
207
- if (element.openingElement) {
208
- element.openingElement.selfClosing = true;
209
- element.closingElement = null;
210
- }
211
- ctx.dirty = true;
131
+ // An app's own class on the <i> has no home on bestax's glyph, so
132
+ // the child is kept (and flagged below) rather than losing it.
133
+ if (parsed && parsed.leftovers.length === 0) {
134
+ applyIconProps(ctx, path, element, parsed);
212
135
  return {};
213
136
  }
214
137
  }
@@ -1 +1 @@
1
- {"version":3,"file":"transform.d.ts","sourceRoot":"","sources":["../../../src/sources/rbx/transform.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AA0DvD,MAAM,CAAC,OAAO,UAAU,SAAS,CAC/B,QAAQ,EAAE,QAAQ,EAClB,GAAG,EAAE,GAAG,EACR,OAAO,GAAE,gBAAqB,GAC7B,MAAM,GAAG,SAAS,CAu/BpB"}
1
+ {"version":3,"file":"transform.d.ts","sourceRoot":"","sources":["../../../src/sources/rbx/transform.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AA2DvD,MAAM,CAAC,OAAO,UAAU,SAAS,CAC/B,QAAQ,EAAE,QAAQ,EAClB,GAAG,EAAE,GAAG,EACR,OAAO,GAAE,gBAAqB,GAC7B,MAAM,GAAG,SAAS,CAmoCpB"}
@@ -22,7 +22,7 @@
22
22
  import { BADGE_PROPS, MAPPING, TOOLTIP_PROPS, UNIVERSAL_PROPS, resolveMapping, } from './mapping.js';
23
23
  import { addTodo, attributesOf, findAttr, jsxNameParts, literalValueOf, removeAttr, renameElement, } from '../_shared/jsx-utils.js';
24
24
  import { applyPropAction, applyUniversalProps } from '../_shared/props.js';
25
- import { collectBoundNames, makeReserve, nameOf, prefersTabs, resolvesToBinding, } from '../_shared/imports.js';
25
+ import { collectBoundNames, makeAliasRegistry, makeReserve, nameOf, prefersTabs, resolvesToBinding, } from '../_shared/imports.js';
26
26
  import { flattenResponsiveProps, RESPONSIVE_KINDS } from './responsive.js';
27
27
  import { runSpecial } from './specials.js';
28
28
  /* eslint-disable @typescript-eslint/no-explicit-any */
@@ -108,6 +108,21 @@ export default function transform(fileInfo, api, options = {}) {
108
108
  addTodo(ctx, path, 'imports', `\`${source}\` reaches into rbx's internals; bestax's equivalents are its \`Theme\` and \`ConfigProvider\` helpers (https://bestax.io/docs/api/helpers/theme)`);
109
109
  }
110
110
  });
111
+ // A barrel re-export reaches the library without an import declaration,
112
+ // so nothing above saw it; it cannot be rewritten (a compound bestax
113
+ // target has no single name to re-export) and must not vanish silently.
114
+ root
115
+ .find(j.ExportNamedDeclaration)
116
+ .filter(p => String(p.node.source?.value ?? '') === RBX)
117
+ .forEach(p => {
118
+ addTodo(ctx, p, 'imports', `re-exports from '${RBX}' were not migrated; import the components, migrate them, then re-export the bestax ones by hand`);
119
+ });
120
+ root
121
+ .find(j.ExportAllDeclaration)
122
+ .filter(p => String(p.node.source?.value ?? '') === RBX)
123
+ .forEach(p => {
124
+ addTodo(ctx, p, 'imports', `\`export * from '${RBX}'\` was not migrated; re-export the bestax components you need by hand`);
125
+ });
111
126
  // ---- 1a. Stylesheet imports (mode-driven) -----------------------------
112
127
  // `bestax` (default): everything converges on the recommended combined
113
128
  // bundle. `bulma`: plain Bulma v1 CSS plus the separate extras file.
@@ -199,68 +214,20 @@ export default function transform(fileInfo, api, options = {}) {
199
214
  // The scope each binding was collected in, so a reference can be checked
200
215
  // against the binding it actually resolves to rather than matched by name.
201
216
  const programScope = root.find(j.Program).paths()[0]?.scope;
202
- // Aliases are keyed by the AST node that owns the declaring scope, not by
203
- // bare name. Two functions can each destructure `Header` from a different
204
- // component; a name-keyed map let the second overwrite the first, and both
205
- // declarations were then pruned while both `<Header…>` references were left
206
- // resolving to nothing.
207
- //
208
- // Resolution walks the reference's ANCESTORS rather than consulting
209
- // `scope.lookup`, because by the time references are rewritten the alias
210
- // pass has already pruned the declaration -- so the binding is gone from
211
- // the scope table, and `path.scope` objects are not identity-stable across
212
- // traversals either.
213
- const aliasesByOwner = new Map();
214
- const ownersFor = (name) => {
215
- const owners = [];
216
- for (const [owner, perScope] of aliasesByOwner) {
217
- if (perScope.has(name))
218
- owners.push(owner);
219
- }
220
- return owners;
221
- };
222
- const aliasAt = (name, at) => {
223
- const owners = ownersFor(name);
224
- if (owners.length === 0)
225
- return undefined;
226
- // Without a reference location there is nothing to walk, so the only
227
- // owner is the best answer available. WITH one, always walk: an unrelated
228
- // `Header` parameter in another function must not resolve to the alias
229
- // merely because the alias is the only one by that name. A single-owner
230
- // shortcut here rewrote such a parameter to `{ Header: Card.Header }`,
231
- // which is not even valid syntax.
232
- if (at === undefined) {
233
- return owners.length === 1
234
- ? aliasesByOwner.get(owners[0]).get(name)
235
- : undefined;
236
- }
237
- let cursor = at;
238
- while (cursor) {
239
- if (cursor.node !== undefined) {
240
- const hit = aliasesByOwner.get(cursor.node)?.get(name);
241
- if (hit) {
242
- // The walk finds the nearest OWNER, but a binding can intervene
243
- // between the reference and that owner: `function F(Header)` under
244
- // a module-level `const { Header } = Card` shadows the alias, and
245
- // resolving through it rewrote the parameter itself to
246
- // `F(Card.Header)`. If the scope table still binds the name
247
- // somewhere other than the owner, that nearer binding wins. (After
248
- // the alias declaration is pruned the lookup returns nothing for
249
- // the alias itself, which is the case this must keep allowing.)
250
- const declaring = at.scope?.lookup(name)?.path?.node;
251
- if (declaring !== undefined &&
252
- declaring !== null &&
253
- declaring !== cursor.node) {
254
- return undefined;
255
- }
256
- return hit;
257
- }
258
- }
259
- cursor = cursor.parent;
260
- }
261
- return undefined;
262
- };
263
- const anyAlias = (name) => ownersFor(name).length > 0;
217
+ const { register: registerAlias, aliasAt, anyAlias } = makeAliasRegistry();
218
+ /**
219
+ * Locals a re-export names. Pruning the declaration that binds one of them
220
+ * (`const { Header } = Card; export { Header };`) leaves the specifier
221
+ * pointing at nothing, so those declarations are kept: the value-reference
222
+ * pass rewrites their initialiser to the bestax component, and the export
223
+ * goes on re-exporting the same member.
224
+ */
225
+ const reExported = new Set();
226
+ root.find(j.ExportSpecifier).forEach(p => {
227
+ const local = p.node.local ?? p.node.exported;
228
+ if (local?.type === 'Identifier')
229
+ reExported.add(local.name);
230
+ });
264
231
  // ---- 1b. Resolve `const { Item } = Card` destructuring -----------------
265
232
  root.find(j.VariableDeclarator).forEach(path => {
266
233
  const node = path.node;
@@ -283,13 +250,8 @@ export default function transform(fileInfo, api, options = {}) {
283
250
  prop.value?.type === 'Identifier') {
284
251
  const owner = path.scope
285
252
  ?.path?.node;
286
- let perScope = aliasesByOwner.get(owner);
287
- if (!perScope) {
288
- perScope = new Map();
289
- aliasesByOwner.set(owner, perScope);
290
- }
291
253
  const target = [...basePath, prop.key.name];
292
- perScope.set(prop.value.name, target);
254
+ registerAlias(owner, prop.value.name, target);
293
255
  aliases.set(prop.value.name, target);
294
256
  // Pruning the declaration is only safe if every use of the alias will
295
257
  // actually be rewritten. For a name the table cannot map, the JSX is
@@ -310,7 +272,10 @@ export default function transform(fileInfo, api, options = {}) {
310
272
  allResolved = false;
311
273
  }
312
274
  }
313
- if (allResolved) {
275
+ const exportsAnAlias = node.id.properties.some((prop) => (prop.type === 'ObjectProperty' || prop.type === 'Property') &&
276
+ prop.value?.type === 'Identifier' &&
277
+ reExported.has(prop.value.name));
278
+ if (allResolved && !exportsAnAlias) {
314
279
  const declaration = path.parent;
315
280
  if (declaration?.node?.type === 'VariableDeclaration' &&
316
281
  declaration.node.declarations.length === 1) {
@@ -426,6 +391,16 @@ export default function transform(fileInfo, api, options = {}) {
426
391
  bound.add(head);
427
392
  });
428
393
  ctx.reserve = makeReserve(ctx, bound);
394
+ // Names the passes actually asked for. `ctx.needed` is also seeded from an
395
+ // existing bestax import's specifiers below (so JSX reuses their locals),
396
+ // and that seeding alone must not turn a type-only specifier into a value
397
+ // import nobody needed.
398
+ const requested = new Set();
399
+ const baseReserve = ctx.reserve;
400
+ ctx.reserve = root => {
401
+ requested.add(root);
402
+ return baseReserve(root);
403
+ };
429
404
  // Merge with an existing bestax import: reuse its locals verbatim.
430
405
  // Only a declaration that already uses NAMED specifiers can absorb more of
431
406
  // them. `import * as Bestax from '…'` cannot: a namespace specifier may not
@@ -434,13 +409,24 @@ export default function transform(fileInfo, api, options = {}) {
434
409
  const existingBestax = root
435
410
  .find(j.ImportDeclaration, { source: { value: BESTAX } })
436
411
  .paths()
437
- .find(p => (p.node.specifiers ?? []).every((spec) => spec.type === 'ImportSpecifier'));
412
+ .find(p =>
413
+ // A type-only declaration cannot take a value specifier: merging a
414
+ // component into `import type { … }` erases it at runtime.
415
+ p.node.importKind !== 'type' &&
416
+ (p.node.specifiers ?? []).every((spec) => spec.type === 'ImportSpecifier'));
438
417
  const preExistingImports = new Set();
439
418
  if (existingBestax) {
440
419
  for (const spec of existingBestax.node.specifiers ?? []) {
441
420
  if (spec.type === 'ImportSpecifier' && spec.local) {
421
+ // The local name is reused either way, so the JSX never needs an
422
+ // alias. But an inline `type` specifier (`import { type Box }`) is
423
+ // not a value binding: it is not counted as already imported, so the
424
+ // component is written as a value below — onto this specifier, which
425
+ // keeps its name, rather than as a duplicate.
442
426
  ctx.needed.set(nameOf(spec.imported), nameOf(spec.local));
443
- preExistingImports.add(nameOf(spec.imported));
427
+ if (spec.importKind !== 'type') {
428
+ preExistingImports.add(nameOf(spec.imported));
429
+ }
444
430
  }
445
431
  }
446
432
  }
@@ -633,6 +619,10 @@ export default function transform(fileInfo, api, options = {}) {
633
619
  parentType === 'JSXMemberExpression') {
634
620
  return;
635
621
  }
622
+ // A TS qualified name (`RBC.ButtonProps` in a type position) is erased at
623
+ // runtime, so it is not a value use of the namespace at all.
624
+ if (parentType === 'TSQualifiedName')
625
+ return;
636
626
  // A namespace binding in a real value position. `rbx.Button` is still a
637
627
  // mappable component reference, so fall through to the member-expression
638
628
  // branch below with an empty prefix (the namespace itself is not part of
@@ -643,6 +633,46 @@ export default function transform(fileInfo, api, options = {}) {
643
633
  parentNode.object === path.node &&
644
634
  !parentNode.computed)) {
645
635
  namespaceStillReferenced = true;
636
+ addTodo(ctx, path, 'value-reference', `\`${name}\` is the library's namespace import used as a plain value (destructured, or passed around); nothing reached through it was migrated — reference its components as \`${name}.Component\` or import them by name, then re-run`);
637
+ return;
638
+ }
639
+ // Members, keys and signatures name a thing; they do not reference the
640
+ // import. An export specifier does, and is handled on its own: a flat
641
+ // target keeps the public name (\`export { SubTitle as Subtitle }\`), a
642
+ // dotted one cannot be re-exported under a member name and is flagged.
643
+ if (parentType === 'ExportSpecifier') {
644
+ if (parentNode.local !== path.node)
645
+ return;
646
+ const exportedName = nameOf(parentNode.exported ?? parentNode.local);
647
+ const mapping = aliasPath ? resolveMapping(aliasPath) : MAPPING[imported];
648
+ if (mapping &&
649
+ mapping.status !== 'todo' &&
650
+ mapping.target &&
651
+ !mapping.target.includes('.')) {
652
+ const local = ctx.reserve(mapping.target);
653
+ // Replace the node: recast reprints a specifier whose identifiers were
654
+ // swapped in place in its original shorthand form, dropping the `as`.
655
+ path.parent.replace(j.exportSpecifier.from({
656
+ local: j.identifier(local),
657
+ exported: j.identifier(exportedName),
658
+ }));
659
+ ctx.dirty = true;
660
+ return;
661
+ }
662
+ ctx.retained.add(imported);
663
+ addTodo(ctx, path, 'value-reference', `\`${name}\` is re-exported; ${mapping?.target ? `its bestax counterpart \`${mapping.target}\` is a member of \`${mapping.target.split('.')[0]}\` and cannot be re-exported under this name` : 'it has no bestax counterpart to re-export'} — migrate the consumers by hand`);
664
+ return;
665
+ }
666
+ if (parentType === 'TSEnumMember' ||
667
+ ((parentType === 'ObjectMethod' ||
668
+ parentType === 'ClassMethod' ||
669
+ parentType === 'ClassProperty' ||
670
+ parentType === 'PropertyDefinition' ||
671
+ parentType === 'MethodDefinition' ||
672
+ parentType === 'TSPropertySignature' ||
673
+ parentType === 'TSMethodSignature') &&
674
+ parentNode.key === path.node &&
675
+ !parentNode.computed)) {
646
676
  return;
647
677
  }
648
678
  // Non-reference positions: member property names and object keys.
@@ -835,15 +865,61 @@ export default function transform(fileInfo, api, options = {}) {
835
865
  if (imports.size > 0) {
836
866
  const retainedNames = [...ctx.retained].sort((a, b) => a.localeCompare(b));
837
867
  const freshNames = [...ctx.needed.entries()]
838
- .filter(([imported]) => !preExistingImports.has(imported))
868
+ .filter(([imported]) => requested.has(imported) && !preExistingImports.has(imported))
839
869
  .sort((a, b) => a[0].localeCompare(b[0]));
840
870
  const bestaxImport = freshNames.length > 0
841
871
  ? j.importDeclaration(freshNames.map(([imported, local]) => j.importSpecifier(j.identifier(imported), j.identifier(local))), j.stringLiteral(BESTAX))
842
872
  : null;
873
+ /**
874
+
875
+ * A pruned declaration takes its comments with it — a licence header, an
876
+
877
+ * eslint directive. Hand them to what replaces it, or to the next statement.
878
+
879
+ */
880
+ const carryComments = (node, importPath) => {
881
+ const comments = node.comments ?? [];
882
+ if (comments.length === 0)
883
+ return;
884
+ const body = importPath.parent?.node?.body ?? [];
885
+ const index = body.indexOf(node);
886
+ // The fresh specifiers are merged INTO an existing bestax import when there
887
+ // is one, and the `bestaxImport` node is then discarded — so the comments
888
+ // have to follow the declaration that survives, not the one that does not.
889
+ const carrier = (existingBestax ? existingBestax.node : bestaxImport) ??
890
+ (index >= 0 ? body[index + 1] : undefined);
891
+ if (carrier) {
892
+ carrier.comments = [...comments, ...(carrier.comments ?? [])];
893
+ }
894
+ else if (index > 0) {
895
+ // The import was the last statement: the comments stay where they
896
+ // were, after what precedes it.
897
+ const previous = body[index - 1];
898
+ previous.comments = [
899
+ ...(previous.comments ?? []),
900
+ ...comments.map((c) => ({
901
+ ...c,
902
+ leading: false,
903
+ trailing: true,
904
+ })),
905
+ ];
906
+ }
907
+ else {
908
+ // The import was the only statement: the comments become the file's.
909
+ const program = importPath.parent?.node;
910
+ if (program)
911
+ program.comments = [...comments, ...(program.comments ?? [])];
912
+ }
913
+ node.comments = [];
914
+ };
843
915
  let inserted = false;
844
916
  // A retained rbx specifier must never collide with a bestax import local
845
917
  // (possible when one component is both JSX-migrated and value-retained).
846
- const bestaxLocals = new Set(ctx.needed.values());
918
+ // Only a VALUE local can collide at runtime: a type-only specifier the
919
+ // seeding above recorded, and nothing promoted, binds no value.
920
+ const bestaxLocals = new Set([...ctx.needed.entries()]
921
+ .filter(([imported]) => requested.has(imported) || preExistingImports.has(imported))
922
+ .map(([, local]) => local));
847
923
  for (const path of rbxImportPaths) {
848
924
  const node = path.node;
849
925
  const keepSpecifiers = (node.specifiers ?? []).filter((spec) => {
@@ -862,10 +938,23 @@ export default function transform(fileInfo, api, options = {}) {
862
938
  });
863
939
  if (!inserted) {
864
940
  if (existingBestax && bestaxImport) {
865
- existingBestax.node.specifiers = [
866
- ...(existingBestax.node.specifiers ?? []),
867
- ...bestaxImport.specifiers,
868
- ];
941
+ const current = existingBestax.node.specifiers ?? [];
942
+ const appended = [];
943
+ for (const fresh of bestaxImport.specifiers) {
944
+ // A type-only specifier for the same name becomes the value
945
+ // import (a value import carries the type too); appending a
946
+ // second `Box` beside `type Box` would be a duplicate identifier.
947
+ const typeOnly = current.find((spec) => spec.type === 'ImportSpecifier' &&
948
+ spec.importKind === 'type' &&
949
+ nameOf(spec.imported) === nameOf(fresh.imported));
950
+ if (typeOnly) {
951
+ typeOnly.importKind = null;
952
+ }
953
+ else {
954
+ appended.push(fresh);
955
+ }
956
+ }
957
+ existingBestax.node.specifiers = [...current, ...appended];
869
958
  }
870
959
  else if (bestaxImport) {
871
960
  path.insertBefore(bestaxImport);
@@ -892,6 +981,7 @@ export default function transform(fileInfo, api, options = {}) {
892
981
  }
893
982
  }
894
983
  else {
984
+ carryComments(node, path);
895
985
  path.prune();
896
986
  }
897
987
  ctx.dirty = true;
@@ -1 +1 @@
1
- {"version":3,"file":"responsive.d.ts","sourceRoot":"","sources":["../../../src/sources/react-bulma-components/responsive.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAE3C,OAAO,EAML,KAAK,gBAAgB,EACtB,MAAM,yBAAyB,CAAC;AAIjC,MAAM,MAAM,cAAc,GAAG,SAAS,GAAG,SAAS,GAAG,QAAQ,CAAC;AAkB9D,wBAAgB,sBAAsB,CACpC,GAAG,EAAE,gBAAgB,EACrB,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC,EAClB,OAAO,EAAE,GAAG,EACZ,IAAI,EAAE,cAAc,GACnB,IAAI,CAkHN"}
1
+ {"version":3,"file":"responsive.d.ts","sourceRoot":"","sources":["../../../src/sources/react-bulma-components/responsive.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAE3C,OAAO,EAOL,KAAK,gBAAgB,EACtB,MAAM,yBAAyB,CAAC;AAIjC,MAAM,MAAM,cAAc,GAAG,SAAS,GAAG,SAAS,GAAG,QAAQ,CAAC;AAS9D,wBAAgB,sBAAsB,CACpC,GAAG,EAAE,gBAAgB,EACrB,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC,EAClB,OAAO,EAAE,GAAG,EACZ,IAAI,EAAE,cAAc,GACnB,IAAI,CAkHN"}
@@ -11,25 +11,13 @@
11
11
  * TODO. Non-object or dynamic values get a TODO.
12
12
  */
13
13
  import { RESPONSIVE_BREAKPOINTS } from './mapping.js';
14
- import { addAttr, addTodo, attributesOf, makeAttr, removeAttr, } from '../_shared/jsx-utils.js';
14
+ import { addAttr, addTodo, attributesOf, literalOf, makeAttr, removeAttr, } from '../_shared/jsx-utils.js';
15
15
  const TEXT_ALIGN_MAP = {
16
16
  center: 'centered',
17
17
  justify: 'justified',
18
18
  left: 'left',
19
19
  right: 'right',
20
20
  };
21
- function literalOf(prop) {
22
- const v = prop.value;
23
- if (!v)
24
- return undefined;
25
- if (v.type === 'StringLiteral')
26
- return v.value;
27
- if (v.type === 'NumericLiteral')
28
- return v.value;
29
- if (v.type === 'BooleanLiteral')
30
- return v.value;
31
- return undefined;
32
- }
33
21
  export function flattenResponsiveProps(ctx, path, element, kind) {
34
22
  const { j } = ctx;
35
23
  for (const attr of [...attributesOf(element)]) {
@@ -57,7 +45,7 @@ export function flattenResponsiveProps(ctx, path, element, kind) {
57
45
  continue;
58
46
  }
59
47
  const key = prop.key?.name ?? prop.key?.value;
60
- const literal = literalOf(prop);
48
+ const literal = literalOf(prop.value);
61
49
  if (key === 'display' && typeof literal === 'string') {
62
50
  if (literal === 'hidden') {
63
51
  addAttr(element, makeAttr(j, `visibility${suffix}`, 'hidden'));
@@ -1 +1 @@
1
- {"version":3,"file":"specials.d.ts","sourceRoot":"","sources":["../../../src/sources/react-bulma-components/specials.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAE3C,OAAO,EAWL,KAAK,gBAAgB,EACtB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAML,KAAK,aAAa,EACnB,MAAM,8BAA8B,CAAC;AAEtC,YAAY,EAAE,aAAa,EAAE,CAAC;AAqwB9B,wBAAgB,UAAU,CACxB,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,gBAAgB,EACrB,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC,EAClB,OAAO,EAAE,GAAG,GACX,aAAa,CAIf;AAED,kEAAkE;AAClE,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,GAAG,QAAQ,CAGjE,CAAC"}
1
+ {"version":3,"file":"specials.d.ts","sourceRoot":"","sources":["../../../src/sources/react-bulma-components/specials.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAE3C,OAAO,EAWL,KAAK,gBAAgB,EACtB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAOL,KAAK,aAAa,EACnB,MAAM,8BAA8B,CAAC;AAEtC,YAAY,EAAE,aAAa,EAAE,CAAC;AAgxB9B,wBAAgB,UAAU,CACxB,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,gBAAgB,EACrB,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC,EAClB,OAAO,EAAE,GAAG,GACX,aAAa,CAIf;AAED,kEAAkE;AAClE,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,GAAG,QAAQ,CAGjE,CAAC"}