From c579bf00638ccc9b6dec524d3af967bce9903360 Mon Sep 17 00:00:00 2001 From: Remco Haszing Date: Thu, 15 Jun 2023 15:03:37 +0200 Subject: [PATCH 1/4] Use spread syntax for shallow clones --- lib/index.js | 53 +++----- test.js | 366 +++++++++++++++++++++++---------------------------- 2 files changed, 183 insertions(+), 236 deletions(-) diff --git a/lib/index.js b/lib/index.js index 36d9c52..d06c82a 100644 --- a/lib/index.js +++ b/lib/index.js @@ -281,11 +281,9 @@ export function buildJsx(tree, options) { /** @type {MemberExpression | Literal | Identifier} */ let name - /** @type {Array} */ - let fields = [] + /** @type {Array} */ + const properties = [] /** @type {Array} */ - const objects = [] - /** @type {Array} */ let parameters = [] /** @type {Expression | undefined} */ let key @@ -314,12 +312,16 @@ export function buildJsx(tree, options) { const attribute = attributes[index] if (attribute.type === 'JSXSpreadAttribute') { - if (fields.length > 0) { - objects.push({type: 'ObjectExpression', properties: fields}) - fields = [] + const {argument} = attribute + if (argument.type === 'ObjectExpression') { + properties.push(...argument.properties) + } else { + properties.push({ + type: 'SpreadElement', + argument + }) } - objects.push(attribute.argument) spread = true } else { const prop = toProperty(attribute) @@ -339,7 +341,7 @@ export function buildJsx(tree, options) { // attribute values? 🤷‍♂️ key = prop.value } else { - fields.push(prop) + properties.push(prop) } } } @@ -356,7 +358,7 @@ export function buildJsx(tree, options) { if (automatic) { if (children.length > 0) { - fields.push({ + properties.push({ type: 'Property', key: {type: 'Identifier', name: 'children'}, value: @@ -373,33 +375,11 @@ export function buildJsx(tree, options) { parameters = children } - if (fields.length > 0) { - objects.push({type: 'ObjectExpression', properties: fields}) - } - - /** @type {Expression | undefined} */ - let props /** @type {MemberExpression | Literal | Identifier} */ let callee - if (objects.length > 1) { - // Don’t mutate the first object, shallow clone instead. - if (objects[0].type !== 'ObjectExpression') { - objects.unshift({type: 'ObjectExpression', properties: []}) - } - - props = { - type: 'CallExpression', - callee: toMemberExpression('Object.assign'), - arguments: objects, - optional: false - } - } else if (objects.length > 0) { - props = objects[0] - } - if (automatic) { - parameters.push(props || {type: 'ObjectExpression', properties: []}) + parameters.push({type: 'ObjectExpression', properties}) if (key) { parameters.push(key) @@ -470,9 +450,10 @@ export function buildJsx(tree, options) { } // Classic. else { - // There are props or children. - if (props || parameters.length > 0) { - parameters.unshift(props || {type: 'Literal', value: null}) + if (properties.length > 0) { + parameters.unshift({type: 'ObjectExpression', properties}) + } else if (parameters.length > 0) { + parameters.unshift({type: 'Literal', value: null}) } callee = toMemberExpression( diff --git a/test.js b/test.js index 287dfba..12835c4 100644 --- a/test.js +++ b/test.js @@ -546,7 +546,12 @@ test('should support a single spread prop', () => { callee: {type: 'Identifier', name: 'h'}, arguments: [ {type: 'Literal', value: 'a'}, - {type: 'Identifier', name: 'b'} + { + type: 'ObjectExpression', + properties: [ + {type: 'SpreadElement', argument: {type: 'Identifier', name: 'b'}} + ] + } ], optional: false }) @@ -561,33 +566,22 @@ test('should support a spread prop and another prop', () => { arguments: [ {type: 'Literal', value: 'a'}, { - type: 'CallExpression', - callee: { - type: 'MemberExpression', - object: {type: 'Identifier', name: 'Object'}, - property: {type: 'Identifier', name: 'assign'}, - computed: false, - optional: false - }, - arguments: [ - {type: 'ObjectExpression', properties: []}, - {type: 'Identifier', name: 'b'}, + type: 'ObjectExpression', + properties: [ + { + type: 'SpreadElement', + argument: {type: 'Identifier', name: 'b'} + }, { - type: 'ObjectExpression', - properties: [ - { - type: 'Property', - key: {type: 'Identifier', name: 'c'}, - value: {type: 'Literal', value: true}, - kind: 'init', - method: false, - shorthand: false, - computed: false - } - ] + type: 'Property', + key: {type: 'Identifier', name: 'c'}, + value: {type: 'Literal', value: true}, + kind: 'init', + method: false, + shorthand: false, + computed: false } - ], - optional: false + ] } ], optional: false @@ -604,32 +598,19 @@ test('should support a prop and a spread prop', () => { arguments: [ {type: 'Literal', value: 'a'}, { - type: 'CallExpression', - callee: { - type: 'MemberExpression', - object: {type: 'Identifier', name: 'Object'}, - property: {type: 'Identifier', name: 'assign'}, - computed: false, - optional: false - }, - arguments: [ + type: 'ObjectExpression', + properties: [ { - type: 'ObjectExpression', - properties: [ - { - type: 'Property', - key: {type: 'Identifier', name: 'b'}, - value: {type: 'Literal', value: true}, - kind: 'init', - method: false, - shorthand: false, - computed: false - } - ] + type: 'Property', + key: {type: 'Identifier', name: 'b'}, + value: {type: 'Literal', value: true}, + kind: 'init', + method: false, + shorthand: false, + computed: false }, - {type: 'Identifier', name: 'c'} - ], - optional: false + {type: 'SpreadElement', argument: {type: 'Identifier', name: 'c'}} + ] } ], optional: false @@ -646,20 +627,17 @@ test('should support two spread props', () => { arguments: [ {type: 'Literal', value: 'a'}, { - type: 'CallExpression', - callee: { - type: 'MemberExpression', - object: {type: 'Identifier', name: 'Object'}, - property: {type: 'Identifier', name: 'assign'}, - computed: false, - optional: false - }, - arguments: [ - {type: 'ObjectExpression', properties: []}, - {type: 'Identifier', name: 'b'}, - {type: 'Identifier', name: 'c'} - ], - optional: false + type: 'ObjectExpression', + properties: [ + { + type: 'SpreadElement', + argument: {type: 'Identifier', name: 'b'} + }, + { + type: 'SpreadElement', + argument: {type: 'Identifier', name: 'c'} + } + ] } ], optional: false @@ -861,7 +839,7 @@ test('should integrate w/ generators (`astring`)', () => { pragmaFrag: 'f' }) ), - 'h(f, null, h("a", Object.assign({\n b: true,\n c: "d",\n e: f\n}, g), "h"));\n' + 'h(f, null, h("a", {\n b: true,\n c: "d",\n e: f,\n ...g\n}, "h"));\n' ) }) @@ -873,7 +851,7 @@ test('should integrate w/ generators (`recast`)', () => { pragmaFrag: 'f' }) ).code, - 'h(f, null, h("a", Object.assign({\n b: true,\n c: "d",\n e: f\n}, g), "h"));' + 'h(f, null, h("a", {\n b: true,\n c: "d",\n e: f,\n ...g\n}, "h"));' ) }) @@ -885,7 +863,7 @@ test('should integrate w/ generators (`escodegen`)', () => { pragmaFrag: 'f' }) ), - "h(f, null, h('a', Object.assign({\n b: true,\n c: 'd',\n e: f\n}, g), 'h'));" + "h(f, null, h('a', {\n b: true,\n c: 'd',\n e: f,\n ...g\n}, 'h'));" ) }) @@ -945,131 +923,121 @@ test('should support positional info', () => { range: [6, 7] }, { - type: 'CallExpression', - callee: { - type: 'MemberExpression', - object: {type: 'Identifier', name: 'Object'}, - property: {type: 'Identifier', name: 'assign'}, - computed: false, - optional: false - }, - arguments: [ + 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: '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} }, - { - 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] + range: [10, 11] + }, + value: { + type: 'Literal', + start: 12, + end: 15, + loc: { + start: {line: 2, column: 9}, + end: {line: 2, column: 12} }, - { - 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] - } - ] + 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: 'Identifier', - start: 26, - end: 27, + 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: 23}, - end: {line: 2, column: 24} + start: {line: 2, column: 13}, + end: {line: 2, column: 18} }, - range: [26, 27], - name: 'g' + 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' + } } - ], - optional: false + ] }, { type: 'Literal', @@ -1183,11 +1151,11 @@ test('should support the automatic runtime (props, spread, children)', () => { generate(buildJsx(parse('d'), {runtime: 'automatic'})), [ 'import {jsx as _jsx} from "react/jsx-runtime";', - '_jsx("a", Object.assign({', - ' b: "1"', - '}, c, {', + '_jsx("a", {', + ' b: "1",', + ' ...c,', ' children: "d"', - '}));', + '});', '' ].join('\n') ) @@ -1202,13 +1170,12 @@ test('should support the automatic runtime (spread, props, children)', () => { ), [ 'import {jsx as _jsx} from "react/jsx-runtime";', - '_jsx("a", Object.assign({', + '_jsx("a", {', ' b: 1,', - ' c: 2', - '}, {', + ' c: 2,', ' d: "e",', ' children: "f"', - '}));', + '});', '' ].join('\n') ) @@ -1306,11 +1273,11 @@ test('should support the automatic runtime (props, spread, children, development ), [ 'import {jsxDEV as _jsxDEV} from "react/jsx-dev-runtime";', - '_jsxDEV("a", Object.assign({', - ' b: "1"', - '}, c, {', + '_jsxDEV("a", {', + ' b: "1",', + ' ...c,', ' children: "d"', - '}), undefined, false, {', + '}, undefined, false, {', ' fileName: "index.js",', ' lineNumber: 1,', ' columnNumber: 1', @@ -1331,13 +1298,12 @@ test('should support the automatic runtime (spread, props, children, development ), [ 'import {jsxDEV as _jsxDEV} from "react/jsx-dev-runtime";', - '_jsxDEV("a", Object.assign({', + '_jsxDEV("a", {', ' b: 1,', - ' c: 2', - '}, {', + ' c: 2,', ' d: "e",', ' children: "f"', - '}), undefined, false, {', + '}, undefined, false, {', ' fileName: "index.js",', ' lineNumber: 1,', ' columnNumber: 1', From 9fbb129c0a59c6cc72cc4589c6f3fde6924b40f4 Mon Sep 17 00:00:00 2001 From: Remco Haszing Date: Fri, 16 Jun 2023 14:15:21 +0200 Subject: [PATCH 2/4] Rename properties to fields --- lib/index.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/index.js b/lib/index.js index d06c82a..f974003 100644 --- a/lib/index.js +++ b/lib/index.js @@ -282,7 +282,7 @@ export function buildJsx(tree, options) { /** @type {MemberExpression | Literal | Identifier} */ let name /** @type {Array} */ - const properties = [] + const fields = [] /** @type {Array} */ let parameters = [] /** @type {Expression | undefined} */ @@ -314,9 +314,9 @@ export function buildJsx(tree, options) { if (attribute.type === 'JSXSpreadAttribute') { const {argument} = attribute if (argument.type === 'ObjectExpression') { - properties.push(...argument.properties) + fields.push(...argument.properties) } else { - properties.push({ + fields.push({ type: 'SpreadElement', argument }) @@ -341,7 +341,7 @@ export function buildJsx(tree, options) { // attribute values? 🤷‍♂️ key = prop.value } else { - properties.push(prop) + fields.push(prop) } } } @@ -358,7 +358,7 @@ export function buildJsx(tree, options) { if (automatic) { if (children.length > 0) { - properties.push({ + fields.push({ type: 'Property', key: {type: 'Identifier', name: 'children'}, value: @@ -379,7 +379,7 @@ export function buildJsx(tree, options) { let callee if (automatic) { - parameters.push({type: 'ObjectExpression', properties}) + parameters.push({type: 'ObjectExpression', properties: fields}) if (key) { parameters.push(key) @@ -450,8 +450,8 @@ export function buildJsx(tree, options) { } // Classic. else { - if (properties.length > 0) { - parameters.unshift({type: 'ObjectExpression', properties}) + if (fields.length > 0) { + parameters.unshift({type: 'ObjectExpression', properties: fields}) } else if (parameters.length > 0) { parameters.unshift({type: 'Literal', value: null}) } From 11912e4f665fd0fb341398a2ea626f6dd5f0859c Mon Sep 17 00:00:00 2001 From: Remco Haszing Date: Fri, 16 Jun 2023 14:19:08 +0200 Subject: [PATCH 3/4] Fit statement on single line --- lib/index.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/index.js b/lib/index.js index f974003..43e10bc 100644 --- a/lib/index.js +++ b/lib/index.js @@ -316,10 +316,7 @@ export function buildJsx(tree, options) { if (argument.type === 'ObjectExpression') { fields.push(...argument.properties) } else { - fields.push({ - type: 'SpreadElement', - argument - }) + fields.push({type: 'SpreadElement', argument}) } spread = true From 25114151216a54c7c0917cbb36b5f87f39057d4c Mon Sep 17 00:00:00 2001 From: Remco Haszing Date: Sat, 17 Jun 2023 15:20:01 +0200 Subject: [PATCH 4/4] Replace destructuring syntax --- lib/index.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/index.js b/lib/index.js index 43e10bc..d31088c 100644 --- a/lib/index.js +++ b/lib/index.js @@ -312,11 +312,10 @@ export function buildJsx(tree, options) { const attribute = attributes[index] if (attribute.type === 'JSXSpreadAttribute') { - const {argument} = attribute - if (argument.type === 'ObjectExpression') { - fields.push(...argument.properties) + if (attribute.argument.type === 'ObjectExpression') { + fields.push(...attribute.argument.properties) } else { - fields.push({type: 'SpreadElement', argument}) + fields.push({type: 'SpreadElement', argument: attribute.argument}) } spread = true