bestax-migrate 2.11.0 → 2.12.0
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/sources/bulma-classes/class-map.d.ts +6 -0
- package/dist/sources/bulma-classes/class-map.d.ts.map +1 -1
- package/dist/sources/bulma-classes/class-map.js +10 -6
- package/dist/sources/bulma-classes/plan.d.ts +18 -0
- package/dist/sources/bulma-classes/plan.d.ts.map +1 -1
- package/dist/sources/bulma-classes/plan.js +50 -6
- package/dist/sources/bulma-classes/transform.d.ts.map +1 -1
- package/dist/sources/bulma-classes/transform.js +360 -28
- package/package.json +4 -2
|
@@ -29,6 +29,12 @@ export interface Modifier {
|
|
|
29
29
|
readonly writes: readonly PropWrite[];
|
|
30
30
|
/** The raw tags the writes are exact on (Title's size picks its heading). */
|
|
31
31
|
readonly tagIn?: readonly string[];
|
|
32
|
+
/**
|
|
33
|
+
* The prop renders the class for `true` alone, not for any truthy value
|
|
34
|
+
* (`Field`'s `grouped`), so a condition a joiner adds it under can't be
|
|
35
|
+
* handed to the prop as it is.
|
|
36
|
+
*/
|
|
37
|
+
readonly onlyTrue?: boolean;
|
|
32
38
|
}
|
|
33
39
|
/**
|
|
34
40
|
* - `mapped`: converts to `target`.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"class-map.d.ts","sourceRoot":"","sources":["../../../src/sources/bulma-classes/class-map.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,uCAAuC;IACvC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,qEAAqE;IACrE,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED,MAAM,MAAM,MAAM,GAChB;IAAE,QAAQ,CAAC,EAAE,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,GAAG;IAAE,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,CAAC;AAE9E,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAE1D;AAED,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,MAAM,EAAE,SAAS,SAAS,EAAE,CAAC;IACtC,6EAA6E;IAC7E,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"class-map.d.ts","sourceRoot":"","sources":["../../../src/sources/bulma-classes/class-map.ts"],"names":[],"mappings":"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"}
|
|
@@ -621,12 +621,16 @@ export const ROOTS = {
|
|
|
621
621
|
tag: 'div',
|
|
622
622
|
// `has-addons-centered`, `is-grouped-right` and the like stay classes:
|
|
623
623
|
// the prop values that render them render `has-addons` too.
|
|
624
|
-
modifiers:
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
624
|
+
modifiers: {
|
|
625
|
+
...flags({
|
|
626
|
+
'is-horizontal': 'horizontal',
|
|
627
|
+
'has-addons': 'hasAddons',
|
|
628
|
+
'is-narrow': 'narrow',
|
|
629
|
+
}),
|
|
630
|
+
// `grouped` renders `is-grouped` for `true` and its three layouts, and
|
|
631
|
+
// for nothing else truthy.
|
|
632
|
+
'is-grouped': { writes: [{ prop: 'grouped' }], onlyTrue: true },
|
|
633
|
+
},
|
|
630
634
|
wrapsChildren: {
|
|
631
635
|
in: 'field-body',
|
|
632
636
|
unless: ['Field.Label', 'Field.Body'],
|
|
@@ -13,6 +13,19 @@ export interface ElementFacts {
|
|
|
13
13
|
tag: string;
|
|
14
14
|
/** The static className, split on whitespace, in order. */
|
|
15
15
|
tokens: readonly string[];
|
|
16
|
+
/**
|
|
17
|
+
* For a `className` a class joiner builds (`clsx('button', busy &&
|
|
18
|
+
* 'is-loading')`), the classes each condition adds, one list per condition;
|
|
19
|
+
* `tokens` then holds the ones it always adds. Set only when the codemod
|
|
20
|
+
* can read every argument.
|
|
21
|
+
*/
|
|
22
|
+
conditional?: readonly (readonly string[])[];
|
|
23
|
+
/**
|
|
24
|
+
* Indexes into `conditional` of conditions that may have side effects (a
|
|
25
|
+
* call, an assignment). A condition that becomes a prop is evaluated before
|
|
26
|
+
* the ones left in the call, so it passes them only when neither is one.
|
|
27
|
+
*/
|
|
28
|
+
impure?: readonly number[];
|
|
16
29
|
/**
|
|
17
30
|
* The element's other attributes: a string value, `true` for a bare
|
|
18
31
|
* attribute, or null for an expression.
|
|
@@ -115,6 +128,11 @@ export interface Conversion {
|
|
|
115
128
|
* they go, and the element closes itself.
|
|
116
129
|
*/
|
|
117
130
|
rendersChildren?: true;
|
|
131
|
+
/**
|
|
132
|
+
* Conditional classes (`ElementFacts.conditional`) that become a boolean
|
|
133
|
+
* prop set to their condition, each once. The rest stay in the joiner call.
|
|
134
|
+
*/
|
|
135
|
+
conditional?: Array<[prop: string, token: string]>;
|
|
118
136
|
}
|
|
119
137
|
export interface Plan {
|
|
120
138
|
conversion: Conversion | null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plan.d.ts","sourceRoot":"","sources":["../../../src/sources/bulma-classes/plan.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAiBH,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,2DAA2D;IAC3D,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IAC1B;;;OAGG;IACH,UAAU,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAAC,CAAC;IACtD,SAAS,EAAE,OAAO,CAAC;IACnB,MAAM,EAAE,OAAO,CAAC;IAChB,4EAA4E;IAC5E,WAAW,EAAE,OAAO,CAAC;IACrB;;;;OAIG;IACH,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;OAGG;IACH,SAAS,CAAC,EAAE,UAAU,CAAC;IACvB;;;;OAIG;IACH,aAAa,CAAC,EAAE,SAAS,UAAU,EAAE,CAAC;IACtC,qEAAqE;IACrE,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,qEAAqE;IACrE,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC;;;OAGG;IACH,gBAAgB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ;;;OAGG;IACH,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAAC;IAClC;;;OAGG;IACH,UAAU,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,IAAI,CAAC,CAAC;IAC/D,SAAS,EAAE,OAAO,CAAC;IACnB;;;OAGG;IACH,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,IAAI;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,UAAU;IACzB,0CAA0C;IAC1C,MAAM,EAAE,MAAM,CAAC;IACf,6DAA6D;IAC7D,KAAK,EAAE,KAAK,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC;IACnD,4DAA4D;IAC5D,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,mEAAmE;IACnE,IAAI,EAAE,MAAM,EAAE,CAAC;IACf;;;OAGG;IACH,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB;;;;OAIG;IACH,OAAO,CAAC,EAAE;QACR,oEAAoE;QACpE,KAAK,EAAE,KAAK,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC;QACnD,4DAA4D;QAC5D,OAAO,EAAE,KAAK,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,CAAC,CAAC,CAAC;QAC3C,qDAAqD;QACrD,IAAI,EAAE,MAAM,EAAE,CAAC;KAChB,CAAC;IACF;;;OAGG;IACH,eAAe,CAAC,EAAE,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"plan.d.ts","sourceRoot":"","sources":["../../../src/sources/bulma-classes/plan.ts"],"names":[],"mappings":"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"}
|
|
@@ -42,7 +42,13 @@ function tagsItReaches(entry) {
|
|
|
42
42
|
export function plan(facts) {
|
|
43
43
|
const todos = [];
|
|
44
44
|
const { tag, tokens } = facts;
|
|
45
|
-
|
|
45
|
+
// Every class the element may carry: its fixed ones, and any a condition
|
|
46
|
+
// adds, which the element has to be right with as well as without.
|
|
47
|
+
const carried = [
|
|
48
|
+
...new Set([...tokens, ...(facts.conditional ?? []).flat()]),
|
|
49
|
+
];
|
|
50
|
+
const mayCarry = (token) => carried.includes(token);
|
|
51
|
+
for (const token of carried) {
|
|
46
52
|
const hint = legacyHint(token);
|
|
47
53
|
if (hint)
|
|
48
54
|
todos.push({ rule: ruleId('legacy', token), message: hint });
|
|
@@ -50,7 +56,7 @@ export function plan(facts) {
|
|
|
50
56
|
// A family this source leaves as markup keeps its element as markup, even
|
|
51
57
|
// beside a class it would convert (`navbar box`): the family's parts carry no
|
|
52
58
|
// TODO of their own, so its outermost class is the one place it is flagged.
|
|
53
|
-
const family =
|
|
59
|
+
const family = carried.find(token => {
|
|
54
60
|
const found = rootFor(token);
|
|
55
61
|
return found?.status === 'todo' && !found.part;
|
|
56
62
|
});
|
|
@@ -74,7 +80,7 @@ export function plan(facts) {
|
|
|
74
80
|
else {
|
|
75
81
|
// A root the table does not convert keeps the element as markup, so a
|
|
76
82
|
// later pass (or a person) still finds it as the class it is.
|
|
77
|
-
if (
|
|
83
|
+
if (carried.some(token => rootFor(token))) {
|
|
78
84
|
return { conversion: null, todos };
|
|
79
85
|
}
|
|
80
86
|
entry = wrapperEntry(tag);
|
|
@@ -163,7 +169,7 @@ export function plan(facts) {
|
|
|
163
169
|
}
|
|
164
170
|
const wraps = entry.wrapsChildren;
|
|
165
171
|
if (wraps &&
|
|
166
|
-
(!wraps.when ||
|
|
172
|
+
(!wraps.when || mayCarry(wraps.when)) &&
|
|
167
173
|
(facts.hasChildren || wraps.whenEmpty) &&
|
|
168
174
|
!facts.childTargets?.some(child => wraps.unless.includes(child))) {
|
|
169
175
|
return refuse('children', target, `bestax \`${target}\` renders its children inside a \`.${wraps.in}\` of its own unless one of them is a ${orList(wraps.unless)}, so this element stays markup`);
|
|
@@ -273,8 +279,45 @@ export function plan(facts) {
|
|
|
273
279
|
props.push([counts.prop, String(facts.childElements.length)]);
|
|
274
280
|
numbers.push(counts.prop);
|
|
275
281
|
}
|
|
282
|
+
// A class a condition adds becomes the boolean prop that renders it, set to
|
|
283
|
+
// that condition, when it is one of the root's flags on its own: the
|
|
284
|
+
// component renders it exactly when the condition is truthy, as the joiner
|
|
285
|
+
// does. Anything else a condition adds stays in the joiner call.
|
|
286
|
+
const parts = facts.conditional ?? [];
|
|
287
|
+
const conditional = [];
|
|
288
|
+
let conditionalStays = false;
|
|
289
|
+
const places = (token) => parts.filter(part => part.includes(token)).length +
|
|
290
|
+
(tokens.includes(token) ? 1 : 0);
|
|
291
|
+
const paired = new Set(Object.values(entry.absorbs?.pairs ?? {}));
|
|
292
|
+
const impure = new Set(facts.impure ?? []);
|
|
293
|
+
let impureStays = false;
|
|
294
|
+
parts.forEach((part, index) => {
|
|
295
|
+
const [token] = part;
|
|
296
|
+
const modifier = part.length === 1 && places(token) === 1 && !paired.has(token)
|
|
297
|
+
? modifierFor(entry, token)
|
|
298
|
+
: undefined;
|
|
299
|
+
const [write, ...more] = modifier?.writes ?? [];
|
|
300
|
+
const flag = write !== undefined &&
|
|
301
|
+
more.length === 0 &&
|
|
302
|
+
write.value === undefined &&
|
|
303
|
+
!modifier.onlyTrue &&
|
|
304
|
+
(!modifier.tagIn || modifier.tagIn.includes(tag)) &&
|
|
305
|
+
!writes.has(write.prop) &&
|
|
306
|
+
!conditional.some(([prop]) => prop === write.prop);
|
|
307
|
+
// Written as a prop, the condition is evaluated before any left in the
|
|
308
|
+
// call ahead of it, which is safe only when neither can have side effects.
|
|
309
|
+
const reorders = conditionalStays && (impureStays || impure.has(index));
|
|
310
|
+
if (flag && !reorders) {
|
|
311
|
+
conditional.push([write.prop, token]);
|
|
312
|
+
}
|
|
313
|
+
else {
|
|
314
|
+
conditionalStays = true;
|
|
315
|
+
if (impure.has(index))
|
|
316
|
+
impureStays = true;
|
|
317
|
+
}
|
|
318
|
+
});
|
|
276
319
|
const rest = tokens.filter(token => !converted.has(token));
|
|
277
|
-
if (entry.ownClassOnly && rest.length > 0) {
|
|
320
|
+
if (entry.ownClassOnly && (rest.length > 0 || conditionalStays)) {
|
|
278
321
|
return refuse('attr', 'className', `bestax \`${target}\` drops its own class when it is given a \`className\`, so this element's other classes would take the place of \`.${root}\`; keep it as markup`);
|
|
279
322
|
}
|
|
280
323
|
return {
|
|
@@ -286,6 +329,7 @@ export function plan(facts) {
|
|
|
286
329
|
numbers,
|
|
287
330
|
...(absorbed ? { absorbs: absorbed } : {}),
|
|
288
331
|
...(counts ? { rendersChildren: true } : {}),
|
|
332
|
+
...(conditional.length > 0 ? { conditional } : {}),
|
|
289
333
|
},
|
|
290
334
|
todos,
|
|
291
335
|
};
|
|
@@ -314,7 +358,7 @@ function absorb(facts, root, entry, target, refuse) {
|
|
|
314
358
|
return refuse('spread', target, `the <${spec.tag}> inside spreads props, which bestax \`${target}\` may read differently than the <${spec.tag}> did; convert the two to \`${target}\` by hand`);
|
|
315
359
|
}
|
|
316
360
|
if (child.tokens === null) {
|
|
317
|
-
return refuse('dynamic-class', target, `the <${spec.tag}> inside has a computed \`className\`,
|
|
361
|
+
return refuse('dynamic-class', target, `the <${spec.tag}> inside has a computed \`className\`, which the codemod does not read there; convert the two to bestax \`${target}\` by hand, turning each condition into its prop`);
|
|
318
362
|
}
|
|
319
363
|
if (child.tokens?.length === 0) {
|
|
320
364
|
return refuse('attr', 'className', `the <${spec.tag}> inside has an empty \`className\`, which renders \`class=""\`, and bestax \`${target}\` renders it with no class attribute; drop the empty \`className\`, then re-run`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transform.d.ts","sourceRoot":"","sources":["../../../src/sources/bulma-classes/transform.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAW,QAAQ,EAAE,MAAM,aAAa,CAAC;AAE1D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"transform.d.ts","sourceRoot":"","sources":["../../../src/sources/bulma-classes/transform.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAW,QAAQ,EAAE,MAAM,aAAa,CAAC;AAE1D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAmtBvD,MAAM,CAAC,OAAO,UAAU,SAAS,CAC/B,QAAQ,EAAE,QAAQ,EAClB,GAAG,EAAE,GAAG,EACR,OAAO,GAAE,gBAAqB,GAC7B,MAAM,GAAG,SAAS,CAygBpB"}
|
|
@@ -21,8 +21,9 @@ import { addTodo, findAttr, forgetJsxParens, jsxNameParts, literalValueOf, makeA
|
|
|
21
21
|
import { collectBoundNames, makeReserve, prefersTabs, resolvesToBinding, } from '../_shared/imports.js';
|
|
22
22
|
import { rewriteStylesheetImports } from '../_shared/css-imports.js';
|
|
23
23
|
import { BESTAX, buildBestaxImport, placeBestaxImport, seedBestaxImport, } from '../_shared/bestax-import.js';
|
|
24
|
-
import { plan } from './plan.js';
|
|
24
|
+
import { plan, } from './plan.js';
|
|
25
25
|
import { REACT_RUNTIMES, } from './project.js';
|
|
26
|
+
import { rootFor } from './class-map.js';
|
|
26
27
|
import { ruleId } from './rules.js';
|
|
27
28
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
28
29
|
/**
|
|
@@ -48,48 +49,293 @@ const HEADER_PRAGMA = /^[\s*]*(?:@ts-(?:no)?check|@flow|@noflow|eslint-disable(?
|
|
|
48
49
|
/** Directives that act on the line after them, so must stay above it. */
|
|
49
50
|
const NEXT_LINE_DIRECTIVE = /^[\s*]*(?:eslint-disable-next-line|@ts-expect-error|@ts-ignore|prettier-ignore)\b/;
|
|
50
51
|
/**
|
|
51
|
-
* A comment moved
|
|
52
|
-
*
|
|
53
|
-
* closing tag
|
|
52
|
+
* A comment moved into a JSX tag, before or after a node there. It has to be
|
|
53
|
+
* a block comment: after a tag name recast prints a line comment as `<// …`,
|
|
54
|
+
* which TypeScript reads as a closing tag, and before an attribute it would
|
|
55
|
+
* run on into it.
|
|
54
56
|
*/
|
|
55
|
-
function
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
function asBlock(comment, trailing) {
|
|
58
|
+
const placed = { leading: !trailing, trailing };
|
|
59
|
+
if (comment.type !== 'CommentLine')
|
|
60
|
+
return { ...comment, ...placed };
|
|
59
61
|
return {
|
|
60
62
|
type: 'CommentBlock',
|
|
61
63
|
value: ` ${comment.value.trim().replace(/\*\//g, '*\\/')} `,
|
|
62
|
-
|
|
63
|
-
trailing: true,
|
|
64
|
+
...placed,
|
|
64
65
|
};
|
|
65
66
|
}
|
|
67
|
+
/** A comment moved to sit after a JSX tag name. */
|
|
68
|
+
function afterTagName(comment) {
|
|
69
|
+
return asBlock(comment, true);
|
|
70
|
+
}
|
|
66
71
|
/**
|
|
67
|
-
* Put `
|
|
68
|
-
*
|
|
69
|
-
* place: the first new attribute, else the next one along, else the tag
|
|
72
|
+
* Put `replacement` where an element's `className` was. A comment on the
|
|
73
|
+
* className, and any `carried` from inside it, goes with whatever takes its
|
|
74
|
+
* place: the first new attribute, else the next one along, else the tag
|
|
75
|
+
* name. A className kept in `replacement` keeps its own.
|
|
70
76
|
*/
|
|
71
|
-
function
|
|
77
|
+
function spliceClassName(element, replacement, carried = []) {
|
|
72
78
|
const attrs = element.openingElement.attributes;
|
|
73
79
|
const classAttr = findAttr(element, 'className');
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
if (classAttr.comments?.length) {
|
|
80
|
+
const kept = replacement.includes(classAttr);
|
|
81
|
+
const moving = [...(kept ? [] : (classAttr.comments ?? [])), ...carried];
|
|
82
|
+
if (moving.length > 0) {
|
|
78
83
|
const name = element.openingElement.name;
|
|
79
84
|
const carrier = replacement[0] ?? attrs.find((attr) => attr !== classAttr) ?? name;
|
|
80
85
|
carrier.comments = [
|
|
81
86
|
...(carrier.comments ?? []),
|
|
82
|
-
...
|
|
87
|
+
...moving.map((comment) => carrier === name ? afterTagName(comment) : comment),
|
|
83
88
|
];
|
|
84
89
|
}
|
|
85
90
|
attrs.splice(attrs.indexOf(classAttr), 1, ...replacement);
|
|
86
91
|
}
|
|
92
|
+
/** Put `props`, then what stays in `className`, where `className` was. */
|
|
93
|
+
function writeClassName(j, element, props, className) {
|
|
94
|
+
const replacement = props.map(([name, value]) => makeAttr(j, name, value === true ? undefined : value));
|
|
95
|
+
if (className)
|
|
96
|
+
replacement.push(makeAttr(j, 'className', className));
|
|
97
|
+
spliceClassName(element, replacement);
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* The same, for a `className` a joiner builds: the classes a prop now
|
|
101
|
+
* renders leave the call, each condition that became a prop moves onto it,
|
|
102
|
+
* and what is left stays in the call (a plain string when nothing in it is
|
|
103
|
+
* conditional, no `className` when nothing is left). Returns whether the
|
|
104
|
+
* call is gone.
|
|
105
|
+
*/
|
|
106
|
+
function writeJoined(j, element, conversion, joined) {
|
|
107
|
+
const { call, statics, parts } = joined;
|
|
108
|
+
const kept = new Set(conversion.className?.split(' ') ?? []);
|
|
109
|
+
const carried = [];
|
|
110
|
+
const remove = (list, node) => list.splice(list.indexOf(node), 1);
|
|
111
|
+
for (const node of statics) {
|
|
112
|
+
const classes = classesIn(staticText(node));
|
|
113
|
+
const remaining = classes.filter(token => kept.has(token));
|
|
114
|
+
if (remaining.length === classes.length)
|
|
115
|
+
continue;
|
|
116
|
+
if (remaining.length === 0) {
|
|
117
|
+
carried.push(...commentsWithin(node));
|
|
118
|
+
remove(call.arguments, node);
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
const literal = j.stringLiteral(remaining.join(' '));
|
|
122
|
+
literal.comments = node.comments;
|
|
123
|
+
call.arguments[call.arguments.indexOf(node)] = literal;
|
|
124
|
+
}
|
|
125
|
+
const conditions = (conversion.conditional ?? []).map(([prop, token]) => {
|
|
126
|
+
const part = parts.find(found => found.tokens.length === 1 && found.tokens[0] === token);
|
|
127
|
+
carried.push(...commentsWithin(part.node, part.condition));
|
|
128
|
+
if (part.object) {
|
|
129
|
+
remove(part.object.properties, part.node);
|
|
130
|
+
if (part.object.properties.length === 0) {
|
|
131
|
+
carried.push(...commentsWithin(part.object));
|
|
132
|
+
remove(call.arguments, part.object);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
remove(call.arguments, part.node);
|
|
137
|
+
}
|
|
138
|
+
const value = part.negate
|
|
139
|
+
? j.unaryExpression('!', part.condition)
|
|
140
|
+
: part.condition;
|
|
141
|
+
return j.jsxAttribute(j.jsxIdentifier(prop), j.jsxExpressionContainer(value));
|
|
142
|
+
});
|
|
143
|
+
const classAttr = findAttr(element, 'className');
|
|
144
|
+
let rest = [classAttr];
|
|
145
|
+
const gone = call.arguments.every((arg) => staticText(arg) !== null);
|
|
146
|
+
if (gone) {
|
|
147
|
+
carried.push(...commentsWithin(classAttr.value));
|
|
148
|
+
const classes = call.arguments.flatMap((arg) => classesIn(staticText(arg)));
|
|
149
|
+
rest =
|
|
150
|
+
classes.length > 0 ? [makeAttr(j, 'className', classes.join(' '))] : [];
|
|
151
|
+
}
|
|
152
|
+
spliceClassName(element, [
|
|
153
|
+
...conversion.props.map(([name, value]) => makeAttr(j, name, value === true ? undefined : value)),
|
|
154
|
+
...conditions,
|
|
155
|
+
...rest,
|
|
156
|
+
], carried.map(comment => asBlock(comment, false)));
|
|
157
|
+
return gone;
|
|
158
|
+
}
|
|
87
159
|
/** A lowercase intrinsic tag: not a component, not a custom element. */
|
|
88
160
|
const INTRINSIC = /^[a-z][a-z0-9]*$/;
|
|
89
161
|
/** Subtrees whose tags are not HTML. */
|
|
90
162
|
const FOREIGN_ROOTS = new Set(['svg', 'math']);
|
|
91
163
|
/** Parents that render their only child as it is. */
|
|
92
164
|
const PASS_THROUGH_PARENTS = new Set(['Fragment', 'React.Fragment']);
|
|
165
|
+
/**
|
|
166
|
+
* Class joiners whose arguments the codemod can read exactly, by package and
|
|
167
|
+
* export: a string adds its classes, and an object key, `a && 'x'` or
|
|
168
|
+
* `a ? 'x' : ''` adds them when the condition is truthy. Not `clsx/lite`,
|
|
169
|
+
* which ignores objects, `classnames/dedupe`, where a later key can take a
|
|
170
|
+
* class back, or `classnames/bind`, which looks names up in a CSS module.
|
|
171
|
+
*/
|
|
172
|
+
const JOINERS = {
|
|
173
|
+
clsx: ['default', 'clsx'],
|
|
174
|
+
classnames: ['default'],
|
|
175
|
+
};
|
|
176
|
+
/** A string literal's or an expression-free template's text, else null. */
|
|
177
|
+
function staticText(node) {
|
|
178
|
+
if (node?.type === 'StringLiteral')
|
|
179
|
+
return node.value;
|
|
180
|
+
if (node?.type === 'TemplateLiteral' && node.expressions.length === 0) {
|
|
181
|
+
return node.quasis[0].value.cooked ?? null;
|
|
182
|
+
}
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
function classesIn(text) {
|
|
186
|
+
return text.split(/\s+/).filter(Boolean);
|
|
187
|
+
}
|
|
188
|
+
/** A joiner argument that adds nothing: `''`, `null`, `undefined`, `false`. */
|
|
189
|
+
function addsNothing(node) {
|
|
190
|
+
if (staticText(node)?.trim() === '')
|
|
191
|
+
return true;
|
|
192
|
+
if (node.type === 'NullLiteral')
|
|
193
|
+
return true;
|
|
194
|
+
if (node.type === 'BooleanLiteral')
|
|
195
|
+
return node.value === false;
|
|
196
|
+
return node.type === 'Identifier' && node.name === 'undefined';
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Whether evaluating an expression can't change anything: names, literals,
|
|
200
|
+
* property reads, and operators over those. A call, an assignment, `new` or
|
|
201
|
+
* `delete` can. (So can a getter; a property read is taken as a read.)
|
|
202
|
+
*/
|
|
203
|
+
function sideEffectFree(node) {
|
|
204
|
+
switch (node?.type) {
|
|
205
|
+
case 'Identifier':
|
|
206
|
+
case 'StringLiteral':
|
|
207
|
+
case 'NumericLiteral':
|
|
208
|
+
case 'BooleanLiteral':
|
|
209
|
+
case 'NullLiteral':
|
|
210
|
+
case 'BigIntLiteral':
|
|
211
|
+
case 'ThisExpression':
|
|
212
|
+
return true;
|
|
213
|
+
case 'TemplateLiteral':
|
|
214
|
+
return node.expressions.every(sideEffectFree);
|
|
215
|
+
case 'MemberExpression':
|
|
216
|
+
case 'OptionalMemberExpression':
|
|
217
|
+
return (sideEffectFree(node.object) &&
|
|
218
|
+
(!node.computed || sideEffectFree(node.property)));
|
|
219
|
+
case 'UnaryExpression':
|
|
220
|
+
return node.operator !== 'delete' && sideEffectFree(node.argument);
|
|
221
|
+
case 'BinaryExpression':
|
|
222
|
+
case 'LogicalExpression':
|
|
223
|
+
return sideEffectFree(node.left) && sideEffectFree(node.right);
|
|
224
|
+
case 'ConditionalExpression':
|
|
225
|
+
return [node.test, node.consequent, node.alternate].every(sideEffectFree);
|
|
226
|
+
case 'TSAsExpression':
|
|
227
|
+
case 'TSSatisfiesExpression':
|
|
228
|
+
case 'TSNonNullExpression':
|
|
229
|
+
case 'TSTypeAssertion':
|
|
230
|
+
case 'ParenthesizedExpression':
|
|
231
|
+
return sideEffectFree(node.expression);
|
|
232
|
+
default:
|
|
233
|
+
return false;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* A `className={joiner(…)}` whose every argument the codemod can read, or
|
|
238
|
+
* undefined: an argument it can't (a variable, a call, a spread, an array)
|
|
239
|
+
* could add any class.
|
|
240
|
+
*/
|
|
241
|
+
function readJoiner(value, isJoiner) {
|
|
242
|
+
const call = value?.type === 'JSXExpressionContainer' ? value.expression : null;
|
|
243
|
+
if (call?.type !== 'CallExpression' || !isJoiner(call.callee)) {
|
|
244
|
+
return undefined;
|
|
245
|
+
}
|
|
246
|
+
const statics = [];
|
|
247
|
+
const parts = [];
|
|
248
|
+
for (const arg of call.arguments) {
|
|
249
|
+
if (staticText(arg) !== null) {
|
|
250
|
+
statics.push(arg);
|
|
251
|
+
}
|
|
252
|
+
else if (arg.type === 'ObjectExpression') {
|
|
253
|
+
for (const property of arg.properties) {
|
|
254
|
+
const plain = (property.type === 'ObjectProperty' ||
|
|
255
|
+
property.type === 'Property') &&
|
|
256
|
+
!property.computed &&
|
|
257
|
+
!property.method &&
|
|
258
|
+
(property.kind === undefined || property.kind === 'init');
|
|
259
|
+
const key = !plain
|
|
260
|
+
? undefined
|
|
261
|
+
: property.key.type === 'Identifier'
|
|
262
|
+
? property.key.name
|
|
263
|
+
: staticText(property.key);
|
|
264
|
+
if (key == null)
|
|
265
|
+
return undefined;
|
|
266
|
+
parts.push({
|
|
267
|
+
tokens: classesIn(key),
|
|
268
|
+
condition: property.value,
|
|
269
|
+
negate: false,
|
|
270
|
+
node: property,
|
|
271
|
+
object: arg,
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
else if (arg.type === 'LogicalExpression' &&
|
|
276
|
+
arg.operator === '&&' &&
|
|
277
|
+
staticText(arg.right) !== null) {
|
|
278
|
+
parts.push({
|
|
279
|
+
tokens: classesIn(staticText(arg.right)),
|
|
280
|
+
condition: arg.left,
|
|
281
|
+
negate: false,
|
|
282
|
+
node: arg,
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
else if (arg.type === 'ConditionalExpression' &&
|
|
286
|
+
staticText(arg.consequent) !== null &&
|
|
287
|
+
addsNothing(arg.alternate)) {
|
|
288
|
+
parts.push({
|
|
289
|
+
tokens: classesIn(staticText(arg.consequent)),
|
|
290
|
+
condition: arg.test,
|
|
291
|
+
negate: false,
|
|
292
|
+
node: arg,
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
else if (arg.type === 'ConditionalExpression' &&
|
|
296
|
+
staticText(arg.alternate) !== null &&
|
|
297
|
+
addsNothing(arg.consequent)) {
|
|
298
|
+
parts.push({
|
|
299
|
+
tokens: classesIn(staticText(arg.alternate)),
|
|
300
|
+
condition: arg.test,
|
|
301
|
+
negate: true,
|
|
302
|
+
node: arg,
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
else {
|
|
306
|
+
return undefined;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
const tokens = [
|
|
310
|
+
...new Set(statics.flatMap(node => classesIn(staticText(node)))),
|
|
311
|
+
];
|
|
312
|
+
return { call, tokens, statics, parts };
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Every comment on a node and inside it, except inside `keep` (a condition
|
|
316
|
+
* that moves, taking its comments with it).
|
|
317
|
+
*/
|
|
318
|
+
function commentsWithin(node, keep) {
|
|
319
|
+
const found = [];
|
|
320
|
+
const visit = (current) => {
|
|
321
|
+
if (!current || typeof current !== 'object' || current === keep)
|
|
322
|
+
return;
|
|
323
|
+
if (Array.isArray(current)) {
|
|
324
|
+
current.forEach(visit);
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
if (typeof current.type !== 'string')
|
|
328
|
+
return;
|
|
329
|
+
found.push(...(current.comments ?? []));
|
|
330
|
+
for (const [key, child] of Object.entries(current)) {
|
|
331
|
+
if (key !== 'comments' && key !== 'loc' && key !== 'original') {
|
|
332
|
+
visit(child);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
};
|
|
336
|
+
visit(node);
|
|
337
|
+
return found;
|
|
338
|
+
}
|
|
93
339
|
/**
|
|
94
340
|
* The static text of a `className`, or null when it is computed. A string
|
|
95
341
|
* literal, `{'…'}`, and a template literal with no expressions are static.
|
|
@@ -413,6 +659,33 @@ export default function transform(fileInfo, api, options = {}) {
|
|
|
413
659
|
// a child that is one of its parts.
|
|
414
660
|
const planned = new Map();
|
|
415
661
|
const programScope = root.find(j.Program).paths()[0]?.scope;
|
|
662
|
+
// The joiners the file imports, by local name, and the elements whose
|
|
663
|
+
// joiner call converts.
|
|
664
|
+
const joinerLocals = new Set();
|
|
665
|
+
root.find(j.ImportDeclaration).forEach((importPath) => {
|
|
666
|
+
const node = importPath.node;
|
|
667
|
+
const exported = Object.hasOwn(JOINERS, node.source.value)
|
|
668
|
+
? JOINERS[node.source.value]
|
|
669
|
+
: undefined;
|
|
670
|
+
if (!exported || node.importKind === 'type')
|
|
671
|
+
return;
|
|
672
|
+
for (const spec of node.specifiers ?? []) {
|
|
673
|
+
const name = spec.type === 'ImportDefaultSpecifier'
|
|
674
|
+
? 'default'
|
|
675
|
+
: spec.type === 'ImportSpecifier' && spec.importKind !== 'type'
|
|
676
|
+
? (spec.imported.name ?? spec.imported.value)
|
|
677
|
+
: undefined;
|
|
678
|
+
if (name && exported.includes(name))
|
|
679
|
+
joinerLocals.add(spec.local.name);
|
|
680
|
+
}
|
|
681
|
+
});
|
|
682
|
+
/** Whether a callee is one of those joiners where it is called. */
|
|
683
|
+
const isJoiner = (callee, scopePath) => callee?.type === 'Identifier' &&
|
|
684
|
+
joinerLocals.has(callee.name) &&
|
|
685
|
+
resolvesToBinding(scopePath, callee.name, programScope);
|
|
686
|
+
const joins = new Map();
|
|
687
|
+
/** Joiners a rewritten className no longer calls. */
|
|
688
|
+
const unjoined = new Set();
|
|
416
689
|
/**
|
|
417
690
|
* The bestax component an element already is, when its name is the bestax
|
|
418
691
|
* import and not a local that shadows it; `scopePath` is where the name is
|
|
@@ -519,21 +792,46 @@ export default function transform(fileInfo, api, options = {}) {
|
|
|
519
792
|
componentsAround: surrounding.other,
|
|
520
793
|
onlyChildOf: onlyChildOf(elementPath, bestaxLocals),
|
|
521
794
|
};
|
|
522
|
-
|
|
795
|
+
// A joiner call the codemod can read converts exactly: its fixed classes
|
|
796
|
+
// as for a static className, and each condition on a flag as its prop.
|
|
797
|
+
const joined = className === null
|
|
798
|
+
? readJoiner(classAttr.value, callee => isJoiner(callee, elementPath))
|
|
799
|
+
: undefined;
|
|
800
|
+
let exact;
|
|
801
|
+
if (joined) {
|
|
802
|
+
exact = plan({
|
|
803
|
+
...facts,
|
|
804
|
+
tokens: joined.tokens,
|
|
805
|
+
conditional: joined.parts.map(part => part.tokens),
|
|
806
|
+
impure: joined.parts.flatMap((part, index) => sideEffectFree(part.condition) ? [] : [index]),
|
|
807
|
+
});
|
|
808
|
+
if (exact.conversion)
|
|
809
|
+
joins.set(element, joined);
|
|
810
|
+
else
|
|
811
|
+
exact = undefined;
|
|
812
|
+
}
|
|
813
|
+
let result = exact ?? plan(facts);
|
|
523
814
|
const converts = result.conversion !== null || result.fold !== undefined;
|
|
524
815
|
const becomes = result.conversion?.target ?? result.fold?.target;
|
|
525
|
-
if (className === null && becomes) {
|
|
526
|
-
// The same element with a static className would convert; with
|
|
527
|
-
// computed
|
|
816
|
+
if (className === null && !exact && becomes) {
|
|
817
|
+
// The same element with a static className would convert; with one
|
|
818
|
+
// computed any other way, only a person can turn each condition into
|
|
819
|
+
// its prop.
|
|
528
820
|
const target = becomes;
|
|
821
|
+
// A call it could read says what stopped it instead.
|
|
822
|
+
const conditionalRoot = joined?.parts
|
|
823
|
+
.flatMap(part => part.tokens)
|
|
824
|
+
.find(token => rootFor(token)?.target === target);
|
|
825
|
+
const message = !joined
|
|
826
|
+
? `this \`className\` is computed, and the codemod reads only a \`clsx\` or \`classnames\` call of class strings and conditional classes; convert this element to bestax \`${target}\` by hand, turning each condition into its prop`
|
|
827
|
+
: conditionalRoot
|
|
828
|
+
? `this \`className\` adds \`.${conditionalRoot}\` only under a condition, so whether this element is a bestax \`${target}\` at all depends on it; convert it by hand if it always is, turning each condition into its prop`
|
|
829
|
+
: `this \`className\` is computed, and the codemod can't write every class it adds onto bestax \`${target}\` as it is; convert this element by hand, turning each condition into its prop`;
|
|
529
830
|
result = {
|
|
530
831
|
conversion: null,
|
|
531
832
|
todos: [
|
|
532
833
|
...result.todos,
|
|
533
|
-
{
|
|
534
|
-
rule: ruleId('dynamic-class', target),
|
|
535
|
-
message: `this \`className\` is computed, and the codemod converts static class strings only; convert this element to bestax \`${target}\` by hand, turning each condition into its prop`,
|
|
536
|
-
},
|
|
834
|
+
{ rule: ruleId('dynamic-class', target), message },
|
|
537
835
|
],
|
|
538
836
|
};
|
|
539
837
|
}
|
|
@@ -673,7 +971,13 @@ export default function transform(fileInfo, api, options = {}) {
|
|
|
673
971
|
const holder = findAttr(element, name) ? element : child;
|
|
674
972
|
removeAttr(holder, findAttr(holder, name));
|
|
675
973
|
}
|
|
676
|
-
|
|
974
|
+
const joined = joins.get(element);
|
|
975
|
+
if (!joined) {
|
|
976
|
+
writeClassName(j, element, conversion.props, conversion.className);
|
|
977
|
+
}
|
|
978
|
+
else if (writeJoined(j, element, conversion, joined)) {
|
|
979
|
+
unjoined.add(joined.call.callee.name);
|
|
980
|
+
}
|
|
677
981
|
if (conversion.rendersChildren) {
|
|
678
982
|
element.children = [];
|
|
679
983
|
element.openingElement.selfClosing = true;
|
|
@@ -715,6 +1019,34 @@ export default function transform(fileInfo, api, options = {}) {
|
|
|
715
1019
|
}
|
|
716
1020
|
ctx.dirty = true;
|
|
717
1021
|
}
|
|
1022
|
+
// A joiner import only these rewrites stopped using goes too, so the file
|
|
1023
|
+
// gains no unused import; one the file already left unused stays.
|
|
1024
|
+
for (const name of unjoined) {
|
|
1025
|
+
const referenced = root
|
|
1026
|
+
.find(j.Identifier, { name })
|
|
1027
|
+
.filter((identifierPath) => identifierPath.parent.node.type !== 'ImportSpecifier' &&
|
|
1028
|
+
identifierPath.parent.node.type !== 'ImportDefaultSpecifier');
|
|
1029
|
+
if (referenced.size() > 0)
|
|
1030
|
+
continue;
|
|
1031
|
+
root.find(j.ImportDeclaration).forEach((importPath) => {
|
|
1032
|
+
const node = importPath.node;
|
|
1033
|
+
const specifiers = node.specifiers ?? [];
|
|
1034
|
+
const spec = specifiers.find((found) => found.local?.name === name);
|
|
1035
|
+
if (!spec)
|
|
1036
|
+
return;
|
|
1037
|
+
specifiers.splice(specifiers.indexOf(spec), 1);
|
|
1038
|
+
if (specifiers.length > 0)
|
|
1039
|
+
return;
|
|
1040
|
+
// The declaration goes with its last name; its comments (a file
|
|
1041
|
+
// header, a directive) stay, on the statement beside it.
|
|
1042
|
+
const index = program.body.indexOf(node);
|
|
1043
|
+
const neighbour = program.body[index + 1] ?? program.body[index - 1];
|
|
1044
|
+
if (node.comments?.length && neighbour) {
|
|
1045
|
+
neighbour.comments = [...node.comments, ...(neighbour.comments ?? [])];
|
|
1046
|
+
}
|
|
1047
|
+
program.body.splice(index, 1);
|
|
1048
|
+
});
|
|
1049
|
+
}
|
|
718
1050
|
// ---- 5. The bestax import ---------------------------------------------------
|
|
719
1051
|
const body = program.body;
|
|
720
1052
|
placeBestaxImport(bestaxImportState, buildBestaxImport(ctx, bestaxImportState), {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bestax-migrate",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.12.0",
|
|
4
4
|
"description": "Codemods for migrating existing React apps from other Bulma libraries to @allxsmith/bestax-bulma",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"chalk": "^6.0.0",
|
|
39
39
|
"commander": "^15.0.0",
|
|
40
40
|
"jscodeshift": "^17.3.0",
|
|
41
|
-
"@allxsmith/bestax-bulma": "^5.16.
|
|
41
|
+
"@allxsmith/bestax-bulma": "^5.16.10"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"@jest/globals": "^30.0.0",
|
|
@@ -47,6 +47,8 @@
|
|
|
47
47
|
"@types/node": "^26.3.0",
|
|
48
48
|
"@types/react": "^19.2.18",
|
|
49
49
|
"@types/react-dom": "^19.2.5",
|
|
50
|
+
"classnames": "^2.5.1",
|
|
51
|
+
"clsx": "^2.1.1",
|
|
50
52
|
"jest": "^30.2.0",
|
|
51
53
|
"react": "^19.2.8",
|
|
52
54
|
"react-dom": "^19.2.8",
|