bestax-migrate 2.25.5 → 2.25.6
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/html/attributes.d.ts +96 -0
- package/dist/sources/_shared/html/attributes.d.ts.map +1 -0
- package/dist/sources/_shared/html/attributes.js +313 -0
- package/dist/sources/_shared/html/emit.d.ts +49 -0
- package/dist/sources/_shared/html/emit.d.ts.map +1 -0
- package/dist/sources/_shared/html/emit.js +355 -0
- package/dist/sources/_shared/html/parse.d.ts +54 -0
- package/dist/sources/_shared/html/parse.d.ts.map +1 -0
- package/dist/sources/_shared/html/parse.js +85 -0
- package/dist/sources/_shared/html/style.d.ts +61 -0
- package/dist/sources/_shared/html/style.d.ts.map +1 -0
- package/dist/sources/_shared/html/style.js +185 -0
- package/dist/sources/_shared/html/text.d.ts +43 -0
- package/dist/sources/_shared/html/text.d.ts.map +1 -0
- package/dist/sources/_shared/html/text.js +120 -0
- package/dist/sources/_shared/html/whitespace.d.ts +80 -0
- package/dist/sources/_shared/html/whitespace.d.ts.map +1 -0
- package/dist/sources/_shared/html/whitespace.js +409 -0
- package/dist/sources/bulma-classes/display.d.ts +74 -0
- package/dist/sources/bulma-classes/display.d.ts.map +1 -0
- package/dist/sources/bulma-classes/display.js +215 -0
- package/dist/sources/rbx/mapping.js +1 -1
- package/dist/sources/react-bulma-components/mapping.js +1 -1
- package/package.json +4 -2
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTML attributes → JSX props.
|
|
3
|
+
*
|
|
4
|
+
* Names and kinds come from `property-information`, the table
|
|
5
|
+
* `hast-util-to-jsx-runtime` (and so MDX) reads: `class` is `className`,
|
|
6
|
+
* `for` is `htmlFor`, `tabindex` is `tabIndex`, SVG's `stroke-width` is
|
|
7
|
+
* `strokeWidth`, and it knows which attributes are boolean. What it can't
|
|
8
|
+
* know is written here by hand, from the HTML spec and from React:
|
|
9
|
+
*
|
|
10
|
+
* - a boolean attribute is on whatever its value (`disabled="false"` too),
|
|
11
|
+
* so it is written bare; one that is also a string (`download`,
|
|
12
|
+
* `hidden`) is bare only when empty or its own name;
|
|
13
|
+
* - a value React would make read-only is its default instead: an input's
|
|
14
|
+
* `value` and `checked`, a `<select>`'s selected options, a
|
|
15
|
+
* `<textarea>`'s text (an input whose `value` isn't what is typed in it,
|
|
16
|
+
* a button's or a checkbox's, keeps `value`);
|
|
17
|
+
* - every other value stays the string it was, numbers included, since the
|
|
18
|
+
* bulma-classes planner reads attributes as strings; `numberLiteral` is
|
|
19
|
+
* there to write a number-typed one as a number afterwards;
|
|
20
|
+
* - `data-*` and `aria-*` keep their names, and so does an attribute
|
|
21
|
+
* neither table knows, one React gets wrong under the table's name
|
|
22
|
+
* (`REACT_UNKNOWN`), and every attribute on a custom element, which React
|
|
23
|
+
* passes through as written (`class` aside);
|
|
24
|
+
* - an `on…` attribute is code React can't run from a string, and a name
|
|
25
|
+
* JSX can't spell (`@click`, `x-on:click`) can't be written at all; both
|
|
26
|
+
* are handed back to the caller instead of emitted.
|
|
27
|
+
*/
|
|
28
|
+
import { type Element } from './parse.js';
|
|
29
|
+
export type PropValue = true | string | {
|
|
30
|
+
expression: string;
|
|
31
|
+
};
|
|
32
|
+
export interface Prop {
|
|
33
|
+
/** The JSX name (`className`, `aria-label`). */
|
|
34
|
+
name: string;
|
|
35
|
+
value: PropValue;
|
|
36
|
+
/**
|
|
37
|
+
* The attribute it was written as (`class`, `xlink:href`). A select's
|
|
38
|
+
* `defaultValue` comes from its options' `selected`, and a textarea's from
|
|
39
|
+
* its text, named `textarea`.
|
|
40
|
+
*/
|
|
41
|
+
attribute: string;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Something the converter can't carry over as written, for the caller to
|
|
45
|
+
* report. Emitted: `unknown-attribute`, `javascript-url`, `style-important`
|
|
46
|
+
* (without its priority), and the element kinds. Not emitted:
|
|
47
|
+
* `attribute-name`, `event-handler`, `style` (the one declaration it
|
|
48
|
+
* quotes), `element-name` (its children are), and a `conditional-comment`.
|
|
49
|
+
*/
|
|
50
|
+
export interface HtmlIssue {
|
|
51
|
+
kind: 'attribute-name' | 'event-handler' | 'unknown-attribute' | 'javascript-url' | 'style' | 'style-important' | 'custom-element' | 'obsolete-element' | 'unknown-element' | 'template' | 'element-name' | 'conditional-comment';
|
|
52
|
+
/** The attribute, tag or CSS property it is about, as written. */
|
|
53
|
+
token: string;
|
|
54
|
+
/** The attribute it is on, for its line. */
|
|
55
|
+
attribute?: string;
|
|
56
|
+
/** What a report quotes: a handler's code, a style, a declaration. */
|
|
57
|
+
detail?: string;
|
|
58
|
+
/** For an `event-handler`, the React prop that takes a function. */
|
|
59
|
+
prop?: string;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Names `property-information` gives that React gets wrong: it doesn't
|
|
63
|
+
* recognize them (and drops a boolean one), takes a boolean one for a
|
|
64
|
+
* string, or renders them as another attribute (`panose1`, `navDown`).
|
|
65
|
+
* They are written with the attribute's own name and its string value,
|
|
66
|
+
* which React passes through. `e2e/html-attributes-react.test.ts` holds this
|
|
67
|
+
* list to React, both ways.
|
|
68
|
+
*/
|
|
69
|
+
export declare const REACT_UNKNOWN: ReadonlySet<string>;
|
|
70
|
+
/**
|
|
71
|
+
* Whether React renders it as a custom element, passing its attributes
|
|
72
|
+
* through as written: an HTML tag with a dash. A built-in extended with
|
|
73
|
+
* `is` is not one to React's server renderer, which gives it a built-in's
|
|
74
|
+
* props (a bare `disabled`, a textarea's `defaultValue`), and those are the
|
|
75
|
+
* DOM properties React's client sets on it too.
|
|
76
|
+
*/
|
|
77
|
+
export declare function isCustomElement(element: Element): boolean;
|
|
78
|
+
/** A JSX prop as source: `name`, `name="value"` or `name={…}`. */
|
|
79
|
+
export declare function printProp(prop: Prop): string;
|
|
80
|
+
/** An element's attributes as JSX props, and what couldn't be carried. */
|
|
81
|
+
export declare function convertAttributes(element: Element): {
|
|
82
|
+
props: Prop[];
|
|
83
|
+
issues: HtmlIssue[];
|
|
84
|
+
};
|
|
85
|
+
/**
|
|
86
|
+
* Whether a JSX prop on a plain element takes a number (`tabIndex`,
|
|
87
|
+
* `colSpan`, `aria-valuenow`), by the same table.
|
|
88
|
+
*/
|
|
89
|
+
export declare function isNumberProp(name: string, space?: 'html' | 'svg'): boolean;
|
|
90
|
+
/**
|
|
91
|
+
* A string value as a number literal's source, when the number is spelled
|
|
92
|
+
* the way it renders: not `040`, `1.50` or ` 4`, which a number would turn
|
|
93
|
+
* into `40`, `1.5` and `4`. Null otherwise, and the value stays a string.
|
|
94
|
+
*/
|
|
95
|
+
export declare function numberLiteral(value: string): string | null;
|
|
96
|
+
//# sourceMappingURL=attributes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attributes.d.ts","sourceRoot":"","sources":["../../../../src/sources/_shared/html/attributes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAGH,OAAO,EAAqB,KAAK,OAAO,EAAE,MAAM,YAAY,CAAC;AAI7D,MAAM,MAAM,SAAS,GAAG,IAAI,GAAG,MAAM,GAAG;IAAE,UAAU,EAAE,MAAM,CAAA;CAAE,CAAC;AAE/D,MAAM,WAAW,IAAI;IACnB,gDAAgD;IAChD,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,SAAS,CAAC;IACjB;;;;OAIG;IACH,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,SAAS;IACxB,IAAI,EACA,gBAAgB,GAChB,eAAe,GACf,mBAAmB,GACnB,gBAAgB,GAChB,OAAO,GACP,iBAAiB,GACjB,gBAAgB,GAChB,kBAAkB,GAClB,iBAAiB,GACjB,UAAU,GACV,cAAc,GACd,qBAAqB,CAAC;IAC1B,kEAAkE;IAClE,KAAK,EAAE,MAAM,CAAC;IACd,4CAA4C;IAC5C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,sEAAsE;IACtE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,oEAAoE;IACpE,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAKD;;;;;;;GAOG;AACH,eAAO,MAAM,aAAa,EAAE,WAAW,CAAC,MAAM,CAwD5C,CAAC;AAuCH;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAMzD;AA8DD,kEAAkE;AAClE,wBAAgB,SAAS,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,CAK5C;AAED,0EAA0E;AAC1E,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,OAAO,GAAG;IACnD,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,MAAM,EAAE,SAAS,EAAE,CAAC;CACrB,CAiGA;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAC1B,IAAI,EAAE,MAAM,EACZ,KAAK,GAAE,MAAM,GAAG,KAAc,GAC7B,OAAO,CAET;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAI1D"}
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTML attributes → JSX props.
|
|
3
|
+
*
|
|
4
|
+
* Names and kinds come from `property-information`, the table
|
|
5
|
+
* `hast-util-to-jsx-runtime` (and so MDX) reads: `class` is `className`,
|
|
6
|
+
* `for` is `htmlFor`, `tabindex` is `tabIndex`, SVG's `stroke-width` is
|
|
7
|
+
* `strokeWidth`, and it knows which attributes are boolean. What it can't
|
|
8
|
+
* know is written here by hand, from the HTML spec and from React:
|
|
9
|
+
*
|
|
10
|
+
* - a boolean attribute is on whatever its value (`disabled="false"` too),
|
|
11
|
+
* so it is written bare; one that is also a string (`download`,
|
|
12
|
+
* `hidden`) is bare only when empty or its own name;
|
|
13
|
+
* - a value React would make read-only is its default instead: an input's
|
|
14
|
+
* `value` and `checked`, a `<select>`'s selected options, a
|
|
15
|
+
* `<textarea>`'s text (an input whose `value` isn't what is typed in it,
|
|
16
|
+
* a button's or a checkbox's, keeps `value`);
|
|
17
|
+
* - every other value stays the string it was, numbers included, since the
|
|
18
|
+
* bulma-classes planner reads attributes as strings; `numberLiteral` is
|
|
19
|
+
* there to write a number-typed one as a number afterwards;
|
|
20
|
+
* - `data-*` and `aria-*` keep their names, and so does an attribute
|
|
21
|
+
* neither table knows, one React gets wrong under the table's name
|
|
22
|
+
* (`REACT_UNKNOWN`), and every attribute on a custom element, which React
|
|
23
|
+
* passes through as written (`class` aside);
|
|
24
|
+
* - an `on…` attribute is code React can't run from a string, and a name
|
|
25
|
+
* JSX can't spell (`@click`, `x-on:click`) can't be written at all; both
|
|
26
|
+
* are handed back to the caller instead of emitted.
|
|
27
|
+
*/
|
|
28
|
+
import { find, hastToReact, html, svg } from 'property-information';
|
|
29
|
+
import { isElement, isText } from './parse.js';
|
|
30
|
+
import { styleObject } from './style.js';
|
|
31
|
+
import { attributeValue, jsString } from './text.js';
|
|
32
|
+
const SVG = 'http://www.w3.org/2000/svg';
|
|
33
|
+
const MATHML = 'http://www.w3.org/1998/Math/MathML';
|
|
34
|
+
/**
|
|
35
|
+
* Names `property-information` gives that React gets wrong: it doesn't
|
|
36
|
+
* recognize them (and drops a boolean one), takes a boolean one for a
|
|
37
|
+
* string, or renders them as another attribute (`panose1`, `navDown`).
|
|
38
|
+
* They are written with the attribute's own name and its string value,
|
|
39
|
+
* which React passes through. `e2e/html-attributes-react.test.ts` holds this
|
|
40
|
+
* list to React, both ways.
|
|
41
|
+
*/
|
|
42
|
+
export const REACT_UNKNOWN = new Set([
|
|
43
|
+
'aLink',
|
|
44
|
+
'allowPaymentRequest',
|
|
45
|
+
'allowTransparency',
|
|
46
|
+
'allowUserMedia',
|
|
47
|
+
'alpha',
|
|
48
|
+
'bgColor',
|
|
49
|
+
'borderColor',
|
|
50
|
+
'bottomMargin',
|
|
51
|
+
'charOff',
|
|
52
|
+
'closedBy',
|
|
53
|
+
'codeBase',
|
|
54
|
+
'codeType',
|
|
55
|
+
'colorSpace',
|
|
56
|
+
'commandFor',
|
|
57
|
+
'compact',
|
|
58
|
+
'declare',
|
|
59
|
+
'dirName',
|
|
60
|
+
'exportParts',
|
|
61
|
+
'hSpace',
|
|
62
|
+
'isMap',
|
|
63
|
+
'leftMargin',
|
|
64
|
+
'longDesc',
|
|
65
|
+
'lowSrc',
|
|
66
|
+
'noHref',
|
|
67
|
+
'noResize',
|
|
68
|
+
'noShade',
|
|
69
|
+
'noWrap',
|
|
70
|
+
'rightMargin',
|
|
71
|
+
'shadowRootClonable',
|
|
72
|
+
'shadowRootCustomElementRegistry',
|
|
73
|
+
'shadowRootDelegatesFocus',
|
|
74
|
+
'shadowRootMode',
|
|
75
|
+
'shadowRootSerializable',
|
|
76
|
+
'topMargin',
|
|
77
|
+
'typeMustMatch',
|
|
78
|
+
'vAlign',
|
|
79
|
+
'valueType',
|
|
80
|
+
'vLink',
|
|
81
|
+
'vSpace',
|
|
82
|
+
'writingSuggestions',
|
|
83
|
+
// SVG's, where the attribute's case is its name.
|
|
84
|
+
'horizOriginY',
|
|
85
|
+
'navDown',
|
|
86
|
+
'navDownLeft',
|
|
87
|
+
'navDownRight',
|
|
88
|
+
'navLeft',
|
|
89
|
+
'navNext',
|
|
90
|
+
'navPrev',
|
|
91
|
+
'navRight',
|
|
92
|
+
'navUp',
|
|
93
|
+
'navUpLeft',
|
|
94
|
+
'navUpRight',
|
|
95
|
+
'panose1',
|
|
96
|
+
'playbackOrder',
|
|
97
|
+
'timelineBegin',
|
|
98
|
+
]);
|
|
99
|
+
/** Input types whose `value` React leaves alone: it isn't what is typed. */
|
|
100
|
+
const VALUE_NOT_TYPED = new Set([
|
|
101
|
+
'button',
|
|
102
|
+
'checkbox',
|
|
103
|
+
'hidden',
|
|
104
|
+
'image',
|
|
105
|
+
'radio',
|
|
106
|
+
'reset',
|
|
107
|
+
'submit',
|
|
108
|
+
]);
|
|
109
|
+
/** What JSX can write as an attribute name (no namespace, no symbols). */
|
|
110
|
+
const JSX_NAME = /^[A-Za-z_$][\w$-]*$/;
|
|
111
|
+
/**
|
|
112
|
+
* A `javascript:` URL, read the way a browser reads one (and React, which
|
|
113
|
+
* replaces it with a URL that throws): leading control characters and
|
|
114
|
+
* spaces skipped, tabs and line breaks anywhere ignored.
|
|
115
|
+
*/
|
|
116
|
+
const JAVASCRIPT_URL = new RegExp(`^[\\x00-\\x20]*${[...'javascript:'].join('[\\t\\n\\r]*')}`, 'i');
|
|
117
|
+
/** The attributes React reads as a URL to follow. */
|
|
118
|
+
const URL_ATTRIBUTES = new Set([
|
|
119
|
+
'href',
|
|
120
|
+
'src',
|
|
121
|
+
'action',
|
|
122
|
+
'formaction',
|
|
123
|
+
'xlink:href',
|
|
124
|
+
]);
|
|
125
|
+
function attributeOf(element, name) {
|
|
126
|
+
return element.attrs.find(attr => attr.name === name && !attr.prefix)?.value;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Whether React renders it as a custom element, passing its attributes
|
|
130
|
+
* through as written: an HTML tag with a dash. A built-in extended with
|
|
131
|
+
* `is` is not one to React's server renderer, which gives it a built-in's
|
|
132
|
+
* props (a bare `disabled`, a textarea's `defaultValue`), and those are the
|
|
133
|
+
* DOM properties React's client sets on it too.
|
|
134
|
+
*/
|
|
135
|
+
export function isCustomElement(element) {
|
|
136
|
+
return (element.namespaceURI !== SVG &&
|
|
137
|
+
element.namespaceURI !== MATHML &&
|
|
138
|
+
element.tagName.includes('-'));
|
|
139
|
+
}
|
|
140
|
+
/** An element's text, its descendants' included, as `textContent` reads. */
|
|
141
|
+
function textContent(element) {
|
|
142
|
+
return element.childNodes
|
|
143
|
+
.map(child => isText(child) ? child.value : isElement(child) ? textContent(child) : '')
|
|
144
|
+
.join('');
|
|
145
|
+
}
|
|
146
|
+
/** The options a `<select>` lists: its own, and its optgroups'. */
|
|
147
|
+
function optionsOf(select) {
|
|
148
|
+
return select.childNodes.filter(isElement).flatMap(child => {
|
|
149
|
+
if (child.tagName === 'option')
|
|
150
|
+
return [child];
|
|
151
|
+
return child.tagName === 'optgroup'
|
|
152
|
+
? child.childNodes.filter(isElement).filter(o => o.tagName === 'option')
|
|
153
|
+
: [];
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* The `<select>` that lists an option, if any: the one whose `defaultValue`
|
|
158
|
+
* carries the option's `selected`. It is found through `optionsOf`, which
|
|
159
|
+
* is what that default reads, so the option never drops a `selected` the
|
|
160
|
+
* select doesn't take.
|
|
161
|
+
*/
|
|
162
|
+
function selectOf(option) {
|
|
163
|
+
const parent = option.parentNode;
|
|
164
|
+
const grand = parent && isElement(parent) ? parent.parentNode : null;
|
|
165
|
+
return [parent, grand].find((node) => !!node &&
|
|
166
|
+
isElement(node) &&
|
|
167
|
+
node.tagName === 'select' &&
|
|
168
|
+
optionsOf(node).includes(option));
|
|
169
|
+
}
|
|
170
|
+
/** An option's value: its `value`, else its text, stripped and collapsed. */
|
|
171
|
+
function optionValue(option) {
|
|
172
|
+
return (attributeOf(option, 'value') ??
|
|
173
|
+
textContent(option)
|
|
174
|
+
.replace(/[ \t\n\f\r]+/g, ' ')
|
|
175
|
+
.trim());
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* The `defaultValue` a `<select>` takes from the options marked `selected`:
|
|
179
|
+
* the last of them, or all of them when it is `multiple`.
|
|
180
|
+
*/
|
|
181
|
+
function selectDefault(select) {
|
|
182
|
+
const selected = optionsOf(select)
|
|
183
|
+
.filter(option => attributeOf(option, 'selected') !== undefined)
|
|
184
|
+
.map(optionValue);
|
|
185
|
+
if (selected.length === 0)
|
|
186
|
+
return undefined;
|
|
187
|
+
if (attributeOf(select, 'multiple') === undefined)
|
|
188
|
+
return selected.pop();
|
|
189
|
+
return { expression: `[${selected.map(jsString).join(', ')}]` };
|
|
190
|
+
}
|
|
191
|
+
/** A JSX prop as source: `name`, `name="value"` or `name={…}`. */
|
|
192
|
+
export function printProp(prop) {
|
|
193
|
+
const { name, value } = prop;
|
|
194
|
+
if (value === true)
|
|
195
|
+
return name;
|
|
196
|
+
if (typeof value === 'string')
|
|
197
|
+
return `${name}=${attributeValue(value)}`;
|
|
198
|
+
return `${name}={${value.expression}}`;
|
|
199
|
+
}
|
|
200
|
+
/** An element's attributes as JSX props, and what couldn't be carried. */
|
|
201
|
+
export function convertAttributes(element) {
|
|
202
|
+
const props = [];
|
|
203
|
+
const issues = [];
|
|
204
|
+
const tag = element.tagName;
|
|
205
|
+
const schema = element.namespaceURI === SVG ? svg : html;
|
|
206
|
+
const custom = isCustomElement(element);
|
|
207
|
+
const type = attributeOf(element, 'type')?.toLowerCase() ?? 'text';
|
|
208
|
+
const add = (name, value, attribute) => {
|
|
209
|
+
if (!JSX_NAME.test(name) || props.some(prop => prop.name === name)) {
|
|
210
|
+
issues.push({ kind: 'attribute-name', token: attribute, attribute });
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
props.push({ name, value, attribute });
|
|
214
|
+
};
|
|
215
|
+
for (const attr of element.attrs) {
|
|
216
|
+
const attribute = attr.prefix ? `${attr.prefix}:${attr.name}` : attr.name;
|
|
217
|
+
const { value } = attr;
|
|
218
|
+
if (/^on./i.test(attribute)) {
|
|
219
|
+
const info = find(schema, attribute);
|
|
220
|
+
issues.push({
|
|
221
|
+
kind: 'event-handler',
|
|
222
|
+
token: attribute,
|
|
223
|
+
attribute,
|
|
224
|
+
detail: value,
|
|
225
|
+
prop: info.space ? info.property : attribute,
|
|
226
|
+
});
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
if (URL_ATTRIBUTES.has(attribute) && JAVASCRIPT_URL.test(value)) {
|
|
230
|
+
issues.push({ kind: 'javascript-url', token: attribute, attribute });
|
|
231
|
+
}
|
|
232
|
+
if (attribute === 'style') {
|
|
233
|
+
const style = styleObject(value);
|
|
234
|
+
for (const issue of style.issues) {
|
|
235
|
+
issues.push(issue.kind === 'style'
|
|
236
|
+
? { kind: 'style', token: 'style', attribute, detail: issue.detail }
|
|
237
|
+
: { ...issue, attribute });
|
|
238
|
+
}
|
|
239
|
+
if (style.code)
|
|
240
|
+
add('style', { expression: style.code }, attribute);
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
if (custom) {
|
|
244
|
+
add(attribute === 'class' ? 'className' : attribute, value, attribute);
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
if (tag === 'option' && attribute === 'selected' && selectOf(element)) {
|
|
248
|
+
continue; // The select carries it, as its `defaultValue`.
|
|
249
|
+
}
|
|
250
|
+
if (tag === 'input' && attribute === 'checked') {
|
|
251
|
+
add('defaultChecked', true, attribute);
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
if (tag === 'input' &&
|
|
255
|
+
attribute === 'value' &&
|
|
256
|
+
!VALUE_NOT_TYPED.has(type)) {
|
|
257
|
+
add('defaultValue', value, attribute);
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
const info = find(schema, attribute);
|
|
261
|
+
if (!info.space) {
|
|
262
|
+
// `data-*`, `aria-*`, `role`, and names neither table knows.
|
|
263
|
+
const unknown = !info.defined &&
|
|
264
|
+
!attribute.includes('-') &&
|
|
265
|
+
JSX_NAME.test(attribute) &&
|
|
266
|
+
element.namespaceURI !== MATHML;
|
|
267
|
+
if (unknown) {
|
|
268
|
+
issues.push({ kind: 'unknown-attribute', token: attribute, attribute });
|
|
269
|
+
}
|
|
270
|
+
add(attribute, value, attribute);
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
const name = hastToReact[info.property] ?? info.property;
|
|
274
|
+
if (REACT_UNKNOWN.has(name)) {
|
|
275
|
+
issues.push({ kind: 'unknown-attribute', token: attribute, attribute });
|
|
276
|
+
add(info.attribute, value, attribute);
|
|
277
|
+
}
|
|
278
|
+
else if (info.boolean) {
|
|
279
|
+
add(name, true, attribute);
|
|
280
|
+
}
|
|
281
|
+
else if (info.overloadedBoolean) {
|
|
282
|
+
const on = value === '' || value.toLowerCase() === info.attribute;
|
|
283
|
+
add(name, on ? true : value, attribute);
|
|
284
|
+
}
|
|
285
|
+
else {
|
|
286
|
+
add(name, value, attribute);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
const selected = tag === 'select' ? selectDefault(element) : undefined;
|
|
290
|
+
if (selected !== undefined)
|
|
291
|
+
add('defaultValue', selected, 'selected');
|
|
292
|
+
const text = tag === 'textarea' ? textContent(element) : '';
|
|
293
|
+
if (text !== '')
|
|
294
|
+
add('defaultValue', text, 'textarea');
|
|
295
|
+
return { props, issues };
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Whether a JSX prop on a plain element takes a number (`tabIndex`,
|
|
299
|
+
* `colSpan`, `aria-valuenow`), by the same table.
|
|
300
|
+
*/
|
|
301
|
+
export function isNumberProp(name, space = 'html') {
|
|
302
|
+
return find(space === 'svg' ? svg : html, name).number;
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* A string value as a number literal's source, when the number is spelled
|
|
306
|
+
* the way it renders: not `040`, `1.50` or ` 4`, which a number would turn
|
|
307
|
+
* into `40`, `1.5` and `4`. Null otherwise, and the value stays a string.
|
|
308
|
+
*/
|
|
309
|
+
export function numberLiteral(value) {
|
|
310
|
+
return /^-?\d+(?:\.\d+)?$/.test(value) && String(Number(value)) === value
|
|
311
|
+
? value
|
|
312
|
+
: null;
|
|
313
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A parsed HTML tree → JSX source, laid out the way the HTML was.
|
|
3
|
+
*
|
|
4
|
+
* Each element keeps its tag (SVG's with the casing parse5 restores, so
|
|
5
|
+
* `foreignObject`), its attributes become props (`attributes.ts`), and its
|
|
6
|
+
* children follow the source's own lines: a run of whitespace with a line
|
|
7
|
+
* break in it is a line break in the JSX, at two spaces an indent. JSX drops
|
|
8
|
+
* such a run, so where the run renders (`whitespace.ts`) and a line break
|
|
9
|
+
* would lose it, the space is written out as `{" "}`; between two texts JSX
|
|
10
|
+
* joins the lines with a space already. Preformatted content is written on
|
|
11
|
+
* the lines it has, with a template literal for text that spans several, and
|
|
12
|
+
* a script's or stylesheet's text is always one.
|
|
13
|
+
*
|
|
14
|
+
* Void elements close themselves, and so does an element with nothing left
|
|
15
|
+
* in it. A comment becomes a JSX comment, except Internet Explorer's
|
|
16
|
+
* conditional ones, which no supported browser reads. What can't be written
|
|
17
|
+
* as it was is reported, with the HTML line it is on, for the caller to turn
|
|
18
|
+
* into TODOs: `HtmlIssue` lists the kinds, and which of them are emitted.
|
|
19
|
+
*/
|
|
20
|
+
import { type HtmlIssue } from './attributes.js';
|
|
21
|
+
import { type ParentNode } from './parse.js';
|
|
22
|
+
import { type DisplayOf } from './whitespace.js';
|
|
23
|
+
export interface EmitOptions {
|
|
24
|
+
/** Each element's CSS display; the HTML UA stylesheet's by default. */
|
|
25
|
+
displayOf?: DisplayOf;
|
|
26
|
+
/**
|
|
27
|
+
* Indent levels to start every line at, for code that goes inside more
|
|
28
|
+
* code (a component's `return (…)`). Indenting the result afterwards
|
|
29
|
+
* would change a multi-line template literal's text, so it is done here,
|
|
30
|
+
* where those lines are known.
|
|
31
|
+
*/
|
|
32
|
+
indent?: number;
|
|
33
|
+
}
|
|
34
|
+
export interface Reported extends HtmlIssue {
|
|
35
|
+
/** The 1-based HTML line it is on, when the node has a location. */
|
|
36
|
+
line: number | null;
|
|
37
|
+
}
|
|
38
|
+
export interface Emitted {
|
|
39
|
+
/** One JSX expression: the content's only element, or a fragment. */
|
|
40
|
+
code: string;
|
|
41
|
+
/**
|
|
42
|
+
* The HTML line an emitted line (1-based) came from, or null for a line
|
|
43
|
+
* the emitter wrote itself (a fragment's tags) or a node with no location.
|
|
44
|
+
*/
|
|
45
|
+
htmlLineOf(line: number): number | null;
|
|
46
|
+
reported: Reported[];
|
|
47
|
+
}
|
|
48
|
+
export declare function emitJsx(root: ParentNode, options?: EmitOptions): Emitted;
|
|
49
|
+
//# sourceMappingURL=emit.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"emit.d.ts","sourceRoot":"","sources":["../../../../src/sources/_shared/html/emit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAIL,KAAK,SAAS,EACf,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAIL,KAAK,UAAU,EAChB,MAAM,YAAY,CAAC;AAOpB,OAAO,EAGL,KAAK,SAAS,EAEf,MAAM,iBAAiB,CAAC;AAEzB,MAAM,WAAW,WAAW;IAC1B,uEAAuE;IACvE,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB;;;;;OAKG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,QAAS,SAAQ,SAAS;IACzC,oEAAoE;IACpE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CACrB;AAED,MAAM,WAAW,OAAO;IACtB,qEAAqE;IACrE,IAAI,EAAE,MAAM,CAAC;IACb;;;OAGG;IACH,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IACxC,QAAQ,EAAE,QAAQ,EAAE,CAAC;CACtB;AAqLD,wBAAgB,OAAO,CAAC,IAAI,EAAE,UAAU,EAAE,OAAO,GAAE,WAAgB,GAAG,OAAO,CA6K5E"}
|