Skip to content

Commit ed479a5

Browse files
committed
Update @types/hast, utilities
1 parent 771379a commit ed479a5

File tree

4 files changed

+110
-50
lines changed

4 files changed

+110
-50
lines changed

lib/index.js

Lines changed: 49 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,11 @@
11
/**
22
* @typedef {import('property-information').Schema} Schema
3-
* @typedef {import('hast').Content} Content
3+
* @typedef {import('hast').Nodes} Nodes
4+
* @typedef {import('hast').Parents} Parents
45
* @typedef {import('hast').Element} Element
5-
* @typedef {import('hast').Root} Root
66
* @typedef {import('./components.js').Components} Components
77
*/
88

9-
/**
10-
* @typedef {Content | Root} Node
11-
* @typedef {Extract<Node, import('unist').Parent>} Parent
12-
*/
13-
149
/**
1510
* @typedef {unknown} Fragment
1611
* Represent the children, typically a symbol.
@@ -91,7 +86,7 @@
9186
*
9287
* @callback Create
9388
* Create something in development or production.
94-
* @param {Node} node
89+
* @param {Nodes} node
9590
* hast node.
9691
* @param {unknown} type
9792
* Fragment symbol or tag name.
@@ -106,6 +101,8 @@
106101
* Info passed around.
107102
* @property {string | undefined} filePath
108103
* File path.
104+
* @property {Array<Parents>} ancestors
105+
* Stack of parents.
109106
* @property {Partial<Components>} components
110107
* Components to swap.
111108
* @property {boolean} passKeys
@@ -232,7 +229,7 @@ const tableElements = new Set(['table', 'thead', 'tbody', 'tfoot', 'tr'])
232229
* Transform a hast tree to preact, react, solid, svelte, vue, etc.,
233230
* with an automatic JSX runtime.
234231
*
235-
* @param {Node} tree
232+
* @param {Nodes} tree
236233
* Tree to transform.
237234
* @param {Options} options
238235
* Configuration (required).
@@ -272,6 +269,7 @@ export function toJsxRuntime(tree, options) {
272269
/** @type {State} */
273270
const state = {
274271
Fragment: options.Fragment,
272+
ancestors: [],
275273
schema: options.space === 'svg' ? svg : html,
276274
passKeys: options.passKeys !== false,
277275
passNode: options.passNode || false,
@@ -303,7 +301,7 @@ export function toJsxRuntime(tree, options) {
303301
*
304302
* @param {State} state
305303
* Info passed around.
306-
* @param {Node} node
304+
* @param {Nodes} node
307305
* Current node.
308306
* @param {string | undefined} key
309307
* Key.
@@ -324,13 +322,19 @@ function one(state, node, key) {
324322
state.schema = schema
325323
}
326324

325+
state.ancestors.push(node)
326+
327327
let children = createChildren(state, node)
328-
const props = createProperties(state, node)
328+
const props = createProperties(state, state.ancestors)
329329
let type = state.Fragment
330330

331+
state.ancestors.pop()
332+
331333
if (node.type === 'element') {
332334
if (children && tableElements.has(node.tagName)) {
333-
children = children.filter((child) => !whitespace(child))
335+
children = children.filter(
336+
(child) => typeof child !== 'string' || !whitespace(child)
337+
)
334338
}
335339

336340
if (own.call(state.components, node.tagName)) {
@@ -408,8 +412,8 @@ function developmentCreate(filePath, jsxDEV) {
408412
isStaticChildren,
409413
{
410414
fileName: filePath,
411-
lineNumber: point.line === null ? undefined : point.line,
412-
columnNumber: point.column === null ? undefined : point.column - 1
415+
lineNumber: point ? point.line : undefined,
416+
columnNumber: point ? point.column - 1 : undefined
413417
},
414418
undefined
415419
)
@@ -421,7 +425,7 @@ function developmentCreate(filePath, jsxDEV) {
421425
*
422426
* @param {State} state
423427
* Info passed around.
424-
* @param {Parent} node
428+
* @param {Parents} node
425429
* Current element.
426430
* @returns {Array<Child>}
427431
* Children.
@@ -458,12 +462,13 @@ function createChildren(state, node) {
458462
*
459463
* @param {State} state
460464
* Info passed around.
461-
* @param {Parent} node
462-
* Current element.
465+
* @param {Array<Parents>} ancestors
466+
* Stack of parents.
463467
* @returns {Props}
464468
* Props for runtime.
465469
*/
466-
function createProperties(state, node) {
470+
function createProperties(state, ancestors) {
471+
const node = ancestors[ancestors.length - 1]
467472
/** @type {Props} */
468473
const props = {}
469474
/** @type {string} */
@@ -472,7 +477,12 @@ function createProperties(state, node) {
472477
if ('properties' in node && node.properties) {
473478
for (prop in node.properties) {
474479
if (prop !== 'children' && own.call(node.properties, prop)) {
475-
const result = createProperty(state, node, prop, node.properties[prop])
480+
const result = createProperty(
481+
state,
482+
ancestors,
483+
prop,
484+
node.properties[prop]
485+
)
476486

477487
if (result) {
478488
props[result[0]] = result[1]
@@ -489,16 +499,16 @@ function createProperties(state, node) {
489499
*
490500
* @param {State} state
491501
* Info passed around.
492-
* @param {Element} node
493-
* Current element.
502+
* @param {Array<Parents>} ancestors
503+
* Stack of parents.
494504
* @param {string} prop
495505
* Key.
496506
* @param {Array<string | number> | string | number | boolean | null | undefined} value
497507
* hast property value.
498508
* @returns {Field | void}
499509
* Field for runtime, optional.
500510
*/
501-
function createProperty(state, node, prop, value) {
511+
function createProperty(state, ancestors, prop, value) {
502512
const info = find(state.schema, prop)
503513

504514
// Ignore nullish and `NaN` values.
@@ -519,7 +529,9 @@ function createProperty(state, node, prop, value) {
519529
// React only accepts `style` as object.
520530
if (info.property === 'style') {
521531
let styleObject =
522-
typeof value === 'object' ? value : parseStyle(state, node, String(value))
532+
typeof value === 'object'
533+
? value
534+
: parseStyle(state, ancestors, String(value))
523535

524536
if (state.stylePropertyNameCase === 'css') {
525537
styleObject = transformStyleToCssCasing(styleObject)
@@ -541,31 +553,33 @@ function createProperty(state, node, prop, value) {
541553
*
542554
* @param {State} state
543555
* Info passed around.
544-
* @param {Element} node
545-
* Current element.
556+
* @param {Array<Nodes>} ancestors
557+
* Stack of nodes.
546558
* @param {string} value
547559
* CSS declarations.
548560
* @returns {Style}
549561
* Properties.
550562
* @throws
551563
* Throws `VFileMessage` when CSS cannot be parsed.
552564
*/
553-
function parseStyle(state, node, value) {
565+
function parseStyle(state, ancestors, value) {
554566
/** @type {Style} */
555567
const result = {}
556568

557569
try {
558570
styleToObject(value, replacer)
559571
} catch (error_) {
560-
const error = /** @type {Error} */ (error_)
561-
const cleanMessage = error.message.replace(/^undefined:\d+:\d+: /, '')
562-
563-
const message = new VFileMessage(
564-
'Cannot parse style attribute: ' + cleanMessage,
565-
node,
566-
'hast-util-to-jsx-runtime:style'
567-
)
568-
message.file = state.filePath || null
572+
const cause = /** @type {Error} */ (error_)
573+
574+
const message = new VFileMessage('Cannot parse `style` attribute', {
575+
ancestors,
576+
cause,
577+
source: 'hast-util-to-jsx-runtime',
578+
ruleId: 'style'
579+
})
580+
message.file = state.filePath || undefined
581+
message.url =
582+
'https://github.com/syntax-tree/hast-util-to-jsx-runtime#cannot-parse-style-attribute'
569583

570584
throw message
571585
}

package.json

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,23 +36,23 @@
3636
"index.js"
3737
],
3838
"dependencies": {
39-
"@types/hast": "^2.0.0",
40-
"@types/unist": "^2.0.0",
39+
"@types/hast": "^3.0.0",
40+
"@types/unist": "^3.0.0",
4141
"comma-separated-tokens": "^2.0.0",
42-
"hast-util-whitespace": "^2.0.0",
42+
"hast-util-whitespace": "^3.0.0",
4343
"property-information": "^6.0.0",
4444
"space-separated-tokens": "^2.0.0",
45-
"style-to-object": "^0.4.1",
46-
"unist-util-position": "^4.0.0",
47-
"vfile-message": "^3.0.0"
45+
"style-to-object": "^0.4.0",
46+
"unist-util-position": "^5.0.0",
47+
"vfile-message": "^4.0.0"
4848
},
4949
"devDependencies": {
5050
"@types/node": "^20.0.0",
5151
"@types/react": "^18.0.0",
5252
"@types/react-dom": "^18.0.0",
5353
"c8": "^8.0.0",
5454
"esbuild": "^0.18.0",
55-
"hastscript": "^7.0.0",
55+
"hastscript": "^8.0.0",
5656
"prettier": "^3.0.0",
5757
"react": "^18.0.0",
5858
"react-dom": "^18.0.0",
@@ -84,6 +84,7 @@
8484
"#": "`n` is wrong",
8585
"rules": {
8686
"n/file-extension-in-import": "off",
87+
"unicorn/prefer-at": "off",
8788
"unicorn/prefer-string-replace-all": "off"
8889
}
8990
},

readme.md

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,17 +113,64 @@ There is no default export.
113113
Transform a hast tree to preact, react, solid, svelte, vue, etc., with an
114114
automatic JSX runtime.
115115

116-
###### Parameters
116+
##### Parameters
117117

118118
* `tree` ([`Node`][node])
119119
— tree to transform
120120
* `options` ([`Options`][api-options], required)
121121
— configuration
122122

123-
###### Returns
123+
##### Returns
124124

125125
Result from your configured JSX runtime (`JSX.Element`).
126126

127+
##### Throws
128+
129+
The following errors are thrown:
130+
131+
###### ``Expected `Fragment` in options``
132+
133+
This error is thrown when either `options` is not passed at all or
134+
when `options.Fragment` is `undefined`.
135+
136+
The automatic JSX runtime needs a symbol for a fragment to work.
137+
138+
To solve the error, make sure you are passing the correct fragment symbol from
139+
your framework.
140+
141+
###### `` Expected `jsxDEV` in options when `development: true` ``
142+
143+
This error is thrown when `options.development` is turned on (`true`), but when
144+
`options.jsxDEV` is not a function.
145+
146+
The automatic JSX runtime, in development, needs this function.
147+
148+
To solve the error, make sure you are importing the correct runtime functions
149+
(for example, `'react/jsx-dev-runtime'`), and pass `jsxDEV`.
150+
151+
###### ``Expected `jsx` in production options``
152+
153+
###### ``Expected `jsxs` in production options``
154+
155+
These errors are thrown when `options.development` is *not* turned on (`false`
156+
or not defined), and when `options.jsx` or `options.jsxs` are not functions.
157+
158+
The automatic JSX runtime, in production, needs these functions.
159+
160+
To solve the error, make sure you are importing the correct runtime functions
161+
(for example, `'react/jsx-runtime'`), and pass `jsx` and `jsxs`.
162+
163+
###### ``Cannot parse `style` attribute``
164+
165+
This error is thrown when a `style` attribute is found on an element, which
166+
cannot be parsed as CSS.
167+
168+
Most frameworks don’t accept `style` as a string, so we need to parse it as
169+
CSS, and pass it as an object.
170+
But when broken CSS is used, such as `style="color:red; /*"`, we crash.
171+
172+
To solve the error, make sure authors write valid CSS.
173+
127174
### `Options`
128175

129176
Configuration (TypeScript type).

test/index.js

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -106,15 +106,13 @@ test('core', () => {
106106
)
107107

108108
assert.equal(
109-
// @ts-expect-error: Types are out of date, `name` is no longer used.
110109
// type-coverage:ignore-next-line
111110
toJsxRuntime({type: 'doctype'}, production).type,
112111
production.Fragment,
113112
'should support a doctype (1)'
114113
)
115114

116115
assert.equal(
117-
// @ts-expect-error: Types are out of date, `name` is no longer used.
118116
renderToStaticMarkup(toJsxRuntime({type: 'doctype'}, production)),
119117
'',
120118
'should support a doctype (2)'
@@ -262,7 +260,7 @@ test('properties', () => {
262260
() => {
263261
toJsxRuntime(h('div', {style: 'color:red; /*'}), production)
264262
},
265-
/^1:1-1:1: Cannot parse style attribute: End of comment missing$/,
263+
/Cannot parse `style` attribute/,
266264
'should crash on invalid style strings (default)'
267265
)
268266

@@ -279,7 +277,7 @@ test('properties', () => {
279277
production
280278
)
281279
},
282-
/^3:2-3:123: Cannot parse style attribute: End of comment missing$/,
280+
/^3:2-3:123: Cannot parse `style` attribute/,
283281
'Cannot parse style attribute: End of comment missing'
284282
)
285283

@@ -295,7 +293,7 @@ test('properties', () => {
295293
{...production, filePath: 'example.html'}
296294
)
297295
},
298-
/^1:1-1:1: Cannot parse style attribute: End of comment missing$/,
296+
/^1:1: Cannot parse `style` attribut/,
299297
'should crash on invalid style strings (w/ file path, w/o positional info)'
300298
)
301299

@@ -312,7 +310,7 @@ test('properties', () => {
312310
{...production, filePath: 'example.html'}
313311
)
314312
},
315-
/^3:2-3:123: Cannot parse style attribute: End of comment missing$/,
313+
/^3:2-3:123: Cannot parse `style` attribute/,
316314
'should crash on invalid style strings (w/ file path, w/ positional info)'
317315
)
318316

0 commit comments

Comments
 (0)