This commit is contained in:
chrosey
2017-09-13 07:52:34 +02:00
parent a1f16c37f4
commit 2340b0226b
24621 changed files with 2912161 additions and 149 deletions
+81
View File
@@ -0,0 +1,81 @@
import acorn from 'acorn';
import acornJsx from 'acorn-jsx/inject';
import acornObjectSpread from 'acorn-object-spread/inject';
import Program from './program/Program.js';
import { features, matrix } from './support.js';
import getSnippet from './utils/getSnippet.js';
const { parse } = [
acornObjectSpread,
acornJsx
].reduce( ( final, plugin ) => plugin( final ), acorn );
const dangerousTransforms = [
'dangerousTaggedTemplateString',
'dangerousForOf'
];
export function target ( target ) {
const targets = Object.keys( target );
let bitmask = targets.length ?
0b1111111111111111111111111111111 :
0b1000000000000000000000000000000;
Object.keys( target ).forEach( environment => {
const versions = matrix[ environment ];
if ( !versions ) throw new Error( `Unknown environment '${environment}'. Please raise an issue at https://gitlab.com/Rich-Harris/buble/issues` );
const targetVersion = target[ environment ];
if ( !( targetVersion in versions ) ) throw new Error( `Support data exists for the following versions of ${environment}: ${Object.keys( versions ).join( ', ')}. Please raise an issue at https://gitlab.com/Rich-Harris/buble/issues` );
const support = versions[ targetVersion ];
bitmask &= support;
});
let transforms = Object.create( null );
features.forEach( ( name, i ) => {
transforms[ name ] = !( bitmask & 1 << i );
});
dangerousTransforms.forEach( name => {
transforms[ name ] = false;
});
return transforms;
}
export function transform ( source, options = {} ) {
let ast;
try {
ast = parse( source, {
ecmaVersion: 7,
preserveParens: true,
sourceType: 'module',
plugins: {
jsx: true,
objectSpread: true
}
});
} catch ( err ) {
err.snippet = getSnippet( source, err.loc );
err.toString = () => `${err.name}: ${err.message}\n${err.snippet}`;
throw err;
}
let transforms = target( options.target || {} );
Object.keys( options.transforms || {} ).forEach( name => {
if ( name === 'modules' ) {
if ( !( 'moduleImport' in options.transforms ) ) transforms.moduleImport = options.transforms.modules;
if ( !( 'moduleExport' in options.transforms ) ) transforms.moduleExport = options.transforms.modules;
return;
}
if ( !( name in transforms ) ) throw new Error( `Unknown transform '${name}'` );
transforms[ name ] = options.transforms[ name ];
});
return new Program( source, ast, transforms, options ).export( options );
}
export { version as VERSION } from '../package.json';
+305
View File
@@ -0,0 +1,305 @@
import wrap from './wrap.js';
import Node from './Node.js';
import Scope from './Scope.js';
import CompileError from '../utils/CompileError.js';
import destructure from '../utils/destructure.js';
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';
}
export default 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 )
});
if ( this.parentIsFunction ) {
this.parent.params.forEach( node => {
this.scope.addDeclaration( node, 'param' );
});
}
}
initialise ( transforms ) {
this.thisAlias = null;
this.argumentsAlias = null;
this.defaultParameters = [];
// 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 ) {
const source = this.program.magicString.original;
const useOuter = this.synthetic || !this.body.length;
let c = useOuter ? this.start : this.body[0].start;
while ( c && source[c] !== '\n' ) c -= 1;
this.indentation = '';
while ( true ) {
c += 1;
const char = source[c];
if ( char !== ' ' && char !== '\t' ) break;
this.indentation += char;
}
const indentString = this.program.magicString.getIndentString();
// account for dedented class constructors
let 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 ) {
const indentation = this.getIndentation();
let introStatementGenerators = [];
if ( this.argumentsAlias ) {
introStatementGenerators.push( ( start, prefix, suffix ) => {
const assignment = `${prefix}var ${this.argumentsAlias} = arguments;${suffix}`;
code.insertLeft( start, assignment );
});
}
if ( this.thisAlias ) {
introStatementGenerators.push( ( start, prefix, suffix ) => {
const assignment = `${prefix}var ${this.thisAlias} = this;${suffix}`;
code.insertLeft( start, assignment );
});
}
if ( this.argumentsArrayAlias ) {
introStatementGenerators.push( ( start, prefix, suffix ) => {
const i = this.scope.createIdentifier( 'i' );
const assignment = `${prefix}var ${i} = arguments.length, ${this.argumentsArrayAlias} = Array(${i});\n${indentation}while ( ${i}-- ) ${this.argumentsArrayAlias}[${i}] = arguments[${i}];${suffix}`;
code.insertLeft( start, assignment );
});
}
if ( /Function/.test( this.parent.type ) ) {
this.transpileParameters( code, transforms, indentation, introStatementGenerators );
}
if ( transforms.letConst && this.isFunctionBlock ) {
this.transpileBlockScopedIdentifiers( code );
}
super.transpile( code, transforms );
if ( this.synthetic ) {
if ( this.parent.type === 'ArrowFunctionExpression' ) {
const expr = this.body[0];
if ( introStatementGenerators.length ) {
code.insertLeft( this.start, `{` ).insertRight( this.end, `${this.parent.getIndentation()}}` );
code.insertRight( expr.start, `\n${indentation}return ` );
code.insertLeft( expr.end, `;\n` );
} else if ( transforms.arrow ) {
code.insertRight( expr.start, `{ return ` );
code.insertLeft( expr.end, `; }` );
}
}
else if ( introStatementGenerators.length ) {
code.insertLeft( this.start, `{` ).insertRight( this.end, `}` );
}
}
let 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;
}
let prefix = `\n${indentation}`;
let suffix = '';
introStatementGenerators.forEach( ( fn, i ) => {
if ( i === introStatementGenerators.length - 1 ) suffix = `\n`;
fn( start, prefix, suffix );
});
}
transpileParameters ( code, transforms, indentation, introStatementGenerators ) {
const params = this.parent.params;
params.forEach( param => {
if ( param.type === 'AssignmentPattern' && param.left.type === 'Identifier' ) {
if ( transforms.defaultParameter ) {
introStatementGenerators.push( ( start, prefix, suffix ) => {
const lhs = `${prefix}if ( ${param.left.name} === void 0 ) ${param.left.name}`;
code
.insertRight( param.left.end, `${lhs}` )
.move( param.left.end, param.right.end, start )
.insertLeft( param.right.end, `;${suffix}` );
});
}
}
else if ( param.type === 'RestElement' ) {
if ( transforms.spreadRest ) {
introStatementGenerators.push( ( start, prefix, suffix ) => {
const penultimateParam = params[ params.length - 2 ];
if ( penultimateParam ) {
code.remove( penultimateParam ? penultimateParam.end : param.start, param.end );
} else {
let start = param.start, end = param.end; // TODO https://gitlab.com/Rich-Harris/buble/issues/8
while ( /\s/.test( code.original[ start - 1 ] ) ) start -= 1;
while ( /\s/.test( code.original[ end ] ) ) end += 1;
code.remove( start, end );
}
const name = param.argument.name;
const len = this.scope.createIdentifier( 'len' );
const count = params.length - 1;
if ( count ) {
code.insertLeft( start, `${prefix}var ${name} = [], ${len} = arguments.length - ${count};\n${indentation}while ( ${len}-- > 0 ) ${name}[ ${len} ] = arguments[ ${len} + ${count} ];${suffix}` );
} else {
code.insertLeft( start, `${prefix}var ${name} = [], ${len} = arguments.length;\n${indentation}while ( ${len}-- ) ${name}[ ${len} ] = arguments[ ${len} ];${suffix}` );
}
});
}
}
else if ( param.type !== 'Identifier' ) {
if ( transforms.parameterDestructuring ) {
const ref = this.scope.createIdentifier( 'ref' );
destructure( code, this.scope, param, ref, introStatementGenerators );
code.insertLeft( param.start, ref );
}
}
});
}
transpileBlockScopedIdentifiers ( code ) {
Object.keys( this.scope.blockScopedDeclarations ).forEach( name => {
const declarations = this.scope.blockScopedDeclarations[ name ];
for ( let declaration of declarations ) {
let cont = false; // TODO implement proper continue...
if ( declaration.kind === 'for.let' ) {
// special case
const forStatement = declaration.node.findNearest( 'ForStatement' );
if ( forStatement.shouldRewriteAsFunction ) {
const outerAlias = this.scope.createIdentifier( name );
const innerAlias = forStatement.reassigned[ name ] ?
this.scope.createIdentifier( name ) :
name;
declaration.name = outerAlias;
code.overwrite( declaration.node.start, declaration.node.end, outerAlias, true );
forStatement.aliases[ name ] = {
outer: outerAlias,
inner: innerAlias
};
for ( const identifier of declaration.instances ) {
const alias = forStatement.body.contains( identifier ) ?
innerAlias :
outerAlias;
if ( name !== alias ) {
code.overwrite( identifier.start, identifier.end, alias, true );
}
}
cont = true;
}
}
if ( !cont ) {
const alias = this.scope.createIdentifier( name );
if ( name !== alias ) {
declaration.name = alias;
code.overwrite( declaration.node.start, declaration.node.end, alias, true );
for ( const identifier of declaration.instances ) {
identifier.rewritten = true;
code.overwrite( identifier.start, identifier.end, alias, true );
}
}
}
}
});
}
}
+108
View File
@@ -0,0 +1,108 @@
import wrap from './wrap.js';
import keys from './keys.js';
// 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;
}
export default class Node {
constructor ( raw, parent ) {
raw.parent = parent;
raw.program = parent.program || parent;
raw.depth = parent.depth + 1;
raw.keys = keys[ raw.type ];
raw.indentation = undefined;
for ( const key of keys[ raw.type ] ) {
wrap( raw[ key ], raw );
}
raw.program.magicString.addSourcemapLocation( raw.start );
raw.program.magicString.addSourcemapLocation( raw.end );
}
ancestor ( level ) {
let 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 );
}
findScope ( functionScope ) {
return this.parent.findScope( functionScope );
}
getIndentation () {
return this.parent.getIndentation();
}
initialise ( transforms ) {
for ( var key of this.keys ) {
const 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 ( const key of this.keys ) {
const 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 );
}
}
}
}
+53
View File
@@ -0,0 +1,53 @@
import MagicString from 'magic-string';
import BlockStatement from './BlockStatement.js';
import wrap from './wrap.js';
export default function Program ( source, ast, transforms, options ) {
this.type = 'Root';
// options
this.jsx = options.jsx || 'React.createElement';
this.objectAssign = options.objectAssign || 'Object.assign';
this.source = source;
this.magicString = new MagicString( source );
this.ast = ast;
this.depth = 0;
wrap( this.body = ast, this );
this.body.__proto__ = BlockStatement.prototype;
this.templateElements = [];
this.body.initialise( transforms );
this.indentExclusions = {};
for ( const node of this.templateElements ) {
for ( let i = node.start; i < node.end; i += 1 ) {
this.indentExclusions[ node.start + i ] = true;
}
}
this.body.transpile( this.magicString, transforms );
}
Program.prototype = {
export ( options = {} ) {
return {
code: this.magicString.toString(),
map: this.magicString.generateMap({
file: options.file,
source: options.source,
includeContent: options.includeContent !== false
})
};
},
findNearest () {
return null;
},
findScope () {
return null;
}
};
+98
View File
@@ -0,0 +1,98 @@
import extractNames from './extractNames.js';
import reserved from '../utils/reserved.js';
import CompileError from '../utils/CompileError.js';
const letConst = /^(?:let|const)$/;
export default function Scope ( options ) {
options = options || {};
this.parent = options.parent;
this.isBlockScope = !!options.block;
let 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 = this.isBlockScope ? null : Object.create( null );
}
Scope.prototype = {
addDeclaration ( node, kind ) {
for ( const identifier of extractNames( node ) ) {
const name = identifier.name;
const existingDeclaration = this.declarations[ name ];
if ( existingDeclaration && ( letConst.test( kind ) || letConst.test( existingDeclaration.kind ) ) ) {
// TODO warn about double var declarations?
throw new CompileError( identifier, `${name} is already declared` );
}
const 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 ( let i = 0; i < this.identifiers.length; i += 1 ) { // we might push to the array during consolidation, so don't cache length
const identifier = this.identifiers[i];
this.consolidateReference( identifier );
}
this.consolidated = true; // TODO understand why this is necessary... seems bad
},
consolidateReference ( identifier ) {
const 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 ) {
base = base
.replace( /\s/g, '' )
.replace( /\[([^\]]+)\]/g, '_$1' )
.replace( /[^a-zA-Z0-9_$]/g, '_' )
.replace( /_{2,}/, '_' );
let name = base;
let counter = 1;
while ( this.declarations[ name ] || this.references[ name ] || this.aliases[ name ] || name in reserved ) {
name = `${base}$${counter++}`;
}
this.aliases[ name ] = true;
return name;
},
findDeclaration ( name ) {
return this.declarations[ name ] ||
( this.parent && this.parent.findDeclaration( name ) );
}
};
+31
View File
@@ -0,0 +1,31 @@
export default function extractNames ( node ) {
const names = [];
extractors[ node.type ]( names, node );
return names;
}
const extractors = {
Identifier ( names, node ) {
names.push( node );
},
ObjectPattern ( names, node ) {
for ( const prop of node.properties ) {
extractors[ prop.value.type ]( names, prop.value );
}
},
ArrayPattern ( names, node ) {
for ( const element of node.elements ) {
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 );
}
};
+4
View File
@@ -0,0 +1,4 @@
export default {
Program: [ 'body' ],
Literal: []
};
+48
View File
@@ -0,0 +1,48 @@
import Node from '../Node.js';
import spread, { isArguments } from '../../utils/spread.js';
export default class ArrayExpression extends Node {
initialise ( transforms ) {
if ( transforms.spreadRest && this.elements.length ) {
const lexicalBoundary = this.findLexicalBoundary();
let i = this.elements.length;
while ( i-- ) {
const element = this.elements[i];
if ( element && element.type === 'SpreadElement' && isArguments( element.argument ) ) {
this.argumentsArrayAlias = lexicalBoundary.getArgumentsArrayAlias();
}
}
}
super.initialise( transforms );
}
transpile ( code, transforms ) {
if ( transforms.spreadRest ) {
if ( this.elements.length === 1 ) {
const 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 {
const hasSpreadElements = spread( code, this.elements, this.start, this.argumentsArrayAlias );
if ( hasSpreadElements ) {
code.overwrite( this.end - 1, this.end, ')' );
}
}
}
super.transpile( code, transforms );
}
}
+30
View File
@@ -0,0 +1,30 @@
import Node from '../Node.js';
export default class ArrowFunctionExpression extends Node {
initialise ( transforms ) {
this.body.createScope();
super.initialise( transforms );
}
transpile ( code, transforms ) {
if ( transforms.arrow ) {
// remove arrow
let charIndex = this.body.start;
while ( code.original[ charIndex ] !== '=' ) {
charIndex -= 1;
}
code.remove( charIndex, this.body.start );
// wrap naked parameter
if ( this.params.length === 1 && this.start === this.params[0].start ) {
code.insertRight( this.params[0].start, '(' );
code.insertLeft( this.params[0].end, ')' );
}
// add function
code.insertRight( this.start, 'function ' );
}
super.transpile( code, transforms );
}
}
+134
View File
@@ -0,0 +1,134 @@
import Node from '../Node.js';
import CompileError from '../../utils/CompileError.js';
export default class AssignmentExpression extends Node {
initialise ( transforms ) {
if ( this.left.type === 'Identifier' ) {
const declaration = this.findScope( false ).findDeclaration( this.left.name );
if ( declaration && declaration.kind === 'const' ) {
throw new CompileError( this.left, `${this.left.name} is read-only` );
}
// special case https://gitlab.com/Rich-Harris/buble/issues/11
const statement = declaration && declaration.node.ancestor( 3 );
if ( statement && statement.type === 'ForStatement' && statement.body.contains( this ) ) {
statement.reassigned[ this.left.name ] = true;
}
}
if ( /Pattern/.test( this.left.type ) ) {
throw new CompileError( this.left, 'Destructuring assignments are not currently supported. Coming soon!' );
}
super.initialise( transforms );
}
transpile ( code, transforms ) {
if ( this.operator === '**=' && transforms.exponentiation ) {
const scope = this.findScope( false );
const getAlias = name => {
const declaration = scope.findDeclaration( name );
return declaration ? declaration.name : name;
};
// first, the easy part `**=` -> `=`
let 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
let base;
let left = this.left;
while ( left.type === 'ParenthesizedExpression' ) left = left.expression;
if ( left.type === 'Identifier' ) {
base = getAlias( left.name );
} else if ( left.type === 'MemberExpression' ) {
let object;
let needsObjectVar = false;
let property;
let needsPropertyVar = false;
const statement = this.findNearest( /(?:Statement|Declaration)$/ );
const i0 = statement.getIndentation();
if ( left.property.type === 'Identifier' ) {
property = left.computed ? getAlias( left.property.name ) : left.property.name;
} else {
property = scope.createIdentifier( 'property' );
needsPropertyVar = true;
}
if ( left.object.type === 'Identifier' ) {
object = getAlias( left.object.name );
} else {
object = scope.createIdentifier( 'object' );
needsObjectVar = true;
}
if ( left.start === statement.start ) {
if ( needsObjectVar && needsPropertyVar ) {
code.insertRight( statement.start, `var ${object} = ` );
code.overwrite( left.object.end, left.property.start, `;\n${i0}var ${property} = ` );
code.overwrite( left.property.end, left.end, `;\n${i0}${object}[${property}]` );
}
else if ( needsObjectVar ) {
code.insertRight( statement.start, `var ${object} = ` );
code.insertLeft( left.object.end, `;\n${i0}` );
code.insertLeft( left.object.end, object );
}
else if ( needsPropertyVar ) {
code.insertRight( left.property.start, `var ${property} = ` );
code.insertLeft( left.property.end, `;\n${i0}` );
code.move( left.property.start, left.property.end, this.start );
code.insertLeft( left.object.end, `[${property}]` );
code.remove( left.object.end, left.property.start );
code.remove( left.property.end, left.end );
}
}
else {
let declarators = [];
if ( needsObjectVar ) declarators.push( object );
if ( needsPropertyVar ) declarators.push( property );
code.insertRight( statement.start, `var ${declarators.join( ', ' )};\n${i0}` );
if ( needsObjectVar && needsPropertyVar ) {
code.insertRight( 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.insertRight( left.start, `( ${object} = ` );
code.insertLeft( left.object.end, `, ${object}` );
}
else if ( needsPropertyVar ) {
code.insertRight( left.property.start, `( ${property} = ` );
code.insertLeft( 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 );
}
code.insertLeft( this.end, ` )` );
}
base = object + ( left.computed || needsPropertyVar ? `[${property}]` : `.${property}` );
}
code.insertRight( this.right.start, `Math.pow( ${base}, ` );
code.insertLeft( this.right.end, ` )` );
}
super.transpile( code, transforms );
}
}
+12
View File
@@ -0,0 +1,12 @@
import Node from '../Node.js';
export default class BinaryExpression extends Node {
transpile ( code, transforms ) {
if ( this.operator === '**' && transforms.exponentiation ) {
code.insertRight( this.start, `Math.pow( ` );
code.overwrite( this.left.end, this.right.start, `, ` );
code.insertLeft( this.end, ` )` );
}
super.transpile( code, transforms );
}
}
+21
View File
@@ -0,0 +1,21 @@
import Node from '../Node.js';
import CompileError from '../../utils/CompileError.js';
export default class BreakStatement extends Node {
initialise ( transforms ) {
const loop = this.findNearest( /(?:For(?:In)?|While)Statement/ );
const switchCase = this.findNearest( 'SwitchCase' );
if ( loop && ( !switchCase || loop.depth > switchCase.depth ) ) {
loop.canBreak = true;
this.loop = loop;
}
}
transpile ( code, transforms ) {
if ( this.loop && this.loop.shouldRewriteAsFunction ) {
if ( this.label ) throw new CompileError( this, 'Labels are not currently supported in a loop with locally-scoped variables' );
code.overwrite( this.start, this.start + 5, `return 'break'` );
}
}
}
+86
View File
@@ -0,0 +1,86 @@
import Node from '../Node.js';
import spread, { isArguments } from '../../utils/spread.js';
export default class CallExpression extends Node {
initialise ( transforms ) {
if ( transforms.spreadRest && this.arguments.length > 1 ) {
const lexicalBoundary = this.findLexicalBoundary();
let i = this.arguments.length;
while ( i-- ) {
const 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 ) {
let hasSpreadElements = false;
let context;
const 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 ) {
if ( this.callee.type === 'MemberExpression' ) {
if ( this.callee.object.type === 'Identifier' ) {
context = this.callee.object.name;
} else {
const statement = this.callee.object;
const i0 = statement.getIndentation();
context = this.findScope( true ).createIdentifier( 'ref' );
code.insertRight( statement.start, `var ${context} = ` );
code.insertLeft( statement.end, `;\n${i0}${context}` );
}
} else {
context = 'void 0';
}
code.insertLeft( this.callee.end, '.apply' );
// we need to handle `super()` different, because `SuperClass.call.apply`
// isn't very helpful
const isSuper = this.callee.type === 'Super';
if ( isSuper ) {
this.callee.noCall = true; // bit hacky...
if ( this.arguments.length > 1 ) {
if ( firstArgument.type !== 'SpreadElement' ) {
code.insertRight( firstArgument.start, `[ ` );
}
code.insertLeft( this.arguments[ this.arguments.length - 1 ].end, ' )' );
}
}
else if ( this.arguments.length === 1 ) {
code.insertRight( firstArgument.start, `${context}, ` );
} else {
if ( firstArgument.type === 'SpreadElement' ) {
code.insertRight( firstArgument.start, `${context}, ` );
} else {
code.insertRight( firstArgument.start, `${context}, [ ` );
}
code.insertLeft( this.arguments[ this.arguments.length - 1 ].end, ' )' );
}
}
}
super.transpile( code, transforms );
}
}
+164
View File
@@ -0,0 +1,164 @@
import Node from '../Node.js';
import { findIndex } from '../../utils/array.js';
import reserved from '../../utils/reserved.js';
// TODO this code is pretty wild, tidy it up
export default class ClassBody extends Node {
transpile ( code, transforms, inFunctionExpression, superName ) {
if ( transforms.classes ) {
const name = this.parent.name;
const indentStr = code.getIndentString();
const i0 = this.getIndentation() + ( inFunctionExpression ? indentStr : '' );
const i1 = i0 + indentStr;
const constructorIndex = findIndex( this.body, node => node.kind === 'constructor' );
const constructor = this.body[ constructorIndex ];
let introBlock = '';
let 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;
const previousMethod = this.body[ constructorIndex - 1 ];
const 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.insertLeft( constructor.end, ';' );
}
if ( this.parent.superClass ) {
let 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 {
const 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 ) {
let fn = `function ${name} () {}`;
if ( this.parent.type === 'ClassDeclaration' ) fn += ';';
if ( this.body.length ) fn += `\n\n${i0}`;
introBlock += fn;
}
const scope = this.findScope( false );
let prototypeGettersAndSetters = [];
let staticGettersAndSetters = [];
let prototypeAccessors;
let staticAccessors;
this.body.forEach( ( method, i ) => {
if ( method.kind === 'constructor' ) {
code.overwrite( method.key.start, method.key.end, `function ${name}` );
return;
}
if ( method.static ) code.remove( method.start, method.start + 7 );
const isAccessor = method.kind !== 'method';
let lhs;
let methodName = method.key.name;
if ( scope.contains( methodName ) || reserved[ methodName ] ) methodName = scope.createIdentifier( methodName );
if ( isAccessor ) {
if ( method.computed ) {
throw new Error( 'Computed accessor properties are not currently supported' );
}
code.remove( method.start, method.key.start );
if ( method.static ) {
if ( !~staticGettersAndSetters.indexOf( method.key.name ) ) staticGettersAndSetters.push( method.key.name );
if ( !staticAccessors ) staticAccessors = scope.createIdentifier( 'staticAccessors' );
lhs = `${staticAccessors}`;
} else {
if ( !~prototypeGettersAndSetters.indexOf( method.key.name ) ) prototypeGettersAndSetters.push( method.key.name );
if ( !prototypeAccessors ) prototypeAccessors = scope.createIdentifier( 'prototypeAccessors' );
lhs = `${prototypeAccessors}`;
}
} else {
lhs = method.static ?
`${name}` :
`${name}.prototype`;
}
if ( !method.computed ) lhs += '.';
const insertNewlines = ( constructorIndex > 0 && i === constructorIndex + 1 ) ||
( i === 0 && constructorIndex === this.body.length - 1 );
if ( insertNewlines ) lhs = `\n\n${i0}${lhs}`;
let c = method.key.end;
if ( method.computed ) {
while ( code.original[c] !== ']' ) c += 1;
c += 1;
}
code.insertRight( method.start, lhs );
const rhs = ( isAccessor ? `.${method.kind}` : '' ) + ` = function` + ( method.value.generator ? '* ' : ' ' ) + ( method.computed || isAccessor ? '' : `${methodName} ` );
code.remove( c, method.value.start );
code.insertRight( method.value.start, rhs );
code.insertLeft( method.end, ';' );
if ( method.value.generator ) code.remove( method.start, method.key.start );
});
if ( prototypeGettersAndSetters.length || staticGettersAndSetters.length ) {
let intro = [];
let outro = [];
if ( prototypeGettersAndSetters.length ) {
intro.push( `var ${prototypeAccessors} = { ${prototypeGettersAndSetters.map( name => `${name}: {}` ).join( ',' )} };` );
outro.push( `Object.defineProperties( ${name}.prototype, ${prototypeAccessors} );` );
}
if ( staticGettersAndSetters.length ) {
intro.push( `var ${staticAccessors} = { ${staticGettersAndSetters.map( name => `${name}: {}` ).join( ',' )} };` );
outro.push( `Object.defineProperties( ${name}, ${staticAccessors} );` );
}
if ( constructor ) introBlock += `\n\n${i0}`;
introBlock += intro.join( `\n${i0}` );
if ( !constructor ) introBlock += `\n\n${i0}`;
outroBlock += `\n\n${i0}` + outro.join( `\n${i0}` );
}
if ( constructor ) {
code.insertLeft( constructor.end, introBlock );
} else {
code.insertRight( this.start, introBlock );
}
code.insertLeft( this.end, outroBlock );
}
super.transpile( code, transforms );
}
}
+62
View File
@@ -0,0 +1,62 @@
import Node from '../Node.js';
import deindent from '../../utils/deindent.js';
export default class ClassDeclaration extends Node {
initialise ( transforms ) {
this.name = this.id.name;
this.findScope( true ).addDeclaration( this.id, 'class' );
super.initialise( transforms );
}
transpile ( code, transforms ) {
if ( transforms.classes ) {
if ( !this.superClass ) deindent( this.body, code );
const superName = this.superClass && ( this.superClass.name || 'superclass' );
const i0 = this.getIndentation();
const i1 = i0 + code.getIndentString();
// if this is an export default statement, we have to move the export to
// after the declaration, because `export default var Foo = ...` is illegal
const syntheticDefaultExport = this.parent.type === 'ExportDefaultDeclaration' ?
`\n\n${i0}export default ${this.id.name};` :
'';
if ( syntheticDefaultExport ) code.remove( this.parent.start, this.start );
code.overwrite( this.start, this.id.start, 'var ' );
if ( this.superClass ) {
if ( this.superClass.end === this.body.start ) {
code.remove( this.id.end, this.superClass.start );
code.insertLeft( this.id.end, ` = (function (${superName}) {\n${i1}` );
} else {
code.overwrite( this.id.end, this.superClass.start, ' = ' );
code.overwrite( this.superClass.end, this.body.start, `(function (${superName}) {\n${i1}` );
}
} else {
if ( this.id.end === this.body.start ) {
code.insertLeft( this.id.end, ' = ' );
} else {
code.overwrite( this.id.end, this.body.start, ' = ' );
}
}
this.body.transpile( code, transforms, !!this.superClass, superName );
if ( this.superClass ) {
code.insertLeft( this.end, `\n\n${i1}return ${this.name};\n${i0}}(` );
code.move( this.superClass.start, this.superClass.end, this.end );
code.insertRight( this.end, `));${syntheticDefaultExport}` );
} else if ( syntheticDefaultExport ) {
code.insertRight( this.end, syntheticDefaultExport );
}
}
else {
this.body.transpile( code, transforms, false, null );
}
}
}
+45
View File
@@ -0,0 +1,45 @@
import Node from '../Node.js';
export default class ClassExpression extends Node {
initialise ( transforms ) {
this.name = this.id ? this.id.name :
this.parent.type === 'VariableDeclarator' ? this.parent.id.name :
this.parent.type === 'AssignmentExpression' ? this.parent.left.name :
this.findScope( true ).createIdentifier( 'anonymous' );
super.initialise( transforms );
}
transpile ( code, transforms ) {
if ( transforms.classes ) {
const superName = this.superClass && ( this.superClass.name || 'superclass' );
const i0 = this.getIndentation();
const i1 = i0 + code.getIndentString();
if ( this.superClass ) {
code.remove( this.start, this.superClass.start );
code.remove( this.superClass.end, this.body.start );
code.insertLeft( this.start, `(function (${superName}) {\n${i1}` );
} else {
code.overwrite( this.start, this.body.start, `(function () {\n${i1}` );
}
this.body.transpile( code, transforms, true, superName );
const outro = `\n\n${i1}return ${this.name};\n${i0}}(`;
if ( this.superClass ) {
code.insertLeft( this.end, outro );
code.move( this.superClass.start, this.superClass.end, this.end );
code.insertRight( this.end, '))' );
} else {
code.insertLeft( this.end, `\n\n${i1}return ${this.name};\n${i0}}())` );
}
}
else {
this.body.transpile( code, transforms, false );
}
}
}
+12
View File
@@ -0,0 +1,12 @@
import Node from '../Node.js';
import CompileError from '../../utils/CompileError.js';
export default class ContinueStatement extends Node {
transpile ( code, transforms ) {
const loop = this.findNearest( /(?:For(?:In|Of)?|While)Statement/ );
if ( loop.shouldRewriteAsFunction ) {
if ( this.label ) throw new CompileError( this, 'Labels are not currently supported in a loop with locally-scoped variables' );
code.overwrite( this.start, this.start + 8, 'return' );
}
}
}
+9
View File
@@ -0,0 +1,9 @@
import Node from '../Node.js';
import CompileError from '../../utils/CompileError.js';
export default class ExportDefaultDeclaration extends Node {
initialise ( transforms ) {
if ( transforms.moduleExport ) throw new CompileError( this, 'export is not supported' );
super.initialise( transforms );
}
}
+9
View File
@@ -0,0 +1,9 @@
import Node from '../Node.js';
import CompileError from '../../utils/CompileError.js';
export default class ExportNamedDeclaration extends Node {
initialise ( transforms ) {
if ( transforms.moduleExport ) throw new CompileError( this, 'export is not supported' );
super.initialise( transforms );
}
}
+22
View File
@@ -0,0 +1,22 @@
import LoopStatement from './shared/LoopStatement.js';
import extractNames from '../extractNames.js';
export default class ForInStatement extends LoopStatement {
findScope ( functionScope ) {
return functionScope || !this.createdScope ? this.parent.findScope( functionScope ) : this.body.scope;
}
transpile ( code, transforms ) {
if ( this.shouldRewriteAsFunction ) {
// which variables are declared in the init statement?
const names = this.left.type === 'VariableDeclaration' ?
[].concat.apply( [], this.left.declarations.map( declarator => extractNames( declarator.id ) ) ) :
[];
this.args = names.map( name => name in this.aliases ? this.aliases[ name ].outer : name );
this.params = names.map( name => name in this.aliases ? this.aliases[ name ].inner : name );
}
super.transpile( code, transforms );
}
}
+52
View File
@@ -0,0 +1,52 @@
import LoopStatement from './shared/LoopStatement.js';
import CompileError from '../../utils/CompileError.js';
export default class ForOfStatement extends LoopStatement {
initialise ( transforms ) {
if ( transforms.forOf && !transforms.dangerousForOf ) throw new CompileError( this, 'for...of statements are not supported. Use `transforms: { forOf: false }` to skip transformation and disable this error, or `transforms: { dangerousForOf: true }` if you know what you\'re doing' );
super.initialise( transforms );
}
transpile ( code, transforms ) {
if ( !transforms.dangerousForOf ) {
super.transpile( code, transforms );
return;
}
// edge case (#80)
if ( !this.body.body[0] ) {
if ( this.left.type === 'VariableDeclaration' && this.left.kind === 'var' ) {
code.remove( this.start, this.left.start );
code.insertLeft( this.left.end, ';' );
code.remove( this.left.end, this.end );
} else {
code.remove( this.start, this.end );
}
return;
}
const scope = this.findScope( true );
const i0 = this.getIndentation();
const i1 = i0 + code.getIndentString();
const key = scope.createIdentifier( 'i' );
const list = scope.createIdentifier( 'list' );
if ( this.body.synthetic ) {
code.insertRight( this.left.start, `{\n${i1}` );
code.insertLeft( this.body.body[0].end, `\n${i0}}` );
}
const bodyStart = this.body.body[0].start;
code.remove( this.left.end, this.right.start );
code.move( this.left.start, this.left.end, bodyStart );
code.insertLeft( this.left.end, ` = ${list}[${key}];\n\n${i1}` );
code.insertRight( this.right.start, `var ${key} = 0, ${list} = ` );
code.insertLeft( this.right.end, `; ${key} < ${list}.length; ${key} += 1` );
super.transpile( code, transforms );
}
}
+38
View File
@@ -0,0 +1,38 @@
import LoopStatement from './shared/LoopStatement.js';
import extractNames from '../extractNames.js';
export default class ForStatement extends LoopStatement {
findScope ( functionScope ) {
return functionScope || !this.createdScope ? this.parent.findScope( functionScope ) : this.body.scope;
}
transpile ( code, transforms ) {
const i1 = this.getIndentation() + code.getIndentString();
if ( this.shouldRewriteAsFunction ) {
// which variables are declared in the init statement?
const names = this.init.type === 'VariableDeclaration' ?
[].concat.apply( [], this.init.declarations.map( declarator => extractNames( declarator.id ) ) ) :
[];
const aliases = this.aliases;
this.args = names.map( name => name in this.aliases ? this.aliases[ name ].outer : name );
this.params = names.map( name => name in this.aliases ? this.aliases[ name ].inner : name );
const updates = Object.keys( this.reassigned )
.map( name => `${aliases[ name ].outer} = ${aliases[ name ].inner};` );
if ( updates.length ) {
if ( this.body.synthetic ) {
code.insertLeft( this.body.body[0].end, `; ${updates.join(` `)}` );
} else {
const lastStatement = this.body.body[ this.body.body.length - 1 ];
code.insertLeft( lastStatement.end, `\n\n${i1}${updates.join(`\n${i1}`)}` );
}
}
}
super.transpile( code, transforms );
}
}
+15
View File
@@ -0,0 +1,15 @@
import Node from '../Node.js';
import CompileError from '../../utils/CompileError.js';
export default class FunctionDeclaration extends Node {
initialise ( transforms ) {
if ( this.generator && transforms.generator ) {
throw new CompileError( this, 'Generators are not supported' );
}
this.body.createScope();
this.findScope( true ).addDeclaration( this.id, 'function' );
super.initialise( transforms );
}
}
+19
View File
@@ -0,0 +1,19 @@
import Node from '../Node.js';
import CompileError from '../../utils/CompileError.js';
export default class FunctionExpression extends Node {
initialise ( transforms ) {
if ( this.generator && transforms.generator ) {
throw new CompileError( this, 'Generators are not supported' );
}
this.body.createScope();
if ( this.id ) {
// function expression IDs belong to the child scope...
this.body.scope.addDeclaration( this.id, 'function' );
}
super.initialise( transforms );
}
}
+42
View File
@@ -0,0 +1,42 @@
import Node from '../Node.js';
import isReference from '../../utils/isReference.js';
export default class Identifier extends Node {
findScope ( functionScope ) {
if ( this.parent.params && ~this.parent.params.indexOf( this ) ) {
return this.parent.body.scope;
}
if ( this.parent.type === 'FunctionExpression' && this === this.parent.id ) {
return this.parent.body.scope;
}
return this.parent.findScope( functionScope );
}
initialise ( transforms ) {
if ( transforms.arrow && isReference( this, this.parent ) ) {
if ( this.name === 'arguments' && !this.findScope( false ).contains( this.name ) ) {
const lexicalBoundary = this.findLexicalBoundary();
const arrowFunction = this.findNearest( 'ArrowFunctionExpression' );
const loop = this.findNearest( /(?:For(?:In|Of)?|While)Statement/ );
if ( arrowFunction && arrowFunction.depth > lexicalBoundary.depth ) {
this.alias = lexicalBoundary.getArgumentsAlias();
}
if ( loop && loop.body.contains( this ) && loop.depth > lexicalBoundary.depth ) {
this.alias = lexicalBoundary.getArgumentsAlias();
}
}
this.findScope( false ).addReference( this );
}
}
transpile ( code, transforms ) {
if ( this.alias ) {
code.overwrite( this.start, this.end, this.alias, true );
}
}
}
+9
View File
@@ -0,0 +1,9 @@
import Node from '../Node.js';
import CompileError from '../../utils/CompileError.js';
export default class ImportDeclaration extends Node {
initialise ( transforms ) {
if ( transforms.moduleImport ) throw new CompileError( this, 'import is not supported' );
super.initialise( transforms );
}
}
+8
View File
@@ -0,0 +1,8 @@
import Node from '../Node.js';
export default class ImportDefaultSpecifier extends Node {
initialise ( transforms ) {
this.findScope( true ).addDeclaration( this.local, 'import' );
super.initialise( transforms );
}
}
+8
View File
@@ -0,0 +1,8 @@
import Node from '../Node.js';
export default class ImportSpecifier extends Node {
initialise ( transforms ) {
this.findScope( true ).addDeclaration( this.local, 'import' );
super.initialise( transforms );
}
}
+20
View File
@@ -0,0 +1,20 @@
import Node from '../Node.js';
const IS_DATA_ATTRIBUTE = /-/;
export default class JSXAttribute extends Node {
transpile ( code, transforms ) {
if ( this.value ) {
code.overwrite( this.name.end, this.value.start, ': ' );
} else {
// tag without value
code.overwrite( this.name.start, this.name.end, `${this.name.name}: true`)
}
if ( IS_DATA_ATTRIBUTE.test( this.name.name ) ) {
code.overwrite( this.name.start, this.name.end, `'${this.name.name}'` );
}
super.transpile( code, transforms );
}
}
+22
View File
@@ -0,0 +1,22 @@
import Node from '../Node.js';
function containsNewLine ( node ) {
return node.type === 'Literal' && !/\S/.test( node.value ) && /\n/.test( node.value );
}
export default class JSXClosingElement extends Node {
transpile ( code, transforms ) {
let spaceBeforeParen = true;
const lastChild = this.parent.children[ this.parent.children.length - 1 ];
// omit space before closing paren if
// a) this is on a separate line, or
// b) there are no children but there are attributes
if ( ( lastChild && containsNewLine( lastChild ) ) || ( this.parent.openingElement.attributes.length ) ) {
spaceBeforeParen = false;
}
code.overwrite( this.start, this.end, spaceBeforeParen ? ' )' : ')' );
}
}
+46
View File
@@ -0,0 +1,46 @@
import Node from '../Node.js';
function normalise ( str, removeTrailingWhitespace ) {
if ( removeTrailingWhitespace && /\n/.test( str ) ) {
str = str.replace( /\s+$/, '' );
}
str = str
.replace( /^\n\r?\s+/, '' ) // remove leading newline + space
.replace( /\s*\n\r?\s*/gm, ' ' ); // replace newlines with spaces
// TODO prefer single quotes?
return JSON.stringify( str );
}
export default class JSXElement extends Node {
transpile ( code, transforms ) {
super.transpile( code, transforms );
const children = this.children.filter( child => {
if ( child.type !== 'Literal' ) return true;
// remove whitespace-only literals, unless on a single line
return /\S/.test( child.value ) || !/\n/.test( child.value );
});
if ( children.length ) {
let c = this.openingElement.end;
let i;
for ( i = 0; i < children.length; i += 1 ) {
const child = children[i];
const tail = code.original[ c ] === '\n' && child.type !== 'Literal' ? '' : ' ';
code.insertLeft( c, `,${tail}` );
if ( child.type === 'Literal' ) {
const str = normalise( child.value, i === children.length - 1 );
code.overwrite( child.start, child.end, str );
}
c = child.end;
}
}
}
}
+10
View File
@@ -0,0 +1,10 @@
import Node from '../Node.js';
export default class JSXExpressionContainer extends Node {
transpile ( code, transforms ) {
code.remove( this.start, this.expression.start );
code.remove( this.expression.end, this.end );
super.transpile( code, transforms );
}
}
+81
View File
@@ -0,0 +1,81 @@
import Node from '../Node.js';
export default class JSXOpeningElement extends Node {
transpile ( code, transforms ) {
code.overwrite( this.start, this.name.start, `${this.program.jsx}( ` );
const html = this.name.type === 'JSXIdentifier' && this.name.name[0] === this.name.name[0].toLowerCase();
if ( html ) code.insertRight( this.name.start, `'` );
const len = this.attributes.length;
let c = this.name.end;
if ( len ) {
let i;
let hasSpread = false;
for ( i = 0; i < len; i += 1 ) {
if ( this.attributes[i].type === 'JSXSpreadAttribute' ) {
hasSpread = true;
break;
}
}
c = this.attributes[0].end;
for ( i = 0; i < len; i += 1 ) {
const attr = this.attributes[i];
if ( i > 0 ) {
code.overwrite( c, attr.start, ', ' );
}
if ( hasSpread && attr.type !== 'JSXSpreadAttribute' ) {
const lastAttr = this.attributes[ i - 1 ];
const nextAttr = this.attributes[ i + 1 ];
if ( !lastAttr || lastAttr.type === 'JSXSpreadAttribute' ) {
code.insertRight( attr.start, '{ ' );
}
if ( !nextAttr || nextAttr.type === 'JSXSpreadAttribute' ) {
code.insertLeft( attr.end, ' }' );
}
}
c = attr.end;
}
let after;
let before;
if ( hasSpread ) {
if ( len === 1 ) {
before = html ? `',` : ',';
} else {
before = html ? `', ${this.program.objectAssign}({},` : `, ${this.program.objectAssign}({},`;
after = ')';
}
} else {
before = html ? `', {` : ', {';
after = ' }';
}
code.insertRight( this.name.end, before );
if ( after ) {
code.insertLeft( this.attributes[ len - 1 ].end, after );
}
} else {
code.insertLeft( this.name.end, html ? `', null` : `, null` );
c = this.name.end;
}
super.transpile( code, transforms );
if ( this.selfClosing ) {
code.overwrite( c, this.end, this.attributes.length ? `)` : ` )` );
} else {
code.remove( c, this.end );
}
}
}
+10
View File
@@ -0,0 +1,10 @@
import Node from '../Node.js';
export default class JSXSpreadAttribute extends Node {
transpile ( code, transforms ) {
code.remove( this.start, this.argument.start );
code.remove( this.argument.end, this.end );
super.transpile( code, transforms );
}
}
+23
View File
@@ -0,0 +1,23 @@
import Node from '../Node.js';
import CompileError from '../../utils/CompileError.js';
import rewritePattern from 'regexpu-core';
export default class Literal extends Node {
transpile ( code, transforms ) {
if ( transforms.numericLiteral ) {
const leading = this.raw.slice( 0, 2 );
if ( leading === '0b' || leading === '0o' ) {
code.overwrite( this.start, this.end, String( this.value ), true );
}
}
if ( this.regex ) {
const { pattern, flags } = this.regex;
if ( transforms.stickyRegExp && /y/.test( flags ) ) throw new CompileError( this, 'Regular expression sticky flag is not supported' );
if ( transforms.unicodeRegExp && /u/.test( flags ) ) {
code.overwrite( this.start, this.end, `/${rewritePattern( pattern, flags )}/${flags.replace( 'u', '' )}` );
}
}
}
}
+13
View File
@@ -0,0 +1,13 @@
import Node from '../Node.js';
import reserved from '../../utils/reserved.js';
export default class MemberExpression extends Node {
transpile ( code, transforms ) {
if ( transforms.reservedProperties && reserved[ this.property.name ] ) {
code.overwrite( this.object.end, this.property.start, `['` );
code.insertLeft( this.property.end, `']` );
}
super.transpile( code, transforms );
}
}
+121
View File
@@ -0,0 +1,121 @@
import Node from '../Node.js';
import deindent from '../../utils/deindent.js';
export default class ObjectExpression extends Node {
transpile ( code, transforms ) {
super.transpile( code, transforms );
let spreadPropertyCount = 0;
let computedPropertyCount = 0;
for ( let prop of this.properties ) {
if ( prop.type === 'SpreadProperty' ) spreadPropertyCount += 1;
if ( prop.computed ) computedPropertyCount += 1;
}
if ( spreadPropertyCount ) {
// enclose run of non-spread properties in curlies
let i = this.properties.length;
while ( i-- ) {
const prop = this.properties[i];
if ( prop.type === 'Property' ) {
const lastProp = this.properties[ i - 1 ];
const nextProp = this.properties[ i + 1 ];
if ( !lastProp || lastProp.type !== 'Property' ) {
code.insertRight( prop.start, '{' );
}
if ( !nextProp || nextProp.type !== 'Property' ) {
code.insertLeft( prop.end, '}' );
}
}
}
// wrap the whole thing in Object.assign
code.overwrite( this.start, this.properties[0].start, `${this.program.objectAssign}({}, `);
code.overwrite( this.properties[ this.properties.length - 1 ].end, this.end, ')' );
}
if ( computedPropertyCount && transforms.computedProperty ) {
const i0 = this.getIndentation();
let isSimpleAssignment;
let name;
let start;
let end;
if ( this.parent.type === 'VariableDeclarator' && this.parent.parent.declarations.length === 1 ) {
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?
}
// handle block scoping
const declaration = this.findScope( false ).findDeclaration( name );
if ( declaration ) name = declaration.name;
start = this.start + 1;
end = this.end;
if ( isSimpleAssignment ) {
// ???
} else {
name = this.findScope( true ).createIdentifier( 'obj' );
const statement = this.findNearest( /(?:Statement|Declaration)$/ );
code.insertRight( statement.start, `var ${name};\n${i0}` );
code.insertRight( this.start, `( ${name} = ` );
}
const len = this.properties.length;
let lastComputedProp;
for ( let i = 0; i < len; i += 1 ) {
const prop = this.properties[i];
if ( prop.computed ) {
lastComputedProp = prop;
let moveStart = i > 0 ? this.properties[ i - 1 ].end : start;
code.overwrite( moveStart, prop.start, isSimpleAssignment ? `;\n${i0}${name}` : `, ${name}` );
let c = prop.key.end;
while ( code.original[c] !== ']' ) c += 1;
c += 1;
if ( prop.value.start > c ) code.remove( c, prop.value.start );
code.insertLeft( c, ' = ' );
code.move( moveStart, prop.end, end );
if ( i === 0 && len > 1 ) {
// remove trailing comma
c = prop.end;
while ( code.original[c] !== ',' ) c += 1;
code.remove( prop.end, c + 1 );
}
if ( prop.method && transforms.conciseMethodProperty ) {
code.insertRight( prop.value.start, 'function ' );
}
deindent( prop.value, code );
}
}
// special case
if ( computedPropertyCount === len ) {
code.remove( this.properties[ len - 1 ].end, this.end - 1 );
}
if ( !isSimpleAssignment ) {
code.insertLeft( lastComputedProp.end, `, ${name} )` );
}
}
}
}
+23
View File
@@ -0,0 +1,23 @@
import Node from '../Node.js';
import reserved from '../../utils/reserved.js';
export default class Property extends Node {
transpile ( code, transforms ) {
if ( transforms.conciseMethodProperty && !this.computed && this.parent.type !== 'ObjectPattern' ) {
if ( this.shorthand ) {
code.insertRight( this.start, `${this.key.name}: ` );
} else if ( this.method ) {
const name = this.findScope( true ).createIdentifier( this.key.type === 'Identifier' ? this.key.name : this.key.value );
if ( this.value.generator ) code.remove( this.start, this.key.start );
code.insertLeft( this.key.end, `: function${this.value.generator ? '*' : ''} ${name}` );
}
}
if ( transforms.reservedProperties && reserved[ this.key.name ] ) {
code.insertRight( this.key.start, `'` );
code.insertLeft( this.key.end, `'` );
}
super.transpile( code, transforms );
}
}
+27
View File
@@ -0,0 +1,27 @@
import Node from '../Node.js';
export default class ReturnStatement extends Node {
initialise ( transforms ) {
this.loop = this.findNearest( /(?:For(?:In)?|While)Statement/ );
this.nearestFunction = this.findNearest( /Function/ );
if ( this.loop && ( !this.nearestFunction || this.loop.depth > this.nearestFunction.depth ) ) {
this.loop.canReturn = true;
this.shouldWrap = true;
}
if ( this.argument ) this.argument.initialise( transforms );
}
transpile ( code, transforms ) {
if ( this.argument ) {
const shouldWrap = this.shouldWrap && this.loop && this.loop.shouldRewriteAsFunction;
if ( shouldWrap ) code.insertRight( this.argument.start, `{ v: ` );
if ( this.argument ) this.argument.transpile( code, transforms );
if ( shouldWrap ) code.insertLeft( this.argument.end, ` }` );
}
}
}
+11
View File
@@ -0,0 +1,11 @@
import Node from '../Node.js';
import reserved from '../../utils/reserved.js';
export default class SpreadProperty extends Node {
transpile ( code, transforms ) {
code.remove( this.start, this.argument.start );
code.remove( this.argument.end, this.end );
super.transpile( code, transforms );
}
}
+68
View File
@@ -0,0 +1,68 @@
import Node from '../Node.js';
import CompileError from '../../utils/CompileError.js';
export default class Super extends Node {
initialise ( transforms ) {
if ( transforms.classes ) {
this.method = this.findNearest( 'MethodDefinition' );
if ( !this.method ) throw new CompileError( this, 'use of super outside class method' );
const parentClass = this.findNearest( 'ClassBody' ).parent;
this.superClassName = parentClass.superClass && (parentClass.superClass.name || 'superclass');
if ( !this.superClassName ) throw new CompileError( this, 'super used in base class' );
this.isCalled = this.parent.type === 'CallExpression' && this === this.parent.callee;
if ( this.method.kind !== 'constructor' && this.isCalled ) {
throw new CompileError( this, 'super() not allowed outside class constructor' );
}
this.isMember = this.parent.type === 'MemberExpression';
if ( !this.isCalled && !this.isMember ) {
throw new CompileError( this, 'Unexpected use of `super` (expected `super(...)` or `super.*`)' );
}
}
if ( transforms.arrow ) {
const lexicalBoundary = this.findLexicalBoundary();
const arrowFunction = this.findNearest( 'ArrowFunctionExpression' );
const loop = this.findNearest( /(?:For(?:In|Of)?|While)Statement/ );
if ( arrowFunction && arrowFunction.depth > lexicalBoundary.depth ) {
this.thisAlias = lexicalBoundary.getThisAlias();
}
if ( loop && loop.body.contains( this ) && loop.depth > lexicalBoundary.depth ) {
this.thisAlias = lexicalBoundary.getThisAlias();
}
}
}
transpile ( code, transforms ) {
if ( transforms.classes ) {
const expression = ( this.isCalled || this.method.static ) ?
this.superClassName :
`${this.superClassName}.prototype`;
code.overwrite( this.start, this.end, expression, true );
const callExpression = this.isCalled ? this.parent : this.parent.parent;
if ( callExpression && callExpression.type === 'CallExpression' ) {
if ( !this.noCall ) { // special case `super( ...args )`
code.insertLeft( callExpression.callee.end, '.call' );
}
const thisAlias = this.thisAlias || 'this';
if ( callExpression.arguments.length ) {
code.insertLeft( callExpression.arguments[0].start, `${thisAlias}, ` );
} else {
code.insertLeft( callExpression.end - 1, `${thisAlias}` );
}
}
}
}
}
+37
View File
@@ -0,0 +1,37 @@
import Node from '../Node.js';
import CompileError from '../../utils/CompileError.js';
export default class TaggedTemplateExpression extends Node {
initialise ( transforms ) {
if ( transforms.templateString && !transforms.dangerousTaggedTemplateString ) {
throw new CompileError( this, 'Tagged template strings are not supported. Use `transforms: { templateString: false }` to skip transformation and disable this error, or `transforms: { dangerousTaggedTemplateString: true }` if you know what you\'re doing' );
}
super.initialise( transforms );
}
transpile ( code, transforms ) {
if ( transforms.templateString && transforms.dangerousTaggedTemplateString ) {
const ordered = this.quasi.expressions.concat( this.quasi.quasis ).sort( ( a, b ) => a.start - b.start );
// insert strings at start
const templateStrings = this.quasi.quasis.map( quasi => JSON.stringify( quasi.value.cooked ) );
code.overwrite( this.tag.end, ordered[0].start, `([${templateStrings.join(', ')}]` );
let lastIndex = ordered[0].start;
ordered.forEach( node => {
if ( node.type === 'TemplateElement' ) {
code.remove( lastIndex, node.end );
} else {
code.overwrite( lastIndex, node.start, ', ' );
}
lastIndex = node.end;
});
code.overwrite( lastIndex, this.end, ')' );
}
super.transpile( code, transforms );
}
}
+7
View File
@@ -0,0 +1,7 @@
import Node from '../Node.js';
export default class TemplateElement extends Node {
initialise ( transforms ) {
this.program.templateElements.push( this );
}
}
+69
View File
@@ -0,0 +1,69 @@
import Node from '../Node.js';
export default class TemplateLiteral extends Node {
transpile ( code, transforms ) {
if ( transforms.templateString && this.parent.type !== 'TaggedTemplateExpression' ) {
let ordered = this.expressions.concat( this.quasis )
.sort( ( a, b ) => a.start - b.start || a.end - b.end )
.filter( ( node, i ) => {
// include all expressions
if ( node.type !== 'TemplateElement' ) return true;
// include all non-empty strings
if ( node.value.raw ) return true;
// exclude all empty strings not at the head
return !i;
});
// special case we may be able to skip the first element,
// if it's the empty string, but only if the second and
// third elements aren't both expressions (since they maybe
// be numeric, and `1 + 2 + '3' === '33'`)
if ( ordered.length >= 3 ) {
const [ first, , third ] = ordered;
if ( first.type === 'TemplateElement' && first.value.raw === '' && third.type === 'TemplateElement' ) {
ordered.shift();
}
}
const parenthesise = ( this.quasis.length !== 1 || this.expressions.length !== 0 ) &&
this.parent.type !== 'AssignmentExpression' &&
this.parent.type !== 'VariableDeclarator' &&
( this.parent.type !== 'BinaryExpression' || this.parent.operator !== '+' );
if ( parenthesise ) code.insertRight( this.start, '(' );
let lastIndex = this.start;
ordered.forEach( ( node, i ) => {
if ( node.type === 'TemplateElement' ) {
let replacement = '';
if ( i ) replacement += ' + ';
replacement += JSON.stringify( node.value.cooked );
code.overwrite( lastIndex, node.end, replacement );
} else {
const parenthesise = node.type !== 'Identifier'; // TODO other cases where it's safe
let replacement = '';
if ( i ) replacement += ' + ';
if ( parenthesise ) replacement += '(';
code.overwrite( lastIndex, node.start, replacement );
if ( parenthesise ) code.insertLeft( node.end, ')' );
}
lastIndex = node.end;
});
let close = '';
if ( parenthesise ) close += ')';
code.overwrite( lastIndex, this.end, close );
}
super.transpile( code, transforms );
}
}
+25
View File
@@ -0,0 +1,25 @@
import Node from '../Node.js';
export default class ThisExpression extends Node {
initialise ( transforms ) {
if ( transforms.arrow ) {
const lexicalBoundary = this.findLexicalBoundary();
const arrowFunction = this.findNearest( 'ArrowFunctionExpression' );
const loop = this.findNearest( /(?:For(?:In|Of)?|While)Statement/ );
if ( arrowFunction && arrowFunction.depth > lexicalBoundary.depth ) {
this.alias = lexicalBoundary.getThisAlias();
}
if ( loop && loop.body.contains( this ) && loop.depth > lexicalBoundary.depth ) {
this.alias = lexicalBoundary.getThisAlias();
}
}
}
transpile ( code, transforms ) {
if ( this.alias ) {
code.overwrite( this.start, this.end, this.alias, true );
}
}
}
+15
View File
@@ -0,0 +1,15 @@
import Node from '../Node.js';
import CompileError from '../../utils/CompileError.js';
export default class UpdateExpression extends Node {
initialise ( transforms ) {
if ( this.argument.type === 'Identifier' ) {
const declaration = this.findScope( false ).findDeclaration( this.argument.name );
if ( declaration && declaration.kind === 'const' ) {
throw new CompileError( this, `${this.argument.name} is read-only` );
}
}
super.initialise( transforms );
}
}
+85
View File
@@ -0,0 +1,85 @@
import Node from '../Node.js';
import destructure from '../../utils/destructure.js';
export default class VariableDeclaration extends Node {
initialise ( transforms ) {
this.scope = this.findScope( this.kind === 'var' );
this.declarations.forEach( declarator => declarator.initialise( transforms ) );
}
transpile ( code, transforms ) {
const i0 = this.getIndentation();
let kind = this.kind;
if ( transforms.letConst && kind !== 'var' ) {
kind = 'var';
code.overwrite( this.start, this.start + this.kind.length, kind, true );
}
if ( transforms.destructuring ) {
let c = this.start;
let lastDeclaratorIsPattern;
this.declarations.forEach( ( declarator, i ) => {
if ( declarator.id.type === 'Identifier' ) {
if ( i > 0 && this.declarations[ i - 1 ].id.type !== 'Identifier' ) {
code.overwrite( c, declarator.id.start, `var ` );
}
} else {
if ( i === 0 ) {
code.remove( c, declarator.id.start );
} else {
code.overwrite( c, declarator.id.start, `;\n${i0}` );
}
const simple = declarator.init.type === 'Identifier' && !declarator.init.rewritten;
const name = simple ? declarator.init.name : declarator.findScope( true ).createIdentifier( 'ref' );
let c = declarator.start;
let statementGenerators = [];
if ( simple ) {
code.remove( declarator.id.end, declarator.end );
} else {
statementGenerators.push( ( start, prefix, suffix ) => {
code.insertRight( declarator.id.end, `var ${name}` );
code.insertLeft( declarator.init.end, `;${suffix}` );
code.move( declarator.id.end, declarator.end, start );
});
}
destructure( code, declarator.findScope( false ), declarator.id, name, statementGenerators );
let suffix = `\n${i0}`;
statementGenerators.forEach( ( fn, j ) => {
if ( i === this.declarations.length - 1 && j === statementGenerators.length - 1 ) {
suffix = '';
}
fn( declarator.start, '', suffix );
});
}
if ( declarator.init ) {
declarator.init.transpile( code, transforms );
}
c = declarator.end;
lastDeclaratorIsPattern = declarator.id.type !== 'Identifier';
});
if ( lastDeclaratorIsPattern ) {
code.remove( c, this.end );
}
}
else {
this.declarations.forEach( declarator => {
if ( declarator.init ) declarator.init.transpile( code, transforms );
});
}
}
}
+14
View File
@@ -0,0 +1,14 @@
import Node from '../Node.js';
import CompileError from '../../utils/CompileError.js';
export default class VariableDeclarator extends Node {
initialise ( transforms ) {
let kind = this.parent.kind;
if ( kind === 'let' && this.parent.parent.type === 'ForStatement' ) {
kind = 'for.let'; // special case...
}
this.parent.scope.addDeclaration( this.id, kind );
super.initialise( transforms );
}
}
+88
View File
@@ -0,0 +1,88 @@
import ArrayExpression from './ArrayExpression.js';
import ArrowFunctionExpression from './ArrowFunctionExpression.js';
import AssignmentExpression from './AssignmentExpression.js';
import BinaryExpression from './BinaryExpression.js';
import BreakStatement from './BreakStatement.js';
import CallExpression from './CallExpression.js';
import ClassBody from './ClassBody.js';
import ClassDeclaration from './ClassDeclaration.js';
import ClassExpression from './ClassExpression.js';
import ContinueStatement from './ContinueStatement.js';
import ExportDefaultDeclaration from './ExportDefaultDeclaration.js';
import ExportNamedDeclaration from './ExportNamedDeclaration.js';
import ForStatement from './ForStatement.js';
import ForInStatement from './ForInStatement.js';
import ForOfStatement from './ForOfStatement.js';
import FunctionDeclaration from './FunctionDeclaration.js';
import FunctionExpression from './FunctionExpression.js';
import Identifier from './Identifier.js';
import ImportDeclaration from './ImportDeclaration.js';
import ImportDefaultSpecifier from './ImportDefaultSpecifier.js';
import ImportSpecifier from './ImportSpecifier.js';
import JSXAttribute from './JSXAttribute.js';
import JSXClosingElement from './JSXClosingElement.js';
import JSXElement from './JSXElement.js';
import JSXExpressionContainer from './JSXExpressionContainer.js';
import JSXOpeningElement from './JSXOpeningElement.js';
import JSXSpreadAttribute from './JSXSpreadAttribute.js';
import Literal from './Literal.js';
import LoopStatement from './shared/LoopStatement.js';
import MemberExpression from './MemberExpression.js';
import ObjectExpression from './ObjectExpression.js';
import Property from './Property.js';
import ReturnStatement from './ReturnStatement.js';
import SpreadProperty from './SpreadProperty.js';
import Super from './Super.js';
import TaggedTemplateExpression from './TaggedTemplateExpression.js';
import TemplateElement from './TemplateElement.js';
import TemplateLiteral from './TemplateLiteral.js';
import ThisExpression from './ThisExpression.js';
import UpdateExpression from './UpdateExpression.js';
import VariableDeclaration from './VariableDeclaration.js';
import VariableDeclarator from './VariableDeclarator.js';
export default {
ArrayExpression,
ArrowFunctionExpression,
AssignmentExpression,
BinaryExpression,
BreakStatement,
CallExpression,
ClassBody,
ClassDeclaration,
ClassExpression,
ContinueStatement,
DoWhileStatement: LoopStatement,
ExportNamedDeclaration,
ExportDefaultDeclaration,
ForStatement,
ForInStatement,
ForOfStatement,
FunctionDeclaration,
FunctionExpression,
Identifier,
ImportDeclaration,
ImportDefaultSpecifier,
ImportSpecifier,
JSXAttribute,
JSXClosingElement,
JSXElement,
JSXExpressionContainer,
JSXOpeningElement,
JSXSpreadAttribute,
Literal,
MemberExpression,
ObjectExpression,
Property,
ReturnStatement,
SpreadProperty,
Super,
TaggedTemplateExpression,
TemplateElement,
TemplateLiteral,
ThisExpression,
UpdateExpression,
VariableDeclaration,
VariableDeclarator,
WhileStatement: LoopStatement
};
+85
View File
@@ -0,0 +1,85 @@
import Node from '../../Node.js';
import extractNames from '../../extractNames.js';
export default class LoopStatement extends Node {
findScope ( functionScope ) {
return functionScope || !this.createdScope ? this.parent.findScope( functionScope ) : this.body.scope;
}
initialise ( transforms ) {
this.body.createScope();
this.createdScope = true;
// this is populated as and when reassignments occur
this.reassigned = Object.create( null );
this.aliases = Object.create( null );
super.initialise( transforms );
if ( transforms.letConst ) {
// see if any block-scoped declarations are referenced
// inside function expressions
const names = Object.keys( this.body.scope.declarations );
let i = names.length;
while ( i-- ) {
const name = names[i];
const declaration = this.body.scope.declarations[ name ];
let j = declaration.instances.length;
while ( j-- ) {
const instance = declaration.instances[j];
const nearestFunctionExpression = instance.findNearest( /Function/ );
if ( nearestFunctionExpression && nearestFunctionExpression.depth > this.depth ) {
this.shouldRewriteAsFunction = true;
break;
}
}
if ( this.shouldRewriteAsFunction ) break;
}
}
}
transpile ( code, transforms ) {
if ( this.shouldRewriteAsFunction ) {
const i0 = this.getIndentation();
const i1 = i0 + code.getIndentString();
const argString = this.args ? ` ${this.args.join( ', ' )} ` : '';
const paramString = this.params ? ` ${this.params.join( ', ' )} ` : '';
const functionScope = this.findScope( true );
const loop = functionScope.createIdentifier( 'loop' );
const before = `var ${loop} = function (${paramString}) ` + ( this.body.synthetic ? `{\n${i0}${code.getIndentString()}` : '' );
const after = ( this.body.synthetic ? `\n${i0}}` : '' ) + `;\n\n${i0}`;
code.insertRight( this.body.start, before );
code.insertLeft( this.body.end, after );
code.move( this.start, this.body.start, this.body.end );
if ( this.canBreak || this.canReturn ) {
const returned = functionScope.createIdentifier( 'returned' );
let insert = `{\n${i1}var ${returned} = ${loop}(${argString});\n`;
if ( this.canBreak ) insert += `\n${i1}if ( ${returned} === 'break' ) break;`;
if ( this.canReturn ) insert += `\n${i1}if ( ${returned} ) return returned.v;`;
insert += `\n${i0}}`;
code.insertRight( this.body.end, insert );
} else {
const callExpression = `${loop}(${argString});`;
if ( this.type === 'DoWhileStatement' ) {
code.overwrite( this.start, this.body.start, `do {\n${i1}${callExpression}\n${i0}}` );
} else {
code.insertRight( this.body.end, callExpression );
}
}
}
super.transpile( code, transforms );
}
}
+9
View File
@@ -0,0 +1,9 @@
import Node from '../../Node.js';
import CompileError from '../../../utils/CompileError.js';
export default class ModuleDeclaration extends Node {
initialise ( transforms ) {
if ( transforms.moduleImport ) throw new CompileError( this, 'Modules are not supported' );
super.initialise( transforms );
}
}
+54
View File
@@ -0,0 +1,54 @@
import types from './types/index.js';
import BlockStatement from './BlockStatement.js';
import Node from './Node.js';
import keys from './keys.js';
const statementsWithBlocks = {
IfStatement: 'consequent',
ForStatement: 'body',
ForInStatement: 'body',
ForOfStatement: 'body',
WhileStatement: 'body',
DoWhileStatement: 'body',
ArrowFunctionExpression: 'body'
};
export default function wrap ( raw, parent ) {
if ( !raw ) return;
if ( 'length' in raw ) {
let i = raw.length;
while ( i-- ) wrap( raw[i], parent );
return;
}
// with e.g. shorthand properties, key and value are
// the same node. We don't want to wrap an object twice
if ( raw.__wrapped ) return;
raw.__wrapped = true;
if ( !keys[ raw.type ] ) {
keys[ raw.type ] = Object.keys( raw ).filter( key => typeof raw[ key ] === 'object' );
}
// special case body-less if/for/while statements. TODO others?
const bodyType = statementsWithBlocks[ raw.type ];
if ( bodyType && raw[ bodyType ].type !== 'BlockStatement' ) {
const expression = raw[ bodyType ];
// create a synthetic block statement, otherwise all hell
// breaks loose when it comes to block scoping
raw[ bodyType ] = {
start: expression.start,
end: expression.end,
type: 'BlockStatement',
body: [ expression ],
synthetic: true
};
}
Node( raw, parent );
const type = ( raw.type === 'BlockStatement' ? BlockStatement : types[ raw.type ] ) || Node;
raw.__proto__ = type.prototype;
}
+72
View File
@@ -0,0 +1,72 @@
export const matrix = {
chrome: {
48: 0b1001111011111100111110101111101,
49: 0b1001111111111100111111111111111,
50: 0b1011111111111100111111111111111
},
firefox: {
43: 0b1000111111101100000110111011101,
44: 0b1000111111101100000110111011101,
45: 0b1000111111101100000110111011101
},
safari: {
8: 0b1000000000000000000000000000000,
9: 0b1001111001101100000011101011110
},
ie: {
8: 0b0000000000000000000000000000000,
9: 0b1000000000000000000000000000000,
10: 0b1000000000000000000000000000000,
11: 0b1000000000000000111000001100000
},
edge: {
12: 0b1011110110111100011010001011101,
13: 0b1011111110111100011111001011111
},
node: {
'0.10': 0b1000000000101000000000001000000,
'0.12': 0b1000001000101000000010001000100,
4: 0b1001111000111100111111001111111,
5: 0b1001111000111100111111001111111,
6: 0b1011111111111100111111111111111
}
};
export const features = [
'arrow',
'classes',
'collections',
'computedProperty',
'conciseMethodProperty',
'constLoop',
'constRedef',
'defaultParameter',
'destructuring',
'extendNatives',
'forOf',
'generator',
'letConst',
'letLoop',
'letLoopScope',
'moduleExport',
'moduleImport',
'numericLiteral',
'objectProto',
'objectSuper',
'oldOctalLiteral',
'parameterDestructuring',
'spreadRest',
'stickyRegExp',
'symbol',
'templateString',
'unicodeEscape',
'unicodeIdentifier',
'unicodeRegExp',
// ES2016
'exponentiation',
// additional transforms, not from
// https://featuretests.io
'reservedProperties'
];
+23
View File
@@ -0,0 +1,23 @@
import locate from './locate.js';
import getSnippet from './getSnippet.js';
export default class CompileError extends Error {
constructor ( node, message ) {
super();
const source = node.program.magicString.original;
const loc = locate( source, node.start );
this.name = 'CompileError';
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}`;
}
}
+11
View File
@@ -0,0 +1,11 @@
export function findIndex ( array, fn ) {
for ( let i = 0; i < array.length; i += 1 ) {
if ( fn( array[i], i ) ) return i;
}
return -1;
}
export function find ( array, fn ) {
return array[ findIndex( array, fn ) ];
}
+21
View File
@@ -0,0 +1,21 @@
// TODO this function is slightly flawed it works on the original string,
// not its current edited state.
// That's not a problem for the way that it's currently used, but it could
// be in future...
export default function deindent ( node, code ) {
const start = node.start;
const end = node.end;
const indentStr = code.getIndentString();
const pattern = new RegExp( indentStr + '\\S', 'g' );
if ( code.original.slice( start - indentStr.length, start ) === indentStr ) {
code.remove( start - indentStr.length, start );
}
const slice = code.original.slice( start, end );
let match;
while ( match = pattern.exec( slice ) ) {
if ( !node.program.indentExclusions[ match.index ] ) code.remove( start + match.index, start + match.index + indentStr.length );
}
}
+170
View File
@@ -0,0 +1,170 @@
import { findIndex } from './array.js';
const handlers = {
ArrayPattern: destructureArrayPattern,
ObjectPattern: destructureObjectPattern,
AssignmentPattern: destructureAssignmentPattern,
Identifier: destructureIdentifier
};
export default function destructure ( code, scope, node, ref, statementGenerators ) {
_destructure( code, scope, node, ref, ref, statementGenerators );
}
function _destructure ( code, scope, node, ref, expr, statementGenerators ) {
const handler = handlers[ node.type ];
if ( !handler ) throw new Error( `not implemented: ${node.type}` );
handler( code, scope, node, ref, expr, statementGenerators );
}
function destructureIdentifier ( code, scope, node, ref, expr, statementGenerators ) {
statementGenerators.push( ( start, prefix, suffix ) => {
code.insertRight( node.start, `${prefix}var ` );
code.insertLeft( node.end, ` = ${expr};${suffix}` );
code.move( node.start, node.end, start );
});
}
function handleProperty ( code, scope, c, node, value, statementGenerators ) {
switch ( node.type ) {
case 'Identifier':
code.remove( c, node.start );
statementGenerators.push( ( start, prefix, suffix ) => {
code.insertRight( node.start, `${prefix}var ` );
code.insertLeft( node.end, ` = ${value};${suffix}` );
code.move( node.start, node.end, start );
});
break;
case 'AssignmentPattern':
let name;
const isIdentifier = node.left.type === 'Identifier';
if ( isIdentifier ) {
name = node.left.name;
const declaration = scope.findDeclaration( name );
if ( declaration ) name = declaration.name;
} else {
name = scope.createIdentifier( value );
}
statementGenerators.push( ( start, prefix, suffix ) => {
code.insertRight( node.right.start, `${prefix}var ${name} = ${value}; if ( ${name} === void 0 ) ${name} = ` );
code.move( node.right.start, node.right.end, start );
code.insertLeft( node.right.end, `;${suffix}` );
});
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, scope, c, node.left, name, statementGenerators );
}
break;
case 'ObjectPattern':
code.remove( c, c = node.start );
if ( node.properties.length > 1 ) {
const ref = scope.createIdentifier( value );
statementGenerators.push( ( start, prefix, suffix ) => {
// this feels a tiny bit hacky, but we can't do a
// straightforward insertLeft and keep correct order...
code.insertRight( node.start, `${prefix}var ${ref} = ` );
code.overwrite( node.start, c = node.start + 1, value );
code.insertLeft( c, `;${suffix}` );
code.move( node.start, c, start );
});
node.properties.forEach( prop => {
handleProperty( code, scope, c, prop.value, `${ref}.${prop.key.name}`, statementGenerators );
c = prop.end;
});
} else {
const prop = node.properties[0];
handleProperty( code, scope, c, prop.value, `${value}.${prop.key.name}`, statementGenerators );
c = prop.end;
}
code.remove( c, node.end );
break;
case 'ArrayPattern':
code.remove( c, c = node.start );
if ( node.elements.filter( Boolean ).length > 1 ) {
const ref = scope.createIdentifier( value );
statementGenerators.push( ( start, prefix, suffix ) => {
code.insertRight( node.start, `${prefix}var ${ref} = ` );
code.overwrite( node.start, c = node.start + 1, value );
code.insertLeft( c, `;${suffix}` );
code.move( node.start, c, start );
});
node.elements.forEach( ( element, i ) => {
if ( !element ) return;
handleProperty( code, scope, c, element, `${ref}[${i}]`, statementGenerators );
c = element.end;
});
} else {
const index = findIndex( node.elements, Boolean );
const element = node.elements[ index ];
handleProperty( code, scope, c, element, `${value}[${index}]`, statementGenerators );
c = element.end;
}
code.remove( c, node.end );
break;
default:
throw new Error( `Unexpected node type in destructuring (${node.type})` );
}
}
function destructureArrayPattern ( code, scope, node, ref, expr, statementGenerators ) {
let c = node.start;
node.elements.forEach( ( element, i ) => {
if ( !element ) return;
handleProperty( code, scope, c, element, `${ref}[${i}]`, statementGenerators );
c = element.end;
});
code.remove( c, node.end );
}
function destructureObjectPattern ( code, scope, node, ref, expr, statementGenerators ) {
let c = node.start;
node.properties.forEach( prop => {
handleProperty( code, scope, c, prop.value, `${ref}.${prop.key.name}`, statementGenerators );
c = prop.end;
});
code.remove( c, node.end );
}
function destructureAssignmentPattern ( code, scope, node, ref, expr, statementGenerators ) {
const isIdentifier = node.left.type === 'Identifier';
const name = isIdentifier ? node.left.name : ref;
statementGenerators.push( ( start, prefix, suffix ) => {
code.insertRight( node.left.end, `${prefix}if ( ${name} === void 0 ) ${name}` );
code.move( node.left.end, node.right.end, start );
code.insertLeft( node.right.end, `;${suffix}` );
});
if ( !isIdentifier ) {
_destructure( code, scope, node.left, ref, expr, statementGenerators );
}
}
+30
View File
@@ -0,0 +1,30 @@
function pad ( num, len ) {
let result = String( num );
return result + repeat( ' ', len - result.length );
}
function repeat ( str, times ) {
let result = '';
while ( times-- ) result += str;
return result;
}
export default function getSnippet ( source, loc, length = 1 ) {
const first = Math.max( loc.line - 5, 0 );
const last = loc.line;
const numDigits = String( last ).length;
const lines = source.split( '\n' ).slice( first, last );
const lastLine = lines[ lines.length - 1 ];
const offset = lastLine.slice( 0, loc.column ).replace( /\t/g, ' ' ).length;
let 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;
}
+37
View File
@@ -0,0 +1,37 @@
export default function isReference ( node, parent ) {
if ( node.type === 'MemberExpression' ) {
return !node.computed && isReference( node.object, node );
}
if ( node.type === 'Identifier' ) {
// the only time we could have an identifier node without a parent is
// if it's the entire body of a function without a block statement
// i.e. an arrow function expression like `a => a`
if ( !parent ) return true;
if ( /(Function|Class)Expression/.test( parent.type ) ) return false;
if ( parent.type === 'VariableDeclarator' ) return node === parent.init;
// TODO is this right?
if ( parent.type === 'MemberExpression' || parent.type === 'MethodDefinition' ) {
return parent.computed || node === parent.object;
}
if ( parent.type === 'ArrayPattern' ) return false;
// disregard the `bar` in `{ bar: foo }`, but keep it in `{ [bar]: foo }`
if ( parent.type === 'Property' ) {
if ( parent.parent.type === 'ObjectPattern' ) return false;
return parent.computed || node === parent.value;
}
// disregard the `bar` in `class Foo { bar () {...} }`
if ( parent.type === 'MethodDefinition' ) return false;
// disregard the `bar` in `export { foo as bar }`
if ( parent.type === 'ExportSpecifier' && node !== parent.local ) return false;
return true;
}
}
+20
View File
@@ -0,0 +1,20 @@
export default 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' );
}
+5
View File
@@ -0,0 +1,5 @@
let 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 );
export default reserved;
+46
View File
@@ -0,0 +1,46 @@
export function isArguments ( node ) {
return node.type === 'Identifier' && node.name === 'arguments';
}
export default function spread ( code, elements, start, argumentsArrayAlias ) {
let i = elements.length;
let firstSpreadIndex = -1;
while ( i-- ) {
const element = elements[i];
if ( element && element.type === 'SpreadElement' ) {
if ( isArguments( element.argument ) ) {
code.overwrite( element.argument.start, element.argument.end, argumentsArrayAlias );
}
firstSpreadIndex = i;
}
}
if ( firstSpreadIndex === -1 ) return false; // false indicates no spread elements
let element = elements[ firstSpreadIndex ];
const previousElement = elements[ firstSpreadIndex - 1 ];
if ( !previousElement ) {
code.remove( start, element.start );
code.overwrite( element.end, elements[1].start, '.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.insertRight( element.start, '[' );
code.insertLeft( element.end, ']' );
}
}
}
return true; // true indicates some spread elements
}