bestax-migrate 2.11.0 → 2.13.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.
- package/dist/sources/_shared/polymorphic.d.ts.map +1 -1
- package/dist/sources/_shared/polymorphic.js +1 -0
- package/dist/sources/bloomer/mapping.d.ts +3 -3
- package/dist/sources/bloomer/mapping.d.ts.map +1 -1
- package/dist/sources/bloomer/mapping.js +4 -5
- package/dist/sources/bloomer/transform.js +5 -5
- package/dist/sources/bulma-classes/class-map.d.ts +6 -5
- package/dist/sources/bulma-classes/class-map.d.ts.map +1 -1
- package/dist/sources/bulma-classes/class-map.js +39 -16
- package/dist/sources/bulma-classes/plan.d.ts +18 -0
- package/dist/sources/bulma-classes/plan.d.ts.map +1 -1
- package/dist/sources/bulma-classes/plan.js +50 -9
- package/dist/sources/bulma-classes/rules.js +1 -1
- package/dist/sources/bulma-classes/transform.d.ts.map +1 -1
- package/dist/sources/bulma-classes/transform.js +360 -28
- package/package.json +4 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"polymorphic.d.ts","sourceRoot":"","sources":["../../../src/sources/_shared/polymorphic.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAML,KAAK,gBAAgB,EACtB,MAAM,gBAAgB,CAAC;AA0BxB,eAAO,MAAM,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"polymorphic.d.ts","sourceRoot":"","sources":["../../../src/sources/_shared/polymorphic.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAML,KAAK,gBAAgB,EACtB,MAAM,gBAAgB,CAAC;AA0BxB,eAAO,MAAM,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAkB,CAAC;AAmK3E;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,WAAW,GACtB,UAAU,MAAM,GAAG,SAAS,EAC5B,eAAa,KACZ,MAmBF,CAAC;AAMF,oDAAoD;AACpD,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAElD;AAQD,mDAAmD;AACnD,eAAO,MAAM,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAW,CAAC;AAC1D,eAAO,MAAM,cAAc,EAAE,SAAS,MAAM,EAAuB,CAAC;AAkEpE;;;;;;;GAOG;AACH,eAAO,MAAM,aAAa,EAAE,SAAS,MAAM,EAAkC,CAAC;AAE9E;;;;;;;;;;GAUG;AACH,eAAO,MAAM,eAAe,EAAE,SAAS,MAAM,EAwH5C,CAAC;AAEF;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAEnD;AASD,sFAAsF;AACtF,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAG3E;AAED,0DAA0D;AAC1D,eAAO,MAAM,UAAU,EAAE,SAAS,MAAM,EAAoC,CAAC;AAE7E,6CAA6C;AAC7C,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAC1C,CAAC;AAqLrB;;;GAGG;AACH,wBAAgB,uBAAuB,CACrC,GAAG,EAAE,gBAAgB,EACrB,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC,EAClB,OAAO,EAAE,GAAG,EACZ,MAAM,EAAE,MAAM;AACd;;;;;;;;;GASG;AACH,gBAAgB,UAAQ,GACvB,IAAI,CAsEN"}
|
|
@@ -54,9 +54,9 @@ export declare const RESPONSIVE_PROPS: Record<string, string | null>;
|
|
|
54
54
|
* emitting an excess-property type error.
|
|
55
55
|
*/
|
|
56
56
|
/**
|
|
57
|
-
* Targets whose `className` cannot be used: `
|
|
58
|
-
* `
|
|
59
|
-
*
|
|
57
|
+
* Targets whose `className` cannot be used: `Pagination.Ellipsis` spreads
|
|
58
|
+
* `{...props}` AFTER its own className (so one written here replaces the
|
|
59
|
+
* class that makes it work), and
|
|
60
60
|
* `Dropdown.Divider` is a bare `React.FC` that declares no props at all and
|
|
61
61
|
* renders its own `<hr>` regardless. A helper on one of these is named, never
|
|
62
62
|
* converted.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mapping.d.ts","sourceRoot":"","sources":["../../../src/sources/bloomer/mapping.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAkBnE;;;;GAIG;AACH,eAAO,MAAM,WAAW,EAAE,UAEzB,CAAC;AA8CF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAsBtD,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAG1D,CAAC;AAEF;;;;;GAKG;AACH;;;;;;;GAOG;AACH,eAAO,MAAM,oBAAoB,
|
|
1
|
+
{"version":3,"file":"mapping.d.ts","sourceRoot":"","sources":["../../../src/sources/bloomer/mapping.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAkBnE;;;;GAIG;AACH,eAAO,MAAM,WAAW,EAAE,UAEzB,CAAC;AA8CF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAsBtD,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAG1D,CAAC;AAEF;;;;;GAKG;AACH;;;;;;;GAOG;AACH,eAAO,MAAM,oBAAoB,aAG/B,CAAC;AAEH,eAAO,MAAM,kBAAkB,aAqB7B,CAAC;AAEH,8EAA8E;AAC9E,wBAAgB,eAAe,CAC7B,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,GAC3C,MAAM,GAAG,IAAI,CA0Bf;AAED,iFAAiF;AACjF,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAOlD,CAAC;AAEF,eAAO,MAAM,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CA0nBpD,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAiIpD,CAAC;AAEF,sDAAsD;AACtD,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,gBAAgB,GAAG,SAAS,CAM3E"}
|
|
@@ -128,15 +128,14 @@ export const RESPONSIVE_PROPS = {
|
|
|
128
128
|
* emitting an excess-property type error.
|
|
129
129
|
*/
|
|
130
130
|
/**
|
|
131
|
-
* Targets whose `className` cannot be used: `
|
|
132
|
-
* `
|
|
133
|
-
*
|
|
131
|
+
* Targets whose `className` cannot be used: `Pagination.Ellipsis` spreads
|
|
132
|
+
* `{...props}` AFTER its own className (so one written here replaces the
|
|
133
|
+
* class that makes it work), and
|
|
134
134
|
* `Dropdown.Divider` is a bare `React.FC` that declares no props at all and
|
|
135
135
|
* renders its own `<hr>` regardless. A helper on one of these is named, never
|
|
136
136
|
* converted.
|
|
137
137
|
*/
|
|
138
138
|
export const NO_CLASSNAME_TARGETS = new Set([
|
|
139
|
-
'Navbar.Divider',
|
|
140
139
|
'Pagination.Ellipsis',
|
|
141
140
|
'Dropdown.Divider',
|
|
142
141
|
]);
|
|
@@ -145,7 +144,7 @@ export const HELPERLESS_TARGETS = new Set([
|
|
|
145
144
|
'Pagination.Next',
|
|
146
145
|
'Navbar.Dropdown',
|
|
147
146
|
'Navbar.DropdownMenu',
|
|
148
|
-
'
|
|
147
|
+
'Navbar.Divider',
|
|
149
148
|
'Panel.Heading',
|
|
150
149
|
'Panel.Tabs',
|
|
151
150
|
'Panel.Block',
|
|
@@ -262,13 +262,13 @@ export default function transform(fileInfo, api, options = {}) {
|
|
|
262
262
|
// Targets that extend only React's HTML attributes take none of bloomer's
|
|
263
263
|
// universal helpers; each becomes a TODO naming the Bulma class rather
|
|
264
264
|
// than an excess-property type error the report never mentions.
|
|
265
|
-
// `
|
|
266
|
-
//
|
|
267
|
-
//
|
|
268
|
-
//
|
|
265
|
+
// `Pagination.Ellipsis` spreads its props after its own className, and
|
|
266
|
+
// `Dropdown.Divider` declares none at all, so a class cannot be written
|
|
267
|
+
// on either of them: every helper is named instead, and anything else
|
|
268
|
+
// left on the divider would not compile.
|
|
269
269
|
if (NO_CLASSNAME_TARGETS.has(target)) {
|
|
270
270
|
// A spread may carry a `className` that would replace the structural
|
|
271
|
-
// class on
|
|
271
|
+
// class on `Pagination.Ellipsis`, or props `Dropdown.Divider` silently ignores —
|
|
272
272
|
// and the codemod cannot read inside it. Keep bloomer's element.
|
|
273
273
|
const spread = (element.openingElement.attributes ?? []).some((a) => a.type === 'JSXSpreadAttribute');
|
|
274
274
|
if (spread) {
|
|
@@ -29,6 +29,12 @@ export interface Modifier {
|
|
|
29
29
|
readonly writes: readonly PropWrite[];
|
|
30
30
|
/** The raw tags the writes are exact on (Title's size picks its heading). */
|
|
31
31
|
readonly tagIn?: readonly string[];
|
|
32
|
+
/**
|
|
33
|
+
* The prop renders the class for `true` alone, not for any truthy value
|
|
34
|
+
* (`Field`'s `grouped`), so a condition a joiner adds it under can't be
|
|
35
|
+
* handed to the prop as it is.
|
|
36
|
+
*/
|
|
37
|
+
readonly onlyTrue?: boolean;
|
|
32
38
|
}
|
|
33
39
|
/**
|
|
34
40
|
* - `mapped`: converts to `target`.
|
|
@@ -144,11 +150,6 @@ export interface RootEntry {
|
|
|
144
150
|
};
|
|
145
151
|
/** The target takes no helper props, so every helper class stays a class. */
|
|
146
152
|
readonly noHelpers?: boolean;
|
|
147
|
-
/**
|
|
148
|
-
* The target drops its own class when it is given a `className`, so the
|
|
149
|
-
* element converts only when it carries no other class.
|
|
150
|
-
*/
|
|
151
|
-
readonly ownClassOnly?: boolean;
|
|
152
153
|
/**
|
|
153
154
|
* Attributes the target types as numbers, among the ones it is given (the
|
|
154
155
|
* child's, when it `absorbs` the child and puts them there). A numeric
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"class-map.d.ts","sourceRoot":"","sources":["../../../src/sources/bulma-classes/class-map.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,uCAAuC;IACvC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,qEAAqE;IACrE,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED,MAAM,MAAM,MAAM,GAChB;IAAE,QAAQ,CAAC,EAAE,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,GAAG;IAAE,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,CAAC;AAE9E,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAE1D;AAED,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,MAAM,EAAE,SAAS,SAAS,EAAE,CAAC;IACtC,6EAA6E;IAC7E,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"class-map.d.ts","sourceRoot":"","sources":["../../../src/sources/bulma-classes/class-map.ts"],"names":[],"mappings":"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"}
|
|
@@ -379,7 +379,7 @@ export const ROOTS = {
|
|
|
379
379
|
target: 'Card.Header.Title',
|
|
380
380
|
tag: 'div',
|
|
381
381
|
modifiers: flags({ 'is-centered': 'centered' }),
|
|
382
|
-
ownProps: ['color', 'bgColor', 'textColor', 'centered'],
|
|
382
|
+
ownProps: ['as', 'color', 'bgColor', 'textColor', 'centered'],
|
|
383
383
|
},
|
|
384
384
|
'card-header-icon': {
|
|
385
385
|
...BASE,
|
|
@@ -410,7 +410,33 @@ export const ROOTS = {
|
|
|
410
410
|
...BASE,
|
|
411
411
|
target: 'Card.FooterItem',
|
|
412
412
|
tag: 'span',
|
|
413
|
-
|
|
413
|
+
// The anchor attributes but `type`, and `rel`, reach only an <a>, and the
|
|
414
|
+
// button attributes only a <button> (`STRIP_FROM_NON_ANCHOR` and
|
|
415
|
+
// `STRIP_FROM_NON_BUTTON` in bulma-ui's Card.tsx).
|
|
416
|
+
dropsAttr: Object.fromEntries([
|
|
417
|
+
...[
|
|
418
|
+
'href',
|
|
419
|
+
'target',
|
|
420
|
+
'download',
|
|
421
|
+
'hrefLang',
|
|
422
|
+
'ping',
|
|
423
|
+
'referrerPolicy',
|
|
424
|
+
'media',
|
|
425
|
+
'rel',
|
|
426
|
+
].map(name => [name, { except: ['a'] }]),
|
|
427
|
+
...[
|
|
428
|
+
'disabled',
|
|
429
|
+
'form',
|
|
430
|
+
'formAction',
|
|
431
|
+
'formEncType',
|
|
432
|
+
'formMethod',
|
|
433
|
+
'formNoValidate',
|
|
434
|
+
'formTarget',
|
|
435
|
+
'name',
|
|
436
|
+
'value',
|
|
437
|
+
].map(name => [name, { except: ['button'] }]),
|
|
438
|
+
]),
|
|
439
|
+
ownProps: ['as', 'color', 'bgColor', 'textColor'],
|
|
414
440
|
},
|
|
415
441
|
columns: {
|
|
416
442
|
...BASE,
|
|
@@ -621,12 +647,16 @@ export const ROOTS = {
|
|
|
621
647
|
tag: 'div',
|
|
622
648
|
// `has-addons-centered`, `is-grouped-right` and the like stay classes:
|
|
623
649
|
// the prop values that render them render `has-addons` too.
|
|
624
|
-
modifiers:
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
650
|
+
modifiers: {
|
|
651
|
+
...flags({
|
|
652
|
+
'is-horizontal': 'horizontal',
|
|
653
|
+
'has-addons': 'hasAddons',
|
|
654
|
+
'is-narrow': 'narrow',
|
|
655
|
+
}),
|
|
656
|
+
// `grouped` renders `is-grouped` for `true` and its three layouts, and
|
|
657
|
+
// for nothing else truthy.
|
|
658
|
+
'is-grouped': { writes: [{ prop: 'grouped' }], onlyTrue: true },
|
|
659
|
+
},
|
|
630
660
|
wrapsChildren: {
|
|
631
661
|
in: 'field-body',
|
|
632
662
|
unless: ['Field.Label', 'Field.Body'],
|
|
@@ -940,9 +970,7 @@ export const ROOTS = {
|
|
|
940
970
|
defaults: { role: 'navigation', 'aria-label': 'main navigation' },
|
|
941
971
|
ownProps: ['textColor', 'color', 'bgColor', 'transparent', 'fixed'],
|
|
942
972
|
},
|
|
943
|
-
// No background prop on these parts.
|
|
944
|
-
// onto the element rather than reading it, and type `color` to six colors,
|
|
945
|
-
// so their text color stays a class.
|
|
973
|
+
// No background prop on these parts.
|
|
946
974
|
'navbar-brand': {
|
|
947
975
|
...BASE,
|
|
948
976
|
target: 'Navbar.Brand',
|
|
@@ -954,7 +982,6 @@ export const ROOTS = {
|
|
|
954
982
|
...BASE,
|
|
955
983
|
target: 'Navbar.Menu',
|
|
956
984
|
tag: 'div',
|
|
957
|
-
textColor: null,
|
|
958
985
|
bgColor: null,
|
|
959
986
|
modifiers: flags({ 'is-active': 'active' }),
|
|
960
987
|
ownProps: ['textColor', 'color', 'active'],
|
|
@@ -963,7 +990,6 @@ export const ROOTS = {
|
|
|
963
990
|
...BASE,
|
|
964
991
|
target: 'Navbar.Start',
|
|
965
992
|
tag: 'div',
|
|
966
|
-
textColor: null,
|
|
967
993
|
bgColor: null,
|
|
968
994
|
ownProps: ['textColor', 'color'],
|
|
969
995
|
},
|
|
@@ -971,7 +997,6 @@ export const ROOTS = {
|
|
|
971
997
|
...BASE,
|
|
972
998
|
target: 'Navbar.End',
|
|
973
999
|
tag: 'div',
|
|
974
|
-
textColor: null,
|
|
975
1000
|
bgColor: null,
|
|
976
1001
|
ownProps: ['textColor', 'color'],
|
|
977
1002
|
},
|
|
@@ -1002,8 +1027,6 @@ export const ROOTS = {
|
|
|
1002
1027
|
textColor: null,
|
|
1003
1028
|
bgColor: null,
|
|
1004
1029
|
noHelpers: true,
|
|
1005
|
-
// bulma-ui's Navbar.Divider spreads `className` over its own class.
|
|
1006
|
-
ownClassOnly: true,
|
|
1007
1030
|
ownProps: [],
|
|
1008
1031
|
},
|
|
1009
1032
|
notification: {
|
|
@@ -13,6 +13,19 @@ export interface ElementFacts {
|
|
|
13
13
|
tag: string;
|
|
14
14
|
/** The static className, split on whitespace, in order. */
|
|
15
15
|
tokens: readonly string[];
|
|
16
|
+
/**
|
|
17
|
+
* For a `className` a class joiner builds (`clsx('button', busy &&
|
|
18
|
+
* 'is-loading')`), the classes each condition adds, one list per condition;
|
|
19
|
+
* `tokens` then holds the ones it always adds. Set only when the codemod
|
|
20
|
+
* can read every argument.
|
|
21
|
+
*/
|
|
22
|
+
conditional?: readonly (readonly string[])[];
|
|
23
|
+
/**
|
|
24
|
+
* Indexes into `conditional` of conditions that may have side effects (a
|
|
25
|
+
* call, an assignment). A condition that becomes a prop is evaluated before
|
|
26
|
+
* the ones left in the call, so it passes them only when neither is one.
|
|
27
|
+
*/
|
|
28
|
+
impure?: readonly number[];
|
|
16
29
|
/**
|
|
17
30
|
* The element's other attributes: a string value, `true` for a bare
|
|
18
31
|
* attribute, or null for an expression.
|
|
@@ -115,6 +128,11 @@ export interface Conversion {
|
|
|
115
128
|
* they go, and the element closes itself.
|
|
116
129
|
*/
|
|
117
130
|
rendersChildren?: true;
|
|
131
|
+
/**
|
|
132
|
+
* Conditional classes (`ElementFacts.conditional`) that become a boolean
|
|
133
|
+
* prop set to their condition, each once. The rest stay in the joiner call.
|
|
134
|
+
*/
|
|
135
|
+
conditional?: Array<[prop: string, token: string]>;
|
|
118
136
|
}
|
|
119
137
|
export interface Plan {
|
|
120
138
|
conversion: Conversion | null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plan.d.ts","sourceRoot":"","sources":["../../../src/sources/bulma-classes/plan.ts"],"names":[],"mappings":"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;;;;OAIG;IACH,aAAa,CAAC,EAAE,SAAS,UAAU,EAAE,CAAC;IACtC,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;IACnB;;;OAGG;IACH,OAAO,EAAE,OAAO,CAAC;CAClB;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;IACF;;;OAGG;IACH,eAAe,CAAC,EAAE,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"plan.d.ts","sourceRoot":"","sources":["../../../src/sources/bulma-classes/plan.ts"],"names":[],"mappings":"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"}
|
|
@@ -42,7 +42,13 @@ function tagsItReaches(entry) {
|
|
|
42
42
|
export function plan(facts) {
|
|
43
43
|
const todos = [];
|
|
44
44
|
const { tag, tokens } = facts;
|
|
45
|
-
|
|
45
|
+
// Every class the element may carry: its fixed ones, and any a condition
|
|
46
|
+
// adds, which the element has to be right with as well as without.
|
|
47
|
+
const carried = [
|
|
48
|
+
...new Set([...tokens, ...(facts.conditional ?? []).flat()]),
|
|
49
|
+
];
|
|
50
|
+
const mayCarry = (token) => carried.includes(token);
|
|
51
|
+
for (const token of carried) {
|
|
46
52
|
const hint = legacyHint(token);
|
|
47
53
|
if (hint)
|
|
48
54
|
todos.push({ rule: ruleId('legacy', token), message: hint });
|
|
@@ -50,7 +56,7 @@ export function plan(facts) {
|
|
|
50
56
|
// A family this source leaves as markup keeps its element as markup, even
|
|
51
57
|
// beside a class it would convert (`navbar box`): the family's parts carry no
|
|
52
58
|
// TODO of their own, so its outermost class is the one place it is flagged.
|
|
53
|
-
const family =
|
|
59
|
+
const family = carried.find(token => {
|
|
54
60
|
const found = rootFor(token);
|
|
55
61
|
return found?.status === 'todo' && !found.part;
|
|
56
62
|
});
|
|
@@ -74,7 +80,7 @@ export function plan(facts) {
|
|
|
74
80
|
else {
|
|
75
81
|
// A root the table does not convert keeps the element as markup, so a
|
|
76
82
|
// later pass (or a person) still finds it as the class it is.
|
|
77
|
-
if (
|
|
83
|
+
if (carried.some(token => rootFor(token))) {
|
|
78
84
|
return { conversion: null, todos };
|
|
79
85
|
}
|
|
80
86
|
entry = wrapperEntry(tag);
|
|
@@ -163,7 +169,7 @@ export function plan(facts) {
|
|
|
163
169
|
}
|
|
164
170
|
const wraps = entry.wrapsChildren;
|
|
165
171
|
if (wraps &&
|
|
166
|
-
(!wraps.when ||
|
|
172
|
+
(!wraps.when || mayCarry(wraps.when)) &&
|
|
167
173
|
(facts.hasChildren || wraps.whenEmpty) &&
|
|
168
174
|
!facts.childTargets?.some(child => wraps.unless.includes(child))) {
|
|
169
175
|
return refuse('children', target, `bestax \`${target}\` renders its children inside a \`.${wraps.in}\` of its own unless one of them is a ${orList(wraps.unless)}, so this element stays markup`);
|
|
@@ -185,7 +191,7 @@ export function plan(facts) {
|
|
|
185
191
|
if (attributes.has(name) && inTagSet(tags, tag)) {
|
|
186
192
|
// Bulma greys out `.button[disabled]` on any tag, so there it is not
|
|
187
193
|
// inert: dropping it changes how the element looks.
|
|
188
|
-
const visible = name === 'disabled';
|
|
194
|
+
const visible = name === 'disabled' && target === 'Button';
|
|
189
195
|
return refuse('drops', target, visible
|
|
190
196
|
? `bestax \`${target}\` drops \`disabled\` on a <${tag}>, and Bulma styles a disabled \`.${rootLabel(facts.tokens)}\` on any tag, so converting would change how it looks; keep this element as markup`
|
|
191
197
|
: `bestax \`${target}\` drops \`${name}\` on a <${tag}>, where it does nothing anyway; remove it, then re-run`);
|
|
@@ -273,10 +279,44 @@ export function plan(facts) {
|
|
|
273
279
|
props.push([counts.prop, String(facts.childElements.length)]);
|
|
274
280
|
numbers.push(counts.prop);
|
|
275
281
|
}
|
|
282
|
+
// A class a condition adds becomes the boolean prop that renders it, set to
|
|
283
|
+
// that condition, when it is one of the root's flags on its own: the
|
|
284
|
+
// component renders it exactly when the condition is truthy, as the joiner
|
|
285
|
+
// does. Anything else a condition adds stays in the joiner call.
|
|
286
|
+
const parts = facts.conditional ?? [];
|
|
287
|
+
const conditional = [];
|
|
288
|
+
let conditionalStays = false;
|
|
289
|
+
const places = (token) => parts.filter(part => part.includes(token)).length +
|
|
290
|
+
(tokens.includes(token) ? 1 : 0);
|
|
291
|
+
const paired = new Set(Object.values(entry.absorbs?.pairs ?? {}));
|
|
292
|
+
const impure = new Set(facts.impure ?? []);
|
|
293
|
+
let impureStays = false;
|
|
294
|
+
parts.forEach((part, index) => {
|
|
295
|
+
const [token] = part;
|
|
296
|
+
const modifier = part.length === 1 && places(token) === 1 && !paired.has(token)
|
|
297
|
+
? modifierFor(entry, token)
|
|
298
|
+
: undefined;
|
|
299
|
+
const [write, ...more] = modifier?.writes ?? [];
|
|
300
|
+
const flag = write !== undefined &&
|
|
301
|
+
more.length === 0 &&
|
|
302
|
+
write.value === undefined &&
|
|
303
|
+
!modifier.onlyTrue &&
|
|
304
|
+
(!modifier.tagIn || modifier.tagIn.includes(tag)) &&
|
|
305
|
+
!writes.has(write.prop) &&
|
|
306
|
+
!conditional.some(([prop]) => prop === write.prop);
|
|
307
|
+
// Written as a prop, the condition is evaluated before any left in the
|
|
308
|
+
// call ahead of it, which is safe only when neither can have side effects.
|
|
309
|
+
const reorders = conditionalStays && (impureStays || impure.has(index));
|
|
310
|
+
if (flag && !reorders) {
|
|
311
|
+
conditional.push([write.prop, token]);
|
|
312
|
+
}
|
|
313
|
+
else {
|
|
314
|
+
conditionalStays = true;
|
|
315
|
+
if (impure.has(index))
|
|
316
|
+
impureStays = true;
|
|
317
|
+
}
|
|
318
|
+
});
|
|
276
319
|
const rest = tokens.filter(token => !converted.has(token));
|
|
277
|
-
if (entry.ownClassOnly && rest.length > 0) {
|
|
278
|
-
return refuse('attr', 'className', `bestax \`${target}\` drops its own class when it is given a \`className\`, so this element's other classes would take the place of \`.${root}\`; keep it as markup`);
|
|
279
|
-
}
|
|
280
320
|
return {
|
|
281
321
|
conversion: {
|
|
282
322
|
target,
|
|
@@ -286,6 +326,7 @@ export function plan(facts) {
|
|
|
286
326
|
numbers,
|
|
287
327
|
...(absorbed ? { absorbs: absorbed } : {}),
|
|
288
328
|
...(counts ? { rendersChildren: true } : {}),
|
|
329
|
+
...(conditional.length > 0 ? { conditional } : {}),
|
|
289
330
|
},
|
|
290
331
|
todos,
|
|
291
332
|
};
|
|
@@ -314,7 +355,7 @@ function absorb(facts, root, entry, target, refuse) {
|
|
|
314
355
|
return refuse('spread', target, `the <${spec.tag}> inside spreads props, which bestax \`${target}\` may read differently than the <${spec.tag}> did; convert the two to \`${target}\` by hand`);
|
|
315
356
|
}
|
|
316
357
|
if (child.tokens === null) {
|
|
317
|
-
return refuse('dynamic-class', target, `the <${spec.tag}> inside has a computed \`className\`,
|
|
358
|
+
return refuse('dynamic-class', target, `the <${spec.tag}> inside has a computed \`className\`, which the codemod does not read there; convert the two to bestax \`${target}\` by hand, turning each condition into its prop`);
|
|
318
359
|
}
|
|
319
360
|
if (child.tokens?.length === 0) {
|
|
320
361
|
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`);
|
|
@@ -43,7 +43,7 @@ const VOCABULARY = new Set([
|
|
|
43
43
|
// Refused on every target, so no entry lists them.
|
|
44
44
|
'dangerouslySetInnerHTML',
|
|
45
45
|
'children',
|
|
46
|
-
// Refused
|
|
46
|
+
// Refused on a wrapper a target renders and on a child it renders itself.
|
|
47
47
|
'className',
|
|
48
48
|
// Refused on the child a target renders itself.
|
|
49
49
|
'key',
|
|
@@ -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;
|
|
1
|
+
{"version":3,"file":"transform.d.ts","sourceRoot":"","sources":["../../../src/sources/bulma-classes/transform.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAW,QAAQ,EAAE,MAAM,aAAa,CAAC;AAE1D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAmtBvD,MAAM,CAAC,OAAO,UAAU,SAAS,CAC/B,QAAQ,EAAE,QAAQ,EAClB,GAAG,EAAE,GAAG,EACR,OAAO,GAAE,gBAAqB,GAC7B,MAAM,GAAG,SAAS,CAygBpB"}
|
|
@@ -21,8 +21,9 @@ import { addTodo, findAttr, forgetJsxParens, jsxNameParts, literalValueOf, makeA
|
|
|
21
21
|
import { collectBoundNames, makeReserve, prefersTabs, resolvesToBinding, } from '../_shared/imports.js';
|
|
22
22
|
import { rewriteStylesheetImports } from '../_shared/css-imports.js';
|
|
23
23
|
import { BESTAX, buildBestaxImport, placeBestaxImport, seedBestaxImport, } from '../_shared/bestax-import.js';
|
|
24
|
-
import { plan } from './plan.js';
|
|
24
|
+
import { plan, } from './plan.js';
|
|
25
25
|
import { REACT_RUNTIMES, } from './project.js';
|
|
26
|
+
import { rootFor } from './class-map.js';
|
|
26
27
|
import { ruleId } from './rules.js';
|
|
27
28
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
28
29
|
/**
|
|
@@ -48,48 +49,293 @@ const HEADER_PRAGMA = /^[\s*]*(?:@ts-(?:no)?check|@flow|@noflow|eslint-disable(?
|
|
|
48
49
|
/** Directives that act on the line after them, so must stay above it. */
|
|
49
50
|
const NEXT_LINE_DIRECTIVE = /^[\s*]*(?:eslint-disable-next-line|@ts-expect-error|@ts-ignore|prettier-ignore)\b/;
|
|
50
51
|
/**
|
|
51
|
-
* A comment moved
|
|
52
|
-
*
|
|
53
|
-
* closing tag
|
|
52
|
+
* A comment moved into a JSX tag, before or after a node there. It has to be
|
|
53
|
+
* a block comment: after a tag name recast prints a line comment as `<// …`,
|
|
54
|
+
* which TypeScript reads as a closing tag, and before an attribute it would
|
|
55
|
+
* run on into it.
|
|
54
56
|
*/
|
|
55
|
-
function
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
function asBlock(comment, trailing) {
|
|
58
|
+
const placed = { leading: !trailing, trailing };
|
|
59
|
+
if (comment.type !== 'CommentLine')
|
|
60
|
+
return { ...comment, ...placed };
|
|
59
61
|
return {
|
|
60
62
|
type: 'CommentBlock',
|
|
61
63
|
value: ` ${comment.value.trim().replace(/\*\//g, '*\\/')} `,
|
|
62
|
-
|
|
63
|
-
trailing: true,
|
|
64
|
+
...placed,
|
|
64
65
|
};
|
|
65
66
|
}
|
|
67
|
+
/** A comment moved to sit after a JSX tag name. */
|
|
68
|
+
function afterTagName(comment) {
|
|
69
|
+
return asBlock(comment, true);
|
|
70
|
+
}
|
|
66
71
|
/**
|
|
67
|
-
* Put `
|
|
68
|
-
*
|
|
69
|
-
* place: the first new attribute, else the next one along, else the tag
|
|
72
|
+
* Put `replacement` where an element's `className` was. A comment on the
|
|
73
|
+
* className, and any `carried` from inside it, goes with whatever takes its
|
|
74
|
+
* place: the first new attribute, else the next one along, else the tag
|
|
75
|
+
* name. A className kept in `replacement` keeps its own.
|
|
70
76
|
*/
|
|
71
|
-
function
|
|
77
|
+
function spliceClassName(element, replacement, carried = []) {
|
|
72
78
|
const attrs = element.openingElement.attributes;
|
|
73
79
|
const classAttr = findAttr(element, 'className');
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
if (classAttr.comments?.length) {
|
|
80
|
+
const kept = replacement.includes(classAttr);
|
|
81
|
+
const moving = [...(kept ? [] : (classAttr.comments ?? [])), ...carried];
|
|
82
|
+
if (moving.length > 0) {
|
|
78
83
|
const name = element.openingElement.name;
|
|
79
84
|
const carrier = replacement[0] ?? attrs.find((attr) => attr !== classAttr) ?? name;
|
|
80
85
|
carrier.comments = [
|
|
81
86
|
...(carrier.comments ?? []),
|
|
82
|
-
...
|
|
87
|
+
...moving.map((comment) => carrier === name ? afterTagName(comment) : comment),
|
|
83
88
|
];
|
|
84
89
|
}
|
|
85
90
|
attrs.splice(attrs.indexOf(classAttr), 1, ...replacement);
|
|
86
91
|
}
|
|
92
|
+
/** Put `props`, then what stays in `className`, where `className` was. */
|
|
93
|
+
function writeClassName(j, element, props, className) {
|
|
94
|
+
const replacement = props.map(([name, value]) => makeAttr(j, name, value === true ? undefined : value));
|
|
95
|
+
if (className)
|
|
96
|
+
replacement.push(makeAttr(j, 'className', className));
|
|
97
|
+
spliceClassName(element, replacement);
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* The same, for a `className` a joiner builds: the classes a prop now
|
|
101
|
+
* renders leave the call, each condition that became a prop moves onto it,
|
|
102
|
+
* and what is left stays in the call (a plain string when nothing in it is
|
|
103
|
+
* conditional, no `className` when nothing is left). Returns whether the
|
|
104
|
+
* call is gone.
|
|
105
|
+
*/
|
|
106
|
+
function writeJoined(j, element, conversion, joined) {
|
|
107
|
+
const { call, statics, parts } = joined;
|
|
108
|
+
const kept = new Set(conversion.className?.split(' ') ?? []);
|
|
109
|
+
const carried = [];
|
|
110
|
+
const remove = (list, node) => list.splice(list.indexOf(node), 1);
|
|
111
|
+
for (const node of statics) {
|
|
112
|
+
const classes = classesIn(staticText(node));
|
|
113
|
+
const remaining = classes.filter(token => kept.has(token));
|
|
114
|
+
if (remaining.length === classes.length)
|
|
115
|
+
continue;
|
|
116
|
+
if (remaining.length === 0) {
|
|
117
|
+
carried.push(...commentsWithin(node));
|
|
118
|
+
remove(call.arguments, node);
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
const literal = j.stringLiteral(remaining.join(' '));
|
|
122
|
+
literal.comments = node.comments;
|
|
123
|
+
call.arguments[call.arguments.indexOf(node)] = literal;
|
|
124
|
+
}
|
|
125
|
+
const conditions = (conversion.conditional ?? []).map(([prop, token]) => {
|
|
126
|
+
const part = parts.find(found => found.tokens.length === 1 && found.tokens[0] === token);
|
|
127
|
+
carried.push(...commentsWithin(part.node, part.condition));
|
|
128
|
+
if (part.object) {
|
|
129
|
+
remove(part.object.properties, part.node);
|
|
130
|
+
if (part.object.properties.length === 0) {
|
|
131
|
+
carried.push(...commentsWithin(part.object));
|
|
132
|
+
remove(call.arguments, part.object);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
remove(call.arguments, part.node);
|
|
137
|
+
}
|
|
138
|
+
const value = part.negate
|
|
139
|
+
? j.unaryExpression('!', part.condition)
|
|
140
|
+
: part.condition;
|
|
141
|
+
return j.jsxAttribute(j.jsxIdentifier(prop), j.jsxExpressionContainer(value));
|
|
142
|
+
});
|
|
143
|
+
const classAttr = findAttr(element, 'className');
|
|
144
|
+
let rest = [classAttr];
|
|
145
|
+
const gone = call.arguments.every((arg) => staticText(arg) !== null);
|
|
146
|
+
if (gone) {
|
|
147
|
+
carried.push(...commentsWithin(classAttr.value));
|
|
148
|
+
const classes = call.arguments.flatMap((arg) => classesIn(staticText(arg)));
|
|
149
|
+
rest =
|
|
150
|
+
classes.length > 0 ? [makeAttr(j, 'className', classes.join(' '))] : [];
|
|
151
|
+
}
|
|
152
|
+
spliceClassName(element, [
|
|
153
|
+
...conversion.props.map(([name, value]) => makeAttr(j, name, value === true ? undefined : value)),
|
|
154
|
+
...conditions,
|
|
155
|
+
...rest,
|
|
156
|
+
], carried.map(comment => asBlock(comment, false)));
|
|
157
|
+
return gone;
|
|
158
|
+
}
|
|
87
159
|
/** A lowercase intrinsic tag: not a component, not a custom element. */
|
|
88
160
|
const INTRINSIC = /^[a-z][a-z0-9]*$/;
|
|
89
161
|
/** Subtrees whose tags are not HTML. */
|
|
90
162
|
const FOREIGN_ROOTS = new Set(['svg', 'math']);
|
|
91
163
|
/** Parents that render their only child as it is. */
|
|
92
164
|
const PASS_THROUGH_PARENTS = new Set(['Fragment', 'React.Fragment']);
|
|
165
|
+
/**
|
|
166
|
+
* Class joiners whose arguments the codemod can read exactly, by package and
|
|
167
|
+
* export: a string adds its classes, and an object key, `a && 'x'` or
|
|
168
|
+
* `a ? 'x' : ''` adds them when the condition is truthy. Not `clsx/lite`,
|
|
169
|
+
* which ignores objects, `classnames/dedupe`, where a later key can take a
|
|
170
|
+
* class back, or `classnames/bind`, which looks names up in a CSS module.
|
|
171
|
+
*/
|
|
172
|
+
const JOINERS = {
|
|
173
|
+
clsx: ['default', 'clsx'],
|
|
174
|
+
classnames: ['default'],
|
|
175
|
+
};
|
|
176
|
+
/** A string literal's or an expression-free template's text, else null. */
|
|
177
|
+
function staticText(node) {
|
|
178
|
+
if (node?.type === 'StringLiteral')
|
|
179
|
+
return node.value;
|
|
180
|
+
if (node?.type === 'TemplateLiteral' && node.expressions.length === 0) {
|
|
181
|
+
return node.quasis[0].value.cooked ?? null;
|
|
182
|
+
}
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
function classesIn(text) {
|
|
186
|
+
return text.split(/\s+/).filter(Boolean);
|
|
187
|
+
}
|
|
188
|
+
/** A joiner argument that adds nothing: `''`, `null`, `undefined`, `false`. */
|
|
189
|
+
function addsNothing(node) {
|
|
190
|
+
if (staticText(node)?.trim() === '')
|
|
191
|
+
return true;
|
|
192
|
+
if (node.type === 'NullLiteral')
|
|
193
|
+
return true;
|
|
194
|
+
if (node.type === 'BooleanLiteral')
|
|
195
|
+
return node.value === false;
|
|
196
|
+
return node.type === 'Identifier' && node.name === 'undefined';
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Whether evaluating an expression can't change anything: names, literals,
|
|
200
|
+
* property reads, and operators over those. A call, an assignment, `new` or
|
|
201
|
+
* `delete` can. (So can a getter; a property read is taken as a read.)
|
|
202
|
+
*/
|
|
203
|
+
function sideEffectFree(node) {
|
|
204
|
+
switch (node?.type) {
|
|
205
|
+
case 'Identifier':
|
|
206
|
+
case 'StringLiteral':
|
|
207
|
+
case 'NumericLiteral':
|
|
208
|
+
case 'BooleanLiteral':
|
|
209
|
+
case 'NullLiteral':
|
|
210
|
+
case 'BigIntLiteral':
|
|
211
|
+
case 'ThisExpression':
|
|
212
|
+
return true;
|
|
213
|
+
case 'TemplateLiteral':
|
|
214
|
+
return node.expressions.every(sideEffectFree);
|
|
215
|
+
case 'MemberExpression':
|
|
216
|
+
case 'OptionalMemberExpression':
|
|
217
|
+
return (sideEffectFree(node.object) &&
|
|
218
|
+
(!node.computed || sideEffectFree(node.property)));
|
|
219
|
+
case 'UnaryExpression':
|
|
220
|
+
return node.operator !== 'delete' && sideEffectFree(node.argument);
|
|
221
|
+
case 'BinaryExpression':
|
|
222
|
+
case 'LogicalExpression':
|
|
223
|
+
return sideEffectFree(node.left) && sideEffectFree(node.right);
|
|
224
|
+
case 'ConditionalExpression':
|
|
225
|
+
return [node.test, node.consequent, node.alternate].every(sideEffectFree);
|
|
226
|
+
case 'TSAsExpression':
|
|
227
|
+
case 'TSSatisfiesExpression':
|
|
228
|
+
case 'TSNonNullExpression':
|
|
229
|
+
case 'TSTypeAssertion':
|
|
230
|
+
case 'ParenthesizedExpression':
|
|
231
|
+
return sideEffectFree(node.expression);
|
|
232
|
+
default:
|
|
233
|
+
return false;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* A `className={joiner(…)}` whose every argument the codemod can read, or
|
|
238
|
+
* undefined: an argument it can't (a variable, a call, a spread, an array)
|
|
239
|
+
* could add any class.
|
|
240
|
+
*/
|
|
241
|
+
function readJoiner(value, isJoiner) {
|
|
242
|
+
const call = value?.type === 'JSXExpressionContainer' ? value.expression : null;
|
|
243
|
+
if (call?.type !== 'CallExpression' || !isJoiner(call.callee)) {
|
|
244
|
+
return undefined;
|
|
245
|
+
}
|
|
246
|
+
const statics = [];
|
|
247
|
+
const parts = [];
|
|
248
|
+
for (const arg of call.arguments) {
|
|
249
|
+
if (staticText(arg) !== null) {
|
|
250
|
+
statics.push(arg);
|
|
251
|
+
}
|
|
252
|
+
else if (arg.type === 'ObjectExpression') {
|
|
253
|
+
for (const property of arg.properties) {
|
|
254
|
+
const plain = (property.type === 'ObjectProperty' ||
|
|
255
|
+
property.type === 'Property') &&
|
|
256
|
+
!property.computed &&
|
|
257
|
+
!property.method &&
|
|
258
|
+
(property.kind === undefined || property.kind === 'init');
|
|
259
|
+
const key = !plain
|
|
260
|
+
? undefined
|
|
261
|
+
: property.key.type === 'Identifier'
|
|
262
|
+
? property.key.name
|
|
263
|
+
: staticText(property.key);
|
|
264
|
+
if (key == null)
|
|
265
|
+
return undefined;
|
|
266
|
+
parts.push({
|
|
267
|
+
tokens: classesIn(key),
|
|
268
|
+
condition: property.value,
|
|
269
|
+
negate: false,
|
|
270
|
+
node: property,
|
|
271
|
+
object: arg,
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
else if (arg.type === 'LogicalExpression' &&
|
|
276
|
+
arg.operator === '&&' &&
|
|
277
|
+
staticText(arg.right) !== null) {
|
|
278
|
+
parts.push({
|
|
279
|
+
tokens: classesIn(staticText(arg.right)),
|
|
280
|
+
condition: arg.left,
|
|
281
|
+
negate: false,
|
|
282
|
+
node: arg,
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
else if (arg.type === 'ConditionalExpression' &&
|
|
286
|
+
staticText(arg.consequent) !== null &&
|
|
287
|
+
addsNothing(arg.alternate)) {
|
|
288
|
+
parts.push({
|
|
289
|
+
tokens: classesIn(staticText(arg.consequent)),
|
|
290
|
+
condition: arg.test,
|
|
291
|
+
negate: false,
|
|
292
|
+
node: arg,
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
else if (arg.type === 'ConditionalExpression' &&
|
|
296
|
+
staticText(arg.alternate) !== null &&
|
|
297
|
+
addsNothing(arg.consequent)) {
|
|
298
|
+
parts.push({
|
|
299
|
+
tokens: classesIn(staticText(arg.alternate)),
|
|
300
|
+
condition: arg.test,
|
|
301
|
+
negate: true,
|
|
302
|
+
node: arg,
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
else {
|
|
306
|
+
return undefined;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
const tokens = [
|
|
310
|
+
...new Set(statics.flatMap(node => classesIn(staticText(node)))),
|
|
311
|
+
];
|
|
312
|
+
return { call, tokens, statics, parts };
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Every comment on a node and inside it, except inside `keep` (a condition
|
|
316
|
+
* that moves, taking its comments with it).
|
|
317
|
+
*/
|
|
318
|
+
function commentsWithin(node, keep) {
|
|
319
|
+
const found = [];
|
|
320
|
+
const visit = (current) => {
|
|
321
|
+
if (!current || typeof current !== 'object' || current === keep)
|
|
322
|
+
return;
|
|
323
|
+
if (Array.isArray(current)) {
|
|
324
|
+
current.forEach(visit);
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
if (typeof current.type !== 'string')
|
|
328
|
+
return;
|
|
329
|
+
found.push(...(current.comments ?? []));
|
|
330
|
+
for (const [key, child] of Object.entries(current)) {
|
|
331
|
+
if (key !== 'comments' && key !== 'loc' && key !== 'original') {
|
|
332
|
+
visit(child);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
};
|
|
336
|
+
visit(node);
|
|
337
|
+
return found;
|
|
338
|
+
}
|
|
93
339
|
/**
|
|
94
340
|
* The static text of a `className`, or null when it is computed. A string
|
|
95
341
|
* literal, `{'…'}`, and a template literal with no expressions are static.
|
|
@@ -413,6 +659,33 @@ export default function transform(fileInfo, api, options = {}) {
|
|
|
413
659
|
// a child that is one of its parts.
|
|
414
660
|
const planned = new Map();
|
|
415
661
|
const programScope = root.find(j.Program).paths()[0]?.scope;
|
|
662
|
+
// The joiners the file imports, by local name, and the elements whose
|
|
663
|
+
// joiner call converts.
|
|
664
|
+
const joinerLocals = new Set();
|
|
665
|
+
root.find(j.ImportDeclaration).forEach((importPath) => {
|
|
666
|
+
const node = importPath.node;
|
|
667
|
+
const exported = Object.hasOwn(JOINERS, node.source.value)
|
|
668
|
+
? JOINERS[node.source.value]
|
|
669
|
+
: undefined;
|
|
670
|
+
if (!exported || node.importKind === 'type')
|
|
671
|
+
return;
|
|
672
|
+
for (const spec of node.specifiers ?? []) {
|
|
673
|
+
const name = spec.type === 'ImportDefaultSpecifier'
|
|
674
|
+
? 'default'
|
|
675
|
+
: spec.type === 'ImportSpecifier' && spec.importKind !== 'type'
|
|
676
|
+
? (spec.imported.name ?? spec.imported.value)
|
|
677
|
+
: undefined;
|
|
678
|
+
if (name && exported.includes(name))
|
|
679
|
+
joinerLocals.add(spec.local.name);
|
|
680
|
+
}
|
|
681
|
+
});
|
|
682
|
+
/** Whether a callee is one of those joiners where it is called. */
|
|
683
|
+
const isJoiner = (callee, scopePath) => callee?.type === 'Identifier' &&
|
|
684
|
+
joinerLocals.has(callee.name) &&
|
|
685
|
+
resolvesToBinding(scopePath, callee.name, programScope);
|
|
686
|
+
const joins = new Map();
|
|
687
|
+
/** Joiners a rewritten className no longer calls. */
|
|
688
|
+
const unjoined = new Set();
|
|
416
689
|
/**
|
|
417
690
|
* The bestax component an element already is, when its name is the bestax
|
|
418
691
|
* import and not a local that shadows it; `scopePath` is where the name is
|
|
@@ -519,21 +792,46 @@ export default function transform(fileInfo, api, options = {}) {
|
|
|
519
792
|
componentsAround: surrounding.other,
|
|
520
793
|
onlyChildOf: onlyChildOf(elementPath, bestaxLocals),
|
|
521
794
|
};
|
|
522
|
-
|
|
795
|
+
// A joiner call the codemod can read converts exactly: its fixed classes
|
|
796
|
+
// as for a static className, and each condition on a flag as its prop.
|
|
797
|
+
const joined = className === null
|
|
798
|
+
? readJoiner(classAttr.value, callee => isJoiner(callee, elementPath))
|
|
799
|
+
: undefined;
|
|
800
|
+
let exact;
|
|
801
|
+
if (joined) {
|
|
802
|
+
exact = plan({
|
|
803
|
+
...facts,
|
|
804
|
+
tokens: joined.tokens,
|
|
805
|
+
conditional: joined.parts.map(part => part.tokens),
|
|
806
|
+
impure: joined.parts.flatMap((part, index) => sideEffectFree(part.condition) ? [] : [index]),
|
|
807
|
+
});
|
|
808
|
+
if (exact.conversion)
|
|
809
|
+
joins.set(element, joined);
|
|
810
|
+
else
|
|
811
|
+
exact = undefined;
|
|
812
|
+
}
|
|
813
|
+
let result = exact ?? plan(facts);
|
|
523
814
|
const converts = result.conversion !== null || result.fold !== undefined;
|
|
524
815
|
const becomes = result.conversion?.target ?? result.fold?.target;
|
|
525
|
-
if (className === null && becomes) {
|
|
526
|
-
// The same element with a static className would convert; with
|
|
527
|
-
// computed
|
|
816
|
+
if (className === null && !exact && becomes) {
|
|
817
|
+
// The same element with a static className would convert; with one
|
|
818
|
+
// computed any other way, only a person can turn each condition into
|
|
819
|
+
// its prop.
|
|
528
820
|
const target = becomes;
|
|
821
|
+
// A call it could read says what stopped it instead.
|
|
822
|
+
const conditionalRoot = joined?.parts
|
|
823
|
+
.flatMap(part => part.tokens)
|
|
824
|
+
.find(token => rootFor(token)?.target === target);
|
|
825
|
+
const message = !joined
|
|
826
|
+
? `this \`className\` is computed, and the codemod reads only a \`clsx\` or \`classnames\` call of class strings and conditional classes; convert this element to bestax \`${target}\` by hand, turning each condition into its prop`
|
|
827
|
+
: conditionalRoot
|
|
828
|
+
? `this \`className\` adds \`.${conditionalRoot}\` only under a condition, so whether this element is a bestax \`${target}\` at all depends on it; convert it by hand if it always is, turning each condition into its prop`
|
|
829
|
+
: `this \`className\` is computed, and the codemod can't write every class it adds onto bestax \`${target}\` as it is; convert this element by hand, turning each condition into its prop`;
|
|
529
830
|
result = {
|
|
530
831
|
conversion: null,
|
|
531
832
|
todos: [
|
|
532
833
|
...result.todos,
|
|
533
|
-
{
|
|
534
|
-
rule: ruleId('dynamic-class', target),
|
|
535
|
-
message: `this \`className\` is computed, and the codemod converts static class strings only; convert this element to bestax \`${target}\` by hand, turning each condition into its prop`,
|
|
536
|
-
},
|
|
834
|
+
{ rule: ruleId('dynamic-class', target), message },
|
|
537
835
|
],
|
|
538
836
|
};
|
|
539
837
|
}
|
|
@@ -673,7 +971,13 @@ export default function transform(fileInfo, api, options = {}) {
|
|
|
673
971
|
const holder = findAttr(element, name) ? element : child;
|
|
674
972
|
removeAttr(holder, findAttr(holder, name));
|
|
675
973
|
}
|
|
676
|
-
|
|
974
|
+
const joined = joins.get(element);
|
|
975
|
+
if (!joined) {
|
|
976
|
+
writeClassName(j, element, conversion.props, conversion.className);
|
|
977
|
+
}
|
|
978
|
+
else if (writeJoined(j, element, conversion, joined)) {
|
|
979
|
+
unjoined.add(joined.call.callee.name);
|
|
980
|
+
}
|
|
677
981
|
if (conversion.rendersChildren) {
|
|
678
982
|
element.children = [];
|
|
679
983
|
element.openingElement.selfClosing = true;
|
|
@@ -715,6 +1019,34 @@ export default function transform(fileInfo, api, options = {}) {
|
|
|
715
1019
|
}
|
|
716
1020
|
ctx.dirty = true;
|
|
717
1021
|
}
|
|
1022
|
+
// A joiner import only these rewrites stopped using goes too, so the file
|
|
1023
|
+
// gains no unused import; one the file already left unused stays.
|
|
1024
|
+
for (const name of unjoined) {
|
|
1025
|
+
const referenced = root
|
|
1026
|
+
.find(j.Identifier, { name })
|
|
1027
|
+
.filter((identifierPath) => identifierPath.parent.node.type !== 'ImportSpecifier' &&
|
|
1028
|
+
identifierPath.parent.node.type !== 'ImportDefaultSpecifier');
|
|
1029
|
+
if (referenced.size() > 0)
|
|
1030
|
+
continue;
|
|
1031
|
+
root.find(j.ImportDeclaration).forEach((importPath) => {
|
|
1032
|
+
const node = importPath.node;
|
|
1033
|
+
const specifiers = node.specifiers ?? [];
|
|
1034
|
+
const spec = specifiers.find((found) => found.local?.name === name);
|
|
1035
|
+
if (!spec)
|
|
1036
|
+
return;
|
|
1037
|
+
specifiers.splice(specifiers.indexOf(spec), 1);
|
|
1038
|
+
if (specifiers.length > 0)
|
|
1039
|
+
return;
|
|
1040
|
+
// The declaration goes with its last name; its comments (a file
|
|
1041
|
+
// header, a directive) stay, on the statement beside it.
|
|
1042
|
+
const index = program.body.indexOf(node);
|
|
1043
|
+
const neighbour = program.body[index + 1] ?? program.body[index - 1];
|
|
1044
|
+
if (node.comments?.length && neighbour) {
|
|
1045
|
+
neighbour.comments = [...node.comments, ...(neighbour.comments ?? [])];
|
|
1046
|
+
}
|
|
1047
|
+
program.body.splice(index, 1);
|
|
1048
|
+
});
|
|
1049
|
+
}
|
|
718
1050
|
// ---- 5. The bestax import ---------------------------------------------------
|
|
719
1051
|
const body = program.body;
|
|
720
1052
|
placeBestaxImport(bestaxImportState, buildBestaxImport(ctx, bestaxImportState), {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bestax-migrate",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.13.0",
|
|
4
4
|
"description": "Codemods for migrating existing React apps from other Bulma libraries to @allxsmith/bestax-bulma",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"chalk": "^6.0.0",
|
|
39
39
|
"commander": "^15.0.0",
|
|
40
40
|
"jscodeshift": "^17.3.0",
|
|
41
|
-
"@allxsmith/bestax-bulma": "^5.
|
|
41
|
+
"@allxsmith/bestax-bulma": "^5.17.0"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"@jest/globals": "^30.0.0",
|
|
@@ -47,6 +47,8 @@
|
|
|
47
47
|
"@types/node": "^26.3.0",
|
|
48
48
|
"@types/react": "^19.2.18",
|
|
49
49
|
"@types/react-dom": "^19.2.5",
|
|
50
|
+
"classnames": "^2.5.1",
|
|
51
|
+
"clsx": "^2.1.1",
|
|
50
52
|
"jest": "^30.2.0",
|
|
51
53
|
"react": "^19.2.8",
|
|
52
54
|
"react-dom": "^19.2.8",
|