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.
@@ -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":"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"}
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: flags({
625
- 'is-horizontal': 'horizontal',
626
- 'is-grouped': 'grouped',
627
- 'has-addons': 'hasAddons',
628
- 'is-narrow': 'narrow',
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":"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"}
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
- for (const token of tokens) {
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 = tokens.find(token => {
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 (tokens.some(token => rootFor(token))) {
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 || tokens.includes(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\`, and the codemod converts static class strings only; convert the two to bestax \`${target}\` by hand, turning each condition into its prop`);
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;AAmavD,MAAM,CAAC,OAAO,UAAU,SAAS,CAC/B,QAAQ,EAAE,QAAQ,EAClB,GAAG,EAAE,GAAG,EACR,OAAO,GAAE,gBAAqB,GAC7B,MAAM,GAAG,SAAS,CAkbpB"}
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 to sit after a JSX tag name. It has to be a block comment
52
- * there: recast prints a line comment as `<// …`, which TypeScript reads as a
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 afterTagName(comment) {
56
- if (comment.type !== 'CommentLine') {
57
- return { ...comment, leading: false, trailing: true };
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
- leading: false,
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 `props`, then what stays in `className`, where an element's
68
- * `className` was. A comment on the className goes with whatever takes its
69
- * place: the first new attribute, else the next one along, else the tag name.
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 writeClassName(j, element, props, className) {
77
+ function spliceClassName(element, replacement, carried = []) {
72
78
  const attrs = element.openingElement.attributes;
73
79
  const classAttr = findAttr(element, 'className');
74
- const replacement = props.map(([name, value]) => makeAttr(j, name, value === true ? undefined : value));
75
- if (className)
76
- replacement.push(makeAttr(j, 'className', className));
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
- ...classAttr.comments.map((comment) => carrier === name ? afterTagName(comment) : comment),
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
- let result = plan(facts);
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 a
527
- // computed one, only a person can turn each condition into its prop.
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
- writeClassName(j, element, conversion.props, conversion.className);
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.11.0",
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.9"
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",