bestax-migrate 2.9.0 → 2.10.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.
@@ -40,6 +40,38 @@ export interface Modifier {
40
40
  * `Table isResponsive`).
41
41
  */
42
42
  export type RootStatus = 'mapped' | 'todo' | 'plain' | 'fold';
43
+ /**
44
+ * An element whose only child the target renders itself (the `<select>`
45
+ * inside `.select`), so the two convert together: the target is written
46
+ * where the child was, and the element goes.
47
+ */
48
+ export interface Absorbs {
49
+ /** The child's tag. */
50
+ readonly tag: string;
51
+ /**
52
+ * Where the target puts the attributes it is given. On the child
53
+ * (`SelectBase`): the child's become the target's, and the element may
54
+ * carry none but a `key`. On the element (`Breadcrumb`): the element's
55
+ * stay, and the child may carry none.
56
+ */
57
+ readonly attributesOn: 'child' | 'element';
58
+ /** The child's classes, as the target's props; any other class refuses. */
59
+ readonly modifiers?: Readonly<Record<string, Modifier>>;
60
+ /**
61
+ * A bare child attribute that one of the element's modifiers writes as a
62
+ * prop (`multiple`, beside `is-multiple`). Each converts only beside the
63
+ * other, and the attribute goes, since the prop renders it.
64
+ */
65
+ readonly pairs?: Readonly<Record<string, string>>;
66
+ /**
67
+ * A child attribute the target reads under another name, only beside a
68
+ * paired attribute (`size` as `multipleSize`, beside `multiple`).
69
+ */
70
+ readonly renames?: Readonly<Record<string, {
71
+ readonly to: string;
72
+ readonly beside: string;
73
+ }>>;
74
+ }
43
75
  export interface RootEntry {
44
76
  readonly status: RootStatus;
45
77
  /**
@@ -97,6 +129,8 @@ export interface RootEntry {
97
129
  * modifiers add their props beside these.
98
130
  */
99
131
  readonly folds?: readonly PropWrite[];
132
+ /** The target renders the element's only child itself. */
133
+ readonly absorbs?: Absorbs;
100
134
  /** The target takes no helper props, so every helper class stays a class. */
101
135
  readonly noHelpers?: boolean;
102
136
  /**
@@ -105,8 +139,10 @@ export interface RootEntry {
105
139
  */
106
140
  readonly ownClassOnly?: boolean;
107
141
  /**
108
- * Attributes the target types as numbers. A numeric string (`value="40"`)
109
- * becomes a number, which renders the same; any other string refuses.
142
+ * Attributes the target types as numbers, among the ones it is given (the
143
+ * child's, when it `absorbs` the child and puts them there). A numeric
144
+ * string (`value="40"`) becomes a number, which renders the same; any
145
+ * other string refuses.
110
146
  */
111
147
  readonly numberAttrs?: readonly string[];
112
148
  /** Attributes the target drops: on these tags, or on all tags except these. */
@@ -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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"}
@@ -774,6 +774,55 @@ export const ROOTS = {
774
774
  ],
775
775
  passThrough: ['disabled', 'readOnly', 'rows', 'ref'],
776
776
  },
777
+ // `SelectBase` renders `.select` and the `<select>` inside it together, and
778
+ // gives the `<select>` every attribute it is given.
779
+ select: {
780
+ ...BASE,
781
+ target: 'SelectBase',
782
+ tag: 'div',
783
+ textColor: null,
784
+ bgColor: 'backgroundColor',
785
+ modifiers: {
786
+ ...tokens('is-', ['small', 'medium', 'large'], 'size'),
787
+ ...flags({
788
+ 'is-rounded': 'isRounded',
789
+ 'is-loading': 'isLoading',
790
+ 'is-active': 'isActive',
791
+ 'is-fullwidth': 'isFullwidth',
792
+ 'is-multiple': 'multiple',
793
+ }),
794
+ },
795
+ omits: Object.fromEntries(COMPONENT_COLORS.map(color => [
796
+ `is-${color}`,
797
+ '`color` renders `has-text-<color>` on the select as well',
798
+ ])),
799
+ absorbs: {
800
+ tag: 'select',
801
+ attributesOn: 'child',
802
+ modifiers: flags({
803
+ 'is-hovered': 'isHovered',
804
+ 'is-focused': 'isFocused',
805
+ }),
806
+ pairs: { multiple: 'is-multiple' },
807
+ renames: { size: { to: 'multipleSize', beside: 'multiple' } },
808
+ },
809
+ numberAttrs: ['multipleSize'],
810
+ adoptsIdFrom: ['Field'],
811
+ ownProps: [
812
+ 'color',
813
+ 'size',
814
+ 'isRounded',
815
+ 'isLoading',
816
+ 'isActive',
817
+ 'isHovered',
818
+ 'isFocused',
819
+ 'isFullwidth',
820
+ 'isFullWidth',
821
+ 'multiple',
822
+ 'multipleSize',
823
+ ],
824
+ passThrough: ['disabled', 'ref'],
825
+ },
777
826
  footer: {
778
827
  ...BASE,
779
828
  target: 'Footer',
@@ -1088,13 +1137,29 @@ export const ROOTS = {
1088
1137
  },
1089
1138
  ownProps: ['size', 'isSpaced', 'as', 'hasSkeleton', 'textColor', 'bgColor'],
1090
1139
  },
