@plumeria/codemod 18.2.23 → 18.2.24

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/cli.js CHANGED
@@ -302,8 +302,11 @@ async function migrateFromCssModules(options) {
302
302
  return 0;
303
303
  }
304
304
  for (const sheet of stylesheets) {
305
+ if (!modules[sheet.source])
306
+ continue;
305
307
  console.log(` ${relative(sheet.source)} -> ${relative(sheet.target)}`);
306
308
  }
309
+ const converting = Object.keys(modules).length;
307
310
  const config = {
308
311
  overrideConfigFile: true,
309
312
  overrideConfig: migrateConfig(modules),
@@ -312,7 +315,7 @@ async function migrateFromCssModules(options) {
312
315
  if (dryRun) {
313
316
  const results = await new eslint_1.ESLint(config).lintFiles(targets);
314
317
  const touched = results.filter((r) => r.messages.length > 0);
315
- console.log(`\n${stylesheets.length} stylesheet(s) would be converted, ${touched.length} consumer(s) rewritten.`);
318
+ console.log(`\n${converting} stylesheet(s) would be converted, ${touched.length} consumer(s) rewritten.`);
316
319
  console.log('Run without --dry-run to apply.');
317
320
  }
318
321
  else {
@@ -321,7 +324,7 @@ async function migrateFromCssModules(options) {
321
324
  const applied = await new eslint_1.ESLint({ ...config, fix: true }).lintFiles(targets);
322
325
  await eslint_1.ESLint.outputFixes(applied);
323
326
  const touched = applied.filter((r) => r.output !== undefined);
324
- console.log(`\n✔ converted ${stylesheets.length} stylesheet(s) and rewrote ${touched.length} consumer(s).`);
327
+ console.log(`\n✔ converted ${converting} stylesheet(s) and rewrote ${touched.length} consumer(s).`);
325
328
  }
326
329
  const lines = (0, migrate_1.formatReports)(stylesheets, cwd);
327
330
  if (lines.length === 0)
package/dist/migrate.js CHANGED
@@ -82,6 +82,23 @@ function plan(targets) {
82
82
  for (const source of findStylesheets(targets)) {
83
83
  const converted = (0, css_modules_1.convertStylesheet)(fs.readFileSync(source, 'utf-8'));
84
84
  const target = (0, exports.targetPath)(source);
85
+ if (fs.existsSync(target)) {
86
+ stylesheets.push({
87
+ source,
88
+ target,
89
+ reports: [
90
+ ...converted.reports,
91
+ {
92
+ line: 0,
93
+ column: 0,
94
+ kind: 'target-exists',
95
+ source: '',
96
+ hint: `${path.basename(target)} already exists and was not overwritten.`,
97
+ },
98
+ ],
99
+ });
100
+ continue;
101
+ }
85
102
  stylesheets.push({ source, target, reports: converted.reports });
86
103
  modules[source] = {
87
104
  source: `./${path.basename(target, '.ts')}`,
@@ -96,6 +113,8 @@ function plan(targets) {
96
113
  function write(targets) {
97
114
  const result = plan(targets);
98
115
  for (const sheet of result.stylesheets) {
116
+ if (!result.modules[sheet.source])
117
+ continue;
99
118
  const converted = (0, css_modules_1.convertStylesheet)(fs.readFileSync(sheet.source, 'utf-8'));
100
119
  fs.writeFileSync(sheet.target, converted.code, 'utf-8');
101
120
  }
@@ -108,7 +127,7 @@ function formatReports(sheets, cwd) {
108
127
  continue;
109
128
  lines.push(path.relative(cwd, sheet.source) || sheet.source);
110
129
  for (const report of sheet.reports) {
111
- lines.push(` ${report.line}:${report.column} ${report.kind} ${report.source}`);
130
+ lines.push(` ${report.line}:${report.column} ${report.kind}${report.source ? ` ${report.source}` : ''}`);
112
131
  lines.push(` ${report.hint}`);
113
132
  }
114
133
  }
@@ -55,6 +55,7 @@ exports.adoptStyles = {
55
55
  source: 'Import the generated styles from "{{source}}" instead of the stylesheet.',
56
56
  core: `Add \`import '${CORE}';\` — the compiler collects a file only when it imports the package.`,
57
57
  key: 'The generated key for "{{from}}" is "{{to}}".',
58
+ binding: 'The stylesheet import is now "{{local}}".',
58
59
  prop: 'Styles are carried by the "{{prop}}" prop.',
59
60
  composes: '"{{key}}" composed other classes; pass them as an array.',
60
61
  value: 'A style read as a value goes back through `css.use`.',
@@ -138,9 +139,9 @@ exports.adoptStyles = {
138
139
  : !read.computed && read.property.type === 'Identifier'
139
140
  ? read.property.name
140
141
  : undefined;
141
- if (written === undefined)
142
- return context.sourceCode.getText(element);
143
- const access = `${binding.local}.${binding.entry.names[written] ?? written}`;
142
+ const access = written === undefined
143
+ ? `${binding.local}${context.sourceCode.text.slice(read.object.range[1], read.range[1])}`
144
+ : `${binding.local}.${binding.entry.names[written] ?? written}`;
144
145
  return element.type === 'LogicalExpression'
145
146
  ? `${context.sourceCode.getText(element.left)} ${element.operator} ${access}`
146
147
  : access;
@@ -311,15 +312,23 @@ exports.adoptStyles = {
311
312
  : undefined;
312
313
  if (!binding)
313
314
  return;
315
+ const local = binding.local;
314
316
  const written = node.computed && node.property.type === 'Literal'
315
317
  ? String(node.property.value)
316
318
  : !node.computed && node.property.type === 'Identifier'
317
319
  ? node.property.name
318
320
  : null;
319
- if (written === null)
321
+ if (written === null) {
322
+ if (local !== node.object.name)
323
+ context.report({
324
+ node: node.object,
325
+ messageId: 'binding',
326
+ data: { local },
327
+ fix: (fixer) => fixer.replaceText(node.object, local),
328
+ });
320
329
  return;
330
+ }
321
331
  const key = binding.entry.names[written] ?? written;
322
- const local = binding.local;
323
332
  if (coreLocal && !stylingPosition(node) && !insideUse(node)) {
324
333
  context.report({
325
334
  node,
@@ -100,6 +100,7 @@ exports.releaseStyles = {
100
100
  token: 'Inline the value this token resolved to.',
101
101
  styleType: 'A released style is a class name.',
102
102
  folded: 'This only named a style key, which the export has resolved.',
103
+ read: 'Read this style by the name the export gave it.',
103
104
  functionStyle: 'Use the CSS Module class and pass function arguments as custom properties.',
104
105
  unsupportedFunctionStyle: 'This function style call cannot be converted without changing its behavior.',
105
106
  },
@@ -269,13 +270,16 @@ exports.releaseStyles = {
269
270
  }
270
271
  return undefined;
271
272
  };
273
+ const moduleVariable = (local) => {
274
+ const scope = sourceCode.getScope(sourceCode.ast);
275
+ const moduleScope = scope.type === 'global' ? (scope.childScopes[0] ?? scope) : scope;
276
+ return moduleScope.variables.find((candidate) => candidate.name === local);
277
+ };
272
278
  const onlyUsedByReleasedStyles = (local) => {
273
279
  const range = releasedRange();
274
280
  if (!range)
275
281
  return false;
276
- const scope = sourceCode.getScope(sourceCode.ast);
277
- const moduleScope = scope.type === 'global' ? (scope.childScopes[0] ?? scope) : scope;
278
- const variable = moduleScope.variables.find((candidate) => candidate.name === local);
282
+ const variable = moduleVariable(local);
279
283
  if (!variable || variable.references.length === 0)
280
284
  return false;
281
285
  return variable.references.every((reference) => reference.identifier.range[0] >= range[0] &&
@@ -291,12 +295,20 @@ exports.releaseStyles = {
291
295
  if (removed.length === 0)
292
296
  return false;
293
297
  const retained = node.specifiers.filter((specifier) => !removed.includes(specifier));
298
+ const named = node.specifiers.every((specifier) => specifier.type === 'ImportSpecifier');
294
299
  context.report({
295
300
  node,
296
301
  messageId,
297
302
  fix: (fixer) => retained.length === 0
298
303
  ? removeStatement(fixer, node)
299
- : fixer.replaceText(node, importOf(retained, node.source)),
304
+ : named
305
+ ? fixer.replaceTextRange([
306
+ node.specifiers[0].range[0],
307
+ node.specifiers[node.specifiers.length - 1].range[1],
308
+ ], retained
309
+ .map((specifier) => sourceCode.getText(specifier))
310
+ .join(', '))
311
+ : fixer.replaceText(node, importOf(retained, node.source)),
300
312
  });
301
313
  return true;
302
314
  };
@@ -321,6 +333,19 @@ exports.releaseStyles = {
321
333
  const key = alias ? alias[written] : written;
322
334
  return key === undefined ? undefined : { module, key };
323
335
  };
336
+ const onlyNamesStyleKeys = (variable) => {
337
+ if (!variable || variable.references.length === 0)
338
+ return false;
339
+ return variable.references.every((reference) => {
340
+ if (reference.init)
341
+ return true;
342
+ const parent = reference.identifier.parent;
343
+ return (parent?.type === 'MemberExpression' &&
344
+ parent.computed &&
345
+ parent.property === reference.identifier &&
346
+ Boolean(moduleOf(parent)));
347
+ });
348
+ };
324
349
  const accessOf = (module, key) => `${module.binding}${IDENTIFIER.test(key) ? `.${key}` : `['${key}']`}`;
325
350
  const classText = (node) => {
326
351
  const resolved = styleKey(node);
@@ -526,6 +551,19 @@ exports.releaseStyles = {
526
551
  if (removeImportedBindings(node, importedStatics, 'staticImport', (specifier) => onlyUsedByReleasedStyles(specifier.local.name)))
527
552
  return;
528
553
  }
554
+ const foldedKeys = node.specifiers
555
+ .filter((specifier) => specifier.type === 'ImportSpecifier' &&
556
+ constants[specifier.local.name] !== undefined)
557
+ .map((specifier) => specifier.imported.name ?? specifier.imported.value);
558
+ if (active && foldedKeys.length > 0) {
559
+ if (removeImportedBindings(node, foldedKeys, 'folded', (specifier) => {
560
+ const variable = moduleVariable(specifier.local.name);
561
+ return Boolean(variable &&
562
+ (variable.references.length === 0 ||
563
+ onlyNamesStyleKeys(variable)));
564
+ }))
565
+ return;
566
+ }
529
567
  const importedModule = importedReleasedModule(String(node.source.value));
530
568
  if (importedModule) {
531
569
  const importedBinding = node.specifiers.find((specifier) => specifier.type === 'ImportSpecifier' &&
@@ -634,19 +672,7 @@ exports.releaseStyles = {
634
672
  node.parent.parent?.type === 'ExportNamedDeclaration')
635
673
  return;
636
674
  const scope = sourceCode.getScope(node);
637
- const variable = scope.variables.find((candidate) => candidate.name === node.id.name);
638
- if (!variable || variable.references.length === 0)
639
- return;
640
- const onlyNamesKeys = variable.references.every((reference) => {
641
- if (reference.init)
642
- return true;
643
- const parent = reference.identifier.parent;
644
- return (parent?.type === 'MemberExpression' &&
645
- parent.computed &&
646
- parent.property === reference.identifier &&
647
- Boolean(moduleOf(parent)));
648
- });
649
- if (!onlyNamesKeys)
675
+ if (!onlyNamesStyleKeys(scope.variables.find((candidate) => candidate.name === node.id.name)))
650
676
  return;
651
677
  const target = node.parent;
652
678
  context.report({
@@ -894,8 +920,26 @@ exports.releaseStyles = {
894
920
  takeProp();
895
921
  return;
896
922
  }
897
- if (!complete && expression && resolveText(expression) === undefined)
923
+ const text = expression ? resolveText(expression) : undefined;
924
+ if (!complete && expression && text === undefined)
898
925
  return;
926
+ const foldsKey = expression?.type === 'MemberExpression' &&
927
+ expression.computed &&
928
+ expression.property.type === 'Identifier' &&
929
+ constants[expression.property.name] !== undefined;
930
+ if (foldsKey &&
931
+ text !== undefined &&
932
+ text !== sourceCode.getText(expression)) {
933
+ if (carried) {
934
+ takeProp(text);
935
+ return;
936
+ }
937
+ context.report({
938
+ node: expression,
939
+ messageId: 'read',
940
+ fix: (fixer) => fixer.replaceText(expression, text),
941
+ });
942
+ }
899
943
  takeProp();
900
944
  },
901
945
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plumeria/codemod",
3
- "version": "18.2.23",
3
+ "version": "18.2.24",
4
4
  "description": "Codemods for migrating Plumeria APIs",
5
5
  "author": "Refirst 11",
6
6
  "license": "MIT",