UNPKG

buble

Version:

The blazing fast, batteries-included ES2015 compiler

1,953 lines (1,666 loc) 118 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; } var acorn = require('acorn'); var acornJsx = _interopDefault(require('acorn-jsx')); var acornDynamicImport = _interopDefault(require('acorn-dynamic-import')); var MagicString = _interopDefault(require('magic-string')); var rewritePattern = _interopDefault(require('regexpu-core')); // used for debugging, without the noise created by // circular references function toJSON(node) { var obj = {}; Object.keys(node).forEach(key => { if ( key === 'parent' || key === 'program' || key === 'keys' || key === '__wrapped' ) { return; } if (Array.isArray(node[key])) { obj[key] = node[key].map(toJSON); } else if (node[key] && node[key].toJSON) { obj[key] = node[key].toJSON(); } else { obj[key] = node[key]; } }); return obj; } class Node { ancestor(level) { var node = this; while (level--) { node = node.parent; if (!node) { return null; } } return node; } contains(node) { while (node) { if (node === this) { return true; } node = node.parent; } return false; } findLexicalBoundary() { return this.parent.findLexicalBoundary(); } findNearest(type) { if (typeof type === 'string') { type = new RegExp(`^${type}$`); } if (type.test(this.type)) { return this; } return this.parent.findNearest(type); } unparenthesizedParent() { var node = this.parent; while (node && node.type === 'ParenthesizedExpression') { node = node.parent; } return node; } unparenthesize() { var node = this; while (node.type === 'ParenthesizedExpression') { node = node.expression; } return node; } findScope(functionScope) { return this.parent.findScope(functionScope); } getIndentation() { return this.parent.getIndentation(); } initialise(transforms) { for (var i = 0, list = this.keys; i < list.length; i += 1) { var key = list[i]; var value = this[key]; if (Array.isArray(value)) { value.forEach(node => node && node.initialise(transforms)); } else if (value && typeof value === 'object') { value.initialise(transforms); } } } toJSON() { return toJSON(this); } toString() { return this.program.magicString.original.slice(this.start, this.end); } transpile(code, transforms) { for (var i = 0, list = this.keys; i < list.length; i += 1) { var key = list[i]; var value = this[key]; if (Array.isArray(value)) { value.forEach(node => node && node.transpile(code, transforms)); } else if (value && typeof value === 'object') { value.transpile(code, transforms); } } } } function extractNames(node) { var names = []; extractors[node.type](names, node); return names; } var extractors = { Identifier(names, node) { names.push(node); }, ObjectPattern(names, node) { for (var i = 0, list = node.properties; i < list.length; i += 1) { var prop = list[i]; extractors[prop.type](names, prop); } }, Property(names, node) { extractors[node.value.type](names, node.value); }, ArrayPattern(names, node) { for (var i = 0, list = node.elements; i < list.length; i += 1) { var element = list[i]; if (element) { extractors[element.type](names, element); } } }, RestElement(names, node) { extractors[node.argument.type](names, node.argument); }, AssignmentPattern(names, node) { extractors[node.left.type](names, node.left); } }; var reserved = Object.create(null); 'do if in for let new try var case else enum eval null this true void with await break catch class const false super throw while yield delete export import public return static switch typeof default extends finally package private continue debugger function arguments interface protected implements instanceof' .split(' ') .forEach(word => (reserved[word] = true)); function Scope(options) { options = options || {}; this.parent = options.parent; this.isBlockScope = !!options.block; this.createDeclarationCallback = options.declare; var scope = this; while (scope.isBlockScope) { scope = scope.parent; } this.functionScope = scope; this.identifiers = []; this.declarations = Object.create(null); this.references = Object.create(null); this.blockScopedDeclarations = this.isBlockScope ? null : Object.create(null); this.aliases = Object.create(null); } Scope.prototype = { addDeclaration(node, kind) { for (var i = 0, list = extractNames(node); i < list.length; i += 1) { var identifier = list[i]; var name = identifier.name; var declaration = { name, node: identifier, kind, instances: [] }; this.declarations[name] = declaration; if (this.isBlockScope) { if (!this.functionScope.blockScopedDeclarations[name]) { this.functionScope.blockScopedDeclarations[name] = []; } this.functionScope.blockScopedDeclarations[name].push(declaration); } } }, addReference(identifier) { if (this.consolidated) { this.consolidateReference(identifier); } else { this.identifiers.push(identifier); } }, consolidate() { for (var i = 0; i < this.identifiers.length; i += 1) { // we might push to the array during consolidation, so don't cache length var identifier = this.identifiers[i]; this.consolidateReference(identifier); } this.consolidated = true; // TODO understand why this is necessary... seems bad }, consolidateReference(identifier) { var declaration = this.declarations[identifier.name]; if (declaration) { declaration.instances.push(identifier); } else { this.references[identifier.name] = true; if (this.parent) { this.parent.addReference(identifier); } } }, contains(name) { return ( this.declarations[name] || (this.parent ? this.parent.contains(name) : false) ); }, createIdentifier(base) { if (typeof base === 'number') { base = base.toString(); } base = base .replace(/\s/g, '') .replace(/\[([^\]]+)\]/g, '_$1') .replace(/[^a-zA-Z0-9_$]/g, '_') .replace(/_{2,}/, '_'); var name = base; var counter = 1; while ( this.declarations[name] || this.references[name] || this.aliases[name] || name in reserved ) { name = `${base}$${counter++}`; } this.aliases[name] = true; return name; }, createDeclaration(base) { var id = this.createIdentifier(base); this.createDeclarationCallback(id); return id; }, findDeclaration(name) { return ( this.declarations[name] || (this.parent && this.parent.findDeclaration(name)) ); }, // Sometimes, block scope declarations change name during transpilation resolveName(name) { var declaration = this.findDeclaration(name); return declaration ? declaration.name : name; } }; function locate(source, index) { var lines = source.split('\n'); var len = lines.length; var lineStart = 0; var i; for (i = 0; i < len; i += 1) { var line = lines[i]; var lineEnd = lineStart + line.length + 1; // +1 for newline if (lineEnd > index) { return { line: i + 1, column: index - lineStart, char: i }; } lineStart = lineEnd; } throw new Error('Could not determine location of character'); } function pad(num, len) { var result = String(num); return result + repeat(' ', len - result.length); } function repeat(str, times) { var result = ''; while (times--) { result += str; } return result; } function getSnippet(source, loc, length) { if ( length === void 0 ) length = 1; var first = Math.max(loc.line - 5, 0); var last = loc.line; var numDigits = String(last).length; var lines = source.split('\n').slice(first, last); var lastLine = lines[lines.length - 1]; var offset = lastLine.slice(0, loc.column).replace(/\t/g, ' ').length; var snippet = lines .map((line, i) => `${pad(i + first + 1, numDigits)} : ${line.replace(/\t/g, ' ')}`) .join('\n'); snippet += '\n' + repeat(' ', numDigits + 3 + offset) + repeat('^', length); return snippet; } class CompileError extends Error { constructor(message, node) { super(message); this.name = 'CompileError'; if (!node) { return; } var source = node.program.magicString.original; var loc = locate(source, node.start); this.message = message + ` (${loc.line}:${loc.column})`; this.stack = new Error().stack.replace( new RegExp(`.+new ${this.name}.+\\n`, 'm'), '' ); this.loc = loc; this.snippet = getSnippet(source, loc, node.end - node.start); } toString() { return `${this.name}: ${this.message}\n${this.snippet}`; } static missingTransform(feature, transformKey, node, dangerousKey) { if ( dangerousKey === void 0 ) dangerousKey = null; var maybeDangerous = dangerousKey ? `, or \`transforms: { ${dangerousKey}: true }\` if you know what you're doing` : ''; throw new CompileError(`Transforming ${feature} is not ${dangerousKey ? "fully supported" : "implemented"}. Use \`transforms: { ${transformKey}: false }\` to skip transformation and disable this error${maybeDangerous}.`, node); } } function findIndex(array, fn) { for (var i = 0; i < array.length; i += 1) { if (fn(array[i], i)) { return i; } } return -1; } var handlers = { Identifier: destructureIdentifier, AssignmentPattern: destructureAssignmentPattern, ArrayPattern: destructureArrayPattern, ObjectPattern: destructureObjectPattern }; function destructure( code, createIdentifier, resolveName, node, ref, inline, statementGenerators ) { handlers[node.type](code, createIdentifier, resolveName, node, ref, inline, statementGenerators); } function destructureIdentifier( code, createIdentifier, resolveName, node, ref, inline, statementGenerators ) { statementGenerators.push((start, prefix, suffix) => { code.overwrite(node.start, node.end, (inline ? prefix : `${prefix}var `) + resolveName(node) + ` = ${ref}${suffix}`); code.move(node.start, node.end, start); }); } function destructureMemberExpression( code, createIdentifier, resolveName, node, ref, inline, statementGenerators ) { statementGenerators.push((start, prefix, suffix) => { code.prependRight(node.start, inline ? prefix : `${prefix}var `); code.appendLeft(node.end, ` = ${ref}${suffix}`); code.move(node.start, node.end, start); }); } function destructureAssignmentPattern( code, createIdentifier, resolveName, node, ref, inline, statementGenerators ) { var isIdentifier = node.left.type === 'Identifier'; var name = isIdentifier ? node.left.name : ref; if (!inline) { statementGenerators.push((start, prefix, suffix) => { code.prependRight( node.left.end, `${prefix}if ( ${name} === void 0 ) ${name}` ); code.move(node.left.end, node.right.end, start); code.appendLeft(node.right.end, suffix); }); } if (!isIdentifier) { destructure(code, createIdentifier, resolveName, node.left, ref, inline, statementGenerators); } } function destructureArrayPattern( code, createIdentifier, resolveName, node, ref, inline, statementGenerators ) { var c = node.start; node.elements.forEach((element, i) => { if (!element) { return; } if (element.type === 'RestElement') { handleProperty( code, createIdentifier, resolveName, c, element.argument, `${ref}.slice(${i})`, inline, statementGenerators ); } else { handleProperty( code, createIdentifier, resolveName, c, element, `${ref}[${i}]`, inline, statementGenerators ); } c = element.end; }); code.remove(c, node.end); } function destructureObjectPattern( code, createIdentifier, resolveName, node, ref, inline, statementGenerators ) { var c = node.start; var nonRestKeys = []; node.properties.forEach(prop => { var value; var content; if (prop.type === 'Property') { content = prop.value; if (!prop.computed && prop.key.type === 'Identifier') { value = `${ref}.${prop.key.name}`; nonRestKeys.push(`"${prop.key.name}"`); } else if (!prop.computed && prop.key.type === 'Literal') { value = `${ref}[${prop.key.raw}]`; nonRestKeys.push(JSON.stringify(String(prop.key.value))); } else { var expr = code.slice(prop.key.start, prop.key.end); value = `${ref}[${expr}]`; nonRestKeys.push(`String(${expr})`); } } else if (prop.type === 'RestElement') { content = prop.argument; value = createIdentifier('rest'); statementGenerators.push((start, prefix, suffix) => { var helper = prop.program.getObjectWithoutPropertiesHelper(code); code.overwrite( prop.start, (c = prop.argument.start), (inline ? prefix : `${prefix}var `) + `${value} = ${helper}( ${ref}, [${nonRestKeys.join(', ')}] )${suffix}` ); code.move(prop.start, c, start); }); } else { throw new CompileError( this, `Unexpected node of type ${prop.type} in object pattern` ); } handleProperty(code, createIdentifier, resolveName, c, content, value, inline, statementGenerators); c = prop.end; }); code.remove(c, node.end); } function handleProperty( code, createIdentifier, resolveName, c, node, value, inline, statementGenerators ) { switch (node.type) { case 'Identifier': { code.remove(c, node.start); destructureIdentifier( code, createIdentifier, resolveName, node, value, inline, statementGenerators ); break; } case 'MemberExpression': code.remove(c, node.start); destructureMemberExpression( code, createIdentifier, resolveName, node, value, true, statementGenerators ); break; case 'AssignmentPattern': { var name; var isIdentifier = node.left.type === 'Identifier'; if (isIdentifier) { name = resolveName(node.left); } else { name = createIdentifier(value); } statementGenerators.push((start, prefix, suffix) => { if (inline) { code.prependRight( node.right.start, `${name} = ${value}, ${name} = ${name} === void 0 ? ` ); code.appendLeft(node.right.end, ` : ${name}${suffix}`); } else { code.prependRight( node.right.start, `${prefix}var ${name} = ${value}; if ( ${name} === void 0 ) ${name} = ` ); code.appendLeft(node.right.end, suffix); } code.move(node.right.start, node.right.end, start); }); if (isIdentifier) { code.remove(c, node.right.start); } else { code.remove(c, node.left.start); code.remove(node.left.end, node.right.start); handleProperty( code, createIdentifier, resolveName, c, node.left, name, inline, statementGenerators ); } break; } case 'ObjectPattern': { code.remove(c, (c = node.start)); var ref = value; if (node.properties.length > 1) { ref = createIdentifier(value); statementGenerators.push((start, prefix, suffix) => { // this feels a tiny bit hacky, but we can't do a // straightforward appendLeft and keep correct order... code.prependRight(node.start, (inline ? '' : `${prefix}var `) + `${ref} = `); code.overwrite(node.start, (c = node.start + 1), value); code.appendLeft(c, suffix); code.overwrite( node.start, (c = node.start + 1), (inline ? '' : `${prefix}var `) + `${ref} = ${value}${suffix}` ); code.move(node.start, c, start); }); } destructureObjectPattern( code, createIdentifier, resolveName, node, ref, inline, statementGenerators ); break; } case 'ArrayPattern': { code.remove(c, (c = node.start)); if (node.elements.filter(Boolean).length > 1) { var ref$1 = createIdentifier(value); statementGenerators.push((start, prefix, suffix) => { code.prependRight(node.start, (inline ? '' : `${prefix}var `) + `${ref$1} = `); code.overwrite(node.start, (c = node.start + 1), value, { contentOnly: true }); code.appendLeft(c, suffix); code.move(node.start, c, start); }); node.elements.forEach((element, i) => { if (!element) { return; } if (element.type === 'RestElement') { handleProperty( code, createIdentifier, resolveName, c, element.argument, `${ref$1}.slice(${i})`, inline, statementGenerators ); } else { handleProperty( code, createIdentifier, resolveName, c, element, `${ref$1}[${i}]`, inline, statementGenerators ); } c = element.end; }); } else { var index = findIndex(node.elements, Boolean); var element = node.elements[index]; if (element.type === 'RestElement') { handleProperty( code, createIdentifier, resolveName, c, element.argument, `${value}.slice(${index})`, inline, statementGenerators ); } else { handleProperty( code, createIdentifier, resolveName, c, element, `${value}[${index}]`, inline, statementGenerators ); } c = element.end; } code.remove(c, node.end); break; } default: { throw new Error(`Unexpected node type in destructuring (${node.type})`); } } } function isUseStrict(node) { if (!node) { return false; } if (node.type !== 'ExpressionStatement') { return false; } if (node.expression.type !== 'Literal') { return false; } return node.expression.value === 'use strict'; } class BlockStatement extends Node { createScope() { this.parentIsFunction = /Function/.test(this.parent.type); this.isFunctionBlock = this.parentIsFunction || this.parent.type === 'Root'; this.scope = new Scope({ block: !this.isFunctionBlock, parent: this.parent.findScope(false), declare: id => this.createdDeclarations.push(id) }); if (this.parentIsFunction) { this.parent.params.forEach(node => { this.scope.addDeclaration(node, 'param'); }); } } initialise(transforms) { this.thisAlias = null; this.argumentsAlias = null; this.defaultParameters = []; this.createdDeclarations = []; // normally the scope gets created here, during initialisation, // but in some cases (e.g. `for` statements), we need to create // the scope early, as it pertains to both the init block and // the body of the statement if (!this.scope) { this.createScope(); } this.body.forEach(node => node.initialise(transforms)); this.scope.consolidate(); } findLexicalBoundary() { if (this.type === 'Program') { return this; } if (/^Function/.test(this.parent.type)) { return this; } return this.parent.findLexicalBoundary(); } findScope(functionScope) { if (functionScope && !this.isFunctionBlock) { return this.parent.findScope(functionScope); } return this.scope; } getArgumentsAlias() { if (!this.argumentsAlias) { this.argumentsAlias = this.scope.createIdentifier('arguments'); } return this.argumentsAlias; } getArgumentsArrayAlias() { if (!this.argumentsArrayAlias) { this.argumentsArrayAlias = this.scope.createIdentifier('argsArray'); } return this.argumentsArrayAlias; } getThisAlias() { if (!this.thisAlias) { this.thisAlias = this.scope.createIdentifier('this'); } return this.thisAlias; } getIndentation() { if (this.indentation === undefined) { var source = this.program.magicString.original; var useOuter = this.synthetic || !this.body.length; var c = useOuter ? this.start : this.body[0].start; while (c && source[c] !== '\n') { c -= 1; } this.indentation = ''; // eslint-disable-next-line no-constant-condition while (true) { c += 1; var char = source[c]; if (char !== ' ' && char !== '\t') { break; } this.indentation += char; } var indentString = this.program.magicString.getIndentString(); // account for dedented class constructors var parent = this.parent; while (parent) { if (parent.kind === 'constructor' && !parent.parent.parent.superClass) { this.indentation = this.indentation.replace(indentString, ''); } parent = parent.parent; } if (useOuter) { this.indentation += indentString; } } return this.indentation; } transpile(code, transforms) { var indentation = this.getIndentation(); var introStatementGenerators = []; if (this.argumentsAlias) { introStatementGenerators.push((start, prefix, suffix) => { var assignment = `${prefix}var ${this.argumentsAlias} = arguments${ suffix }`; code.appendLeft(start, assignment); }); } if (this.thisAlias) { introStatementGenerators.push((start, prefix, suffix) => { var assignment = `${prefix}var ${this.thisAlias} = this${suffix}`; code.appendLeft(start, assignment); }); } if (this.argumentsArrayAlias) { introStatementGenerators.push((start, prefix, suffix) => { var i = this.scope.createIdentifier('i'); var assignment = `${prefix}var ${i} = arguments.length, ${ this.argumentsArrayAlias } = Array(${i});\n${indentation}while ( ${i}-- ) ${ this.argumentsArrayAlias }[${i}] = arguments[${i}]${suffix}`; code.appendLeft(start, assignment); }); } if (/Function/.test(this.parent.type)) { this.transpileParameters( this.parent.params, code, transforms, indentation, introStatementGenerators ); } else if ('CatchClause' === this.parent.type) { this.transpileParameters( [this.parent.param], code, transforms, indentation, introStatementGenerators ); } if (transforms.letConst && this.isFunctionBlock) { this.transpileBlockScopedIdentifiers(code); } super.transpile(code, transforms); if (this.createdDeclarations.length) { introStatementGenerators.push((start, prefix, suffix) => { var assignment = `${prefix}var ${this.createdDeclarations.join(', ')}${suffix}`; code.appendLeft(start, assignment); }); } if (this.synthetic) { if (this.parent.type === 'ArrowFunctionExpression') { var expr = this.body[0]; if (introStatementGenerators.length) { code .appendLeft(this.start, `{`) .prependRight(this.end, `${this.parent.getIndentation()}}`); code.prependRight(expr.start, `\n${indentation}return `); code.appendLeft(expr.end, `;\n`); } else if (transforms.arrow) { code.prependRight(expr.start, `{ return `); code.appendLeft(expr.end, `; }`); } } else if (introStatementGenerators.length) { code.prependRight(this.start, `{`).appendLeft(this.end, `}`); } } var start; if (isUseStrict(this.body[0])) { start = this.body[0].end; } else if (this.synthetic || this.parent.type === 'Root') { start = this.start; } else { start = this.start + 1; } var prefix = `\n${indentation}`; var suffix = ';'; introStatementGenerators.forEach((fn, i) => { if (i === introStatementGenerators.length - 1) { suffix = `;\n`; } fn(start, prefix, suffix); }); } transpileParameters(params, code, transforms, indentation, introStatementGenerators) { params.forEach(param => { if ( param.type === 'AssignmentPattern' && param.left.type === 'Identifier' ) { if (transforms.defaultParameter) { introStatementGenerators.push((start, prefix, suffix) => { var lhs = `${prefix}if ( ${param.left.name} === void 0 ) ${ param.left.name }`; code .prependRight(param.left.end, lhs) .move(param.left.end, param.right.end, start) .appendLeft(param.right.end, suffix); }); } } else if (param.type === 'RestElement') { if (transforms.spreadRest) { introStatementGenerators.push((start, prefix, suffix) => { var penultimateParam = params[params.length - 2]; if (penultimateParam) { code.remove( penultimateParam ? penultimateParam.end : param.start, param.end ); } else { var start$1 = param.start, end = param.end; // TODO https://gitlab.com/Rich-Harris/buble/issues/8 while (/\s/.test(code.original[start$1 - 1])) { start$1 -= 1; } while (/\s/.test(code.original[end])) { end += 1; } code.remove(start$1, end); } var name = param.argument.name; var len = this.scope.createIdentifier('len'); var count = params.length - 1; if (count) { code.prependRight( start, `${prefix}var ${name} = [], ${len} = arguments.length - ${ count };\n${indentation}while ( ${len}-- > 0 ) ${name}[ ${ len } ] = arguments[ ${len} + ${count} ]${suffix}` ); } else { code.prependRight( start, `${prefix}var ${name} = [], ${len} = arguments.length;\n${ indentation }while ( ${len}-- ) ${name}[ ${len} ] = arguments[ ${len} ]${ suffix }` ); } }); } } else if (param.type !== 'Identifier') { if (transforms.parameterDestructuring) { var ref = this.scope.createIdentifier('ref'); destructure( code, id => this.scope.createIdentifier(id), (ref) => { var name = ref.name; return this.scope.resolveName(name); }, param, ref, false, introStatementGenerators ); code.prependRight(param.start, ref); } } }); } transpileBlockScopedIdentifiers(code) { Object.keys(this.scope.blockScopedDeclarations).forEach(name => { var declarations = this.scope.blockScopedDeclarations[name]; for (var i$2 = 0, list$2 = declarations; i$2 < list$2.length; i$2 += 1) { var declaration = list$2[i$2]; var cont = false; // TODO implement proper continue... if (declaration.kind === 'for.let') { // special case var forStatement = declaration.node.findNearest('ForStatement'); if (forStatement.shouldRewriteAsFunction) { var outerAlias = this.scope.createIdentifier(name); var innerAlias = forStatement.reassigned[name] ? this.scope.createIdentifier(name) : name; declaration.name = outerAlias; code.overwrite( declaration.node.start, declaration.node.end, outerAlias, { storeName: true } ); forStatement.aliases[name] = { outer: outerAlias, inner: innerAlias }; for (var i = 0, list = declaration.instances; i < list.length; i += 1) { var identifier = list[i]; var alias = forStatement.body.contains(identifier) ? innerAlias : outerAlias; if (name !== alias) { code.overwrite(identifier.start, identifier.end, alias, { storeName: true }); } } cont = true; } } if (!cont) { var alias$1 = this.scope.createIdentifier(name); if (name !== alias$1) { var declarationParent = declaration.node.parent; declaration.name = alias$1; code.overwrite( declaration.node.start, declaration.node.end, alias$1, { storeName: true } ); if (declarationParent.type === 'Property' && declarationParent.shorthand) { declarationParent.shorthand = false; code.prependLeft(declaration.node.start, `${name}: `); } for (var i$1 = 0, list$1 = declaration.instances; i$1 < list$1.length; i$1 += 1) { var identifier$1 = list$1[i$1]; identifier$1.rewritten = true; var identifierParent = identifier$1.parent; code.overwrite(identifier$1.start, identifier$1.end, alias$1, { storeName: true }); if (identifierParent.type === 'Property' && identifierParent.shorthand) { identifierParent.shorthand = false; code.prependLeft(identifier$1.start, `${name}: `); } } } } } }); } } function isArguments(node) { return node.type === 'Identifier' && node.name === 'arguments'; } function inlineSpreads( code, node, elements ) { var i = elements.length; while (i--) { var element = elements[i]; if (!element || element.type !== 'SpreadElement') { continue; } var argument = element.argument; if (argument.type !== 'ArrayExpression') { continue; } var subelements = argument.elements; if (subelements.some(subelement => subelement === null)) { // Not even going to try inlining spread arrays with holes. // It's a lot of work (got to be VERY careful in comma counting for // ArrayExpression, and turn blanks into undefined for // CallExpression and NewExpression), and probably literally no one // would ever benefit from it. continue; } // We can inline it: drop the `...[` and `]` and sort out any commas. var isLast = i === elements.length - 1; if (subelements.length === 0) { code.remove( isLast && i !== 0 ? elements[i - 1].end // Take the previous comma too : element.start, isLast ? node.end - 1 // Must remove trailing comma; element.end wouldn’t : elements[i + 1].start); } else { // Strip the `...[` and the `]` with a possible trailing comma before it, // leaving just the possible trailing comma after it. code.remove(element.start, subelements[0].start); code.remove( // Strip a possible trailing comma after the last element subelements[subelements.length - 1].end, // And also a possible trailing comma after the spread isLast ? node.end - 1 : element.end ); } elements.splice.apply(elements, [ i, 1 ].concat( subelements )); i += subelements.length; } } // Returns false if it’s safe to simply append a method call to the node, // e.g. `a` → `a.concat()`. // // Returns true if it may not be and so parentheses should be employed, // e.g. `a ? b : c` → `a ? b : c.concat()` would be wrong. // // This test may be overcautious; if desired it can be refined over time. function needsParentheses(node) { switch (node.type) { // Currently whitelisted are all relevant ES5 node types ('Literal' and // 'ObjectExpression' are skipped as irrelevant for array/call spread.) case 'ArrayExpression': case 'CallExpression': case 'Identifier': case 'ParenthesizedExpression': case 'ThisExpression': return false; default: return true; } } function spread( code, elements, start, argumentsArrayAlias, isNew ) { var i = elements.length; var firstSpreadIndex = -1; while (i--) { var element$1 = elements[i]; if (element$1 && element$1.type === 'SpreadElement') { if (isArguments(element$1.argument)) { code.overwrite( element$1.argument.start, element$1.argument.end, argumentsArrayAlias ); } firstSpreadIndex = i; } } if (firstSpreadIndex === -1) { return false; } // false indicates no spread elements if (isNew) { for (i = 0; i < elements.length; i += 1) { var element$2 = elements[i]; if (element$2.type === 'SpreadElement') { code.remove(element$2.start, element$2.argument.start); } else { code.prependRight(element$2.start, '['); code.prependRight(element$2.end, ']'); } } return true; // true indicates some spread elements } var element = elements[firstSpreadIndex]; var previousElement = elements[firstSpreadIndex - 1]; if (!previousElement) { // We may need to parenthesize it to handle ternaries like [...a ? b : c]. var addClosingParen; if (start !== element.start) { if ((addClosingParen = needsParentheses(element.argument))) { code.overwrite(start, element.start, '( '); } else { code.remove(start, element.start); } } else if (element.parent.type === 'CallExpression') { // CallExpression inserts `( ` itself, we add the ). // (Yeah, CallExpression did the needsParentheses call already, // but we don’t have its result handy, so do it again. It’s cheap.) addClosingParen = needsParentheses(element.argument); } else { // Should be unreachable, but doing this is more robust. throw new CompileError( 'Unsupported spread construct, please raise an issue at https://github.com/bublejs/buble/issues', element ); } code.overwrite(element.end, elements[1].start, addClosingParen ? ' ).concat( ' : '.concat( '); } else { code.overwrite(previousElement.end, element.start, ' ].concat( '); } for (i = firstSpreadIndex; i < elements.length; i += 1) { element = elements[i]; if (element) { if (element.type === 'SpreadElement') { code.remove(element.start, element.argument.start); } else { code.appendLeft(element.start, '['); code.appendLeft(element.end, ']'); } } } return true; // true indicates some spread elements } class ArrayExpression extends Node { initialise(transforms) { if (transforms.spreadRest && this.elements.length) { var lexicalBoundary = this.findLexicalBoundary(); var i = this.elements.length; while (i--) { var element = this.elements[i]; if ( element && element.type === 'SpreadElement' && isArguments(element.argument) ) { this.argumentsArrayAlias = lexicalBoundary.getArgumentsArrayAlias(); } } } super.initialise(transforms); } transpile(code, transforms) { super.transpile(code, transforms); if (transforms.spreadRest) { inlineSpreads(code, this, this.elements); // erase trailing comma after last array element if not an array hole if (this.elements.length) { var lastElement = this.elements[this.elements.length - 1]; if ( lastElement && /\s*,/.test(code.original.slice(lastElement.end, this.end)) ) { code.overwrite(lastElement.end, this.end - 1, ' '); } } if (this.elements.length === 1) { var element = this.elements[0]; if (element && element.type === 'SpreadElement') { // special case – [ ...arguments ] if (isArguments(element.argument)) { code.overwrite( this.start, this.end, `[].concat( ${this.argumentsArrayAlias} )` ); // TODO if this is the only use of argsArray, don't bother concating } else { code.overwrite(this.start, element.argument.start, '[].concat( '); code.overwrite(element.end, this.end, ' )'); } } } else { var hasSpreadElements = spread( code, this.elements, this.start, this.argumentsArrayAlias ); if (hasSpreadElements) { code.overwrite(this.end - 1, this.end, ')'); } } } } } function removeTrailingComma(code, c) { while (code.original[c] !== ')') { if (code.original[c] === ',') { code.remove(c, c + 1); return; } if (code.original[c] === '/') { if (code.original[c + 1] === '/') { c = code.original.indexOf('\n', c); } else { c = code.original.indexOf('*/', c) + 1; } } c += 1; } } class ArrowFunctionExpression extends Node { initialise(transforms) { if (this.async && transforms.asyncAwait) { CompileError.missingTransform("async arrow functions", "asyncAwait", this); } this.body.createScope(); super.initialise(transforms); } transpile(code, transforms) { var openParensPos = this.start; for (var end = (this.body || this.params[0]).start - 1; code.original[openParensPos] !== '(' && openParensPos < end;) { ++openParensPos; } if (code.original[openParensPos] !== '(') { openParensPos = -1; } var naked = openParensPos === -1; if (transforms.arrow || this.needsArguments(transforms)) { // remove arrow var charIndex = this.body.start; while (code.original[charIndex] !== '=') { charIndex -= 1; } code.remove(charIndex, this.body.start); super.transpile(code, transforms); // wrap naked parameter if (naked) { code.prependRight(this.params[0].start, '('); code.appendLeft(this.params[0].end, ')'); } // standalone expression statement var standalone = this.parent && this.parent.type === 'ExpressionStatement'; var start, text = standalone ? '!' : ''; if (this.async) { text += 'async '; } text += 'function'; if (!standalone) { text += ' '; } if (naked) { start = this.params[0].start; } else { start = openParensPos; } // add function if (start > this.start) { code.overwrite(this.start, start, text); } else { code.prependRight(this.start, text); } } else { super.transpile(code, transforms); } if (transforms.trailingFunctionCommas && this.params.length && !naked) { removeTrailingComma(code, this.params[this.params.length - 1].end); } } // Returns whether any transforms that will happen use `arguments` needsArguments(transforms) { return ( transforms.spreadRest && this.params.filter(param => param.type === 'RestElement').length > 0 ); } } function checkConst(identifier, scope) { var declaration = scope.findDeclaration(identifier.name); if (declaration && declaration.kind === 'const') { throw new CompileError(`${identifier.name} is read-only`, identifier); } } class AssignmentExpression extends Node { initialise(transforms) { if (this.left.type === 'Identifier') { var declaration = this.findScope(false).findDeclaration(this.left.name); // special case – https://gitlab.com/Rich-Harris/buble/issues/11 var statement = declaration && declaration.node.ancestor(3); if ( statement && statement.type === 'ForStatement' && statement.body.contains(this) ) { statement.reassigned[this.left.name] = true; } } super.initialise(transforms); } transpile(code, transforms) { if (this.left.type === 'Identifier') { // Do this check after everything has been initialized to find // shadowing declarations after this expression checkConst(this.left, this.findScope(false)); } if (this.operator === '**=' && transforms.exponentiation) { this.transpileExponentiation(code, transforms); } else if (/Pattern/.test(this.left.type) && transforms.destructuring) { this.transpileDestructuring(code); } super.transpile(code, transforms); } transpileDestructuring(code) { var writeScope = this.findScope(true); var lookupScope = this.findScope(false); var assign = writeScope.createDeclaration('assign'); code.appendRight(this.left.end, `(${assign}`); code.appendLeft(this.right.end, ', '); var statementGenerators = []; destructure( code, id => writeScope.createDeclaration(id), node => { var name = lookupScope.resolveName(node.name); checkConst(node, lookupScope); return name; }, this.left, assign, true, statementGenerators ); var suffix = ', '; statementGenerators.forEach((fn, j) => { if (j === statementGenerators.length - 1) { suffix = ''; } fn(this.end, '', suffix); }); if (this.unparenthesizedParent().type === 'ExpressionStatement') { // no rvalue needed for expression statement code.prependRight(this.end, `)`); } else { // destructuring is part of an expression - need an rvalue code.appendRight(this.end, `, ${assign})`); } } transpileExponentiation(code) { var scope = this.findScope(false); // first, the easy part – `**=` -> `=` var charIndex = this.left.end; while (code.original[charIndex] !== '*') { charIndex += 1; } code.remove(charIndex, charIndex + 2); // how we do the next part depends on a number of factors – whether // this is a top-level statement, and whether we're updating a // simple or complex reference var base; var left = this.left.unparenthesize(); if (left.type === 'Identifier') { base = scope.resolveName(left.name); } else if (left.type === 'MemberExpression') { var object; var needsObjectVar = false; var property; var needsPropertyVar = false; var statement = this.findNearest(/(?:Statement|Declaration)$/); var i0 = statement.getIndentation(); if (left.property.type === 'Identifier') { property = left.computed ? scope.resolveName(left.property.name) : left.property.name; } else { property = scope.createDeclaration('property'); needsPropertyVar = true; } if (left.object.type === 'Identifier') { object = scope.resolveName(left.object.name); } else { object = scope.createDeclaration('object'); needsObjectVar = true; } if (left.start === statement.start) { if (needsObjectVar && needsPropertyVar) { code.prependRight(statement.start, `${object} = `); code.overwrite( left.object.end, left.property.start, `;\n${i0}${property} = ` ); code.overwrite( left.property.end, left.end, `;\n${i0}${object}[${property}]` ); } else if (needsObjectVar) { code.prependRight(statement.start, `${object} = `); code.appendLeft(left.object.end, `;\n${i0}`); code.appendLeft(left.object.end, object); } else if (needsPropertyVar) { code.prependRight(left.property.start, `${property} = `); code.appendLeft(left.property.end, `;\n${i0}`); code.move(left.property.start, left.property.end, this.start); code.appendLeft(left.object.end, `[${property}]`); code.remove(left.object.end, left.property.start); code.remove(left.property.end, left.end); } } else { if (needsObjectVar && needsPropertyVar) { code.prependRight(left.start, `( ${object} = `); code.overwrite( left.object.end, left.property.start, `, ${property} = ` ); code.overwrite( left.property.end, left.end, `, ${object}[${property}]` ); } else if (needsObjectVar) { code.prependRight(left.start, `( ${object} = `); code.appendLeft(left.object.end, `, ${object}`); } else if (needsPropertyVar) { code.prependRight(left.property.start, `( ${property} = `); code.appendLeft(left.property.end, `, `); code.move(left.property.start, left.property.end, left.start); code.overwrite(left.object.end, left.property.start, `[${property}]`); code.remove(left.property.end, left.end); } if (needsPropertyVar) { code.appendLeft(this.end, ` )`); } } base = object + (left.computed || needsPropertyVar ? `[${property}]` : `.${property}`); } code.prependRight(this.right.start, `Math.pow( ${base}, `); code.appendLeft(this.right.end, ` )`); } } class AwaitExpression extends Node { initialise(transforms) { if (transforms.asyncAwait) { CompileError.missingTransform("await", "asyncAwait", this); } super.initialise(transforms); } } class BinaryExpression extends Node { transpile(code, transforms) { if (this.operator === '**' && transforms.exponentiation) { code.prependRight(this.start, `Math.pow( `); code.overwrite(this.left.end, this.right.start, `, `); code.appendLeft(this.end, ` )`); } super.transpile(code, transforms); } } var loopStatement = /(?:For(?:In|Of)?|While)Statement/; class BreakStatement extends Node { initialise() { var loop = this.findNearest(loopStatement); var switchCase = this.findNearest('SwitchCase'); if (loop && (!switchCase || loop.depth > switchCase.depth)) { loop.canBreak = true; this.loop = loop; } } transpile(code) { if (this.loop && this.loop.shouldRewriteAsFunction) { if (this.label) { throw new CompileError( 'Labels are not currently supported in a loop with locally-scoped variables', this ); } code.overwrite(this.start, this.start + 5, `return 'break'`); } } } class CallExpression extends Node { initialise(transforms) { if (transforms.spreadRest && this.arguments.length > 1) { var lexicalBoundary = this.findLexicalBoundary(); var i = this.arguments.length; while (i--) { var arg = this.arguments[i]; if (arg.type === 'SpreadElement' && isArguments(arg.argument)) { this.argumentsArrayAlias = lexicalBoundary.getArgumentsArrayAlias(); } } } super.initialise(transforms); } transpile(code, transforms) { if (transforms.spreadRest && this.arguments.length) { inlineSpreads(code, this, this.arguments); // this.arguments.length may have changed, must retest. } if (transforms.spreadRest && this.arguments.length) { var hasSpreadElements = false; var context; var firstArgument = this.arguments[0]; if (this.arguments.length === 1) { if (firstArgument.type === 'SpreadElement') { code.remove(firstArgument.start, firstArgument.argument.start); hasSpreadElements = true; } } else { hasSpreadElements = spread( code, this.arguments, firstArgument.start, this.argumentsArrayAlias ); } if (hasSpreadElements) { // we need to handle super() and super.method() differently // due to its instance var _super = null; if (this.callee.type === 'Super') { _super = this.callee; } else if ( this.callee.type === 'MemberExpression' && this.callee.object.type === 'Super' ) { _super = this.callee.object; } if (!_super && this.callee.type === 'MemberExpression') { if (this.callee.object.type === 'Identifier') { context = this.callee.object.name; } else { context = this.findScope(true).createDeclaration('ref'); var callExpression = this.callee.object; code.prependRight(callExpression.start, `(${context} = `); code.appendLeft(callExpression.end, `)`); } } else { context = 'void 0'; } code.appendLeft(this.callee.end, '.apply'); if (_super) { _super.noCall = true; // bit hacky... if (this.arguments.length > 1) { if (firstArgument.type === 'SpreadElement') { if (needsParentheses(firstArgument.argument)) { code.prependRight(firstArgument.start, `( `); } } else { code.prependRight(firstArgument.start, `[ `); } code.appendLeft( this.arguments[this.arguments.length - 1].end, ' )' ); } } else if (this.arguments.length === 1) { code.prependRight(firstArgument.start, `${context}, `); } else { if (firstArgument.type === 'SpreadElement') { if (needsParentheses(firstArgument.argument)) { code.appendLeft(firstArgument.start, `${context}, ( `); } else { code.appendLeft(firstArgument.start, `${context}, `); } } else { code.appendLeft(firstArgument.start, `${context}, [ `); } code.appendLeft(this.arguments[this.arguments.length - 1].end, ' )'); } } } if (transforms.trailingFunctionCommas && this.arguments.length) { removeTrailingComma(code, this.arguments[this.arguments.length - 1].end); } super.transpile(code, transforms); } } class CatchClause extends Node { initialise(transforms) { this.createdDeclarations = []; this.scope = new Scope({ block: true, parent: this.parent.findScope(false), declare: id => this.createdDeclarations.push(id) }); this.scope.addDeclaration(this.param, 'catch'); super.initialise(transforms); this.scope.consolidate(); } findScope(functionScope) { return functionScope ? this.parent.findScope(functionScope) : this.scope; } } // TODO this code is pretty wild, tidy it up class ClassBody extends Node { transpile(code, transforms, inFunctionExpression, superName) { if (transforms.classes) { var name = this.parent.name; var indentStr = code.getIndentString(); var i0 = this.getIndentation() + (inFunctionExpression ? indentStr : ''); var i1 = i0 + indentStr; var constructorIndex = findIndex( this.body, node => node.kind === 'constructor' ); var constructor = this.body[constructorIndex]; var introBlock = ''; var outroBlock = ''; if (this.body.length) { code.remove(this.start, this.body[0].start); code.remove(this.body[this.body.length - 1].end, this.end); } else { code.remove(this.start, this.end); } if (constructor) { constructor.value.body.isConstructorBody = true; var previousMethod = this.body[constructorIndex - 1]; var nextMethod = this.body[constructorIndex + 1]; // ensure constructor is first if (constructorIndex > 0) { code.remove(previousMethod.end, constructor.start); code.move( constructor.start, nextMethod ? nextMethod.start : this.end - 1, this.body[0].start ); } if (!inFunctionExpression) { code.appendLeft(constructor.end, ';'); } } var namedFunctions = this.program.options.namedFunctionExpressions !== false; var namedConstructor = namedFunctions || this.parent.superClass || this.parent.type !== 'ClassDeclaration'; if (this.parent.superClass) { var inheritanceBlock = `if ( ${superName} ) ${name}.__proto__ = ${ superName };\n${i0}${name}.prototype = Object.create( ${superName} && ${ superName }.prototype );\n${i0}${name}.prototype.constructor = ${name};`; if (constructor) { introBlock += `\n\n${i0}` + inheritanceBlock; } else { var fn = `function ${name} () {` + (superName ? `\n${i1}${superName}.apply(this, arguments);\n${i0}}` : `}`) + (inFunctionExpression ? '' : ';') + (this.body.length ? `\n\n${i0}` : ''); inheritanceBlock = fn + inheritanceBlock; introBlock += inheritanceBlock + `\n\n${i0}`; } } else if (!constructor) { var fn$1 = 'function ' + (namedConstructor ? name + ' ' : '') + '() {}'; if (this.parent.type === 'ClassDeclaration') { fn$1 += ';'; } if (this.body.length) { fn$1 += `\n\n${i0}`; } introBlock += fn$1; } var scope = this.findScope(false); var prototypeGettersAndSetters = []; var staticGettersAndSetters = []; var prototypeAccessors; var staticAccessors; this.body.forEach((method, i) => { if ((method.kind === 'get' || method.kind === 'set') && transforms.getterSetter) { CompileError.missingTransform("getters and setters", "getterSetter", method); } if (method.kind === 'constructor') { var constructorName = namedConstructor ? ' ' + name : ''; code.overwrite( method.key.start, method.key.end, `function${constructorName}` ); return; } if (method.static) { var len = code.ori