Browse Source

broken snapshot

value-tracking
Rich Harris 8 years ago
parent
commit
8d5c73ee2f
  1. 42
      src/Bundle.js
  2. 8
      src/Module.js
  3. 27
      src/ast/Node.js
  4. 31
      src/ast/nodes/ArrowFunctionExpression.js
  5. 2
      src/ast/nodes/AssignmentExpression.js
  6. 4
      src/ast/nodes/BlockStatement.js
  7. 36
      src/ast/nodes/CallExpression.js
  8. 27
      src/ast/nodes/ClassDeclaration.js
  9. 12
      src/ast/nodes/ExportDefaultDeclaration.js
  10. 2
      src/ast/nodes/ExpressionStatement.js
  11. 29
      src/ast/nodes/FunctionDeclaration.js
  12. 5
      src/ast/nodes/FunctionExpression.js
  13. 26
      src/ast/nodes/Identifier.js
  14. 17
      src/ast/nodes/MemberExpression.js
  15. 40
      src/ast/nodes/NewExpression.js
  16. 11
      src/ast/nodes/ReturnStatement.js
  17. 2
      src/ast/nodes/UpdateExpression.js
  18. 27
      src/ast/nodes/VariableDeclarator.js
  19. 15
      src/ast/nodes/shared/Statement.js
  20. 16
      src/ast/scopes/BundleScope.js
  21. 14
      src/ast/scopes/ModuleScope.js
  22. 55
      src/ast/scopes/Scope.js
  23. 1
      src/ast/values.js
  24. 3
      test/form/_tk/_config.js
  25. 5
      test/form/_tk/_expected/es.js
  26. 3
      test/form/_tk/main.js
  27. 7
      test/form/_tk/utils.js

42
src/Bundle.js

