Skip to content
Merged
115 changes: 93 additions & 22 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const regex = /@(jsx|jsxFrag|jsxImportSource|jsxRuntime)\s+(\S+)/g
* @typedef {import('estree-jsx').Comment} Comment
* @typedef {import('estree-jsx').Expression} Expression
* @typedef {import('estree-jsx').Pattern} Pattern
* @typedef {import('estree-jsx').ObjectExpression} ObjectExpression
* @typedef {import('estree-jsx').Property} Property
* @typedef {import('estree-jsx').ImportSpecifier} ImportSpecifier
* @typedef {import('estree-jsx').SpreadElement} SpreadElement
Expand Down Expand Up @@ -35,6 +36,8 @@ const regex = /@(jsx|jsxFrag|jsxImportSource|jsxRuntime)\s+(\S+)/g
* @property {string} [importSource='react']
* @property {string} [pragma='React.createElement']
* @property {string} [pragmaFrag='React.Fragment']
* @property {boolean} [development=false]
* @property {string} [filePath]
*/

/**
Expand All @@ -55,7 +58,7 @@ export function buildJsx(tree, options = {}) {
let automatic = options.runtime === 'automatic'
/** @type {Annotations} */
const annotations = {}
/** @type {{fragment?: boolean, jsx?: boolean, jsxs?: boolean}} */
/** @type {{fragment?: boolean, jsx?: boolean, jsxs?: boolean, jsxDEV?: boolean}} */
const imports = {}

walk(tree, {
Expand Down Expand Up @@ -139,6 +142,14 @@ export function buildJsx(tree, options = {}) {
})
}

if (imports.jsxDEV) {
specifiers.push({
type: 'ImportSpecifier',
imported: {type: 'Identifier', name: 'jsxDEV'},
local: {type: 'Identifier', name: '_jsxDEV'}
})
}

if (specifiers.length > 0) {
node.body.unshift({
type: 'ImportDeclaration',
Expand All @@ -148,7 +159,8 @@ export function buildJsx(tree, options = {}) {
value:
(annotations.jsxImportSource ||
options.importSource ||
'react') + '/jsx-runtime'
'react') +
(options.development ? '/jsx-dev-runtime' : '/jsx-runtime')
}
})
}
Expand Down Expand Up @@ -267,19 +279,21 @@ export function buildJsx(tree, options = {}) {
)
}

