Skip to content

Commit

Permalink
Merge pull request #1740 from rollup/refactor-assignment-handling
Browse files Browse the repository at this point in the history
Refactor assignment handling
  • Loading branch information
lukastaegert committed Nov 19, 2017
2 parents 172d72a + 945bb9d commit 1042217
Show file tree
Hide file tree
Showing 59 changed files with 237 additions and 286 deletions.
6 changes: 3 additions & 3 deletions src/ast/Node.js
Expand Up @@ -20,16 +20,16 @@ export default class Node {
}

/**
* Bind an expression as an assignment to a node given a path.
* E.g., node.bindAssignmentAtPath(['x', 'y'], otherNode) is called when otherNode
* Reassign a given path of an object.
* E.g., node.reassignPath(['x', 'y']) is called when something
* is assigned to node.x.y.
* The default noop implementation is ok as long as hasEffectsWhenAssignedAtPath
* always returns true for this node. Otherwise it should be overridden.
* @param {String[]} path
* @param {Node} expression
* @param {ExecutionPathOptions} options
*/
bindAssignmentAtPath ( path, expression, options ) {}
reassignPath ( path, options ) {}

/**
* Override to control on which children "bind" is called.
Expand Down
4 changes: 2 additions & 2 deletions src/ast/nodes/ArrayPattern.js
Expand Up @@ -2,9 +2,9 @@ import Node from '../Node.js';
import { UNKNOWN_ASSIGNMENT } from '../values';

export default class ArrayPattern extends Node {
bindAssignmentAtPath ( path, expression, options ) {
reassignPath ( path, options ) {
path.length === 0
&& this.eachChild( child => child.bindAssignmentAtPath( [], UNKNOWN_ASSIGNMENT, options ) );
&& this.eachChild( child => child.reassignPath( [], options ) );
}

hasEffectsWhenAssignedAtPath ( path, options ) {
Expand Down
2 changes: 1 addition & 1 deletion src/ast/nodes/AssignmentExpression.js
Expand Up @@ -5,7 +5,7 @@ import ExecutionPathOptions from '../ExecutionPathOptions';
export default class AssignmentExpression extends Node {
bindNode () {
disallowIllegalReassignment( this.scope, this.left );
this.left.bindAssignmentAtPath( [], this.right, ExecutionPathOptions.create() );
this.left.reassignPath( [], ExecutionPathOptions.create() );
}

hasEffects ( options ) {
Expand Down
6 changes: 3 additions & 3 deletions src/ast/nodes/AssignmentPattern.js
Expand Up @@ -3,12 +3,12 @@ import ExecutionPathOptions from '../ExecutionPathOptions';

export default class AssignmentPattern extends Node {
bindNode () {
this.left.bindAssignmentAtPath( [], this.right, ExecutionPathOptions.create() );
this.left.reassignPath( [], ExecutionPathOptions.create() );
}

bindAssignmentAtPath ( path, expression, options ) {
reassignPath ( path, options ) {
path.length === 0
&& this.left.bindAssignmentAtPath( path, expression, options );
&& this.left.reassignPath( path, options );
}

hasEffectsWhenAssignedAtPath ( path, options ) {
Expand Down
4 changes: 2 additions & 2 deletions src/ast/nodes/CallExpression.js
Expand Up @@ -2,10 +2,10 @@ import Node from '../Node.js';
import CallOptions from '../CallOptions';

export default class CallExpression extends Node {
bindAssignmentAtPath ( path, expression, options ) {
reassignPath ( path, options ) {
!options.hasReturnExpressionBeenAssignedAtPath( path, this )
&& this.callee.forEachReturnExpressionWhenCalledAtPath( [], this._callOptions, innerOptions => node =>
node.bindAssignmentAtPath( path, expression, innerOptions.addAssignedReturnExpressionAtPath( path, this ) ), options );
node.reassignPath( path, innerOptions.addAssignedReturnExpressionAtPath( path, this ) ), options );
}

bindNode () {
Expand Down
4 changes: 2 additions & 2 deletions src/ast/nodes/ConditionalExpression.js
Expand Up @@ -2,9 +2,9 @@ import Node from '../Node.js';
import { UNKNOWN_VALUE } from '../values.js';

export default class ConditionalExpression extends Node {
bindAssignmentAtPath ( path, expression, options ) {
reassignPath ( path, options ) {
path.length > 0
&& this._forEachRelevantBranch( node => node.bindAssignmentAtPath( path, expression, options ) );
&& this._forEachRelevantBranch( node => node.reassignPath( path, options ) );
}

forEachReturnExpressionWhenCalledAtPath ( path, callOptions, callback, options ) {
Expand Down
3 changes: 1 addition & 2 deletions src/ast/nodes/ForOfStatement.js
@@ -1,11 +1,10 @@
import Statement from './shared/Statement.js';
import BlockScope from '../scopes/BlockScope';
import { UNKNOWN_ASSIGNMENT } from '../values';
import ExecutionPathOptions from '../ExecutionPathOptions';

export default class ForOfStatement extends Statement {
bindNode () {
this.left.bindAssignmentAtPath( [], UNKNOWN_ASSIGNMENT, ExecutionPathOptions.create() );
this.left.reassignPath( [], ExecutionPathOptions.create() );
}

hasEffects ( options ) {
Expand Down
4 changes: 2 additions & 2 deletions src/ast/nodes/Identifier.js
Expand Up @@ -3,10 +3,10 @@ import isReference from 'is-reference';
import { UNKNOWN_ASSIGNMENT } from '../values';

export default class Identifier extends Node {
bindAssignmentAtPath ( path, expression, options ) {
reassignPath ( path, options ) {
this._bindVariableIfMissing();
this.variable
&& this.variable.bindAssignmentAtPath( path, expression, options );
&& this.variable.reassignPath( path, options );
}

bindNode () {
Expand Down
4 changes: 2 additions & 2 deletions src/ast/nodes/LogicalExpression.js
Expand Up @@ -2,9 +2,9 @@ import Node from '../Node.js';
import { UNKNOWN_VALUE } from '../values.js';

export default class LogicalExpression extends Node {
bindAssignmentAtPath ( path, expression, options ) {
reassignPath ( path, options ) {
path.length > 0
&& this._forEachRelevantBranch( node => node.bindAssignmentAtPath( path, expression, options ) );
&& this._forEachRelevantBranch( node => node.reassignPath( path, options ) );
}

forEachReturnExpressionWhenCalledAtPath ( path, callOptions, callback, options ) {
Expand Down
8 changes: 4 additions & 4 deletions src/ast/nodes/MemberExpression.js
@@ -1,6 +1,6 @@
import relativeId from '../../utils/relativeId.js';
import Node from '../Node.js';
import { UNKNOWN_KEY } from '../variables/VariableShapeTracker';
import { UNKNOWN_KEY } from '../variables/VariableReassignmentTracker';

const validProp = /^[a-zA-Z_$][a-zA-Z_$0-9]*$/;

Expand Down Expand Up @@ -76,12 +76,12 @@ export default class MemberExpression extends Node {
}
}

bindAssignmentAtPath ( path, expression, options ) {
reassignPath ( path, options ) {
if ( !this._bound ) this.bind();
if ( this.variable ) {
this.variable.bindAssignmentAtPath( path, expression, options );
this.variable.reassignPath( path, options );
} else {
this.object.bindAssignmentAtPath( [ this._getPathSegment(), ...path ], expression, options );
this.object.reassignPath( [ this._getPathSegment(), ...path ], options );
}
}

Expand Down
6 changes: 3 additions & 3 deletions src/ast/nodes/ObjectExpression.js
@@ -1,18 +1,18 @@
import Node from '../Node.js';
import { UNKNOWN_KEY } from '../variables/VariableShapeTracker';
import { UNKNOWN_KEY } from '../variables/VariableReassignmentTracker';

const PROPERTY_KINDS_READ = [ 'init', 'get' ];
const PROPERTY_KINDS_WRITE = [ 'init', 'set' ];

export default class ObjectExpression extends Node {
bindAssignmentAtPath ( path, expression, options ) {
reassignPath ( path, options ) {
if ( path.length === 0 ) return;

const { properties, hasCertainHit } = this._getPossiblePropertiesWithName(
path[ 0 ], path.length === 1 ? PROPERTY_KINDS_WRITE : PROPERTY_KINDS_READ );
(path.length === 1 || hasCertainHit)
&& properties.forEach( property => (path.length > 1 || property.kind === 'set')
&& property.bindAssignmentAtPath( path.slice( 1 ), expression, options ) );
&& property.reassignPath( path.slice( 1 ), options ) );
}

forEachReturnExpressionWhenCalledAtPath ( path, callOptions, callback, options ) {
Expand Down
4 changes: 2 additions & 2 deletions src/ast/nodes/ObjectPattern.js
@@ -1,9 +1,9 @@
import Node from '../Node.js';

export default class ObjectPattern extends Node {
bindAssignmentAtPath ( path, expression, options ) {
reassignPath ( path, options ) {
path.length === 0
&& this.properties.forEach( child => child.bindAssignmentAtPath( path, expression, options ) );
&& this.properties.forEach( child => child.reassignPath( path, options ) );
}

hasEffectsWhenAssignedAtPath ( path, options ) {
Expand Down
6 changes: 3 additions & 3 deletions src/ast/nodes/Property.js
Expand Up @@ -3,13 +3,13 @@ import CallOptions from '../CallOptions';
import { UNKNOWN_ASSIGNMENT } from '../values';

export default class Property extends Node {
bindAssignmentAtPath ( path, expression, options ) {
reassignPath ( path, options ) {
if ( this.kind === 'get' ) {
path.length > 0
&& this.value.forEachReturnExpressionWhenCalledAtPath( [], this._accessorCallOptions, innerOptions => node =>
node.bindAssignmentAtPath( path, expression, innerOptions.addAssignedReturnExpressionAtPath( path, this ) ), options );
node.reassignPath( path, innerOptions.addAssignedReturnExpressionAtPath( path, this ) ), options );
} else if ( this.kind !== 'set' ) {
this.value.bindAssignmentAtPath( path, expression, options );
this.value.reassignPath( path, options );
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/ast/nodes/RestElement.js
Expand Up @@ -2,9 +2,9 @@ import Node from '../Node.js';
import { UNKNOWN_ASSIGNMENT } from '../values';

export default class RestElement extends Node {
bindAssignmentAtPath ( path, expression, options ) {
reassignPath ( path, options ) {
path.length === 0
&& this.argument.bindAssignmentAtPath( [], UNKNOWN_ASSIGNMENT, options );
&& this.argument.reassignPath( [], options );
}

hasEffectsWhenAssignedAtPath ( path, options ) {
Expand Down
4 changes: 2 additions & 2 deletions src/ast/nodes/UnaryExpression.js
@@ -1,5 +1,5 @@
import Node from '../Node.js';
import { UNDEFINED_ASSIGNMENT, UNKNOWN_VALUE } from '../values';
import { 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( [], UNDEFINED_ASSIGNMENT, ExecutionPathOptions.create() );
this.argument.reassignPath( [], ExecutionPathOptions.create() );
}
}

Expand Down
3 changes: 1 addition & 2 deletions src/ast/nodes/UpdateExpression.js
@@ -1,12 +1,11 @@
import Node from '../Node.js';
import disallowIllegalReassignment from './shared/disallowIllegalReassignment.js';
import VirtualNumberLiteral from './shared/VirtualNumberLiteral';
import ExecutionPathOptions from '../ExecutionPathOptions';

export default class UpdateExpression extends Node {
bindNode () {
disallowIllegalReassignment( this.scope, this.argument );
this.argument.bindAssignmentAtPath( [], new VirtualNumberLiteral(), ExecutionPathOptions.create() );
this.argument.reassignPath( [], ExecutionPathOptions.create() );
if ( this.argument.type === 'Identifier' ) {
const variable = this.scope.findVariable( this.argument.name );
variable.isReassigned = true;
Expand Down
5 changes: 2 additions & 3 deletions src/ast/nodes/VariableDeclaration.js
@@ -1,6 +1,5 @@
import Node from '../Node.js';
import extractNames from '../utils/extractNames.js';
import { UNKNOWN_ASSIGNMENT } from '../values';
import ExecutionPathOptions from '../ExecutionPathOptions';

function getSeparator ( code, start ) {
Expand All @@ -19,8 +18,8 @@ function getSeparator ( code, start ) {
const forStatement = /^For(?:Of|In)?Statement/;

export default class VariableDeclaration extends Node {
bindAssignmentAtPath () {
this.eachChild( child => child.bindAssignmentAtPath( [], UNKNOWN_ASSIGNMENT, ExecutionPathOptions.create() ) );
reassignPath () {
this.eachChild( child => child.reassignPath( [], ExecutionPathOptions.create() ) );
}

hasEffectsWhenAssignedAtPath () {
Expand Down
4 changes: 2 additions & 2 deletions src/ast/nodes/VariableDeclarator.js
Expand Up @@ -2,8 +2,8 @@ import Node from '../Node.js';
import extractNames from '../utils/extractNames.js';

export default class VariableDeclarator extends Node {
bindAssignmentAtPath ( path, expression, options ) {
this.id.bindAssignmentAtPath( path, expression, options );
reassignPath ( path, options ) {
this.id.reassignPath( path, options );
}

initialiseDeclarator ( parentScope, kind ) {
Expand Down
15 changes: 0 additions & 15 deletions src/ast/nodes/shared/VirtualNumberLiteral.js

This file was deleted.

3 changes: 2 additions & 1 deletion src/ast/scopes/Scope.js
Expand Up @@ -2,6 +2,7 @@ import { blank, keys } from '../../utils/object.js';
import LocalVariable from '../variables/LocalVariable';
import ExportDefaultVariable from '../variables/ExportDefaultVariable';
import { UNDEFINED_ASSIGNMENT } from '../values';
import ExecutionPathOptions from '../ExecutionPathOptions';

export default class Scope {
constructor ( options = {} ) {
Expand All @@ -26,7 +27,7 @@ export default class Scope {
if ( this.variables[ name ] ) {
const variable = this.variables[ name ];
variable.addDeclaration( identifier );
options.init && variable.bindAssignmentAtPath( [], options.init );
variable.reassignPath( [], ExecutionPathOptions.create() );
} else {
this.variables[ name ] = new LocalVariable( identifier.name, identifier, options.init || UNDEFINED_ASSIGNMENT );
}
Expand Down
4 changes: 2 additions & 2 deletions src/ast/values.js
Expand Up @@ -2,7 +2,7 @@ export const UNKNOWN_VALUE = { toString: () => '[[UNKNOWN]]' };

export const UNKNOWN_ASSIGNMENT = {
type: 'UNKNOWN',
bindAssignmentAtPath: () => {},
reassignPath: () => {},
forEachReturnExpressionWhenCalledAtPath: () => {},
hasEffectsWhenAccessedAtPath: path => path.length > 0,
hasEffectsWhenAssignedAtPath: path => path.length > 0,
Expand All @@ -13,7 +13,7 @@ export const UNKNOWN_ASSIGNMENT = {

export const UNDEFINED_ASSIGNMENT = {
type: 'UNDEFINED',
bindAssignmentAtPath: () => {},
reassignPath: () => {},
forEachReturnExpressionWhenCalledAtPath: () => {},
hasEffectsWhenAccessedAtPath: path => path.length > 0,
hasEffectsWhenAssignedAtPath: path => path.length > 0,
Expand Down
4 changes: 2 additions & 2 deletions src/ast/variables/ArgumentsVariable.js
Expand Up @@ -11,10 +11,10 @@ export default class ArgumentsVariable extends LocalVariable {
this._parameters = parameters;
}

bindAssignmentAtPath ( path, expression, options ) {
reassignPath ( path, options ) {
if ( path.length > 0 ) {
if ( path[ 0 ] >= 0 && this._parameters[ path[ 0 ] ] ) {
this._parameters[ path[ 0 ] ].bindAssignmentAtPath( path.slice( 1 ), expression, options );
this._parameters[ path[ 0 ] ].reassignPath( path.slice( 1 ), options );
}
}
}
Expand Down

0 comments on commit 1042217

Please sign in to comment.