@plumeria/codemod 18.2.19 → 18.2.20

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.
@@ -79,6 +79,9 @@ exports.releaseStyles = {
79
79
  core: 'Remove the Plumeria import after exporting the styles.',
80
80
  prop: 'Use className after exporting this style to CSS Modules.',
81
81
  composition: 'Join the composed CSS Module class names.',
82
+ use: 'Read the class name straight from the CSS Module.',
83
+ token: 'Inline the value this token resolved to.',
84
+ styleType: 'A released style is a class name.',
82
85
  functionStyle: 'Use the CSS Module class and pass function arguments as custom properties.',
83
86
  unsupportedFunctionStyle: 'This function style call cannot be converted without changing its behavior.',
84
87
  },
@@ -90,6 +93,8 @@ exports.releaseStyles = {
90
93
  themes: { type: 'object' },
91
94
  animations: { type: 'object' },
92
95
  statics: { type: 'object' },
96
+ values: { type: 'object' },
97
+ complete: { type: 'boolean' },
93
98
  styleProp: { type: 'string' },
94
99
  active: { type: 'boolean' },
95
100
  },
@@ -100,13 +105,85 @@ exports.releaseStyles = {
100
105
  create(context) {
101
106
  const raw = (context.options[0] ?? {});
102
107
  const declared = raw.modules?.[context.filename];
103
- const entry = declared?.definitionOnly ? undefined : declared;
104
- const themeBindings = raw.themes?.[context.filename] ?? [];
105
- const animationBindings = raw.animations?.[context.filename] ?? [];
108
+ const keptDefinition = declared?.definitionOnly === true;
109
+ const complete = raw.complete === true;
110
+ const entry = keptDefinition ? undefined : declared;
111
+ const themeBindings = keptDefinition
112
+ ? []
113
+ : (raw.themes?.[context.filename] ?? []);
114
+ const animationBindings = keptDefinition
115
+ ? []
116
+ : (raw.animations?.[context.filename] ?? []);
106
117
  const styleProp = raw.styleProp ?? 'classStyle';
107
118
  const sourceCode = context.sourceCode;
108
- let active = Boolean(entry || raw.active);
109
- let activeModule = entry;
119
+ const moduleByLocal = new Map();
120
+ let active = Boolean(entry || raw.active || raw.complete);
121
+ let coreImport;
122
+ let coreLocal;
123
+ let coreNeeded = true;
124
+ const declarationOf = (statement) => statement.type === 'ExportNamedDeclaration'
125
+ ? statement.declaration
126
+ : statement;
127
+ const removesDeclaration = (statement) => {
128
+ const declaration = declarationOf(statement);
129
+ if (declaration?.type !== 'VariableDeclaration' ||
130
+ declaration.declarations.length !== 1)
131
+ return false;
132
+ const only = declaration.declarations[0];
133
+ if (only.id.type !== 'Identifier')
134
+ return false;
135
+ const called = only.init?.type === 'CallExpression'
136
+ ? memberPath(only.init.callee)
137
+ : undefined;
138
+ if (called?.length !== 2)
139
+ return false;
140
+ if (called[1] === 'createStatic')
141
+ return Boolean(entry);
142
+ if (called[1] === 'createTheme')
143
+ return themeBindings.includes(only.id.name);
144
+ if (called[1] === 'keyframes' || called[1] === 'viewTransition')
145
+ return animationBindings.includes(only.id.name);
146
+ if (called[1] === 'create')
147
+ return Boolean(entry &&
148
+ (only.id.name === entry.binding || entry.aliases?.[only.id.name]));
149
+ return false;
150
+ };
151
+ const removesStatement = (statement) => {
152
+ if (keptDefinition)
153
+ return false;
154
+ if (statement.type !== 'ImportDeclaration')
155
+ return removesDeclaration(statement);
156
+ const specifier = String(statement.source.value);
157
+ if (specifier === '@plumeria/core')
158
+ return !coreNeeded;
159
+ const bindings = [
160
+ ...(importedAnimationBindings(specifier) ?? []),
161
+ ...(importedThemeBindings(specifier) ?? []),
162
+ ...(importedStaticBindings(specifier) ?? []),
163
+ ];
164
+ return (bindings.length > 0 &&
165
+ statement.specifiers.length > 0 &&
166
+ statement.specifiers.every((candidate) => candidate.type === 'ImportSpecifier' &&
167
+ bindings.includes(candidate.imported.name ?? candidate.imported.value)));
168
+ };
169
+ const removeStatement = (fixer, node) => {
170
+ const body = sourceCode.ast.body;
171
+ const next = body.find((statement) => statement.range[0] >= node.range[1]);
172
+ if (!next)
173
+ return fixer.removeRange([node.range[0], sourceCode.text.length]);
174
+ if (removesStatement(next))
175
+ return fixer.remove(node);
176
+ const anchor = importAnchor();
177
+ if (node.range[1] <= anchor && next.range[0] >= anchor)
178
+ return fixer.remove(node);
179
+ return fixer.removeRange([node.range[0], next.range[0]]);
180
+ };
181
+ const importAnchor = () => {
182
+ const imports = sourceCode.ast.body.filter((statement) => statement.type === 'ImportDeclaration');
183
+ return imports.length > 0
184
+ ? Math.min(imports[imports.length - 1].range[1] + 1, sourceCode.text.length)
185
+ : 0;
186
+ };
110
187
  const specifierFor = (module) => {
111
188
  if (!module.target)
112
189
  return module.source;
@@ -133,10 +210,23 @@ exports.releaseStyles = {
133
210
  const resolved = (0, resolve_1.resolveSourcePath)(specifier, context.filename);
134
211
  return resolved ? table?.[resolved] : undefined;
135
212
  };
136
- const importedReleasedModule = (specifier) => importedFrom(specifier, raw.modules);
213
+ const byTarget = new Map(Object.values(raw.modules ?? {}).flatMap((module) => module.target ? [[module.target, module]] : []));
214
+ const importedReleasedModule = (specifier) => {
215
+ const declared = importedFrom(specifier, raw.modules);
216
+ if (declared)
217
+ return declared;
218
+ const resolved = (0, resolve_1.resolveSourcePath)(specifier, context.filename);
219
+ return resolved ? byTarget.get(resolved) : undefined;
220
+ };
137
221
  const importedThemeBindings = (specifier) => importedFrom(specifier, raw.themes);
138
222
  const importedAnimationBindings = (specifier) => importedFrom(specifier, raw.animations);
139
223
  const importedStaticBindings = (specifier) => importedFrom(specifier, raw.statics);
224
+ const inlineByLocal = new Map();
225
+ const literalOf = (value) => typeof value === 'string'
226
+ ? `'${value.replace(/\\/g, '\\\\').replace(/'/g, "\\'")}'`
227
+ : typeof value === 'number'
228
+ ? String(value)
229
+ : undefined;
140
230
  const releasedRange = () => {
141
231
  if (!entry)
142
232
  return undefined;
@@ -181,19 +271,204 @@ exports.releaseStyles = {
181
271
  node,
182
272
  messageId,
183
273
  fix: (fixer) => retained.length === 0
184
- ? fixer.remove(node)
274
+ ? removeStatement(fixer, node)
185
275
  : fixer.replaceText(node, importOf(retained, node.source)),
186
276
  });
187
277
  return true;
188
278
  };
279
+ const moduleOf = (node) => node?.type === 'MemberExpression' && node.object.type === 'Identifier'
280
+ ? moduleByLocal.get(node.object.name)
281
+ : undefined;
282
+ const styleKey = (node) => {
283
+ const module = moduleOf(node);
284
+ if (!module)
285
+ return undefined;
286
+ const written = node.computed
287
+ ? node.property.type === 'Literal'
288
+ ? String(node.property.value)
289
+ : undefined
290
+ : node.property.name;
291
+ if (written === undefined)
292
+ return undefined;
293
+ const alias = module.aliases?.[node.object.name];
294
+ const key = alias ? alias[written] : written;
295
+ return key === undefined ? undefined : { module, key };
296
+ };
297
+ const accessOf = (module, key) => `${module.binding}${IDENTIFIER.test(key) ? `.${key}` : `['${key}']`}`;
298
+ const classText = (node) => {
299
+ const resolved = styleKey(node);
300
+ return resolved ? accessOf(resolved.module, resolved.key) : undefined;
301
+ };
302
+ const releasedUse = (node, parent) => parent?.type === 'CallExpression' &&
303
+ parent.callee === node &&
304
+ parent.arguments.length > 0 &&
305
+ parent.arguments.every((argument) => classText(argument) !== undefined);
306
+ const removesCall = (method) => method === 'create' || method === 'createStatic'
307
+ ? Boolean(entry)
308
+ : method === 'createTheme'
309
+ ? themeBindings.length > 0
310
+ : method === 'keyframes' || method === 'viewTransition'
311
+ ? animationBindings.length > 0
312
+ : false;
313
+ const survivesCore = (program) => {
314
+ if (!coreLocal)
315
+ return false;
316
+ let survives = false;
317
+ const visit = (node, parent) => {
318
+ if (survives || !node || typeof node !== 'object')
319
+ return;
320
+ if (Array.isArray(node)) {
321
+ for (const child of node)
322
+ visit(child, parent);
323
+ return;
324
+ }
325
+ if (typeof node.type !== 'string')
326
+ return;
327
+ if (node.type === 'MemberExpression' &&
328
+ node.object.type === 'Identifier' &&
329
+ node.object.name === coreLocal) {
330
+ const method = node.computed
331
+ ? node.property.type === 'Literal'
332
+ ? String(node.property.value)
333
+ : ''
334
+ : node.property.name;
335
+ survives =
336
+ method === 'use'
337
+ ? !releasedUse(node, parent)
338
+ : !removesCall(method);
339
+ return;
340
+ }
341
+ for (const key of Object.keys(node)) {
342
+ if (key === 'parent' || key === 'loc' || key === 'range')
343
+ continue;
344
+ visit(node[key], node);
345
+ }
346
+ };
347
+ visit(program.body, undefined);
348
+ return survives;
349
+ };
350
+ const functionStyle = (value) => {
351
+ if (value?.type !== 'CallExpression')
352
+ return undefined;
353
+ const resolved = styleKey(value.callee);
354
+ if (!resolved)
355
+ return undefined;
356
+ const func = resolved.module.functions?.[resolved.key];
357
+ if (!func || func.variables.length !== value.arguments.length)
358
+ return undefined;
359
+ return {
360
+ className: accessOf(resolved.module, resolved.key),
361
+ declarations: func.variables.map((variable, index) => {
362
+ const argument = sourceCode.getText(value.arguments[index]);
363
+ return `['${variable}' as string]: ${argument}`;
364
+ }),
365
+ };
366
+ };
367
+ const resolveText = (value) => {
368
+ if (!value)
369
+ return undefined;
370
+ const called = functionStyle(value);
371
+ if (called)
372
+ return called.className;
373
+ if (value.type === 'LogicalExpression') {
374
+ const right = resolveText(value.right);
375
+ return right === undefined
376
+ ? undefined
377
+ : `${sourceCode.getText(value.left)} ${value.operator} ${right}`;
378
+ }
379
+ if (value.type === 'ConditionalExpression') {
380
+ const consequent = resolveText(value.consequent);
381
+ const alternate = resolveText(value.alternate);
382
+ return consequent === undefined || alternate === undefined
383
+ ? undefined
384
+ : `${sourceCode.getText(value.test)} ? ${consequent} : ${alternate}`;
385
+ }
386
+ if (value.type === 'Literal' && !value.value)
387
+ return sourceCode.getText(value);
388
+ return classText(value);
389
+ };
390
+ const declarationsOf = (value) => {
391
+ if (!value)
392
+ return [];
393
+ const called = functionStyle(value);
394
+ if (called)
395
+ return called.declarations;
396
+ if (value.type === 'LogicalExpression')
397
+ return declarationsOf(value.right);
398
+ if (value.type === 'ConditionalExpression')
399
+ return [
400
+ ...declarationsOf(value.consequent),
401
+ ...declarationsOf(value.alternate),
402
+ ];
403
+ return [];
404
+ };
189
405
  return {
406
+ Program(program) {
407
+ if (entry) {
408
+ moduleByLocal.set(entry.binding, entry);
409
+ for (const owner of Object.keys(entry.aliases ?? {}))
410
+ moduleByLocal.set(owner, entry);
411
+ }
412
+ for (const node of program.body) {
413
+ if (node.type !== 'ImportDeclaration')
414
+ continue;
415
+ const specifier = String(node.source.value);
416
+ if (specifier === '@plumeria/core') {
417
+ coreImport = node;
418
+ for (const candidate of node.specifiers) {
419
+ if (candidate.type === 'ImportNamespaceSpecifier')
420
+ coreLocal = candidate.local.name;
421
+ else if (candidate.type === 'ImportSpecifier' &&
422
+ (candidate.imported.name ?? candidate.imported.value) === 'css')
423
+ coreLocal = candidate.local.name;
424
+ }
425
+ continue;
426
+ }
427
+ const table = importedFrom(specifier, raw.values);
428
+ if (table)
429
+ for (const candidate of node.specifiers) {
430
+ if (candidate.type !== 'ImportSpecifier')
431
+ continue;
432
+ const imported = candidate.imported.name ?? candidate.imported.value;
433
+ if (imported in table)
434
+ inlineByLocal.set(candidate.local.name, table[imported]);
435
+ }
436
+ const module = importedReleasedModule(specifier);
437
+ if (!module)
438
+ continue;
439
+ const importedBinding = node.specifiers.find((candidate) => (candidate.type === 'ImportSpecifier' &&
440
+ (candidate.imported.name ?? candidate.imported.value) ===
441
+ module.binding) ||
442
+ (candidate.type === 'ImportDefaultSpecifier' &&
443
+ specifier.endsWith('.css')));
444
+ if (!importedBinding)
445
+ continue;
446
+ const local = importedBinding.local.name;
447
+ moduleByLocal.set(local, { ...module, binding: local });
448
+ active = true;
449
+ }
450
+ const migrating = active || themeBindings.length > 0 || animationBindings.length > 0;
451
+ coreNeeded = keptDefinition || !migrating || survivesCore(program);
452
+ if (coreImport &&
453
+ !coreNeeded &&
454
+ !entry &&
455
+ themeBindings.length === 0 &&
456
+ animationBindings.length === 0) {
457
+ context.report({
458
+ node: coreImport,
459
+ messageId: 'core',
460
+ fix: (fixer) => removeStatement(fixer, coreImport),
461
+ });
462
+ }
463
+ },
190
464
  ImportDeclaration(node) {
191
- if ((entry || themeBindings.length > 0 || animationBindings.length > 0) &&
465
+ if (!coreNeeded &&
466
+ (entry || themeBindings.length > 0 || animationBindings.length > 0) &&
192
467
  node.source.value === '@plumeria/core') {
193
468
  context.report({
194
469
  node,
195
470
  messageId: 'core',
196
- fix: (fixer) => fixer.remove(node),
471
+ fix: (fixer) => removeStatement(fixer, node),
197
472
  });
198
473
  return;
199
474
  }
@@ -214,12 +489,10 @@ exports.releaseStyles = {
214
489
  }
215
490
  const importedModule = importedReleasedModule(String(node.source.value));
216
491
  if (importedModule) {
217
- active = true;
218
492
  const importedBinding = node.specifiers.find((specifier) => specifier.type === 'ImportSpecifier' &&
219
493
  (specifier.imported.name ?? specifier.imported.value) ===
220
494
  importedModule.binding);
221
495
  const local = importedBinding?.local.name ?? importedModule.binding;
222
- activeModule = { ...importedModule, binding: local };
223
496
  if (importedModule.definitionOnly && importedBinding) {
224
497
  const retained = node.specifiers.filter((specifier) => specifier !== importedBinding);
225
498
  const released = `import ${local} from '${specifierFor(importedModule)}';`;
@@ -256,7 +529,7 @@ exports.releaseStyles = {
256
529
  : path[1] === 'keyframes' || path[1] === 'viewTransition'
257
530
  ? 'animation'
258
531
  : 'static',
259
- fix: (fixer) => fixer.remove(target),
532
+ fix: (fixer) => removeStatement(fixer, target),
260
533
  });
261
534
  return;
262
535
  }
@@ -269,7 +542,7 @@ exports.releaseStyles = {
269
542
  context.report({
270
543
  node: aliased,
271
544
  messageId: 'definition',
272
- fix: (fixer) => fixer.remove(aliased),
545
+ fix: (fixer) => removeStatement(fixer, aliased),
273
546
  });
274
547
  return;
275
548
  }
@@ -281,13 +554,73 @@ exports.releaseStyles = {
281
554
  const replacement = exported
282
555
  ? `${imported}\nexport { ${entry.binding} };`
283
556
  : imported;
557
+ const anchor = importAnchor();
284
558
  context.report({
285
559
  node: target,
286
560
  messageId: 'definition',
287
- fix: (fixer) => fixer.replaceText(target, replacement),
561
+ fix: (fixer) => anchor >= target.range[0] && anchor <= target.range[1]
562
+ ? fixer.replaceText(target, replacement)
563
+ : [
564
+ fixer.insertTextBeforeRange([anchor, anchor], `${replacement}\n`),
565
+ removeStatement(fixer, target),
566
+ ],
567
+ });
568
+ },
569
+ Identifier(node) {
570
+ const value = inlineByLocal.get(node.name);
571
+ const literal = literalOf(value);
572
+ if (literal === undefined)
573
+ return;
574
+ const parent = node.parent;
575
+ if (parent?.type === 'ImportSpecifier' ||
576
+ parent?.type === 'ExportSpecifier' ||
577
+ (parent?.type === 'MemberExpression' && parent.property === node) ||
578
+ (parent?.type === 'Property' &&
579
+ parent.key === node &&
580
+ !parent.computed))
581
+ return;
582
+ context.report({
583
+ node,
584
+ messageId: 'token',
585
+ fix: (fixer) => fixer.replaceText(node, literal),
586
+ });
587
+ },
588
+ TSTypeReference(node) {
589
+ if (!complete || keptDefinition)
590
+ return;
591
+ const name = node.typeName.type === 'TSQualifiedName'
592
+ ? node.typeName.right.name
593
+ : node.typeName.name;
594
+ if (name !== 'Style')
595
+ return;
596
+ context.report({
597
+ node,
598
+ messageId: 'styleType',
599
+ fix: (fixer) => fixer.replaceText(node, 'string'),
288
600
  });
289
601
  },
290
602
  MemberExpression(node) {
603
+ if (node.object.type === 'Identifier') {
604
+ const table = inlineByLocal.get(node.object.name);
605
+ if (table && typeof table === 'object') {
606
+ const key = node.computed
607
+ ? node.property.type === 'Literal'
608
+ ? String(node.property.value)
609
+ : undefined
610
+ : node.property.name;
611
+ const literal = key === undefined
612
+ ? undefined
613
+ : literalOf(table[key]);
614
+ if (literal !== undefined) {
615
+ context.report({
616
+ node,
617
+ messageId: 'token',
618
+ fix: (fixer) => fixer.replaceText(node, literal),
619
+ });
620
+ return;
621
+ }
622
+ }
623
+ }
291
624
  if (!entry?.aliases || node.object.type !== 'Identifier')
292
625
  return;
293
626
  const alias = entry.aliases[node.object.name];
@@ -310,6 +643,28 @@ exports.releaseStyles = {
310
643
  fix: (fixer) => fixer.replaceText(node, `${entry.binding}${access}`),
311
644
  });
312
645
  },
646
+ CallExpression(node) {
647
+ if (node.callee.type !== 'MemberExpression' ||
648
+ node.callee.object.type !== 'Identifier' ||
649
+ node.callee.property.name !== 'use')
650
+ return;
651
+ const reader = node.callee.object.name;
652
+ if (coreLocal ? reader !== coreLocal : moduleByLocal.has(reader))
653
+ return;
654
+ const texts = node.arguments.map(resolveText);
655
+ if (texts.length === 0 || texts.some((text) => text === undefined))
656
+ return;
657
+ const replacement = texts.length === 1 && !node.arguments.some(needsFilter)
658
+ ? texts[0]
659
+ : node.arguments.some(needsFilter)
660
+ ? `[${texts.join(', ')}].filter(Boolean).join(' ')`
661
+ : `[${texts.join(', ')}].join(' ')`;
662
+ context.report({
663
+ node,
664
+ messageId: 'use',
665
+ fix: (fixer) => fixer.replaceText(node, replacement),
666
+ });
667
+ },
313
668
  JSXAttribute(node) {
314
669
  if (!active)
315
670
  return;
@@ -318,40 +673,49 @@ exports.releaseStyles = {
318
673
  const expression = node.value?.type === 'JSXExpressionContainer'
319
674
  ? node.value.expression
320
675
  : undefined;
321
- const moduleInfo = activeModule;
322
- const readsModule = (callee) => Boolean(moduleInfo &&
323
- callee?.type === 'MemberExpression' &&
324
- callee.object.type === 'Identifier' &&
325
- (callee.object.name === moduleInfo.binding ||
326
- moduleInfo.aliases?.[callee.object.name]));
327
- const styleKey = (callee) => {
328
- if (!readsModule(callee))
329
- return undefined;
330
- const raw = callee.computed
331
- ? callee.property.type === 'Literal'
332
- ? String(callee.property.value)
333
- : undefined
334
- : callee.property.name;
335
- if (raw === undefined)
336
- return undefined;
337
- const alias = moduleInfo.aliases?.[callee.object.name];
338
- return alias ? alias[raw] : raw;
676
+ const carried = node.parent.attributes.find((attribute) => attribute !== node &&
677
+ attribute.type === 'JSXAttribute' &&
678
+ attribute.name?.type === 'JSXIdentifier' &&
679
+ attribute.name.name === 'className');
680
+ const takeProp = (valueText) => {
681
+ if (!carried) {
682
+ context.report({
683
+ node: node.name,
684
+ messageId: 'prop',
685
+ fix: (fixer) => fixer.replaceText(node.name, 'className'),
686
+ });
687
+ return;
688
+ }
689
+ const ours = valueText ??
690
+ (node.value ? sourceCode.getText(node.value).slice(1, -1) : '');
691
+ const theirs = carried.value?.type === 'JSXExpressionContainer'
692
+ ? sourceCode.getText(carried.value.expression)
693
+ : carried.value
694
+ ? sourceCode.getText(carried.value)
695
+ : "''";
696
+ context.report({
697
+ node,
698
+ messageId: 'prop',
699
+ fix: (fixer) => [
700
+ fixer.replaceText(carried, `className={[${theirs}, ${ours}].filter(Boolean).join(' ')}`),
701
+ fixer.removeRange([
702
+ Math.min(node.range[0], carried.range[1]),
703
+ node.range[1],
704
+ ]),
705
+ ],
706
+ });
339
707
  };
340
- const functionStyle = (value) => {
341
- if (!moduleInfo || value?.type !== 'CallExpression')
708
+ const mergedClass = (elements) => {
709
+ if (elements.length < 2)
710
+ return undefined;
711
+ const resolved = elements.map(styleKey);
712
+ if (resolved.some((part) => !part))
342
713
  return undefined;
343
- const key = styleKey(value.callee);
344
- const func = key ? moduleInfo.functions?.[key] : undefined;
345
- if (!key || !func || func.variables.length !== value.arguments.length)
714
+ const module = resolved[0].module;
715
+ if (resolved.some((part) => part.module !== module))
346
716
  return undefined;
347
- const access = IDENTIFIER.test(key) ? `.${key}` : `['${key}']`;
348
- return {
349
- className: `${moduleInfo.binding}${access}`,
350
- declarations: func.variables.map((variable, index) => {
351
- const argument = sourceCode.getText(value.arguments[index]);
352
- return `'${variable}': ${argument}`;
353
- }),
354
- };
717
+ const name = module.merges?.[resolved.map((part) => part.key).join('|')];
718
+ return name ? accessOf(module, name) : undefined;
355
719
  };
356
720
  const replaceWithFunctionStyle = (className, declarations) => {
357
721
  const styleAttribute = node.parent.attributes.find((attribute) => attribute.type === 'JSXAttribute' &&
@@ -387,7 +751,7 @@ exports.releaseStyles = {
387
751
  return;
388
752
  }
389
753
  if (expression?.type === 'CallExpression' &&
390
- readsModule(expression.callee)) {
754
+ moduleOf(expression.callee)) {
391
755
  context.report({
392
756
  node,
393
757
  messageId: 'unsupportedFunctionStyle',
@@ -396,34 +760,70 @@ exports.releaseStyles = {
396
760
  }
397
761
  if (expression?.type === 'ArrayExpression') {
398
762
  const elements = expression.elements.filter(Boolean);
399
- if (elements.length > 0) {
400
- const declarations = [];
401
- const texts = elements.map((element) => {
402
- const resolved = functionStyle(element);
403
- if (!resolved)
404
- return sourceCode.getText(element);
405
- declarations.push(...resolved.declarations);
406
- return resolved.className;
407
- });
408
- const replacement = elements.some(needsFilter)
409
- ? `[${texts.join(', ')}].filter(Boolean).join(' ')`
410
- : `\`${texts.map((text) => `\${${text}}`).join(' ')}\``;
411
- if (declarations.length > 0) {
412
- replaceWithFunctionStyle(replacement, declarations);
763
+ const merged = mergedClass(elements);
764
+ if (merged) {
765
+ if (carried) {
766
+ takeProp(merged);
413
767
  return;
414
768
  }
415
769
  context.report({
416
770
  node: expression,
417
771
  messageId: 'composition',
418
- fix: (fixer) => fixer.replaceText(expression, replacement),
772
+ fix: (fixer) => fixer.replaceText(expression, merged),
419
773
  });
774
+ takeProp();
775
+ return;
776
+ }
777
+ if (elements.length === 0) {
778
+ takeProp();
779
+ return;
420
780
  }
781
+ const texts = elements.map(resolveText);
782
+ if (texts.some((text) => text === undefined))
783
+ return;
784
+ const ranked = elements.map((element) => element.type === 'LogicalExpression'
785
+ ? styleKey(element.right)
786
+ : styleKey(element));
787
+ const module = ranked[0]?.module;
788
+ const perSlot = module && ranked.every((part) => part?.module === module)
789
+ ? module.overrides?.[ranked.map((part) => part.key).join('|')]
790
+ : undefined;
791
+ if (perSlot) {
792
+ for (let slot = elements.length - 1; slot >= 0; slot--) {
793
+ const name = perSlot[slot];
794
+ if (!name)
795
+ continue;
796
+ const element = elements[slot];
797
+ const access = accessOf(module, name);
798
+ texts.splice(slot + 1, 0, element.type === 'LogicalExpression'
799
+ ? `${sourceCode.getText(element.left)} ${element.operator} ${access}`
800
+ : access);
801
+ elements.splice(slot + 1, 0, element);
802
+ }
803
+ }
804
+ const declarations = elements.flatMap(declarationsOf);
805
+ const replacement = elements.some(needsFilter)
806
+ ? `[${texts.join(', ')}].filter(Boolean).join(' ')`
807
+ : `[${texts.join(', ')}].join(' ')`;
808
+ if (declarations.length > 0) {
809
+ replaceWithFunctionStyle(replacement, declarations);
810
+ return;
811
+ }
812
+ if (carried) {
813
+ takeProp(replacement);
814
+ return;
815
+ }
816
+ context.report({
817
+ node: expression,
818
+ messageId: 'composition',
819
+ fix: (fixer) => fixer.replaceText(expression, replacement),
820
+ });
821
+ takeProp();
822
+ return;
421
823
  }
422
- context.report({
423
- node: node.name,
424
- messageId: 'prop',
425
- fix: (fixer) => fixer.replaceText(node.name, 'className'),
426
- });
824
+ if (!complete && expression && resolveText(expression) === undefined)
825
+ return;
826
+ takeProp();
427
827
  },
428
828
  };
429
829
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plumeria/codemod",
3
- "version": "18.2.19",
3
+ "version": "18.2.20",
4
4
  "description": "Codemods for migrating Plumeria APIs",
5
5
  "author": "Refirst 11",
6
6
  "license": "MIT",