Browse Source

another broken snapshot

value-tracking
Rich Harris 8 years ago
parent
commit
393d49e5eb
  1. 2
      package.json
  2. 18
      src/Declaration.js
  3. 4
      src/ast/Node.js
  4. 6
      src/ast/nodes/BinaryExpression.js
  5. 11
      src/ast/nodes/CallExpression.js
  6. 10
      src/ast/nodes/ConditionalExpression.js
  7. 4
      src/ast/nodes/ForOfStatement.js
  8. 19
      src/ast/nodes/FunctionDeclaration.js
  9. 31
      src/ast/nodes/FunctionExpression.js
  10. 11
      src/ast/nodes/Identifier.js
  11. 158
      src/ast/nodes/IfStatement.js
  12. 6
      src/ast/nodes/LogicalExpression.js
  13. 18
      src/ast/nodes/MemberExpression.js
  14. 10
      src/ast/nodes/ObjectExpression.js
  15. 5
      src/ast/nodes/ReturnStatement.js
  16. 10
      src/ast/nodes/UnaryExpression.js
  17. 5
      src/ast/nodes/VariableDeclarator.js
  18. 4
      src/ast/nodes/shared/callHasEffects.js
  19. 4
      src/ast/nodes/shared/isUsedByBundle.js
  20. 12
      src/ast/scopes/BundleScope.js
  21. 3
      src/ast/scopes/ModuleScope.js
  22. 4
      src/ast/scopes/Scope.js
  23. 19
      src/ast/values.js
  24. 2
      test/form/_tk/_config.js
  25. 1
      test/form/empty-if-statement/_config.js
  26. 4
      test/form/relative-external-with-global/_expected/amd.js
  27. 2
      test/form/relative-external-with-global/_expected/cjs.js
  28. 2
      test/form/relative-external-with-global/_expected/es.js
  29. 2
      test/form/relative-external-with-global/_expected/iife.js
  30. 2
      test/form/relative-external-with-global/_expected/umd.js
  31. 2
      test/form/relative-external-with-global/main.js

2
package.json

@ -59,7 +59,7 @@
"mocha": "^3.0.0",
"remap-istanbul": "^0.6.4",
"require-relative": "^0.8.7",
"rollup": "^0.39.0",
"rollup": "^0.41.0",
"rollup-plugin-buble": "^0.13.0",
"rollup-plugin-commonjs": "^7.0.0",
"rollup-plugin-json": "^2.0.0",

18
src/Declaration.js