1140
+ // `Breadcrumb` renders the bare `<ul>` inside it, and its `aria-label`
1141
+ // comes before the attributes it is given, which replace it.
1142
+ breadcrumb: {
1143
+ ...BASE,
1144
+ target: 'Breadcrumb',
1145
+ tag: 'nav',
1146
+ // Breadcrumb types no color prop at all.
1147
+ textColor: null,
1148
+ bgColor: null,
1149
+ modifiers: {
1150
+ ...tokens('is-', ['centered', 'right'], 'alignment'),
1151
+ ...tokens('has-', ['arrow', 'bullet', 'dot', 'succeeds'], 'separator', '-separator'),
1152
+ ...tokens('is-', ['small', 'medium', 'large'], 'size'),
1153
+ },
1154
+ defaults: { 'aria-label': 'breadcrumbs' },
1155
+ absorbs: { tag: 'ul', attributesOn: 'element' },
1156
+ ownProps: ['alignment', 'separator', 'size'],
1157
+ },
1091
1158
  // ---- Families bestax has but that do not map element by element yet ------
1092
- breadcrumb: todo('Breadcrumb', 'bestax `Breadcrumb` renders the `<ul>` itself and adds an `aria-label`'),
1093
1159
  checkbox: todo('Checkbox', "bestax `Checkbox` renders its own styled markup, not Bulma's"),
1094
1160
  checkboxes: todo('Checkboxes', 'bestax `Checkboxes` renders its own `.field` and `.control` wrappers'),
1095
1161
  radio: todo('Radio', "bestax `Radio` renders its own styled markup, not Bulma's"),
1096
1162
  radios: todo('Radios', 'bestax `Radios` renders its own `.field` and `.control` wrappers'),
1097
- select: todo('Select', 'bestax `Select` renders the `.select` wrapper and the `<select>` together'),
1098
1163
  file: todo('File', 'bestax `File` renders the whole `.file-label` tree itself'),
1099
1164
  'file-label': part(),
1100
1165
  'file-input': part(),
@@ -1257,6 +1322,7 @@ export const FORWARDS_REF = [
1257
1322
  'Control',
1258
1323
  'InputBase',
1259
1324
  'TextAreaBase',
1325
+ 'SelectBase',
1260
1326
  ];
1261
1327
  /**
1262
1328
  * Plain tags bestax wraps, for an element with helper classes and no root
@@ -1419,9 +1485,17 @@ export const PASSTHROUGH = [
1419
1485
  why: 'a Bulma helper with no bestax prop',
1420
1486
  match: /^(?:is-display-.+|is-visibility-.+|is-overflow-.+|is-position-.+|is-float-.+|is-clear-.+|has-radius-.+|has-text-weight-extrabold|is-align-content-(?:baseline|start|end)|is-align-items-self-(?:start|end)|is-offset-0(?:-[a-z]+)?)$/,
1421
1487
  },
1488
+ {
1489
+ why: 'a `.breadcrumb` separator, which converts only on its own `.breadcrumb`',
1490
+ match: /^has-[a-z]+-separator$/,
1491
+ },
1492
+ {
1493
+ why: "`.select`'s `is-multiple`, which converts only on its own `.select`, around a `<select multiple>`",
1494
+ match: /^is-multiple$/,
1495
+ },
1422
1496
  {
1423
1497
  why: 'a modifier of markup this source leaves alone, or one bestax has no prop for',
1424
- match: /^(?:has-addons-.+|has-[a-z]+-separator|has-dropdown(?:-up)?|has-fixed-size|has-icons-(?:left|right)|has-name|has-(?:spaced-)?navbar-fixed-(?:top|bottom)(?:-desktop)?|has-shadow|is-arrowless|is-boxed|is-center|is-current|is-delete|is-disabled|is-empty|is-expanded|is-fixed-(?:top|bottom)(?:-desktop)?|is-flexible|is-grouped(?:-.+)?|is-halfheight|is-horizontal|is-left|is-(?:lower|upper)-(?:alpha|roman)|is-multiple|is-responsive|is-selected|is-tab|is-toggle(?:-rounded)?|is-transparent|is-underlined|is-up|is-wrapped)$/,
1498
+ match: /^(?:has-addons-.+|has-dropdown(?:-up)?|has-fixed-size|has-icons-(?:left|right)|has-name|has-(?:spaced-)?navbar-fixed-(?:top|bottom)(?:-desktop)?|has-shadow|is-arrowless|is-boxed|is-center|is-current|is-delete|is-disabled|is-empty|is-expanded|is-fixed-(?:top|bottom)(?:-desktop)?|is-flexible|is-grouped(?:-.+)?|is-halfheight|is-horizontal|is-left|is-(?:lower|upper)-(?:alpha|roman)|is-responsive|is-selected|is-tab|is-toggle(?:-rounded)?|is-transparent|is-underlined|is-up|is-wrapped)$/,
1425
1499
  },
1426
1500
  ];
1427
1501
  /**
@@ -33,6 +33,11 @@ export interface ElementFacts {
33
33
  * element and nothing else (not even whitespace React would render).
34
34
  */
