UNPKG

@philpl/buble

Version:

The blazing fast, batteries-included ES2015 compiler

269 lines (240 loc) 8.82 kB
import Node from '../Node.js'; import CompileError from '../../utils/CompileError.js'; export default class ObjectExpression extends Node { transpile(code, transforms) { super.transpile(code, transforms); let firstPropertyStart = this.start + 1; let spreadPropertyCount = 0; let computedPropertyCount = 0; let firstSpreadProperty = null; let firstComputedProperty = null; for (let i = 0; i < this.properties.length; ++i) { const prop = this.properties[i]; if (prop.type === 'SpreadElement') { // First see if we can inline the spread, to save needing objectAssign. const argument = prop.argument; if ( argument.type === 'ObjectExpression' || ( argument.type === 'Literal' && typeof argument.value !== 'string' ) ) { if (argument.type === 'ObjectExpression' && argument.properties.length > 0) { // Strip the `...{` and the `}` with a possible trailing comma before it, // leaving just the possible trailing comma after it. code.remove(prop.start, argument.properties[0].start); code.remove(argument.properties[argument.properties.length - 1].end, prop.end); this.properties.splice(i, 1, ...argument.properties); i--; } else { // An empty object, boolean, null, undefined, number or regexp (but NOT // string) will spread to nothing, so just remove the element altogether, // including a possible trailing comma. code.remove(prop.start, i === this.properties.length - 1 ? prop.end : this.properties[i + 1].start); this.properties.splice(i, 1); i--; } } else { spreadPropertyCount += 1; if (firstSpreadProperty === null) firstSpreadProperty = i; } } else if (prop.computed && transforms.computedProperty) { computedPropertyCount += 1; if (firstComputedProperty === null) firstComputedProperty = i; } } if (spreadPropertyCount && !transforms.objectRestSpread && !(computedPropertyCount && transforms.computedProperty)) { spreadPropertyCount = 0; firstSpreadProperty = null; } else if (spreadPropertyCount) { if (!this.program.options.objectAssign) { throw new CompileError( "Object spread operator requires specified objectAssign option with 'Object.assign' or polyfill helper.", this ); } let i = this.properties.length; while (i--) { const prop = this.properties[i]; // enclose run of non-spread properties in curlies if (prop.type === 'Property' && !computedPropertyCount) { const lastProp = this.properties[i - 1]; const nextProp = this.properties[i + 1]; if (!lastProp || lastProp.type !== 'Property') { code.prependRight(prop.start, '{'); } if (!nextProp || nextProp.type !== 'Property') { code.appendLeft(prop.end, '}'); } } // Remove ellipsis on spread property if (prop.type === 'SpreadElement') { code.remove(prop.start, prop.argument.start); code.remove(prop.argument.end, prop.end); } } // wrap the whole thing in Object.assign firstPropertyStart = this.properties[0].start; if (!computedPropertyCount) { code.overwrite( this.start, firstPropertyStart, `${this.program.options.objectAssign}({}, ` ); code.overwrite( this.properties[this.properties.length - 1].end, this.end, ')' ); } else if (this.properties[0].type === 'SpreadElement') { code.overwrite( this.start, firstPropertyStart, `${this.program.options.objectAssign}({}, ` ); code.remove(this.end - 1, this.end); code.appendRight(this.end, ')'); } else { code.prependLeft(this.start, `${this.program.options.objectAssign}(`); code.appendRight(this.end, ')'); } } if (computedPropertyCount && transforms.computedProperty) { const i0 = this.getIndentation(); let isSimpleAssignment; let name; if ( this.parent.type === 'VariableDeclarator' && this.parent.parent.declarations.length === 1 && this.parent.id.type === 'Identifier' ) { isSimpleAssignment = true; name = this.parent.id.alias || this.parent.id.name; // TODO is this right? } else if ( this.parent.type === 'AssignmentExpression' && this.parent.parent.type === 'ExpressionStatement' && this.parent.left.type === 'Identifier' ) { isSimpleAssignment = true; name = this.parent.left.alias || this.parent.left.name; // TODO is this right? } else if ( this.parent.type === 'AssignmentPattern' && this.parent.left.type === 'Identifier' ) { isSimpleAssignment = true; name = this.parent.left.alias || this.parent.left.name; // TODO is this right? } if (spreadPropertyCount) isSimpleAssignment = false; // handle block scoping name = this.findScope(false).resolveName(name); const start = firstPropertyStart; const end = this.end; if (isSimpleAssignment) { // ??? } else { if ( firstSpreadProperty === null || firstComputedProperty < firstSpreadProperty ) { name = this.findScope(true).createDeclaration('obj'); code.prependRight(this.start, `( ${name} = `); } else name = null; // We don't actually need this variable } const len = this.properties.length; let lastComputedProp; let sawNonComputedProperty = false; let isFirst = true; for (let i = 0; i < len; i += 1) { const prop = this.properties[i]; let moveStart = i > 0 ? this.properties[i - 1].end : start; if ( prop.type === 'Property' && (prop.computed || (lastComputedProp && !spreadPropertyCount)) ) { if (i === 0) moveStart = this.start + 1; // Trim leading whitespace lastComputedProp = prop; if (!name) { name = this.findScope(true).createDeclaration('obj'); const propId = name + (prop.computed ? '' : '.'); code.appendRight(prop.start, `( ${name} = {}, ${propId}`); } else { const propId = (isSimpleAssignment ? `;\n${i0}${name}` : `, ${name}`) + (prop.key.type === 'Literal' || prop.computed ? '' : '.'); if (moveStart < prop.start) { code.overwrite(moveStart, prop.start, propId); } else { code.prependRight(prop.start, propId); } } let c = prop.key.end; if (prop.computed) { while (code.original[c] !== ']') c += 1; c += 1; } if (prop.key.type === 'Literal' && !prop.computed) { code.overwrite( prop.start, prop.key.end + 1, '[' + code.slice(prop.start, prop.key.end) + '] = ' ); } else if (prop.shorthand || (prop.method && !prop.computed && transforms.conciseMethodProperty)) { // Replace : with = if Property::transpile inserted the : code.overwrite( prop.key.start, prop.key.end, code.slice(prop.key.start, prop.key.end).replace(/:/, ' =') ); } else { if (prop.value.start > c) code.remove(c, prop.value.start); code.prependLeft(c, ' = '); } // This duplicates behavior from Property::transpile which is disabled // for computed properties or if conciseMethodProperty is false if (prop.method && (prop.computed || !transforms.conciseMethodProperty)) { if (prop.value.generator) code.remove(prop.start, prop.key.start); code.prependRight(prop.value.start, `function${prop.value.generator ? '*' : ''} `); } } else if (prop.type === 'SpreadElement') { if (name && i > 0) { if (!lastComputedProp) { lastComputedProp = this.properties[i - 1]; } code.appendLeft(lastComputedProp.end, `, ${name} )`); lastComputedProp = null; name = null; } } else { if (!isFirst && spreadPropertyCount) { // We are in an Object.assign context, so we need to wrap regular properties code.prependRight(prop.start, '{'); code.appendLeft(prop.end, '}'); } sawNonComputedProperty = true; } if (isFirst && (prop.type === 'SpreadElement' || prop.computed)) { let beginEnd = sawNonComputedProperty ? this.properties[this.properties.length - 1].end : this.end - 1; // Trim trailing comma because it can easily become a leading comma which is illegal if (code.original[beginEnd] == ',') ++beginEnd; const closing = code.slice(beginEnd, end); code.prependLeft(moveStart, closing); code.remove(beginEnd, end); isFirst = false; } // Clean up some extranous whitespace let c = prop.end; if (i < len - 1 && !sawNonComputedProperty) { while (code.original[c] !== ',') c += 1; } else if (i == len - 1) c = this.end; if (prop.end != c) code.overwrite(prop.end, c, '', {contentOnly: true}); } if (!isSimpleAssignment && name) { code.appendLeft(lastComputedProp.end, `, ${name} )`); } } } }