Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master'
Browse files Browse the repository at this point in the history
  • Loading branch information
lukastaegert committed Nov 11, 2017
2 parents 6d0e492 + df0cc03 commit e3f780f
Show file tree
Hide file tree
Showing 19 changed files with 82 additions and 123 deletions.
4 changes: 2 additions & 2 deletions src/ast/nodes/BlockStatement.js
@@ -1,12 +1,12 @@
import Statement from './shared/Statement.js';
import BlockScope from '../scopes/BlockScope';
import { UNKNOWN_ASSIGNMENT } from '../values';
import { UNDEFINED_ASSIGNMENT } from '../values';

export default class BlockStatement extends Statement {
bindImplicitReturnExpressionToScope () {
const lastStatement = this.body[ this.body.length - 1 ];
if ( !lastStatement || lastStatement.type !== 'ReturnStatement' ) {
this.scope.addReturnExpression( UNKNOWN_ASSIGNMENT );
this.scope.addReturnExpression( UNDEFINED_ASSIGNMENT );
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/ast/nodes/ReturnStatement.js
@@ -1,5 +1,5 @@
import Statement from './shared/Statement.js';
import {UNKNOWN_ASSIGNMENT} from '../values';
import { UNDEFINED_ASSIGNMENT } from '../values';

export default class ReturnStatement extends Statement {
hasEffects ( options ) {
Expand All @@ -8,6 +8,6 @@ export default class ReturnStatement extends Statement {
}

initialiseNode () {
this.scope.addReturnExpression( this.argument || UNKNOWN_ASSIGNMENT );
this.scope.addReturnExpression( this.argument || UNDEFINED_ASSIGNMENT );
}
}
4 changes: 2 additions & 2 deletions src/ast/nodes/UnaryExpression.js
@@ -1,5 +1,5 @@
import Node from '../Node.js';
import { UNKNOWN_ASSIGNMENT, UNKNOWN_VALUE } from '../values';
import { UNDEFINED_ASSIGNMENT, UNKNOWN_VALUE } from '../values';
import ExecutionPathOptions from '../ExecutionPathOptions';

const operators = {
Expand All @@ -15,7 +15,7 @@ const operators = {
export default class UnaryExpression extends Node {
bindNode () {
if ( this.operator === 'delete' ) {
this.argument.bindAssignmentAtPath( [], UNKNOWN_ASSIGNMENT, ExecutionPathOptions.create() );
this.argument.bindAssignmentAtPath( [], UNDEFINED_ASSIGNMENT, ExecutionPathOptions.create() );
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/ast/scopes/ModuleScope.js
Expand Up @@ -2,7 +2,7 @@ import { forOwn } from '../../utils/object.js';
import relativeId from '../../utils/relativeId.js';
import Scope from './Scope.js';
import LocalVariable from '../variables/LocalVariable';
import { UNKNOWN_ASSIGNMENT } from '../values';
import { UNDEFINED_ASSIGNMENT } from '../values';

export default class ModuleScope extends Scope {
constructor ( module ) {
Expand All @@ -12,7 +12,7 @@ export default class ModuleScope extends Scope {
} );

this.module = module;
this.variables.this = new LocalVariable( 'this', null, UNKNOWN_ASSIGNMENT );
this.variables.this = new LocalVariable( 'this', null, UNDEFINED_ASSIGNMENT );
}

deshadow ( names ) {
Expand Down
4 changes: 2 additions & 2 deletions src/ast/scopes/Scope.js
@@ -1,7 +1,7 @@
import { blank, keys } from '../../utils/object.js';
import LocalVariable from '../variables/LocalVariable';
import ExportDefaultVariable from '../variables/ExportDefaultVariable';
import { UNKNOWN_ASSIGNMENT } from '../values';
import { UNDEFINED_ASSIGNMENT } from '../values';

export default class Scope {
constructor ( options = {} ) {
Expand All @@ -28,7 +28,7 @@ export default class Scope {
variable.addDeclaration( identifier );
options.init && variable.bindAssignmentAtPath( [], options.init );
} else {
this.variables[ name ] = new LocalVariable( identifier.name, identifier, options.init || UNKNOWN_ASSIGNMENT );
this.variables[ name ] = new LocalVariable( identifier.name, identifier, options.init || UNDEFINED_ASSIGNMENT );
}
return this.variables[ name ];
}
Expand Down
11 changes: 11 additions & 0 deletions src/ast/values.js
Expand Up @@ -10,3 +10,14 @@ export const UNKNOWN_ASSIGNMENT = {
someReturnExpressionWhenCalledAtPath: () => true,
toString: () => '[[UNKNOWN]]'
};

export const UNDEFINED_ASSIGNMENT = {
type: 'UNDEFINED',
bindAssignmentAtPath: () => {},
forEachReturnExpressionWhenCalledAtPath: () => {},
hasEffectsWhenAccessedAtPath: path => path.length > 0,
hasEffectsWhenAssignedAtPath: path => path.length > 0,
hasEffectsWhenCalledAtPath: () => true,
someReturnExpressionWhenCalledAtPath: () => true,
toString: () => '[[UNDEFINED]]'
};
4 changes: 2 additions & 2 deletions src/ast/variables/ArgumentsVariable.js
@@ -1,9 +1,9 @@
import LocalVariable from './LocalVariable';
import { UNKNOWN_ASSIGNMENT } from '../values';
import { UNDEFINED_ASSIGNMENT, UNKNOWN_ASSIGNMENT } from '../values';

const getParameterVariable = ( path, options ) =>
(path[ 0 ] < options.getArgumentsVariables().length && options.getArgumentsVariables()[ path[ 0 ] ] )
|| UNKNOWN_ASSIGNMENT;
|| UNDEFINED_ASSIGNMENT;

export default class ArgumentsVariable extends LocalVariable {
constructor ( parameters ) {
Expand Down
15 changes: 9 additions & 6 deletions src/ast/variables/LocalVariable.js
@@ -1,8 +1,8 @@
import Variable from './Variable';
import VariableShapeTracker from './VariableShapeTracker';

// To avoid infinite recursions
const MAX_PATH_LENGTH = 6;
// To avoid exponential performance degradation for complex object manipulations
const MAX_PATH_LENGTH = 2;

export default class LocalVariable extends Variable {
constructor ( name, declarator, init ) {
Expand All @@ -19,7 +19,10 @@ export default class LocalVariable extends Variable {
}

bindAssignmentAtPath ( path, expression, options ) {
if ( path.length > MAX_PATH_LENGTH || this.boundExpressions.hasAtPath( path, expression ) ) return;
if ( expression.variable ) {
expression = expression.variable;
}
if ( path.length > MAX_PATH_LENGTH || expression === this || this.boundExpressions.hasAtPath( path, expression ) ) return;
this.boundExpressions.addAtPath( path, expression );
this.boundExpressions.forEachAssignedToPath( path, ( subPath, node ) => {
subPath.length > 0
Expand Down Expand Up @@ -51,9 +54,9 @@ export default class LocalVariable extends Variable {
hasEffectsWhenAccessedAtPath ( path, options ) {
return path.length > MAX_PATH_LENGTH
|| this.boundExpressions.someAtPath( path, ( relativePath, node ) =>
!options.hasNodeBeenAccessedAtPath( relativePath, node ) && node
.hasEffectsWhenAccessedAtPath( relativePath,
options.addAccessedNodeAtPath( relativePath, node ) ) );
relativePath.length > 0
&& !options.hasNodeBeenAccessedAtPath( relativePath, node )
&& node.hasEffectsWhenAccessedAtPath( relativePath, options.addAccessedNodeAtPath( relativePath, node ) ) );
}

hasEffectsWhenAssignedAtPath ( path, options ) {
Expand Down
69 changes: 31 additions & 38 deletions src/ast/variables/VariableShapeTracker.js
Expand Up @@ -4,20 +4,22 @@ const SET_KEY = { type: 'SET_KEY' };
export const UNKNOWN_KEY = { type: 'UNKNOWN_KEY' };

const UNKNOWN_ASSIGNMENTS = new Map( [ [ SET_KEY, new Set( [ UNKNOWN_ASSIGNMENT ] ) ] ] );
const UNKNOWN_KEY_ASSIGNMENT = [ UNKNOWN_KEY, { toString: ( path = '' ) => path + '[[UNKNOWN_KEY]]' } ];

export default class VariableShapeTracker {
constructor () {
this._assignments = new Map( [ [ SET_KEY, new Set() ] ] );
}

addAtPath ( path, assignment ) {
if ( this._assignments === UNKNOWN_ASSIGNMENTS ) return;
if ( path.length === 0 ) {
if ( assignment === UNKNOWN_ASSIGNMENT ) {
this._assignments = UNKNOWN_ASSIGNMENTS;
} else {
this._assignments.get( SET_KEY ).add( assignment );
}
if ( this._assignments === UNKNOWN_ASSIGNMENTS
|| (path.length > 0 && this._assignments.has( UNKNOWN_KEY ) ) ) return;
if ( path.length === 0 && assignment === UNKNOWN_ASSIGNMENT ) {
this._assignments = UNKNOWN_ASSIGNMENTS;
} else if ( path[ 0 ] === UNKNOWN_KEY ) {
this._assignments = new Map( [ [ SET_KEY, this._assignments.get( SET_KEY ) ], UNKNOWN_KEY_ASSIGNMENT ] );
} else if ( path.length === 0 ) {
this._assignments.get( SET_KEY ).add( assignment );
} else {
const [ nextPath, ...remainingPath ] = path;
if ( !this._assignments.has( nextPath ) ) {
Expand All @@ -30,35 +32,26 @@ export default class VariableShapeTracker {
forEachAtPath ( path, callback ) {
const [ nextPath, ...remainingPath ] = path;
this._assignments.get( SET_KEY ).forEach( assignment => callback( path, assignment ) );
if ( path.length > 0 ) {
if ( nextPath === UNKNOWN_KEY ) {
this._assignments.forEach( ( assignment, subPath ) => {
if ( subPath !== SET_KEY ) {
assignment.forEachAtPath( remainingPath, callback );
}
} );
} else {
if ( this._assignments.has( nextPath ) ) {
this._assignments.get( nextPath ).forEachAtPath( remainingPath, callback );
}
if ( this._assignments.has( UNKNOWN_KEY ) ) {
this._assignments.get( UNKNOWN_KEY ).forEachAtPath( remainingPath, callback );
}
}
if ( path.length > 0
&& nextPath !== UNKNOWN_KEY
&& !this._assignments.has( UNKNOWN_KEY )
&& this._assignments.has( nextPath ) ) {
this._assignments.get( nextPath ).forEachAtPath( remainingPath, callback );
}
}

forEachAssignedToPath ( path, callback ) {
if ( this._assignments === UNKNOWN_ASSIGNMENTS || this._assignments.has( UNKNOWN_KEY ) ) return;
if ( path.length > 0 ) {
const [ nextPath, ...remainingPath ] = path;
if ( nextPath === UNKNOWN_KEY || this._assignments.has( UNKNOWN_KEY ) ) return;
if ( this._assignments.has( nextPath ) ) {
this._assignments.get( nextPath ).forEachAssignedToPath( remainingPath, callback );
}
} else {
this._assignments.get( SET_KEY ).forEach( assignment => callback( [], assignment ) );
this._assignments.forEach( ( assignment, subPath ) => {
if ( subPath === SET_KEY ) {
assignment.forEach( subAssignment => callback( [], subAssignment ) );
} else {
if ( subPath !== SET_KEY ) {
assignment.forEachAssignedToPath( [],
( relativePath, assignment ) => callback( [ subPath, ...relativePath ], assignment ) );
}
Expand All @@ -67,9 +60,11 @@ export default class VariableShapeTracker {
}

hasAtPath ( path, assignment ) {
if ( this._assignments === UNKNOWN_ASSIGNMENTS ) return true;
if ( path.length === 0 ) {
return this._assignments.get( SET_KEY ).has( assignment );
} else {
if ( this._assignments.has( UNKNOWN_KEY ) ) return true;
const [ nextPath, ...remainingPath ] = path;
if ( !this._assignments.has( nextPath ) ) {
return false;
Expand All @@ -84,20 +79,18 @@ export default class VariableShapeTracker {
|| (
path.length > 0
&& (
(nextPath === UNKNOWN_KEY
&& Array.from( this._assignments ).some( ( [ subPath, assignment ] ) => {
if ( subPath !== SET_KEY ) {
return assignment.someAtPath( remainingPath, predicateFunction );
}
} ))
|| (nextPath !== UNKNOWN_KEY
&& (
(this._assignments.has( nextPath )
&& this._assignments.get( nextPath ).someAtPath( remainingPath, predicateFunction ))
|| (this._assignments.has( UNKNOWN_KEY )
&& this._assignments.get( UNKNOWN_KEY ).someAtPath( remainingPath, predicateFunction ))
))
(nextPath === UNKNOWN_KEY || this._assignments.has( UNKNOWN_KEY )
? predicateFunction( remainingPath, UNKNOWN_ASSIGNMENT )
: this._assignments.has( nextPath )
&& this._assignments.get( nextPath ).someAtPath( remainingPath, predicateFunction ))
)
);
}

// For debugging purposes
toString ( pathString = '/' ) {
return Array.from( this._assignments ).map( ( [ subPath, subAssignment ] ) => subPath === SET_KEY
? Array.from( subAssignment ).map( assignment => pathString + assignment.toString() ).join( '\n' )
: subAssignment.toString( pathString + subPath + ': ' ) ).join( '\n' );
}
}
Expand Up @@ -17,7 +17,3 @@ retained5[ 'f' + 'oo' ]();

const removed1 = { foo: {} };
removed1.foo[ 'b' + 'ar' ] = 1;

const removed2 = { foo: function () {} };
removed2[ 'f' + 'oo' ] = function () {this.x = 1;};
const result2 = new removed2.foo();
Expand Up @@ -11,7 +11,6 @@ define(function () { 'use strict';
};

const retained1b = retained1a.effect;
const retained1c = retained1a[ 'eff' + 'ect' ];

const retained3 = {
set effect ( value ) {
Expand All @@ -21,14 +20,6 @@ define(function () { 'use strict';

retained3.effect = 'retained';

const retained4 = {
set effect ( value ) {
console.log( value );
}
};

retained4[ 'eff' + 'ect' ] = 'retained';

const retained7 = {
foo: () => {},
get foo () {
Expand Down
Expand Up @@ -11,7 +11,6 @@ const retained1a = {
};

const retained1b = retained1a.effect;
const retained1c = retained1a[ 'eff' + 'ect' ];

const retained3 = {
set effect ( value ) {
Expand All @@ -21,14 +20,6 @@ const retained3 = {

retained3.effect = 'retained';

const retained4 = {
set effect ( value ) {
console.log( value );
}
};

retained4[ 'eff' + 'ect' ] = 'retained';

const retained7 = {
foo: () => {},
get foo () {
Expand Down
Expand Up @@ -9,7 +9,6 @@ const retained1a = {
};

const retained1b = retained1a.effect;
const retained1c = retained1a[ 'eff' + 'ect' ];

const retained3 = {
set effect ( value ) {
Expand All @@ -19,14 +18,6 @@ const retained3 = {

retained3.effect = 'retained';

const retained4 = {
set effect ( value ) {
console.log( value );
}
};

retained4[ 'eff' + 'ect' ] = 'retained';

const retained7 = {
foo: () => {},
get foo () {
Expand Down
Expand Up @@ -12,7 +12,6 @@
};

const retained1b = retained1a.effect;
const retained1c = retained1a[ 'eff' + 'ect' ];

const retained3 = {
set effect ( value ) {
Expand All @@ -22,14 +21,6 @@

retained3.effect = 'retained';

const retained4 = {
set effect ( value ) {
console.log( value );
}
};

retained4[ 'eff' + 'ect' ] = 'retained';

const retained7 = {
foo: () => {},
get foo () {
Expand Down
Expand Up @@ -15,7 +15,6 @@
};

const retained1b = retained1a.effect;
const retained1c = retained1a[ 'eff' + 'ect' ];

const retained3 = {
set effect ( value ) {
Expand All @@ -25,14 +24,6 @@

retained3.effect = 'retained';

const retained4 = {
set effect ( value ) {
console.log( value );
}
};

retained4[ 'eff' + 'ect' ] = 'retained';

const retained7 = {
foo: () => {},
get foo () {
Expand Down

0 comments on commit e3f780f

Please sign in to comment.