bestax-migrate 2.8.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.
- package/dist/sources/bulma-classes/class-map.d.ts +47 -3
- package/dist/sources/bulma-classes/class-map.d.ts.map +1 -1
- package/dist/sources/bulma-classes/class-map.js +105 -10
- package/dist/sources/bulma-classes/plan.d.ts +46 -0
- package/dist/sources/bulma-classes/plan.d.ts.map +1 -1
- package/dist/sources/bulma-classes/plan.js +181 -13
- package/dist/sources/bulma-classes/rules.d.ts.map +1 -1
- package/dist/sources/bulma-classes/rules.js +2 -0
- package/dist/sources/bulma-classes/transform.d.ts.map +1 -1
- package/dist/sources/bulma-classes/transform.js +184 -44
- package/package.json +1 -1
|
@@ -35,8 +35,43 @@ export interface Modifier {
|
|
|
35
35
|
* - `todo`: bestax has a component, but the markup does not map element by
|
|
36
36
|
* element yet; the family's outermost class gets a TODO.
|
|
37
37
|
* - `plain`: valid Bulma with nothing to convert to; left alone, no TODO.
|
|
38
|
+
* - `fold`: a wrapper the `target` renders itself from a prop, so the element
|
|
39
|
+
* folds into the one `target` inside it (`.table-container` into
|
|
40
|
+
* `Table isResponsive`).
|
|
38
41
|
*/
|
|
39
|
-
export type RootStatus = 'mapped' | 'todo' | 'plain';
|
|
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
|
+
}
|
|
40
75
|
export interface RootEntry {
|
|
41
76
|
readonly status: RootStatus;
|
|
42
77
|
/**
|
|
@@ -89,6 +124,13 @@ export interface RootEntry {
|
|
|
89
124
|
* takes a generated `id` when the element has none of its own.
|
|
90
125
|
*/
|
|
91
126
|
readonly adoptsIdFrom?: readonly string[];
|
|
127
|
+
/**
|
|
128
|
+
* For a `fold` root: the props that render it on the target inside. Its
|
|
129
|
+
* modifiers add their props beside these.
|
|
130
|
+
*/
|
|
131
|
+
readonly folds?: readonly PropWrite[];
|
|
132
|
+
/** The target renders the element's only child itself. */
|
|
133
|
+
readonly absorbs?: Absorbs;
|
|
92
134
|
/** The target takes no helper props, so every helper class stays a class. */
|
|
93
135
|
readonly noHelpers?: boolean;
|
|
94
136
|
/**
|
|
@@ -97,8 +139,10 @@ export interface RootEntry {
|
|
|
97
139
|
*/
|
|
98
140
|
readonly ownClassOnly?: boolean;
|
|
99
141
|
/**
|
|
100
|
-
* Attributes the target types as numbers
|
|
101
|
-
*
|
|
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.
|
|
102
146
|
*/
|
|
103
147
|
readonly numberAttrs?: readonly string[];
|
|
104
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":"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;CACpC;AAED
|
|
1
|
+
{"version":3,"file":"class-map.d.ts","sourceRoot":"","sources":["../../../src/sources/bulma-classes/class-map.ts"],"names":[],"mappings":"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"}
|
|
@@ -185,10 +185,13 @@ const flag = (prop) => ({ writes: [{ prop }] });
|
|
|
185
185
|
const set = (prop, value) => ({
|
|
186
186
|
writes: [{ prop, value }],
|
|
187
187
|
});
|
|
188
|
-
/**
|
|
189
|
-
|
|
188
|
+
/**
|
|
189
|
+
* `prefix + n + suffix` → `prop={n}` for `n` from 1 to `max`, for a prop
|
|
190
|
+
* typed as a number.
|
|
191
|
+
*/
|
|
192
|
+
function numbers(prefix, max, prop, suffix = '') {
|
|
190
193
|
return Object.fromEntries(Array.from({ length: max }, (_, i) => String(i + 1)).map(value => [
|
|
191
|
-
`${prefix}${value}`,
|
|
194
|
+
`${prefix}${value}${suffix}`,
|
|
192
195
|
{ writes: [{ prop, value, numeric: true }] },
|
|
193
196
|
]));
|
|
194
197
|
}
|
|
@@ -771,6 +774,55 @@ export const ROOTS = {
|
|
|
771
774
|
],
|
|
772
775
|
passThrough: ['disabled', 'readOnly', 'rows', 'ref'],
|
|
773
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
|
+
},
|
|
774
826
|
footer: {
|
|
775
827
|
...BASE,
|
|
776
828
|
target: 'Footer',
|
|
@@ -1085,14 +1137,29 @@ export const ROOTS = {
|
|
|
1085
1137
|
},
|
|
1086
1138
|
ownProps: ['size', 'isSpaced', 'as', 'hasSkeleton', 'textColor', 'bgColor'],
|
|
1087
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
|
+
},
|
|
1088
1158
|
// ---- Families bestax has but that do not map element by element yet ------
|
|
1089
|
-
breadcrumb: todo('Breadcrumb', 'bestax `Breadcrumb` renders the `<ul>` itself and adds an `aria-label`'),
|
|
1090
|
-
'fixed-grid': todo('Grid', '`Grid isFixed` renders the `.fixed-grid` wrapper itself, from `fixedCols`; replace the wrapper and its `.grid` with one `Grid`, by hand'),
|
|
1091
1159
|
checkbox: todo('Checkbox', "bestax `Checkbox` renders its own styled markup, not Bulma's"),
|
|
1092
1160
|
checkboxes: todo('Checkboxes', 'bestax `Checkboxes` renders its own `.field` and `.control` wrappers'),
|
|
1093
1161
|
radio: todo('Radio', "bestax `Radio` renders its own styled markup, not Bulma's"),
|
|
1094
1162
|
radios: todo('Radios', 'bestax `Radios` renders its own `.field` and `.control` wrappers'),
|
|
1095
|
-
select: todo('Select', 'bestax `Select` renders the `.select` wrapper and the `<select>` together'),
|
|
1096
1163
|
file: todo('File', 'bestax `File` renders the whole `.file-label` tree itself'),
|
|
1097
1164
|
'file-label': part(),
|
|
1098
1165
|
'file-input': part(),
|
|
@@ -1138,9 +1205,28 @@ export const ROOTS = {
|
|
|
1138
1205
|
'panel-icon': part(),
|
|
1139
1206
|
'panel-list': part(),
|
|
1140
1207
|
tabs: todo('Tabs', "bestax `Tabs` renders each tab's `<li>` and `<a>` together, with tab roles"),
|
|
1141
|
-
'table-container': todo('Table', 'bestax renders `.table-container` from `Table isResponsive`'),
|
|
1142
1208
|
'skeleton-block': todo('Skeleton', 'bestax `Skeleton` renders its own markup'),
|
|
1143
1209
|
'skeleton-lines': todo('Skeleton', 'bestax `Skeleton` renders its own markup'),
|
|
1210
|
+
// ---- Wrappers a component renders from a prop ---------------------------------
|
|
1211
|
+
// Each renders the wrapper with its own class and nothing else, so the
|
|
1212
|
+
// element folds only when it carries no attribute and no other class.
|
|
1213
|
+
'table-container': {
|
|
1214
|
+
status: 'fold',
|
|
1215
|
+
target: 'Table',
|
|
1216
|
+
tag: 'div',
|
|
1217
|
+
folds: [{ prop: 'isResponsive' }],
|
|
1218
|
+
},
|
|
1219
|
+
'fixed-grid': {
|
|
1220
|
+
status: 'fold',
|
|
1221
|
+
target: 'Grid',
|
|
1222
|
+
tag: 'div',
|
|
1223
|
+
folds: [{ prop: 'isFixed' }],
|
|
1224
|
+
modifiers: {
|
|
1225
|
+
...numbers('has-', 12, 'fixedCols', '-cols'),
|
|
1226
|
+
...Object.assign({}, ...SIZE_VIEWPORTS.map(viewport => numbers('has-', 12, `fixedCols${viewportSuffix(viewport)}`, `-cols-${viewport}`))),
|
|
1227
|
+
'has-auto-count': set('fixedCols', 'auto'),
|
|
1228
|
+
},
|
|
1229
|
+
},
|
|
1144
1230
|
// ---- Valid Bulma, nothing to convert to --------------------------------------
|
|
1145
1231
|
// The other sources emit these on purpose where bestax has no component.
|
|
1146
1232
|
help: plain('bestax renders `.help` only inside its form controls'),
|
|
@@ -1236,6 +1322,7 @@ export const FORWARDS_REF = [
|
|
|
1236
1322
|
'Control',
|
|
1237
1323
|
'InputBase',
|
|
1238
1324
|
'TextAreaBase',
|
|
1325
|
+
'SelectBase',
|
|
1239
1326
|
];
|
|
1240
1327
|
/**
|
|
1241
1328
|
* Plain tags bestax wraps, for an element with helper classes and no root
|
|
@@ -1387,8 +1474,8 @@ export const PASSTHROUGH = [
|
|
|
1387
1474
|
match: /^(?:is-(?:col|row)-.+|is-(?:column|row)-gap-\d+|is-gap-\d+|is-auto-fill)$/,
|
|
1388
1475
|
},
|
|
1389
1476
|
{
|
|
1390
|
-
why: 'a `.fixed-grid` column count, which
|
|
1391
|
-
match: /^has-\d+-cols(?:-.+)
|
|
1477
|
+
why: 'a `.fixed-grid` column count, which converts only on its own `.fixed-grid`',
|
|
1478
|
+
match: /^(?:has-\d+-cols(?:-.+)?|has-auto-count)$/,
|
|
1392
1479
|
},
|
|
1393
1480
|
{
|
|
1394
1481
|
why: 'an `.image` modifier; `.image` stays as markup',
|
|
@@ -1398,9 +1485,17 @@ export const PASSTHROUGH = [
|
|
|
1398
1485
|
why: 'a Bulma helper with no bestax prop',
|
|
1399
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]+)?)$/,
|
|
1400
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
|
+
},
|
|
1401
1496
|
{
|
|
1402
1497
|
why: 'a modifier of markup this source leaves alone, or one bestax has no prop for',
|
|
1403
|
-
match: /^(?:has-addons-.+|has-
|
|
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)$/,
|
|
1404
1499
|
},
|
|
1405
1500
|
];
|
|
1406
1501
|
/**
|
|
@@ -28,6 +28,16 @@ export interface ElementFacts {
|
|
|
28
28
|
* counts: one in an expression may not render.
|
|
29
29
|
*/
|
|
30
30
|
childTargets?: readonly string[];
|
|
31
|
+
/**
|
|
32
|
+
* What the element's only child converts to, when the element holds one
|
|
33
|
+
* element and nothing else (not even whitespace React would render).
|
|
34
|
+
*/
|
|
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;
|
|
31
41
|
/** The bestax components already in the file inside this element. */
|
|
32
42
|
bestaxInside?: readonly string[];
|
|
33
43
|
/** The bestax components already in the file around this element. */
|
|
@@ -44,6 +54,20 @@ export interface ElementFacts {
|
|
|
44
54
|
*/
|
|
45
55
|
onlyChildOf?: string;
|
|
46
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
|
+
}
|
|
47
71
|
export interface Todo {
|
|
48
72
|
rule: string;
|
|
49
73
|
message: string;
|
|
@@ -62,9 +86,31 @@ export interface Conversion {
|
|
|
62
86
|
* a number (`minCol={4}`), because the target types them as numbers.
|
|
63
87
|
*/
|
|
64
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
|
+
};
|
|
65
102
|
}
|
|
66
103
|
export interface Plan {
|
|
67
104
|
conversion: Conversion | null;
|
|
105
|
+
/**
|
|
106
|
+
* The element is a wrapper its only child's component renders from these
|
|
107
|
+
* props: they join that child's conversion, and the element goes.
|
|
108
|
+
*/
|
|
109
|
+
fold?: {
|
|
110
|
+
target: string;
|
|
111
|
+
props: Array<[name: string, value: string | true]>;
|
|
112
|
+
numbers: string[];
|
|
113
|
+
};
|
|
68
114
|
todos: Todo[];
|
|
69
115
|
}
|
|
70
116
|
export declare function plan(facts: ElementFacts): Plan;
|
|
@@ -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,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;
|
|
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"}
|
|
@@ -61,6 +61,9 @@ export function plan(facts) {
|
|
|
61
61
|
});
|
|
62
62
|
return { conversion: null, todos };
|
|
63
63
|
}
|
|
64
|
+
const wrapper = tokens.find(token => rootFor(token)?.status === 'fold');
|
|
65
|
+
if (wrapper)
|
|
66
|
+
return planFold(facts, wrapper, todos);
|
|
64
67
|
const root = tokens
|
|
65
68
|
.filter(token => rootFor(token)?.status === 'mapped')
|
|
66
69
|
.sort((a, b) => precedence(a) - precedence(b))[0];
|
|
@@ -93,21 +96,34 @@ export function plan(facts) {
|
|
|
93
96
|
if (facts.onlyChildOf) {
|
|
94
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`);
|
|
95
98
|
}
|
|
96
|
-
|
|
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')) {
|
|
97
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`);
|
|
98
112
|
}
|
|
99
|
-
for (const name of
|
|
113
|
+
for (const name of attributes.keys()) {
|
|
114
|
+
if (renamed.has(name))
|
|
115
|
+
continue;
|
|
100
116
|
const readAsProp = entry.ownProps?.includes(name) || HELPER_PROPS.has(name);
|
|
101
117
|
if (readAsProp && !entry.passThrough?.includes(name)) {
|
|
102
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`);
|
|
103
119
|
}
|
|
104
120
|
}
|
|
105
|
-
const missing = Object.entries(entry.defaults ?? {}).filter(([name]) => !
|
|
121
|
+
const missing = Object.entries(entry.defaults ?? {}).filter(([name]) => !attributes.has(name));
|
|
106
122
|
const drop = [];
|
|
107
123
|
for (const name of entry.untypedAttrs ?? []) {
|
|
108
|
-
if (!
|
|
124
|
+
if (!attributes.has(name))
|
|
109
125
|
continue;
|
|
110
|
-
if (entry.defaults?.[name] ===
|
|
126
|
+
if (entry.defaults?.[name] === attributes.get(name)) {
|
|
111
127
|
drop.push(name);
|
|
112
128
|
continue;
|
|
113
129
|
}
|
|
@@ -115,7 +131,7 @@ export function plan(facts) {
|
|
|
115
131
|
}
|
|
116
132
|
const numbers = [];
|
|
117
133
|
for (const name of entry.numberAttrs ?? []) {
|
|
118
|
-
const value =
|
|
134
|
+
const value = attributes.get(name);
|
|
119
135
|
if (typeof value !== 'string')
|
|
120
136
|
continue;
|
|
121
137
|
// Only a string that is already the number's own spelling: `040`, `1.50`
|
|
@@ -131,7 +147,7 @@ export function plan(facts) {
|
|
|
131
147
|
if (entry.providesContext && facts.bestaxInside?.length) {
|
|
132
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`);
|
|
133
149
|
}
|
|
134
|
-
if (entry.adoptsIdFrom && !
|
|
150
|
+
if (entry.adoptsIdFrom && !attributes.has('id')) {
|
|
135
151
|
// Context follows the render tree, not the file: a component of the app
|
|
136
152
|
// around the element can render a labelled `Field` around it just as well.
|
|
137
153
|
const around = facts.bestaxAround?.find(name => entry.adoptsIdFrom.includes(name)) ??
|
|
@@ -145,18 +161,14 @@ export function plan(facts) {
|
|
|
145
161
|
(!wraps.when || tokens.includes(wraps.when)) &&
|
|
146
162
|
(facts.hasChildren || wraps.whenEmpty) &&
|
|
147
163
|
!facts.childTargets?.some(child => wraps.unless.includes(child))) {
|
|
148
|
-
|
|
149
|
-
const list = parts.length > 1
|
|
150
|
-
? `${parts.slice(0, -1).join(', ')} or ${parts[parts.length - 1]}`
|
|
151
|
-
: parts[0];
|
|
152
|
-
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`);
|
|
153
165
|
}
|
|
154
166
|
if (missing.length > 0) {
|
|
155
167
|
const list = missing.map(([name, value]) => `\`${name}="${value}"\``);
|
|
156
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`);
|
|
157
169
|
}
|
|
158
170
|
for (const [name, tags] of Object.entries(entry.dropsAttr ?? {})) {
|
|
159
|
-
if (
|
|
171
|
+
if (attributes.has(name) && inTagSet(tags, tag)) {
|
|
160
172
|
// Bulma greys out `.button[disabled]` on any tag, so there it is not
|
|
161
173
|
// inert: dropping it changes how the element looks.
|
|
162
174
|
const visible = name === 'disabled';
|
|
@@ -253,7 +265,163 @@ export function plan(facts) {
|
|
|
253
265
|
className: rest.length > 0 ? rest.join(' ') : null,
|
|
254
266
|
drop,
|
|
255
267
|
numbers,
|
|
268
|
+
...(absorbed ? { absorbs: absorbed } : {}),
|
|
256
269
|
},
|
|
257
270
|
todos,
|
|
258
271
|
};
|
|
259
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
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* A wrapper the component inside it renders from a prop (`.table-container`
|
|
376
|
+
* from `Table isResponsive`). It folds into that component only when it is
|
|
377
|
+
* exactly what the component renders for it: its own tag and class, its own
|
|
378
|
+
* modifiers, no attribute, and one child that becomes the component.
|
|
379
|
+
*/
|
|
380
|
+
function planFold(facts, wrapper, todos) {
|
|
381
|
+
const entry = rootFor(wrapper);
|
|
382
|
+
const target = entry.target;
|
|
383
|
+
if (!target)
|
|
384
|
+
return { conversion: null, todos };
|
|
385
|
+
const folds = entry.folds ?? [];
|
|
386
|
+
const renders = `\`${target} ${folds.map(write => write.prop).join(' ')}\``;
|
|
387
|
+
const refuse = (kind, token, message) => ({
|
|
388
|
+
conversion: null,
|
|
389
|
+
todos: [...todos, { rule: ruleId(kind, token), message }],
|
|
390
|
+
});
|
|
391
|
+
if (facts.tag !== entry.tag) {
|
|
392
|
+
return refuse('tag', target, `bestax ${renders} renders \`.${wrapper}\` on a <${entry.tag}>, not a <${facts.tag}>; keep the markup, or change the tag and re-run`);
|
|
393
|
+
}
|
|
394
|
+
if (facts.hasSpread) {
|
|
395
|
+
return refuse('spread', target, `this element spreads props, and bestax ${renders} renders \`.${wrapper}\` with none of its own; keep it as markup`);
|
|
396
|
+
}
|
|
397
|
+
if (facts.onlyChildOf) {
|
|
398
|
+
return refuse('only-child', target, `this element is the only child of \`<${facts.onlyChildOf}>\`, which may hand it props or a ref with \`cloneElement\`, and folding it would move those onto the \`${target}\` inside; convert it by hand if \`<${facts.onlyChildOf}>\` only renders its children`);
|
|
399
|
+
}
|
|
400
|
+
const attribute = [...facts.attributes.keys()][0];
|
|
401
|
+
if (attribute) {
|
|
402
|
+
return refuse('attr', attribute, `bestax ${renders} renders \`.${wrapper}\` with no attributes, so this element's \`${attribute}\` would be lost; keep it as markup`);
|
|
403
|
+
}
|
|
404
|
+
const props = folds.map(write => [
|
|
405
|
+
write.prop,
|
|
406
|
+
write.value ?? true,
|
|
407
|
+
]);
|
|
408
|
+
const numbers = [];
|
|
409
|
+
for (const token of facts.tokens) {
|
|
410
|
+
if (token === wrapper)
|
|
411
|
+
continue;
|
|
412
|
+
const modifier = modifierFor(entry, token);
|
|
413
|
+
const taken = modifier?.writes.some(write => props.some(([prop]) => prop === write.prop));
|
|
414
|
+
if (!modifier || taken) {
|
|
415
|
+
return refuse('attr', 'className', `bestax ${renders} renders \`.${wrapper}\` with no class but its own and one of each of its modifiers, so \`${token}\` would be lost; keep this element as markup`);
|
|
416
|
+
}
|
|
417
|
+
for (const write of modifier.writes) {
|
|
418
|
+
props.push([write.prop, write.value ?? true]);
|
|
419
|
+
if (write.numeric)
|
|
420
|
+
numbers.push(write.prop);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
if (facts.soleChildTarget !== target) {
|
|
424
|
+
return refuse('children', target, `bestax ${renders} renders \`.${wrapper}\` itself, so this element converts only around a single element that becomes a \`${target}\`, with nothing else beside it`);
|
|
425
|
+
}
|
|
426
|
+
return { conversion: null, fold: { target, props, numbers }, todos };
|
|
427
|
+
}
|
|
@@ -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;
|
|
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;
|
|
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';
|
|
@@ -47,6 +47,43 @@ const JSX_FACTORY = /^\s*\*?\s*@jsx\s+(\S+)/m;
|
|
|
47
47
|
const HEADER_PRAGMA = /^[\s*]*(?:@ts-(?:no)?check|@flow|@noflow|eslint-disable(?!-)|@jsx|@jsxImportSource|@jsxRuntime|@license|@preserve)\b/;
|
|
48
48
|
/** Directives that act on the line after them, so must stay above it. */
|
|
49
49
|
const NEXT_LINE_DIRECTIVE = /^[\s*]*(?:eslint-disable-next-line|@ts-expect-error|@ts-ignore|prettier-ignore)\b/;
|
|
50
|
+
/**
|
|
51
|
+
* A comment moved to sit after a JSX tag name. It has to be a block comment
|
|
52
|
+
* there: recast prints a line comment as `<// …`, which TypeScript reads as a
|
|
53
|
+
* closing tag.
|
|
54
|
+
*/
|
|
55
|
+
function afterTagName(comment) {
|
|
56
|
+
if (comment.type !== 'CommentLine') {
|
|
57
|
+
return { ...comment, leading: false, trailing: true };
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
type: 'CommentBlock',
|
|
61
|
+
value: ` ${comment.value.trim().replace(/\*\//g, '*\\/')} `,
|
|
62
|
+
leading: false,
|
|
63
|
+
trailing: true,
|
|
64
|
+
};
|
|
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
|
+
}
|
|
50
87
|
/** A lowercase intrinsic tag: not a component, not a custom element. */
|
|
51
88
|
const INTRINSIC = /^[a-z][a-z0-9]*$/;
|
|
52
89
|
/** Subtrees whose tags are not HTML. */
|
|
@@ -126,6 +163,34 @@ function attributeName(attr) {
|
|
|
126
163
|
? `${attr.name.namespace.name}:${attr.name.name.name}`
|
|
127
164
|
: attr.name.name;
|
|
128
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
|
+
}
|
|
129
194
|
/** Every comment in the file (recast hangs them on the nodes). */
|
|
130
195
|
function commentsOf(root, j) {
|
|
131
196
|
const comments = new Set();
|
|
@@ -229,6 +294,25 @@ function reachesReact(child) {
|
|
|
229
294
|
return isContent(child);
|
|
230
295
|
return /[^ \t\r\n]/.test(child.value) || !/[\r\n]/.test(child.value);
|
|
231
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
|
+
}
|
|
232
316
|
function insideForeignContent(elementPath) {
|
|
233
317
|
let current = elementPath.parent;
|
|
234
318
|
while (current) {
|
|
@@ -325,10 +409,21 @@ export default function transform(fileInfo, api, options = {}) {
|
|
|
325
409
|
if (child.type !== 'JSXElement')
|
|
326
410
|
return [];
|
|
327
411
|
const target = planned.get(child)?.conversion?.target ??
|
|
412
|
+
planned.get(child)?.fold?.target ??
|
|
328
413
|
// A direct child is looked up in the element's own scope, the child's.
|
|
329
414
|
bestaxTarget(child, elementPath);
|
|
330
415
|
return target ? [target] : [];
|
|
331
416
|
});
|
|
417
|
+
/**
|
|
418
|
+
* The one element an element holds, when it holds nothing else: no text
|
|
419
|
+
* React renders, and no comment, which folding the element would drop.
|
|
420
|
+
*/
|
|
421
|
+
const soleChild = (element) => {
|
|
422
|
+
const content = (element.children ?? []).filter((child) => child.type !== 'JSXText' || reachesReact(child));
|
|
423
|
+
return content.length === 1 && content[0].type === 'JSXElement'
|
|
424
|
+
? content[0]
|
|
425
|
+
: undefined;
|
|
426
|
+
};
|
|
332
427
|
// For each element, the bestax components already in the file inside it:
|
|
333
428
|
// `Field` and `Control` change how bestax's form controls render.
|
|
334
429
|
const bestaxInside = new Map();
|
|
@@ -381,35 +476,34 @@ export default function transform(fileInfo, api, options = {}) {
|
|
|
381
476
|
const classAttr = findAttr(element, 'className');
|
|
382
477
|
if (!classAttr)
|
|
383
478
|
continue;
|
|
384
|
-
const attributes =
|
|
385
|
-
for (const attr of element.openingElement.attributes ?? []) {
|
|
386
|
-
if (attr.type !== 'JSXAttribute' || attr === classAttr)
|
|
387
|
-
continue;
|
|
388
|
-
attributes.set(attributeName(attr), attributeValue(attr));
|
|
389
|
-
}
|
|
479
|
+
const attributes = attributesBesides(element, classAttr, attributeValue);
|
|
390
480
|
const className = staticClassName(classAttr);
|
|
391
481
|
const surrounding = around(elementPath);
|
|
482
|
+
const sole = soleChild(element);
|
|
392
483
|
const facts = {
|
|
393
484
|
tag: name.name,
|
|
394
485
|
tokens: className === null
|
|
395
486
|
? classTokensOf(classAttr.value)
|
|
396
487
|
: [...new Set(className.split(/\s+/).filter(Boolean))],
|
|
397
488
|
attributes,
|
|
398
|
-
hasSpread: (element
|
|
489
|
+
hasSpread: hasSpread(element),
|
|
399
490
|
hasRef: attributes.has('ref'),
|
|
400
491
|
hasChildren: (element.children ?? []).some(reachesReact),
|
|
401
492
|
childTargets: childTargets(elementPath),
|
|
493
|
+
soleChildTarget: planned.get(sole)?.conversion?.target,
|
|
494
|
+
soleChild: sole && childFacts(sole),
|
|
402
495
|
bestaxInside: bestaxInside.get(element) ?? [],
|
|
403
496
|
bestaxAround: surrounding.bestax,
|
|
404
497
|
componentsAround: surrounding.other,
|
|
405
498
|
onlyChildOf: onlyChildOf(elementPath, bestaxLocals),
|
|
406
499
|
};
|
|
407
500
|
let result = plan(facts);
|
|
408
|
-
const converts = result.conversion !== null;
|
|
409
|
-
|
|
501
|
+
const converts = result.conversion !== null || result.fold !== undefined;
|
|
502
|
+
const becomes = result.conversion?.target ?? result.fold?.target;
|
|
503
|
+
if (className === null && becomes) {
|
|
410
504
|
// The same element with a static className would convert; with a
|
|
411
505
|
// computed one, only a person can turn each condition into its prop.
|
|
412
|
-
const target =
|
|
506
|
+
const target = becomes;
|
|
413
507
|
result = {
|
|
414
508
|
conversion: null,
|
|
415
509
|
todos: [
|
|
@@ -421,8 +515,15 @@ export default function transform(fileInfo, api, options = {}) {
|
|
|
421
515
|
],
|
|
422
516
|
};
|
|
423
517
|
}
|
|
518
|
+
if (result.fold) {
|
|
519
|
+
// The wrapper's props join its child's conversion, which has not been
|
|
520
|
+
// applied yet: conversions are applied after every element is planned.
|
|
521
|
+
const inner = planned.get(soleChild(element)).conversion;
|
|
522
|
+
inner.props.push(...result.fold.props);
|
|
523
|
+
inner.numbers.push(...result.fold.numbers);
|
|
524
|
+
}
|
|
424
525
|
planned.set(element, result);
|
|
425
|
-
if (result.conversion || result.todos.length > 0) {
|
|
526
|
+
if (result.conversion || result.fold || result.todos.length > 0) {
|
|
426
527
|
elements.unshift({ path: elementPath, plan: result, converts });
|
|
427
528
|
}
|
|
428
529
|
}
|
|
@@ -436,7 +537,7 @@ export default function transform(fileInfo, api, options = {}) {
|
|
|
436
537
|
if (converting)
|
|
437
538
|
addTodo(ctx, elements[0].path, rule, message);
|
|
438
539
|
for (const element of elements) {
|
|
439
|
-
element.plan = { ...element.plan, conversion: null };
|
|
540
|
+
element.plan = { ...element.plan, conversion: null, fold: undefined };
|
|
440
541
|
}
|
|
441
542
|
};
|
|
442
543
|
const runtime = fileJsxRuntime(root, j) ??
|
|
@@ -482,50 +583,89 @@ export default function transform(fileInfo, api, options = {}) {
|
|
|
482
583
|
}
|
|
483
584
|
}
|
|
484
585
|
for (const { path: elementPath, plan: result } of [...elements].reverse()) {
|
|
586
|
+
if (result.fold) {
|
|
587
|
+
// The child inside has converted already (innermost first), with the
|
|
588
|
+
// wrapper's props on it; the wrapper itself goes, its comments with the
|
|
589
|
+
// child: one on the wrapper moves to the child, and one inside the
|
|
590
|
+
// wrapper's tags moves inside the child's opening tag.
|
|
591
|
+
const wrapper = elementPath.node;
|
|
592
|
+
const inner = soleChild(wrapper);
|
|
593
|
+
const inTags = [
|
|
594
|
+
...tagComments(wrapper),
|
|
595
|
+
...(wrapper.openingElement.attributes ?? []).flatMap((attr) => attr.comments ?? []),
|
|
596
|
+
];
|
|
597
|
+
if (inTags.length) {
|
|
598
|
+
const name = inner.openingElement.name;
|
|
599
|
+
name.comments = [...(name.comments ?? []), ...inTags.map(afterTagName)];
|
|
600
|
+
}
|
|
601
|
+
if (wrapper.comments?.length) {
|
|
602
|
+
inner.comments = [...wrapper.comments, ...(inner.comments ?? [])];
|
|
603
|
+
}
|
|
604
|
+
elementPath.replace(inner);
|
|
605
|
+
ctx.dirty = true;
|
|
606
|
+
continue;
|
|
607
|
+
}
|
|
485
608
|
const conversion = result.conversion;
|
|
486
609
|
if (!conversion)
|
|
487
610
|
continue;
|
|
488
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
|
+
}
|
|
489
634
|
const [head, ...rest] = conversion.target.split('.');
|
|
490
635
|
renameElement(j, element, [ctx.reserve(head), ...rest].join('.'));
|
|
491
|
-
const attrs = element.openingElement.attributes;
|
|
492
636
|
for (const name of conversion.drop) {
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
const classAttr = findAttr(element, 'className');
|
|
496
|
-
const replacement = conversion.props.map(([name, value]) => makeAttr(j, name, value === true ? undefined : value));
|
|
497
|
-
if (conversion.className) {
|
|
498
|
-
replacement.push(makeAttr(j, 'className', conversion.className));
|
|
637
|
+
const holder = findAttr(element, name) ? element : child;
|
|
638
|
+
removeAttr(holder, findAttr(holder, name));
|
|
499
639
|
}
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
return { ...comment, leading: false, trailing: true };
|
|
514
|
-
}
|
|
515
|
-
return {
|
|
516
|
-
type: 'CommentBlock',
|
|
517
|
-
value: ` ${comment.value.trim().replace(/\*\//g, '*\\/')} `,
|
|
518
|
-
leading: false,
|
|
519
|
-
trailing: true,
|
|
520
|
-
};
|
|
521
|
-
}),
|
|
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 ?? []),
|
|
522
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;
|
|
523
664
|
}
|
|
524
|
-
attrs.splice(attrs.indexOf(classAttr), 1, ...replacement);
|
|
525
665
|
// After the splice, so a prop the conversion wrote is found as well as
|
|
526
666
|
// an attribute the element already had.
|
|
527
667
|
for (const name of conversion.numbers) {
|
|
528
|
-
const attr = findAttr(
|
|
668
|
+
const attr = findAttr(written, name);
|
|
529
669
|
attr.value = j.jsxExpressionContainer(j.numericLiteral(Number(attributeValue(attr))));
|
|
530
670
|
}
|
|
531
671
|
ctx.dirty = true;
|