mirror of https://github.com/lukechilds/rollup.git
Rich-Harris
8 years ago
440 changed files with 4437 additions and 1403 deletions
@ -1,30 +0,0 @@ |
|||||
export class Reference { |
|
||||
constructor ( node, scope, statement ) { |
|
||||
this.node = node; |
|
||||
this.scope = scope; |
|
||||
this.statement = statement; |
|
||||
|
|
||||
this.declaration = null; // bound later
|
|
||||
|
|
||||
this.parts = []; |
|
||||
|
|
||||
let root = node; |
|
||||
while ( root.type === 'MemberExpression' ) { |
|
||||
this.parts.unshift( root.property ); |
|
||||
root = root.object; |
|
||||
} |
|
||||
|
|
||||
this.name = root.name; |
|
||||
|
|
||||
this.start = node.start; |
|
||||
this.end = node.start + this.name.length; // can be overridden in the case of namespace members
|
|
||||
this.rewritten = false; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
export class SyntheticReference { |
|
||||
constructor ( name ) { |
|
||||
this.name = name; |
|
||||
this.parts = []; |
|
||||
} |
|
||||
} |
|
@ -1,160 +0,0 @@ |
|||||
import { walk } from 'estree-walker'; |
|
||||
import Scope from './ast/Scope.js'; |
|
||||
import attachScopes from './ast/attachScopes.js'; |
|
||||
import modifierNodes, { isModifierNode } from './ast/modifierNodes.js'; |
|
||||
import isFunctionDeclaration from './ast/isFunctionDeclaration.js'; |
|
||||
import isReference from './ast/isReference.js'; |
|
||||
import getLocation from './utils/getLocation.js'; |
|
||||
import run from './utils/run.js'; |
|
||||
import { Reference } from './Reference.js'; |
|
||||
|
|
||||
export default class Statement { |
|
||||
constructor ( node, module, start, end ) { |
|
||||
this.node = node; |
|
||||
this.module = module; |
|
||||
this.start = start; |
|
||||
this.end = end; |
|
||||
this.next = null; // filled in later
|
|
||||
|
|
||||
this.scope = new Scope({ statement: this }); |
|
||||
|
|
||||
this.references = []; |
|
||||
this.stringLiteralRanges = []; |
|
||||
|
|
||||
this.isIncluded = false; |
|
||||
this.ran = false; |
|
||||
|
|
||||
this.isImportDeclaration = node.type === 'ImportDeclaration'; |
|
||||
this.isExportDeclaration = /^Export/.test( node.type ); |
|
||||
this.isReexportDeclaration = this.isExportDeclaration && !!node.source; |
|
||||
|
|
||||
this.isFunctionDeclaration = isFunctionDeclaration( node ) || |
|
||||
this.isExportDeclaration && isFunctionDeclaration( node.declaration ); |
|
||||
} |
|
||||
|
|
||||
firstPass () { |
|
||||
if ( this.isImportDeclaration ) return; // nothing to analyse
|
|
||||
|
|
||||
// attach scopes
|
|
||||
attachScopes( this ); |
|
||||
|
|
||||
// find references
|
|
||||
const statement = this; |
|
||||
let { module, references, scope, stringLiteralRanges } = this; |
|
||||
let contextDepth = 0; |
|
||||
|
|
||||
walk( this.node, { |
|
||||
enter ( node, parent, prop ) { |
|
||||
// warn about eval
|
|
||||
if ( node.type === 'CallExpression' && node.callee.name === 'eval' && !scope.contains( 'eval' ) ) { |
|
||||
// TODO show location
|
|
||||
module.bundle.onwarn( `Use of \`eval\` (in ${module.id}) is strongly discouraged, as it poses security risks and may cause issues with minification. See https://github.com/rollup/rollup/wiki/Troubleshooting#avoiding-eval for more details` ); |
|
||||
} |
|
||||
|
|
||||
// skip re-export declarations
|
|
||||
if ( node.type === 'ExportNamedDeclaration' && node.source ) return this.skip(); |
|
||||
|
|
||||
if ( node.type === 'TemplateElement' ) stringLiteralRanges.push([ node.start, node.end ]); |
|
||||
if ( node.type === 'Literal' && typeof node.value === 'string' && /\n/.test( node.raw ) ) { |
|
||||
stringLiteralRanges.push([ node.start + 1, node.end - 1 ]); |
|
||||
} |
|
||||
|
|
||||
if ( node.type === 'ThisExpression' && contextDepth === 0 ) { |
|
||||
module.magicString.overwrite( node.start, node.end, module.bundle.context ); |
|
||||
if ( module.bundle.context === 'undefined' ) module.bundle.onwarn( 'The `this` keyword is equivalent to `undefined` at the top level of an ES module, and has been rewritten' ); |
|
||||
} |
|
||||
|
|
||||
if ( node._scope ) scope = node._scope; |
|
||||
if ( /^Function/.test( node.type ) ) contextDepth += 1; |
|
||||
|
|
||||
let isReassignment; |
|
||||
|
|
||||
if ( parent && isModifierNode( parent ) ) { |
|
||||
let subject = parent[ modifierNodes[ parent.type ] ]; |
|
||||
|
|
||||
if ( node === subject ) { |
|
||||
let depth = 0; |
|
||||
|
|
||||
while ( subject.type === 'MemberExpression' ) { |
|
||||
subject = subject.object; |
|
||||
depth += 1; |
|
||||
} |
|
||||
|
|
||||
const importDeclaration = module.imports[ subject.name ]; |
|
||||
|
|
||||
if ( !scope.contains( subject.name ) && importDeclaration ) { |
|
||||
const minDepth = importDeclaration.name === '*' ? |
|
||||
2 : // cannot do e.g. `namespace.foo = bar`
|
|
||||
1; // cannot do e.g. `foo = bar`, but `foo.bar = bar` is fine
|
|
||||
|
|
||||
if ( depth < minDepth ) { |
|
||||
const err = new Error( `Illegal reassignment to import '${subject.name}'` ); |
|
||||
err.file = module.id; |
|
||||
err.loc = getLocation( module.magicString.original, subject.start ); |
|
||||
throw err; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
isReassignment = !depth; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
if ( isReference( node, parent ) ) { |
|
||||
// function declaration IDs are a special case – they're associated
|
|
||||
// with the parent scope
|
|
||||
const referenceScope = parent.type === 'FunctionDeclaration' && node === parent.id ? |
|
||||
scope.parent : |
|
||||
scope; |
|
||||
|
|
||||
const isShorthandProperty = parent.type === 'Property' && parent.shorthand; |
|
||||
|
|
||||
// Since `node.key` can equal `node.value` for shorthand properties
|
|
||||
// we must use the `prop` argument provided by `estree-walker` to determine
|
|
||||
// if we're looking at the key or the value.
|
|
||||
// If they are equal, we'll return to not create duplicate references.
|
|
||||
if ( isShorthandProperty && parent.value === parent.key && prop === 'value' ) { |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
const reference = new Reference( node, referenceScope, statement ); |
|
||||
reference.isReassignment = isReassignment; |
|
||||
reference.isShorthandProperty = isShorthandProperty; |
|
||||
references.push( reference ); |
|
||||
|
|
||||
this.skip(); // don't descend from `foo.bar.baz` into `foo.bar`
|
|
||||
} |
|
||||
}, |
|
||||
leave ( node ) { |
|
||||
if ( node._scope ) scope = scope.parent; |
|
||||
if ( /^Function/.test( node.type ) ) contextDepth -= 1; |
|
||||
} |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
mark () { |
|
||||
if ( this.isIncluded ) return; // prevent infinite loops
|
|
||||
this.isIncluded = true; |
|
||||
|
|
||||
this.references.forEach( reference => { |
|
||||
if ( reference.declaration ) reference.declaration.use(); |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
run ( strongDependencies ) { |
|
||||
if ( ( this.ran && this.isIncluded ) || this.isImportDeclaration || this.isFunctionDeclaration ) return; |
|
||||
this.ran = true; |
|
||||
|
|
||||
if ( run( this.node, this.scope, this, strongDependencies, false ) ) { |
|
||||
this.mark(); |
|
||||
return true; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
source () { |
|
||||
return this.module.source.slice( this.start, this.end ); |
|
||||
} |
|
||||
|
|
||||
toString () { |
|
||||
return this.module.magicString.slice( this.start, this.end ); |
|
||||
} |
|
||||
} |
|
@ -0,0 +1,94 @@ |
|||||
|
import { UNKNOWN } from './values.js'; |
||||
|
import getLocation from '../utils/getLocation.js'; |
||||
|
|
||||
|
export default class Node { |
||||
|
bind ( scope ) { |
||||
|
this.eachChild( child => child.bind( this.scope || scope ) ); |
||||
|
} |
||||
|
|
||||
|
eachChild ( callback ) { |
||||
|
for ( const key of this.keys ) { |
||||
|
if ( this.shorthand && key === 'key' ) continue; // key and value are the same
|
||||
|
|
||||
|
const value = this[ key ]; |
||||
|
|
||||
|
if ( value ) { |
||||
|
if ( 'length' in value ) { |
||||
|
for ( const child of value ) { |
||||
|
if ( child ) callback( child ); |
||||
|
} |
||||
|
} else if ( value ) { |
||||
|
callback( value ); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
findParent ( selector ) { |
||||
|
return selector.test( this.type ) ? this : this.parent.findParent( selector ); |
||||
|
} |
||||
|
|
||||
|
// TODO abolish findScope. if a node needs to store scope, store it
|
||||
|
findScope ( functionScope ) { |
||||
|
return this.parent.findScope( functionScope ); |
||||
|
} |
||||
|
|
||||
|
gatherPossibleValues ( values ) { |
||||
|
//this.eachChild( child => child.gatherPossibleValues( values ) );
|
||||
|
values.add( UNKNOWN ); |
||||
|
} |
||||
|
|
||||
|
getValue () { |
||||
|
return UNKNOWN; |
||||
|
} |
||||
|
|
||||
|
hasEffects ( scope ) { |
||||
|
if ( this.scope ) scope = this.scope; |
||||
|
|
||||
|
for ( const key of this.keys ) { |
||||
|
const value = this[ key ]; |
||||
|
|
||||
|
if ( value ) { |
||||
|
if ( 'length' in value ) { |
||||
|
for ( const child of value ) { |
||||
|
if ( child && child.hasEffects( scope ) ) { |
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
|
} else if ( value && value.hasEffects( scope ) ) { |
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
initialise ( scope ) { |
||||
|
this.eachChild( child => child.initialise( this.scope || scope ) ); |
||||
|
} |
||||
|
|
||||
|
locate () { |
||||
|
// useful for debugging
|
||||
|
const location = getLocation( this.module.code, this.start ); |
||||
|
location.file = this.module.id; |
||||
|
location.toString = () => JSON.stringify( location ); |
||||
|
|
||||
|
return location; |
||||
|
} |
||||
|
|
||||
|
render ( code, es ) { |
||||
|
this.eachChild( child => child.render( code, es ) ); |
||||
|
} |
||||
|
|
||||
|
run ( scope ) { |
||||
|
if ( this.ran ) return; |
||||
|
this.ran = true; |
||||
|
|
||||
|
this.eachChild( child => { |
||||
|
child.run( this.scope || scope ); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
toString () { |
||||
|
return this.module.code.slice( this.start, this.end ); |
||||
|
} |
||||
|
} |
@ -1,52 +0,0 @@ |
|||||
import { blank, keys } from '../utils/object.js'; |
|
||||
import Declaration from '../Declaration.js'; |
|
||||
import extractNames from './extractNames.js'; |
|
||||
|
|
||||
export default class Scope { |
|
||||
constructor ( options ) { |
|
||||
options = options || {}; |
|
||||
|
|
||||
this.parent = options.parent; |
|
||||
this.statement = options.statement || this.parent.statement; |
|
||||
this.isBlockScope = !!options.block; |
|
||||
this.isTopLevel = !this.parent || ( this.parent.isTopLevel && this.isBlockScope ); |
|
||||
|
|
||||
this.declarations = blank(); |
|
||||
|
|
||||
if ( options.params ) { |
|
||||
options.params.forEach( param => { |
|
||||
extractNames( param ).forEach( name => { |
|
||||
this.declarations[ name ] = new Declaration( param, true, this.statement ); |
|
||||
}); |
|
||||
}); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
addDeclaration ( node, isBlockDeclaration, isVar ) { |
|
||||
if ( !isBlockDeclaration && this.isBlockScope ) { |
|
||||
// it's a `var` or function node, and this
|
|
||||
// is a block scope, so we need to go up
|
|
||||
this.parent.addDeclaration( node, isBlockDeclaration, isVar ); |
|
||||
} else { |
|
||||
extractNames( node.id ).forEach( name => { |
|
||||
this.declarations[ name ] = new Declaration( node, false, this.statement ); |
|
||||
}); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
contains ( name ) { |
|
||||
return this.declarations[ name ] || |
|
||||
( this.parent ? this.parent.contains( name ) : false ); |
|
||||
} |
|
||||
|
|
||||
eachDeclaration ( fn ) { |
|
||||
keys( this.declarations ).forEach( key => { |
|
||||
fn( key, this.declarations[ key ] ); |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
findDeclaration ( name ) { |
|
||||
return this.declarations[ name ] || |
|
||||
( this.parent && this.parent.findDeclaration( name ) ); |
|
||||
} |
|
||||
} |
|
@ -1,78 +0,0 @@ |
|||||
import { walk } from 'estree-walker'; |
|
||||
import Scope from './Scope.js'; |
|
||||
|
|
||||
const blockDeclarations = { |
|
||||
const: true, |
|
||||
let: true |
|
||||
}; |
|
||||
|
|
||||
export default function attachScopes ( statement ) { |
|
||||
let { node, scope } = statement; |
|
||||
|
|
||||
walk( node, { |
|
||||
enter ( node, parent ) { |
|
||||
// function foo () {...}
|
|
||||
// class Foo {...}
|
|
||||
if ( /(Function|Class)Declaration/.test( node.type ) ) { |
|
||||
scope.addDeclaration( node, false, false ); |
|
||||
} |
|
||||
|
|
||||
// var foo = 1, bar = 2
|
|
||||
if ( node.type === 'VariableDeclaration' ) { |
|
||||
const isBlockDeclaration = blockDeclarations[ node.kind ]; |
|
||||
|
|
||||
node.declarations.forEach( declarator => { |
|
||||
scope.addDeclaration( declarator, isBlockDeclaration, true ); |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
let newScope; |
|
||||
|
|
||||
// create new function scope
|
|
||||
if ( /(Function|Class)/.test( node.type ) ) { |
|
||||
newScope = new Scope({ |
|
||||
parent: scope, |
|
||||
block: false, |
|
||||
params: node.params |
|
||||
}); |
|
||||
|
|
||||
// named function expressions - the name is considered
|
|
||||
// part of the function's scope
|
|
||||
if ( /(Function|Class)Expression/.test( node.type ) && node.id ) { |
|
||||
newScope.addDeclaration( node, false, false ); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// create new block scope
|
|
||||
if ( node.type === 'BlockStatement' && ( !parent || !/Function/.test( parent.type ) ) ) { |
|
||||
newScope = new Scope({ |
|
||||
parent: scope, |
|
||||
block: true |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
// catch clause has its own block scope
|
|
||||
if ( node.type === 'CatchClause' ) { |
|
||||
newScope = new Scope({ |
|
||||
parent: scope, |
|
||||
params: [ node.param ], |
|
||||
block: true |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
if ( newScope ) { |
|
||||
Object.defineProperty( node, '_scope', { |
|
||||
value: newScope, |
|
||||
configurable: true |
|
||||
}); |
|
||||
|
|
||||
scope = newScope; |
|
||||
} |
|
||||
}, |
|
||||
leave ( node ) { |
|
||||
if ( node._scope ) { |
|
||||
scope = scope.parent; |
|
||||
} |
|
||||
} |
|
||||
}); |
|
||||
} |
|
@ -1,38 +0,0 @@ |
|||||
export function isTruthy ( node ) { |
|
||||
if ( node.type === 'Literal' ) return !!node.value; |
|
||||
if ( node.type === 'ParenthesizedExpression' ) return isTruthy( node.expression ); |
|
||||
if ( node.operator in operators ) return operators[ node.operator ]( node ); |
|
||||
} |
|
||||
|
|
||||
export function isFalsy ( node ) { |
|
||||
return not( isTruthy( node ) ); |
|
||||
} |
|
||||
|
|
||||
function not ( value ) { |
|
||||
return value === undefined ? value : !value; |
|
||||
} |
|
||||
|
|
||||
function equals ( a, b, strict ) { |
|
||||
if ( a.type !== b.type ) return undefined; |
|
||||
if ( a.type === 'Literal' ) return strict ? a.value === b.value : a.value == b.value; |
|
||||
} |
|
||||
|
|
||||
const operators = { |
|
||||
'==': x => { |
|
||||
return equals( x.left, x.right, false ); |
|
||||
}, |
|
||||
|
|
||||
'!=': x => not( operators['==']( x ) ), |
|
||||
|
|
||||
'===': x => { |
|
||||
return equals( x.left, x.right, true ); |
|
||||
}, |
|
||||
|
|
||||
'!==': x => not( operators['===']( x ) ), |
|
||||
|
|
||||
'!': x => isFalsy( x.argument ), |
|
||||
|
|
||||
'&&': x => isTruthy( x.left ) && isTruthy( x.right ), |
|
||||
|
|
||||
'||': x => isTruthy( x.left ) || isTruthy( x.right ) |
|
||||
}; |
|
@ -1,7 +0,0 @@ |
|||||
export function emptyBlockStatement ( start, end ) { |
|
||||
return { |
|
||||
start, end, |
|
||||
type: 'BlockStatement', |
|
||||
body: [] |
|
||||
}; |
|
||||
} |
|
@ -0,0 +1,63 @@ |
|||||
|
import nodes from './nodes/index.js'; |
||||
|
import Node from './Node.js'; |
||||
|
import keys from './keys.js'; |
||||
|
|
||||
|
const newline = /\n/; |
||||
|
|
||||
|
export default function enhance ( ast, module, comments ) { |
||||
|
enhanceNode( ast, module, module, module.magicString ); |
||||
|
|
||||
|
let comment = comments.shift(); |
||||
|
|
||||
|
for ( const node of ast.body ) { |
||||
|
if ( comment && ( comment.start < node.start ) ) { |
||||
|
node.leadingCommentStart = comment.start; |
||||
|
} |
||||
|
|
||||
|
while ( comment && comment.end < node.end ) comment = comments.shift(); |
||||
|
|
||||
|
// if the next comment is on the same line as the end of the node,
|
||||
|
// treat is as a trailing comment
|
||||
|
if ( comment && !newline.test( module.code.slice( node.end, comment.start ) ) ) { |
||||
|
node.trailingCommentEnd = comment.end; // TODO is node.trailingCommentEnd used anywhere?
|
||||
|
comment = comments.shift(); |
||||
|
} |
||||
|
|
||||
|
node.initialise( module.scope ); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function enhanceNode ( raw, parent, module, code ) { |
||||
|
if ( !raw ) return; |
||||
|
|
||||
|
if ( 'length' in raw ) { |
||||
|
for ( let i = 0; i < raw.length; i += 1 ) { |
||||
|
enhanceNode( raw[i], parent, module, code ); |
||||
|
} |
||||
|
|
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// with e.g. shorthand properties, key and value are
|
||||
|
// the same node. We don't want to enhance an object twice
|
||||
|
if ( raw.__enhanced ) return; |
||||
|
raw.__enhanced = true; |
||||
|
|
||||
|
if ( !keys[ raw.type ] ) { |
||||
|
keys[ raw.type ] = Object.keys( raw ).filter( key => typeof raw[ key ] === 'object' ); |
||||
|
} |
||||
|
|
||||
|
raw.parent = parent; |
||||
|
raw.module = module; |
||||
|
raw.keys = keys[ raw.type ]; |
||||
|
|
||||
|
code.addSourcemapLocation( raw.start ); |
||||
|
code.addSourcemapLocation( raw.end ); |
||||
|
|
||||
|
for ( const key of keys[ raw.type ] ) { |
||||
|
enhanceNode( raw[ key ], raw, module, code ); |
||||
|
} |
||||
|
|
||||
|
const type = nodes[ raw.type ] || Node; |
||||
|
raw.__proto__ = type.prototype; |
||||
|
} |
@ -1,6 +0,0 @@ |
|||||
export default function isFunctionDeclaration ( node ) { |
|
||||
if ( !node ) return false; |
|
||||
|
|
||||
return node.type === 'FunctionDeclaration' || |
|
||||
( node.type === 'VariableDeclaration' && node.init && /FunctionExpression/.test( node.init.type ) ); |
|
||||
} |
|
@ -0,0 +1,4 @@ |
|||||
|
export default { |
||||
|
Program: [ 'body' ], |
||||
|
Literal: [] |
||||
|
}; |
@ -1,19 +0,0 @@ |
|||||
const modifierNodes = { |
|
||||
AssignmentExpression: 'left', |
|
||||
UpdateExpression: 'argument', |
|
||||
UnaryExpression: 'argument' |
|
||||
}; |
|
||||
|
|
||||
export default modifierNodes; |
|
||||
|
|
||||
export function isModifierNode ( node ) { |
|
||||
if ( !( node.type in modifierNodes ) ) { |
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
if ( node.type === 'UnaryExpression' ) { |
|
||||
return node.operator === 'delete'; |
|
||||
} |
|
||||
|
|
||||
return true; |
|
||||
} |
|
@ -0,0 +1,8 @@ |
|||||
|
import Node from '../Node.js'; |
||||
|
import { ARRAY } from '../values.js'; |
||||
|
|
||||
|
export default class ArrayExpression extends Node { |
||||
|
gatherPossibleValues ( values ) { |
||||
|
values.add( ARRAY ); |
||||
|
} |
||||
|
} |
@ -0,0 +1,38 @@ |
|||||
|
import Node from '../Node.js'; |
||||
|
import Scope from '../scopes/Scope.js'; |
||||
|
import extractNames from '../utils/extractNames.js'; |
||||
|
|
||||
|
export default class ArrowFunctionExpression extends Node { |
||||
|
bind ( scope ) { |
||||
|
super.bind( this.scope || scope ); |
||||
|
} |
||||
|
|
||||
|
findScope ( functionScope ) { |
||||
|
return this.scope || this.parent.findScope( functionScope ); |
||||
|
} |
||||
|
|
||||
|
hasEffects () { |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
initialise ( scope ) { |
||||
|
if ( this.body.type === 'BlockStatement' ) { |
||||
|
this.body.createScope( scope ); |
||||
|
this.scope = this.body.scope; |
||||
|
} else { |
||||
|
this.scope = new Scope({ |
||||
|
parent: scope, |
||||
|
isBlockScope: false, |
||||
|
isLexicalBoundary: false |
||||
|
}); |
||||
|
|
||||
|
for ( const param of this.params ) { |
||||
|
for ( const name of extractNames( param ) ) { |
||||
|
this.scope.addDeclaration( name, null, null, true ); // TODO ugh
|
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
super.initialise( this.scope ); |
||||
|
} |
||||
|
} |
@ -0,0 +1,47 @@ |
|||||
|
import Node from '../Node.js'; |
||||
|
import disallowIllegalReassignment from './shared/disallowIllegalReassignment.js'; |
||||
|
import isUsedByBundle from './shared/isUsedByBundle.js'; |
||||
|
import { NUMBER, STRING } from '../values.js'; |
||||
|
|
||||
|
export default class AssignmentExpression extends Node { |
||||
|
bind ( scope ) { |
||||
|
let subject = this.left; |
||||
|
while ( this.left.type === 'ParenthesizedExpression' ) subject = subject.expression; |
||||
|
|
||||
|
this.subject = subject; |
||||
|
disallowIllegalReassignment( scope, subject ); |
||||
|
|
||||
|
if ( subject.type === 'Identifier' ) { |
||||
|
const declaration = scope.findDeclaration( subject.name ); |
||||
|
declaration.isReassigned = true; |
||||
|
|
||||
|
if ( declaration.possibleValues ) { // TODO this feels hacky
|
||||
|
if ( this.operator === '=' ) { |
||||
|
declaration.possibleValues.add( this.right ); |
||||
|
} else if ( this.operator === '+=' ) { |
||||
|
declaration.possibleValues.add( STRING ).add( NUMBER ); |
||||
|
} else { |
||||
|
declaration.possibleValues.add( NUMBER ); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
super.bind( scope ); |
||||
|
} |
||||
|
|
||||
|
hasEffects ( scope ) { |
||||
|
const hasEffects = this.isUsedByBundle() || this.right.hasEffects( scope ); |
||||
|
return hasEffects; |
||||
|
} |
||||
|
|
||||
|
initialise ( scope ) { |
||||
|
this.scope = scope; |
||||
|
|
||||
|
this.module.bundle.dependentExpressions.push( this ); |
||||
|
super.initialise( scope ); |
||||
|
} |
||||
|
|
||||
|
isUsedByBundle () { |
||||
|
return isUsedByBundle( this.scope, this.subject ); |
||||
|
} |
||||
|
} |
@ -0,0 +1,38 @@ |
|||||
|
import Node from '../Node.js'; |
||||
|
import { UNKNOWN } from '../values.js'; |
||||
|
|
||||
|
const operators = { |
||||
|
'==': ( left, right ) => left == right, |
||||
|
'!=': ( left, right ) => left != right, |
||||
|
'===': ( left, right ) => left === right, |
||||
|
'!==': ( left, right ) => left !== right, |
||||
|
'<': ( left, right ) => left < right, |
||||
|
'<=': ( left, right ) => left <= right, |
||||
|
'>': ( left, right ) => left > right, |
||||
|
'>=': ( left, right ) => left >= right, |
||||
|
'<<': ( left, right ) => left << right, |
||||
|
'>>': ( left, right ) => left >> right, |
||||
|
'>>>': ( left, right ) => left >>> right, |
||||
|
'+': ( left, right ) => left + right, |
||||
|
'-': ( left, right ) => left - right, |
||||
|
'*': ( left, right ) => left * right, |
||||
|
'/': ( left, right ) => left / right, |
||||
|
'%': ( left, right ) => left % right, |
||||
|
'|': ( left, right ) => left | right, |
||||
|
'^': ( left, right ) => left ^ right, |
||||
|
'&': ( left, right ) => left & right, |
||||
|
in: ( left, right ) => left in right, |
||||
|
instanceof: ( left, right ) => left instanceof right |
||||
|
}; |
||||
|
|
||||
|
export default class BinaryExpression extends Node { |
||||
|
getValue () { |
||||
|
const leftValue = this.left.getValue(); |
||||
|
if ( leftValue === UNKNOWN ) return UNKNOWN; |
||||
|
|
||||
|
const rightValue = this.right.getValue(); |
||||
|
if ( rightValue === UNKNOWN ) return UNKNOWN; |
||||
|
|
||||
|
return operators[ this.operator ]( leftValue, rightValue ); |
||||
|
} |
||||
|
} |
@ -0,0 +1,49 @@ |
|||||
|
import Statement from './shared/Statement.js'; |
||||
|
import Scope from '../scopes/Scope.js'; |
||||
|
import extractNames from '../utils/extractNames.js'; |
||||
|
|
||||
|
export default class BlockStatement extends Statement { |
||||
|
bind () { |
||||
|
for ( const node of this.body ) { |
||||
|
node.bind( this.scope ); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
createScope ( parent ) { |
||||
|
this.parentIsFunction = /Function/.test( this.parent.type ); |
||||
|
this.isFunctionBlock = this.parentIsFunction || this.parent.type === 'Module'; |
||||
|
|
||||
|
this.scope = new Scope({ |
||||
|
parent, |
||||
|
isBlockScope: !this.isFunctionBlock, |
||||
|
isLexicalBoundary: this.isFunctionBlock && this.parent.type !== 'ArrowFunctionExpression', |
||||
|
owner: this // TODO is this used anywhere?
|
||||
|
}); |
||||
|
|
||||
|
const params = this.parent.params || ( this.parent.type === 'CatchClause' && [ this.parent.param ] ); |
||||
|
|
||||
|
if ( params && params.length ) { |
||||
|
params.forEach( node => { |
||||
|
extractNames( node ).forEach( name => { |
||||
|
this.scope.addDeclaration( name, node, false, true ); |
||||
|
}); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
findScope ( functionScope ) { |
||||
|
return functionScope && !this.isFunctionBlock ? this.parent.findScope( functionScope ) : this.scope; |
||||
|
} |
||||
|
|
||||
|
initialise ( scope ) { |
||||
|
if ( !this.scope ) this.createScope( scope ); // scope can be created early in some cases, e.g for (let i... )
|
||||
|
|
||||
|
let lastNode; |
||||
|
for ( const node of this.body ) { |
||||
|
node.initialise( this.scope ); |
||||
|
|
||||
|
if ( lastNode ) lastNode.next = node.start; |
||||
|
lastNode = node; |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,40 @@ |
|||||
|
import getLocation from '../../utils/getLocation.js'; |
||||
|
import error from '../../utils/error.js'; |
||||
|
import Node from '../Node.js'; |
||||
|
import callHasEffects from './shared/callHasEffects.js'; |
||||
|
|
||||
|
export default class CallExpression extends Node { |
||||
|
bind ( scope ) { |
||||
|
if ( this.callee.type === 'Identifier' ) { |
||||
|
const declaration = scope.findDeclaration( this.callee.name ); |
||||
|
|
||||
|
if ( declaration.isNamespace ) { |
||||
|
error({ |
||||
|
message: `Cannot call a namespace ('${this.callee.name}')`, |
||||
|
file: this.module.id, |
||||
|
pos: this.start, |
||||
|
loc: getLocation( this.module.code, this.start ) |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
if ( this.callee.name === 'eval' && declaration.isGlobal ) { |
||||
|
this.module.bundle.onwarn( `Use of \`eval\` (in ${this.module.id}) is strongly discouraged, as it poses security risks and may cause issues with minification. See https://github.com/rollup/rollup/wiki/Troubleshooting#avoiding-eval for more details` ); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
super.bind( scope ); |
||||
|
} |
||||
|
|
||||
|
hasEffects ( scope ) { |
||||
|
return callHasEffects( scope, this.callee ); |
||||
|
} |
||||
|
|
||||
|
initialise ( scope ) { |
||||
|
this.module.bundle.dependentExpressions.push( this ); |
||||
|
super.initialise( scope ); |
||||
|
} |
||||
|
|
||||
|
isUsedByBundle () { |
||||
|
return this.hasEffects( this.findScope() ); |
||||
|
} |
||||
|
} |
@ -0,0 +1,45 @@ |
|||||
|
import Node from '../Node.js'; |
||||
|
|
||||
|
// TODO is this basically identical to FunctionDeclaration?
|
||||
|
export default class ClassDeclaration extends Node { |
||||
|
activate () { |
||||
|
if ( this.activated ) return; |
||||
|
this.activated = true; |
||||
|
|
||||
|
if ( this.superClass ) this.superClass.run( this.scope ); |
||||
|
this.body.run(); |
||||
|
} |
||||
|
|
||||
|
addReference () { |
||||
|
/* noop? */ |
||||
|
} |
||||
|
|
||||
|
gatherPossibleValues ( values ) { |
||||
|
values.add( this ); |
||||
|
} |
||||
|
|
||||
|
getName () { |
||||
|
return this.name; |
||||
|
} |
||||
|
|
||||
|
hasEffects () { |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
initialise ( scope ) { |
||||
|
this.scope = scope; |
||||
|
|
||||
|
this.name = this.id.name; |
||||
|
|
||||
|
scope.addDeclaration( this.name, this, false, false ); |
||||
|
super.initialise( scope ); |
||||
|
} |
||||
|
|
||||
|
render ( code, es ) { |
||||
|
if ( this.activated ) { |
||||
|
super.render( code, es ); |
||||
|
} else { |
||||
|
code.remove( this.leadingCommentStart || this.start, this.next || this.end ); |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,26 @@ |
|||||
|
import Node from '../Node.js'; |
||||
|
import Scope from '../scopes/Scope.js'; |
||||
|
|
||||
|
export default class ClassExpression extends Node { |
||||
|
bind () { |
||||
|
super.bind( this.scope ); |
||||
|
} |
||||
|
|
||||
|
findScope () { |
||||
|
return this.scope; |
||||
|
} |
||||
|
|
||||
|
initialise () { |
||||
|
this.scope = new Scope({ |
||||
|
isBlockScope: true, |
||||
|
parent: this.parent.findScope( false ) |
||||
|
}); |
||||
|
|
||||
|
if ( this.id ) { |
||||
|
// function expression IDs belong to the child scope...
|
||||
|
this.scope.addDeclaration( this.id.name, this, false, true ); |
||||
|
} |
||||
|
|
||||
|
super.initialise( this.scope ); |
||||
|
} |
||||
|
} |
@ -0,0 +1,65 @@ |
|||||
|
import Node from '../Node.js'; |
||||
|
import { UNKNOWN } from '../values.js'; |
||||
|
|
||||
|
export default class ConditionalExpression extends Node { |
||||
|
initialise ( scope ) { |
||||
|
if ( this.module.bundle.treeshake ) { |
||||
|
this.testValue = this.test.getValue(); |
||||
|
|
||||
|
if ( this.testValue === UNKNOWN ) { |
||||
|
super.initialise( scope ); |
||||
|
} |
||||
|
|
||||
|
else if ( this.testValue ) { |
||||
|
this.consequent.initialise( scope ); |
||||
|
this.alternate = null; |
||||
|
} else if ( this.alternate ) { |
||||
|
this.alternate.initialise( scope ); |
||||
|
this.consequent = null; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
else { |
||||
|
super.initialise( scope ); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
gatherPossibleValues ( values ) { |
||||
|
const testValue = this.test.getValue(); |
||||
|
|
||||
|
if ( testValue === UNKNOWN ) { |
||||
|
values.add( this.consequent ).add( this.alternate ); |
||||
|
} else { |
||||
|
values.add( testValue ? this.consequent : this.alternate ); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
getValue () { |
||||
|
const testValue = this.test.getValue(); |
||||
|
if ( testValue === UNKNOWN ) return UNKNOWN; |
||||
|
|
||||
|
return testValue ? this.consequent.getValue() : this.alternate.getValue(); |
||||
|
} |
||||
|
|
||||
|
render ( code, es ) { |
||||
|
if ( !this.module.bundle.treeshake ) { |
||||
|
super.render( code, es ); |
||||
|
} |
||||
|
|
||||
|
else { |
||||
|
if ( this.testValue === UNKNOWN ) { |
||||
|
super.render( code, es ); |
||||
|
} |
||||
|
|
||||
|
else if ( this.testValue ) { |
||||
|
code.remove( this.start, this.consequent.start ); |
||||
|
code.remove( this.consequent.end, this.end ); |
||||
|
this.consequent.render( code, es ); |
||||
|
} else { |
||||
|
code.remove( this.start, this.alternate.start ); |
||||
|
code.remove( this.alternate.end, this.end ); |
||||
|
this.alternate.render( code, es ); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,9 @@ |
|||||
|
import Statement from './shared/Statement.js'; |
||||
|
|
||||
|
export default class EmptyStatement extends Statement { |
||||
|
render ( code ) { |
||||
|
if ( this.parent.type === 'BlockStatement' || this.parent.type === 'Program' ) { |
||||
|
code.remove( this.start, this.end ); |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,11 @@ |
|||||
|
import Node from '../Node.js'; |
||||
|
|
||||
|
export default class ExportAllDeclaration extends Node { |
||||
|
initialise () { |
||||
|
this.isExportDeclaration = true; |
||||
|
} |
||||
|
|
||||
|
render ( code ) { |
||||
|
code.remove( this.leadingCommentStart || this.start, this.next || this.end ); |
||||
|
} |
||||
|
} |
@ -0,0 +1,96 @@ |
|||||
|
import Node from '../Node.js'; |
||||
|
|
||||
|
const functionOrClassDeclaration = /^(?:Function|Class)Declaration/; |
||||
|
|
||||
|
export default class ExportDefaultDeclaration extends Node { |
||||
|
initialise ( scope ) { |
||||
|
this.isExportDeclaration = true; |
||||
|
this.isDefault = true; |
||||
|
|
||||
|
this.name = ( this.declaration.id && this.declaration.id.name ) || this.declaration.name || this.module.basename(); |
||||
|
scope.declarations.default = this; |
||||
|
|
||||
|
this.declaration.initialise( scope ); |
||||
|
} |
||||
|
|
||||
|
activate () { |
||||
|
if ( this.activated ) return; |
||||
|
this.activated = true; |
||||
|
|
||||
|
this.run(); |
||||
|
} |
||||
|
|
||||
|
addReference ( reference ) { |
||||
|
this.name = reference.name; |
||||
|
if ( this.original ) this.original.addReference( reference ); |
||||
|
} |
||||
|
|
||||
|
bind ( scope ) { |
||||
|
const name = ( this.declaration.id && this.declaration.id.name ) || this.declaration.name; |
||||
|
if ( name ) this.original = scope.findDeclaration( name ); |
||||
|
|
||||
|
this.declaration.bind( scope ); |
||||
|
} |
||||
|
|
||||
|
gatherPossibleValues ( values ) { |
||||
|
this.declaration.gatherPossibleValues( values ); |
||||
|
} |
||||
|
|
||||
|
getName ( es ) { |
||||
|
if ( this.original && !this.original.isReassigned ) { |
||||
|
return this.original.getName( es ); |
||||
|
} |
||||
|
|
||||
|
return this.name; |
||||
|
} |
||||
|
|
||||
|
// TODO this is total chaos, tidy it up
|
||||
|
render ( code, es ) { |
||||
|
const treeshake = this.module.bundle.treeshake; |
||||
|
const name = this.getName( es ); |
||||
|
|
||||
|
if ( this.shouldInclude ) { |
||||
|
if ( this.activated ) { |
||||
|
if ( functionOrClassDeclaration.test( this.declaration.type ) ) { |
||||
|
if ( this.declaration.id ) { |
||||
|
code.remove( this.start, this.declaration.start ); |
||||
|
} else { |
||||
|
throw new Error( 'TODO anonymous class/function declaration' ); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
else { |
||||
|
if ( this.original && this.original.getName( es ) === name ) { |
||||
|
// prevent `var foo = foo`
|
||||
|
code.remove( this.leadingCommentStart || this.start, this.next || this.end ); |
||||
|
return; // don't render children. TODO this seems like a bit of a hack
|
||||
|
} else { |
||||
|
code.overwrite( this.start, this.declaration.start, `${this.module.bundle.varOrConst} ${name} = ` ); |
||||
|
} |
||||
|
} |
||||
|
} else { |
||||
|
// remove `var foo` from `var foo = bar()`, if `foo` is unused
|
||||
|
code.remove( this.start, this.declaration.start ); |
||||
|
} |
||||
|
|
||||
|
super.render( code, es ); |
||||
|
} else { |
||||
|
if ( treeshake ) { |
||||
|
if ( functionOrClassDeclaration.test( this.declaration.type ) && !this.declaration.activated ) { |
||||
|
code.remove( this.leadingCommentStart || this.start, this.next || this.end ); |
||||
|
} else { |
||||
|
const hasEffects = this.declaration.hasEffects( this.module.scope ); |
||||
|
code.remove( this.start, hasEffects ? this.declaration.start : this.next || this.end ); |
||||
|
} |
||||
|
} else { |
||||
|
code.overwrite( this.start, this.declaration.start, `${this.module.bundle.varOrConst} ${name} = ` ); |
||||
|
} |
||||
|
// code.remove( this.start, this.next || this.end );
|
||||
|
} |
||||
|
} |
||||
|
|
||||
|
run ( scope ) { |
||||
|
this.shouldInclude = true; |
||||
|
super.run( scope ); |
||||
|
} |
||||
|
} |
@ -0,0 +1,25 @@ |
|||||
|
import Node from '../Node.js'; |
||||
|
|
||||
|
export default class ExportNamedDeclaration extends Node { |
||||
|
initialise ( scope ) { |
||||
|
this.isExportDeclaration = true; |
||||
|
if ( this.declaration ) { |
||||
|
this.declaration.initialise( scope ); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
bind ( scope ) { |
||||
|
if ( this.declaration ) { |
||||
|
this.declaration.bind( scope ); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
render ( code, es ) { |
||||
|
if ( this.declaration ) { |
||||
|
code.remove( this.start, this.declaration.start ); |
||||
|
this.declaration.render( code, es ); |
||||
|
} else { |
||||
|
code.remove( this.leadingCommentStart || this.start, this.next || this.end ); |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,5 @@ |
|||||
|
import Statement from './shared/Statement.js'; |
||||
|
|
||||
|
export default class ExpressionStatement extends Statement { |
||||
|
|
||||
|
} |
@ -0,0 +1,22 @@ |
|||||
|
import Statement from './shared/Statement.js'; |
||||
|
import assignTo from './shared/assignTo.js'; |
||||
|
import Scope from '../scopes/Scope.js'; |
||||
|
import { STRING } from '../values.js'; |
||||
|
|
||||
|
export default class ForInStatement extends Statement { |
||||
|
initialise ( scope ) { |
||||
|
if ( this.body.type === 'BlockStatement' ) { |
||||
|
this.body.createScope( scope ); |
||||
|
this.scope = this.body.scope; |
||||
|
} else { |
||||
|
this.scope = new Scope({ |
||||
|
parent: scope, |
||||
|
isBlockScope: true, |
||||
|
isLexicalBoundary: false |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
super.initialise( this.scope ); |
||||
|
assignTo( this.left, this.scope, STRING ); |
||||
|
} |
||||
|
} |
@ -0,0 +1,22 @@ |
|||||
|
import Statement from './shared/Statement.js'; |
||||
|
import assignTo from './shared/assignTo.js'; |
||||
|
import Scope from '../scopes/Scope.js'; |
||||
|
import { UNKNOWN } from '../values.js'; |
||||
|
|
||||
|
export default class ForOfStatement extends Statement { |
||||
|
initialise ( scope ) { |
||||
|
if ( this.body.type === 'BlockStatement' ) { |
||||
|
this.body.createScope( scope ); |
||||
|
this.scope = this.body.scope; |
||||
|
} else { |
||||
|
this.scope = new Scope({ |
||||
|
parent: scope, |
||||
|
isBlockScope: true, |
||||
|
isLexicalBoundary: false |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
super.initialise( this.scope ); |
||||
|
assignTo( this.left, this.scope, UNKNOWN ); |
||||
|
} |
||||
|
} |
@ -0,0 +1,23 @@ |
|||||
|
import Statement from './shared/Statement.js'; |
||||
|
import Scope from '../scopes/Scope.js'; |
||||
|
|
||||
|
export default class ForStatement extends Statement { |
||||
|
initialise ( scope ) { |
||||
|
if ( this.body.type === 'BlockStatement' ) { |
||||
|
this.body.createScope( scope ); |
||||
|
this.scope = this.body.scope; |
||||
|
} else { |
||||
|
this.scope = new Scope({ |
||||
|
parent: scope, |
||||
|
isBlockScope: true, |
||||
|
isLexicalBoundary: false |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
// can't use super, because we need to control the order
|
||||
|
if ( this.init ) this.init.initialise( this.scope ); |
||||
|
if ( this.test ) this.test.initialise( this.scope ); |
||||
|
if ( this.update ) this.update.initialise( this.scope ); |
||||
|
this.body.initialise( this.scope ); |
||||
|
} |
||||
|
} |
@ -0,0 +1,53 @@ |
|||||
|
import Node from '../Node.js'; |
||||
|
|
||||
|
export default class FunctionDeclaration extends Node { |
||||
|
activate () { |
||||
|
if ( this.activated ) return; |
||||
|
this.activated = true; |
||||
|
|
||||
|
const scope = this.body.scope; |
||||
|
this.params.forEach( param => param.run( scope ) ); // in case of assignment patterns
|
||||
|
this.body.run(); |
||||
|
} |
||||
|
|
||||
|
addReference () { |
||||
|
/* noop? */ |
||||
|
} |
||||
|
|
||||
|
bind ( scope ) { |
||||
|
this.id.bind( scope ); |
||||
|
this.params.forEach( param => param.bind( this.body.scope ) ); |
||||
|
this.body.bind( scope ); |
||||
|
} |
||||
|
|
||||
|
gatherPossibleValues ( values ) { |
||||
|
values.add( this ); |
||||
|
} |
||||
|
|
||||
|
getName () { |
||||
|
return this.name; |
||||
|
} |
||||
|
|
||||
|
hasEffects () { |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
initialise ( scope ) { |
||||
|
this.name = this.id.name; // may be overridden by bundle.deconflict
|
||||
|
scope.addDeclaration( this.name, this, false, false ); |
||||
|
|
||||
|
this.body.createScope( scope ); |
||||
|
|
||||
|
this.id.initialise( scope ); |
||||
|
this.params.forEach( param => param.initialise( this.body.scope ) ); |
||||
|
this.body.initialise(); |
||||
|
} |
||||
|
|
||||
|
render ( code, es ) { |
||||
|
if ( !this.module.bundle.treeshake || this.activated ) { |
||||
|
super.render( code, es ); |
||||
|
} else { |
||||
|
code.remove( this.leadingCommentStart || this.start, this.next || this.end ); |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,21 @@ |
|||||
|
import Node from '../Node.js'; |
||||
|
|
||||
|
export default class FunctionExpression extends Node { |
||||
|
bind () { |
||||
|
if ( this.id ) this.id.bind( this.body.scope ); |
||||
|
this.params.forEach( param => param.bind( this.body.scope ) ); |
||||
|
this.body.bind(); |
||||
|
} |
||||
|
|
||||
|
hasEffects () { |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
initialise ( scope ) { |
||||
|
this.body.createScope( scope ); |
||||
|
|
||||
|
if ( this.id ) this.id.initialise( this.body.scope ); |
||||
|
this.params.forEach( param => param.initialise( this.body.scope ) ); |
||||
|
this.body.initialise(); |
||||
|
} |
||||
|
} |
@ -0,0 +1,35 @@ |
|||||
|
import Node from '../Node.js'; |
||||
|
import isReference from '../utils/isReference.js'; |
||||
|
|
||||
|
export default class Identifier extends Node { |
||||
|
bind ( scope ) { |
||||
|
if ( isReference( this, this.parent ) ) { |
||||
|
this.declaration = scope.findDeclaration( this.name ); |
||||
|
this.declaration.addReference( this ); // TODO necessary?
|
||||
|
} |
||||
|
} |
||||
|
|
||||
|
gatherPossibleValues ( values ) { |
||||
|
if ( isReference( this, this.parent ) ) { |
||||
|
values.add( this ); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
render ( code, es ) { |
||||
|
if ( this.declaration ) { |
||||
|
const name = this.declaration.getName( es ); |
||||
|
if ( name !== this.name ) { |
||||
|
code.overwrite( this.start, this.end, name, true ); |
||||
|
|
||||
|
// special case
|
||||
|
if ( this.parent.type === 'Property' && this.parent.shorthand ) { |
||||
|
code.insertLeft( this.start, `${this.name}: ` ); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
run () { |
||||
|
if ( this.declaration ) this.declaration.activate(); |
||||
|
} |
||||
|
} |
@ -0,0 +1,55 @@ |
|||||
|
import Statement from './shared/Statement.js'; |
||||
|
import { UNKNOWN } from '../values.js'; |
||||
|
|
||||
|
// TODO DRY this out
|
||||
|
export default class IfStatement extends Statement { |
||||
|
initialise ( scope ) { |
||||
|
this.testValue = this.test.getValue(); |
||||
|
|
||||
|
if ( this.module.bundle.treeshake ) { |
||||
|
if ( this.testValue === UNKNOWN ) { |
||||
|
super.initialise( scope ); |
||||
|
} |
||||
|
|
||||
|
else if ( this.testValue ) { |
||||
|
this.consequent.initialise( scope ); |
||||
|
this.alternate = null; |
||||
|
} else { |
||||
|
if ( this.alternate ) this.alternate.initialise( scope ); |
||||
|
this.consequent = null; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
else { |
||||
|
super.initialise( scope ); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
render ( code, es ) { |
||||
|
if ( this.module.bundle.treeshake ) { |
||||
|
if ( this.testValue === UNKNOWN ) { |
||||
|
super.render( code, es ); |
||||
|
} |
||||
|
|
||||
|
else { |
||||
|
code.overwrite( this.test.start, this.test.end, JSON.stringify( this.testValue ) ); |
||||
|
|
||||
|
// TODO if no block-scoped declarations, remove enclosing
|
||||
|
// curlies and dedent block (if there is a block)
|
||||
|
|
||||
|
if ( this.testValue ) { |
||||
|
code.remove( this.start, this.consequent.start ); |
||||
|
code.remove( this.consequent.end, this.end ); |
||||
|
this.consequent.render( code, es ); |
||||
|
} else { |
||||
|
code.remove( this.start, this.alternate ? this.alternate.start : this.next || this.end ); |
||||
|
if ( this.alternate ) this.alternate.render( code, es ); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
else { |
||||
|
super.render( code, es ); |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,16 @@ |
|||||
|
import Node from '../Node.js'; |
||||
|
|
||||
|
export default class ImportDeclaration extends Node { |
||||
|
bind () { |
||||
|
// noop
|
||||
|
// TODO do the inter-module binding setup here?
|
||||
|
} |
||||
|
|
||||
|
initialise () { |
||||
|
this.isImportDeclaration = true; |
||||
|
} |
||||
|
|
||||
|
render ( code ) { |
||||
|
code.remove( this.start, this.next || this.end ); |
||||
|
} |
||||
|
} |
@ -0,0 +1,17 @@ |
|||||
|
import Node from '../Node.js'; |
||||
|
|
||||
|
export default class Literal extends Node { |
||||
|
getValue () { |
||||
|
return this.value; |
||||
|
} |
||||
|
|
||||
|
gatherPossibleValues ( values ) { |
||||
|
values.add( this ); |
||||
|
} |
||||
|
|
||||
|
render ( code ) { |
||||
|
if ( typeof this.value === 'string' ) { |
||||
|
code.indentExclusionRanges.push([ this.start + 1, this.end - 1 ]); |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,74 @@ |
|||||
|
import isReference from '../utils/isReference.js'; |
||||
|
import Node from '../Node.js'; |
||||
|
import { UNKNOWN } from '../values.js'; |
||||
|
|
||||
|
class Keypath { |
||||
|
constructor ( node ) { |
||||
|
this.parts = []; |
||||
|
|
||||
|
while ( node.type === 'MemberExpression' ) { |
||||
|
this.parts.unshift( node.property ); |
||||
|
node = node.object; |
||||
|
} |
||||
|
|
||||
|
this.root = node; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export default class MemberExpression extends Node { |
||||
|
bind ( scope ) { |
||||
|
// if this resolves to a namespaced declaration, prepare
|
||||
|
// to replace it
|
||||
|
// TODO this code is a bit inefficient
|
||||
|
if ( isReference( this ) ) { // TODO optimise namespace access like `foo['bar']` as well
|
||||
|
const keypath = new Keypath( this ); |
||||
|
|
||||
|
let declaration = scope.findDeclaration( keypath.root.name ); |
||||
|
|
||||
|
while ( declaration.isNamespace && keypath.parts.length ) { |
||||
|
const part = keypath.parts[0]; |
||||
|
declaration = declaration.module.traceExport( part.name ); |
||||
|
|
||||
|
if ( !declaration ) { |
||||
|
this.module.bundle.onwarn( `Export '${part.name}' is not defined by '${this.module.id}'` ); |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
keypath.parts.shift(); |
||||
|
} |
||||
|
|
||||
|
if ( keypath.parts.length ) { |
||||
|
super.bind( scope ); |
||||
|
return; // not a namespaced declaration
|
||||
|
} |
||||
|
|
||||
|
this.declaration = declaration; |
||||
|
|
||||
|
if ( declaration.isExternal ) { |
||||
|
declaration.module.suggestName( keypath.root.name ); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
else { |
||||
|
super.bind( scope ); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
gatherPossibleValues ( values ) { |
||||
|
values.add( UNKNOWN ); // TODO
|
||||
|
} |
||||
|
|
||||
|
render ( code, es ) { |
||||
|
if ( this.declaration ) { |
||||
|
const name = this.declaration.getName( es ); |
||||
|
if ( name !== this.name ) code.overwrite( this.start, this.end, name, true ); |
||||
|
} |
||||
|
|
||||
|
super.render( code, es ); |
||||
|
} |
||||
|
|
||||
|
run ( scope ) { |
||||
|
if ( this.declaration ) this.declaration.activate(); |
||||
|
super.run( scope ); |
||||
|
} |
||||
|
} |
@ -0,0 +1,8 @@ |
|||||
|
import Node from '../Node.js'; |
||||
|
import callHasEffects from './shared/callHasEffects.js'; |
||||
|
|
||||
|
export default class NewExpression extends Node { |
||||
|
hasEffects ( scope ) { |
||||
|
return callHasEffects( scope, this.callee ); |
||||
|
} |
||||
|
} |
@ -0,0 +1,8 @@ |
|||||
|
import Node from '../Node.js'; |
||||
|
import { OBJECT } from '../values.js'; |
||||
|
|
||||
|
export default class ObjectExpression extends Node { |
||||
|
gatherPossibleValues ( values ) { |
||||
|
values.add( OBJECT ); |
||||
|
} |
||||
|
} |
@ -0,0 +1,11 @@ |
|||||
|
import Node from '../Node.js'; |
||||
|
|
||||
|
export default class ParenthesizedExpression extends Node { |
||||
|
getPossibleValues ( values ) { |
||||
|
return this.expression.getPossibleValues( values ); |
||||
|
} |
||||
|
|
||||
|
getValue () { |
||||
|
return this.expression.getValue(); |
||||
|
} |
||||
|
} |
@ -0,0 +1,7 @@ |
|||||
|
import Node from '../Node.js'; |
||||
|
|
||||
|
export default class ReturnStatement extends Node { |
||||
|
// hasEffects () {
|
||||
|
// return true;
|
||||
|
// }
|
||||
|
} |
@ -0,0 +1,8 @@ |
|||||
|
import Node from '../Node.js'; |
||||
|
|
||||
|
export default class TemplateLiteral extends Node { |
||||
|
render ( code, es ) { |
||||
|
code.indentExclusionRanges.push([ this.start, this.end ]); |
||||
|
super.render( code, es ); |
||||
|
} |
||||
|
} |
@ -0,0 +1,20 @@ |
|||||
|
import Node from '../Node.js'; |
||||
|
|
||||
|
export default class ThisExpression extends Node { |
||||
|
initialise ( scope ) { |
||||
|
const lexicalBoundary = scope.findLexicalBoundary(); |
||||
|
|
||||
|
if ( lexicalBoundary.isModuleScope ) { |
||||
|
this.alias = this.module.bundle.context; |
||||
|
if ( this.alias === 'undefined' ) { |
||||
|
this.module.bundle.onwarn( 'The `this` keyword is equivalent to `undefined` at the top level of an ES module, and has been rewritten' ); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
render ( code ) { |
||||
|
if ( this.alias ) { |
||||
|
code.overwrite( this.start, this.end, this.alias, true ); |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,7 @@ |
|||||
|
import Node from '../Node.js'; |
||||
|
|
||||
|
export default class ThrowStatement extends Node { |
||||
|
hasEffects ( scope ) { |
||||
|
return scope.findLexicalBoundary().isModuleScope; // TODO should this just be `true`? probably...
|
||||
|
} |
||||
|
} |
@ -0,0 +1,34 @@ |
|||||
|
import Node from '../Node.js'; |
||||
|
import { UNKNOWN } from '../values.js'; |
||||
|
|
||||
|
const operators = { |
||||
|
"-": value => -value, |
||||
|
"+": value => +value, |
||||
|
"!": value => !value, |
||||
|
"~": value => ~value, |
||||
|
typeof: value => typeof value, |
||||
|
void: () => undefined, |
||||
|
delete: () => UNKNOWN |
||||
|
}; |
||||
|
|
||||
|
export default class UnaryExpression extends Node { |
||||
|
bind ( scope ) { |
||||
|
if ( this.value === UNKNOWN ) super.bind( scope ); |
||||
|
} |
||||
|
|
||||
|
getValue () { |
||||
|
const argumentValue = this.argument.getValue(); |
||||
|
if ( argumentValue === UNKNOWN ) return UNKNOWN; |
||||
|
|
||||
|
return operators[ this.operator ]( argumentValue ); |
||||
|
} |
||||
|
|
||||
|
hasEffects ( scope ) { |
||||
|
return this.operator === 'delete' || this.argument.hasEffects( scope ); |
||||
|
} |
||||
|
|
||||
|
initialise ( scope ) { |
||||
|
this.value = this.getValue(); |
||||
|
if ( this.value === UNKNOWN ) super.initialise( scope ); |
||||
|
} |
||||
|
} |
@ -0,0 +1,40 @@ |
|||||
|
import Node from '../Node.js'; |
||||
|
import disallowIllegalReassignment from './shared/disallowIllegalReassignment.js'; |
||||
|
import isUsedByBundle from './shared/isUsedByBundle.js'; |
||||
|
import { NUMBER } from '../values.js'; |
||||
|
|
||||
|
export default class UpdateExpression extends Node { |
||||
|
bind ( scope ) { |
||||
|
let subject = this.argument; |
||||
|
while ( this.argument.type === 'ParenthesizedExpression' ) subject = subject.expression; |
||||
|
|
||||
|
this.subject = subject; |
||||
|
disallowIllegalReassignment( scope, this.argument ); |
||||
|
|
||||
|
if ( subject.type === 'Identifier' ) { |
||||
|
const declaration = scope.findDeclaration( subject.name ); |
||||
|
declaration.isReassigned = true; |
||||
|
|
||||
|
if ( declaration.possibleValues ) { |
||||
|
declaration.possibleValues.add( NUMBER ); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
super.bind( scope ); |
||||
|
} |
||||
|
|
||||
|
hasEffects ( scope ) { |
||||
|
return isUsedByBundle( scope, this.subject ); |
||||
|
} |
||||
|
|
||||
|
initialise ( scope ) { |
||||
|
this.scope = scope; |
||||
|
|
||||
|
this.module.bundle.dependentExpressions.push( this ); |
||||
|
super.initialise( scope ); |
||||
|
} |
||||
|
|
||||
|
isUsedByBundle () { |
||||
|
return isUsedByBundle( this.scope, this.subject ); |
||||
|
} |
||||
|
} |
@ -0,0 +1,100 @@ |
|||||
|
import Node from '../Node.js'; |
||||
|
import extractNames from '../utils/extractNames.js'; |
||||
|
|
||||
|
function getSeparator ( code, start ) { |
||||
|
let c = start; |
||||
|
|
||||
|
while ( c > 0 && code[ c - 1 ] !== '\n' ) { |
||||
|
c -= 1; |
||||
|
if ( code[c] === ';' || code[c] === '{' ) return '; '; |
||||
|
} |
||||
|
|
||||
|
const lineStart = code.slice( c, start ).match( /^\s*/ )[0]; |
||||
|
|
||||
|
return `;\n${lineStart}`; |
||||
|
} |
||||
|
|
||||
|
const forStatement = /^For(?:Of|In)Statement/; |
||||
|
|
||||
|
export default class VariableDeclaration extends Node { |
||||
|
initialise ( scope ) { |
||||
|
this.scope = scope; |
||||
|
super.initialise( scope ); |
||||
|
} |
||||
|
|
||||
|
render ( code, es ) { |
||||
|
const treeshake = this.module.bundle.treeshake; |
||||
|
|
||||
|
let shouldSeparate = false; |
||||
|
let separator; |
||||
|
|
||||
|
if ( this.scope.isModuleScope && !forStatement.test( this.parent.type ) ) { |
||||
|
shouldSeparate = true; |
||||
|
separator = getSeparator( this.module.code, this.start ); |
||||
|
} |
||||
|
|
||||
|
let c = this.start; |
||||
|
let empty = true; |
||||
|
|
||||
|
for ( let i = 0; i < this.declarations.length; i += 1 ) { |
||||
|
const declarator = this.declarations[i]; |
||||
|
|
||||
|
const prefix = empty ? '' : separator; // TODO indentation
|
||||
|
|
||||
|
if ( declarator.id.type === 'Identifier' ) { |
||||
|
const proxy = declarator.proxies.get( declarator.id.name ); |
||||
|
const isExportedAndReassigned = !es && proxy.exportName && proxy.isReassigned; |
||||
|
|
||||
|
if ( isExportedAndReassigned ) { |
||||
|
if ( declarator.init ) { |
||||
|
if ( shouldSeparate ) code.overwrite( c, declarator.start, prefix ); |
||||
|
c = declarator.end; |
||||
|
empty = false; |
||||
|
} |
||||
|
} else if ( !treeshake || proxy.activated ) { |
||||
|
if ( shouldSeparate ) code.overwrite( c, declarator.start, `${prefix}${this.kind} ` ); // TODO indentation
|
||||
|
c = declarator.end; |
||||
|
empty = false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
else { |
||||
|
const exportAssignments = []; |
||||
|
let activated = false; |
||||
|
|
||||
|
extractNames( declarator.id ).forEach( name => { |
||||
|
const proxy = declarator.proxies.get( name ); |
||||
|
const isExportedAndReassigned = !es && proxy.exportName && proxy.isReassigned; |
||||
|
|
||||
|
if ( isExportedAndReassigned ) { |
||||
|
// code.overwrite( c, declarator.start, prefix );
|
||||
|
// c = declarator.end;
|
||||
|
// empty = false;
|
||||
|
exportAssignments.push( 'TODO' ); |
||||
|
} else if ( declarator.activated ) { |
||||
|
activated = true; |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
if ( !treeshake || activated ) { |
||||
|
if ( shouldSeparate ) code.overwrite( c, declarator.start, `${prefix}${this.kind} ` ); // TODO indentation
|
||||
|
c = declarator.end; |
||||
|
empty = false; |
||||
|
} |
||||
|
|
||||
|
if ( exportAssignments.length ) { |
||||
|
throw new Error( 'TODO' ); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
declarator.render( code, es ); |
||||
|
} |
||||
|
|
||||
|
if ( treeshake && empty ) { |
||||
|
code.remove( this.leadingCommentStart || this.start, this.next || this.end ); |
||||
|
} else if ( this.end > c ) { |
||||
|
const hasSemicolon = code.original[ this.end - 1 ] === ';'; |
||||
|
code.overwrite( c, this.end, hasSemicolon ? ';' : '' ); |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,91 @@ |
|||||
|
import Node from '../Node.js'; |
||||
|
import extractNames from '../utils/extractNames.js'; |
||||
|
import { UNKNOWN } from '../values.js'; |
||||
|
|
||||
|
class DeclaratorProxy { |
||||
|
constructor ( name, declarator, isTopLevel, init ) { |
||||
|
this.name = name; |
||||
|
this.declarator = declarator; |
||||
|
|
||||
|
this.activated = false; |
||||
|
this.isReassigned = false; |
||||
|
this.exportName = null; |
||||
|
|
||||
|
this.duplicates = []; |
||||
|
this.possibleValues = new Set( init ? [ init ] : null ); |
||||
|
} |
||||
|
|
||||
|
activate () { |
||||
|
this.activated = true; |
||||
|
this.declarator.activate(); |
||||
|
this.duplicates.forEach( dupe => dupe.activate() ); |
||||
|
} |
||||
|
|
||||
|
addReference () { |
||||
|
/* noop? */ |
||||
|
} |
||||
|
|
||||
|
gatherPossibleValues ( values ) { |
||||
|
this.possibleValues.forEach( value => values.add( value ) ); |
||||
|
} |
||||
|
|
||||
|
getName ( es ) { |
||||
|
// TODO desctructuring...
|
||||
|
if ( es ) return this.name; |
||||
|
if ( !this.isReassigned || !this.exportName ) return this.name; |
||||
|
|
||||
|
return `exports.${this.exportName}`; |
||||
|
} |
||||
|
|
||||
|
toString () { |
||||
|
return this.name; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export default class VariableDeclarator extends Node { |
||||
|
activate () { |
||||
|
if ( this.activated ) return; |
||||
|
this.activated = true; |
||||
|
|
||||
|
this.run( this.findScope() ); |
||||
|
} |
||||
|
|
||||
|
hasEffects ( scope ) { |
||||
|
return this.init && this.init.hasEffects( scope ); |
||||
|
} |
||||
|
|
||||
|
initialise ( scope ) { |
||||
|
this.proxies = new Map(); |
||||
|
|
||||
|
const lexicalBoundary = scope.findLexicalBoundary(); |
||||
|
|
||||
|
const init = this.init ? |
||||
|
( this.id.type === 'Identifier' ? this.init : UNKNOWN ) : // TODO maybe UNKNOWN is unnecessary
|
||||
|
null; |
||||
|
|
||||
|
extractNames( this.id ).forEach( name => { |
||||
|
const proxy = new DeclaratorProxy( name, this, lexicalBoundary.isModuleScope, init ); |
||||
|
|
||||
|
this.proxies.set( name, proxy ); |
||||
|
scope.addDeclaration( name, proxy, this.parent.kind === 'var' ); |
||||
|
}); |
||||
|
|
||||
|
super.initialise( scope ); |
||||
|
} |
||||
|
|
||||
|
render ( code, es ) { |
||||
|
extractNames( this.id ).forEach( name => { |
||||
|
const declaration = this.proxies.get( name ); |
||||
|
|
||||
|
if ( !es && declaration.exportName && declaration.isReassigned ) { |
||||
|
if ( this.init ) { |
||||
|
code.overwrite( this.start, this.id.end, declaration.getName( es ) ); |
||||
|
} else if ( this.module.bundle.treeshake ) { |
||||
|
code.remove( this.start, this.end ); |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
super.render( code, es ); |
||||
|
} |
||||
|
} |
@ -0,0 +1,78 @@ |
|||||
|
import ArrayExpression from './ArrayExpression.js'; |
||||
|
import ArrowFunctionExpression from './ArrowFunctionExpression.js'; |
||||
|
import AssignmentExpression from './AssignmentExpression.js'; |
||||
|
import BinaryExpression from './BinaryExpression.js'; |
||||
|
import BlockStatement from './BlockStatement.js'; |
||||
|
import CallExpression from './CallExpression.js'; |
||||
|
import ClassDeclaration from './ClassDeclaration.js'; |
||||
|
import ClassExpression from './ClassExpression.js'; |
||||
|
import ConditionalExpression from './ConditionalExpression.js'; |
||||
|
import EmptyStatement from './EmptyStatement.js'; |
||||
|
import ExportAllDeclaration from './ExportAllDeclaration.js'; |
||||
|
import ExportDefaultDeclaration from './ExportDefaultDeclaration.js'; |
||||
|
import ExportNamedDeclaration from './ExportNamedDeclaration.js'; |
||||
|
import ExpressionStatement from './ExpressionStatement.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 IfStatement from './IfStatement.js'; |
||||
|
import ImportDeclaration from './ImportDeclaration.js'; |
||||
|
import Literal from './Literal.js'; |
||||
|
import MemberExpression from './MemberExpression.js'; |
||||
|
import NewExpression from './NewExpression.js'; |
||||
|
import ObjectExpression from './ObjectExpression.js'; |
||||
|
import ParenthesizedExpression from './ParenthesizedExpression.js'; |
||||
|
import ReturnStatement from './ReturnStatement.js'; |
||||
|
import Statement from './shared/Statement.js'; |
||||
|
import TemplateLiteral from './TemplateLiteral.js'; |
||||
|
import ThisExpression from './ThisExpression.js'; |
||||
|
import ThrowStatement from './ThrowStatement.js'; |
||||
|
import UnaryExpression from './UnaryExpression.js'; |
||||
|
import UpdateExpression from './UpdateExpression.js'; |
||||
|
import VariableDeclarator from './VariableDeclarator.js'; |
||||
|
import VariableDeclaration from './VariableDeclaration.js'; |
||||
|
|
||||
|
export default { |
||||
|
ArrayExpression, |
||||
|
ArrowFunctionExpression, |
||||
|
AssignmentExpression, |
||||
|
BinaryExpression, |
||||
|
BlockStatement, |
||||
|
CallExpression, |
||||
|
ClassDeclaration, |
||||
|
ClassExpression, |
||||
|
ConditionalExpression, |
||||
|
DoWhileStatement: Statement, |
||||
|
EmptyStatement, |
||||
|
ExportAllDeclaration, |
||||
|
ExportDefaultDeclaration, |
||||
|
ExportNamedDeclaration, |
||||
|
ExpressionStatement, |
||||
|
ForStatement, |
||||
|
ForInStatement, |
||||
|
ForOfStatement, |
||||
|
FunctionDeclaration, |
||||
|
FunctionExpression, |
||||
|
Identifier, |
||||
|
IfStatement, |
||||
|
ImportDeclaration, |
||||
|
Literal, |
||||
|
MemberExpression, |
||||
|
NewExpression, |
||||
|
ObjectExpression, |
||||
|
ParenthesizedExpression, |
||||
|
ReturnStatement, |
||||
|
SwitchStatement: Statement, |
||||
|
TemplateLiteral, |
||||
|
ThisExpression, |
||||
|
ThrowStatement, |
||||
|
TryStatement: Statement, |
||||
|
UnaryExpression, |
||||
|
UpdateExpression, |
||||
|
VariableDeclarator, |
||||
|
VariableDeclaration, |
||||
|
WhileStatement: Statement |
||||
|
}; |
@ -0,0 +1,16 @@ |
|||||
|
import Node from '../../Node.js'; |
||||
|
|
||||
|
export default class Statement extends Node { |
||||
|
render ( code, es ) { |
||||
|
if ( !this.module.bundle.treeshake || this.shouldInclude ) { |
||||
|
super.render( code, es ); |
||||
|
} else { |
||||
|
code.remove( this.leadingCommentStart || this.start, this.next || this.end ); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
run ( scope ) { |
||||
|
this.shouldInclude = true; |
||||
|
super.run( scope ); |
||||
|
} |
||||
|
} |
@ -0,0 +1,29 @@ |
|||||
|
import extractNames from '../../utils/extractNames.js'; |
||||
|
|
||||
|
export default function assignToForLoopLeft ( node, scope, value ) { |
||||
|
if ( node.type === 'VariableDeclaration' ) { |
||||
|
for ( const proxy of node.declarations[0].proxies.values() ) { |
||||
|
proxy.possibleValues.add( value ); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
else { |
||||
|
while ( node.type === 'ParenthesizedExpression' ) node = node.expression; |
||||
|
|
||||
|
if ( node.type === 'MemberExpression' ) { |
||||
|
// apparently this is legal JavaScript? Though I don't know what
|
||||
|
// kind of monster would write `for ( foo.bar of thing ) {...}`
|
||||
|
|
||||
|
// for now, do nothing, as I'm not sure anything needs to happen...
|
||||
|
} |
||||
|
|
||||
|
else { |
||||
|
for ( const name of extractNames( node ) ) { |
||||
|
const declaration = scope.findDeclaration( name ); |
||||
|
if ( declaration.possibleValues ) { |
||||
|
declaration.possibleValues.add( value ); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,67 @@ |
|||||
|
import flatten from '../../utils/flatten.js'; |
||||
|
import isReference from '../../utils/isReference.js'; |
||||
|
import pureFunctions from './pureFunctions.js'; |
||||
|
import { UNKNOWN } from '../../values.js'; |
||||
|
|
||||
|
const currentlyCalling = new Set(); |
||||
|
|
||||
|
function fnHasEffects ( fn ) { |
||||
|
if ( currentlyCalling.has( fn ) ) return false; // prevent infinite loops... TODO there must be a better way
|
||||
|
currentlyCalling.add( fn ); |
||||
|
|
||||
|
// handle body-less arrow functions
|
||||
|
const scope = fn.body.scope || fn.scope; |
||||
|
const body = fn.body.body || [ fn.body ]; |
||||
|
|
||||
|
for ( const node of body ) { |
||||
|
if ( node.hasEffects( scope ) ) { |
||||
|
currentlyCalling.delete( fn ); |
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
currentlyCalling.delete( fn ); |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
export default function callHasEffects ( scope, callee ) { |
||||
|
const values = new Set([ callee ]); |
||||
|
|
||||
|
for ( const node of values ) { |
||||
|
if ( node === UNKNOWN ) return true; // err on side of caution
|
||||
|
|
||||
|
if ( /Function/.test( node.type ) ) { |
||||
|
if ( fnHasEffects( node ) ) return true; |
||||
|
} |
||||
|
|
||||
|
else if ( isReference( node ) ) { |
||||
|
const flattened = flatten( node ); |
||||
|
const declaration = scope.findDeclaration( flattened.name ); |
||||
|
|
||||
|
if ( declaration.isGlobal ) { |
||||
|
if ( !pureFunctions[ flattened.keypath ] ) return true; |
||||
|
} |
||||
|
|
||||
|
else if ( declaration.isExternal ) { |
||||
|
return true; // TODO make this configurable? e.g. `path.[whatever]`
|
||||
|
} |
||||
|
|
||||
|
else { |
||||
|
if ( node.declaration ) { |
||||
|
node.declaration.gatherPossibleValues( values ); |
||||
|
} else { |
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
else { |
||||
|
if ( !node.gatherPossibleValues ) { |
||||
|
throw new Error( 'TODO' ); |
||||
|
} |
||||
|
node.gatherPossibleValues( values ); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return false; |
||||
|
} |
@ -0,0 +1,28 @@ |
|||||
|
import getLocation from '../../../utils/getLocation.js'; |
||||
|
import error from '../../../utils/error.js'; |
||||
|
|
||||
|
// TODO tidy this up a bit (e.g. they can both use node.module.imports)
|
||||
|
export default function disallowIllegalReassignment ( scope, node ) { |
||||
|
if ( node.type === 'MemberExpression' && node.object.type === 'Identifier' ) { |
||||
|
const declaration = scope.findDeclaration( node.object.name ); |
||||
|
if ( declaration.isNamespace ) { |
||||
|
error({ |
||||
|
message: `Illegal reassignment to import '${node.object.name}'`, |
||||
|
file: node.module.id, |
||||
|
pos: node.start, |
||||
|
loc: getLocation( node.module.code, node.start ) |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
else if ( node.type === 'Identifier' ) { |
||||
|
if ( node.module.imports[ node.name ] && !scope.contains( node.name ) ) { |
||||
|
error({ |
||||
|
message: `Illegal reassignment to import '${node.name}'`, |
||||
|
file: node.module.id, |
||||
|
pos: node.start, |
||||
|
loc: getLocation( node.module.code, node.start ) |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,40 @@ |
|||||
|
import { UNKNOWN } from '../../values.js'; |
||||
|
|
||||
|
export default function isUsedByBundle ( scope, node ) { |
||||
|
while ( node.type === 'ParenthesizedExpression' ) node = node.expression; |
||||
|
|
||||
|
// const expression = node;
|
||||
|
while ( node.type === 'MemberExpression' ) node = node.object; |
||||
|
|
||||
|
const declaration = scope.findDeclaration( node.name ); |
||||
|
|
||||
|
if ( declaration.isParam ) { |
||||
|
return true; |
||||
|
|
||||
|
// TODO if we mutate a parameter, assume the worst
|
||||
|
// return node !== expression;
|
||||
|
} |
||||
|
|
||||
|
if ( declaration.activated ) return true; |
||||
|
|
||||
|
const values = new Set(); |
||||
|
declaration.gatherPossibleValues( values ); |
||||
|
for ( const value of values ) { |
||||
|
if ( value === UNKNOWN ) { |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
if ( value.type === 'Identifier' ) { |
||||
|
if ( value.declaration.activated ) { |
||||
|
return true; |
||||
|
} |
||||
|
value.declaration.gatherPossibleValues( values ); |
||||
|
} |
||||
|
|
||||
|
else if ( value.gatherPossibleValues ) { |
||||
|
value.gatherPossibleValues( values ); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return false; |
||||
|
} |
@ -0,0 +1,40 @@ |
|||||
|
import Scope from './Scope.js'; |
||||
|
import { UNKNOWN } from '../values'; |
||||
|
|
||||
|
class SyntheticGlobalDeclaration { |
||||
|
constructor ( name ) { |
||||
|
this.name = name; |
||||
|
this.isExternal = true; |
||||
|
this.isGlobal = true; |
||||
|
this.isReassigned = false; |
||||
|
|
||||
|
this.activated = true; |
||||
|
} |
||||
|
|
||||
|
activate () { |
||||
|
/* noop */ |
||||
|
} |
||||
|
|
||||
|
addReference ( reference ) { |
||||
|
reference.declaration = this; |
||||
|
if ( reference.isReassignment ) this.isReassigned = true; |
||||
|
} |
||||
|
|
||||
|
gatherPossibleValues ( values ) { |
||||
|
values.add( UNKNOWN ); |
||||
|
} |
||||
|
|
||||
|
getName () { |
||||
|
return this.name; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export default class BundleScope extends Scope { |
||||
|
findDeclaration ( name ) { |
||||
|
if ( !this.declarations[ name ] ) { |
||||
|
this.declarations[ name ] = new SyntheticGlobalDeclaration( name ); |
||||
|
} |
||||
|
|
||||
|
return this.declarations[ name ]; |
||||
|
} |
||||
|
} |
@ -0,0 +1,53 @@ |
|||||
|
import { forOwn } from '../../utils/object.js'; |
||||
|
import Scope from './Scope.js'; |
||||
|
|
||||
|
export default class ModuleScope extends Scope { |
||||
|
constructor ( module ) { |
||||
|
super({ |
||||
|
isBlockScope: false, |
||||
|
isLexicalBoundary: true, |
||||
|
isModuleScope: true, |
||||
|
parent: module.bundle.scope |
||||
|
}); |
||||
|
|
||||
|
this.module = module; |
||||
|
} |
||||
|
|
||||
|
deshadow ( names ) { |
||||
|
names = new Map( names ); |
||||
|
|
||||
|
forOwn( this.module.imports, specifier => { |
||||
|
if ( specifier.module.isExternal ) return; |
||||
|
|
||||
|
specifier.module.getExports().forEach( name => { |
||||
|
names.set(name); |
||||
|
}); |
||||
|
|
||||
|
if ( specifier.name !== '*' ) { |
||||
|
const declaration = specifier.module.traceExport( specifier.name ); |
||||
|
if ( !declaration ) { |
||||
|
this.module.bundle.onwarn( `Non-existent export '${specifier.name}' is imported from ${specifier.module.id} by ${this.module.id}` ); |
||||
|
return; |
||||
|
} |
||||
|
const name = declaration.getName( true ); |
||||
|
if ( name !== specifier.name ) { |
||||
|
names.set( declaration.getName( true ) ); |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
super.deshadow( names ); |
||||
|
} |
||||
|
|
||||
|
findDeclaration ( name ) { |
||||
|
if ( this.declarations[ name ] ) { |
||||
|
return this.declarations[ name ]; |
||||
|
} |
||||
|
|
||||
|
return this.module.trace( name ) || this.parent.findDeclaration( name ); |
||||
|
} |
||||
|
|
||||
|
findLexicalBoundary () { |
||||
|
return this; |
||||
|
} |
||||
|
} |
@ -0,0 +1,98 @@ |
|||||
|
import { blank, keys } from '../../utils/object.js'; |
||||
|
import { UNKNOWN } from '../values.js'; |
||||
|
|
||||
|
class Parameter { |
||||
|
constructor ( name ) { |
||||
|
this.name = name; |
||||
|
|
||||
|
this.isParam = true; |
||||
|
this.activated = true; |
||||
|
} |
||||
|
|
||||
|
activate () { |
||||
|
// noop
|
||||
|
} |
||||
|
|
||||
|
addReference () { |
||||
|
// noop?
|
||||
|
} |
||||
|
|
||||
|
gatherPossibleValues ( values ) { |
||||
|
values.add( UNKNOWN ); // TODO populate this at call time
|
||||
|
} |
||||
|
|
||||
|
getName () { |
||||
|
return this.name; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export default class Scope { |
||||
|
constructor ( options ) { |
||||
|
options = options || {}; |
||||
|
|
||||
|
this.parent = options.parent; |
||||
|
this.isBlockScope = !!options.isBlockScope; |
||||
|
this.isLexicalBoundary = !!options.isLexicalBoundary; |
||||
|
this.isModuleScope = !!options.isModuleScope; |
||||
|
|
||||
|
this.children = []; |
||||
|
if ( this.parent ) this.parent.children.push( this ); |
||||
|
|
||||
|
this.declarations = blank(); |
||||
|
|
||||
|
if ( this.isLexicalBoundary && !this.isModuleScope ) { |
||||
|
this.declarations.arguments = new Parameter( 'arguments' ); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
addDeclaration ( name, declaration, isVar, isParam ) { |
||||
|
if ( isVar && this.isBlockScope ) { |
||||
|
this.parent.addDeclaration( name, declaration, isVar, isParam ); |
||||
|
} else { |
||||
|
const existingDeclaration = this.declarations[ name ]; |
||||
|
|
||||
|
if ( existingDeclaration && existingDeclaration.duplicates ) { |
||||
|
// TODO warn/throw on duplicates?
|
||||
|
existingDeclaration.duplicates.push( declaration ); |
||||
|
} else { |
||||
|
this.declarations[ name ] = isParam ? new Parameter( name ) : declaration; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
contains ( name ) { |
||||
|
return !!this.declarations[ name ] || |
||||
|
( this.parent ? this.parent.contains( name ) : false ); |
||||
|
} |
||||
|
|
||||
|
deshadow ( names ) { |
||||
|
keys( this.declarations ).forEach( key => { |
||||
|
const declaration = this.declarations[ key ]; |
||||
|
|
||||
|
// we can disregard exports.foo etc
|
||||
|
if ( declaration.exportName && declaration.isReassigned ) return; |
||||
|
|
||||
|
const name = declaration.getName( true ); |
||||
|
let deshadowed = name; |
||||
|
|
||||
|
let i = 1; |
||||
|
|
||||
|
while ( names.has( deshadowed ) ) { |
||||
|
deshadowed = `${name}$$${i++}`; |
||||
|
} |
||||
|
|
||||
|
declaration.name = deshadowed; |
||||
|
}); |
||||
|
|
||||
|
this.children.forEach( scope => scope.deshadow( names ) ); |
||||
|
} |
||||
|
|
||||
|
findDeclaration ( name ) { |
||||
|
return this.declarations[ name ] || |
||||
|
( this.parent && this.parent.findDeclaration( name ) ); |
||||
|
} |
||||
|
|
||||
|
findLexicalBoundary () { |
||||
|
return this.isLexicalBoundary ? this : this.parent.findLexicalBoundary(); |
||||
|
} |
||||
|
} |
@ -0,0 +1,8 @@ |
|||||
|
// properties are for debugging purposes only
|
||||
|
export const ARRAY = { ARRAY: true, toString: () => '[[ARRAY]]' }; |
||||
|
export const BOOLEAN = { BOOLEAN: true, toString: () => '[[BOOLEAN]]' }; |
||||
|
export const FUNCTION = { FUNCTION: true, toString: () => '[[FUNCTION]]' }; |
||||
|
export const NUMBER = { NUMBER: true, toString: () => '[[NUMBER]]' }; |
||||
|
export const OBJECT = { OBJECT: true, toString: () => '[[OBJECT]]' }; |
||||
|
export const STRING = { STRING: true, toString: () => '[[STRING]]' }; |
||||
|
export const UNKNOWN = { UNKNOWN: true, toString: () => '[[UNKNOWN]]' }; |
@ -0,0 +1,55 @@ |
|||||
|
const DEBUG = false; |
||||
|
const map = new Map; |
||||
|
|
||||
|
let timeStartHelper; |
||||
|
let timeEndHelper; |
||||
|
|
||||
|
if ( typeof process === 'undefined' ) { |
||||
|
timeStartHelper = function timeStartHelper () { |
||||
|
return window.performance.now(); |
||||
|
}; |
||||
|
|
||||
|
timeEndHelper = function timeEndHelper ( previous ) { |
||||
|
return window.performance.now() - previous; |
||||
|
}; |
||||
|
} else { |
||||
|
timeStartHelper = function timeStartHelper () { |
||||
|
return process.hrtime(); |
||||
|
}; |
||||
|
|
||||
|
timeEndHelper = function timeEndHelper ( previous ) { |
||||
|
const hrtime = process.hrtime( previous ); |
||||
|
return hrtime[0] * 1e3 + Math.floor( hrtime[1] / 1e6 ); |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
export function timeStart ( label ) { |
||||
|
if ( !map.has( label ) ) { |
||||
|
map.set( label, { |
||||
|
time: 0 |
||||
|
}); |
||||
|
} |
||||
|
map.get( label ).start = timeStartHelper(); |
||||
|
} |
||||
|
|
||||
|
export function timeEnd ( label ) { |
||||
|
if ( map.has( label ) ) { |
||||
|
const item = map.get( label ); |
||||
|
item.time += timeEndHelper( item.start ); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export function flushTime ( log = defaultLog ) { |
||||
|
for ( const item of map.entries() ) { |
||||
|
log( item[0], item[1].time ); |
||||
|
} |
||||
|
map.clear(); |
||||
|
} |
||||
|
|
||||
|
function defaultLog ( label, time ) { |
||||
|
if ( DEBUG ) { |
||||
|
/* eslint-disable no-console */ |
||||
|
console.info( '%dms: %s', time, label ); |
||||
|
/* eslint-enable no-console */ |
||||
|
} |
||||
|
} |
@ -1,119 +0,0 @@ |
|||||
import { walk } from 'estree-walker'; |
|
||||
import modifierNodes, { isModifierNode } from '../ast/modifierNodes.js'; |
|
||||
import isReference from '../ast/isReference.js'; |
|
||||
import flatten from '../ast/flatten'; |
|
||||
import pureFunctions from './pureFunctions.js'; |
|
||||
import getLocation from './getLocation.js'; |
|
||||
import error from './error.js'; |
|
||||
|
|
||||
function call ( callee, scope, statement, strongDependencies ) { |
|
||||
while ( callee.type === 'ParenthesizedExpression' ) callee = callee.expression; |
|
||||
|
|
||||
if ( callee.type === 'Identifier' ) { |
|
||||
const declaration = scope.findDeclaration( callee.name ) || |
|
||||
statement.module.trace( callee.name ); |
|
||||
|
|
||||
if ( declaration ) { |
|
||||
if ( declaration.isNamespace ) { |
|
||||
error({ |
|
||||
message: `Cannot call a namespace ('${callee.name}')`, |
|
||||
file: statement.module.id, |
|
||||
pos: callee.start, |
|
||||
loc: getLocation( statement.module.code, callee.start ) |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
return declaration.run( strongDependencies ); |
|
||||
} |
|
||||
|
|
||||
return !pureFunctions[ callee.name ]; |
|
||||
} |
|
||||
|
|
||||
if ( /FunctionExpression/.test( callee.type ) ) { |
|
||||
return run( callee.body, scope, statement, strongDependencies ); |
|
||||
} |
|
||||
|
|
||||
if ( callee.type === 'MemberExpression' ) { |
|
||||
const flattened = flatten( callee ); |
|
||||
|
|
||||
if ( flattened ) { |
|
||||
// if we're calling e.g. Object.keys(thing), there are no side-effects
|
|
||||
// TODO make pureFunctions configurable
|
|
||||
const declaration = scope.findDeclaration( flattened.name ) || statement.module.trace( flattened.name ); |
|
||||
|
|
||||
return ( !!declaration || !pureFunctions[ flattened.keypath ] ); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// complex case like `( a ? b : c )()` or foo[bar].baz()`
|
|
||||
// – err on the side of caution
|
|
||||
return true; |
|
||||
} |
|
||||
|
|
||||
export default function run ( node, scope, statement, strongDependencies, force ) { |
|
||||
let hasSideEffect = false; |
|
||||
|
|
||||
walk( node, { |
|
||||
enter ( node, parent ) { |
|
||||
if ( !force && /Function/.test( node.type ) ) return this.skip(); |
|
||||
|
|
||||
if ( node._scope ) scope = node._scope; |
|
||||
|
|
||||
if ( isReference( node, parent ) ) { |
|
||||
const flattened = flatten( node ); |
|
||||
|
|
||||
if ( flattened.name === 'arguments' ) { |
|
||||
hasSideEffect = true; |
|
||||
} |
|
||||
|
|
||||
else if ( !scope.contains( flattened.name ) ) { |
|
||||
const declaration = statement.module.trace( flattened.name ); |
|
||||
if ( declaration && !declaration.isExternal ) { |
|
||||
const module = declaration.module || declaration.statement.module; // TODO is this right?
|
|
||||
if ( !module.isExternal && !~strongDependencies.indexOf( module ) ) strongDependencies.push( module ); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
else if ( node.type === 'DebuggerStatement' ) { |
|
||||
hasSideEffect = true; |
|
||||
} |
|
||||
|
|
||||
else if ( node.type === 'ThrowStatement' ) { |
|
||||
// we only care about errors thrown at the top level, otherwise
|
|
||||
// any function with error checking gets included if called
|
|
||||
if ( scope.isTopLevel ) hasSideEffect = true; |
|
||||
} |
|
||||
|
|
||||
else if ( node.type === 'CallExpression' || node.type === 'NewExpression' ) { |
|
||||
if ( call( node.callee, scope, statement, strongDependencies ) ) { |
|
||||
hasSideEffect = true; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
else if ( isModifierNode( node ) ) { |
|
||||
let subject = node[ modifierNodes[ node.type ] ]; |
|
||||
while ( subject.type === 'MemberExpression' ) subject = subject.object; |
|
||||
|
|
||||
let declaration = scope.findDeclaration( subject.name ); |
|
||||
|
|
||||
if ( declaration ) { |
|
||||
if ( declaration.isParam ) hasSideEffect = true; |
|
||||
} else if ( !scope.isTopLevel ) { |
|
||||
hasSideEffect = true; |
|
||||
} else { |
|
||||
declaration = statement.module.trace( subject.name ); |
|
||||
|
|
||||
if ( !declaration || declaration.isExternal || declaration.isUsed || ( declaration.original && declaration.original.isUsed ) ) { |
|
||||
hasSideEffect = true; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
}, |
|
||||
leave ( node ) { |
|
||||
if ( node._scope ) scope = scope.parent; |
|
||||
} |
|
||||
}); |
|
||||
|
|
||||
return hasSideEffect; |
|
||||
} |
|
@ -0,0 +1,3 @@ |
|||||
|
module.exports = { |
||||
|
description: 'supports body-less for loops' |
||||
|
}; |
@ -0,0 +1,16 @@ |
|||||
|
define(function () { 'use strict'; |
||||
|
|
||||
|
for ( let i = 0; i < 10; i += 1 ) console.log( i ); |
||||
|
for ( const letter of array ) console.log( letter ); |
||||
|
for ( const index in array ) console.log( index ); |
||||
|
|
||||
|
let i; |
||||
|
for ( i = 0; i < 10; i += 1 ) console.log( i ); |
||||
|
|
||||
|
let letter; |
||||
|
for ( letter of array ) console.log( letter ); |
||||
|
|
||||
|
let index; |
||||
|
for ( index in array ) console.log( index ); |
||||
|
|
||||
|
}); |
@ -0,0 +1,14 @@ |
|||||
|
'use strict'; |
||||
|
|
||||
|
for ( let i = 0; i < 10; i += 1 ) console.log( i ); |
||||
|
for ( const letter of array ) console.log( letter ); |
||||
|
for ( const index in array ) console.log( index ); |
||||
|
|
||||
|
let i; |
||||
|
for ( i = 0; i < 10; i += 1 ) console.log( i ); |
||||
|
|
||||
|
let letter; |
||||
|
for ( letter of array ) console.log( letter ); |
||||
|
|
||||
|
let index; |
||||
|
for ( index in array ) console.log( index ); |
@ -0,0 +1,12 @@ |
|||||
|
for ( let i = 0; i < 10; i += 1 ) console.log( i ); |
||||
|
for ( const letter of array ) console.log( letter ); |
||||
|
for ( const index in array ) console.log( index ); |
||||
|
|
||||
|
let i; |
||||
|
for ( i = 0; i < 10; i += 1 ) console.log( i ); |
||||
|
|
||||
|
let letter; |
||||
|
for ( letter of array ) console.log( letter ); |
||||
|
|
||||
|
let index; |
||||
|
for ( index in array ) console.log( index ); |
@ -0,0 +1,17 @@ |
|||||
|
(function () { |
||||
|
'use strict'; |
||||
|
|
||||
|
for ( let i = 0; i < 10; i += 1 ) console.log( i ); |
||||
|
for ( const letter of array ) console.log( letter ); |
||||
|
for ( const index in array ) console.log( index ); |
||||
|
|
||||
|
let i; |
||||
|
for ( i = 0; i < 10; i += 1 ) console.log( i ); |
||||
|
|
||||
|
let letter; |
||||
|
for ( letter of array ) console.log( letter ); |
||||
|
|
||||
|
let index; |
||||
|
for ( index in array ) console.log( index ); |
||||
|
|
||||
|
}()); |
@ -0,0 +1,20 @@ |
|||||
|
(function (global, factory) { |
||||
|
typeof exports === 'object' && typeof module !== 'undefined' ? factory() : |
||||
|
typeof define === 'function' && define.amd ? define(factory) : |
||||
|
(factory()); |
||||
|
}(this, (function () { 'use strict'; |
||||
|
|
||||
|
for ( let i = 0; i < 10; i += 1 ) console.log( i ); |
||||
|
for ( const letter of array ) console.log( letter ); |
||||
|
for ( const index in array ) console.log( index ); |
||||
|
|
||||
|
let i; |
||||
|
for ( i = 0; i < 10; i += 1 ) console.log( i ); |
||||
|
|
||||
|
let letter; |
||||
|
for ( letter of array ) console.log( letter ); |
||||
|
|
||||
|
let index; |
||||
|
for ( index in array ) console.log( index ); |
||||
|
|
||||
|
}))); |
@ -0,0 +1,12 @@ |
|||||
|
for ( let i = 0; i < 10; i += 1 ) console.log( i ); |
||||
|
for ( const letter of array ) console.log( letter ); |
||||
|
for ( const index in array ) console.log( index ); |
||||
|
|
||||
|
let i; |
||||
|
for ( i = 0; i < 10; i += 1 ) console.log( i ); |
||||
|
|
||||
|
let letter; |
||||
|
for ( letter of array ) console.log( letter ); |
||||
|
|
||||
|
let index; |
||||
|
for ( index in array ) console.log( index ); |
@ -0,0 +1,3 @@ |
|||||
|
module.exports = { |
||||
|
description: 'does not remove duplicated var declarations (#716)' |
||||
|
}; |
@ -0,0 +1,17 @@ |
|||||
|
define(function () { 'use strict'; |
||||
|
|
||||
|
var a = 1; |
||||
|
var b = 2; |
||||
|
|
||||
|
assert.equal( a, 1 ); |
||||
|
assert.equal( b, 2 ); |
||||
|
|
||||
|
var a = 3; |
||||
|
var b = 4; |
||||
|
var c = 5; |
||||
|
|
||||
|
assert.equal( a, 3 ); |
||||
|
assert.equal( b, 4 ); |
||||
|
assert.equal( c, 5 ); |
||||
|
|
||||
|
}); |
@ -0,0 +1,15 @@ |
|||||
|
'use strict'; |
||||
|
|
||||
|
var a = 1; |
||||
|
var b = 2; |
||||
|
|
||||
|
assert.equal( a, 1 ); |
||||
|
assert.equal( b, 2 ); |
||||
|
|
||||
|
var a = 3; |
||||
|
var b = 4; |
||||
|
var c = 5; |
||||
|
|
||||
|
assert.equal( a, 3 ); |
||||
|
assert.equal( b, 4 ); |
||||
|
assert.equal( c, 5 ); |
@ -0,0 +1,13 @@ |
|||||
|
var a = 1; |
||||
|
var b = 2; |
||||
|
|
||||
|
assert.equal( a, 1 ); |
||||
|
assert.equal( b, 2 ); |
||||
|
|
||||
|
var a = 3; |
||||
|
var b = 4; |
||||
|
var c = 5; |
||||
|
|
||||
|
assert.equal( a, 3 ); |
||||
|
assert.equal( b, 4 ); |
||||
|
assert.equal( c, 5 ); |
@ -0,0 +1,18 @@ |
|||||
|
(function () { |
||||
|
'use strict'; |
||||
|
|
||||
|
var a = 1; |
||||
|
var b = 2; |
||||
|
|
||||
|
assert.equal( a, 1 ); |
||||
|
assert.equal( b, 2 ); |
||||
|
|
||||
|
var a = 3; |
||||
|
var b = 4; |
||||
|
var c = 5; |
||||
|
|
||||
|
assert.equal( a, 3 ); |
||||
|
assert.equal( b, 4 ); |
||||
|
assert.equal( c, 5 ); |
||||
|
|
||||
|
}()); |
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue