diff --git a/src/ast/Node.js b/src/ast/Node.js index caf3fbead86..29d918ad15e 100644 --- a/src/ast/Node.js +++ b/src/ast/Node.js @@ -20,8 +20,8 @@ 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. @@ -29,7 +29,7 @@ export default class Node { * @param {Node} expression * @param {ExecutionPathOptions} options */ - bindAssignmentAtPath ( path, expression, options ) {} + reassignPath ( path, options ) {} /** * Override to control on which children "bind" is called. diff --git a/src/ast/nodes/ArrayPattern.js b/src/ast/nodes/ArrayPattern.js index 8c6437e4d01..b79b1bb1578 100644 --- a/src/ast/nodes/ArrayPattern.js +++ b/src/ast/nodes/ArrayPattern.js @@ -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 ) { diff --git a/src/ast/nodes/AssignmentExpression.js b/src/ast/nodes/AssignmentExpression.js index 6cab3439f4e..9ec54c8d7cb 100644 --- a/src/ast/nodes/AssignmentExpression.js +++ b/src/ast/nodes/AssignmentExpression.js @@ -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 ) { diff --git a/src/ast/nodes/AssignmentPattern.js b/src/ast/nodes/AssignmentPattern.js index eb3df0e338e..876b06f77ca 100644 --- a/src/ast/nodes/AssignmentPattern.js +++ b/src/ast/nodes/AssignmentPattern.js @@ -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 ) { diff --git a/src/ast/nodes/CallExpression.js b/src/ast/nodes/CallExpression.js index b1ab507a2a5..dcd1d3130e9 100644 --- a/src/ast/nodes/CallExpression.js +++ b/src/ast/nodes/CallExpression.js @@ -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 () { diff --git a/src/ast/nodes/ConditionalExpression.js b/src/ast/nodes/ConditionalExpression.js index 25092c3df1a..4aabe5f0354 100644 --- a/src/ast/nodes/ConditionalExpression.js +++ b/src/ast/nodes/ConditionalExpression.js @@ -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 ) { diff --git a/src/ast/nodes/ForOfStatement.js b/src/ast/nodes/ForOfStatement.js index 683d8a3882a..f7963d8545f 100644 --- a/src/ast/nodes/ForOfStatement.js +++ b/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 ) { diff --git a/src/ast/nodes/Identifier.js b/src/ast/nodes/Identifier.js index 153b4c0e3f4..525de18c1fe 100644 --- a/src/ast/nodes/Identifier.js +++ b/src/ast/nodes/Identifier.js @@ -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 () { diff --git a/src/ast/nodes/LogicalExpression.js b/src/ast/nodes/LogicalExpression.js index 3048df12d88..2e0b3bb05ab 100644 --- a/src/ast/nodes/LogicalExpression.js +++ b/src/ast/nodes/LogicalExpression.js @@ -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 ) { diff --git a/src/ast/nodes/MemberExpression.js b/src/ast/nodes/MemberExpression.js index 3fc704f1ef7..a03b0934616 100644 --- a/src/ast/nodes/MemberExpression.js +++ b/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]*$/; @@ -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 ); } } diff --git a/src/ast/nodes/ObjectExpression.js b/src/ast/nodes/ObjectExpression.js index a662d1b8eec..7d7406b0d94 100644 --- a/src/ast/nodes/ObjectExpression.js +++ b/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 ) { diff --git a/src/ast/nodes/ObjectPattern.js b/src/ast/nodes/ObjectPattern.js index 2aea456d45c..788547ee733 100644 --- a/src/ast/nodes/ObjectPattern.js +++ b/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 ) { diff --git a/src/ast/nodes/Property.js b/src/ast/nodes/Property.js index d71b688e8c0..0b5ba59a6b9 100644 --- a/src/ast/nodes/Property.js +++ b/src/ast/nodes/Property.js @@ -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 ); } } diff --git a/src/ast/nodes/RestElement.js b/src/ast/nodes/RestElement.js index c7cd16e12f7..77b6eb89dc4 100644 --- a/src/ast/nodes/RestElement.js +++ b/src/ast/nodes/RestElement.js @@ -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 ) { diff --git a/src/ast/nodes/UnaryExpression.js b/src/ast/nodes/UnaryExpression.js index de72d77c3ed..942c670f8c2 100644 --- a/src/ast/nodes/UnaryExpression.js +++ b/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 = { @@ -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() ); } } diff --git a/src/ast/nodes/UpdateExpression.js b/src/ast/nodes/UpdateExpression.js index f0ba5d97bd7..283637b02fa 100644 --- a/src/ast/nodes/UpdateExpression.js +++ b/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; diff --git a/src/ast/nodes/VariableDeclaration.js b/src/ast/nodes/VariableDeclaration.js index 676a4820c5e..47b837ca6c9 100644 --- a/src/ast/nodes/VariableDeclaration.js +++ b/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 ) { @@ -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 () { diff --git a/src/ast/nodes/VariableDeclarator.js b/src/ast/nodes/VariableDeclarator.js index 8a7f913f587..7656a0535f0 100644 --- a/src/ast/nodes/VariableDeclarator.js +++ b/src/ast/nodes/VariableDeclarator.js @@ -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 ) { diff --git a/src/ast/nodes/shared/VirtualNumberLiteral.js b/src/ast/nodes/shared/VirtualNumberLiteral.js deleted file mode 100644 index cd04544aeb6..00000000000 --- a/src/ast/nodes/shared/VirtualNumberLiteral.js +++ /dev/null @@ -1,15 +0,0 @@ -import Node from '../../Node'; - -export default class VirtualNumberLiteral extends Node { - hasEffectsWhenAccessedAtPath ( path ) { - return path.length > 1; - } - - hasEffectsWhenAssignedAtPath ( path ) { - return path.length > 1; - } - - toString () { - return '[[VIRTUAL NUMBER]]'; - } -} diff --git a/src/ast/scopes/Scope.js b/src/ast/scopes/Scope.js index df470df17c2..7ea90001b63 100644 --- a/src/ast/scopes/Scope.js +++ b/src/ast/scopes/Scope.js @@ -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 = {} ) { @@ -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 ); } diff --git a/src/ast/values.js b/src/ast/values.js index 2815eb227a4..c685d829306 100644 --- a/src/ast/values.js +++ b/src/ast/values.js @@ -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, @@ -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, diff --git a/src/ast/variables/ArgumentsVariable.js b/src/ast/variables/ArgumentsVariable.js index 44a4f3b1a03..d006ef670b2 100644 --- a/src/ast/variables/ArgumentsVariable.js +++ b/src/ast/variables/ArgumentsVariable.js @@ -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 ); } } } diff --git a/src/ast/variables/LocalVariable.js b/src/ast/variables/LocalVariable.js index 32a98f74a23..3c7c80fc024 100644 --- a/src/ast/variables/LocalVariable.js +++ b/src/ast/variables/LocalVariable.js @@ -1,8 +1,8 @@ import Variable from './Variable'; -import VariableShapeTracker from './VariableShapeTracker'; +import VariableReassignmentTracker from './VariableReassignmentTracker'; -// To avoid exponential performance degradation for complex object manipulations -const MAX_PATH_LENGTH = 2; +// To avoid infinite recursions +const MAX_PATH_DEPTH = 7; export default class LocalVariable extends Variable { constructor ( name, declarator, init ) { @@ -10,34 +10,25 @@ export default class LocalVariable extends Variable { this.isReassigned = false; this.exportName = null; this.declarations = new Set( declarator ? [ declarator ] : null ); - this.boundExpressions = new VariableShapeTracker(); - init && this.boundExpressions.addAtPath( [], init ); + this.boundExpressions = new VariableReassignmentTracker( init ); } addDeclaration ( identifier ) { this.declarations.add( identifier ); } - bindAssignmentAtPath ( path, expression, options ) { - 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 - && expression.bindAssignmentAtPath( subPath, node, options ); - } ); - if ( path.length > 0 ) { - this.boundExpressions.forEachAtPath( path.slice( 0, -1 ), ( relativePath, node ) => - node.bindAssignmentAtPath( [ ...relativePath, ...path.slice( -1 ) ], expression, options ) ); - } else { + reassignPath ( path, options ) { + if ( path.length > MAX_PATH_DEPTH ) return; + if ( path.length === 0 ) { this.isReassigned = true; } + if ( !options.hasNodeBeenAssignedAtPath( path, this ) ) { + this.boundExpressions.reassignPath( path, options.addAssignedNodeAtPath( path, this ) ); + } } forEachReturnExpressionWhenCalledAtPath ( path, callOptions, callback, options ) { - if ( path.length > MAX_PATH_LENGTH ) return; + if ( path.length > MAX_PATH_DEPTH ) return; this.boundExpressions.forEachAtPath( path, ( relativePath, node ) => !options.hasNodeBeenCalledAtPathWithOptions( relativePath, node, callOptions ) && node .forEachReturnExpressionWhenCalledAtPath( relativePath, callOptions, callback, @@ -52,7 +43,7 @@ export default class LocalVariable extends Variable { } hasEffectsWhenAccessedAtPath ( path, options ) { - return path.length > MAX_PATH_LENGTH + return path.length > MAX_PATH_DEPTH || this.boundExpressions.someAtPath( path, ( relativePath, node ) => relativePath.length > 0 && !options.hasNodeBeenAccessedAtPath( relativePath, node ) @@ -61,7 +52,7 @@ export default class LocalVariable extends Variable { hasEffectsWhenAssignedAtPath ( path, options ) { return this.included - || path.length > MAX_PATH_LENGTH + || path.length > MAX_PATH_DEPTH || this.boundExpressions.someAtPath( path, ( relativePath, node ) => relativePath.length > 0 && !options.hasNodeBeenAssignedAtPath( relativePath, node ) && node @@ -70,7 +61,7 @@ export default class LocalVariable extends Variable { } hasEffectsWhenCalledAtPath ( path, callOptions, options ) { - return path.length > MAX_PATH_LENGTH + return path.length > MAX_PATH_DEPTH || (this.included && path.length > 0) || this.boundExpressions.someAtPath( path, ( relativePath, node ) => !options.hasNodeBeenCalledAtPathWithOptions( relativePath, node, callOptions ) && node @@ -86,7 +77,7 @@ export default class LocalVariable extends Variable { } someReturnExpressionWhenCalledAtPath ( path, callOptions, predicateFunction, options ) { - return path.length > MAX_PATH_LENGTH + return path.length > MAX_PATH_DEPTH || (this.included && path.length > 0) || this.boundExpressions.someAtPath( path, ( relativePath, node ) => !options.hasNodeBeenCalledAtPathWithOptions( relativePath, node, callOptions ) && node diff --git a/src/ast/variables/Variable.js b/src/ast/variables/Variable.js index 1c443a494eb..14bc230d5e4 100644 --- a/src/ast/variables/Variable.js +++ b/src/ast/variables/Variable.js @@ -15,13 +15,10 @@ export default class Variable { addReference ( identifier ) {} /** - * This enables variables to know which nodes need to be checked for side-effects when - * e.g. an object path is called or mutated. * @param {String[]} path - * @param {Node} expression * @param {ExecutionPathOptions} options */ - bindAssignmentAtPath ( path, expression, options ) {} + reassignPath ( path, options ) {} /** * @param {String[]} path diff --git a/src/ast/variables/VariableReassignmentTracker.js b/src/ast/variables/VariableReassignmentTracker.js new file mode 100644 index 00000000000..c075dac5783 --- /dev/null +++ b/src/ast/variables/VariableReassignmentTracker.js @@ -0,0 +1,79 @@ +import { UNKNOWN_ASSIGNMENT } from '../values'; + +export const UNKNOWN_KEY = { type: 'UNKNOWN_KEY' }; + +class ReassignedPathTracker { + constructor () { + this._reassigned = false; + this._unknownReassignedSubPath = false; + this._subPaths = new Map(); + } + + isReassigned ( path ) { + if ( path.length === 0 ) { + return this._reassigned; + } + const [ subPath, ...remainingPath ] = path; + return this._unknownReassignedSubPath || ( + this._subPaths.has( subPath ) && this._subPaths.get( subPath ).isReassigned( remainingPath ) + ); + } + + reassignPath ( path ) { + if ( this._reassigned ) return; + if ( path.length === 0 ) { + this._reassigned = true; + } else { + this._reassignSubPath( path ); + } + } + + _reassignSubPath ( path ) { + if ( this._unknownReassignedSubPath ) return; + const [ subPath, ...remainingPath ] = path; + if ( subPath === UNKNOWN_KEY ) { + this._unknownReassignedSubPath = true; + } else { + if ( !this._subPaths.has( subPath ) ) { + this._subPaths.set( subPath, new ReassignedPathTracker() ); + } + this._subPaths.get( subPath ).reassignPath( remainingPath ); + } + } + + someReassignedPath ( path, callback ) { + return this._reassigned + ? callback( path, UNKNOWN_ASSIGNMENT ) + : path.length >= 1 && this._onSubPathIfReassigned( path, callback ); + } + + _onSubPathIfReassigned ( path, callback ) { + const [ subPath, ...remainingPath ] = path; + return this._unknownReassignedSubPath || subPath === UNKNOWN_KEY + ? callback( remainingPath, UNKNOWN_ASSIGNMENT ) + : this._subPaths.has( subPath ) && this._subPaths.get( subPath ).someReassignedPath( remainingPath, callback ); + } +} + +export default class VariableReassignmentTracker { + constructor ( initialExpression ) { + this._initialExpression = initialExpression; + this._reassignedPathTracker = new ReassignedPathTracker(); + } + + reassignPath ( path, options ) { + if ( path.length > 0 ) { + this._initialExpression && this._initialExpression.reassignPath( path, options ); + } + this._reassignedPathTracker.reassignPath( path, options ); + } + + forEachAtPath ( path, callback ) { + this._initialExpression && callback( path, this._initialExpression ); + } + + someAtPath ( path, predicateFunction ) { + return this._reassignedPathTracker.someReassignedPath( path, predicateFunction ) + || (this._initialExpression && predicateFunction( path, this._initialExpression )); + } +} diff --git a/src/ast/variables/VariableShapeTracker.js b/src/ast/variables/VariableShapeTracker.js deleted file mode 100644 index 84e3b8951b3..00000000000 --- a/src/ast/variables/VariableShapeTracker.js +++ /dev/null @@ -1,96 +0,0 @@ -import { UNKNOWN_ASSIGNMENT } from '../values'; - -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 - || (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 ) ) { - this._assignments.set( nextPath, new VariableShapeTracker() ); - } - this._assignments.get( nextPath ).addAtPath( remainingPath, assignment ); - } - } - - forEachAtPath ( path, callback ) { - const [ nextPath, ...remainingPath ] = path; - this._assignments.get( SET_KEY ).forEach( assignment => callback( path, assignment ) ); - 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.forEachAssignedToPath( [], - ( relativePath, assignment ) => callback( [ subPath, ...relativePath ], assignment ) ); - } - } ); - } - } - - 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; - } - return this._assignments.get( nextPath ).hasAtPath( remainingPath, assignment ); - } - } - - someAtPath ( path, predicateFunction ) { - const [ nextPath, ...remainingPath ] = path; - return Array.from( this._assignments.get( SET_KEY ) ).some( assignment => predicateFunction( path, assignment ) ) - || ( - path.length > 0 - && ( - (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' ); - } -} diff --git a/test/form/samples/conditional-expression-paths/_expected/amd.js b/test/form/samples/conditional-expression-paths/_expected/amd.js index d9eda46ad22..08c33da808a 100644 --- a/test/form/samples/conditional-expression-paths/_expected/amd.js +++ b/test/form/samples/conditional-expression-paths/_expected/amd.js @@ -7,7 +7,6 @@ define(function () { 'use strict'; var bar2 = { x: () => console.log( 'effect' ) }; var a2 = (unknownValue ? foo2 : bar2).x.y.z; var b2 = (unknownValue ? foo2 : bar2).x(); - var c2 = (unknownValue ? foo2 : bar2).x = () => console.log( 'effect' ); foo2.x(); bar2.x(); diff --git a/test/form/samples/conditional-expression-paths/_expected/cjs.js b/test/form/samples/conditional-expression-paths/_expected/cjs.js index 9c6e7fb0c25..22e029faef2 100644 --- a/test/form/samples/conditional-expression-paths/_expected/cjs.js +++ b/test/form/samples/conditional-expression-paths/_expected/cjs.js @@ -7,7 +7,6 @@ var foo2 = { x: () => {} }; var bar2 = { x: () => console.log( 'effect' ) }; var a2 = (unknownValue ? foo2 : bar2).x.y.z; var b2 = (unknownValue ? foo2 : bar2).x(); -var c2 = (unknownValue ? foo2 : bar2).x = () => console.log( 'effect' ); foo2.x(); bar2.x(); diff --git a/test/form/samples/conditional-expression-paths/_expected/es.js b/test/form/samples/conditional-expression-paths/_expected/es.js index 2863cebd2b0..849aba2e815 100644 --- a/test/form/samples/conditional-expression-paths/_expected/es.js +++ b/test/form/samples/conditional-expression-paths/_expected/es.js @@ -5,7 +5,6 @@ var foo2 = { x: () => {} }; var bar2 = { x: () => console.log( 'effect' ) }; var a2 = (unknownValue ? foo2 : bar2).x.y.z; var b2 = (unknownValue ? foo2 : bar2).x(); -var c2 = (unknownValue ? foo2 : bar2).x = () => console.log( 'effect' ); foo2.x(); bar2.x(); diff --git a/test/form/samples/conditional-expression-paths/_expected/iife.js b/test/form/samples/conditional-expression-paths/_expected/iife.js index d82e880c3de..010662155e5 100644 --- a/test/form/samples/conditional-expression-paths/_expected/iife.js +++ b/test/form/samples/conditional-expression-paths/_expected/iife.js @@ -8,7 +8,6 @@ var bar2 = { x: () => console.log( 'effect' ) }; var a2 = (unknownValue ? foo2 : bar2).x.y.z; var b2 = (unknownValue ? foo2 : bar2).x(); - var c2 = (unknownValue ? foo2 : bar2).x = () => console.log( 'effect' ); foo2.x(); bar2.x(); diff --git a/test/form/samples/conditional-expression-paths/_expected/umd.js b/test/form/samples/conditional-expression-paths/_expected/umd.js index c5d7483058c..0e29c610ba8 100644 --- a/test/form/samples/conditional-expression-paths/_expected/umd.js +++ b/test/form/samples/conditional-expression-paths/_expected/umd.js @@ -11,7 +11,6 @@ var bar2 = { x: () => console.log( 'effect' ) }; var a2 = (unknownValue ? foo2 : bar2).x.y.z; var b2 = (unknownValue ? foo2 : bar2).x(); - var c2 = (unknownValue ? foo2 : bar2).x = () => console.log( 'effect' ); foo2.x(); bar2.x(); diff --git a/test/form/samples/conditional-expression-paths/main.js b/test/form/samples/conditional-expression-paths/main.js index ef824ad3f96..0215e2d07e2 100644 --- a/test/form/samples/conditional-expression-paths/main.js +++ b/test/form/samples/conditional-expression-paths/main.js @@ -5,7 +5,6 @@ var foo1 = { x: () => {} }; var bar1 = { x: () => {} }; var a1 = (unknownValue ? foo1 : bar1).x.y; var b1 = (unknownValue ? foo1 : bar1).x(); -var c1 = (unknownValue ? foo1 : bar1).x = () => {}; foo1.x(); bar1.x(); @@ -14,7 +13,6 @@ var foo2 = { x: () => {} }; var bar2 = { x: () => console.log( 'effect' ) }; var a2 = (unknownValue ? foo2 : bar2).x.y.z; var b2 = (unknownValue ? foo2 : bar2).x(); -var c2 = (unknownValue ? foo2 : bar2).x = () => console.log( 'effect' ); foo2.x(); bar2.x(); diff --git a/test/form/samples/cyclic-assignments/_config.js b/test/form/samples/cyclic-assignments/_config.js deleted file mode 100644 index 31a5b6a2a7b..00000000000 --- a/test/form/samples/cyclic-assignments/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - description: 'properly remove cyclic assignments without side-effects' -}; diff --git a/test/form/samples/cyclic-assignments/_expected/amd.js b/test/form/samples/cyclic-assignments/_expected/amd.js deleted file mode 100644 index f9f8229aa40..00000000000 --- a/test/form/samples/cyclic-assignments/_expected/amd.js +++ /dev/null @@ -1,5 +0,0 @@ -define(function () { 'use strict'; - - - -}); diff --git a/test/form/samples/cyclic-assignments/_expected/cjs.js b/test/form/samples/cyclic-assignments/_expected/cjs.js deleted file mode 100644 index eb109abbed0..00000000000 --- a/test/form/samples/cyclic-assignments/_expected/cjs.js +++ /dev/null @@ -1,2 +0,0 @@ -'use strict'; - diff --git a/test/form/samples/cyclic-assignments/_expected/es.js b/test/form/samples/cyclic-assignments/_expected/es.js deleted file mode 100644 index 8b137891791..00000000000 --- a/test/form/samples/cyclic-assignments/_expected/es.js +++ /dev/null @@ -1 +0,0 @@ - diff --git a/test/form/samples/cyclic-assignments/_expected/iife.js b/test/form/samples/cyclic-assignments/_expected/iife.js deleted file mode 100644 index 43ef5426880..00000000000 --- a/test/form/samples/cyclic-assignments/_expected/iife.js +++ /dev/null @@ -1,6 +0,0 @@ -(function () { - 'use strict'; - - - -}()); diff --git a/test/form/samples/cyclic-assignments/_expected/umd.js b/test/form/samples/cyclic-assignments/_expected/umd.js deleted file mode 100644 index 07ce27e42f1..00000000000 --- a/test/form/samples/cyclic-assignments/_expected/umd.js +++ /dev/null @@ -1,9 +0,0 @@ -(function (global, factory) { - typeof exports === 'object' && typeof module !== 'undefined' ? factory() : - typeof define === 'function' && define.amd ? define(factory) : - (factory()); -}(this, (function () { 'use strict'; - - - -}))); diff --git a/test/form/samples/side-effects-getters-and-setters/_expected/amd.js b/test/form/samples/side-effects-getters-and-setters/_expected/amd.js index 797e7c4bd43..aaaa2022694 100644 --- a/test/form/samples/side-effects-getters-and-setters/_expected/amd.js +++ b/test/form/samples/side-effects-getters-and-setters/_expected/amd.js @@ -11,6 +11,7 @@ define(function () { 'use strict'; }; const retained1b = retained1a.effect; + const retained1c = retained1a[ 'eff' + 'ect' ]; const retained3 = { set effect ( value ) { @@ -20,6 +21,14 @@ define(function () { 'use strict'; retained3.effect = 'retained'; + const retained4 = { + set effect ( value ) { + console.log( value ); + } + }; + + retained4[ 'eff' + 'ect' ] = 'retained'; + const retained7 = { foo: () => {}, get foo () { diff --git a/test/form/samples/side-effects-getters-and-setters/_expected/cjs.js b/test/form/samples/side-effects-getters-and-setters/_expected/cjs.js index 9f78dba83d2..9eb023475b9 100644 --- a/test/form/samples/side-effects-getters-and-setters/_expected/cjs.js +++ b/test/form/samples/side-effects-getters-and-setters/_expected/cjs.js @@ -11,6 +11,7 @@ const retained1a = { }; const retained1b = retained1a.effect; +const retained1c = retained1a[ 'eff' + 'ect' ]; const retained3 = { set effect ( value ) { @@ -20,6 +21,14 @@ const retained3 = { retained3.effect = 'retained'; +const retained4 = { + set effect ( value ) { + console.log( value ); + } +}; + +retained4[ 'eff' + 'ect' ] = 'retained'; + const retained7 = { foo: () => {}, get foo () { diff --git a/test/form/samples/side-effects-getters-and-setters/_expected/es.js b/test/form/samples/side-effects-getters-and-setters/_expected/es.js index ba3a271ce36..8362292d56c 100644 --- a/test/form/samples/side-effects-getters-and-setters/_expected/es.js +++ b/test/form/samples/side-effects-getters-and-setters/_expected/es.js @@ -9,6 +9,7 @@ const retained1a = { }; const retained1b = retained1a.effect; +const retained1c = retained1a[ 'eff' + 'ect' ]; const retained3 = { set effect ( value ) { @@ -18,6 +19,14 @@ const retained3 = { retained3.effect = 'retained'; +const retained4 = { + set effect ( value ) { + console.log( value ); + } +}; + +retained4[ 'eff' + 'ect' ] = 'retained'; + const retained7 = { foo: () => {}, get foo () { diff --git a/test/form/samples/side-effects-getters-and-setters/_expected/iife.js b/test/form/samples/side-effects-getters-and-setters/_expected/iife.js index 063390ac239..aec39303724 100644 --- a/test/form/samples/side-effects-getters-and-setters/_expected/iife.js +++ b/test/form/samples/side-effects-getters-and-setters/_expected/iife.js @@ -12,6 +12,7 @@ }; const retained1b = retained1a.effect; + const retained1c = retained1a[ 'eff' + 'ect' ]; const retained3 = { set effect ( value ) { @@ -21,6 +22,14 @@ retained3.effect = 'retained'; + const retained4 = { + set effect ( value ) { + console.log( value ); + } + }; + + retained4[ 'eff' + 'ect' ] = 'retained'; + const retained7 = { foo: () => {}, get foo () { diff --git a/test/form/samples/side-effects-getters-and-setters/_expected/umd.js b/test/form/samples/side-effects-getters-and-setters/_expected/umd.js index d1ab058c0d2..f9d03f83955 100644 --- a/test/form/samples/side-effects-getters-and-setters/_expected/umd.js +++ b/test/form/samples/side-effects-getters-and-setters/_expected/umd.js @@ -15,6 +15,7 @@ }; const retained1b = retained1a.effect; + const retained1c = retained1a[ 'eff' + 'ect' ]; const retained3 = { set effect ( value ) { @@ -24,6 +25,14 @@ retained3.effect = 'retained'; + const retained4 = { + set effect ( value ) { + console.log( value ); + } + }; + + retained4[ 'eff' + 'ect' ] = 'retained'; + const retained7 = { foo: () => {}, get foo () { diff --git a/test/form/samples/side-effects-getters-and-setters/main.js b/test/form/samples/side-effects-getters-and-setters/main.js index bbe91ad00bb..b3ce3d4a9b6 100644 --- a/test/form/samples/side-effects-getters-and-setters/main.js +++ b/test/form/samples/side-effects-getters-and-setters/main.js @@ -10,6 +10,7 @@ const retained1a = { const removed1 = retained1a.noEffect; const retained1b = retained1a.effect; +const retained1c = retained1a[ 'eff' + 'ect' ]; const removed2a = { get shadowedEffect () { @@ -33,6 +34,14 @@ const retained3 = { retained3.effect = 'retained'; +const retained4 = { + set effect ( value ) { + console.log( value ); + } +}; + +retained4[ 'eff' + 'ect' ] = 'retained'; + const removed5 = { set noEffect ( value ) { const x = value; diff --git a/test/form/samples/side-effects-object-literal-calls/main.js b/test/form/samples/side-effects-object-literal-calls/main.js index 82c05d7d7ad..5f7c1d32a3d 100644 --- a/test/form/samples/side-effects-object-literal-calls/main.js +++ b/test/form/samples/side-effects-object-literal-calls/main.js @@ -2,7 +2,6 @@ const removed1 = { x: () => {} }; removed1.x(); const removed2 = { x: { y: () => {} } }; -removed2.x.y = function () {}; removed2.x.y(); const retained1 = { x: () => {} }; diff --git a/test/form/samples/side-effects-object-literal-mutation/_expected/amd.js b/test/form/samples/side-effects-object-literal-mutation/_expected/amd.js index c8d2cca8be5..7d706cf2c6f 100644 --- a/test/form/samples/side-effects-object-literal-mutation/_expected/amd.js +++ b/test/form/samples/side-effects-object-literal-mutation/_expected/amd.js @@ -11,12 +11,10 @@ define(['exports'], function (exports) { 'use strict'; delete retained3.x; retained3.x.y = 2; - const retained4 = { x: {} }; - retained4.x = undefined; + const retained4 = { x: undefined }; retained4.x.y = 2; - const retained5 = { x: {} }; - retained5.x = null; + const retained5 = { x: null }; retained5.x.y = 2; const retained6 = { x: { y: {} } }; @@ -24,8 +22,7 @@ define(['exports'], function (exports) { 'use strict'; retained6.x.y.z = 3; const retained7 = { x: { y: globalVar } }; - const retained8 = { x: { y: {} } }; - retained8.x = retained7.x; + const retained8 = { x: retained7.x }; retained8.x.y.z = 3; exports.retained1 = retained1; diff --git a/test/form/samples/side-effects-object-literal-mutation/_expected/cjs.js b/test/form/samples/side-effects-object-literal-mutation/_expected/cjs.js index fba914234d2..0629a3d2883 100644 --- a/test/form/samples/side-effects-object-literal-mutation/_expected/cjs.js +++ b/test/form/samples/side-effects-object-literal-mutation/_expected/cjs.js @@ -13,12 +13,10 @@ const retained3 = { x: {} }; delete retained3.x; retained3.x.y = 2; -const retained4 = { x: {} }; -retained4.x = undefined; +const retained4 = { x: undefined }; retained4.x.y = 2; -const retained5 = { x: {} }; -retained5.x = null; +const retained5 = { x: null }; retained5.x.y = 2; const retained6 = { x: { y: {} } }; @@ -26,8 +24,7 @@ retained6.x = {}; retained6.x.y.z = 3; const retained7 = { x: { y: globalVar } }; -const retained8 = { x: { y: {} } }; -retained8.x = retained7.x; +const retained8 = { x: retained7.x }; retained8.x.y.z = 3; exports.retained1 = retained1; diff --git a/test/form/samples/side-effects-object-literal-mutation/_expected/es.js b/test/form/samples/side-effects-object-literal-mutation/_expected/es.js index a65f9857c49..393144c9da4 100644 --- a/test/form/samples/side-effects-object-literal-mutation/_expected/es.js +++ b/test/form/samples/side-effects-object-literal-mutation/_expected/es.js @@ -9,12 +9,10 @@ const retained3 = { x: {} }; delete retained3.x; retained3.x.y = 2; -const retained4 = { x: {} }; -retained4.x = undefined; +const retained4 = { x: undefined }; retained4.x.y = 2; -const retained5 = { x: {} }; -retained5.x = null; +const retained5 = { x: null }; retained5.x.y = 2; const retained6 = { x: { y: {} } }; @@ -22,8 +20,7 @@ retained6.x = {}; retained6.x.y.z = 3; const retained7 = { x: { y: globalVar } }; -const retained8 = { x: { y: {} } }; -retained8.x = retained7.x; +const retained8 = { x: retained7.x }; retained8.x.y.z = 3; export { retained1 }; diff --git a/test/form/samples/side-effects-object-literal-mutation/_expected/iife.js b/test/form/samples/side-effects-object-literal-mutation/_expected/iife.js index b3747927b47..64a2bffe83d 100644 --- a/test/form/samples/side-effects-object-literal-mutation/_expected/iife.js +++ b/test/form/samples/side-effects-object-literal-mutation/_expected/iife.js @@ -12,12 +12,10 @@ var bundle = (function (exports) { delete retained3.x; retained3.x.y = 2; - const retained4 = { x: {} }; - retained4.x = undefined; + const retained4 = { x: undefined }; retained4.x.y = 2; - const retained5 = { x: {} }; - retained5.x = null; + const retained5 = { x: null }; retained5.x.y = 2; const retained6 = { x: { y: {} } }; @@ -25,8 +23,7 @@ var bundle = (function (exports) { retained6.x.y.z = 3; const retained7 = { x: { y: globalVar } }; - const retained8 = { x: { y: {} } }; - retained8.x = retained7.x; + const retained8 = { x: retained7.x }; retained8.x.y.z = 3; exports.retained1 = retained1; diff --git a/test/form/samples/side-effects-object-literal-mutation/_expected/umd.js b/test/form/samples/side-effects-object-literal-mutation/_expected/umd.js index a73414962fe..1ed1a9cb205 100644 --- a/test/form/samples/side-effects-object-literal-mutation/_expected/umd.js +++ b/test/form/samples/side-effects-object-literal-mutation/_expected/umd.js @@ -15,12 +15,10 @@ delete retained3.x; retained3.x.y = 2; - const retained4 = { x: {} }; - retained4.x = undefined; + const retained4 = { x: undefined }; retained4.x.y = 2; - const retained5 = { x: {} }; - retained5.x = null; + const retained5 = { x: null }; retained5.x.y = 2; const retained6 = { x: { y: {} } }; @@ -28,8 +26,7 @@ retained6.x.y.z = 3; const retained7 = { x: { y: globalVar } }; - const retained8 = { x: { y: {} } }; - retained8.x = retained7.x; + const retained8 = { x: retained7.x }; retained8.x.y.z = 3; exports.retained1 = retained1; diff --git a/test/form/samples/side-effects-object-literal-mutation/main.js b/test/form/samples/side-effects-object-literal-mutation/main.js index d8017609bf5..e5ae1d89cac 100644 --- a/test/form/samples/side-effects-object-literal-mutation/main.js +++ b/test/form/samples/side-effects-object-literal-mutation/main.js @@ -13,23 +13,26 @@ const retained3 = { x: {} }; delete retained3.x; retained3.x.y = 2; -const retained4 = { x: {} }; -retained4.x = undefined; +const retained4 = { x: undefined }; retained4.x.y = 2; -const retained5 = { x: {} }; -retained5.x = null; +const retained5 = { x: null }; retained5.x.y = 2; -const removed2 = { x: {} }; -removed2.x = 99; +const removed2 = { x: 99 }; removed2.x.y = 2; +const removed3 = { x: { y: {} } }; +removed3.x.y.z = 3; + const retained6 = { x: { y: {} } }; retained6.x = {}; retained6.x.y.z = 3; const retained7 = { x: { y: globalVar } }; -const retained8 = { x: { y: {} } }; -retained8.x = retained7.x; +const retained8 = { x: retained7.x }; retained8.x.y.z = 3; + +const removed4 = { a: { x: { y: globalVar } } }; +const removed5 = { x: removed4.a.x }; +removed5.x.y = 2; diff --git a/test/form/samples/side-effects-reassignment/_expected/amd.js b/test/form/samples/side-effects-reassignment/_expected/amd.js index 8413149572d..1056781e7d0 100644 --- a/test/form/samples/side-effects-reassignment/_expected/amd.js +++ b/test/form/samples/side-effects-reassignment/_expected/amd.js @@ -1,12 +1,10 @@ define(function () { 'use strict'; - var effect = () => {}; - effect = function() { + var effect = function() { console.log('effect'); }; - var alsoEffect = () => {}; - alsoEffect = effect; + var alsoEffect = effect; alsoEffect(); }); diff --git a/test/form/samples/side-effects-reassignment/_expected/cjs.js b/test/form/samples/side-effects-reassignment/_expected/cjs.js index e246be25b57..a6efcd3596e 100644 --- a/test/form/samples/side-effects-reassignment/_expected/cjs.js +++ b/test/form/samples/side-effects-reassignment/_expected/cjs.js @@ -1,10 +1,8 @@ 'use strict'; -var effect = () => {}; -effect = function() { +var effect = function() { console.log('effect'); }; -var alsoEffect = () => {}; -alsoEffect = effect; +var alsoEffect = effect; alsoEffect(); diff --git a/test/form/samples/side-effects-reassignment/_expected/es.js b/test/form/samples/side-effects-reassignment/_expected/es.js index 634177515de..eb567ddc6ab 100644 --- a/test/form/samples/side-effects-reassignment/_expected/es.js +++ b/test/form/samples/side-effects-reassignment/_expected/es.js @@ -1,8 +1,6 @@ -var effect = () => {}; -effect = function() { +var effect = function() { console.log('effect'); }; -var alsoEffect = () => {}; -alsoEffect = effect; +var alsoEffect = effect; alsoEffect(); diff --git a/test/form/samples/side-effects-reassignment/_expected/iife.js b/test/form/samples/side-effects-reassignment/_expected/iife.js index e09f12b1360..2d21989b418 100644 --- a/test/form/samples/side-effects-reassignment/_expected/iife.js +++ b/test/form/samples/side-effects-reassignment/_expected/iife.js @@ -1,13 +1,11 @@ (function () { 'use strict'; - var effect = () => {}; - effect = function() { + var effect = function() { console.log('effect'); }; - var alsoEffect = () => {}; - alsoEffect = effect; + var alsoEffect = effect; alsoEffect(); }()); diff --git a/test/form/samples/side-effects-reassignment/_expected/umd.js b/test/form/samples/side-effects-reassignment/_expected/umd.js index f6450848e1f..da0c3fe9ef8 100644 --- a/test/form/samples/side-effects-reassignment/_expected/umd.js +++ b/test/form/samples/side-effects-reassignment/_expected/umd.js @@ -4,13 +4,11 @@ (factory()); }(this, (function () { 'use strict'; - var effect = () => {}; - effect = function() { + var effect = function() { console.log('effect'); }; - var alsoEffect = () => {}; - alsoEffect = effect; + var alsoEffect = effect; alsoEffect(); }))); diff --git a/test/form/samples/side-effects-reassignment/main.js b/test/form/samples/side-effects-reassignment/main.js index 1013e127833..602206f9ba5 100644 --- a/test/form/samples/side-effects-reassignment/main.js +++ b/test/form/samples/side-effects-reassignment/main.js @@ -7,20 +7,16 @@ foo = function () { foo = ['foo']; foo = undefined; -var noEffect = () => {}; -noEffect = function(a) { +var noEffect = function(a) { a = 'reassigned parameter'; }; -var stillNoEffect = () => {}; -stillNoEffect = noEffect; +var stillNoEffect = noEffect; stillNoEffect(); -var effect = () => {}; -effect = function() { +var effect = function() { console.log('effect'); }; -var alsoEffect = () => {}; -alsoEffect = effect; +var alsoEffect = effect; alsoEffect(); diff --git a/test/function/samples/cyclic-assignments/_config.js b/test/function/samples/cyclic-assignments/_config.js new file mode 100644 index 00000000000..ca96493478d --- /dev/null +++ b/test/function/samples/cyclic-assignments/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'does not fail for cyclic assignments' +}; diff --git a/test/form/samples/cyclic-assignments/main.js b/test/function/samples/cyclic-assignments/main.js similarity index 100% rename from test/form/samples/cyclic-assignments/main.js rename to test/function/samples/cyclic-assignments/main.js