@ -98,7 +98,7 @@ export default class Bundle {
this.legacy = options.legacy; this.legacy = options.legacy;
this.acornOptions = options.acorn || {}; this.acornOptions = options.acorn || {};
this.dependentExpressions = []; this.potentialEffects = [];
} }
build () { build () {
@ -128,6 +128,7 @@ export default class Bundle {
this.modules.forEach( module => module.bindImportSpecifiers() ); this.modules.forEach( module => module.bindImportSpecifiers() );
this.modules.forEach( module => module.bindReferences() ); this.modules.forEach( module => module.bindReferences() );
this.modules.forEach( module => module.initialise() );
this.orderedModules = this.sort(); this.orderedModules = this.sort();
timeEnd( 'phase 2' ); timeEnd( 'phase 2' );
@ -137,18 +138,6 @@ export default class Bundle {
timeStart( 'phase 3' ); timeStart( 'phase 3' );
// mark all export statements
entryModule.getExports().forEach( name => {
const declaration = entryModule.traceExport( name );
declaration.exportName = name;
declaration.activate();
if ( declaration.isNamespace ) {
declaration.needsNamespaceBlock = true;
}
});
// mark statements that should appear in the bundle // mark statements that should appear in the bundle
if ( this.treeshake ) { if ( this.treeshake ) {
this.orderedModules.forEach( module => { this.orderedModules.forEach( module => {
@ -159,24 +148,33 @@ export default class Bundle {
while ( !settled ) { while ( !settled ) {
settled = true; settled = true;
let i = this.dependentExpressions.length; let i = this.potentialEffects.length;
while ( i-- ) { while ( i-- ) {
const expression = this.dependentExpressions[i]; const expression = this.potentialEffects[i];
let statement = expression;
while ( statement.parent && !/Function/.test( statement.parent.type ) ) statement = statement.parent;
if ( !statement || statement.ran ) { if ( expression.isMarked ) {
this.dependentExpressions.splice( i, 1 ); this.potentialEffects.splice( i, 1 );
} else if ( expression.isUsedByBundle() ) { } else if ( expression.isUsedByBundle() ) {
settled = false; settled = false;
statement.run( statement.findScope() ); expression.mark();
this.dependentExpressions.splice( i, 1 ); this.potentialEffects.splice( i, 1 );
} }
} }
} }
} }
// mark all export statements
entryModule.getExports().forEach( name => {
const declaration = entryModule.traceExport( name );
declaration.exportName = name;
declaration.activate();
if ( declaration.isNamespace ) {
declaration.needsNamespaceBlock = true;
}
});
timeEnd( 'phase 3' ); timeEnd( 'phase 3' );
// Phase 4 – check for unused external imports, then deconflict // Phase 4 – check for unused external imports, then deconflict

8
src/Module.js

@ -337,6 +337,10 @@ export default class Module {
return keys( exports ); return keys( exports );
} }
initialise () {
this.scope.initialise();
}
namespace () { namespace () {
if ( !this.declarations['*'] ) { if ( !this.declarations['*'] ) {
this.declarations['*'] = new SyntheticNamespaceDeclaration( this ); this.declarations['*'] = new SyntheticNamespaceDeclaration( this );
@ -361,9 +365,7 @@ export default class Module {
run () { run () {
for ( const node of this.ast.body ) { for ( const node of this.ast.body ) {
if ( node.hasEffects( this.scope ) ) { node.run();
node.run( this.scope );
}
} }
} }

27
src/ast/Node.js

@ -39,7 +39,7 @@ export default class Node {
} }
getValue () { getValue () {
return UNKNOWN; return this;
} }
hasEffects ( scope ) { hasEffects ( scope ) {
@ -81,16 +81,31 @@ export default class Node {
return location; return location;
} }
mark () {
if ( this.isMarked ) return;
this.isMarked = true;
if ( this.parent.mark ) this.parent.mark();
}
markChildren () {
function visit ( node ) {
node.mark();
if ( node.type === 'BlockStatement' ) return;
node.eachChild( visit );
}
visit( this );
}
render ( code, es ) { render ( code, es ) {
this.eachChild( child => child.render( code, es ) ); this.eachChild( child => child.render( code, es ) );
} }
run ( scope ) { run () {
if ( this.ran ) return;
this.ran = true;
this.eachChild( child => { this.eachChild( child => {
child.run( this.scope || scope ); child.run();
}); });
} }

31
src/ast/nodes/ArrowFunctionExpression.js

@ -7,6 +7,30 @@ export default class ArrowFunctionExpression extends Node {
super.bind( this.scope || scope ); super.bind( this.scope || scope );
} }
call ( context, args ) {
// TODO account for `this` and `arguments`
if ( this.isCalling ) return; // recursive functions
this.isCalling = true;
this.body.scope.initialise();
args.forEach( ( arg, i ) => {
const param = this.params[i];
if ( param.type !== 'Identifier' ) {
throw new Error( 'TODO desctructuring' );
}
throw new Error( 'TODO setValue' );
});
for ( const node of this.body.body ) {
node.run();
}
this.isCalling = false;
}
findScope ( functionScope ) { findScope ( functionScope ) {
return this.scope || this.parent.findScope( functionScope ); return this.scope || this.parent.findScope( functionScope );
} }
@ -33,6 +57,13 @@ export default class ArrowFunctionExpression extends Node {
} }
} }
this.returnStatements = [];
super.initialise( this.scope ); super.initialise( this.scope );
} }
markReturnStatements () {
// TODO implicit returns
this.returnStatements.forEach( statement => statement.mark() );
}
} }

2
src/ast/nodes/AssignmentExpression.js

@ -38,7 +38,7 @@ export default class AssignmentExpression extends Node {
this.scope = scope; this.scope = scope;
if ( isProgramLevel( this ) ) { if ( isProgramLevel( this ) ) {
this.module.bundle.dependentExpressions.push( this ); this.module.bundle.potentialEffects.push( this );
} }
super.initialise( scope ); super.initialise( scope );

4
src/ast/nodes/BlockStatement.js

@ -48,12 +48,12 @@ export default class BlockStatement extends Statement {
} }
render ( code, es ) { render ( code, es ) {
if (this.body.length) { if ( this.isMarked ) {
for ( const node of this.body ) { for ( const node of this.body ) {
node.render( code, es ); node.render( code, es );
} }
} else { } else {
Statement.prototype.render.call(this, code, es); code.remove( this.start, this.next || this.end );
} }
} }
} }

36
src/ast/nodes/CallExpression.js

@ -1,5 +1,4 @@
import Node from '../Node.js'; import Node from '../Node.js';
import isProgramLevel from '../utils/isProgramLevel.js';
import callHasEffects from './shared/callHasEffects.js'; import callHasEffects from './shared/callHasEffects.js';
export default class CallExpression extends Node { export default class CallExpression extends Node {
@ -31,13 +30,42 @@ export default class CallExpression extends Node {
} }
initialise ( scope ) { initialise ( scope ) {
if ( isProgramLevel( this ) ) { this.scope = scope;
this.module.bundle.dependentExpressions.push( this );
}
super.initialise( scope ); super.initialise( scope );
} }
isUsedByBundle () { isUsedByBundle () {
return this.hasEffects( this.findScope() ); return this.hasEffects( this.findScope() );
} }
mark () {
if ( this.isMarked ) return;
this.isMarked = true;
if ( !this.callee.markReturnStatements ) {
throw new Error( `${this.callee} does not have markReturnStatements method` );
}
// TODO should there be a more general way to handle this? marking a
// statement marks children (down to a certain barrier) as well as
// its parents? or is CallExpression a special case?
this.callee.mark();
this.arguments.forEach( arg => arg.mark() );
this.callee.markReturnStatements( this.arguments );
if ( this.parent.mark ) this.parent.mark();
}
run () {
this.module.bundle.potentialEffects.push( this );
if ( !this.callee.call ) {
throw new Error( `${this.callee} does not have call method` );
}
this.callee.call( this.arguments );
super.run();
}
} }

27
src/ast/nodes/ClassDeclaration.js

@ -6,14 +6,26 @@ export default class ClassDeclaration extends Node {
if ( this.activated ) return; if ( this.activated ) return;
this.activated = true; this.activated = true;
if ( this.superClass ) this.superClass.run( this.scope ); if ( this.superClass ) {
this.body.run(); // TODO is this right?
this.superClass.activate();
}
this.body.mark();
// TODO don't mark all methods willy-nilly
this.body.markChildren();
} }
addReference () { addReference () {
/* noop? */ /* noop? */
} }
call ( context, args ) {
// TODO create a generic context object which represents all instances of this class
// TODO identify the constructor (may be on a superclass, which may not be a class!)
}
gatherPossibleValues ( values ) { gatherPossibleValues ( values ) {
values.add( this ); values.add( this );
} }
@ -26,6 +38,10 @@ export default class ClassDeclaration extends Node {
return false; return false;
} }
markReturnStatements () {
// noop?
}
initialise ( scope ) { initialise ( scope ) {
this.scope = scope; this.scope = scope;
@ -43,9 +59,8 @@ export default class ClassDeclaration extends Node {
} }
} }
run ( scope ) { run () {
if ( this.parent.type === 'ExportDefaultDeclaration' ) { this.scope.setValue( this.id.name, this );
super.run( scope ); super.run();
}
} }
} }

