@plumeria/codemod 18.2.21 → 18.2.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -194,8 +194,10 @@ under the same condition as the style that has to win:
194
194
  ```
195
195
 
196
196
  Order is only forced where the two classes actually disagree, measured per
197
- at-rule and per selector. Anything left unsettled is reported as
198
- `composition-order`.
197
+ at-rule and per selector. Where one property covers another — `padding` against
198
+ `padding-top` — Plumeria settles it by specificity rather than order, so the
199
+ shorthand is written first and the array has no say. Anything left unsettled is
200
+ reported as `composition-order`.
199
201
 
200
202
  A variant picked with a bracket is resolved to the class its key names, whether
201
203
  the constant is declared in the same file or imported, and a constant left
package/dist/release.js CHANGED
@@ -326,7 +326,11 @@ function planRelease(targets) {
326
326
  const held = new Map();
327
327
  const exported = new Map();
328
328
  const duplicated = new Set();
329
+ const seen = new Set();
329
330
  const read = (statement, isExport) => {
331
+ if (seen.has(statement))
332
+ return;
333
+ seen.add(statement);
330
334
  if (statement?.type !== 'VariableDeclaration' ||
331
335
  statement.kind !== 'const')
332
336
  return;
@@ -359,6 +363,8 @@ function planRelease(targets) {
359
363
  }
360
364
  const resolvedConstants = new Map();
361
365
  const compositions = new Map();
366
+ const foldings = new Map();
367
+ const reached = new Map();
362
368
  for (const source of sources) {
363
369
  let ast;
364
370
  try {
@@ -449,14 +455,43 @@ function planRelease(targets) {
449
455
  if (owners_.some((candidate) => !candidate))
450
456
  return;
451
457
  const owner = owners_[0].source;
452
- if (owners_.some((candidate) => candidate.source !== owner))
453
- return;
454
458
  const named = slots.map((slot) => slot.map((part) => ({
455
459
  binding: owners.get(part.object).binding,
456
460
  key: part.key,
457
461
  })));
458
462
  const collapsible = expression.elements.every((element) => memberOf(element)) &&
459
463
  named.every((slot) => slot.length === 1);
464
+ if (owners_.some((candidate) => candidate.source !== owner)) {
465
+ if (!collapsible)
466
+ return;
467
+ const parts = slots.map((slot, index) => {
468
+ const held = owners.get(slot[0].object);
469
+ return {
470
+ binding: held.binding,
471
+ key: slot[0].key,
472
+ ...(held.source === owner ? {} : { source: held.source }),
473
+ target: (0, exports.releasedPath)(held.source),
474
+ at: index,
475
+ };
476
+ });
477
+ foldings.set(owner, [
478
+ ...(foldings.get(owner) ?? []),
479
+ {
480
+ signature: parts
481
+ .map((part) => `${part.target}#${part.key}`)
482
+ .join('|'),
483
+ parts: parts.map(({ binding, key, source }) => ({
484
+ binding,
485
+ key,
486
+ ...(source ? { source } : {}),
487
+ })),
488
+ },
489
+ ]);
490
+ for (const part of parts)
491
+ if (part.source)
492
+ reached.set(owner, (reached.get(owner) ?? new Set()).add(part.source));
493
+ return;
494
+ }
460
495
  compositions.set(owner, [
461
496
  ...(compositions.get(owner) ?? []),
462
497
  {
@@ -473,7 +508,22 @@ function planRelease(targets) {
473
508
  for (const source of sources) {
474
509
  let module;
475
510
  try {
476
- module = (0, from_plumeria_1.convertPlumeriaModule)(sourceText.get(source), importedPlumeriaValues(source), compositions.get(source) ?? []);
511
+ module = (0, from_plumeria_1.convertPlumeriaModule)(sourceText.get(source), importedPlumeriaValues(source), compositions.get(source) ?? [], {
512
+ target: (0, exports.releasedPath)(source),
513
+ foldings: foldings.get(source) ?? [],
514
+ foreign: Object.fromEntries([...(reached.get(source) ?? [])].map((other) => [
515
+ other,
516
+ {
517
+ text: sourceText.get(other),
518
+ values: {
519
+ ...importedPlumeriaValues(other),
520
+ ...(staticValues.get(other) ?? {}),
521
+ ...(themeValues.get(other) ?? {}),
522
+ ...(animationValues.get(other) ?? {}),
523
+ },
524
+ },
525
+ ])),
526
+ });
477
527
  }
478
528
  catch {
479
529
  continue;
@@ -80,6 +80,13 @@ exports.adoptStyles = {
80
80
  let coreLocal;
81
81
  let readsValue = false;
82
82
  let several = false;
83
+ const moduleFor = (specifier) => {
84
+ const resolved = (0, resolve_1.resolveSourcePath)(specifier, context.filename);
85
+ if (resolved && modules[resolved])
86
+ return modules[resolved];
87
+ const named = Object.entries(modules).filter(([key]) => key === specifier || path.basename(key) === path.basename(specifier));
88
+ return named.length === 1 ? named[0][1] : undefined;
89
+ };
83
90
  const stylingPosition = (node) => {
84
91
  let current = node;
85
92
  while (current.parent &&
@@ -143,6 +150,26 @@ exports.adoptStyles = {
143
150
  : node?.type === 'MemberExpression'
144
151
  ? node
145
152
  : undefined;
153
+ const unguarded = (node) => {
154
+ if (node?.type !== 'ConditionalExpression')
155
+ return node;
156
+ const { test, consequent, alternate } = node;
157
+ if (test.type !== 'BinaryExpression' ||
158
+ test.operator !== '===' ||
159
+ test.left.type !== 'UnaryExpression' ||
160
+ test.left.operator !== 'typeof' ||
161
+ test.right.type !== 'Literal' ||
162
+ test.right.value !== 'number' ||
163
+ consequent.type !== 'TemplateLiteral' ||
164
+ consequent.expressions.length !== 1 ||
165
+ consequent.quasis.at(-1)?.value.raw !== 'px')
166
+ return node;
167
+ const written = context.sourceCode.getText(alternate);
168
+ return context.sourceCode.getText(test.left.argument) === written &&
169
+ context.sourceCode.getText(consequent.expressions[0]) === written
170
+ ? alternate
171
+ : node;
172
+ };
146
173
  const insideUse = (node) => {
147
174
  let current = node;
148
175
  while (current.parent &&
@@ -171,8 +198,7 @@ exports.adoptStyles = {
171
198
  coreLocal = specifier.local.name;
172
199
  continue;
173
200
  }
174
- const target = (0, resolve_1.resolveSourcePath)(String(statement.source.value), context.filename);
175
- if (!target || !modules[target])
201
+ if (!moduleFor(String(statement.source.value)))
176
202
  continue;
177
203
  for (const specifier of statement.specifiers)
178
204
  if (specifier.type === 'ImportDefaultSpecifier') {
@@ -229,14 +255,12 @@ exports.adoptStyles = {
229
255
  },
230
256
  ImportDeclaration(node) {
231
257
  const specifier = String(node.source.value);
232
- const resolved = (0, resolve_1.resolveSourcePath)(specifier, context.filename);
233
- const named = Object.entries(modules).filter(([key]) => key === specifier ||
234
- path.basename(key) === path.basename(specifier));
235
- const entry = (resolved ? modules[resolved] : undefined) ??
236
- (named.length === 1 ? named[0][1] : undefined);
258
+ const entry = moduleFor(specifier);
237
259
  if (!entry) {
238
- const adopted = resolved &&
239
- Object.values(modules).find((candidate) => candidate.target === resolved);
260
+ const resolved = (0, resolve_1.resolveSourcePath)(specifier, context.filename);
261
+ const adopted = Object.values(modules).find((candidate) => candidate.target === resolved ||
262
+ candidate.source === specifier ||
263
+ path.basename(candidate.source) === path.basename(specifier));
240
264
  if (!adopted)
241
265
  return;
242
266
  const already = node.specifiers.find((specifier) => specifier.type === 'ImportSpecifier' &&
@@ -355,7 +379,7 @@ exports.adoptStyles = {
355
379
  for (const variable of variables)
356
380
  consumed.add(named.get(variable));
357
381
  return `${binding.local}.${key}(${variables
358
- .map((variable) => context.sourceCode.getText(named.get(variable).value))
382
+ .map((variable) => context.sourceCode.getText(unguarded(named.get(variable).value)))
359
383
  .join(', ')})`;
360
384
  };
361
385
  const expression = node.value.expression;
@@ -402,7 +426,21 @@ exports.adoptStyles = {
402
426
  if (joined) {
403
427
  if (!joined.some((element) => bindings.has(styleRead(element)?.object?.name)))
404
428
  return;
405
- const list = joined.map(renamedText).join(', ');
429
+ const styles_ = joined.filter((element) => bindings.has(styleRead(element)?.object?.name));
430
+ const carried = joined.filter((element) => !styles_.includes(element));
431
+ const list = styles_.map(renamedText).join(', ');
432
+ if (carried.length > 0) {
433
+ const kept = carried
434
+ .map((element) => context.sourceCode.getText(element))
435
+ .join(', ');
436
+ context.report({
437
+ node,
438
+ messageId: 'prop',
439
+ data: { prop: styleProp },
440
+ fix: (fixer) => fixer.replaceText(node, `className={[${kept}].filter(Boolean).join(' ')} ${styleProp}={[${list}]}`),
441
+ });
442
+ return;
443
+ }
406
444
  if (!intrinsic(node.parent) && coreLocal) {
407
445
  context.report({
408
446
  node: expression,
@@ -12,6 +12,7 @@ export interface PlumeriaModule {
12
12
  functions: Record<string, {
13
13
  params: string[];
14
14
  variables: string[];
15
+ lengths: boolean[];
15
16
  }>;
16
17
  aliases: Record<string, Record<string, string>>;
17
18
  merges: Record<string, string>;
@@ -32,7 +33,22 @@ export interface PlumeriaComposition {
32
33
  line: number;
33
34
  column: number;
34
35
  }
35
- export declare const compositionSignature: (classNames: string[]) => string;
36
+ export interface PlumeriaFolding {
37
+ signature: string;
38
+ parts: {
39
+ binding: string;
40
+ key: string;
41
+ source?: string;
42
+ }[];
43
+ }
44
+ export interface PlumeriaForeign {
45
+ text: string;
46
+ values: Record<string, unknown>;
47
+ }
48
+ export declare const compositionSignature: (parts: {
49
+ target: string;
50
+ className: string;
51
+ }[]) => string;
36
52
  export interface ThemeExtraction {
37
53
  bindings: Record<string, Record<string, string>>;
38
54
  globalCss: string;
@@ -58,4 +74,8 @@ export declare const __private: {
58
74
  export declare function extractPlumeriaThemes(source: string): ThemeExtraction;
59
75
  export declare function extractPlumeriaStatics(source: string, externalValues?: Record<string, unknown>): StaticExtraction;
60
76
  export declare function extractPlumeriaAnimations(source: string, externalValues?: Record<string, unknown>): AnimationExtraction;
61
- export declare function convertPlumeriaModule(source: string, externalValues?: Record<string, unknown>, compositions?: PlumeriaComposition[]): PlumeriaModule | null;
77
+ export declare function convertPlumeriaModule(source: string, externalValues?: Record<string, unknown>, compositions?: PlumeriaComposition[], options?: {
78
+ target?: string;
79
+ foldings?: PlumeriaFolding[];
80
+ foreign?: Record<string, PlumeriaForeign>;
81
+ }): PlumeriaModule | null;
@@ -7,8 +7,87 @@ exports.extractPlumeriaAnimations = extractPlumeriaAnimations;
7
7
  exports.convertPlumeriaModule = convertPlumeriaModule;
8
8
  const parser_1 = require("@typescript-eslint/parser");
9
9
  const zss_engine_1 = require("zss-engine");
10
- const compositionSignature = (classNames) => classNames.join('|');
10
+ const UNITLESS = new Set(zss_engine_1.exceptionCamelCase.map((property) => (0, zss_engine_1.camelToKebabCase)(property)));
11
+ const compositionSignature = (parts) => parts.map((part) => `${part.target}#${part.className}`).join('|');
11
12
  exports.compositionSignature = compositionSignature;
13
+ const own = (target, classNames) => (0, exports.compositionSignature)(classNames.map((className) => ({ target, className })));
14
+ const DIRECT_SHORTHANDS = {};
15
+ for (const [shorthand, longhands] of Object.entries(zss_engine_1.DIRECT_LONGHANDS)) {
16
+ for (const longhand of longhands) {
17
+ if (!DIRECT_SHORTHANDS[longhand])
18
+ DIRECT_SHORTHANDS[longhand] = [];
19
+ DIRECT_SHORTHANDS[longhand].push(shorthand);
20
+ }
21
+ }
22
+ const depths = new Map();
23
+ const depthOf = (property) => {
24
+ const cached = depths.get(property);
25
+ if (cached !== undefined)
26
+ return cached;
27
+ depths.set(property, 0);
28
+ let depth = 0;
29
+ for (const shorthand of DIRECT_SHORTHANDS[property] || [])
30
+ depth = Math.max(depth, depthOf(shorthand) + 1);
31
+ depths.set(property, depth);
32
+ return depth;
33
+ };
34
+ const coverages = new Map();
35
+ const coverageOf = (property) => {
36
+ const cached = coverages.get(property);
37
+ if (cached)
38
+ return cached;
39
+ const longhands = zss_engine_1.DIRECT_LONGHANDS[property];
40
+ const coverage = longhands
41
+ ? new Set(longhands.flatMap((longhand) => [...coverageOf(longhand)]))
42
+ : new Set([property]);
43
+ coverages.set(property, coverage);
44
+ return coverage;
45
+ };
46
+ const covers = (outer, inner) => {
47
+ if (outer === inner)
48
+ return false;
49
+ const wide = coverageOf(outer);
50
+ const narrow = coverageOf(inner);
51
+ return wide.size > narrow.size && [...narrow].every((leaf) => wide.has(leaf));
52
+ };
53
+ const overlaps = (first, second) => {
54
+ const left = coverageOf(first);
55
+ return [...coverageOf(second)].some((leaf) => left.has(leaf));
56
+ };
57
+ const byRank = (written) => {
58
+ const outranked = (later, earlier) => (later.held ?? []).some((mine) => (earlier.held ?? []).some((theirs) => later.suffix === earlier.suffix &&
59
+ overlaps(mine.property, theirs.property) &&
60
+ mine.rank > theirs.rank));
61
+ const ordered = [];
62
+ const pending = [...written];
63
+ while (pending.length > 0) {
64
+ const index = pending.findIndex((candidate, position) => !pending.some((other, held) => held !== position && outranked(candidate, other)));
65
+ ordered.push(...pending.splice(index === -1 ? 0 : index, 1));
66
+ }
67
+ return ordered;
68
+ };
69
+ const byImplication = (written) => {
70
+ const ordered = [];
71
+ const pending = [...written];
72
+ while (pending.length > 0) {
73
+ const index = pending.findIndex((candidate, position) => !pending.some((other, held) => held !== position &&
74
+ candidate.condition !== undefined &&
75
+ other.condition !== undefined &&
76
+ candidate.condition !== other.condition &&
77
+ (0, zss_engine_1.impliesCondition)(candidate.condition, other.condition)));
78
+ ordered.push(...pending.splice(index === -1 ? 0 : index, 1));
79
+ }
80
+ return ordered.map((entry) => entry.text);
81
+ };
82
+ const byContainment = (declared) => {
83
+ const ordered = [];
84
+ const pending = [...declared];
85
+ while (pending.length > 0) {
86
+ const index = pending.findIndex((candidate, position) => !pending.some((other, held) => held !== position && covers(other.property, candidate.property)));
87
+ ordered.push(...pending.splice(index === -1 ? 0 : index, 1));
88
+ }
89
+ return ordered.map((entry) => entry.text);
90
+ };
12
91
  const toIdent = (value) => value.replace(/[^A-Za-z0-9-]/g, '');
13
92
  const capitalize = (value) => `${value.charAt(0).toUpperCase()}${value.slice(1)}`;
14
93
  const camelJoin = (names) => names
@@ -366,7 +445,8 @@ function extractPlumeriaAnimations(source, externalValues = {}) {
366
445
  reports,
367
446
  };
368
447
  }
369
- function convertPlumeriaModule(source, externalValues = {}, compositions = []) {
448
+ function convertPlumeriaModule(source, externalValues = {}, compositions = [], options = {}) {
449
+ const target = options.target ?? '';
370
450
  const ast = (0, parser_1.parse)(source, {
371
451
  sourceType: 'module',
372
452
  ecmaFeatures: { jsx: true },
@@ -435,27 +515,37 @@ function convertPlumeriaModule(source, externalValues = {}, compositions = []) {
435
515
  const members = new Map();
436
516
  const footprints = new Map();
437
517
  const track = (className) => {
438
- const map = new Map();
439
- footprints.set(className, map);
440
- return { root: `.${className}`, map };
518
+ const held = [];
519
+ footprints.set(className, held);
520
+ return { root: `.${className}`, held };
441
521
  };
442
- const conflicts = (first, second) => {
522
+ const relate = (first, second) => {
443
523
  const left = footprints.get(first);
444
524
  const right = footprints.get(second);
445
525
  if (!left || !right)
446
- return true;
447
- for (const [bucket, properties] of left) {
448
- const other = right.get(bucket);
449
- if (!other)
450
- continue;
451
- for (const property of properties)
452
- if (other.has(property))
453
- return true;
526
+ return { crossing: true };
527
+ let ranked;
528
+ let crossing = false;
529
+ for (const mine of left) {
530
+ for (const theirs of right) {
531
+ if (mine.suffix !== theirs.suffix)
532
+ continue;
533
+ if (!overlaps(mine.property, theirs.property))
534
+ continue;
535
+ if (mine.rank > theirs.rank)
536
+ ranked ??= [second, first];
537
+ else if (theirs.rank > mine.rank)
538
+ ranked ??= [first, second];
539
+ else
540
+ crossing = true;
541
+ }
454
542
  }
455
- return false;
543
+ return { ranked, crossing };
456
544
  };
545
+ const crosses = (first, second) => relate(first, second).crossing;
457
546
  const taken = new Set();
458
547
  const functions = {};
548
+ const variableUses = new Map();
459
549
  const classNameFor = (owner, key) => {
460
550
  if (!taken.has(key))
461
551
  return key;
@@ -477,7 +567,7 @@ function convertPlumeriaModule(source, externalValues = {}, compositions = []) {
477
567
  return;
478
568
  }
479
569
  const restrictRoot = restrict ? selectorRoot(selector) : 0;
480
- const declarations = [];
570
+ const declared = [];
481
571
  const nested = [];
482
572
  const markers = [];
483
573
  for (const property of style.properties) {
@@ -519,15 +609,24 @@ function convertPlumeriaModule(source, externalValues = {}, compositions = []) {
519
609
  .get(bucketOf(wrappers, selector.slice(restrictRoot)))
520
610
  ?.has(cssProperty))
521
611
  continue;
612
+ if (typeof value === 'string')
613
+ for (const [, variable] of value.matchAll(/var\((--[\w-]+)\)/g))
614
+ variableUses.set(variable, (variableUses.get(variable) ?? new Set()).add(cssProperty));
522
615
  if (footprint) {
523
- const bucket = bucketOf(wrappers, selector.slice(footprint.root.length));
524
- const held = footprint.map.get(bucket) ?? new Set();
525
- held.add(cssProperty);
526
- footprint.map.set(bucket, held);
616
+ const suffix = selector.slice(footprint.root.length);
617
+ footprint.held.push({
618
+ bucket: bucketOf(wrappers, suffix),
619
+ suffix,
620
+ property: cssProperty,
621
+ rank: depthOf(cssProperty) + (wrappers.length > 0 ? 1 : 0),
622
+ });
527
623
  }
528
- declarations.push(` ${cssProperty}: ${(0, zss_engine_1.applyCssValue)(value, cssProperty)};`);
624
+ declared.push({
625
+ property: cssProperty,
626
+ text: ` ${cssProperty}: ${(0, zss_engine_1.applyCssValue)(value, cssProperty)};`,
627
+ });
529
628
  }
530
- const pushRule = (target, lines) => {
629
+ const pushRule = (target, lines, held = []) => {
531
630
  if (lines.length === 0)
532
631
  return;
533
632
  let rule = `${target} {\n${lines.join('\n')}\n}`;
@@ -537,10 +636,18 @@ function convertPlumeriaModule(source, externalValues = {}, compositions = []) {
537
636
  .map((line) => ` ${line}`)
538
637
  .join('\n')}\n}`;
539
638
  }
540
- rules.push(rule);
639
+ rules.push({
640
+ text: rule,
641
+ condition: wrappers.length === 1 ? wrappers[0] : undefined,
642
+ suffix: target.slice(target.indexOf('.') + 1).replace(/^[\w-]+/, ''),
643
+ held,
644
+ });
541
645
  };
542
646
  if (phase !== 'nested')
543
- pushRule(selector, declarations);
647
+ pushRule(selector, byContainment(declared), declared.map((entry) => ({
648
+ property: entry.property,
649
+ rank: depthOf(entry.property) + (wrappers.length > 0 ? 1 : 0),
650
+ })));
544
651
  if (phase === 'declarations')
545
652
  return;
546
653
  for (const marker of markers) {
@@ -600,10 +707,19 @@ function convertPlumeriaModule(source, externalValues = {}, compositions = []) {
600
707
  params.forEach((parameter, index) => {
601
708
  scope.set(parameter, `var(${variables[index]})`);
602
709
  });
603
- functions[className] = { params, variables };
604
710
  keys.push(className);
605
711
  openGroup(className);
606
712
  emit(body, `.${className}`, [], scope, 'both', track(className));
713
+ functions[className] = {
714
+ params,
715
+ variables,
716
+ lengths: variables.map((variable) => {
717
+ const used = variableUses.get(variable);
718
+ return (used !== undefined &&
719
+ used.size > 0 &&
720
+ [...used].every((property) => !UNITLESS.has(property) && !property.startsWith('--')));
721
+ }),
722
+ };
607
723
  continue;
608
724
  }
609
725
  keys.push(className);
@@ -631,6 +747,7 @@ function convertPlumeriaModule(source, externalValues = {}, compositions = []) {
631
747
  const sort = () => {
632
748
  const edges = new Map();
633
749
  const blame = new Map();
750
+ const link = (before, after) => edges.set(before, (edges.get(before) ?? new Set()).add(after));
634
751
  for (const entry of resolved) {
635
752
  if (inlined.has(entry))
636
753
  continue;
@@ -638,9 +755,14 @@ function convertPlumeriaModule(source, externalValues = {}, compositions = []) {
638
755
  for (let second = first + 1; second < entry.slots.length; second++) {
639
756
  for (const before of entry.slots[first]) {
640
757
  for (const after of entry.slots[second]) {
641
- if (before === after || !conflicts(before, after))
758
+ if (before === after)
759
+ continue;
760
+ const { ranked, crossing } = relate(before, after);
761
+ if (ranked)
762
+ link(ranked[0], ranked[1]);
763
+ if (!crossing)
642
764
  continue;
643
- edges.set(before, (edges.get(before) ?? new Set()).add(after));
765
+ link(before, after);
644
766
  blame.set(`${before}|${after}`, entry.composition);
645
767
  }
646
768
  }
@@ -693,7 +815,7 @@ function convertPlumeriaModule(source, externalValues = {}, compositions = []) {
693
815
  if (entry.slots.some((slot) => slot.length !== 1))
694
816
  continue;
695
817
  const names = entry.slots.map((slot) => slot[0]);
696
- const signature = (0, exports.compositionSignature)(names);
818
+ const signature = own(target, names);
697
819
  if (overrides[signature])
698
820
  continue;
699
821
  const perSlot = {};
@@ -701,7 +823,7 @@ function convertPlumeriaModule(source, externalValues = {}, compositions = []) {
701
823
  const losers = names
702
824
  .slice(0, slot)
703
825
  .filter((earlier) => earlier !== names[slot] &&
704
- conflicts(earlier, names[slot]) &&
826
+ crosses(earlier, names[slot]) &&
705
827
  order.get(earlier) > order.get(names[slot]));
706
828
  if (losers.length === 0)
707
829
  continue;
@@ -721,17 +843,15 @@ function convertPlumeriaModule(source, externalValues = {}, compositions = []) {
721
843
  const theirs = footprints.get(loser);
722
844
  if (!mine || !theirs)
723
845
  continue;
724
- for (const [bucket, properties] of mine) {
725
- const other = theirs.get(bucket);
726
- if (!other)
846
+ for (const entry of mine) {
847
+ const crossed = theirs.some((candidate) => candidate.suffix === entry.suffix &&
848
+ candidate.rank === entry.rank &&
849
+ overlaps(entry.property, candidate.property));
850
+ if (!crossed)
727
851
  continue;
728
- for (const property of properties) {
729
- if (!other.has(property))
730
- continue;
731
- const held = disputed.get(bucket) ?? new Set();
732
- held.add(property);
733
- disputed.set(bucket, held);
734
- }
852
+ const held = disputed.get(entry.bucket) ?? new Set();
853
+ held.add(entry.property);
854
+ disputed.set(entry.bucket, held);
735
855
  }
736
856
  }
737
857
  if (disputed.size === 0)
@@ -747,7 +867,7 @@ function convertPlumeriaModule(source, externalValues = {}, compositions = []) {
747
867
  if (inlined.has(entry))
748
868
  continue;
749
869
  const signature = entry.slots.every((slot) => slot.length === 1) &&
750
- (0, exports.compositionSignature)(entry.slots.map((slot) => slot[0]));
870
+ own(target, entry.slots.map((slot) => slot[0]));
751
871
  let unresolved;
752
872
  for (let first = 0; first < entry.slots.length && !unresolved; first++) {
753
873
  for (let second = first + 1; second < entry.slots.length; second++) {
@@ -755,7 +875,10 @@ function convertPlumeriaModule(source, externalValues = {}, compositions = []) {
755
875
  continue;
756
876
  for (const before of entry.slots[first]) {
757
877
  for (const after of entry.slots[second]) {
758
- if (before === after || !conflicts(before, after))
878
+ if (before === after)
879
+ continue;
880
+ const { ranked, crossing } = relate(before, after);
881
+ if (ranked || !crossing)
759
882
  continue;
760
883
  if (order.get(before) < order.get(after))
761
884
  continue;
@@ -774,7 +897,7 @@ function convertPlumeriaModule(source, externalValues = {}, compositions = []) {
774
897
  });
775
898
  }
776
899
  for (const entry of collapsible) {
777
- const signature = (0, exports.compositionSignature)(entry.parts.map((part) => part.className));
900
+ const signature = own(target, entry.parts.map((part) => part.className));
778
901
  if (merges[signature])
779
902
  continue;
780
903
  const merged = classNameFor(binding, camelJoin(entry.parts.map((part) => part.className)));
@@ -787,13 +910,80 @@ function convertPlumeriaModule(source, externalValues = {}, compositions = []) {
787
910
  emit(part.value, `.${merged}`, [], values, 'nested');
788
911
  }
789
912
  else {
790
- rules.push(`.${merged} {\n composes: ${entry.parts
791
- .map((part) => part.className)
792
- .join(' ')};\n}`);
913
+ rules.push({
914
+ text: `.${merged} {\n composes: ${entry.parts
915
+ .map((part) => part.className)
916
+ .join(' ')};\n}`,
917
+ });
793
918
  }
794
919
  keys.push(merged);
795
920
  merges[signature] = merged;
796
921
  }
922
+ const parsed = new Map();
923
+ const styleOf = (part) => {
924
+ if (!part.source) {
925
+ const held = members.get(`${part.binding}.${part.key}`);
926
+ return held ? { value: held.value, scope: values } : undefined;
927
+ }
928
+ const held = options.foreign?.[part.source];
929
+ if (!held)
930
+ return undefined;
931
+ let tree = parsed.get(part.source);
932
+ if (!tree) {
933
+ try {
934
+ tree = (0, parser_1.parse)(held.text, {
935
+ sourceType: 'module',
936
+ ecmaFeatures: { jsx: true },
937
+ loc: true,
938
+ range: true,
939
+ });
940
+ }
941
+ catch {
942
+ return undefined;
943
+ }
944
+ parsed.set(part.source, tree);
945
+ }
946
+ for (const raw of tree.body) {
947
+ const statement = declarationOf(raw);
948
+ if (statement?.type !== 'VariableDeclaration')
949
+ continue;
950
+ for (const declaration of statement.declarations) {
951
+ if (declaration.id.name !== part.binding ||
952
+ !isCall(declaration.init, 'create'))
953
+ continue;
954
+ for (const property of declaration.init.arguments[0]?.properties ??
955
+ []) {
956
+ if (property.type !== 'Property')
957
+ continue;
958
+ const name = property.computed
959
+ ? undefined
960
+ : propertyKey(property.key);
961
+ if (name === part.key)
962
+ return {
963
+ value: property.value,
964
+ scope: new Map(Object.entries(held.values)),
965
+ };
966
+ }
967
+ }
968
+ }
969
+ return undefined;
970
+ };
971
+ for (const folding of options.foldings ?? []) {
972
+ if (merges[folding.signature])
973
+ continue;
974
+ const parts = folding.parts.map(styleOf);
975
+ if (parts.some((part) => !part))
976
+ continue;
977
+ const merged = classNameFor(binding, camelJoin(folding.parts.map((part) => part.key)));
978
+ taken.add(merged);
979
+ openGroup(merged);
980
+ for (const part of parts)
981
+ emit(part.value, `.${merged}`, [], part.scope, 'declarations');
982
+ for (const part of parts)
983
+ emit(part.value, `.${merged}`, [], part.scope, 'nested');
984
+ keys.push(merged);
985
+ merges[folding.signature] = merged;
986
+ }
797
987
  const emitted = [
798
988
  ...sorted.ordered,
799
989
  ...[...sorted.stuck],
@@ -804,7 +994,8 @@ function convertPlumeriaModule(source, externalValues = {}, compositions = []) {
804
994
  if (seen.has(name))
805
995
  return [];
806
996
  seen.add(name);
807
- return groups.find((group) => group.name === name)?.rules ?? [];
997
+ const group = groups.find((held) => held.name === name);
998
+ return group ? byImplication(byRank(group.rules)) : [];
808
999
  });
809
1000
  return {
810
1001
  binding,
@@ -8,6 +8,7 @@ export interface ReleaseModule {
8
8
  functions?: Record<string, {
9
9
  params: string[];
10
10
  variables: string[];
11
+ lengths?: boolean[];
11
12
  }>;
12
13
  merges?: Record<string, string>;
13
14
  overrides?: Record<string, Record<number, string>>;
@@ -387,7 +387,10 @@ exports.releaseStyles = {
387
387
  className: accessOf(resolved.module, resolved.key),
388
388
  declarations: func.variables.map((variable, index) => {
389
389
  const argument = sourceCode.getText(value.arguments[index]);
390
- return `['${variable}' as string]: ${argument}`;
390
+ const written = func.lengths?.[index]
391
+ ? `typeof ${argument} === 'number' ? \`\${${argument}}px\` : ${argument}`
392
+ : argument;
393
+ return `['${variable}' as string]: ${written}`;
391
394
  }),
392
395
  };
393
396
  };
@@ -627,7 +630,8 @@ exports.releaseStyles = {
627
630
  node.id.type !== 'Identifier' ||
628
631
  node.init?.type !== 'Literal' ||
629
632
  constants[node.id.name] !== node.init.value ||
630
- node.parent.declarations.length !== 1)
633
+ node.parent.declarations.length !== 1 ||
634
+ node.parent.parent?.type === 'ExportNamedDeclaration')
631
635
  return;
632
636
  const scope = sourceCode.getScope(node);
633
637
  const variable = scope.variables.find((candidate) => candidate.name === node.id.name);
@@ -777,13 +781,14 @@ exports.releaseStyles = {
777
781
  if (elements.length < 2)
778
782
  return undefined;
779
783
  const resolved = elements.map(styleKey);
780
- if (resolved.some((part) => !part))
784
+ if (resolved.some((part) => !part?.module.target))
781
785
  return undefined;
782
- const module = resolved[0].module;
783
- if (resolved.some((part) => part.module !== module))
784
- return undefined;
785
- const name = module.merges?.[resolved.map((part) => part.key).join('|')];
786
- return name ? accessOf(module, name) : undefined;
786
+ const host = resolved[0].module;
787
+ const signature = resolved
788
+ .map((part) => `${part.module.target}#${part.key}`)
789
+ .join('|');
790
+ const name = host.merges?.[signature];
791
+ return name ? accessOf(host, name) : undefined;
787
792
  };
788
793
  const replaceWithFunctionStyle = (className, declarations) => {
789
794
  const styleAttribute = node.parent.attributes.find((attribute) => attribute.type === 'JSXAttribute' &&
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plumeria/codemod",
3
- "version": "18.2.21",
3
+ "version": "18.2.23",
4
4
  "description": "Codemods for migrating Plumeria APIs",
5
5
  "author": "Refirst 11",
6
6
  "license": "MIT",