@ -1,6 +1,6 @@
import { blank, forOwn, keys } from './utils/object.js';
import makeLegalIdentifier, { reservedWords } from './utils/makeLegalIdentifier.js';
import { UNKNOWN } from './ast/values.js';
import { unknown } from './ast/values.js';
export default class Declaration {
constructor ( node, isParam ) {
@ -31,6 +31,10 @@ export default class Declaration {
if ( reference.isReassignment ) this.isReassigned = true;
}
getInstance () {
return unknown;
}
render ( es ) {
if ( es ) return this.name;
if ( !this.isReassigned || !this.exportName ) return this.name;
@ -68,7 +72,7 @@ export class SyntheticNamespaceDeclaration {
}
gatherPossibleValues ( values ) {
values.add( UNKNOWN );
values.add( unknown );
}
getName () {
@ -116,8 +120,12 @@ export class ExternalDeclaration {
}
}
call ( context, args ) {
console.log( `args`, args )
}
gatherPossibleValues ( values ) {
values.add( UNKNOWN );
values.add( unknown );
}
getName ( es ) {
@ -134,6 +142,10 @@ export class ExternalDeclaration {
return es ? this.safeName : `${this.module.name}.${this.name}`;
}
markReturnStatements () {
// noop
}
setSafeName ( name ) {
this.safeName = name;
}

4
src/ast/Node.js

@ -1,5 +1,5 @@
import { locate } from 'locate-character';
import { UNKNOWN } from './values.js';
import { unknown } from './values.js';
export default class Node {
bind ( scope ) {
@ -35,7 +35,7 @@ export default class Node {
gatherPossibleValues ( values ) {
//this.eachChild( child => child.gatherPossibleValues( values ) );
values.add( UNKNOWN );
values.add( unknown );
}
getValue () {

6
src/ast/nodes/BinaryExpression.js

@ -1,5 +1,5 @@
import Node from '../Node.js';
import { UNKNOWN } from '../values.js';
import { unknown } from '../values.js';
const operators = {
'==': ( left, right ) => left == right,
@ -28,10 +28,10 @@ const operators = {
export default class BinaryExpression extends Node {
getValue () {
const leftValue = this.left.getValue();
if ( leftValue === UNKNOWN ) return UNKNOWN;
if ( leftValue === unknown ) return unknown;
const rightValue = this.right.getValue();
if ( rightValue === UNKNOWN ) return UNKNOWN;
if ( rightValue === unknown ) return unknown;
return operators[ this.operator ]( leftValue, rightValue );
}

11
src/ast/nodes/CallExpression.js

@ -25,6 +25,17 @@ export default class CallExpression extends Node {
super.bind( scope );
}
getProperty ( name ) {
// TODO unknown properties
return this.getValue().getProperty( name );
}
getValue () {
console.log( `TODO getValue ${this}` )
return this.callee.getReturnValue( this.arguments );
}
hasEffects ( scope ) {
return callHasEffects( scope, this.callee, false );
}

10
src/ast/nodes/ConditionalExpression.js

@ -1,12 +1,12 @@
import Node from '../Node.js';
import { UNKNOWN } from '../values.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 ) {
if ( this.testValue === unknown ) {
super.initialise( scope );
}
@ -27,7 +27,7 @@ export default class ConditionalExpression extends Node {
gatherPossibleValues ( values ) {
const testValue = this.test.getValue();
if ( testValue === UNKNOWN ) {
if ( testValue === unknown ) {
values.add( this.consequent ).add( this.alternate );
} else {
values.add( testValue ? this.consequent : this.alternate );
@ -36,7 +36,7 @@ export default class ConditionalExpression extends Node {
getValue () {
const testValue = this.test.getValue();
if ( testValue === UNKNOWN ) return UNKNOWN;
if ( testValue === unknown ) return unknown;
return testValue ? this.consequent.getValue() : this.alternate.getValue();
}
@ -47,7 +47,7 @@ export default class ConditionalExpression extends Node {
}
else {
if ( this.testValue === UNKNOWN ) {
if ( this.testValue === unknown ) {
super.render( code, es );
}

4
src/ast/nodes/ForOfStatement.js

@ -1,7 +1,7 @@
import Statement from './shared/Statement.js';
import assignTo from './shared/assignTo.js';
import Scope from '../scopes/Scope.js';
import { UNKNOWN } from '../values.js';
import { unknown } from '../values.js';
export default class ForOfStatement extends Statement {
initialise ( scope ) {
@ -17,6 +17,6 @@ export default class ForOfStatement extends Statement {
}
super.initialise( this.scope );
assignTo( this.left, this.scope, UNKNOWN );
assignTo( this.left, this.scope, unknown );
}
}

19
src/ast/nodes/FunctionDeclaration.js

@ -5,9 +5,7 @@ export default class FunctionDeclaration extends Node {
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();
this.body.mark();
}
addReference () {
@ -29,6 +27,8 @@ export default class FunctionDeclaration extends Node {
args.forEach( ( arg, i ) => {
const param = this.params[i];
if ( !param ) return;
if ( param.type !== 'Identifier' ) {
throw new Error( 'TODO desctructuring' );
}
@ -51,6 +51,19 @@ export default class FunctionDeclaration extends Node {
return this.name;
}
getReturnValue ( context, args ) {
if ( this.returnStatements.length === 0 ) {
console.log( `null!!!` )
return null; // TODO need a sentinel value for things like null
}
if ( this.returnStatements[0].parent === this.body ) {
throw new Error( 'TODO' );
}
console.log( `conditional return statements` )
}
hasEffects () {
return false;
}

31
src/ast/nodes/FunctionExpression.js

@ -20,6 +20,31 @@ export default class FunctionExpression extends Node {
this.body.bind();
}
call ( context, args ) {
if ( this.isCalling ) return; // recursive functions
this.isCalling = true;
this.body.scope.initialise();
args.forEach( ( arg, i ) => {
const param = this.params[i];
if ( !param ) return;
if ( param.type !== 'Identifier' ) {
throw new Error( 'TODO desctructuring' );
}
this.body.scope.setValue( param.name, arg );
});
for ( const node of this.body.body ) {
node.run();
}
this.isCalling = false;
}
getName () {
return this.name;
}
@ -37,6 +62,8 @@ export default class FunctionExpression extends Node {
this.body.scope.addDeclaration( this.id.name, this, false, false );
}
this.returnStatements = [];
this.params.forEach( param => param.initialise( this.body.scope ) );
this.body.initialise();
}
@ -45,4 +72,8 @@ export default class FunctionExpression extends Node {
this.body.mark();
super.mark();
}
markReturnStatements () {
this.returnStatements.forEach( statement => statement.mark() );
}
}

11
src/ast/nodes/Identifier.js

@ -35,6 +35,7 @@ export default class Identifier extends Node {
if ( !callee.call ) {
throw new Error( `${callee} does not have call method (${this})` );
}
callee.call( undefined, args );
}
@ -45,7 +46,15 @@ export default class Identifier extends Node {
}
getInstance () {
return this.scope.getValue( this.name ).getInstance();
return this.getValue().getInstance();
}
getReturnValue ( args ) {
return this.declaration.getReturnValue( undefined, args );
}
getValue () {
return this.scope.getValue( this.name );
}
initialise ( scope ) {

158
src/ast/nodes/IfStatement.js

@ -1,6 +1,6 @@
import Statement from './shared/Statement.js';
import extractNames from '../utils/extractNames.js';
import { UNKNOWN } from '../values.js';
import { unknown } from '../values.js';
// Statement types which may contain if-statements as direct children.
const statementsWithIfStatements = new Set([
@ -39,82 +39,82 @@ function handleVarDeclarations ( node, scope ) {
// TODO DRY this out
export default class IfStatement extends Statement {
initialise ( scope ) {
this.scope = 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 );
if ( this.alternate ) this.hoistedVars = handleVarDeclarations( this.alternate, scope );
this.alternate = null;
}
else {
if ( this.alternate ) this.alternate.initialise( scope );
this.hoistedVars = handleVarDeclarations( this.consequent, 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.hoistedVars ) {
const names = this.hoistedVars
.map( name => {
const declaration = this.scope.findDeclaration( name );
return declaration.activated ? declaration.getName() : null;
})
.filter( Boolean );
if ( names.length > 0 ) {
code.insertLeft( this.start, `var ${names.join( ', ' )};\n\n` );
}
}
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 if ( statementsWithIfStatements.has( this.parent.type ) ) {
code.insertRight( this.start, '{}' );
}
}
}
}
else {
super.render( code, es );
}
}
// initialise ( scope ) {
// this.scope = 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 );
//
// if ( this.alternate ) this.hoistedVars = handleVarDeclarations( this.alternate, scope );
// this.alternate = null;
// }
//
// else {
// if ( this.alternate ) this.alternate.initialise( scope );
//
// this.hoistedVars = handleVarDeclarations( this.consequent, 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.hoistedVars ) {
// const names = this.hoistedVars
// .map( name => {
// const declaration = this.scope.findDeclaration( name );
// return declaration.activated ? declaration.getName() : null;
// })
// .filter( Boolean );
//
// if ( names.length > 0 ) {
// code.insertLeft( this.start, `var ${names.join( ', ' )};\n\n` );
// }
// }
//
// 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 if ( statementsWithIfStatements.has( this.parent.type ) ) {
// code.insertRight( this.start, '{}' );
// }
// }
// }
// }
//
// else {
// super.render( code, es );
// }
// }
}

6
src/ast/nodes/LogicalExpression.js

@ -1,5 +1,5 @@
import Node from '../Node.js';
import { UNKNOWN } from '../values.js';
import { unknown } from '../values.js';
const operators = {
'&&': ( left, right ) => left && right,
@ -9,10 +9,10 @@ const operators = {
export default class LogicalExpression extends Node {
getValue () {
const leftValue = this.left.getValue();
if ( leftValue === UNKNOWN ) return UNKNOWN;
if ( leftValue === unknown ) return unknown;
const rightValue = this.right.getValue();
if ( rightValue === UNKNOWN ) return UNKNOWN;
if ( rightValue === unknown ) return unknown;
return operators[ this.operator ]( leftValue, rightValue );
}

18
src/ast/nodes/MemberExpression.js

@ -1,6 +1,6 @@
import relativeId from '../../utils/relativeId.js';
import Node from '../Node.js';
import { UNKNOWN } from '../values.js';
import { unknown } from '../values.js';
const validProp = /^[a-zA-Z_$][a-zA-Z_$0-9]*$/;
@ -73,11 +73,22 @@ export default class MemberExpression extends Node {
}
call ( args ) {
// TODO
this.getValue().call( this.object, args );
}
gatherPossibleValues ( values ) {
values.add( UNKNOWN ); // TODO
values.add( unknown ); // TODO
}
getValue () {
if ( this.declaration ) {
return this.declaration;
}
const objectValue = this.object.getValue();
const propValue = this.computed ? this.property.getValue() : this.property.name;
const value = objectValue.getProperty( propValue ).getValue();
return value;
}
mark () {
@ -87,6 +98,7 @@ export default class MemberExpression extends Node {
markReturnStatements () {
// TODO
this.getValue().markReturnStatements();
}
render ( code, es ) {

10
src/ast/nodes/ObjectExpression.js

@ -5,4 +5,14 @@ export default class ObjectExpression extends Node {
gatherPossibleValues ( values ) {
values.add( OBJECT );
}
getProperty ( name ) {
// TODO handle unknowns
for ( const prop of this.properties ) {
// TODO handle computed properties
if ( prop.key.name === name && !prop.computed ) {
return prop.value;
}
}
}
}

5
src/ast/nodes/ReturnStatement.js

@ -1,8 +1,11 @@
import Statement from './shared/Statement.js';
export default class ReturnStatement extends Statement {
initialise ( scope ) {
bind () {
this.findParent( /Function/ ).returnStatements.push( this );
}
initialise ( scope ) {
super.initialise( scope );
}
}

10
src/ast/nodes/UnaryExpression.js

@ -1,5 +1,5 @@
import Node from '../Node.js';
import { UNKNOWN } from '../values.js';
import { unknown } from '../values.js';
const operators = {
"-": value => -value,
@ -8,17 +8,17 @@ const operators = {
"~": value => ~value,
typeof: value => typeof value,
void: () => undefined,
delete: () => UNKNOWN
delete: () => unknown
};
export default class UnaryExpression extends Node {
bind ( scope ) {
if ( this.value === UNKNOWN ) super.bind( scope );
if ( this.value === unknown ) super.bind( scope );
}
getValue () {
const argumentValue = this.argument.getValue();
if ( argumentValue === UNKNOWN ) return UNKNOWN;
if ( argumentValue === unknown ) return unknown;
return operators[ this.operator ]( argumentValue );
}
@ -29,6 +29,6 @@ export default class UnaryExpression extends Node {
initialise ( scope ) {
this.value = this.getValue();
if ( this.value === UNKNOWN ) super.initialise( scope );
if ( this.value === unknown ) super.initialise( scope );
}
}

5
src/ast/nodes/VariableDeclarator.js

@ -1,6 +1,6 @@
import Node from '../Node.js';
import extractNames from '../utils/extractNames.js';
import { UNKNOWN } from '../values.js';
import { unknown } from '../values.js';
class DeclaratorProxy {
constructor ( name, declarator, isTopLevel, init ) {
@ -63,7 +63,7 @@ export default class VariableDeclarator extends Node {
const lexicalBoundary = scope.findLexicalBoundary();
const init = this.init ?
( this.id.type === 'Identifier' ? this.init : UNKNOWN ) : // TODO maybe UNKNOWN is unnecessary
( this.id.type === 'Identifier' ? this.init : unknown ) : // TODO maybe unknown is unnecessary
null;
extractNames( this.id ).forEach( name => {
@ -98,6 +98,7 @@ export default class VariableDeclarator extends Node {
}
if ( this.init ) {
this.init.run();
this.scope.setValue( this.id.name, this.init.getValue() );
} else if ( this.parent.kind !== 'var' ) {
this.scope.setValue( this.id.name, undefined ); // no longer TDZ violation

4
src/ast/nodes/shared/callHasEffects.js

@ -1,7 +1,7 @@
import isReference from 'is-reference';
import flatten from '../../utils/flatten.js';
import pureFunctions from './pureFunctions.js';
import { UNKNOWN } from '../../values.js';
import { unknown } from '../../values.js';
const currentlyCalling = new Set();
@ -64,7 +64,7 @@ export default function callHasEffects ( scope, callee, isNew ) {
const values = new Set([ callee ]);
for ( const node of values ) {
if ( node === UNKNOWN ) return true; // err on side of caution
if ( node === unknown ) return true; // err on side of caution
if ( /Function/.test( node.type ) ) {
if ( fnHasEffects( node, isNew && isES5Function( node ) ) ) return true;

4
src/ast/nodes/shared/isUsedByBundle.js

@ -1,4 +1,4 @@
import { UNKNOWN } from '../../values.js';
import { unknown } from '../../values.js';
export default function isUsedByBundle ( scope, node ) {
// const expression = node;
@ -18,7 +18,7 @@ export default function isUsedByBundle ( scope, node ) {
const values = new Set();
declaration.gatherPossibleValues( values );
for ( const value of values ) {
if ( value === UNKNOWN ) {
if ( value === unknown ) {
return true;
}

12
src/ast/scopes/BundleScope.js

@ -1,5 +1,5 @@
import Scope from './Scope.js';
import { UNKNOWN } from '../values';
import { unknown } from '../values';
class SyntheticGlobalDeclaration {
constructor ( name ) {
@ -25,7 +25,15 @@ class SyntheticGlobalDeclaration {
}
gatherPossibleValues ( values ) {
values.add( UNKNOWN );
values.add( unknown );
}
getInstance () {
return unknown;
}
getProperty () {
return unknown;
}
getName () {

3
src/ast/scopes/ModuleScope.js

@ -1,5 +1,6 @@
import { forOwn } from '../../utils/object.js';
import relativeId from '../../utils/relativeId.js';
import { unknown } from '../values.js';
import Scope from './Scope.js';
export default class ModuleScope extends Scope {
@ -63,6 +64,8 @@ export default class ModuleScope extends Scope {
const imported = this.module.imports[ name ];
if ( imported ) {
if ( imported.module.isExternal ) return unknown;
const exported = imported.module.exports[ imported.name ];
const exportedName = exported.localName === 'default' && exported.identifier ? exported.identifier : exported.localName; // TODO this is a mess
return imported.module.scope.getValue( exportedName );

4
src/ast/scopes/Scope.js

@ -1,5 +1,5 @@
import { blank, keys } from '../../utils/object.js';
import { UNKNOWN, TDZ_VIOLATION } from '../values.js';
import { unknown, TDZ_VIOLATION } from '../values.js';
class Parameter {
constructor ( name ) {
@ -18,7 +18,7 @@ class Parameter {
}
gatherPossibleValues ( values ) {
values.add( UNKNOWN ); // TODO populate this at call time
values.add( unknown ); // TODO populate this at call time
}
getName () {

19
src/ast/values.js

@ -5,5 +5,22 @@ 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]]' };
export const TDZ_VIOLATION = { TDZ_VIOLATION: true, toString: () => '[[TDZ_VIOLATION]]' };
export class UnknownValue {
call ( context, args ) {
args.forEach( arg => {
// TODO call functions (and children of objects...) with unknown arguments
});
}
getValue () {
return unknown;
}
markReturnStatements () {
// noop?
}
}
export const unknown = new UnknownValue();

2
test/form/_tk/_config.js

@ -1,3 +1,3 @@
module.exports = {
// solo: true
solo: true
};

1
test/form/empty-if-statement/_config.js

@ -1,4 +1,3 @@
module.exports = {
solo: true,
description: 'removes an empty if statement'
};

4
test/form/relative-external-with-global/_expected/amd.js

@ -6,6 +6,6 @@ define(['./lib/throttle.js'], function (throttle) { 'use strict';
console.log( '.' );
}, 500 );
window.addEventListener( 'mousemove', throttle );
window.addEventListener( 'mousemove', fn );
});
});

2
test/form/relative-external-with-global/_expected/cjs.js

@ -8,4 +8,4 @@ const fn = throttle( () => {
console.log( '.' );
}, 500 );
window.addEventListener( 'mousemove', throttle );
window.addEventListener( 'mousemove', fn );

2
test/form/relative-external-with-global/_expected/es.js

@ -4,4 +4,4 @@ const fn = throttle( () => {
console.log( '.' );
}, 500 );
window.addEventListener( 'mousemove', throttle );
window.addEventListener( 'mousemove', fn );

2
test/form/relative-external-with-global/_expected/iife.js

@ -7,6 +7,6 @@
console.log( '.' );
}, 500 );
window.addEventListener( 'mousemove', throttle );
window.addEventListener( 'mousemove', fn );
}(Lib.throttle));

2
test/form/relative-external-with-global/_expected/umd.js

@ -10,6 +10,6 @@
console.log( '.' );
}, 500 );
window.addEventListener( 'mousemove', throttle );
window.addEventListener( 'mousemove', fn );
})));

2
test/form/relative-external-with-global/main.js

@ -4,4 +4,4 @@ const fn = throttle( () => {
console.log( '.' );
}, 500 );
window.addEventListener( 'mousemove', throttle );
window.addEventListener( 'mousemove', fn );

Loading…
Cancel
Save