12
src/ast/nodes/ExportDefaultDeclaration.js

@ -4,6 +4,8 @@ const functionOrClassDeclaration = /^(?:Function|Class)Declaration/;
export default class ExportDefaultDeclaration extends Node { export default class ExportDefaultDeclaration extends Node {
initialise ( scope ) { initialise ( scope ) {
this.scope = scope;
this.isExportDeclaration = true; this.isExportDeclaration = true;
this.isDefault = true; this.isDefault = true;
@ -17,7 +19,7 @@ export default class ExportDefaultDeclaration extends Node {
if ( this.activated ) return; if ( this.activated ) return;
this.activated = true; this.activated = true;
this.run(); this.mark();
} }
addReference ( reference ) { addReference ( reference ) {
@ -56,7 +58,7 @@ export default class ExportDefaultDeclaration extends Node {
declaration_start = this.start + statementStr.match(/^\s*export\s+default\s*/)[0].length; declaration_start = this.start + statementStr.match(/^\s*export\s+default\s*/)[0].length;
} }
if ( this.shouldInclude || this.declaration.activated ) { if ( this.isMarked || this.declaration.activated ) {
if ( this.declaration.type === 'CallExpression' && this.declaration.callee.type === 'FunctionExpression' && this.declaration.arguments.length ) { if ( this.declaration.type === 'CallExpression' && this.declaration.callee.type === 'FunctionExpression' && this.declaration.arguments.length ) {
// we're exporting an IIFE. Check it doesn't look unintentional (#1011) // we're exporting an IIFE. Check it doesn't look unintentional (#1011)
const isWrapped = /\(/.test( code.original.slice( this.start, this.declaration.start ) ); const isWrapped = /\(/.test( code.original.slice( this.start, this.declaration.start ) );
@ -122,8 +124,8 @@ export default class ExportDefaultDeclaration extends Node {
} }
} }
run ( scope ) { run () {
this.shouldInclude = true; this.scope.setValue( 'default', this.declaration.getValue() );
super.run( scope ); super.run();
} }
} }

2
src/ast/nodes/ExpressionStatement.js

@ -3,6 +3,6 @@ import Statement from './shared/Statement.js';
export default class ExpressionStatement extends Statement { export default class ExpressionStatement extends Statement {
render ( code, es ) { render ( code, es ) {
super.render( code, es ); super.render( code, es );
if ( this.shouldInclude ) this.insertSemicolon( code ); if ( this.isMarked ) this.insertSemicolon( code );
} }
} }

29
src/ast/nodes/FunctionDeclaration.js

@ -20,6 +20,29 @@ export default class FunctionDeclaration extends Node {
this.body.bind( scope ); this.body.bind( scope );
} }
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.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;
}
gatherPossibleValues ( values ) { gatherPossibleValues ( values ) {
values.add( this ); values.add( this );
} }
@ -38,11 +61,17 @@ export default class FunctionDeclaration extends Node {
this.body.createScope( scope ); this.body.createScope( scope );
this.returnStatements = [];
this.id.initialise( scope ); this.id.initialise( scope );
this.params.forEach( param => param.initialise( this.body.scope ) ); this.params.forEach( param => param.initialise( this.body.scope ) );
this.body.initialise(); this.body.initialise();
} }
markReturnStatements () {
this.returnStatements.forEach( statement => statement.mark() );
}
render ( code, es ) { render ( code, es ) {
if ( !this.module.bundle.treeshake || this.activated ) { if ( !this.module.bundle.treeshake || this.activated ) {
super.render( code, es ); super.render( code, es );

5
src/ast/nodes/FunctionExpression.js

@ -40,4 +40,9 @@ export default class FunctionExpression extends Node {
this.params.forEach( param => param.initialise( this.body.scope ) ); this.params.forEach( param => param.initialise( this.body.scope ) );
this.body.initialise(); this.body.initialise();
} }
mark () {
this.body.mark();
super.mark();
}
} }

26
src/ast/nodes/Identifier.js

@ -24,12 +24,38 @@ export default class Identifier extends Node {
} }
} }
call ( args ) {
const callee = this.scope.getValue( this.name );
if ( !callee.call ) {
throw new Error( `${callee} does not have call method (${this})` );
}
callee.call( undefined, args );
}
gatherPossibleValues ( values ) { gatherPossibleValues ( values ) {
if ( isReference( this, this.parent ) ) { if ( isReference( this, this.parent ) ) {
values.add( this ); values.add( this );
} }
} }
initialise ( scope ) {
this.scope = scope;
}
mark () {
if ( this.declaration ) {
this.declaration.activate();
}
}
markReturnStatements ( args ) {
const callee = this.scope.getValue( this.name );
if ( !callee.markReturnStatements ) {
throw new Error( `${callee} does not have markReturnStatements method` );
}
callee.markReturnStatements( undefined, args );
}
render ( code, es ) { render ( code, es ) {
if ( this.declaration ) { if ( this.declaration ) {
const name = this.declaration.getName( es ); const name = this.declaration.getName( es );

17
src/ast/nodes/MemberExpression.js

@ -72,10 +72,23 @@ export default class MemberExpression extends Node {
} }
} }
call ( args ) {
// TODO
}
gatherPossibleValues ( values ) { gatherPossibleValues ( values ) {
values.add( UNKNOWN ); // TODO values.add( UNKNOWN ); // TODO
} }
mark () {
this.object.mark();
super.mark();
}
markReturnStatements () {
// TODO
}
render ( code, es ) { render ( code, es ) {
if ( this.declaration ) { if ( this.declaration ) {
const name = this.declaration.getName( es ); const name = this.declaration.getName( es );
@ -89,8 +102,8 @@ export default class MemberExpression extends Node {
super.render( code, es ); super.render( code, es );
} }
run ( scope ) { run () {
if ( this.declaration ) this.declaration.activate(); if ( this.declaration ) this.declaration.activate();
super.run( scope ); super.run();
} }
} }

40
src/ast/nodes/NewExpression.js

@ -5,4 +5,44 @@ export default class NewExpression extends Node {
hasEffects ( scope ) { hasEffects ( scope ) {
return callHasEffects( scope, this.callee, true ); return callHasEffects( scope, this.callee, true );
} }
initialise ( scope ) {
this.scope = scope;
super.initialise( scope );
}
isUsedByBundle () {
return this.hasEffects( this.findScope() );
}
mark () {
if ( this.isMarked ) return;
this.isMarked = true;
if ( !this.callee.markReturnStatements ) {
throw new Error( `${this.callee} does not have markReturnStatements method` );
}
// TODO should there be a more general way to handle this? marking a
// statement marks children (down to a certain barrier) as well as
// its parents? or is CallExpression a special case?
this.callee.mark();
this.arguments.forEach( arg => arg.mark() );
this.callee.markReturnStatements( this.arguments );
if ( this.parent.mark ) this.parent.mark();
}
run () {
this.module.bundle.potentialEffects.push( this );
if ( !this.callee.call ) {
throw new Error( `${this.callee} does not have call method` );
}
this.callee.call( this.arguments );
super.run();
}
} }

11
src/ast/nodes/ReturnStatement.js

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

2
src/ast/nodes/UpdateExpression.js

@ -29,7 +29,7 @@ export default class UpdateExpression extends Node {
initialise ( scope ) { initialise ( scope ) {
this.scope = scope; this.scope = scope;
this.module.bundle.dependentExpressions.push( this ); this.module.bundle.potentialEffects.push( this );
super.initialise( scope ); super.initialise( scope );
} }

27
src/ast/nodes/VariableDeclarator.js

@ -4,6 +4,7 @@ import { UNKNOWN } from '../values.js';
class DeclaratorProxy { class DeclaratorProxy {
constructor ( name, declarator, isTopLevel, init ) { constructor ( name, declarator, isTopLevel, init ) {
this.isDeclaratorProxy = true;
this.name = name; this.name = name;
this.declarator = declarator; this.declarator = declarator;
@ -47,17 +48,8 @@ export default class VariableDeclarator extends Node {
if ( this.activated ) return; if ( this.activated ) return;
this.activated = true; this.activated = true;
this.run( this.findScope() ); this.mark();
if ( this.init ) this.init.markChildren();
// if declaration is inside a block, ensure that the block
// is marked for inclusion
if ( this.parent.kind === 'var' ) {
let node = this.parent.parent;
while ( /Statement/.test( node.type ) ) {
node.shouldInclude = true;
node = node.parent;
}
}
} }
hasEffects ( scope ) { hasEffects ( scope ) {
@ -65,6 +57,7 @@ export default class VariableDeclarator extends Node {
} }
initialise ( scope ) { initialise ( scope ) {
this.scope = scope;
this.proxies = new Map(); this.proxies = new Map();
const lexicalBoundary = scope.findLexicalBoundary(); const lexicalBoundary = scope.findLexicalBoundary();
@ -98,4 +91,16 @@ export default class VariableDeclarator extends Node {
super.render( code, es ); super.render( code, es );
} }
run () {
if ( this.id.type !== 'Identifier' ) {
throw new Error( 'TODO desctructuring' );
}
if ( this.init ) {
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
}
}
} }

15
src/ast/nodes/shared/Statement.js

@ -1,16 +1,19 @@
import Node from '../../Node.js'; import Node from '../../Node.js';
export default class Statement extends Node { export default class Statement extends Node {
mark () {
if ( this.isMarked ) return;
this.isMarked = true;
if ( this.parent.mark ) this.parent.mark();
this.markChildren();
}
render ( code, es ) { render ( code, es ) {
if ( !this.module.bundle.treeshake || this.shouldInclude ) { if ( !this.module.bundle.treeshake || this.isMarked ) {
super.render( code, es ); super.render( code, es );
} else { } else {
code.remove( this.leadingCommentStart || this.start, this.next || this.end ); code.remove( this.leadingCommentStart || this.start, this.next || this.end );
} }
} }
run ( scope ) {
this.shouldInclude = true;
super.run( scope );
}
} }

16
src/ast/scopes/BundleScope.js

@ -20,6 +20,10 @@ class SyntheticGlobalDeclaration {
if ( reference.isReassignment ) this.isReassigned = true; if ( reference.isReassignment ) this.isReassigned = true;
} }
call ( args ) {
// TODO assume args can be called?
}
gatherPossibleValues ( values ) { gatherPossibleValues ( values ) {
values.add( UNKNOWN ); values.add( UNKNOWN );
} }
@ -27,6 +31,14 @@ class SyntheticGlobalDeclaration {
getName () { getName () {
return this.name; return this.name;
} }
markReturnStatements () {
// noop
}
toString () {
return `[[SyntheticGlobalDeclaration:${this.name}]]`;
}
} }
export default class BundleScope extends Scope { export default class BundleScope extends Scope {
@ -37,4 +49,8 @@ export default class BundleScope extends Scope {
return this.declarations[ name ]; return this.declarations[ name ];
} }
getValue ( name ) {
return this.declarations[ name ];
}
} }

14
src/ast/scopes/ModuleScope.js

@ -55,4 +55,18 @@ export default class ModuleScope extends Scope {
findLexicalBoundary () { findLexicalBoundary () {
return this; return this;
} }
getValue ( name ) {
if ( name in this.values ) {
return this.values[ name ];
}
const imported = this.module.imports[ name ];
if ( imported ) {
const exported = imported.module.exports[ imported.name ];
return imported.module.scope.getValue( exported.localName );
}
return this.parent.getValue( name );
}
} }

55
src/ast/scopes/Scope.js

@ -1,5 +1,5 @@
import { blank, keys } from '../../utils/object.js'; import { blank, keys } from '../../utils/object.js';
import { UNKNOWN } from '../values.js'; import { UNKNOWN, TDZ_VIOLATION } from '../values.js';
class Parameter { class Parameter {
constructor ( name ) { constructor ( name ) {
@ -28,6 +28,7 @@ class Parameter {
export default class Scope { export default class Scope {
constructor ( options = {} ) { constructor ( options = {} ) {
this.owner = options.owner;
this.parent = options.parent; this.parent = options.parent;
this.isBlockScope = !!options.isBlockScope; this.isBlockScope = !!options.isBlockScope;
this.isLexicalBoundary = !!options.isLexicalBoundary; this.isLexicalBoundary = !!options.isLexicalBoundary;
@ -37,6 +38,7 @@ export default class Scope {
if ( this.parent ) this.parent.children.push( this ); if ( this.parent ) this.parent.children.push( this );
this.declarations = blank(); this.declarations = blank();
this.values = blank();
if ( this.isLexicalBoundary && !this.isModuleScope ) { if ( this.isLexicalBoundary && !this.isModuleScope ) {
this.declarations.arguments = new Parameter( 'arguments' ); this.declarations.arguments = new Parameter( 'arguments' );
@ -64,9 +66,7 @@ export default class Scope {
} }
deshadow ( names ) { deshadow ( names ) {
keys( this.declarations ).forEach( key => { this.eachDeclaration( ( key, declaration ) => {
const declaration = this.declarations[ key ];
// we can disregard exports.foo etc // we can disregard exports.foo etc
if ( declaration.exportName && declaration.isReassigned ) return; if ( declaration.exportName && declaration.isReassigned ) return;
@ -85,6 +85,13 @@ export default class Scope {
this.children.forEach( scope => scope.deshadow( names ) ); this.children.forEach( scope => scope.deshadow( names ) );
} }
eachDeclaration ( callback ) {
keys( this.declarations ).forEach( key => {
const declaration = this.declarations[ key ];
callback( key, declaration );
});
}
findDeclaration ( name ) { findDeclaration ( name ) {
return this.declarations[ name ] || return this.declarations[ name ] ||
( this.parent && this.parent.findDeclaration( name ) ); ( this.parent && this.parent.findDeclaration( name ) );
@ -93,4 +100,44 @@ export default class Scope {
findLexicalBoundary () { findLexicalBoundary () {
return this.isLexicalBoundary ? this : this.parent.findLexicalBoundary(); return this.isLexicalBoundary ? this : this.parent.findLexicalBoundary();
} }
getValue ( name ) {
if ( name in this.values ) {
return this.values[ name ];
}
if ( this.parent ) {
return this.parent.getValue( name );
}
throw new Error( `hmm ${name}` );
}
initialise () {
this.eachDeclaration( ( name, declaration ) => {
if ( declaration.isDeclaratorProxy ) {
this.values[ name ] = declaration.declarator.kind === 'var' ? undefined : TDZ_VIOLATION;
} else if ( declaration.isParam || declaration.type === 'ExportDefaultDeclaration' ) {
this.values[ name ] = undefined;
} else if ( declaration.type === 'ClassDeclaration' ) {
this.values[ name ] = TDZ_VIOLATION;
} else if ( declaration.type === 'FunctionDeclaration' ) {
this.values[ name ] = declaration;
} else {
console.log( declaration )
throw new Error( 'well this is odd' );
}
});
}
setValue ( name, value ) {
this.values[ name ] = value;
if ( !( name in this.declarations ) ) {
// TODO if this scope's owner is a conditional, the parent scope
// should know that there are multiple possible values, and if
// it's a loop, same. if it's a function? god knows. need to
// figure that out
}
}
} }

1
src/ast/values.js

@ -6,3 +6,4 @@ export const NUMBER = { NUMBER: true, toString: () => '[[NUMBER]]' };
export const OBJECT = { OBJECT: true, toString: () => '[[OBJECT]]' }; export const OBJECT = { OBJECT: true, toString: () => '[[OBJECT]]' };
export const STRING = { STRING: true, toString: () => '[[STRING]]' }; export const STRING = { STRING: true, toString: () => '[[STRING]]' };
export const UNKNOWN = { UNKNOWN: true, toString: () => '[[UNKNOWN]]' }; export const UNKNOWN = { UNKNOWN: true, toString: () => '[[UNKNOWN]]' };
export const TDZ_VIOLATION = { TDZ_VIOLATION: true, toString: () => '[[TDZ_VIOLATION]]' };

3
test/form/_tk/_config.js

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

5
test/form/_tk/_expected/es.js

@ -0,0 +1,5 @@
async function foo () {
return 'foo';
}
foo().then( value => console.log( value ) );

3
test/form/_tk/main.js

@ -0,0 +1,3 @@
import { foo } from './utils.js';
foo().then( value => console.log( value ) );

7
test/form/_tk/utils.js

@ -0,0 +1,7 @@
export async function foo () {
return 'foo';
}
export async function bar () {
return 'bar';
}
Loading…
Cancel
Save