if (automatic && children.length > 0) {
fields.push({
type: 'Property',
key: {type: 'Identifier', name: 'children'},
value:
children.length > 1
? {type: 'ArrayExpression', elements: children}
: children[0],
kind: 'init',
method: false,
shorthand: false,
computed: false
})
if (automatic) {
if (children.length > 0) {
fields.push({
type: 'Property',
key: {type: 'Identifier', name: 'children'},
value:
children.length > 1
? {type: 'ArrayExpression', elements: children}
: children[0],
kind: 'init',
method: false,
shorthand: false,
computed: false
})
}
} else {
parameters = children
}
Expand Down Expand Up @@ -310,19 +324,76 @@ export function buildJsx(tree, options = {}) {
}

if (automatic) {
if (children.length > 1) {
parameters.push(props || {type: 'ObjectExpression', properties: []})

if (key) {
parameters.push(key)
} else if (options.development) {
parameters.push({
type: 'UnaryExpression',
operator: 'void',
prefix: true,
argument: {type: 'Literal', value: 0}
})
}

const isStaticChildren = children.length > 1
if (options.development) {
imports.jsxDEV = true
callee = {
type: 'Identifier',
name: '_jsxDEV'
}
parameters.push({type: 'Literal', value: isStaticChildren})
if (options.filePath) {
/** @type {ObjectExpression} */
const source = {
type: 'ObjectExpression',
properties: [
{
type: 'Property',
method: false,
shorthand: false,
computed: false,
kind: 'init',
key: {type: 'Identifier', name: 'fileName'},
value: {type: 'Literal', value: options.filePath}
}
]
}
if (node.loc?.start.line !== undefined) {
source.properties.push({
type: 'Property',
method: false,
shorthand: false,
computed: false,
kind: 'init',
key: {type: 'Identifier', name: 'lineNumber'},
value: {type: 'Literal', value: node.loc.start.line}
})
}

if (node.loc?.start.column !== undefined) {
source.properties.push({
type: 'Property',
method: false,
shorthand: false,
computed: false,
kind: 'init',
key: {type: 'Identifier', name: 'columnNumber'},
value: {type: 'Literal', value: node.loc.start.column + 1}
})
}

parameters.push(source)
}
} else if (isStaticChildren) {
imports.jsxs = true
callee = {type: 'Identifier', name: '_jsxs'}
} else {
imports.jsx = true
callee = {type: 'Identifier', name: '_jsx'}
}

parameters.push(props || {type: 'ObjectExpression', properties: []})

if (key) {
parameters.push(key)
}
}
// Classic.
else {
Expand Down
189 changes: 189 additions & 0 deletions test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1232,6 +1232,195 @@ test('estree-util-build-jsx', (t) => {
'should support the automatic runtime (key, no props, no children)'
)

t.deepEqual(
generate(
buildJsx(parse('<>a</>', false), {
runtime: 'automatic',
development: true,
filePath: 'index.js'
})
),
[
'import {Fragment as _Fragment, jsxDEV as _jsxDEV} from "react/jsx-dev-runtime";',
'_jsxDEV(_Fragment, {',
' children: "a"',
'}, void 0, false, {',
' fileName: "index.js",',
' lineNumber: 1,',
' columnNumber: 1',
'});',
''
].join('\n'),
'should support the automatic runtime (fragment, jsx, settings, development)'
)

t.deepEqual(
generate(
buildJsx(parse('<a key="a">b{1}</a>', false), {
runtime: 'automatic',
development: true,
filePath: 'index.js'
})
),
[
'import {jsxDEV as _jsxDEV} from "react/jsx-dev-runtime";',
'_jsxDEV("a", {',
' children: ["b", 1]',
'}, "a", true, {',
' fileName: "index.js",',
' lineNumber: 1,',
' columnNumber: 1',
'});',
''
].join('\n'),
'should support the automatic runtime (jsxs, key, comment, development)'
)

t.deepEqual(
generate(
buildJsx(parse('<a b="1" {...c}>d</a>', false), {
runtime: 'automatic',
development: true,
filePath: 'index.js'
})
),
[
'import {jsxDEV as _jsxDEV} from "react/jsx-dev-runtime";',
'_jsxDEV("a", Object.assign({',
' b: "1"',
'}, c, {',
' children: "d"',
'}), void 0, false, {',
' fileName: "index.js",',
' lineNumber: 1,',
' columnNumber: 1',
'});',
''
].join('\n'),
'should support the automatic runtime (props, spread, children, development)'
)

t.deepEqual(
generate(
buildJsx(parse('<a {...{b: 1, c: 2}} d="e">f</a>', false), {
runtime: 'automatic',
development: true,
filePath: 'index.js'
})
),
[
'import {jsxDEV as _jsxDEV} from "react/jsx-dev-runtime";',
'_jsxDEV("a", Object.assign({',
' b: 1,',
' c: 2',
'}, {',
' d: "e",',
' children: "f"',
'}), void 0, false, {',
' fileName: "index.js",',
' lineNumber: 1,',
' columnNumber: 1',
'});',
''
].join('\n'),
'should support the automatic runtime (spread, props, children, development)'
)

t.deepEqual(
generate(
buildJsx(parse('<a>b</a>', false), {
runtime: 'automatic',
development: true,
filePath: 'index.js'
})
),
[
'import {jsxDEV as _jsxDEV} from "react/jsx-dev-runtime";',
'_jsxDEV("a", {',
' children: "b"',
'}, void 0, false, {',
' fileName: "index.js",',
' lineNumber: 1,',
' columnNumber: 1',
'});',
''
].join('\n'),
'should support the automatic runtime (no props, children, development)'
)

t.deepEqual(
generate(
buildJsx(parse('<a/>', false), {
runtime: 'automatic',
development: true,
filePath: 'index.js'
})
),
[
'import {jsxDEV as _jsxDEV} from "react/jsx-dev-runtime";',
'_jsxDEV("a", {}, void 0, false, {',
' fileName: "index.js",',
' lineNumber: 1,',
' columnNumber: 1',
'});',
''
].join('\n'),
'should support the automatic runtime (no props, no children, development)'
)

t.deepEqual(
generate(
buildJsx(parse('<a key/>', false), {
runtime: 'automatic',
development: true,
filePath: 'index.js'
})
),
[
'import {jsxDEV as _jsxDEV} from "react/jsx-dev-runtime";',
'_jsxDEV("a", {}, true, false, {',
' fileName: "index.js",',
' lineNumber: 1,',
' columnNumber: 1',
'});',
''
].join('\n'),
'should support the automatic runtime (key, no props, no children, development)'
)

t.deepEqual(
generate(
buildJsx(parse('<a />', false), {
runtime: 'automatic',
development: true
})
),
[
'import {jsxDEV as _jsxDEV} from "react/jsx-dev-runtime";',
'_jsxDEV("a", {}, void 0, false);',
''
].join('\n'),
'should support the automatic runtime (no props, no children, development, no filePath)'
)

t.deepEqual(
generate(
buildJsx(parse('<a />'), {
runtime: 'automatic',
development: true,
filePath: 'index.js'
})
),
[
'import {jsxDEV as _jsxDEV} from "react/jsx-dev-runtime";',
'_jsxDEV("a", {}, void 0, false, {',
' fileName: "index.js"',
'});',
''
].join('\n'),
'should support the automatic runtime (no props, no children, development, no locations)'
)

t.throws(
() => {
buildJsx(parse('<a {...b} key/>'), {runtime: 'automatic'})
Expand Down