35
35
  soleChildTarget?: string;
36
+ /**
37
+ * That only child as written, when it is a plain HTML element: what an
38
+ * entry that `absorbs` it reads.
39
+ */
40
+ soleChild?: ChildFacts;
36
41
  /** The bestax components already in the file inside this element. */
37
42
  bestaxInside?: readonly string[];
38
43
  /** The bestax components already in the file around this element. */
@@ -49,6 +54,20 @@ export interface ElementFacts {
49
54
  */
50
55
  onlyChildOf?: string;
51
56
  }
57
+ export interface ChildFacts {
58
+ tag: string;
59
+ /**
60
+ * Its static classes, or null when its `className` is computed. Left out
61
+ * when it has no `className` at all: an empty one still renders `class=""`.
62
+ */
63
+ tokens?: readonly string[] | null;
64
+ /**
65
+ * Its attributes but `className`, read as `ElementFacts.attributes` is,
66
+ * except that a number literal (`size={3}`) is that number.
67
+ */
68
+ attributes: ReadonlyMap<string, string | number | true | null>;
69
+ hasSpread: boolean;
70
+ }
52
71
  export interface Todo {
53
72
  rule: string;
54
73
  message: string;
@@ -67,6 +86,19 @@ export interface Conversion {
67
86
  * a number (`minCol={4}`), because the target types them as numbers.
68
87
  */
69
88
  numbers: string[];
89
+ /**
90
+ * The element's only child, which the target renders itself: the target is
91
+ * written in the child's place, with the element's props and attributes
92
+ * first, then the child's, and the element goes.
93
+ */
94
+ absorbs?: {
95
+ /** What the child's classes become, in place of its `className`. */
96
+ props: Array<[name: string, value: string | true]>;
97
+ /** Child attributes the target reads under another name. */
98
+ renames: Array<[from: string, to: string]>;
99
+ /** Child attributes a prop above renders instead. */
100
+ drop: string[];
101
+ };
70
102
  }
71
103
  export interface Plan {
72
104
  conversion: Conversion | null;
@@ -1 +1 @@
1
- {"version":3,"file":"plan.d.ts","sourceRoot":"","sources":["../../../src/sources/bulma-classes/plan.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAiBH,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,2DAA2D;IAC3D,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IAC1B;;;OAGG;IACH,UAAU,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAAC,CAAC;IACtD,SAAS,EAAE,OAAO,CAAC;IACnB,MAAM,EAAE,OAAO,CAAC;IAChB,4EAA4E;IAC5E,WAAW,EAAE,OAAO,CAAC;IACrB;;;;OAIG;IACH,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,qEAAqE;IACrE,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,qEAAqE;IACrE,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC;;;OAGG;IACH,gBAAgB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,IAAI;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,UAAU;IACzB,0CAA0C;IAC1C,MAAM,EAAE,MAAM,CAAC;IACf,6DAA6D;IAC7D,KAAK,EAAE,KAAK,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC;IACnD,4DAA4D;IAC5D,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,mEAAmE;IACnE,IAAI,EAAE,MAAM,EAAE,CAAC;IACf;;;OAGG;IACH,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,IAAI;IACnB,UAAU,EAAE,UAAU,GAAG,IAAI,CAAC;IAC9B;;;OAGG;IACH,IAAI,CAAC,EAAE;QACL,MAAM,EAAE,MAAM,CAAC;QACf,KAAK,EAAE,KAAK,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC;QACnD,OAAO,EAAE,MAAM,EAAE,CAAC;KACnB,CAAC;IACF,KAAK,EAAE,IAAI,EAAE,CAAC;CACf;AA+BD,wBAAgB,IAAI,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI,CAkS9C"}
1
+ {"version":3,"file":"plan.d.ts","sourceRoot":"","sources":["../../../src/sources/bulma-classes/plan.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAiBH,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,2DAA2D;IAC3D,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IAC1B;;;OAGG;IACH,UAAU,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAAC,CAAC;IACtD,SAAS,EAAE,OAAO,CAAC;IACnB,MAAM,EAAE,OAAO,CAAC;IAChB,4EAA4E;IAC5E,WAAW,EAAE,OAAO,CAAC;IACrB;;;;OAIG;IACH,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;OAGG;IACH,SAAS,CAAC,EAAE,UAAU,CAAC;IACvB,qEAAqE;IACrE,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,qEAAqE;IACrE,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC;;;OAGG;IACH,gBAAgB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ;;;OAGG;IACH,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAAC;IAClC;;;OAGG;IACH,UAAU,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,IAAI,CAAC,CAAC;IAC/D,SAAS,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,IAAI;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,UAAU;IACzB,0CAA0C;IAC1C,MAAM,EAAE,MAAM,CAAC;IACf,6DAA6D;IAC7D,KAAK,EAAE,KAAK,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC;IACnD,4DAA4D;IAC5D,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,mEAAmE;IACnE,IAAI,EAAE,MAAM,EAAE,CAAC;IACf;;;OAGG;IACH,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB;;;;OAIG;IACH,OAAO,CAAC,EAAE;QACR,oEAAoE;QACpE,KAAK,EAAE,KAAK,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC;QACnD,4DAA4D;QAC5D,OAAO,EAAE,KAAK,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,CAAC,CAAC,CAAC;QAC3C,qDAAqD;QACrD,IAAI,EAAE,MAAM,EAAE,CAAC;KAChB,CAAC;CACH;AAED,MAAM,WAAW,IAAI;IACnB,UAAU,EAAE,UAAU,GAAG,IAAI,CAAC;IAC9B;;;OAGG;IACH,IAAI,CAAC,EAAE;QACL,MAAM,EAAE,MAAM,CAAC;QACf,KAAK,EAAE,KAAK,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC;QACnD,OAAO,EAAE,MAAM,EAAE,CAAC;KACnB,CAAC;IACF,KAAK,EAAE,IAAI,EAAE,CAAC;CACf;AA+BD,wBAAgB,IAAI,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI,CA0S9C"}
@@ -96,21 +96,34 @@ export function plan(facts) {
96
96
  if (facts.onlyChildOf) {
97
97
  return refuse('only-child', target, `this element is the only child of \`<${facts.onlyChildOf}>\`, which may hand it props or a ref with \`cloneElement\` (next/link's legacy behavior, a tooltip, a Radix \`asChild\` trigger) that bestax \`${target}\` would not take the same way; convert it by hand if \`<${facts.onlyChildOf}>\` only renders its children`);
98
98
  }
99
- if (facts.attributes.has('dangerouslySetInnerHTML')) {
99
+ // The attributes the target is given: the element's, or its only child's
100
+ // when the target renders that child itself and puts them there.
101
+ let attributes = facts.attributes;
102
+ let absorbed;
103
+ if (entry.absorbs) {
104
+ const outcome = absorb(facts, root, entry, target, refuse);
105
+ if ('todos' in outcome)
106
+ return outcome;
107
+ ({ attributes, absorbed } = outcome);
108
+ }
109
+ const renamed = new Set(absorbed?.renames.map(([, to]) => to));
110
+ if (attributes.has('dangerouslySetInnerHTML')) {
100
111
  return refuse('attr', 'dangerouslySetInnerHTML', `\`dangerouslySetInnerHTML\` sets the element's content directly, and some bestax components render content of their own beside \`children\`, which React rejects; keep this element as markup`);
101
112
  }
102
- for (const name of facts.attributes.keys()) {
113
+ for (const name of attributes.keys()) {
114
+ if (renamed.has(name))
115
+ continue;
103
116
  const readAsProp = entry.ownProps?.includes(name) || HELPER_PROPS.has(name);
104
117
  if (readAsProp && !entry.passThrough?.includes(name)) {
105
118
  return refuse('attr', name, `\`${name}\` is also a bestax \`${target}\` prop, which would read it differently; rename or drop the attribute, then re-run`);
106
119
  }
107
120
  }
108
- const missing = Object.entries(entry.defaults ?? {}).filter(([name]) => !facts.attributes.has(name));
121
+ const missing = Object.entries(entry.defaults ?? {}).filter(([name]) => !attributes.has(name));
109
122
  const drop = [];
110
123
  for (const name of entry.untypedAttrs ?? []) {
111
- if (!facts.attributes.has(name))
124
+ if (!attributes.has(name))
112
125
  continue;
113
- if (entry.defaults?.[name] === facts.attributes.get(name)) {
126
+ if (entry.defaults?.[name] === attributes.get(name)) {
114
127
  drop.push(name);
115
128
  continue;
116
129
  }
@@ -118,7 +131,7 @@ export function plan(facts) {
118
131
  }
119
132
  const numbers = [];
120
133
  for (const name of entry.numberAttrs ?? []) {
121
- const value = facts.attributes.get(name);
134
+ const value = attributes.get(name);
122
135
  if (typeof value !== 'string')
123
136
  continue;
124
137
  // Only a string that is already the number's own spelling: `040`, `1.50`
@@ -134,7 +147,7 @@ export function plan(facts) {
134
147
  if (entry.providesContext && facts.bestaxInside?.length) {
135
148
  return refuse('context', target, `bestax \`${target}\` tells the bestax form controls inside it to skip wrappers of their own, and this element already holds \`${facts.bestaxInside[0]}\`, which could render differently inside it; keep this element as markup, or convert it and check that component by hand`);
136
149
  }
137
- if (entry.adoptsIdFrom && !facts.attributes.has('id')) {
150
+ if (entry.adoptsIdFrom && !attributes.has('id')) {
138
151
  // Context follows the render tree, not the file: a component of the app
139
152
  // around the element can render a labelled `Field` around it just as well.
140
153
  const around = facts.bestaxAround?.find(name => entry.adoptsIdFrom.includes(name)) ??
@@ -148,18 +161,14 @@ export function plan(facts) {
148
161
  (!wraps.when || tokens.includes(wraps.when)) &&
149
162
  (facts.hasChildren || wraps.whenEmpty) &&
150
163
  !facts.childTargets?.some(child => wraps.unless.includes(child))) {
151
- const parts = wraps.unless.map(part => `\`${part}\``);
152
- const list = parts.length > 1
153
- ? `${parts.slice(0, -1).join(', ')} or ${parts[parts.length - 1]}`
154
- : parts[0];
155
- return refuse('children', target, `bestax \`${target}\` renders its children inside a \`.${wraps.in}\` of its own unless one of them is a ${list}, so this element stays markup`);
164
+ 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`);
156
165
  }
157
166
  if (missing.length > 0) {
158
167
  const list = missing.map(([name, value]) => `\`${name}="${value}"\``);
159
168
  return refuse('defaults', target, `bestax \`${target}\` renders ${list.join(' and ')} when the element does not set ${missing.length === 1 ? 'it' : 'them'}; add ${missing.length === 1 ? 'it' : 'them'} here if that is what you want, then re-run`);
160
169
  }
161
170
  for (const [name, tags] of Object.entries(entry.dropsAttr ?? {})) {
162
- if (facts.attributes.has(name) && inTagSet(tags, tag)) {
171
+ if (attributes.has(name) && inTagSet(tags, tag)) {
163
172
  // Bulma greys out `.button[disabled]` on any tag, so there it is not
164
173
  // inert: dropping it changes how the element looks.
165
174
  const visible = name === 'disabled';
@@ -256,10 +265,112 @@ export function plan(facts) {
256
265
  className: rest.length > 0 ? rest.join(' ') : null,
257
266
  drop,
258
267
  numbers,
268
+ ...(absorbed ? { absorbs: absorbed } : {}),
259
269
  },
260
270
  todos,
261
271
  };
262
272
  }
273
+ /** `a`, `a or b`, `a, b or c`, each in backticks. */
274
+ function orList(names) {
275
+ const quoted = names.map(name => `\`${name}\``);
276
+ return quoted.length > 1
277
+ ? `${quoted.slice(0, -1).join(', ')} or ${quoted[quoted.length - 1]}`
278
+ : quoted[0];
279
+ }
280
+ /**
281
+ * The only child an entry's target renders itself (`.select`'s `<select>`):
282
+ * what its classes become, and the attributes the target is given. It
283
+ * converts with the element only when the target would render it exactly
284
+ * as written.
285
+ */
286
+ function absorb(facts, root, entry, target, refuse) {
287
+ const spec = entry.absorbs;
288
+ const child = facts.soleChild;
289
+ const itself = `bestax \`${target}\` renders the <${spec.tag}> inside \`.${root}\` itself`;
290
+ if (child?.tag !== spec.tag) {
291
+ return refuse('children', target, `${itself}, so this element converts only around a single <${spec.tag}>, with nothing else beside it`);
292
+ }
293
+ if (child.hasSpread) {
294
+ 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`);
295
+ }
296
+ if (child.tokens === null) {
297
+ 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`);
298
+ }
299
+ if (child.tokens?.length === 0) {
300
+ 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`);
301
+ }
302
+ const modifiers = spec.modifiers ?? {};
303
+ const props = [];
304
+ for (const token of child.tokens ?? []) {
305
+ const modifier = Object.hasOwn(modifiers, token)
306
+ ? modifiers[token]
307
+ : undefined;
308
+ if (!modifier) {
309
+ const allowed = Object.keys(modifiers);
310
+ return refuse('attr', 'className', `${itself}, with no class on it${allowed.length > 0 ? ` but ${orList(allowed)}` : ''}, so its \`${token}\` would be lost; keep this element as markup`);
311
+ }
312
+ for (const write of modifier.writes) {
313
+ props.push([write.prop, write.value ?? true]);
314
+ }
315
+ }
316
+ if (spec.attributesOn === 'element') {
317
+ const name = [...child.attributes.keys()][0];
318
+ if (name !== undefined) {
319
+ return refuse('attr', name, `${itself}, with no attributes, so its \`${name}\` would be lost; keep this element as markup`);
320
+ }
321
+ return {
322
+ attributes: facts.attributes,
323
+ absorbed: { props, renames: [], drop: [] },
324
+ };
325
+ }
326
+ // The target puts what it is given on the child, so the element can carry
327
+ // nothing but its `key`, which stays with the component in its place.
328
+ const own = [...facts.attributes.keys()].find(name => name !== 'key');
329
+ if (own !== undefined) {
330
+ return refuse('attr', own, `bestax \`${target}\` puts the attributes it is given on the <${spec.tag}> inside \`.${root}\`, so this element's \`${own}\` would move there; move it onto the <${spec.tag}> if that is what you want, then re-run`);
331
+ }
332
+ if (child.attributes.has('key')) {
333
+ return refuse('attr', 'key', `the <${spec.tag}> inside has a \`key\`, which would move up to bestax \`${target}\` in place of this element's; keep this element as markup`);
334
+ }
335
+ const drop = [];
336
+ for (const [name, token] of Object.entries(spec.pairs ?? {})) {
337
+ const withClass = facts.tokens.includes(token);
338
+ if (withClass && child.attributes.get(name) === true) {
339
+ drop.push(name);
340
+ }
341
+ else if (withClass || child.attributes.has(name)) {
342
+ return refuse('attr', name, `bestax \`${target}\` renders \`${name}\` on the <${spec.tag}> and \`.${token}\` on this element together, from one prop, so the two convert only as a pair, with \`${name}\` written bare; keep this element as markup`);
343
+ }
344
+ }
345
+ // A number literal is carried over as written, like any other expression.
346
+ const given = (value) => typeof value === 'number' ? null : value;
347
+ const attributes = new Map();
348
+ const renames = [];
349
+ for (const [name, value] of child.attributes) {
350
+ if (drop.includes(name))
351
+ continue;
352
+ const rename = spec.renames && Object.hasOwn(spec.renames, name)
353
+ ? spec.renames[name]
354
+ : undefined;
355
+ if (!rename) {
356
+ attributes.set(name, given(value));
357
+ continue;
358
+ }
359
+ // `SelectBase` writes `multipleSize` back as `size` only when it holds a
360
+ // number, and an expression may not: only a literal converts. A string
361
+ // one is held to a number's spelling below, with the target's numbers.
362
+ if (!drop.includes(rename.beside) ||
363
+ (typeof value !== 'number' && typeof value !== 'string')) {
364
+ return refuse('attr', name, `bestax \`${target}\` writes the <${spec.tag}>'s \`${name}\` only beside \`${rename.beside}\`, and only from a number in \`${rename.to}\`, so it converts only as a number written out (\`${name}={4}\`); keep this element as markup, or convert it by hand if \`${name}\` is always a number`);
365
+ }
366
+ if (child.attributes.has(rename.to)) {
367
+ return refuse('attr', rename.to, `\`${rename.to}\` is also a bestax \`${target}\` prop, which would read it differently; rename or drop the attribute, then re-run`);
368
+ }
369
+ renames.push([name, rename.to]);
370
+ attributes.set(rename.to, given(value));
371
+ }
372
+ return { attributes, absorbed: { props, renames, drop } };
373
+ }
263
374
  /**
264
375
  * A wrapper the component inside it renders from a prop (`.table-container`
265
376
  * from `Table isResponsive`). It folds into that component only when it is
@@ -1 +1 @@
1
- {"version":3,"file":"rules.d.ts","sourceRoot":"","sources":["../../../src/sources/bulma-classes/rules.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAUH,eAAO,MAAM,KAAK,EAAE,WAAW,CAAC,MAAM,CAapC,CAAC;AAuBH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAEnD;AAED,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAK1D"}
1
+ {"version":3,"file":"rules.d.ts","sourceRoot":"","sources":["../../../src/sources/bulma-classes/rules.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAUH,eAAO,MAAM,KAAK,EAAE,WAAW,CAAC,MAAM,CAapC,CAAC;AAyBH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAEnD;AAED,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAK1D"}
@@ -44,6 +44,8 @@ const VOCABULARY = new Set([
44
44
  'dangerouslySetInnerHTML',
45
45
  // Refused where the target drops its own class for one it is given.
46
46
  'className',
47
+ // Refused on the child a target renders itself.
48
+ 'key',
47
49
  ]);
48
50
  export function inVocabulary(token) {
49
51
  return VOCABULARY.has(token);
@@ -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;AAoTvD,MAAM,CAAC,OAAO,UAAU,SAAS,CAC/B,QAAQ,EAAE,QAAQ,EAClB,GAAG,EAAE,GAAG,EACR,OAAO,GAAE,gBAAqB,GAC7B,MAAM,GAAG,SAAS,CAqYpB"}
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;AA8YvD,MAAM,CAAC,OAAO,UAAU,SAAS,CAC/B,QAAQ,EAAE,QAAQ,EAClB,GAAG,EAAE,GAAG,EACR,OAAO,GAAE,gBAAqB,GAC7B,MAAM,GAAG,SAAS,CAwZpB"}
@@ -17,7 +17,7 @@
17
17
  * 5. write the bestax import, after the file's last import
18
18
  */
19
19
  import path from 'node:path';
20
- import { addTodo, findAttr, forgetJsxParens, jsxNameParts, literalValueOf, makeAttr, renameElement, reprintDirectives, } from '../_shared/jsx-utils.js';
20
+ import { addTodo, findAttr, forgetJsxParens, jsxNameParts, literalValueOf, makeAttr, removeAttr, renameElement, reprintDirectives, } from '../_shared/jsx-utils.js';
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';
@@ -63,6 +63,27 @@ function afterTagName(comment) {
63
63
  trailing: true,
64
64
  };
65
65
  }
66
+ /**
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.
70
+ */
71
+ function writeClassName(j, element, props, className) {
72
+ const attrs = element.openingElement.attributes;
73
+ 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) {
78
+ const name = element.openingElement.name;
79
+ const carrier = replacement[0] ?? attrs.find((attr) => attr !== classAttr) ?? name;
80
+ carrier.comments = [
81
+ ...(carrier.comments ?? []),
82
+ ...classAttr.comments.map((comment) => carrier === name ? afterTagName(comment) : comment),
83
+ ];
84
+ }
85
+ attrs.splice(attrs.indexOf(classAttr), 1, ...replacement);
86
+ }
66
87
  /** A lowercase intrinsic tag: not a component, not a custom element. */
67
88
  const INTRINSIC = /^[a-z][a-z0-9]*$/;
68
89
  /** Subtrees whose tags are not HTML. */
@@ -142,6 +163,34 @@ function attributeName(attr) {
142
163
  ? `${attr.name.namespace.name}:${attr.name.name.name}`
143
164
  : attr.name.name;
144
165
  }
166
+ /** An element's attributes but `className`, as the planner reads them. */
167
+ function attributesBesides(element, classAttr, read) {
168
+ const attributes = new Map();
169
+ for (const attr of element.openingElement.attributes ?? []) {
170
+ if (attr.type !== 'JSXAttribute' || attr === classAttr)
171
+ continue;
172
+ attributes.set(attributeName(attr), read(attr));
173
+ }
174
+ return attributes;
175
+ }
176
+ /** A child's attribute as `ChildFacts` reads it: a number literal is that number. */
177
+ function childAttributeValue(attr) {
178
+ const literal = literalValueOf(attr);
179
+ return literal.kind === 'number' ? literal.value : attributeValue(attr);
180
+ }
181
+ function hasSpread(element) {
182
+ return (element.openingElement.attributes ?? []).some((attr) => attr.type === 'JSXSpreadAttribute');
183
+ }
184
+ /** The comments inside an element's tags, on the tags and their names. */
185
+ function tagComments(element) {
186
+ const { openingElement, closingElement } = element;
187
+ return [
188
+ openingElement,
189
+ openingElement.name,
190
+ closingElement,
191
+ closingElement?.name,
192
+ ].flatMap((node) => node?.comments ?? []);
193
+ }
145
194
  /** Every comment in the file (recast hangs them on the nodes). */
146
195
  function commentsOf(root, j) {
147
196
  const comments = new Set();
@@ -245,6 +294,25 @@ function reachesReact(child) {
245
294
  return isContent(child);
246
295
  return /[^ \t\r\n]/.test(child.value) || !/[\r\n]/.test(child.value);
247
296
  }
297
+ /** A plain HTML child as the planner reads it; nothing for a component. */
298
+ function childFacts(child) {
299
+ const name = child.openingElement.name;
300
+ if (name.type !== 'JSXIdentifier' || !INTRINSIC.test(name.name)) {
301
+ return undefined;
302
+ }
303
+ const classAttr = findAttr(child, 'className');
304
+ const className = classAttr && staticClassName(classAttr);
305
+ return {
306
+ tag: name.name,
307
+ ...(classAttr && {
308
+ tokens: className === null
309
+ ? null
310
+ : [...new Set(className.split(/\s+/).filter(Boolean))],
311
+ }),
312
+ attributes: attributesBesides(child, classAttr, childAttributeValue),
313
+ hasSpread: hasSpread(child),
314
+ };
315
+ }
248
316
  function insideForeignContent(elementPath) {
249
317
  let current = elementPath.parent;
250
318
  while (current) {
@@ -408,25 +476,22 @@ export default function transform(fileInfo, api, options = {}) {
408
476
  const classAttr = findAttr(element, 'className');
409
477
  if (!classAttr)
410
478
  continue;
411
- const attributes = new Map();
412
- for (const attr of element.openingElement.attributes ?? []) {
413
- if (attr.type !== 'JSXAttribute' || attr === classAttr)
414
- continue;
415
- attributes.set(attributeName(attr), attributeValue(attr));
416
- }
479
+ const attributes = attributesBesides(element, classAttr, attributeValue);
417
480
  const className = staticClassName(classAttr);
418
481
  const surrounding = around(elementPath);
482
+ const sole = soleChild(element);
419
483
  const facts = {
420
484
  tag: name.name,
421
485
  tokens: className === null
422
486
  ? classTokensOf(classAttr.value)
423
487
  : [...new Set(className.split(/\s+/).filter(Boolean))],
424
488
  attributes,
425
- hasSpread: (element.openingElement.attributes ?? []).some((attr) => attr.type === 'JSXSpreadAttribute'),
489
+ hasSpread: hasSpread(element),
426
490
  hasRef: attributes.has('ref'),
427
491
  hasChildren: (element.children ?? []).some(reachesReact),
428
492
  childTargets: childTargets(elementPath),
429
- soleChildTarget: planned.get(soleChild(element))?.conversion?.target,
493
+ soleChildTarget: planned.get(sole)?.conversion?.target,
494
+ soleChild: sole && childFacts(sole),
430
495
  bestaxInside: bestaxInside.get(element) ?? [],
431
496
  bestaxAround: surrounding.bestax,
432
497
  componentsAround: surrounding.other,
@@ -525,14 +590,10 @@ export default function transform(fileInfo, api, options = {}) {
525
590
  // wrapper's tags moves inside the child's opening tag.
526
591
  const wrapper = elementPath.node;
527
592
  const inner = soleChild(wrapper);
528
- const { openingElement, closingElement } = wrapper;
529
593
  const inTags = [
530
- openingElement,
531
- openingElement.name,
532
- ...(openingElement.attributes ?? []),
533
- closingElement,
534
- closingElement?.name,
535
- ].flatMap((node) => node?.comments ?? []);
594
+ ...tagComments(wrapper),
595
+ ...(wrapper.openingElement.attributes ?? []).flatMap((attr) => attr.comments ?? []),
596
+ ];
536
597
  if (inTags.length) {
537
598
  const name = inner.openingElement.name;
538
599
  name.comments = [...(name.comments ?? []), ...inTags.map(afterTagName)];
@@ -548,34 +609,63 @@ export default function transform(fileInfo, api, options = {}) {
548
609
  if (!conversion)
549
610
  continue;
550
611
  const element = elementPath.node;
612
+ // A target that renders the element's only child itself is written in
613
+ // that child's place, so the child's children stay where they are.
614
+ const child = conversion.absorbs ? soleChild(element) : undefined;
615
+ // The element's tags go, and so do the child's tag names: their comments
616
+ // move to the name that stays. The child's tags themselves stay, and
617
+ // keep their own.
618
+ const inTags = child
619
+ ? [
620
+ ...tagComments(element),
621
+ ...[child.openingElement.name, child.closingElement?.name].flatMap((node) => node?.comments ?? []),
622
+ ]
623
+ : [];
624
+ if (child) {
625
+ // Renamed before the drops below, which name each attribute as the
626
+ // component is given it.
627
+ const { renames, drop } = conversion.absorbs;
628
+ for (const name of drop)
629
+ removeAttr(child, findAttr(child, name));
630
+ for (const [from, to] of renames) {
631
+ findAttr(child, from).name = j.jsxIdentifier(to);
632
+ }
633
+ }
551
634
  const [head, ...rest] = conversion.target.split('.');
552
635
  renameElement(j, element, [ctx.reserve(head), ...rest].join('.'));
553
- const attrs = element.openingElement.attributes;
554
636
  for (const name of conversion.drop) {
555
- attrs.splice(attrs.indexOf(findAttr(element, name)), 1);
637
+ const holder = findAttr(element, name) ? element : child;
638
+ removeAttr(holder, findAttr(holder, name));
556
639
  }
557
- const classAttr = findAttr(element, 'className');
558
- const replacement = conversion.props.map(([name, value]) => makeAttr(j, name, value === true ? undefined : value));
559
- if (conversion.className) {
560
- replacement.push(makeAttr(j, 'className', conversion.className));
561
- }
562
- // A comment on the className goes with whatever takes its place: the
563
- // first new attribute, else the next one along, else the tag name. After
564
- // the name it has to be a block comment: recast prints a line comment
565
- // there as `<// …`, which TypeScript reads as a closing tag.
566
- if (classAttr.comments?.length) {
567
- const name = element.openingElement.name;
568
- const carrier = replacement[0] ?? attrs.find((attr) => attr !== classAttr) ?? name;
569
- carrier.comments = [
570
- ...(carrier.comments ?? []),
571
- ...classAttr.comments.map((comment) => carrier === name ? afterTagName(comment) : comment),
640
+ writeClassName(j, element, conversion.props, conversion.className);
641
+ let written = element;
642
+ if (child) {
643
+ child.openingElement.name = element.openingElement.name;
644
+ if (child.closingElement) {
645
+ child.closingElement.name = element.closingElement.name;
646
+ }
647
+ if (findAttr(child, 'className')) {
648
+ writeClassName(j, child, conversion.absorbs.props, null);
649
+ }
650
+ child.openingElement.attributes = [
651
+ ...(element.openingElement.attributes ?? []),
652
+ ...(child.openingElement.attributes ?? []),
572
653
  ];
654
+ // A comment on the element moves to the component in its place.
655
+ if (inTags.length) {
656
+ const name = child.openingElement.name;
657
+ name.comments = [...(name.comments ?? []), ...inTags.map(afterTagName)];
658
+ }
659
+ if (element.comments?.length) {
660
+ child.comments = [...element.comments, ...(child.comments ?? [])];
661
+ }
662
+ elementPath.replace(child);
663
+ written = child;
573
664
  }
574
- attrs.splice(attrs.indexOf(classAttr), 1, ...replacement);
575
665
  // After the splice, so a prop the conversion wrote is found as well as
576
666
  // an attribute the element already had.
577
667
  for (const name of conversion.numbers) {
578
- const attr = findAttr(element, name);
668
+ const attr = findAttr(written, name);
579
669
  attr.value = j.jsxExpressionContainer(j.numericLiteral(Number(attributeValue(attr))));
580
670
  }
581
671
  ctx.dirty = true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bestax-migrate",
3
- "version": "2.9.0",
3
+ "version": "2.10.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": {