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.
- package/README.md +12 -10
- package/dist/sources/_shared/imports.d.ts +28 -0
- package/dist/sources/_shared/imports.d.ts.map +1 -1
- package/dist/sources/_shared/imports.js +62 -0
- package/dist/sources/_shared/jsx-utils.d.ts +17 -0
- package/dist/sources/_shared/jsx-utils.d.ts.map +1 -1
- package/dist/sources/_shared/jsx-utils.js +49 -1
- package/dist/sources/_shared/props.d.ts +17 -0
- package/dist/sources/_shared/props.d.ts.map +1 -1
- package/dist/sources/_shared/props.js +72 -3
- package/dist/sources/_shared/specials-utils.d.ts +49 -3
- package/dist/sources/_shared/specials-utils.d.ts.map +1 -1
- package/dist/sources/_shared/specials-utils.js +165 -5
- package/dist/sources/_shared/viewports.d.ts +17 -0
- package/dist/sources/_shared/viewports.d.ts.map +1 -0
- package/dist/sources/_shared/viewports.js +32 -0
- package/dist/sources/bloomer/deps.d.ts +17 -0
- package/dist/sources/bloomer/deps.d.ts.map +1 -0
- package/dist/sources/bloomer/deps.js +119 -0
- package/dist/sources/bloomer/index.d.ts +3 -0
- package/dist/sources/bloomer/index.d.ts.map +1 -0
- package/dist/sources/bloomer/index.js +10 -0
- package/dist/sources/bloomer/mapping.d.ts +84 -0
- package/dist/sources/bloomer/mapping.d.ts.map +1 -0
- package/dist/sources/bloomer/mapping.js +988 -0
- package/dist/sources/bloomer/responsive.d.ts +39 -0
- package/dist/sources/bloomer/responsive.d.ts.map +1 -0
- package/dist/sources/bloomer/responsive.js +259 -0
- package/dist/sources/bloomer/specials.d.ts +19 -0
- package/dist/sources/bloomer/specials.d.ts.map +1 -0
- package/dist/sources/bloomer/specials.js +774 -0
- package/dist/sources/bloomer/styles.d.ts +13 -0
- package/dist/sources/bloomer/styles.d.ts.map +1 -0
- package/dist/sources/bloomer/styles.js +17 -0
- package/dist/sources/bloomer/transform.d.ts +27 -0
- package/dist/sources/bloomer/transform.d.ts.map +1 -0
- package/dist/sources/bloomer/transform.js +826 -0
- package/dist/sources/rbx/responsive.d.ts.map +1 -1
- package/dist/sources/rbx/responsive.js +1 -31
- package/dist/sources/rbx/specials.d.ts.map +1 -1
- package/dist/sources/rbx/specials.js +14 -91
- package/dist/sources/rbx/transform.d.ts.map +1 -1
- package/dist/sources/rbx/transform.js +168 -78
- package/dist/sources/react-bulma-components/responsive.d.ts.map +1 -1
- package/dist/sources/react-bulma-components/responsive.js +2 -14
- package/dist/sources/react-bulma-components/specials.d.ts.map +1 -1
- package/dist/sources/react-bulma-components/specials.js +33 -17
- package/dist/sources/react-bulma-components/transform.d.ts.map +1 -1
- package/dist/sources/react-bulma-components/transform.js +168 -78
- package/dist/sources/registry.d.ts.map +1 -1
- package/dist/sources/registry.js +2 -0
- package/dist/types.d.ts +11 -0
- package/dist/types.d.ts.map +1 -1
- 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,
|
|
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,
|
|
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
|
|
72
|
-
*
|
|
73
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
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;
|
|
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
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =>
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
867
|
-
|
|
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,
|
|
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,
|
|
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"}
|