/** * @import {Comment, Expression, Program, Node} from 'estree-jsx' */ import assert from 'node:assert/strict' import test from 'node:test' import {Parser} from 'acorn' import jsx from 'acorn-jsx' import {generate} from 'astring' import {buildJsx} from 'estree-util-build-jsx' import {walk} from 'estree-walker' const parser = Parser.extend(jsx()) test('estree-util-build-jsx', async function (t) { await t.test('should expose the public api', async function () { assert.deepEqual( Object.keys(await import('estree-util-build-jsx')).sort(), ['buildJsx'] ) }) await t.test( 'should default to `React.createElement` / `React.Fragment`', function () { const tree = parse('<>') buildJsx(tree) assert.deepEqual(expression(tree), { type: 'CallExpression', callee: { type: 'MemberExpression', object: {type: 'Identifier', name: 'React'}, property: {type: 'Identifier', name: 'createElement'}, computed: false, optional: false }, arguments: [ { type: 'MemberExpression', object: {type: 'Identifier', name: 'React'}, property: {type: 'Identifier', name: 'Fragment'}, computed: false, optional: false }, {type: 'Literal', value: null}, { type: 'CallExpression', callee: { type: 'MemberExpression', object: {type: 'Identifier', name: 'React'}, property: {type: 'Identifier', name: 'createElement'}, computed: false, optional: false }, arguments: [{type: 'Literal', value: 'x'}], optional: false } ], optional: false }) } ) await t.test('should support `pragma`, `pragmaFrag`', function () { const tree = parse('<>') buildJsx(tree, {pragma: 'a', pragmaFrag: 'b'}) assert.deepEqual(expression(tree), { type: 'CallExpression', callee: {type: 'Identifier', name: 'a'}, arguments: [ {type: 'Identifier', name: 'b'}, {type: 'Literal', value: null}, { type: 'CallExpression', callee: {type: 'Identifier', name: 'a'}, arguments: [{type: 'Literal', value: 'x'}], optional: false } ], optional: false }) }) await t.test('should support `pragma` w/ non-identifiers (1)', function () { const tree = parse('') buildJsx(tree, {pragma: 'a.b-c'}) assert.deepEqual(expression(tree), { type: 'CallExpression', callee: { type: 'MemberExpression', object: {type: 'Identifier', name: 'a'}, property: {type: 'Literal', value: 'b-c'}, computed: true, optional: false }, arguments: [{type: 'Literal', value: 'x'}], optional: false }) assert.equal(generate(tree), 'a["b-c"]("x");\n') }) await t.test('should support `@jsx`, `@jsxFrag` comments', function () { const tree = parse('/* @jsx a @jsxFrag b */\n<>') buildJsx(tree) assert.deepEqual(expression(tree), { type: 'CallExpression', callee: {type: 'Identifier', name: 'a'}, arguments: [ {type: 'Identifier', name: 'b'}, {type: 'Literal', value: null}, { type: 'CallExpression', callee: {type: 'Identifier', name: 'a'}, arguments: [{type: 'Literal', value: 'x'}], optional: false } ], optional: false }) }) await t.test( 'should throw when `@jsx` is set in the automatic runtime', function () { assert.throws(function () { buildJsx(parse('/* @jsx a @jsxRuntime automatic */')) }, /Unexpected `@jsx` pragma w\/ automatic runtime/) } ) await t.test( 'should throw when `@jsxFrag` is set in the automatic runtime', function () { assert.throws(function () { buildJsx(parse('/* @jsxFrag a @jsxRuntime automatic */')) }, /Unexpected `@jsxFrag` pragma w\/ automatic runtime/) } ) await t.test( 'should throw when `@jsxImportSource` is set in the classic runtime', function () { assert.throws(function () { buildJsx(parse('/* @jsxImportSource a @jsxRuntime classic */')) }, /Unexpected `@jsxImportSource` w\/ classic runtime/) } ) await t.test( 'should throw on a non-automatic nor classic `@jsxRuntime`', function () { assert.throws(function () { buildJsx(parse('/* @jsxRuntime a */')) }, /Unexpected `jsxRuntime` `a`, expected `automatic` or `classic`/) } ) await t.test('should ignore other comments', function () { const tree = parse('// a\n<>') buildJsx(tree) assert.deepEqual(expression(tree), { type: 'CallExpression', callee: { type: 'MemberExpression', object: {type: 'Identifier', name: 'React'}, property: {type: 'Identifier', name: 'createElement'}, computed: false, optional: false }, arguments: [ { type: 'MemberExpression', object: {type: 'Identifier', name: 'React'}, property: {type: 'Identifier', name: 'Fragment'}, computed: false, optional: false }, {type: 'Literal', value: null}, { type: 'CallExpression', callee: { type: 'MemberExpression', object: {type: 'Identifier', name: 'React'}, property: {type: 'Identifier', name: 'createElement'}, computed: false, optional: false }, arguments: [{type: 'Literal', value: 'x'}], optional: false } ], optional: false }) }) await t.test('should support a self-closing element', function () { const tree = parse('') buildJsx(tree, {pragma: 'h'}) assert.deepEqual(expression(tree), { type: 'CallExpression', callee: {type: 'Identifier', name: 'h'}, arguments: [{type: 'Literal', value: 'a'}], optional: false }) }) await t.test('should support a closed element', function () { const tree = parse('b') buildJsx(tree, {pragma: 'h'}) assert.deepEqual(expression(tree), { type: 'CallExpression', callee: {type: 'Identifier', name: 'h'}, arguments: [ {type: 'Literal', value: 'a'}, {type: 'Literal', value: null}, {type: 'Literal', value: 'b'} ], optional: false }) }) await t.test( 'should support dots in a tag name for member expressions', function () { const tree = parse('') buildJsx(tree, {pragma: 'h'}) assert.deepEqual(expression(tree), { type: 'CallExpression', callee: {type: 'Identifier', name: 'h'}, arguments: [ { type: 'MemberExpression', object: {type: 'Identifier', name: 'a'}, property: {type: 'Identifier', name: 'b'}, computed: false, optional: false } ], optional: false }) } ) await t.test( 'should support dots *and* dashes in tag names (1)', function () { const tree = parse('') buildJsx(tree, {pragma: 'h'}) assert.deepEqual(expression(tree), { type: 'CallExpression', callee: {type: 'Identifier', name: 'h'}, arguments: [ { type: 'MemberExpression', object: {type: 'Identifier', name: 'a'}, property: {type: 'Literal', value: 'b-c'}, computed: true, optional: false } ], optional: false }) assert.equal(generate(tree), 'h(a["b-c"]);\n') } ) await t.test( 'should support dots *and* dashes in tag names (2)', function () { const tree = parse('') buildJsx(tree, {pragma: 'h'}) assert.deepEqual(expression(tree), { type: 'CallExpression', callee: {type: 'Identifier', name: 'h'}, arguments: [ { type: 'MemberExpression', object: {type: 'Literal', value: 'a-b'}, property: {type: 'Identifier', name: 'c'}, computed: false, optional: false } ], optional: false }) assert.equal(generate(tree), 'h(("a-b").c);\n') } ) await t.test( 'should support dots in a tag name for member expressions (2)', function () { const tree = parse('') buildJsx(tree, {pragma: 'h'}) assert.deepEqual(expression(tree), { type: 'CallExpression', callee: {type: 'Identifier', name: 'h'}, arguments: [ { type: 'MemberExpression', object: { type: 'MemberExpression', object: { type: 'MemberExpression', object: {type: 'Identifier', name: 'a'}, property: {type: 'Identifier', name: 'b'}, computed: false, optional: false }, property: {type: 'Identifier', name: 'c'}, computed: false, optional: false }, property: {type: 'Identifier', name: 'd'}, computed: false, optional: false } ], optional: false }) } ) await t.test( 'should support colons in a tag name for namespaces', function () { const tree = parse('') buildJsx(tree, {pragma: 'h'}) assert.deepEqual(expression(tree), { type: 'CallExpression', callee: {type: 'Identifier', name: 'h'}, arguments: [{type: 'Literal', value: 'a:b'}], optional: false }) } ) await t.test('should support dashes in tag names', function () { const tree = parse('') buildJsx(tree, {pragma: 'h'}) assert.deepEqual(expression(tree), { type: 'CallExpression', callee: {type: 'Identifier', name: 'h'}, arguments: [{type: 'Literal', value: 'a-b'}], optional: false }) }) await t.test('should non-lowercase for components in tag names', function () { const tree = parse('') buildJsx(tree, {pragma: 'h'}) assert.deepEqual(expression(tree), { type: 'CallExpression', callee: {type: 'Identifier', name: 'h'}, arguments: [{type: 'Identifier', name: 'A'}], optional: false }) }) await t.test('should support a boolean prop', function () { const tree = parse('') buildJsx(tree, {pragma: 'h'}) assert.deepEqual(expression(tree), { type: 'CallExpression', callee: {type: 'Identifier', name: 'h'}, arguments: [ {type: 'Literal', value: 'a'}, { type: 'ObjectExpression', properties: [ { type: 'Property', key: {type: 'Identifier', name: 'b'}, value: {type: 'Literal', value: true}, kind: 'init', method: false, shorthand: false, computed: false } ] } ], optional: false }) }) await t.test('should support colons in prop names', function () { const tree = parse('') buildJsx(tree, {pragma: 'h'}) assert.deepEqual(expression(tree), { type: 'CallExpression', callee: {type: 'Identifier', name: 'h'}, arguments: [ {type: 'Literal', value: 'a'}, { type: 'ObjectExpression', properties: [ { type: 'Property', key: {type: 'Literal', value: 'b:c'}, value: {type: 'Literal', value: true}, kind: 'init', method: false, shorthand: false, computed: false } ] } ], optional: false }) }) await t.test( 'should support a prop name that can’t be an identifier', function () { const tree = parse('') buildJsx(tree, {pragma: 'h'}) assert.deepEqual(expression(tree), { type: 'CallExpression', callee: {type: 'Identifier', name: 'h'}, arguments: [ {type: 'Literal', value: 'a'}, { type: 'ObjectExpression', properties: [ { type: 'Property', key: {type: 'Literal', value: 'b-c'}, value: {type: 'Literal', value: true}, kind: 'init', method: false, shorthand: false, computed: false } ] } ], optional: false }) } ) await t.test('should support a prop value', function () { const tree = parse('') buildJsx(tree, {pragma: 'h'}) assert.deepEqual(expression(tree), { type: 'CallExpression', callee: {type: 'Identifier', name: 'h'}, arguments: [ {type: 'Literal', value: 'a'}, { type: 'ObjectExpression', properties: [ { type: 'Property', key: {type: 'Identifier', name: 'b'}, value: {type: 'Literal', value: 'c'}, kind: 'init', method: false, shorthand: false, computed: false } ] } ], optional: false }) }) await t.test('should support an expression as a prop value', function () { const tree = parse('') buildJsx(tree, {pragma: 'h'}) assert.deepEqual(expression(tree), { type: 'CallExpression', callee: {type: 'Identifier', name: 'h'}, arguments: [ {type: 'Literal', value: 'a'}, { type: 'ObjectExpression', properties: [ { type: 'Property', key: {type: 'Identifier', name: 'b'}, value: {type: 'Identifier', name: 'c'}, kind: 'init', method: false, shorthand: false, computed: false } ] } ], optional: false }) }) await t.test('should support an expression as a prop value (2)', function () { const tree = parse('') buildJsx(tree, {pragma: 'h'}) assert.deepEqual(expression(tree), { type: 'CallExpression', callee: {type: 'Identifier', name: 'h'}, arguments: [ {type: 'Literal', value: 'a'}, { type: 'ObjectExpression', properties: [ { type: 'Property', key: {type: 'Identifier', name: 'b'}, value: {type: 'Literal', value: 1}, kind: 'init', method: false, shorthand: false, computed: false } ] } ], optional: false }) }) await t.test('should support a fragment as a prop value', function () { const tree = parse('c />') buildJsx(tree, {pragma: 'h', pragmaFrag: 'f'}) assert.deepEqual(expression(tree), { type: 'CallExpression', callee: {type: 'Identifier', name: 'h'}, arguments: [ {type: 'Literal', value: 'a'}, { type: 'ObjectExpression', properties: [ { type: 'Property', key: {type: 'Identifier', name: 'b'}, value: { type: 'CallExpression', callee: {type: 'Identifier', name: 'h'}, arguments: [ {type: 'Identifier', name: 'f'}, {type: 'Literal', value: null}, {type: 'Literal', value: 'c'} ], optional: false }, kind: 'init', method: false, shorthand: false, computed: false } ] } ], optional: false }) }) await t.test('should support an element as a prop value', function () { const tree = parse(' />') buildJsx(tree, {pragma: 'h'}) assert.deepEqual(expression(tree), { type: 'CallExpression', callee: {type: 'Identifier', name: 'h'}, arguments: [ {type: 'Literal', value: 'a'}, { type: 'ObjectExpression', properties: [ { type: 'Property', key: {type: 'Identifier', name: 'b'}, value: { type: 'CallExpression', callee: {type: 'Identifier', name: 'h'}, arguments: [{type: 'Literal', value: 'c'}], optional: false }, kind: 'init', method: false, shorthand: false, computed: false } ] } ], optional: false }) }) await t.test('should support a single spread prop', function () { const tree = parse('') buildJsx(tree, {pragma: 'h'}) assert.deepEqual(expression(tree), { type: 'CallExpression', callee: {type: 'Identifier', name: 'h'}, arguments: [ {type: 'Literal', value: 'a'}, { type: 'ObjectExpression', properties: [ {type: 'SpreadElement', argument: {type: 'Identifier', name: 'b'}} ] } ], optional: false }) }) await t.test('should support a spread prop and another prop', function () { const tree = parse('') buildJsx(tree, {pragma: 'h'}) assert.deepEqual(expression(tree), { type: 'CallExpression', callee: {type: 'Identifier', name: 'h'}, arguments: [ {type: 'Literal', value: 'a'}, { type: 'ObjectExpression', properties: [ { type: 'SpreadElement', argument: {type: 'Identifier', name: 'b'} }, { type: 'Property', key: {type: 'Identifier', name: 'c'}, value: {type: 'Literal', value: true}, kind: 'init', method: false, shorthand: false, computed: false } ] } ], optional: false }) }) await t.test('should support a prop and a spread prop', function () { const tree = parse('') buildJsx(tree, {pragma: 'h'}) assert.deepEqual(expression(tree), { type: 'CallExpression', callee: {type: 'Identifier', name: 'h'}, arguments: [ {type: 'Literal', value: 'a'}, { type: 'ObjectExpression', properties: [ { type: 'Property', key: {type: 'Identifier', name: 'b'}, value: {type: 'Literal', value: true}, kind: 'init', method: false, shorthand: false, computed: false }, {type: 'SpreadElement', argument: {type: 'Identifier', name: 'c'}} ] } ], optional: false }) }) await t.test('should support two spread props', function () { const tree = parse('') buildJsx(tree, {pragma: 'h'}) assert.deepEqual(expression(tree), { type: 'CallExpression', callee: {type: 'Identifier', name: 'h'}, arguments: [ {type: 'Literal', value: 'a'}, { type: 'ObjectExpression', properties: [ { type: 'SpreadElement', argument: {type: 'Identifier', name: 'b'} }, { type: 'SpreadElement', argument: {type: 'Identifier', name: 'c'} } ] } ], optional: false }) }) await t.test('should support more complex spreads', function () { const tree = parse('') buildJsx(tree, {pragma: 'h'}) assert.deepEqual(expression(tree), { type: 'CallExpression', callee: {type: 'Identifier', name: 'h'}, arguments: [ {type: 'Literal', value: 'a'}, { type: 'ObjectExpression', properties: [ { type: 'Property', method: false, shorthand: false, computed: false, key: {type: 'Identifier', name: 'b'}, value: {type: 'Literal', value: 1}, kind: 'init' }, { type: 'SpreadElement', argument: {type: 'Identifier', name: 'c'} }, { type: 'Property', method: false, shorthand: false, computed: false, key: {type: 'Identifier', name: 'd'}, value: {type: 'Literal', value: 2}, kind: 'init' } ] } ], optional: false }) }) await t.test('should support expressions content', function () { const tree = parse('{1}') buildJsx(tree, {pragma: 'h'}) assert.deepEqual(expression(tree), { type: 'CallExpression', callee: {type: 'Identifier', name: 'h'}, arguments: [ {type: 'Literal', value: 'a'}, {type: 'Literal', value: null}, {type: 'Literal', value: 1} ], optional: false }) }) await t.test('should support empty expressions content', function () { const tree = parse('{}') buildJsx(tree, {pragma: 'h'}) assert.deepEqual(expression(tree), { type: 'CallExpression', callee: {type: 'Identifier', name: 'h'}, arguments: [{type: 'Literal', value: 'a'}], optional: false }) }) await t.test('should support initial spaces in content', function () { const tree = parse(' b') buildJsx(tree, {pragma: 'h'}) assert.deepEqual(expression(tree), { type: 'CallExpression', callee: {type: 'Identifier', name: 'h'}, arguments: [ {type: 'Literal', value: 'a'}, {type: 'Literal', value: null}, {type: 'Literal', value: ' b'} ], optional: false }) }) await t.test('should support final spaces in content', function () { const tree = parse('b ') buildJsx(tree, {pragma: 'h'}) assert.deepEqual(expression(tree), { type: 'CallExpression', callee: {type: 'Identifier', name: 'h'}, arguments: [ {type: 'Literal', value: 'a'}, {type: 'Literal', value: null}, {type: 'Literal', value: 'b '} ], optional: false }) }) await t.test( 'should support initial and final spaces in content', function () { const tree = parse(' b ') buildJsx(tree, {pragma: 'h'}) assert.deepEqual(expression(tree), { type: 'CallExpression', callee: {type: 'Identifier', name: 'h'}, arguments: [ {type: 'Literal', value: 'a'}, {type: 'Literal', value: null}, {type: 'Literal', value: ' b '} ], optional: false }) } ) await t.test('should support spaces around line endings', function () { const tree = parse(' b \r c \n d \n ') buildJsx(tree, {pragma: 'h'}) assert.deepEqual(expression(tree), { type: 'CallExpression', callee: {type: 'Identifier', name: 'h'}, arguments: [ {type: 'Literal', value: 'a'}, {type: 'Literal', value: null}, {type: 'Literal', value: ' b c d'} ], optional: false }) }) await t.test( 'should support skip empty or whitespace only line endings', function () { const tree = parse(' b \r \n c \n\n d \n ') buildJsx(tree, {pragma: 'h'}) assert.deepEqual(expression(tree), { type: 'CallExpression', callee: {type: 'Identifier', name: 'h'}, arguments: [ {type: 'Literal', value: 'a'}, {type: 'Literal', value: null}, {type: 'Literal', value: ' b c d'} ], optional: false }) } ) await t.test('should support skip whitespace only content', function () { const tree = parse(' \t\n ') buildJsx(tree, {pragma: 'h'}) assert.deepEqual(expression(tree), { type: 'CallExpression', callee: {type: 'Identifier', name: 'h'}, arguments: [{type: 'Literal', value: 'a'}], optional: false }) }) await t.test('should trim strings with leading line feed', function () { const tree = parse('\n line1\n') buildJsx(tree, {pragma: 'h'}) assert.deepEqual(expression(tree), { type: 'CallExpression', callee: {type: 'Identifier', name: 'h'}, arguments: [ {type: 'Literal', value: 'a'}, {type: 'Literal', value: null}, {type: 'Literal', value: 'line1'} ], optional: false }) }) await t.test( 'should trim strings with leading line feed (multiline test)', function () { const tree = parse('\n line1{" "}\n line2\n') buildJsx(tree, {pragma: 'h'}) assert.deepEqual(expression(tree), { type: 'CallExpression', callee: {type: 'Identifier', name: 'h'}, arguments: [ {type: 'Literal', value: 'a'}, {type: 'Literal', value: null}, {type: 'Literal', value: 'line1'}, {type: 'Literal', value: ' '}, {type: 'Literal', value: 'line2'} ], optional: false }) } ) await t.test('should integrate w/ generators (`astring`)', function () { const tree = parse('<>\n h\n') buildJsx(tree, {pragma: 'h', pragmaFrag: 'f'}) assert.deepEqual( generate(tree), 'h(f, null, h("a", {\n b: true,\n c: "d",\n e: f,\n ...g\n}, "h"));\n' ) }) await t.test('should support positional info', function () { const tree = parse('<>\n h\n', false) buildJsx(tree) assert.deepEqual(tree, { type: 'Program', start: 0, end: 38, loc: {start: {line: 1, column: 0}, end: {line: 3, column: 3}}, range: [0, 38], body: [ { type: 'ExpressionStatement', start: 0, end: 38, loc: {start: {line: 1, column: 0}, end: {line: 3, column: 3}}, range: [0, 38], expression: { type: 'CallExpression', callee: { type: 'MemberExpression', object: {type: 'Identifier', name: 'React'}, property: {type: 'Identifier', name: 'createElement'}, computed: false, optional: false }, arguments: [ { type: 'MemberExpression', object: {type: 'Identifier', name: 'React'}, property: {type: 'Identifier', name: 'Fragment'}, computed: false, optional: false }, {type: 'Literal', value: null}, { type: 'CallExpression', callee: { type: 'MemberExpression', object: {type: 'Identifier', name: 'React'}, property: {type: 'Identifier', name: 'createElement'}, computed: false, optional: false }, arguments: [ { type: 'Literal', value: 'a', start: 6, end: 7, loc: { start: {line: 2, column: 3}, end: {line: 2, column: 4} }, range: [6, 7] }, { type: 'ObjectExpression', properties: [ { type: 'Property', key: { type: 'Identifier', name: 'b', start: 8, end: 9, loc: { start: {line: 2, column: 5}, end: {line: 2, column: 6} }, range: [8, 9] }, value: {type: 'Literal', value: true}, kind: 'init', method: false, shorthand: false, computed: false, start: 8, end: 9, loc: { start: {line: 2, column: 5}, end: {line: 2, column: 6} }, range: [8, 9] }, { type: 'Property', key: { type: 'Identifier', name: 'c', start: 10, end: 11, loc: { start: {line: 2, column: 7}, end: {line: 2, column: 8} }, range: [10, 11] }, value: { type: 'Literal', start: 12, end: 15, loc: { start: {line: 2, column: 9}, end: {line: 2, column: 12} }, range: [12, 15], value: 'd' }, kind: 'init', method: false, shorthand: false, computed: false, start: 10, end: 15, loc: { start: {line: 2, column: 7}, end: {line: 2, column: 12} }, range: [10, 15] }, { type: 'Property', key: { type: 'Identifier', name: 'e', start: 16, end: 17, loc: { start: {line: 2, column: 13}, end: {line: 2, column: 14} }, range: [16, 17] }, value: { type: 'Identifier', start: 19, end: 20, loc: { start: {line: 2, column: 16}, end: {line: 2, column: 17} }, range: [19, 20], name: 'f' }, kind: 'init', method: false, shorthand: false, computed: false, start: 16, end: 21, loc: { start: {line: 2, column: 13}, end: {line: 2, column: 18} }, range: [16, 21] }, { type: 'SpreadElement', argument: { type: 'Identifier', start: 26, end: 27, loc: { start: {line: 2, column: 23}, end: {line: 2, column: 24} }, range: [26, 27], name: 'g' } } ] }, { type: 'Literal', value: 'h', start: 29, end: 30, loc: { start: {line: 2, column: 26}, end: {line: 2, column: 27} }, range: [29, 30] } ], optional: false, start: 5, end: 34, loc: { start: {line: 2, column: 2}, end: {line: 2, column: 31} }, range: [5, 34] } ], optional: false, start: 0, end: 38, loc: {start: {line: 1, column: 0}, end: {line: 3, column: 3}}, range: [0, 38] } } ], sourceType: 'script', comments: [] }) }) await t.test('should support no comments on `program`', function () { const tree = parse('<>', true, false) buildJsx(tree) assert.deepEqual(tree, { type: 'Program', body: [ { type: 'ExpressionStatement', expression: { type: 'CallExpression', callee: { type: 'MemberExpression', object: {type: 'Identifier', name: 'React'}, property: {type: 'Identifier', name: 'createElement'}, computed: false, optional: false }, arguments: [ { type: 'MemberExpression', object: {type: 'Identifier', name: 'React'}, property: {type: 'Identifier', name: 'Fragment'}, computed: false, optional: false }, {type: 'Literal', value: null}, { type: 'CallExpression', callee: { type: 'MemberExpression', object: {type: 'Identifier', name: 'React'}, property: {type: 'Identifier', name: 'createElement'}, computed: false, optional: false }, arguments: [{type: 'Literal', value: 'x'}], optional: false } ], optional: false } } ], sourceType: 'script' }) }) await t.test( 'should support the automatic runtime (fragment, jsx, settings)', function () { const tree = parse('<>a') buildJsx(tree, {runtime: 'automatic'}) assert.equal( generate(tree), [ 'import {Fragment as _Fragment, jsx as _jsx} from "react/jsx-runtime";', '_jsx(_Fragment, {', ' children: "a"', '});', '' ].join('\n') ) } ) await t.test( 'should support the automatic runtime (jsxs, key, comment)', function () { const tree = parse('/*@jsxRuntime automatic*/\nb{1}') buildJsx(tree) assert.equal( generate(tree), [ 'import {jsxs as _jsxs} from "react/jsx-runtime";', '_jsxs("a", {', ' children: ["b", 1]', '}, "a");', '' ].join('\n') ) } ) await t.test( 'should support the automatic runtime (props, spread, children)', function () { const tree = parse('d') buildJsx(tree, {runtime: 'automatic'}) assert.equal( generate(tree), [ 'import {jsx as _jsx} from "react/jsx-runtime";', '_jsx("a", {', ' b: "1",', ' ...c,', ' children: "d"', '});', '' ].join('\n') ) } ) await t.test( 'should support the automatic runtime (spread, props, children)', function () { const tree = parse('f') buildJsx(tree, {runtime: 'automatic'}) assert.equal( generate(tree), [ 'import {jsx as _jsx} from "react/jsx-runtime";', '_jsx("a", {', ' b: 1,', ' c: 2,', ' d: "e",', ' children: "f"', '});', '' ].join('\n') ) } ) await t.test( 'should support the automatic runtime (no props, children)', function () { const tree = parse('b') buildJsx(tree, {runtime: 'automatic'}) assert.equal( generate(tree), [ 'import {jsx as _jsx} from "react/jsx-runtime";', '_jsx("a", {', ' children: "b"', '});', '' ].join('\n') ) } ) await t.test( 'should support the automatic runtime (no props, no children)', function () { const tree = parse('') buildJsx(tree, {runtime: 'automatic'}) assert.equal( generate(tree), [ 'import {jsx as _jsx} from "react/jsx-runtime";', '_jsx("a", {});', '' ].join('\n') ) } ) await t.test( 'should support the automatic runtime (key, no props, no children)', function () { const tree = parse('') buildJsx(tree, {runtime: 'automatic'}) assert.equal( generate(tree), [ 'import {jsx as _jsx} from "react/jsx-runtime";', '_jsx("a", {}, true);', '' ].join('\n') ) } ) await t.test( 'should support the automatic runtime (fragment, jsx, settings, development)', function () { const tree = parse('<>a', false) buildJsx(tree, { runtime: 'automatic', development: true, filePath: 'index.js' }) assert.equal( generate(tree), [ 'import {Fragment as _Fragment, jsxDEV as _jsxDEV} from "react/jsx-dev-runtime";', '_jsxDEV(_Fragment, {', ' children: "a"', '}, undefined, false, {', ' fileName: "index.js",', ' lineNumber: 1,', ' columnNumber: 1', '}, this);', '' ].join('\n') ) } ) await t.test( 'should support the automatic runtime (jsxs, key, comment, development)', function () { const tree = parse('b{1}', false) buildJsx(tree, { runtime: 'automatic', development: true, filePath: 'index.js' }) assert.equal( generate(tree), [ 'import {jsxDEV as _jsxDEV} from "react/jsx-dev-runtime";', '_jsxDEV("a", {', ' children: ["b", 1]', '}, "a", true, {', ' fileName: "index.js",', ' lineNumber: 1,', ' columnNumber: 1', '}, this);', '' ].join('\n') ) } ) await t.test( 'should support the automatic runtime (props, spread, children, development)', function () { const tree = parse('d', false) buildJsx(tree, { runtime: 'automatic', development: true, filePath: 'index.js' }) assert.equal( generate(tree), [ 'import {jsxDEV as _jsxDEV} from "react/jsx-dev-runtime";', '_jsxDEV("a", {', ' b: "1",', ' ...c,', ' children: "d"', '}, undefined, false, {', ' fileName: "index.js",', ' lineNumber: 1,', ' columnNumber: 1', '}, this);', '' ].join('\n') ) } ) await t.test( 'should support the automatic runtime (spread, props, children, development)', function () { const tree = parse('f', false) buildJsx(tree, { runtime: 'automatic', development: true, filePath: 'index.js' }) assert.equal( generate(tree), [ 'import {jsxDEV as _jsxDEV} from "react/jsx-dev-runtime";', '_jsxDEV("a", {', ' b: 1,', ' c: 2,', ' d: "e",', ' children: "f"', '}, undefined, false, {', ' fileName: "index.js",', ' lineNumber: 1,', ' columnNumber: 1', '}, this);', '' ].join('\n') ) } ) await t.test( 'should support the automatic runtime (no props, children, development)', function () { const tree = parse('b', false) buildJsx(tree, { runtime: 'automatic', development: true, filePath: 'index.js' }) assert.equal( generate(tree), [ 'import {jsxDEV as _jsxDEV} from "react/jsx-dev-runtime";', '_jsxDEV("a", {', ' children: "b"', '}, undefined, false, {', ' fileName: "index.js",', ' lineNumber: 1,', ' columnNumber: 1', '}, this);', '' ].join('\n') ) } ) await t.test( 'should support the automatic runtime (no props, no children, development)', function () { const tree = parse('', false) buildJsx(tree, { runtime: 'automatic', development: true, filePath: 'index.js' }) assert.equal( generate(tree), [ 'import {jsxDEV as _jsxDEV} from "react/jsx-dev-runtime";', '_jsxDEV("a", {}, undefined, false, {', ' fileName: "index.js",', ' lineNumber: 1,', ' columnNumber: 1', '}, this);', '' ].join('\n') ) } ) await t.test( 'should support the automatic runtime (key, no props, no children, development)', function () { const tree = parse('', false) buildJsx(tree, { runtime: 'automatic', development: true, filePath: 'index.js' }) assert.equal( generate(tree), [ 'import {jsxDEV as _jsxDEV} from "react/jsx-dev-runtime";', '_jsxDEV("a", {}, true, false, {', ' fileName: "index.js",', ' lineNumber: 1,', ' columnNumber: 1', '}, this);', '' ].join('\n') ) } ) await t.test( 'should support the automatic runtime (no props, no children, development, no filePath)', function () { const tree = parse('', false) buildJsx(tree, { runtime: 'automatic', development: true }) assert.equal( generate(tree), [ 'import {jsxDEV as _jsxDEV} from "react/jsx-dev-runtime";', '_jsxDEV("a", {}, undefined, false, {', ' fileName: "",', ' lineNumber: 1,', ' columnNumber: 1', '}, this);', '' ].join('\n') ) } ) await t.test( 'should support the automatic runtime (no props, no children, development, empty filePath)', function () { const tree = parse('', false) buildJsx(tree, { runtime: 'automatic', development: true, filePath: '' }) assert.equal( generate(tree), [ 'import {jsxDEV as _jsxDEV} from "react/jsx-dev-runtime";', '_jsxDEV("a", {}, undefined, false, {', ' fileName: "",', ' lineNumber: 1,', ' columnNumber: 1', '}, this);', '' ].join('\n') ) } ) await t.test( 'should support the automatic runtime (no props, no children, development, no locations)', function () { const tree = parse('') buildJsx(tree, { runtime: 'automatic', development: true, filePath: 'index.js' }) assert.equal( generate(tree), [ 'import {jsxDEV as _jsxDEV} from "react/jsx-dev-runtime";', '_jsxDEV("a", {}, undefined, false, {', ' fileName: "index.js"', '}, this);', '' ].join('\n') ) } ) await t.test( 'should support the automatic runtime (no props, nested children, development, positional info)', function () { const tree = parse('\n \n', false) buildJsx(tree, { runtime: 'automatic', development: true, filePath: 'index.js' }) assert.equal( generate(tree), [ 'import {jsxDEV as _jsxDEV} from "react/jsx-dev-runtime";', '_jsxDEV("a", {', ' children: _jsxDEV("b", {}, undefined, false, {', ' fileName: "index.js",', ' lineNumber: 2,', ' columnNumber: 3', ' }, this)', '}, undefined, false, {', ' fileName: "index.js",', ' lineNumber: 1,', ' columnNumber: 1', '}, this);', '' ].join('\n') ) } ) await t.test('should throw on spread after `key`', function () { assert.throws(function () { buildJsx(parse(''), {runtime: 'automatic'}) }, /Expected `key` to come before any spread expressions/) }) await t.test( 'should prefer a `jsxRuntime` comment over a `runtime` option', function () { const tree = parse('/*@jsxRuntime classic*/ ') buildJsx(tree, {runtime: 'automatic'}) assert.equal(generate(tree), 'React.createElement("a");\n') } ) await t.test('should keep directives first', function () { const tree = parse('"use client"\nconst x = ') buildJsx(tree, {runtime: 'automatic'}) assert.equal( generate(tree), '"use client";\nimport {jsx as _jsx} from "react/jsx-runtime";\nconst x = _jsx("a", {});\n' ) }) }) /** * @param {Program} program * @returns {Expression} */ function expression(program) { const head = program.body[0] if (!head || head.type !== 'ExpressionStatement') { throw new Error('Expected single expression') } return head.expression } /** * Parse a string of JS. * * @param {string} document * Value. * @param {boolean} [clean=true] * Clean positional info (default: `true`). * @param {boolean} [addComments=true] * Add comments (default: `true`). * @returns {Program} * ESTree program. */ function parse(document, clean, addComments) { /** @type {Array} */ const comments = [] const tree = /** @type {Program} */ ( parser.parse(document, { ecmaVersion: 'latest', ranges: true, locations: true, // @ts-expect-error: acorn is similar enough to estree. onComment: comments }) ) if (addComments !== false) tree.comments = comments if (clean !== false) walk(tree, {leave}) // eslint-disable-next-line unicorn/prefer-structured-clone -- JSON casting needed to remove class stuff. return JSON.parse(JSON.stringify(tree)) } /** * Clean a node. * * @param {Node} n * ESTree node. */ function leave(n) { delete n.loc delete n.range // @ts-expect-error: exists on acorn nodes. delete n.start // @ts-expect-error: exists on acorn nodes. delete n.end // @ts-expect-error: exists on acorn nodes. delete n.raw }