From ba14bd755c17fc59f6cb207a770ed12a5a9c156a Mon Sep 17 00:00:00 2001 From: Lukas Taegert-Atkinson Date: Mon, 2 Sep 2019 06:52:00 +0200 Subject: [PATCH 01/35] Switch to a mutable context TODO: recursion prevention TODO: replace "immutable" in the immutable entity path tracker --- src/ast/CallOptions.ts | 10 - src/ast/Entity.ts | 4 +- src/ast/ExecutionContext.ts | 18 ++ src/ast/ExecutionPathOptions.ts | 200 ------------------ src/ast/nodes/ArrayExpression.ts | 6 +- src/ast/nodes/ArrayPattern.ts | 6 +- src/ast/nodes/ArrowFunctionExpression.ts | 18 +- src/ast/nodes/AssignmentExpression.ts | 14 +- src/ast/nodes/AssignmentPattern.ts | 6 +- src/ast/nodes/AwaitExpression.ts | 6 +- src/ast/nodes/BinaryExpression.ts | 11 +- src/ast/nodes/BlockStatement.ts | 6 +- src/ast/nodes/BreakStatement.ts | 10 +- src/ast/nodes/CallExpression.ts | 46 ++-- src/ast/nodes/ClassBody.ts | 10 +- src/ast/nodes/ConditionalExpression.ts | 34 +-- src/ast/nodes/DoWhileStatement.ts | 13 +- src/ast/nodes/ExportNamedDeclaration.ts | 6 +- src/ast/nodes/ForInStatement.ts | 21 +- src/ast/nodes/ForStatement.ts | 21 +- src/ast/nodes/Identifier.ts | 14 +- src/ast/nodes/IfStatement.ts | 14 +- src/ast/nodes/LabeledStatement.ts | 12 +- src/ast/nodes/Literal.ts | 6 +- src/ast/nodes/LogicalExpression.ts | 32 +-- src/ast/nodes/MemberExpression.ts | 32 ++- src/ast/nodes/MethodDefinition.ts | 10 +- src/ast/nodes/NewExpression.ts | 22 +- src/ast/nodes/ObjectExpression.ts | 18 +- src/ast/nodes/ObjectPattern.ts | 6 +- src/ast/nodes/Program.ts | 6 +- src/ast/nodes/Property.ts | 59 +++--- src/ast/nodes/RestElement.ts | 6 +- src/ast/nodes/ReturnStatement.ts | 8 +- src/ast/nodes/SequenceExpression.ts | 18 +- src/ast/nodes/SwitchStatement.ts | 10 +- src/ast/nodes/TaggedTemplateExpression.ts | 22 +- src/ast/nodes/TemplateElement.ts | 4 +- src/ast/nodes/ThisExpression.ts | 10 +- src/ast/nodes/ThrowStatement.ts | 4 +- src/ast/nodes/TryStatement.ts | 8 +- src/ast/nodes/UnaryExpression.ts | 10 +- src/ast/nodes/UnknownNode.ts | 4 +- src/ast/nodes/UpdateExpression.ts | 10 +- src/ast/nodes/VariableDeclaration.ts | 8 +- src/ast/nodes/WhileStatement.ts | 13 +- src/ast/nodes/YieldExpression.ts | 8 +- src/ast/nodes/shared/ClassNode.ts | 12 +- src/ast/nodes/shared/Expression.ts | 6 +- src/ast/nodes/shared/FunctionNode.ts | 36 ++-- src/ast/nodes/shared/MultiExpression.ts | 14 +- src/ast/nodes/shared/Node.ts | 20 +- src/ast/scopes/FunctionScope.ts | 13 -- src/ast/values.ts | 64 +++--- src/ast/variables/LocalVariable.ts | 30 +-- src/ast/variables/ThisVariable.ts | 28 +-- src/ast/variables/Variable.ts | 8 +- test/form/index.js | 2 +- .../{_expected/es.js => _expected.js} | 0 .../absolute-path-resolver/_expected/amd.js | 10 - .../absolute-path-resolver/_expected/cjs.js | 8 - .../absolute-path-resolver/_expected/iife.js | 11 - .../_expected/system.js | 15 -- .../absolute-path-resolver/_expected/umd.js | 13 -- .../{_expected/es.js => _expected.js} | 0 .../_expected/amd.js | 33 --- .../_expected/cjs.js | 31 --- .../_expected/iife.js | 34 --- .../_expected/system.js | 38 ---- .../_expected/umd.js | 36 ---- .../{_expected/es.js => _expected.js} | 0 .../_expected/amd.js | 14 -- .../_expected/cjs.js | 12 -- .../_expected/iife.js | 15 -- .../_expected/system.js | 19 -- .../_expected/umd.js | 17 -- .../samples/assignment-to-global/_expected.js | 1 - .../{_expected/es.js => _expected.js} | 0 .../async-function-unused/_expected/amd.js | 9 - .../async-function-unused/_expected/cjs.js | 7 - .../async-function-unused/_expected/iife.js | 10 - .../async-function-unused/_expected/system.js | 14 -- .../async-function-unused/_expected/umd.js | 12 -- .../{_expected/es.js => _expected.js} | 0 .../samples/block-comments/_expected/amd.js | 19 -- .../samples/block-comments/_expected/cjs.js | 17 -- .../samples/block-comments/_expected/iife.js | 20 -- .../block-comments/_expected/system.js | 24 --- .../samples/block-comments/_expected/umd.js | 22 -- test/form/samples/recursive-calls/_config.js | 1 + .../_config.js | 1 + .../samples/self-calling-function/_config.js | 1 + .../samples/side-effect-q/_expected/system.js | 10 - .../samples/side-effect-r/_expected/umd.js | 8 - .../_expected/amd.js | 5 - test/form/samples/supports-core-js/_config.js | 1 + .../form/samples/supports-es5-shim/_config.js | 1 + .../form/samples/supports-es6-shim/_config.js | 1 + .../_expected/cjs.js | 2 - .../_expected/iife.js | 6 - 100 files changed, 457 insertions(+), 1114 deletions(-) create mode 100644 src/ast/ExecutionContext.ts delete mode 100644 src/ast/ExecutionPathOptions.ts rename test/form/samples/absolute-path-resolver/{_expected/es.js => _expected.js} (100%) delete mode 100644 test/form/samples/absolute-path-resolver/_expected/amd.js delete mode 100644 test/form/samples/absolute-path-resolver/_expected/cjs.js delete mode 100644 test/form/samples/absolute-path-resolver/_expected/iife.js delete mode 100644 test/form/samples/absolute-path-resolver/_expected/system.js delete mode 100644 test/form/samples/absolute-path-resolver/_expected/umd.js rename test/form/samples/arrow-function-call-parameters/{_expected/es.js => _expected.js} (100%) delete mode 100644 test/form/samples/arrow-function-call-parameters/_expected/amd.js delete mode 100644 test/form/samples/arrow-function-call-parameters/_expected/cjs.js delete mode 100644 test/form/samples/arrow-function-call-parameters/_expected/iife.js delete mode 100644 test/form/samples/arrow-function-call-parameters/_expected/system.js delete mode 100644 test/form/samples/arrow-function-call-parameters/_expected/umd.js rename test/form/samples/arrow-function-return-values/{_expected/es.js => _expected.js} (100%) delete mode 100644 test/form/samples/arrow-function-return-values/_expected/amd.js delete mode 100644 test/form/samples/arrow-function-return-values/_expected/cjs.js delete mode 100644 test/form/samples/arrow-function-return-values/_expected/iife.js delete mode 100644 test/form/samples/arrow-function-return-values/_expected/system.js delete mode 100644 test/form/samples/arrow-function-return-values/_expected/umd.js delete mode 100644 test/form/samples/assignment-to-global/_expected.js rename test/form/samples/async-function-unused/{_expected/es.js => _expected.js} (100%) delete mode 100644 test/form/samples/async-function-unused/_expected/amd.js delete mode 100644 test/form/samples/async-function-unused/_expected/cjs.js delete mode 100644 test/form/samples/async-function-unused/_expected/iife.js delete mode 100644 test/form/samples/async-function-unused/_expected/system.js delete mode 100644 test/form/samples/async-function-unused/_expected/umd.js rename test/form/samples/block-comments/{_expected/es.js => _expected.js} (100%) delete mode 100644 test/form/samples/block-comments/_expected/amd.js delete mode 100644 test/form/samples/block-comments/_expected/cjs.js delete mode 100644 test/form/samples/block-comments/_expected/iife.js delete mode 100644 test/form/samples/block-comments/_expected/system.js delete mode 100644 test/form/samples/block-comments/_expected/umd.js delete mode 100644 test/form/samples/side-effect-q/_expected/system.js delete mode 100644 test/form/samples/side-effect-r/_expected/umd.js delete mode 100644 test/form/samples/side-effect-with-plusplus-expression/_expected/amd.js delete mode 100644 test/form/samples/tree-shake-curried-functions/_expected/cjs.js delete mode 100644 test/form/samples/tree-shake-curried-functions/_expected/iife.js diff --git a/src/ast/CallOptions.ts b/src/ast/CallOptions.ts index 6a29388fca2..c33e7c50596 100644 --- a/src/ast/CallOptions.ts +++ b/src/ast/CallOptions.ts @@ -1,15 +1,5 @@ -import CallExpression from './nodes/CallExpression'; -import NewExpression from './nodes/NewExpression'; -import Property from './nodes/Property'; import { ExpressionEntity } from './nodes/shared/Expression'; import SpreadElement from './nodes/SpreadElement'; -import TaggedTemplateExpression from './nodes/TaggedTemplateExpression'; - -export type CallExpressionType = - | TaggedTemplateExpression - | CallExpression - | NewExpression - | Property; export interface CallCreateOptions { args?: (ExpressionEntity | SpreadElement)[]; diff --git a/src/ast/Entity.ts b/src/ast/Entity.ts index e67b566d614..3d228ae104c 100644 --- a/src/ast/Entity.ts +++ b/src/ast/Entity.ts @@ -1,4 +1,4 @@ -import { ExecutionPathOptions } from './ExecutionPathOptions'; +import { ExecutionContext } from './ExecutionContext'; import { ObjectPath } from './values'; export interface Entity { @@ -13,5 +13,5 @@ export interface WritableEntity extends Entity { * expression of this node is reassigned as well. */ deoptimizePath(path: ObjectPath): void; - hasEffectsWhenAssignedAtPath(path: ObjectPath, options: ExecutionPathOptions): boolean; + hasEffectsWhenAssignedAtPath(path: ObjectPath, execution: ExecutionContext): boolean; } diff --git a/src/ast/ExecutionContext.ts b/src/ast/ExecutionContext.ts new file mode 100644 index 00000000000..5463397f063 --- /dev/null +++ b/src/ast/ExecutionContext.ts @@ -0,0 +1,18 @@ +import { ExpressionEntity } from './nodes/shared/Expression'; +import ThisVariable from './variables/ThisVariable'; + +export interface ExecutionContext { + ignoreBreakStatements: boolean; + ignoredLabels: Set; + ignoreReturnAwaitYield: boolean; + replacedVariableInits: Map; +} + +export function createExecutionContext(): ExecutionContext { + return { + ignoreBreakStatements: false, + ignoredLabels: new Set(), + ignoreReturnAwaitYield: false, + replacedVariableInits: new Map() + }; +} diff --git a/src/ast/ExecutionPathOptions.ts b/src/ast/ExecutionPathOptions.ts deleted file mode 100644 index 5402bd6f882..00000000000 --- a/src/ast/ExecutionPathOptions.ts +++ /dev/null @@ -1,200 +0,0 @@ -import Immutable from 'immutable'; -import CallOptions from './CallOptions'; -import { Entity, WritableEntity } from './Entity'; -import CallExpression from './nodes/CallExpression'; -import Property from './nodes/Property'; -import { ExpressionEntity } from './nodes/shared/Expression'; -import { ObjectPath } from './values'; -import ThisVariable from './variables/ThisVariable'; - -export enum OptionTypes { - IGNORED_LABELS, - ACCESSED_NODES, - ASSIGNED_NODES, - IGNORE_BREAK_STATEMENTS, - IGNORE_RETURN_AWAIT_YIELD, - NODES_CALLED_AT_PATH_WITH_OPTIONS, - REPLACED_VARIABLE_INITS, - RETURN_EXPRESSIONS_ACCESSED_AT_PATH, - RETURN_EXPRESSIONS_ASSIGNED_AT_PATH, - RETURN_EXPRESSIONS_CALLED_AT_PATH -} - -interface RESULT_KEY {} -const RESULT_KEY: RESULT_KEY = {}; -type KeyTypes = OptionTypes | Entity | RESULT_KEY; - -export class ExecutionPathOptions { - static create() { - return new this(Immutable.Map()); - } - - private optionValues: Immutable.Map; - - private constructor( - optionValues: Immutable.Map - ) { - this.optionValues = optionValues; - } - - addAccessedNodeAtPath(path: ObjectPath, node: ExpressionEntity) { - return this.setIn([OptionTypes.ACCESSED_NODES, node, ...path, RESULT_KEY], true); - } - - addAccessedReturnExpressionAtPath(path: ObjectPath, callExpression: CallExpression | Property) { - return this.setIn( - [OptionTypes.RETURN_EXPRESSIONS_ACCESSED_AT_PATH, callExpression, ...path, RESULT_KEY], - true - ); - } - - addAssignedNodeAtPath(path: ObjectPath, node: WritableEntity) { - return this.setIn([OptionTypes.ASSIGNED_NODES, node, ...path, RESULT_KEY], true); - } - - addAssignedReturnExpressionAtPath(path: ObjectPath, callExpression: CallExpression | Property) { - return this.setIn( - [OptionTypes.RETURN_EXPRESSIONS_ASSIGNED_AT_PATH, callExpression, ...path, RESULT_KEY], - true - ); - } - - addCalledNodeAtPathWithOptions( - path: ObjectPath, - node: ExpressionEntity, - callOptions: CallOptions - ) { - return this.setIn( - [OptionTypes.NODES_CALLED_AT_PATH_WITH_OPTIONS, node, ...path, RESULT_KEY, callOptions], - true - ); - } - - addCalledReturnExpressionAtPath(path: ObjectPath, callExpression: CallExpression | Property) { - return this.setIn( - [OptionTypes.RETURN_EXPRESSIONS_CALLED_AT_PATH, callExpression, ...path, RESULT_KEY], - true - ); - } - - getHasEffectsWhenCalledOptions() { - return this.setIgnoreReturnAwaitYield() - .setIgnoreBreakStatements(false) - .setIgnoreNoLabels(); - } - - getReplacedVariableInit(variable: ThisVariable): ExpressionEntity { - return this.optionValues.getIn([OptionTypes.REPLACED_VARIABLE_INITS, variable]); - } - - hasNodeBeenAccessedAtPath(path: ObjectPath, node: ExpressionEntity): boolean { - return this.optionValues.getIn([OptionTypes.ACCESSED_NODES, node, ...path, RESULT_KEY]); - } - - hasNodeBeenAssignedAtPath(path: ObjectPath, node: WritableEntity): boolean { - return this.optionValues.getIn([OptionTypes.ASSIGNED_NODES, node, ...path, RESULT_KEY]); - } - - hasNodeBeenCalledAtPathWithOptions( - path: ObjectPath, - node: ExpressionEntity, - callOptions: CallOptions - ): boolean { - const previousCallOptions = this.optionValues.getIn([ - OptionTypes.NODES_CALLED_AT_PATH_WITH_OPTIONS, - node, - ...path, - RESULT_KEY - ]); - return ( - previousCallOptions && - previousCallOptions.find((_: any, otherCallOptions: CallOptions) => - otherCallOptions.equals(callOptions) - ) - ); - } - - hasReturnExpressionBeenAccessedAtPath( - path: ObjectPath, - callExpression: CallExpression | Property - ): boolean { - return this.optionValues.getIn([ - OptionTypes.RETURN_EXPRESSIONS_ACCESSED_AT_PATH, - callExpression, - ...path, - RESULT_KEY - ]); - } - - hasReturnExpressionBeenAssignedAtPath( - path: ObjectPath, - callExpression: CallExpression | Property - ): boolean { - return this.optionValues.getIn([ - OptionTypes.RETURN_EXPRESSIONS_ASSIGNED_AT_PATH, - callExpression, - ...path, - RESULT_KEY - ]); - } - - hasReturnExpressionBeenCalledAtPath( - path: ObjectPath, - callExpression: CallExpression | Property - ): boolean { - return this.optionValues.getIn([ - OptionTypes.RETURN_EXPRESSIONS_CALLED_AT_PATH, - callExpression, - ...path, - RESULT_KEY - ]); - } - - ignoreBreakStatements() { - return this.get(OptionTypes.IGNORE_BREAK_STATEMENTS); - } - - ignoreLabel(labelName: string) { - return this.optionValues.getIn([OptionTypes.IGNORED_LABELS, labelName]); - } - - ignoreReturnAwaitYield() { - return this.get(OptionTypes.IGNORE_RETURN_AWAIT_YIELD); - } - - replaceVariableInit(variable: ThisVariable, init: ExpressionEntity) { - return this.setIn([OptionTypes.REPLACED_VARIABLE_INITS, variable], init); - } - - setIgnoreBreakStatements(value = true) { - return this.set(OptionTypes.IGNORE_BREAK_STATEMENTS, value); - } - - setIgnoreLabel(labelName: string) { - return this.setIn([OptionTypes.IGNORED_LABELS, labelName], true); - } - - setIgnoreNoLabels() { - return this.remove(OptionTypes.IGNORED_LABELS); - } - - setIgnoreReturnAwaitYield(value = true) { - return this.set(OptionTypes.IGNORE_RETURN_AWAIT_YIELD, value); - } - - private get(option: OptionTypes) { - return this.optionValues.get(option); - } - - private remove(option: OptionTypes) { - return new ExecutionPathOptions(this.optionValues.remove(option)); - } - - private set(option: OptionTypes, value: boolean | ExpressionEntity[]) { - return new ExecutionPathOptions(this.optionValues.set(option, value)); - } - - private setIn(optionPath: (string | Entity | RESULT_KEY)[], value: boolean | Entity) { - return new ExecutionPathOptions(this.optionValues.setIn(optionPath, value)); - } -} diff --git a/src/ast/nodes/ArrayExpression.ts b/src/ast/nodes/ArrayExpression.ts index e2c432ac6e1..ef7a29f74b7 100644 --- a/src/ast/nodes/ArrayExpression.ts +++ b/src/ast/nodes/ArrayExpression.ts @@ -1,5 +1,5 @@ import CallOptions from '../CallOptions'; -import { ExecutionPathOptions } from '../ExecutionPathOptions'; +import { ExecutionContext } from '../ExecutionContext'; import { arrayMembers, getMemberReturnExpressionWhenCalled, @@ -35,10 +35,10 @@ export default class ArrayExpression extends NodeBase { hasEffectsWhenCalledAtPath( path: ObjectPath, callOptions: CallOptions, - options: ExecutionPathOptions + context: ExecutionContext ): boolean { if (path.length === 1) { - return hasMemberEffectWhenCalled(arrayMembers, path[0], this.included, callOptions, options); + return hasMemberEffectWhenCalled(arrayMembers, path[0], this.included, callOptions, context); } return true; } diff --git a/src/ast/nodes/ArrayPattern.ts b/src/ast/nodes/ArrayPattern.ts index dc59174acf0..b27b1827728 100644 --- a/src/ast/nodes/ArrayPattern.ts +++ b/src/ast/nodes/ArrayPattern.ts @@ -1,4 +1,4 @@ -import { ExecutionPathOptions } from '../ExecutionPathOptions'; +import { ExecutionContext } from '../ExecutionContext'; import { EMPTY_PATH, ObjectPath, UNKNOWN_EXPRESSION } from '../values'; import Variable from '../variables/Variable'; import * as NodeType from './NodeType'; @@ -38,10 +38,10 @@ export default class ArrayPattern extends NodeBase implements PatternNode { } } - hasEffectsWhenAssignedAtPath(path: ObjectPath, options: ExecutionPathOptions) { + hasEffectsWhenAssignedAtPath(path: ObjectPath, context: ExecutionContext) { if (path.length > 0) return true; for (const element of this.elements) { - if (element !== null && element.hasEffectsWhenAssignedAtPath(EMPTY_PATH, options)) + if (element !== null && element.hasEffectsWhenAssignedAtPath(EMPTY_PATH, context)) return true; } return false; diff --git a/src/ast/nodes/ArrowFunctionExpression.ts b/src/ast/nodes/ArrowFunctionExpression.ts index 0aac311d6ec..f8aab8f4a7c 100644 --- a/src/ast/nodes/ArrowFunctionExpression.ts +++ b/src/ast/nodes/ArrowFunctionExpression.ts @@ -1,5 +1,5 @@ import CallOptions from '../CallOptions'; -import { ExecutionPathOptions } from '../ExecutionPathOptions'; +import { ExecutionContext } from '../ExecutionContext'; import ReturnValueScope from '../scopes/ReturnValueScope'; import Scope from '../scopes/Scope'; import { ObjectPath, UNKNOWN_EXPRESSION, UNKNOWN_KEY, UNKNOWN_PATH } from '../values'; @@ -34,30 +34,28 @@ export default class ArrowFunctionExpression extends NodeBase { return path.length === 0 ? this.scope.getReturnExpression() : UNKNOWN_EXPRESSION; } - hasEffects(_options: ExecutionPathOptions) { + hasEffects(_context: ExecutionContext) { return false; } - hasEffectsWhenAccessedAtPath(path: ObjectPath, _options: ExecutionPathOptions) { + hasEffectsWhenAccessedAtPath(path: ObjectPath, _context: ExecutionContext) { return path.length > 1; } - hasEffectsWhenAssignedAtPath(path: ObjectPath, _options: ExecutionPathOptions) { + hasEffectsWhenAssignedAtPath(path: ObjectPath, _context: ExecutionContext) { return path.length > 1; } hasEffectsWhenCalledAtPath( path: ObjectPath, _callOptions: CallOptions, - options: ExecutionPathOptions + context: ExecutionContext ): boolean { - if (path.length > 0) { - return true; - } + if (path.length > 0) return true; for (const param of this.params) { - if (param.hasEffects(options)) return true; + if (param.hasEffects(context)) return true; } - return this.body.hasEffects(options); + return this.body.hasEffects(context); } include(includeChildrenRecursively: boolean | 'variables') { diff --git a/src/ast/nodes/AssignmentExpression.ts b/src/ast/nodes/AssignmentExpression.ts index 804e058c04c..64329f691f8 100644 --- a/src/ast/nodes/AssignmentExpression.ts +++ b/src/ast/nodes/AssignmentExpression.ts @@ -1,7 +1,7 @@ import MagicString from 'magic-string'; import { RenderOptions } from '../../utils/renderHelpers'; import { getSystemExportStatement } from '../../utils/systemJsRendering'; -import { ExecutionPathOptions } from '../ExecutionPathOptions'; +import { ExecutionContext } from '../ExecutionContext'; import { EMPTY_PATH, ObjectPath, UNKNOWN_PATH } from '../values'; import Variable from '../variables/Variable'; import * as NodeType from './NodeType'; @@ -34,16 +34,16 @@ export default class AssignmentExpression extends NodeBase { this.right.deoptimizePath(UNKNOWN_PATH); } - hasEffects(options: ExecutionPathOptions): boolean { + hasEffects(context: ExecutionContext): boolean { return ( - this.right.hasEffects(options) || - this.left.hasEffects(options) || - this.left.hasEffectsWhenAssignedAtPath(EMPTY_PATH, options) + this.right.hasEffects(context) || + this.left.hasEffects(context) || + this.left.hasEffectsWhenAssignedAtPath(EMPTY_PATH, context) ); } - hasEffectsWhenAccessedAtPath(path: ObjectPath, options: ExecutionPathOptions): boolean { - return path.length > 0 && this.right.hasEffectsWhenAccessedAtPath(path, options); + hasEffectsWhenAccessedAtPath(path: ObjectPath, context: ExecutionContext): boolean { + return path.length > 0 && this.right.hasEffectsWhenAccessedAtPath(path, context); } render(code: MagicString, options: RenderOptions) { diff --git a/src/ast/nodes/AssignmentPattern.ts b/src/ast/nodes/AssignmentPattern.ts index f172dd1bf5b..9808e8c3187 100644 --- a/src/ast/nodes/AssignmentPattern.ts +++ b/src/ast/nodes/AssignmentPattern.ts @@ -1,7 +1,7 @@ import MagicString from 'magic-string'; import { BLANK } from '../../utils/blank'; import { NodeRenderOptions, RenderOptions } from '../../utils/renderHelpers'; -import { ExecutionPathOptions } from '../ExecutionPathOptions'; +import { ExecutionContext } from '../ExecutionContext'; import { EMPTY_PATH, ObjectPath, UNKNOWN_PATH } from '../values'; import Variable from '../variables/Variable'; import * as NodeType from './NodeType'; @@ -32,8 +32,8 @@ export default class AssignmentPattern extends NodeBase implements PatternNode { path.length === 0 && this.left.deoptimizePath(path); } - hasEffectsWhenAssignedAtPath(path: ObjectPath, options: ExecutionPathOptions): boolean { - return path.length > 0 || this.left.hasEffectsWhenAssignedAtPath(EMPTY_PATH, options); + hasEffectsWhenAssignedAtPath(path: ObjectPath, context: ExecutionContext): boolean { + return path.length > 0 || this.left.hasEffectsWhenAssignedAtPath(EMPTY_PATH, context); } render( diff --git a/src/ast/nodes/AwaitExpression.ts b/src/ast/nodes/AwaitExpression.ts index cacaaeb42b8..8815d0b0f87 100644 --- a/src/ast/nodes/AwaitExpression.ts +++ b/src/ast/nodes/AwaitExpression.ts @@ -1,6 +1,6 @@ import MagicString from 'magic-string'; import { RenderOptions } from '../../utils/renderHelpers'; -import { ExecutionPathOptions } from '../ExecutionPathOptions'; +import { ExecutionContext } from '../ExecutionContext'; import ArrowFunctionExpression from './ArrowFunctionExpression'; import * as NodeType from './NodeType'; import FunctionNode from './shared/FunctionNode'; @@ -10,8 +10,8 @@ export default class AwaitExpression extends NodeBase { argument!: ExpressionNode; type!: NodeType.tAwaitExpression; - hasEffects(options: ExecutionPathOptions) { - return super.hasEffects(options) || !options.ignoreReturnAwaitYield(); + hasEffects(context: ExecutionContext) { + return super.hasEffects(context) || !context.ignoreReturnAwaitYield; } include(includeChildrenRecursively: IncludeChildren) { diff --git a/src/ast/nodes/BinaryExpression.ts b/src/ast/nodes/BinaryExpression.ts index c0ff4321ae9..9a318c4f69a 100644 --- a/src/ast/nodes/BinaryExpression.ts +++ b/src/ast/nodes/BinaryExpression.ts @@ -1,5 +1,5 @@ import { DeoptimizableEntity } from '../DeoptimizableEntity'; -import { ExecutionPathOptions } from '../ExecutionPathOptions'; +import { ExecutionContext } from '../ExecutionContext'; import { EMPTY_IMMUTABLE_TRACKER, ImmutableEntityPathTracker @@ -66,19 +66,18 @@ export default class BinaryExpression extends NodeBase implements DeoptimizableE return operatorFn(leftValue as LiteralValue, rightValue as LiteralValue); } - hasEffects(options: ExecutionPathOptions): boolean { + hasEffects(context: ExecutionContext): boolean { // support some implicit type coercion runtime errors if ( this.operator === '+' && this.parent instanceof ExpressionStatement && this.left.getLiteralValueAtPath(EMPTY_PATH, EMPTY_IMMUTABLE_TRACKER, this) === '' - ) { + ) return true; - } - return super.hasEffects(options); + return super.hasEffects(context); } - hasEffectsWhenAccessedAtPath(path: ObjectPath, _options: ExecutionPathOptions) { + hasEffectsWhenAccessedAtPath(path: ObjectPath, _context: ExecutionContext) { return path.length > 1; } } diff --git a/src/ast/nodes/BlockStatement.ts b/src/ast/nodes/BlockStatement.ts index 83f3bd01cfb..c34808ff484 100644 --- a/src/ast/nodes/BlockStatement.ts +++ b/src/ast/nodes/BlockStatement.ts @@ -1,6 +1,6 @@ import MagicString from 'magic-string'; import { RenderOptions, renderStatementList } from '../../utils/renderHelpers'; -import { ExecutionPathOptions } from '../ExecutionPathOptions'; +import { ExecutionContext } from '../ExecutionContext'; import BlockScope from '../scopes/BlockScope'; import ChildScope from '../scopes/ChildScope'; import Scope from '../scopes/Scope'; @@ -25,9 +25,9 @@ export default class BlockStatement extends StatementBase { : new BlockScope(parentScope); } - hasEffects(options: ExecutionPathOptions) { + hasEffects(context: ExecutionContext) { for (const node of this.body) { - if (node.hasEffects(options)) return true; + if (node.hasEffects(context)) return true; } return false; } diff --git a/src/ast/nodes/BreakStatement.ts b/src/ast/nodes/BreakStatement.ts index 24b6e3c01e2..34320458b73 100644 --- a/src/ast/nodes/BreakStatement.ts +++ b/src/ast/nodes/BreakStatement.ts @@ -1,4 +1,4 @@ -import { ExecutionPathOptions } from '../ExecutionPathOptions'; +import { ExecutionContext } from '../ExecutionContext'; import Identifier from './Identifier'; import * as NodeType from './NodeType'; import { StatementBase } from './shared/Node'; @@ -7,11 +7,11 @@ export default class BreakStatement extends StatementBase { label!: Identifier | null; type!: NodeType.tBreakStatement; - hasEffects(options: ExecutionPathOptions) { + hasEffects(context: ExecutionContext) { return ( - super.hasEffects(options) || - !options.ignoreBreakStatements() || - (this.label !== null && !options.ignoreLabel(this.label.name)) + super.hasEffects(context) || + !context.ignoreBreakStatements || + (this.label !== null && !context.ignoredLabels.has(this.label.name)) ); } } diff --git a/src/ast/nodes/CallExpression.ts b/src/ast/nodes/CallExpression.ts index 4e2a7ae416c..52f531b8d6c 100644 --- a/src/ast/nodes/CallExpression.ts +++ b/src/ast/nodes/CallExpression.ts @@ -7,7 +7,7 @@ import { } from '../../utils/renderHelpers'; import CallOptions from '../CallOptions'; import { DeoptimizableEntity } from '../DeoptimizableEntity'; -import { ExecutionPathOptions } from '../ExecutionPathOptions'; +import { ExecutionContext } from '../ExecutionContext'; import { EMPTY_IMMUTABLE_TRACKER, ImmutableEntityPathTracker @@ -150,53 +150,47 @@ export default class CallExpression extends NodeBase implements DeoptimizableEnt ); } - hasEffects(options: ExecutionPathOptions): boolean { + // TODO Lukas can this be made more efficient by grouping properties that can be replaced together? + hasEffects(context: ExecutionContext): boolean { for (const argument of this.arguments) { - if (argument.hasEffects(options)) return true; + if (argument.hasEffects(context)) return true; } if (this.context.annotations && this.annotatedPure) return false; - return ( - this.callee.hasEffects(options) || - this.callee.hasEffectsWhenCalledAtPath( - EMPTY_PATH, - this.callOptions, - options.getHasEffectsWhenCalledOptions() - ) - ); + if (this.callee.hasEffects(context)) return true; + const { ignoreBreakStatements, ignoredLabels, ignoreReturnAwaitYield } = context; + Object.assign(context, { + ignoreBreakStatements: false, + ignoredLabels: new Set(), + ignoreReturnAwaitYield: true + }); + if (this.callee.hasEffectsWhenCalledAtPath(EMPTY_PATH, this.callOptions, context)) return true; + Object.assign(context, { ignoreBreakStatements, ignoredLabels, ignoreReturnAwaitYield }); + return false; } - hasEffectsWhenAccessedAtPath(path: ObjectPath, options: ExecutionPathOptions): boolean { + hasEffectsWhenAccessedAtPath(path: ObjectPath, context: ExecutionContext): boolean { return ( path.length > 0 && - !options.hasReturnExpressionBeenAccessedAtPath(path, this) && - (this.returnExpression as ExpressionEntity).hasEffectsWhenAccessedAtPath( - path, - options.addAccessedReturnExpressionAtPath(path, this) - ) + (this.returnExpression as ExpressionEntity).hasEffectsWhenAccessedAtPath(path, context) ); } - hasEffectsWhenAssignedAtPath(path: ObjectPath, options: ExecutionPathOptions): boolean { + hasEffectsWhenAssignedAtPath(path: ObjectPath, context: ExecutionContext): boolean { return ( path.length === 0 || - (!options.hasReturnExpressionBeenAssignedAtPath(path, this) && - (this.returnExpression as ExpressionEntity).hasEffectsWhenAssignedAtPath( - path, - options.addAssignedReturnExpressionAtPath(path, this) - )) + (this.returnExpression as ExpressionEntity).hasEffectsWhenAssignedAtPath(path, context) ); } hasEffectsWhenCalledAtPath( path: ObjectPath, callOptions: CallOptions, - options: ExecutionPathOptions + context: ExecutionContext ): boolean { - if (options.hasReturnExpressionBeenCalledAtPath(path, this)) return false; return (this.returnExpression as ExpressionEntity).hasEffectsWhenCalledAtPath( path, callOptions, - options.addCalledReturnExpressionAtPath(path, this) + context ); } diff --git a/src/ast/nodes/ClassBody.ts b/src/ast/nodes/ClassBody.ts index c6cf7d4e559..0a892fd79f6 100644 --- a/src/ast/nodes/ClassBody.ts +++ b/src/ast/nodes/ClassBody.ts @@ -1,5 +1,5 @@ import CallOptions from '../CallOptions'; -import { ExecutionPathOptions } from '../ExecutionPathOptions'; +import { ExecutionContext } from '../ExecutionContext'; import { EMPTY_PATH, ObjectPath } from '../values'; import MethodDefinition from './MethodDefinition'; import * as NodeType from './NodeType'; @@ -14,14 +14,12 @@ export default class ClassBody extends NodeBase { hasEffectsWhenCalledAtPath( path: ObjectPath, callOptions: CallOptions, - options: ExecutionPathOptions + context: ExecutionContext ) { - if (path.length > 0) { - return true; - } + if (path.length > 0) return true; return ( this.classConstructor !== null && - this.classConstructor.hasEffectsWhenCalledAtPath(EMPTY_PATH, callOptions, options) + this.classConstructor.hasEffectsWhenCalledAtPath(EMPTY_PATH, callOptions, context) ); } diff --git a/src/ast/nodes/ConditionalExpression.ts b/src/ast/nodes/ConditionalExpression.ts index abf71d7af86..5fe32bb77ac 100644 --- a/src/ast/nodes/ConditionalExpression.ts +++ b/src/ast/nodes/ConditionalExpression.ts @@ -9,7 +9,7 @@ import { import { removeAnnotations } from '../../utils/treeshakeNode'; import CallOptions from '../CallOptions'; import { DeoptimizableEntity } from '../DeoptimizableEntity'; -import { ExecutionPathOptions } from '../ExecutionPathOptions'; +import { ExecutionContext } from '../ExecutionContext'; import { EMPTY_IMMUTABLE_TRACKER, ImmutableEntityPathTracker @@ -94,48 +94,48 @@ export default class ConditionalExpression extends NodeBase implements Deoptimiz return this.usedBranch.getReturnExpressionWhenCalledAtPath(path, recursionTracker, origin); } - hasEffects(options: ExecutionPathOptions): boolean { - if (this.test.hasEffects(options)) return true; + hasEffects(context: ExecutionContext): boolean { + if (this.test.hasEffects(context)) return true; if (this.usedBranch === null) { - return this.consequent.hasEffects(options) || this.alternate.hasEffects(options); + return this.consequent.hasEffects(context) || this.alternate.hasEffects(context); } - return this.usedBranch.hasEffects(options); + return this.usedBranch.hasEffects(context); } - hasEffectsWhenAccessedAtPath(path: ObjectPath, options: ExecutionPathOptions): boolean { + hasEffectsWhenAccessedAtPath(path: ObjectPath, context: ExecutionContext): boolean { if (path.length === 0) return false; if (this.usedBranch === null) { return ( - this.consequent.hasEffectsWhenAccessedAtPath(path, options) || - this.alternate.hasEffectsWhenAccessedAtPath(path, options) + this.consequent.hasEffectsWhenAccessedAtPath(path, context) || + this.alternate.hasEffectsWhenAccessedAtPath(path, context) ); } - return this.usedBranch.hasEffectsWhenAccessedAtPath(path, options); + return this.usedBranch.hasEffectsWhenAccessedAtPath(path, context); } - hasEffectsWhenAssignedAtPath(path: ObjectPath, options: ExecutionPathOptions): boolean { + hasEffectsWhenAssignedAtPath(path: ObjectPath, context: ExecutionContext): boolean { if (path.length === 0) return true; if (this.usedBranch === null) { return ( - this.consequent.hasEffectsWhenAssignedAtPath(path, options) || - this.alternate.hasEffectsWhenAssignedAtPath(path, options) + this.consequent.hasEffectsWhenAssignedAtPath(path, context) || + this.alternate.hasEffectsWhenAssignedAtPath(path, context) ); } - return this.usedBranch.hasEffectsWhenAssignedAtPath(path, options); + return this.usedBranch.hasEffectsWhenAssignedAtPath(path, context); } hasEffectsWhenCalledAtPath( path: ObjectPath, callOptions: CallOptions, - options: ExecutionPathOptions + context: ExecutionContext ): boolean { if (this.usedBranch === null) { return ( - this.consequent.hasEffectsWhenCalledAtPath(path, callOptions, options) || - this.alternate.hasEffectsWhenCalledAtPath(path, callOptions, options) + this.consequent.hasEffectsWhenCalledAtPath(path, callOptions, context) || + this.alternate.hasEffectsWhenCalledAtPath(path, callOptions, context) ); } - return this.usedBranch.hasEffectsWhenCalledAtPath(path, callOptions, options); + return this.usedBranch.hasEffectsWhenCalledAtPath(path, callOptions, context); } include(includeChildrenRecursively: IncludeChildren) { diff --git a/src/ast/nodes/DoWhileStatement.ts b/src/ast/nodes/DoWhileStatement.ts index 8d4eac9c8ba..bef34d2f307 100644 --- a/src/ast/nodes/DoWhileStatement.ts +++ b/src/ast/nodes/DoWhileStatement.ts @@ -1,4 +1,4 @@ -import { ExecutionPathOptions } from '../ExecutionPathOptions'; +import { ExecutionContext } from '../ExecutionContext'; import * as NodeType from './NodeType'; import { ExpressionNode, StatementBase, StatementNode } from './shared/Node'; @@ -7,9 +7,12 @@ export default class DoWhileStatement extends StatementBase { test!: ExpressionNode; type!: NodeType.tDoWhileStatement; - hasEffects(options: ExecutionPathOptions): boolean { - return ( - this.test.hasEffects(options) || this.body.hasEffects(options.setIgnoreBreakStatements()) - ); + hasEffects(context: ExecutionContext): boolean { + if (this.test.hasEffects(context)) return true; + const { ignoreBreakStatements } = context; + context.ignoreBreakStatements = true; + if (this.body.hasEffects(context)) return true; + context.ignoreBreakStatements = ignoreBreakStatements; + return false; } } diff --git a/src/ast/nodes/ExportNamedDeclaration.ts b/src/ast/nodes/ExportNamedDeclaration.ts index fbb90c3692e..c0bf829f970 100644 --- a/src/ast/nodes/ExportNamedDeclaration.ts +++ b/src/ast/nodes/ExportNamedDeclaration.ts @@ -1,7 +1,7 @@ import MagicString from 'magic-string'; import { BLANK } from '../../utils/blank'; import { NodeRenderOptions, RenderOptions } from '../../utils/renderHelpers'; -import { ExecutionPathOptions } from '../ExecutionPathOptions'; +import { ExecutionContext } from '../ExecutionContext'; import ClassDeclaration from './ClassDeclaration'; import ExportSpecifier from './ExportSpecifier'; import FunctionDeclaration from './FunctionDeclaration'; @@ -22,8 +22,8 @@ export default class ExportNamedDeclaration extends NodeBase { if (this.declaration !== null) this.declaration.bind(); } - hasEffects(options: ExecutionPathOptions) { - return this.declaration !== null && this.declaration.hasEffects(options); + hasEffects(context: ExecutionContext) { + return this.declaration !== null && this.declaration.hasEffects(context); } initialise() { diff --git a/src/ast/nodes/ForInStatement.ts b/src/ast/nodes/ForInStatement.ts index 515e9ad4413..99669e68e89 100644 --- a/src/ast/nodes/ForInStatement.ts +++ b/src/ast/nodes/ForInStatement.ts @@ -1,6 +1,6 @@ import MagicString from 'magic-string'; import { NO_SEMICOLON, RenderOptions } from '../../utils/renderHelpers'; -import { ExecutionPathOptions } from '../ExecutionPathOptions'; +import { ExecutionContext } from '../ExecutionContext'; import BlockScope from '../scopes/BlockScope'; import Scope from '../scopes/Scope'; import { EMPTY_PATH } from '../values'; @@ -26,14 +26,19 @@ export default class ForInStatement extends StatementBase { this.scope = new BlockScope(parentScope); } - hasEffects(options: ExecutionPathOptions): boolean { - return ( + hasEffects(context: ExecutionContext): boolean { + if ( (this.left && - (this.left.hasEffects(options) || - this.left.hasEffectsWhenAssignedAtPath(EMPTY_PATH, options))) || - (this.right && this.right.hasEffects(options)) || - this.body.hasEffects(options.setIgnoreBreakStatements()) - ); + (this.left.hasEffects(context) || + this.left.hasEffectsWhenAssignedAtPath(EMPTY_PATH, context))) || + (this.right && this.right.hasEffects(context)) + ) + return true; + const { ignoreBreakStatements } = context; + context.ignoreBreakStatements = true; + if (this.body.hasEffects(context)) return true; + context.ignoreBreakStatements = ignoreBreakStatements; + return false; } include(includeChildrenRecursively: IncludeChildren) { diff --git a/src/ast/nodes/ForStatement.ts b/src/ast/nodes/ForStatement.ts index a6d633491aa..bdec5db9581 100644 --- a/src/ast/nodes/ForStatement.ts +++ b/src/ast/nodes/ForStatement.ts @@ -1,6 +1,6 @@ import MagicString from 'magic-string'; import { NO_SEMICOLON, RenderOptions } from '../../utils/renderHelpers'; -import { ExecutionPathOptions } from '../ExecutionPathOptions'; +import { ExecutionContext } from '../ExecutionContext'; import BlockScope from '../scopes/BlockScope'; import Scope from '../scopes/Scope'; import * as NodeType from './NodeType'; @@ -18,13 +18,18 @@ export default class ForStatement extends StatementBase { this.scope = new BlockScope(parentScope); } - hasEffects(options: ExecutionPathOptions): boolean { - return ( - (this.init && this.init.hasEffects(options)) || - (this.test && this.test.hasEffects(options)) || - (this.update && this.update.hasEffects(options)) || - this.body.hasEffects(options.setIgnoreBreakStatements()) - ); + hasEffects(context: ExecutionContext): boolean { + if ( + (this.init && this.init.hasEffects(context)) || + (this.test && this.test.hasEffects(context)) || + (this.update && this.update.hasEffects(context)) + ) + return true; + const { ignoreBreakStatements } = context; + context.ignoreBreakStatements = true; + if (this.body.hasEffects(context)) return true; + context.ignoreBreakStatements = ignoreBreakStatements; + return false; } render(code: MagicString, options: RenderOptions) { diff --git a/src/ast/nodes/Identifier.ts b/src/ast/nodes/Identifier.ts index 056e8338d15..728891c2c92 100644 --- a/src/ast/nodes/Identifier.ts +++ b/src/ast/nodes/Identifier.ts @@ -4,7 +4,7 @@ import { BLANK } from '../../utils/blank'; import { NodeRenderOptions, RenderOptions } from '../../utils/renderHelpers'; import CallOptions from '../CallOptions'; import { DeoptimizableEntity } from '../DeoptimizableEntity'; -import { ExecutionPathOptions } from '../ExecutionPathOptions'; +import { ExecutionContext } from '../ExecutionContext'; import FunctionScope from '../scopes/FunctionScope'; import { ImmutableEntityPathTracker } from '../utils/ImmutableEntityPathTracker'; import { EMPTY_PATH, LiteralValueOrUnknown, ObjectPath } from '../values'; @@ -109,20 +109,20 @@ export default class Identifier extends NodeBase implements PatternNode { ); } - hasEffectsWhenAccessedAtPath(path: ObjectPath, options: ExecutionPathOptions): boolean { - return this.variable !== null && this.variable.hasEffectsWhenAccessedAtPath(path, options); + hasEffectsWhenAccessedAtPath(path: ObjectPath, context: ExecutionContext): boolean { + return this.variable !== null && this.variable.hasEffectsWhenAccessedAtPath(path, context); } - hasEffectsWhenAssignedAtPath(path: ObjectPath, options: ExecutionPathOptions): boolean { - return !this.variable || this.variable.hasEffectsWhenAssignedAtPath(path, options); + hasEffectsWhenAssignedAtPath(path: ObjectPath, context: ExecutionContext): boolean { + return !this.variable || this.variable.hasEffectsWhenAssignedAtPath(path, context); } hasEffectsWhenCalledAtPath( path: ObjectPath, callOptions: CallOptions, - options: ExecutionPathOptions + context: ExecutionContext ) { - return !this.variable || this.variable.hasEffectsWhenCalledAtPath(path, callOptions, options); + return !this.variable || this.variable.hasEffectsWhenCalledAtPath(path, callOptions, context); } include() { diff --git a/src/ast/nodes/IfStatement.ts b/src/ast/nodes/IfStatement.ts index 5d2a9847dbc..6b965120194 100644 --- a/src/ast/nodes/IfStatement.ts +++ b/src/ast/nodes/IfStatement.ts @@ -2,7 +2,7 @@ import MagicString from 'magic-string'; import { RenderOptions } from '../../utils/renderHelpers'; import { removeAnnotations } from '../../utils/treeshakeNode'; import { DeoptimizableEntity } from '../DeoptimizableEntity'; -import { ExecutionPathOptions } from '../ExecutionPathOptions'; +import { ExecutionContext } from '../ExecutionContext'; import { EMPTY_IMMUTABLE_TRACKER } from '../utils/ImmutableEntityPathTracker'; import { EMPTY_PATH, LiteralValueOrUnknown, UNKNOWN_VALUE } from '../values'; import * as NodeType from './NodeType'; @@ -30,17 +30,17 @@ export default class IfStatement extends StatementBase implements DeoptimizableE this.testValue = UNKNOWN_VALUE; } - hasEffects(options: ExecutionPathOptions): boolean { - if (this.test.hasEffects(options)) return true; + hasEffects(context: ExecutionContext): boolean { + if (this.test.hasEffects(context)) return true; if (this.testValue === UNKNOWN_VALUE) { return ( - this.consequent.hasEffects(options) || - (this.alternate !== null && this.alternate.hasEffects(options)) + this.consequent.hasEffects(context) || + (this.alternate !== null && this.alternate.hasEffects(context)) ); } return this.testValue - ? this.consequent.hasEffects(options) - : this.alternate !== null && this.alternate.hasEffects(options); + ? this.consequent.hasEffects(context) + : this.alternate !== null && this.alternate.hasEffects(context); } include(includeChildrenRecursively: IncludeChildren) { diff --git a/src/ast/nodes/LabeledStatement.ts b/src/ast/nodes/LabeledStatement.ts index 9a2e7b8d5a3..12c082baf3b 100644 --- a/src/ast/nodes/LabeledStatement.ts +++ b/src/ast/nodes/LabeledStatement.ts @@ -1,4 +1,4 @@ -import { ExecutionPathOptions } from '../ExecutionPathOptions'; +import { ExecutionContext } from '../ExecutionContext'; import Identifier from './Identifier'; import * as NodeType from './NodeType'; import { StatementBase, StatementNode } from './shared/Node'; @@ -8,7 +8,13 @@ export default class LabeledStatement extends StatementBase { label!: Identifier; type!: NodeType.tLabeledStatement; - hasEffects(options: ExecutionPathOptions) { - return this.body.hasEffects(options.setIgnoreLabel(this.label.name).setIgnoreBreakStatements()); + hasEffects(context: ExecutionContext) { + const { ignoreBreakStatements } = context; + context.ignoreBreakStatements = true; + context.ignoredLabels.add(this.label.name); + if (this.body.hasEffects(context)) return true; + context.ignoreBreakStatements = ignoreBreakStatements; + context.ignoredLabels.delete(this.label.name); + return false; } } diff --git a/src/ast/nodes/Literal.ts b/src/ast/nodes/Literal.ts index 83f61853cc0..05731488909 100644 --- a/src/ast/nodes/Literal.ts +++ b/src/ast/nodes/Literal.ts @@ -1,7 +1,7 @@ import MagicString from 'magic-string'; import { RenderOptions } from '../../utils/renderHelpers'; import CallOptions from '../CallOptions'; -import { ExecutionPathOptions } from '../ExecutionPathOptions'; +import { ExecutionContext } from '../ExecutionContext'; import { getLiteralMembersForValue, getMemberReturnExpressionWhenCalled, @@ -56,10 +56,10 @@ export default class Literal extends NodeBase { hasEffectsWhenCalledAtPath( path: ObjectPath, callOptions: CallOptions, - options: ExecutionPathOptions + context: ExecutionContext ): boolean { if (path.length === 1) { - return hasMemberEffectWhenCalled(this.members, path[0], this.included, callOptions, options); + return hasMemberEffectWhenCalled(this.members, path[0], this.included, callOptions, context); } return true; } diff --git a/src/ast/nodes/LogicalExpression.ts b/src/ast/nodes/LogicalExpression.ts index 56fa2bb047a..c0db14c28d2 100644 --- a/src/ast/nodes/LogicalExpression.ts +++ b/src/ast/nodes/LogicalExpression.ts @@ -9,7 +9,7 @@ import { import { removeAnnotations } from '../../utils/treeshakeNode'; import CallOptions from '../CallOptions'; import { DeoptimizableEntity } from '../DeoptimizableEntity'; -import { ExecutionPathOptions } from '../ExecutionPathOptions'; +import { ExecutionContext } from '../ExecutionContext'; import { EMPTY_IMMUTABLE_TRACKER, ImmutableEntityPathTracker @@ -96,47 +96,47 @@ export default class LogicalExpression extends NodeBase implements Deoptimizable return this.usedBranch.getReturnExpressionWhenCalledAtPath(path, recursionTracker, origin); } - hasEffects(options: ExecutionPathOptions): boolean { + hasEffects(context: ExecutionContext): boolean { if (this.usedBranch === null) { - return this.left.hasEffects(options) || this.right.hasEffects(options); + return this.left.hasEffects(context) || this.right.hasEffects(context); } - return this.usedBranch.hasEffects(options); + return this.usedBranch.hasEffects(context); } - hasEffectsWhenAccessedAtPath(path: ObjectPath, options: ExecutionPathOptions): boolean { + hasEffectsWhenAccessedAtPath(path: ObjectPath, context: ExecutionContext): boolean { if (path.length === 0) return false; if (this.usedBranch === null) { return ( - this.left.hasEffectsWhenAccessedAtPath(path, options) || - this.right.hasEffectsWhenAccessedAtPath(path, options) + this.left.hasEffectsWhenAccessedAtPath(path, context) || + this.right.hasEffectsWhenAccessedAtPath(path, context) ); } - return this.usedBranch.hasEffectsWhenAccessedAtPath(path, options); + return this.usedBranch.hasEffectsWhenAccessedAtPath(path, context); } - hasEffectsWhenAssignedAtPath(path: ObjectPath, options: ExecutionPathOptions): boolean { + hasEffectsWhenAssignedAtPath(path: ObjectPath, context: ExecutionContext): boolean { if (path.length === 0) return true; if (this.usedBranch === null) { return ( - this.left.hasEffectsWhenAssignedAtPath(path, options) || - this.right.hasEffectsWhenAssignedAtPath(path, options) + this.left.hasEffectsWhenAssignedAtPath(path, context) || + this.right.hasEffectsWhenAssignedAtPath(path, context) ); } - return this.usedBranch.hasEffectsWhenAssignedAtPath(path, options); + return this.usedBranch.hasEffectsWhenAssignedAtPath(path, context); } hasEffectsWhenCalledAtPath( path: ObjectPath, callOptions: CallOptions, - options: ExecutionPathOptions + context: ExecutionContext ): boolean { if (this.usedBranch === null) { return ( - this.left.hasEffectsWhenCalledAtPath(path, callOptions, options) || - this.right.hasEffectsWhenCalledAtPath(path, callOptions, options) + this.left.hasEffectsWhenCalledAtPath(path, callOptions, context) || + this.right.hasEffectsWhenCalledAtPath(path, callOptions, context) ); } - return this.usedBranch.hasEffectsWhenCalledAtPath(path, callOptions, options); + return this.usedBranch.hasEffectsWhenCalledAtPath(path, callOptions, context); } include(includeChildrenRecursively: IncludeChildren) { diff --git a/src/ast/nodes/MemberExpression.ts b/src/ast/nodes/MemberExpression.ts index 5b14ca02553..70e921ed80b 100644 --- a/src/ast/nodes/MemberExpression.ts +++ b/src/ast/nodes/MemberExpression.ts @@ -4,7 +4,7 @@ import relativeId from '../../utils/relativeId'; import { NodeRenderOptions, RenderOptions } from '../../utils/renderHelpers'; import CallOptions from '../CallOptions'; import { DeoptimizableEntity } from '../DeoptimizableEntity'; -import { ExecutionPathOptions } from '../ExecutionPathOptions'; +import { ExecutionContext } from '../ExecutionContext'; import { EMPTY_IMMUTABLE_TRACKER, ImmutableEntityPathTracker @@ -167,50 +167,48 @@ export default class MemberExpression extends NodeBase implements DeoptimizableE ); } - hasEffects(options: ExecutionPathOptions): boolean { + hasEffects(context: ExecutionContext): boolean { return ( - this.property.hasEffects(options) || - this.object.hasEffects(options) || + this.property.hasEffects(context) || + this.object.hasEffects(context) || (this.context.propertyReadSideEffects && - this.object.hasEffectsWhenAccessedAtPath([this.propertyKey as ObjectPathKey], options)) + this.object.hasEffectsWhenAccessedAtPath([this.propertyKey as ObjectPathKey], context)) ); } - hasEffectsWhenAccessedAtPath(path: ObjectPath, options: ExecutionPathOptions): boolean { - if (path.length === 0) { - return false; - } + hasEffectsWhenAccessedAtPath(path: ObjectPath, context: ExecutionContext): boolean { + if (path.length === 0) return false; if (this.variable !== null) { - return this.variable.hasEffectsWhenAccessedAtPath(path, options); + return this.variable.hasEffectsWhenAccessedAtPath(path, context); } return this.object.hasEffectsWhenAccessedAtPath( [this.propertyKey as ObjectPathKey, ...path], - options + context ); } - hasEffectsWhenAssignedAtPath(path: ObjectPath, options: ExecutionPathOptions): boolean { + hasEffectsWhenAssignedAtPath(path: ObjectPath, context: ExecutionContext): boolean { if (this.variable !== null) { - return this.variable.hasEffectsWhenAssignedAtPath(path, options); + return this.variable.hasEffectsWhenAssignedAtPath(path, context); } return this.object.hasEffectsWhenAssignedAtPath( [this.propertyKey as ObjectPathKey, ...path], - options + context ); } hasEffectsWhenCalledAtPath( path: ObjectPath, callOptions: CallOptions, - options: ExecutionPathOptions + context: ExecutionContext ): boolean { if (this.variable !== null) { - return this.variable.hasEffectsWhenCalledAtPath(path, callOptions, options); + return this.variable.hasEffectsWhenCalledAtPath(path, callOptions, context); } return this.object.hasEffectsWhenCalledAtPath( [this.propertyKey as ObjectPathKey, ...path], callOptions, - options + context ); } diff --git a/src/ast/nodes/MethodDefinition.ts b/src/ast/nodes/MethodDefinition.ts index 5fca36e5fc1..af5bdc92c35 100644 --- a/src/ast/nodes/MethodDefinition.ts +++ b/src/ast/nodes/MethodDefinition.ts @@ -1,5 +1,5 @@ import CallOptions from '../CallOptions'; -import { ExecutionPathOptions } from '../ExecutionPathOptions'; +import { ExecutionContext } from '../ExecutionContext'; import { EMPTY_PATH, ObjectPath } from '../values'; import FunctionExpression from './FunctionExpression'; import * as NodeType from './NodeType'; @@ -13,17 +13,17 @@ export default class MethodDefinition extends NodeBase { type!: NodeType.tMethodDefinition; value!: FunctionExpression; - hasEffects(options: ExecutionPathOptions) { - return this.key.hasEffects(options); + hasEffects(context: ExecutionContext) { + return this.key.hasEffects(context); } hasEffectsWhenCalledAtPath( path: ObjectPath, callOptions: CallOptions, - options: ExecutionPathOptions + context: ExecutionContext ) { return ( - path.length > 0 || this.value.hasEffectsWhenCalledAtPath(EMPTY_PATH, callOptions, options) + path.length > 0 || this.value.hasEffectsWhenCalledAtPath(EMPTY_PATH, callOptions, context) ); } } diff --git a/src/ast/nodes/NewExpression.ts b/src/ast/nodes/NewExpression.ts index ea5c69813b8..ca2a20a1382 100644 --- a/src/ast/nodes/NewExpression.ts +++ b/src/ast/nodes/NewExpression.ts @@ -1,5 +1,5 @@ import CallOptions from '../CallOptions'; -import { ExecutionPathOptions } from '../ExecutionPathOptions'; +import { ExecutionContext } from '../ExecutionContext'; import { EMPTY_PATH, ObjectPath, UNKNOWN_PATH } from '../values'; import * as NodeType from './NodeType'; import { ExpressionNode, NodeBase } from './shared/Node'; @@ -20,19 +20,23 @@ export default class NewExpression extends NodeBase { } } - hasEffects(options: ExecutionPathOptions): boolean { + hasEffects(context: ExecutionContext): boolean { for (const argument of this.arguments) { - if (argument.hasEffects(options)) return true; + if (argument.hasEffects(context)) return true; } if (this.annotatedPure) return false; - return this.callee.hasEffectsWhenCalledAtPath( - EMPTY_PATH, - this.callOptions, - options.getHasEffectsWhenCalledOptions() - ); + const { ignoreBreakStatements, ignoredLabels, ignoreReturnAwaitYield } = context; + Object.assign(context, { + ignoreBreakStatements: false, + ignoredLabels: new Set(), + ignoreReturnAwaitYield: true + }); + if (this.callee.hasEffectsWhenCalledAtPath(EMPTY_PATH, this.callOptions, context)) return true; + Object.assign(context, { ignoreBreakStatements, ignoredLabels, ignoreReturnAwaitYield }); + return false; } - hasEffectsWhenAccessedAtPath(path: ObjectPath, _options: ExecutionPathOptions) { + hasEffectsWhenAccessedAtPath(path: ObjectPath, _context: ExecutionContext) { return path.length > 1; } diff --git a/src/ast/nodes/ObjectExpression.ts b/src/ast/nodes/ObjectExpression.ts index 666330c4021..0806829194c 100644 --- a/src/ast/nodes/ObjectExpression.ts +++ b/src/ast/nodes/ObjectExpression.ts @@ -3,7 +3,7 @@ import { BLANK } from '../../utils/blank'; import { NodeRenderOptions, RenderOptions } from '../../utils/renderHelpers'; import CallOptions from '../CallOptions'; import { DeoptimizableEntity } from '../DeoptimizableEntity'; -import { ExecutionPathOptions } from '../ExecutionPathOptions'; +import { ExecutionContext } from '../ExecutionContext'; import { EMPTY_IMMUTABLE_TRACKER, ImmutableEntityPathTracker @@ -114,7 +114,7 @@ export default class ObjectExpression extends NodeBase { path.length === 1 && !(this.propertyMap as PropertyMap)[key] && !objectMembers[key] && - (this.unmatchablePropertiesRead).length === 0 + this.unmatchablePropertiesRead.length === 0 ) { const expressionsToBeDeoptimized = this.expressionsToBeDeoptimized.get(key); if (expressionsToBeDeoptimized) { @@ -189,7 +189,7 @@ export default class ObjectExpression extends NodeBase { ); } - hasEffectsWhenAccessedAtPath(path: ObjectPath, options: ExecutionPathOptions) { + hasEffectsWhenAccessedAtPath(path: ObjectPath, context: ExecutionContext) { if (path.length === 0) return false; const key = path[0]; if ( @@ -208,12 +208,12 @@ export default class ObjectExpression extends NodeBase { : (this.propertyMap as PropertyMap)[key] ? (this.propertyMap as PropertyMap)[key].propertiesRead : []) { - if (property.hasEffectsWhenAccessedAtPath(subPath, options)) return true; + if (property.hasEffectsWhenAccessedAtPath(subPath, context)) return true; } return false; } - hasEffectsWhenAssignedAtPath(path: ObjectPath, options: ExecutionPathOptions) { + hasEffectsWhenAssignedAtPath(path: ObjectPath, context: ExecutionContext) { if (path.length === 0) return false; const key = path[0]; if ( @@ -234,7 +234,7 @@ export default class ObjectExpression extends NodeBase { : (this.propertyMap as PropertyMap)[key] ? (this.propertyMap as PropertyMap)[key].propertiesSet : []) { - if (property.hasEffectsWhenAssignedAtPath(subPath, options)) return true; + if (property.hasEffectsWhenAssignedAtPath(subPath, context)) return true; } return false; } @@ -242,7 +242,7 @@ export default class ObjectExpression extends NodeBase { hasEffectsWhenCalledAtPath( path: ObjectPath, callOptions: CallOptions, - options: ExecutionPathOptions + context: ExecutionContext ): boolean { const key = path[0]; if ( @@ -259,10 +259,10 @@ export default class ObjectExpression extends NodeBase { for (const property of (this.propertyMap as PropertyMap)[key] ? (this.propertyMap as PropertyMap)[key].propertiesRead : []) { - if (property.hasEffectsWhenCalledAtPath(subPath, callOptions, options)) return true; + if (property.hasEffectsWhenCalledAtPath(subPath, callOptions, context)) return true; } if (path.length === 1 && objectMembers[key]) - return hasMemberEffectWhenCalled(objectMembers, key, this.included, callOptions, options); + return hasMemberEffectWhenCalled(objectMembers, key, this.included, callOptions, context); return false; } diff --git a/src/ast/nodes/ObjectPattern.ts b/src/ast/nodes/ObjectPattern.ts index c1ed96bfe57..562277d728b 100644 --- a/src/ast/nodes/ObjectPattern.ts +++ b/src/ast/nodes/ObjectPattern.ts @@ -1,4 +1,4 @@ -import { ExecutionPathOptions } from '../ExecutionPathOptions'; +import { ExecutionContext } from '../ExecutionContext'; import { EMPTY_PATH, ObjectPath } from '../values'; import Variable from '../variables/Variable'; import * as NodeType from './NodeType'; @@ -38,10 +38,10 @@ export default class ObjectPattern extends NodeBase implements PatternNode { } } - hasEffectsWhenAssignedAtPath(path: ObjectPath, options: ExecutionPathOptions) { + hasEffectsWhenAssignedAtPath(path: ObjectPath, context: ExecutionContext) { if (path.length > 0) return true; for (const property of this.properties) { - if (property.hasEffectsWhenAssignedAtPath(EMPTY_PATH, options)) return true; + if (property.hasEffectsWhenAssignedAtPath(EMPTY_PATH, context)) return true; } return false; } diff --git a/src/ast/nodes/Program.ts b/src/ast/nodes/Program.ts index d11ca72523d..c8e22eab59e 100644 --- a/src/ast/nodes/Program.ts +++ b/src/ast/nodes/Program.ts @@ -1,6 +1,6 @@ import MagicString from 'magic-string'; import { RenderOptions, renderStatementList } from '../../utils/renderHelpers'; -import { ExecutionPathOptions } from '../ExecutionPathOptions'; +import { ExecutionContext } from '../ExecutionContext'; import * as NodeType from './NodeType'; import { IncludeChildren, NodeBase, StatementNode } from './shared/Node'; @@ -9,9 +9,9 @@ export default class Program extends NodeBase { sourceType!: 'module'; type!: NodeType.tProgram; - hasEffects(options: ExecutionPathOptions) { + hasEffects(context: ExecutionContext) { for (const node of this.body) { - if (node.hasEffects(options)) return true; + if (node.hasEffects(context)) return true; } return false; } diff --git a/src/ast/nodes/Property.ts b/src/ast/nodes/Property.ts index f06dbbcb616..be8df72a369 100644 --- a/src/ast/nodes/Property.ts +++ b/src/ast/nodes/Property.ts @@ -2,7 +2,7 @@ import MagicString from 'magic-string'; import { RenderOptions } from '../../utils/renderHelpers'; import CallOptions from '../CallOptions'; import { DeoptimizableEntity } from '../DeoptimizableEntity'; -import { ExecutionPathOptions } from '../ExecutionPathOptions'; +import { ExecutionContext } from '../ExecutionContext'; import { EMPTY_IMMUTABLE_TRACKER, ImmutableEntityPathTracker @@ -100,58 +100,65 @@ export default class Property extends NodeBase implements DeoptimizableEntity { return this.value.getReturnExpressionWhenCalledAtPath(path, recursionTracker, origin); } - hasEffects(options: ExecutionPathOptions): boolean { - return this.key.hasEffects(options) || this.value.hasEffects(options); + hasEffects(context: ExecutionContext): boolean { + return this.key.hasEffects(context) || this.value.hasEffects(context); } - hasEffectsWhenAccessedAtPath(path: ObjectPath, options: ExecutionPathOptions): boolean { + hasEffectsWhenAccessedAtPath(path: ObjectPath, context: ExecutionContext): boolean { if (this.kind === 'get') { + const { ignoreBreakStatements, ignoredLabels, ignoreReturnAwaitYield } = context; + Object.assign(context, { + ignoreBreakStatements: false, + ignoredLabels: new Set(), + ignoreReturnAwaitYield: true + }); + if (this.value.hasEffectsWhenCalledAtPath(EMPTY_PATH, this.accessorCallOptions, context)) + return true; + Object.assign(context, { ignoreBreakStatements, ignoredLabels, ignoreReturnAwaitYield }); return ( - this.value.hasEffectsWhenCalledAtPath( - EMPTY_PATH, - this.accessorCallOptions, - options.getHasEffectsWhenCalledOptions() - ) || - (path.length > 0 && - (this.returnExpression as ExpressionEntity).hasEffectsWhenAccessedAtPath(path, options)) + path.length > 0 && + (this.returnExpression as ExpressionEntity).hasEffectsWhenAccessedAtPath(path, context) ); } - return this.value.hasEffectsWhenAccessedAtPath(path, options); + return this.value.hasEffectsWhenAccessedAtPath(path, context); } - hasEffectsWhenAssignedAtPath(path: ObjectPath, options: ExecutionPathOptions): boolean { + hasEffectsWhenAssignedAtPath(path: ObjectPath, context: ExecutionContext): boolean { if (this.kind === 'get') { return ( path.length === 0 || - (this.returnExpression as ExpressionEntity).hasEffectsWhenAssignedAtPath(path, options) + (this.returnExpression as ExpressionEntity).hasEffectsWhenAssignedAtPath(path, context) ); } if (this.kind === 'set') { - return ( - path.length > 0 || - this.value.hasEffectsWhenCalledAtPath( - EMPTY_PATH, - this.accessorCallOptions, - options.getHasEffectsWhenCalledOptions() - ) - ); + if (path.length > 0) return true; + const { ignoreBreakStatements, ignoredLabels, ignoreReturnAwaitYield } = context; + Object.assign(context, { + ignoreBreakStatements: false, + ignoredLabels: new Set(), + ignoreReturnAwaitYield: true + }); + if (this.value.hasEffectsWhenCalledAtPath(EMPTY_PATH, this.accessorCallOptions, context)) + return true; + Object.assign(context, { ignoreBreakStatements, ignoredLabels, ignoreReturnAwaitYield }); + return false; } - return this.value.hasEffectsWhenAssignedAtPath(path, options); + return this.value.hasEffectsWhenAssignedAtPath(path, context); } hasEffectsWhenCalledAtPath( path: ObjectPath, callOptions: CallOptions, - options: ExecutionPathOptions + context: ExecutionContext ) { if (this.kind === 'get') { return (this.returnExpression as ExpressionEntity).hasEffectsWhenCalledAtPath( path, callOptions, - options + context ); } - return this.value.hasEffectsWhenCalledAtPath(path, callOptions, options); + return this.value.hasEffectsWhenCalledAtPath(path, callOptions, context); } initialise() { diff --git a/src/ast/nodes/RestElement.ts b/src/ast/nodes/RestElement.ts index 7c5e3e84bbb..f6c245da73a 100644 --- a/src/ast/nodes/RestElement.ts +++ b/src/ast/nodes/RestElement.ts @@ -1,4 +1,4 @@ -import { ExecutionPathOptions } from '../ExecutionPathOptions'; +import { ExecutionContext } from '../ExecutionContext'; import { EMPTY_PATH, ObjectPath, UNKNOWN_EXPRESSION, UNKNOWN_KEY } from '../values'; import Variable from '../variables/Variable'; import * as NodeType from './NodeType'; @@ -32,7 +32,7 @@ export default class RestElement extends NodeBase implements PatternNode { path.length === 0 && this.argument.deoptimizePath(EMPTY_PATH); } - hasEffectsWhenAssignedAtPath(path: ObjectPath, options: ExecutionPathOptions): boolean { - return path.length > 0 || this.argument.hasEffectsWhenAssignedAtPath(EMPTY_PATH, options); + hasEffectsWhenAssignedAtPath(path: ObjectPath, context: ExecutionContext): boolean { + return path.length > 0 || this.argument.hasEffectsWhenAssignedAtPath(EMPTY_PATH, context); } } diff --git a/src/ast/nodes/ReturnStatement.ts b/src/ast/nodes/ReturnStatement.ts index 7f2998b5ea5..67fa52a17c1 100644 --- a/src/ast/nodes/ReturnStatement.ts +++ b/src/ast/nodes/ReturnStatement.ts @@ -1,6 +1,6 @@ import MagicString from 'magic-string'; import { RenderOptions } from '../../utils/renderHelpers'; -import { ExecutionPathOptions } from '../ExecutionPathOptions'; +import { ExecutionContext } from '../ExecutionContext'; import { UNKNOWN_EXPRESSION } from '../values'; import * as NodeType from './NodeType'; import { ExpressionNode, StatementBase } from './shared/Node'; @@ -9,10 +9,10 @@ export default class ReturnStatement extends StatementBase { argument!: ExpressionNode | null; type!: NodeType.tReturnStatement; - hasEffects(options: ExecutionPathOptions) { + hasEffects(context: ExecutionContext) { return ( - !options.ignoreReturnAwaitYield() || - (this.argument !== null && this.argument.hasEffects(options)) + !context.ignoreReturnAwaitYield || + (this.argument !== null && this.argument.hasEffects(context)) ); } diff --git a/src/ast/nodes/SequenceExpression.ts b/src/ast/nodes/SequenceExpression.ts index e4a21b486fb..3eef2ef048a 100644 --- a/src/ast/nodes/SequenceExpression.ts +++ b/src/ast/nodes/SequenceExpression.ts @@ -9,7 +9,7 @@ import { import { treeshakeNode } from '../../utils/treeshakeNode'; import CallOptions from '../CallOptions'; import { DeoptimizableEntity } from '../DeoptimizableEntity'; -import { ExecutionPathOptions } from '../ExecutionPathOptions'; +import { ExecutionContext } from '../ExecutionContext'; import { ImmutableEntityPathTracker } from '../utils/ImmutableEntityPathTracker'; import { LiteralValueOrUnknown, ObjectPath } from '../values'; import CallExpression from './CallExpression'; @@ -36,36 +36,36 @@ export default class SequenceExpression extends NodeBase { ); } - hasEffects(options: ExecutionPathOptions): boolean { + hasEffects(context: ExecutionContext): boolean { for (const expression of this.expressions) { - if (expression.hasEffects(options)) return true; + if (expression.hasEffects(context)) return true; } return false; } - hasEffectsWhenAccessedAtPath(path: ObjectPath, options: ExecutionPathOptions): boolean { + hasEffectsWhenAccessedAtPath(path: ObjectPath, context: ExecutionContext): boolean { return ( path.length > 0 && - this.expressions[this.expressions.length - 1].hasEffectsWhenAccessedAtPath(path, options) + this.expressions[this.expressions.length - 1].hasEffectsWhenAccessedAtPath(path, context) ); } - hasEffectsWhenAssignedAtPath(path: ObjectPath, options: ExecutionPathOptions): boolean { + hasEffectsWhenAssignedAtPath(path: ObjectPath, context: ExecutionContext): boolean { return ( path.length === 0 || - this.expressions[this.expressions.length - 1].hasEffectsWhenAssignedAtPath(path, options) + this.expressions[this.expressions.length - 1].hasEffectsWhenAssignedAtPath(path, context) ); } hasEffectsWhenCalledAtPath( path: ObjectPath, callOptions: CallOptions, - options: ExecutionPathOptions + context: ExecutionContext ): boolean { return this.expressions[this.expressions.length - 1].hasEffectsWhenCalledAtPath( path, callOptions, - options + context ); } diff --git a/src/ast/nodes/SwitchStatement.ts b/src/ast/nodes/SwitchStatement.ts index b826bdf2787..09b32a96166 100644 --- a/src/ast/nodes/SwitchStatement.ts +++ b/src/ast/nodes/SwitchStatement.ts @@ -1,4 +1,4 @@ -import { ExecutionPathOptions } from '../ExecutionPathOptions'; +import { ExecutionContext } from '../ExecutionContext'; import BlockScope from '../scopes/BlockScope'; import Scope from '../scopes/Scope'; import * as NodeType from './NodeType'; @@ -14,7 +14,11 @@ export default class SwitchStatement extends StatementBase { this.scope = new BlockScope(parentScope); } - hasEffects(options: ExecutionPathOptions) { - return super.hasEffects(options.setIgnoreBreakStatements()); + hasEffects(context: ExecutionContext) { + const ignoreBreakStatements = context.ignoreBreakStatements; + context.ignoreBreakStatements = true; + if (super.hasEffects(context)) return true; + context.ignoreBreakStatements = ignoreBreakStatements; + return false; } } diff --git a/src/ast/nodes/TaggedTemplateExpression.ts b/src/ast/nodes/TaggedTemplateExpression.ts index eb2bec9795e..c1f97929a65 100644 --- a/src/ast/nodes/TaggedTemplateExpression.ts +++ b/src/ast/nodes/TaggedTemplateExpression.ts @@ -1,5 +1,5 @@ import CallOptions from '../CallOptions'; -import { ExecutionPathOptions } from '../ExecutionPathOptions'; +import { ExecutionContext } from '../ExecutionContext'; import { EMPTY_PATH } from '../values'; import Identifier from './Identifier'; import * as NodeType from './NodeType'; @@ -41,15 +41,17 @@ export default class TaggedTemplateExpression extends NodeBase { } } - hasEffects(options: ExecutionPathOptions) { - return ( - super.hasEffects(options) || - this.tag.hasEffectsWhenCalledAtPath( - EMPTY_PATH, - this.callOptions, - options.getHasEffectsWhenCalledOptions() - ) - ); + hasEffects(context: ExecutionContext) { + if (super.hasEffects(context)) return true; + const { ignoreBreakStatements, ignoredLabels, ignoreReturnAwaitYield } = context; + Object.assign(context, { + ignoreBreakStatements: false, + ignoredLabels: new Set(), + ignoreReturnAwaitYield: true + }); + if (this.tag.hasEffectsWhenCalledAtPath(EMPTY_PATH, this.callOptions, context)) return true; + Object.assign(context, { ignoreBreakStatements, ignoredLabels, ignoreReturnAwaitYield }); + return false; } initialise() { diff --git a/src/ast/nodes/TemplateElement.ts b/src/ast/nodes/TemplateElement.ts index 25ec2aa11d9..1a44715520e 100644 --- a/src/ast/nodes/TemplateElement.ts +++ b/src/ast/nodes/TemplateElement.ts @@ -1,4 +1,4 @@ -import { ExecutionPathOptions } from '../ExecutionPathOptions'; +import { ExecutionContext } from '../ExecutionContext'; import * as NodeType from './NodeType'; import { NodeBase } from './shared/Node'; @@ -10,7 +10,7 @@ export default class TemplateElement extends NodeBase { raw: string; }; - hasEffects(_options: ExecutionPathOptions) { + hasEffects(_context: ExecutionContext) { return false; } } diff --git a/src/ast/nodes/ThisExpression.ts b/src/ast/nodes/ThisExpression.ts index d575c07041b..31d139436c3 100644 --- a/src/ast/nodes/ThisExpression.ts +++ b/src/ast/nodes/ThisExpression.ts @@ -1,6 +1,6 @@ import MagicString from 'magic-string'; import { RenderOptions } from '../../utils/renderHelpers'; -import { ExecutionPathOptions } from '../ExecutionPathOptions'; +import { ExecutionContext } from '../ExecutionContext'; import ModuleScope from '../scopes/ModuleScope'; import { ObjectPath } from '../values'; import ThisVariable from '../variables/ThisVariable'; @@ -18,12 +18,12 @@ export default class ThisExpression extends NodeBase { this.variable = this.scope.findVariable('this') as ThisVariable; } - hasEffectsWhenAccessedAtPath(path: ObjectPath, options: ExecutionPathOptions): boolean { - return path.length > 0 && this.variable.hasEffectsWhenAccessedAtPath(path, options); + hasEffectsWhenAccessedAtPath(path: ObjectPath, context: ExecutionContext): boolean { + return path.length > 0 && this.variable.hasEffectsWhenAccessedAtPath(path, context); } - hasEffectsWhenAssignedAtPath(path: ObjectPath, options: ExecutionPathOptions): boolean { - return this.variable.hasEffectsWhenAssignedAtPath(path, options); + hasEffectsWhenAssignedAtPath(path: ObjectPath, context: ExecutionContext): boolean { + return this.variable.hasEffectsWhenAssignedAtPath(path, context); } initialise() { diff --git a/src/ast/nodes/ThrowStatement.ts b/src/ast/nodes/ThrowStatement.ts index bc63154df9f..dc786795e29 100644 --- a/src/ast/nodes/ThrowStatement.ts +++ b/src/ast/nodes/ThrowStatement.ts @@ -1,6 +1,6 @@ import MagicString from 'magic-string'; import { RenderOptions } from '../../utils/renderHelpers'; -import { ExecutionPathOptions } from '../ExecutionPathOptions'; +import { ExecutionContext } from '../ExecutionContext'; import * as NodeType from './NodeType'; import { ExpressionNode, StatementBase } from './shared/Node'; @@ -8,7 +8,7 @@ export default class ThrowStatement extends StatementBase { argument!: ExpressionNode; type!: NodeType.tThrowStatement; - hasEffects(_options: ExecutionPathOptions) { + hasEffects(_context: ExecutionContext) { return true; } diff --git a/src/ast/nodes/TryStatement.ts b/src/ast/nodes/TryStatement.ts index c71da45c81b..71c63648a37 100644 --- a/src/ast/nodes/TryStatement.ts +++ b/src/ast/nodes/TryStatement.ts @@ -1,4 +1,4 @@ -import { ExecutionPathOptions } from '../ExecutionPathOptions'; +import { ExecutionContext } from '../ExecutionContext'; import BlockStatement from './BlockStatement'; import CatchClause from './CatchClause'; import * as NodeType from './NodeType'; @@ -12,11 +12,11 @@ export default class TryStatement extends StatementBase { private directlyIncluded = false; - hasEffects(options: ExecutionPathOptions): boolean { + hasEffects(context: ExecutionContext): boolean { return ( this.block.body.length > 0 || - (this.handler !== null && this.handler.hasEffects(options)) || - (this.finalizer !== null && this.finalizer.hasEffects(options)) + (this.handler !== null && this.handler.hasEffects(context)) || + (this.finalizer !== null && this.finalizer.hasEffects(context)) ); } diff --git a/src/ast/nodes/UnaryExpression.ts b/src/ast/nodes/UnaryExpression.ts index 37731ae1860..d4ec6e092a9 100644 --- a/src/ast/nodes/UnaryExpression.ts +++ b/src/ast/nodes/UnaryExpression.ts @@ -1,5 +1,5 @@ import { DeoptimizableEntity } from '../DeoptimizableEntity'; -import { ExecutionPathOptions } from '../ExecutionPathOptions'; +import { ExecutionContext } from '../ExecutionContext'; import { ImmutableEntityPathTracker } from '../utils/ImmutableEntityPathTracker'; import { EMPTY_PATH, LiteralValueOrUnknown, ObjectPath, UNKNOWN_VALUE } from '../values'; import Identifier from './Identifier'; @@ -44,16 +44,16 @@ export default class UnaryExpression extends NodeBase { return unaryOperators[this.operator](argumentValue as LiteralValue); } - hasEffects(options: ExecutionPathOptions): boolean { + hasEffects(context: ExecutionContext): boolean { if (this.operator === 'typeof' && this.argument instanceof Identifier) return false; return ( - this.argument.hasEffects(options) || + this.argument.hasEffects(context) || (this.operator === 'delete' && - this.argument.hasEffectsWhenAssignedAtPath(EMPTY_PATH, options)) + this.argument.hasEffectsWhenAssignedAtPath(EMPTY_PATH, context)) ); } - hasEffectsWhenAccessedAtPath(path: ObjectPath, _options: ExecutionPathOptions) { + hasEffectsWhenAccessedAtPath(path: ObjectPath, _context: ExecutionContext) { if (this.operator === 'void') { return path.length > 0; } diff --git a/src/ast/nodes/UnknownNode.ts b/src/ast/nodes/UnknownNode.ts index 7dca98101eb..c5ee5a2f8db 100644 --- a/src/ast/nodes/UnknownNode.ts +++ b/src/ast/nodes/UnknownNode.ts @@ -1,8 +1,8 @@ -import { ExecutionPathOptions } from '../ExecutionPathOptions'; +import { ExecutionContext } from '../ExecutionContext'; import { NodeBase } from './shared/Node'; export default class UnknownNode extends NodeBase { - hasEffects(_options: ExecutionPathOptions) { + hasEffects(_context: ExecutionContext) { return true; } diff --git a/src/ast/nodes/UpdateExpression.ts b/src/ast/nodes/UpdateExpression.ts index 1083d5948cd..656363c1f69 100644 --- a/src/ast/nodes/UpdateExpression.ts +++ b/src/ast/nodes/UpdateExpression.ts @@ -1,6 +1,6 @@ import MagicString from 'magic-string'; import { RenderOptions } from '../../utils/renderHelpers'; -import { ExecutionPathOptions } from '../ExecutionPathOptions'; +import { ExecutionContext } from '../ExecutionContext'; import { EMPTY_PATH, ObjectPath } from '../values'; import Identifier from './Identifier'; import * as NodeType from './NodeType'; @@ -21,14 +21,14 @@ export default class UpdateExpression extends NodeBase { } } - hasEffects(options: ExecutionPathOptions): boolean { + hasEffects(context: ExecutionContext): boolean { return ( - this.argument.hasEffects(options) || - this.argument.hasEffectsWhenAssignedAtPath(EMPTY_PATH, options) + this.argument.hasEffects(context) || + this.argument.hasEffectsWhenAssignedAtPath(EMPTY_PATH, context) ); } - hasEffectsWhenAccessedAtPath(path: ObjectPath, _options: ExecutionPathOptions) { + hasEffectsWhenAccessedAtPath(path: ObjectPath, _context: ExecutionContext) { return path.length > 1; } diff --git a/src/ast/nodes/VariableDeclaration.ts b/src/ast/nodes/VariableDeclaration.ts index 8a84f93ef93..7dbffb400ad 100644 --- a/src/ast/nodes/VariableDeclaration.ts +++ b/src/ast/nodes/VariableDeclaration.ts @@ -6,7 +6,7 @@ import { RenderOptions } from '../../utils/renderHelpers'; import { getSystemExportStatement } from '../../utils/systemJsRendering'; -import { ExecutionPathOptions } from '../ExecutionPathOptions'; +import { ExecutionContext } from '../ExecutionContext'; import { EMPTY_PATH, ObjectPath } from '../values'; import Variable from '../variables/Variable'; import Identifier, { IdentifierWithVariable } from './Identifier'; @@ -20,9 +20,7 @@ function isReassignedExportsMember(variable: Variable): boolean { function areAllDeclarationsIncludedAndNotExported(declarations: VariableDeclarator[]): boolean { for (const declarator of declarations) { - if (!declarator.included) { - return false; - } + if (!declarator.included) return false; if (declarator.id.type === NodeType.Identifier) { if ((declarator.id.variable as Variable).exportName) return false; } else { @@ -45,7 +43,7 @@ export default class VariableDeclaration extends NodeBase { } } - hasEffectsWhenAssignedAtPath(_path: ObjectPath, _options: ExecutionPathOptions) { + hasEffectsWhenAssignedAtPath(_path: ObjectPath, _context: ExecutionContext) { return false; } diff --git a/src/ast/nodes/WhileStatement.ts b/src/ast/nodes/WhileStatement.ts index 5338276d1e7..3cb8de83f20 100644 --- a/src/ast/nodes/WhileStatement.ts +++ b/src/ast/nodes/WhileStatement.ts @@ -1,4 +1,4 @@ -import { ExecutionPathOptions } from '../ExecutionPathOptions'; +import { ExecutionContext } from '../ExecutionContext'; import * as NodeType from './NodeType'; import { ExpressionNode, StatementBase, StatementNode } from './shared/Node'; @@ -7,9 +7,12 @@ export default class WhileStatement extends StatementBase { test!: ExpressionNode; type!: NodeType.tWhileStatement; - hasEffects(options: ExecutionPathOptions): boolean { - return ( - this.test.hasEffects(options) || this.body.hasEffects(options.setIgnoreBreakStatements()) - ); + hasEffects(context: ExecutionContext): boolean { + if (this.test.hasEffects(context)) return true; + const { ignoreBreakStatements } = context; + context.ignoreBreakStatements = true; + if (this.body.hasEffects(context)) return true; + context.ignoreBreakStatements = ignoreBreakStatements; + return false; } } diff --git a/src/ast/nodes/YieldExpression.ts b/src/ast/nodes/YieldExpression.ts index fef3d5ddd38..dcd4b724eb2 100644 --- a/src/ast/nodes/YieldExpression.ts +++ b/src/ast/nodes/YieldExpression.ts @@ -1,6 +1,6 @@ import MagicString from 'magic-string'; import { RenderOptions } from '../../utils/renderHelpers'; -import { ExecutionPathOptions } from '../ExecutionPathOptions'; +import { ExecutionContext } from '../ExecutionContext'; import { UNKNOWN_PATH } from '../values'; import * as NodeType from './NodeType'; import { ExpressionNode, NodeBase } from './shared/Node'; @@ -17,10 +17,10 @@ export default class YieldExpression extends NodeBase { } } - hasEffects(options: ExecutionPathOptions) { + hasEffects(context: ExecutionContext) { return ( - !options.ignoreReturnAwaitYield() || - (this.argument !== null && this.argument.hasEffects(options)) + !context.ignoreReturnAwaitYield || + (this.argument !== null && this.argument.hasEffects(context)) ); } diff --git a/src/ast/nodes/shared/ClassNode.ts b/src/ast/nodes/shared/ClassNode.ts index fae1c97144d..b05ad6faa79 100644 --- a/src/ast/nodes/shared/ClassNode.ts +++ b/src/ast/nodes/shared/ClassNode.ts @@ -1,5 +1,5 @@ import CallOptions from '../../CallOptions'; -import { ExecutionPathOptions } from '../../ExecutionPathOptions'; +import { ExecutionContext } from '../../ExecutionContext'; import ChildScope from '../../scopes/ChildScope'; import Scope from '../../scopes/Scope'; import { ObjectPath } from '../../values'; @@ -16,23 +16,23 @@ export default class ClassNode extends NodeBase { this.scope = new ChildScope(parentScope); } - hasEffectsWhenAccessedAtPath(path: ObjectPath, _options: ExecutionPathOptions) { + hasEffectsWhenAccessedAtPath(path: ObjectPath, _context: ExecutionContext) { return path.length > 1; } - hasEffectsWhenAssignedAtPath(path: ObjectPath, _options: ExecutionPathOptions) { + hasEffectsWhenAssignedAtPath(path: ObjectPath, _context: ExecutionContext) { return path.length > 1; } hasEffectsWhenCalledAtPath( path: ObjectPath, callOptions: CallOptions, - options: ExecutionPathOptions + context: ExecutionContext ) { return ( - this.body.hasEffectsWhenCalledAtPath(path, callOptions, options) || + this.body.hasEffectsWhenCalledAtPath(path, callOptions, context) || (this.superClass !== null && - this.superClass.hasEffectsWhenCalledAtPath(path, callOptions, options)) + this.superClass.hasEffectsWhenCalledAtPath(path, callOptions, context)) ); } diff --git a/src/ast/nodes/shared/Expression.ts b/src/ast/nodes/shared/Expression.ts index 292aa1fba43..38781587f0a 100644 --- a/src/ast/nodes/shared/Expression.ts +++ b/src/ast/nodes/shared/Expression.ts @@ -1,7 +1,7 @@ import CallOptions from '../../CallOptions'; import { DeoptimizableEntity } from '../../DeoptimizableEntity'; import { WritableEntity } from '../../Entity'; -import { ExecutionPathOptions } from '../../ExecutionPathOptions'; +import { ExecutionContext } from '../../ExecutionContext'; import { ImmutableEntityPathTracker } from '../../utils/ImmutableEntityPathTracker'; import { LiteralValueOrUnknown, ObjectPath } from '../../values'; import SpreadElement from '../SpreadElement'; @@ -25,11 +25,11 @@ export interface ExpressionEntity extends WritableEntity { recursionTracker: ImmutableEntityPathTracker, origin: DeoptimizableEntity ): ExpressionEntity; - hasEffectsWhenAccessedAtPath(path: ObjectPath, options: ExecutionPathOptions): boolean; + hasEffectsWhenAccessedAtPath(path: ObjectPath, context: ExecutionContext): boolean; hasEffectsWhenCalledAtPath( path: ObjectPath, callOptions: CallOptions, - options: ExecutionPathOptions + context: ExecutionContext ): boolean; include(includeChildrenRecursively: IncludeChildren): void; includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void; diff --git a/src/ast/nodes/shared/FunctionNode.ts b/src/ast/nodes/shared/FunctionNode.ts index 4d8ae7002a9..3c36d8ed3ae 100644 --- a/src/ast/nodes/shared/FunctionNode.ts +++ b/src/ast/nodes/shared/FunctionNode.ts @@ -1,7 +1,13 @@ import CallOptions from '../../CallOptions'; -import { ExecutionPathOptions } from '../../ExecutionPathOptions'; +import { ExecutionContext } from '../../ExecutionContext'; import FunctionScope from '../../scopes/FunctionScope'; -import { ObjectPath, UNKNOWN_EXPRESSION, UNKNOWN_KEY, UNKNOWN_PATH } from '../../values'; +import { + ObjectPath, + UNKNOWN_EXPRESSION, + UNKNOWN_KEY, + UNKNOWN_PATH, + UnknownObjectExpression +} from '../../values'; import BlockStatement from '../BlockStatement'; import Identifier, { IdentifierWithVariable } from '../Identifier'; import RestElement from '../RestElement'; @@ -46,9 +52,7 @@ export default class FunctionNode extends NodeBase { } hasEffectsWhenAccessedAtPath(path: ObjectPath) { - if (path.length <= 1) { - return false; - } + if (path.length <= 1) return false; return path.length > 2 || path[0] !== 'prototype' || this.isPrototypeDeoptimized; } @@ -62,16 +66,24 @@ export default class FunctionNode extends NodeBase { hasEffectsWhenCalledAtPath( path: ObjectPath, callOptions: CallOptions, - options: ExecutionPathOptions + context: ExecutionContext ) { - if (path.length > 0) { - return true; - } - const innerOptions = this.scope.getOptionsWhenCalledWith(callOptions, options); + if (path.length > 0) return true; + const thisInit = context.replacedVariableInits.get(this.scope.thisVariable); + context.replacedVariableInits.set( + this.scope.thisVariable, + callOptions.withNew ? new UnknownObjectExpression() : UNKNOWN_EXPRESSION + ); for (const param of this.params) { - if (param.hasEffects(innerOptions)) return true; + if (param.hasEffects(context)) return true; + } + if (this.body.hasEffects(context)) return true; + if (thisInit) { + context.replacedVariableInits.set(this.scope.thisVariable, thisInit); + } else { + context.replacedVariableInits.delete(this.scope.thisVariable); } - return this.body.hasEffects(innerOptions); + return false; } include(includeChildrenRecursively: boolean | 'variables') { diff --git a/src/ast/nodes/shared/MultiExpression.ts b/src/ast/nodes/shared/MultiExpression.ts index 4dbbaf7b345..69e6bf9150e 100644 --- a/src/ast/nodes/shared/MultiExpression.ts +++ b/src/ast/nodes/shared/MultiExpression.ts @@ -1,6 +1,6 @@ import CallOptions from '../../CallOptions'; import { DeoptimizableEntity } from '../../DeoptimizableEntity'; -import { ExecutionPathOptions } from '../../ExecutionPathOptions'; +import { ExecutionContext } from '../../ExecutionContext'; import { ImmutableEntityPathTracker } from '../../utils/ImmutableEntityPathTracker'; import { LiteralValueOrUnknown, ObjectPath, UNKNOWN_VALUE } from '../../values'; import SpreadElement from '../SpreadElement'; @@ -38,16 +38,16 @@ export class MultiExpression implements ExpressionEntity { ); } - hasEffectsWhenAccessedAtPath(path: ObjectPath, options: ExecutionPathOptions): boolean { + hasEffectsWhenAccessedAtPath(path: ObjectPath, context: ExecutionContext): boolean { for (const expression of this.expressions) { - if (expression.hasEffectsWhenAccessedAtPath(path, options)) return true; + if (expression.hasEffectsWhenAccessedAtPath(path, context)) return true; } return false; } - hasEffectsWhenAssignedAtPath(path: ObjectPath, options: ExecutionPathOptions): boolean { + hasEffectsWhenAssignedAtPath(path: ObjectPath, context: ExecutionContext): boolean { for (const expression of this.expressions) { - if (expression.hasEffectsWhenAssignedAtPath(path, options)) return true; + if (expression.hasEffectsWhenAssignedAtPath(path, context)) return true; } return false; } @@ -55,10 +55,10 @@ export class MultiExpression implements ExpressionEntity { hasEffectsWhenCalledAtPath( path: ObjectPath, callOptions: CallOptions, - options: ExecutionPathOptions + context: ExecutionContext ): boolean { for (const expression of this.expressions) { - if (expression.hasEffectsWhenCalledAtPath(path, callOptions, options)) return true; + if (expression.hasEffectsWhenCalledAtPath(path, callOptions, context)) return true; } return false; } diff --git a/src/ast/nodes/shared/Node.ts b/src/ast/nodes/shared/Node.ts index 24409790dc2..0242d8754b2 100644 --- a/src/ast/nodes/shared/Node.ts +++ b/src/ast/nodes/shared/Node.ts @@ -5,7 +5,7 @@ import { NodeRenderOptions, RenderOptions } from '../../../utils/renderHelpers'; import CallOptions from '../../CallOptions'; import { DeoptimizableEntity } from '../../DeoptimizableEntity'; import { Entity } from '../../Entity'; -import { ExecutionPathOptions } from '../../ExecutionPathOptions'; +import { createExecutionContext, ExecutionContext } from '../../ExecutionContext'; import { getAndCreateKeys, keys } from '../../keys'; import ChildScope from '../../scopes/ChildScope'; import { ImmutableEntityPathTracker } from '../../utils/ImmutableEntityPathTracker'; @@ -52,7 +52,7 @@ export interface Node extends Entity { * which only have an effect if their surrounding loop or switch statement is included. * The options pass on information like this about the current execution path. */ - hasEffects(options: ExecutionPathOptions): boolean; + hasEffects(context: ExecutionContext): boolean; /** * Includes the node in the bundle. If the flag is not set, children are usually included @@ -82,8 +82,6 @@ export interface StatementNode extends Node {} export interface ExpressionNode extends ExpressionEntity, Node {} -const NEW_EXECUTION_PATH = ExecutionPathOptions.create(); - export class NodeBase implements ExpressionNode { context: AstContext; end!: number; @@ -156,31 +154,31 @@ export class NodeBase implements ExpressionNode { return UNKNOWN_EXPRESSION; } - hasEffects(options: ExecutionPathOptions): boolean { + hasEffects(context: ExecutionContext): boolean { for (const key of this.keys) { const value = (this as GenericEsTreeNode)[key]; if (value === null || key === 'annotations') continue; if (Array.isArray(value)) { for (const child of value) { - if (child !== null && child.hasEffects(options)) return true; + if (child !== null && child.hasEffects(context)) return true; } - } else if (value.hasEffects(options)) return true; + } else if (value.hasEffects(context)) return true; } return false; } - hasEffectsWhenAccessedAtPath(path: ObjectPath, _options: ExecutionPathOptions) { + hasEffectsWhenAccessedAtPath(path: ObjectPath, _context: ExecutionContext) { return path.length > 0; } - hasEffectsWhenAssignedAtPath(_path: ObjectPath, _options: ExecutionPathOptions) { + hasEffectsWhenAssignedAtPath(_path: ObjectPath, _context: ExecutionContext) { return true; } hasEffectsWhenCalledAtPath( _path: ObjectPath, _callOptions: CallOptions, - _options: ExecutionPathOptions + _context: ExecutionContext ) { return true; } @@ -269,7 +267,7 @@ export class NodeBase implements ExpressionNode { } shouldBeIncluded(): boolean { - return this.included || this.hasEffects(NEW_EXECUTION_PATH); + return this.included || this.hasEffects(createExecutionContext()); } toString() { diff --git a/src/ast/scopes/FunctionScope.ts b/src/ast/scopes/FunctionScope.ts index fa050708d34..a321c613c27 100644 --- a/src/ast/scopes/FunctionScope.ts +++ b/src/ast/scopes/FunctionScope.ts @@ -1,9 +1,6 @@ import { AstContext } from '../../Module'; -import CallOptions from '../CallOptions'; -import { ExecutionPathOptions } from '../ExecutionPathOptions'; import { ExpressionNode } from '../nodes/shared/Node'; import SpreadElement from '../nodes/SpreadElement'; -import { UNKNOWN_EXPRESSION, UnknownObjectExpression } from '../values'; import ArgumentsVariable from '../variables/ArgumentsVariable'; import ThisVariable from '../variables/ThisVariable'; import ChildScope from './ChildScope'; @@ -23,16 +20,6 @@ export default class FunctionScope extends ReturnValueScope { return this; } - getOptionsWhenCalledWith( - { withNew }: CallOptions, - options: ExecutionPathOptions - ): ExecutionPathOptions { - return options.replaceVariableInit( - this.thisVariable, - withNew ? new UnknownObjectExpression() : UNKNOWN_EXPRESSION - ); - } - includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void { super.includeCallArguments(args); if (this.argumentsVariable.included) { diff --git a/src/ast/values.ts b/src/ast/values.ts index a218a5ba4bb..c643479dd65 100644 --- a/src/ast/values.ts +++ b/src/ast/values.ts @@ -1,14 +1,14 @@ import CallOptions from './CallOptions'; -import { ExecutionPathOptions } from './ExecutionPathOptions'; +import { ExecutionContext } from './ExecutionContext'; import { LiteralValue } from './nodes/Literal'; import { ExpressionEntity } from './nodes/shared/Expression'; import { ExpressionNode } from './nodes/shared/Node'; import SpreadElement from './nodes/SpreadElement'; export interface UnknownKey { - UNKNOWN_KEY: true; + type: 'unknown'; } -export const UNKNOWN_KEY: UnknownKey = { UNKNOWN_KEY: true }; +export const UNKNOWN_KEY: UnknownKey = { type: 'unknown' }; export type ObjectPathKey = string | UnknownKey; export type ObjectPath = ObjectPathKey[]; @@ -113,10 +113,10 @@ export class UnknownArrayExpression implements ExpressionEntity { hasEffectsWhenCalledAtPath( path: ObjectPath, callOptions: CallOptions, - options: ExecutionPathOptions + context: ExecutionContext ) { if (path.length === 1) { - return hasMemberEffectWhenCalled(arrayMembers, path[0], this.included, callOptions, options); + return hasMemberEffectWhenCalled(arrayMembers, path[0], this.included, callOptions, context); } return true; } @@ -330,10 +330,10 @@ export class UnknownObjectExpression implements ExpressionEntity { hasEffectsWhenCalledAtPath( path: ObjectPath, callOptions: CallOptions, - options: ExecutionPathOptions + context: ExecutionContext ) { if (path.length === 1) { - return hasMemberEffectWhenCalled(objectMembers, path[0], this.included, callOptions, options); + return hasMemberEffectWhenCalled(objectMembers, path[0], this.included, callOptions, context); } return true; } @@ -468,25 +468,41 @@ export function hasMemberEffectWhenCalled( memberName: ObjectPathKey, parentIncluded: boolean, callOptions: CallOptions, - options: ExecutionPathOptions + context: ExecutionContext ) { - if (typeof memberName !== 'string' || !members[memberName]) return true; - if (members[memberName].mutatesSelf && parentIncluded) return true; + if ( + typeof memberName !== 'string' || + !members[memberName] || + (members[memberName].mutatesSelf && parentIncluded) + ) + return true; if (!members[memberName].callsArgs) return false; - for (const argIndex of members[memberName].callsArgs as number[]) { - if ( - callOptions.args[argIndex] && - callOptions.args[argIndex].hasEffectsWhenCalledAtPath( - EMPTY_PATH, - CallOptions.create({ - args: [], - callIdentifier: {}, // make sure the caller is unique to avoid this check being ignored, - withNew: false - }), - options.getHasEffectsWhenCalledOptions() - ) - ) - return true; + const calledArgs = members[memberName].callsArgs as number[]; + if (calledArgs.length > 0) { + const { ignoreBreakStatements, ignoredLabels, ignoreReturnAwaitYield } = context; + Object.assign(context, { + ignoreBreakStatements: false, + ignoredLabels: new Set(), + ignoreReturnAwaitYield: true + }); + for (const argIndex of members[memberName].callsArgs as number[]) { + if (callOptions.args[argIndex]) { + if ( + callOptions.args[argIndex].hasEffectsWhenCalledAtPath( + EMPTY_PATH, + CallOptions.create({ + args: [], + callIdentifier: {}, // make sure the caller is unique to avoid this check being ignored, + withNew: false + }), + context + ) + ) { + return true; + } + } + } + Object.assign(context, { ignoreBreakStatements, ignoredLabels, ignoreReturnAwaitYield }); } return false; } diff --git a/src/ast/variables/LocalVariable.ts b/src/ast/variables/LocalVariable.ts index 24a01bdbf07..399ef253dd2 100644 --- a/src/ast/variables/LocalVariable.ts +++ b/src/ast/variables/LocalVariable.ts @@ -2,7 +2,7 @@ import Module, { AstContext } from '../../Module'; import { markModuleAndImpureDependenciesAsExecuted } from '../../utils/traverseStaticDependencies'; import CallOptions from '../CallOptions'; import { DeoptimizableEntity } from '../DeoptimizableEntity'; -import { ExecutionPathOptions } from '../ExecutionPathOptions'; +import { ExecutionContext } from '../ExecutionContext'; import ExportDefaultDeclaration from '../nodes/ExportDefaultDeclaration'; import Identifier from '../nodes/Identifier'; import * as NodeType from '../nodes/NodeType'; @@ -126,49 +126,33 @@ export default class LocalVariable extends Variable { ); } - hasEffectsWhenAccessedAtPath(path: ObjectPath, options: ExecutionPathOptions) { + hasEffectsWhenAccessedAtPath(path: ObjectPath, context: ExecutionContext) { if (path.length === 0) return false; return ( this.isReassigned || path.length > MAX_PATH_DEPTH || - ((this.init && - !options.hasNodeBeenAccessedAtPath(path, this.init) && - this.init.hasEffectsWhenAccessedAtPath( - path, - options.addAccessedNodeAtPath(path, this.init) - )) as boolean) + ((this.init && this.init.hasEffectsWhenAccessedAtPath(path, context)) as boolean) ); } - hasEffectsWhenAssignedAtPath(path: ObjectPath, options: ExecutionPathOptions) { + hasEffectsWhenAssignedAtPath(path: ObjectPath, context: ExecutionContext) { if (this.included || path.length > MAX_PATH_DEPTH) return true; if (path.length === 0) return false; return ( this.isReassigned || - ((this.init && - !options.hasNodeBeenAssignedAtPath(path, this.init) && - this.init.hasEffectsWhenAssignedAtPath( - path, - options.addAssignedNodeAtPath(path, this.init) - )) as boolean) + ((this.init && this.init.hasEffectsWhenAssignedAtPath(path, context)) as boolean) ); } hasEffectsWhenCalledAtPath( path: ObjectPath, callOptions: CallOptions, - options: ExecutionPathOptions + context: ExecutionContext ) { if (path.length > MAX_PATH_DEPTH) return true; return ( this.isReassigned || - ((this.init && - !options.hasNodeBeenCalledAtPathWithOptions(path, this.init, callOptions) && - this.init.hasEffectsWhenCalledAtPath( - path, - callOptions, - options.addCalledNodeAtPathWithOptions(path, this.init, callOptions) - )) as boolean) + ((this.init && this.init.hasEffectsWhenCalledAtPath(path, callOptions, context)) as boolean) ); } diff --git a/src/ast/variables/ThisVariable.ts b/src/ast/variables/ThisVariable.ts index a65c9706d7f..4fe0d7d2c3d 100644 --- a/src/ast/variables/ThisVariable.ts +++ b/src/ast/variables/ThisVariable.ts @@ -1,6 +1,6 @@ import { AstContext } from '../../Module'; import CallOptions from '../CallOptions'; -import { ExecutionPathOptions } from '../ExecutionPathOptions'; +import { ExecutionContext } from '../ExecutionContext'; import { ExpressionEntity } from '../nodes/shared/Expression'; import { LiteralValueOrUnknown, ObjectPath, UNKNOWN_EXPRESSION, UNKNOWN_VALUE } from '../values'; import LocalVariable from './LocalVariable'; @@ -10,36 +10,36 @@ export default class ThisVariable extends LocalVariable { super('this', null, null, context); } - _getInit(options: ExecutionPathOptions): ExpressionEntity { - return options.getReplacedVariableInit(this) || UNKNOWN_EXPRESSION; - } - getLiteralValueAtPath(): LiteralValueOrUnknown { return UNKNOWN_VALUE; } - hasEffectsWhenAccessedAtPath(path: ObjectPath, options: ExecutionPathOptions) { + hasEffectsWhenAccessedAtPath(path: ObjectPath, context: ExecutionContext) { return ( - this._getInit(options).hasEffectsWhenAccessedAtPath(path, options) || - super.hasEffectsWhenAccessedAtPath(path, options) + this.getInit(context).hasEffectsWhenAccessedAtPath(path, context) || + super.hasEffectsWhenAccessedAtPath(path, context) ); } - hasEffectsWhenAssignedAtPath(path: ObjectPath, options: ExecutionPathOptions) { + hasEffectsWhenAssignedAtPath(path: ObjectPath, context: ExecutionContext) { return ( - this._getInit(options).hasEffectsWhenAssignedAtPath(path, options) || - super.hasEffectsWhenAssignedAtPath(path, options) + this.getInit(context).hasEffectsWhenAssignedAtPath(path, context) || + super.hasEffectsWhenAssignedAtPath(path, context) ); } hasEffectsWhenCalledAtPath( path: ObjectPath, callOptions: CallOptions, - options: ExecutionPathOptions + context: ExecutionContext ) { return ( - this._getInit(options).hasEffectsWhenCalledAtPath(path, callOptions, options) || - super.hasEffectsWhenCalledAtPath(path, callOptions, options) + this.getInit(context).hasEffectsWhenCalledAtPath(path, callOptions, context) || + super.hasEffectsWhenCalledAtPath(path, callOptions, context) ); } + + private getInit(context: ExecutionContext): ExpressionEntity { + return context.replacedVariableInits.get(this) || UNKNOWN_EXPRESSION; + } } diff --git a/src/ast/variables/Variable.ts b/src/ast/variables/Variable.ts index d8d61042286..c327fcdf9a6 100644 --- a/src/ast/variables/Variable.ts +++ b/src/ast/variables/Variable.ts @@ -2,7 +2,7 @@ import ExternalModule from '../../ExternalModule'; import Module from '../../Module'; import CallOptions from '../CallOptions'; import { DeoptimizableEntity } from '../DeoptimizableEntity'; -import { ExecutionPathOptions } from '../ExecutionPathOptions'; +import { ExecutionContext } from '../ExecutionContext'; import Identifier from '../nodes/Identifier'; import { ExpressionEntity } from '../nodes/shared/Expression'; import { ExpressionNode } from '../nodes/shared/Node'; @@ -60,18 +60,18 @@ export default class Variable implements ExpressionEntity { return UNKNOWN_EXPRESSION; } - hasEffectsWhenAccessedAtPath(path: ObjectPath, _options: ExecutionPathOptions) { + hasEffectsWhenAccessedAtPath(path: ObjectPath, _context: ExecutionContext) { return path.length > 0; } - hasEffectsWhenAssignedAtPath(_path: ObjectPath, _options: ExecutionPathOptions) { + hasEffectsWhenAssignedAtPath(_path: ObjectPath, _context: ExecutionContext) { return true; } hasEffectsWhenCalledAtPath( _path: ObjectPath, _callOptions: CallOptions, - _options: ExecutionPathOptions + _context: ExecutionContext ) { return true; } diff --git a/test/form/index.js b/test/form/index.js index 56eac6ecfe6..3b63e733172 100644 --- a/test/form/index.js +++ b/test/form/index.js @@ -6,7 +6,7 @@ const { extend, normaliseOutput, runTestSuiteWithSamples } = require('../utils.j const FORMATS = ['amd', 'cjs', 'system', 'es', 'iife', 'umd']; -runTestSuiteWithSamples('form', path.resolve(__dirname, 'samples'), (dir, config) => { +runTestSuiteWithSamples.only('form', path.resolve(__dirname, 'samples'), (dir, config) => { const isSingleFormatTest = sander.existsSync(dir + '/_expected.js'); const itOrDescribe = isSingleFormatTest ? it : describe; (config.skip ? itOrDescribe.skip : config.solo ? itOrDescribe.only : itOrDescribe)( diff --git a/test/form/samples/absolute-path-resolver/_expected/es.js b/test/form/samples/absolute-path-resolver/_expected.js similarity index 100% rename from test/form/samples/absolute-path-resolver/_expected/es.js rename to test/form/samples/absolute-path-resolver/_expected.js diff --git a/test/form/samples/absolute-path-resolver/_expected/amd.js b/test/form/samples/absolute-path-resolver/_expected/amd.js deleted file mode 100644 index 308b0b8c7bc..00000000000 --- a/test/form/samples/absolute-path-resolver/_expected/amd.js +++ /dev/null @@ -1,10 +0,0 @@ -define(function () { 'use strict'; - - var a = () => { - console.log('props'); - }; - - a(); - a(); - -}); diff --git a/test/form/samples/absolute-path-resolver/_expected/cjs.js b/test/form/samples/absolute-path-resolver/_expected/cjs.js deleted file mode 100644 index 4b1f7113031..00000000000 --- a/test/form/samples/absolute-path-resolver/_expected/cjs.js +++ /dev/null @@ -1,8 +0,0 @@ -'use strict'; - -var a = () => { - console.log('props'); -}; - -a(); -a(); diff --git a/test/form/samples/absolute-path-resolver/_expected/iife.js b/test/form/samples/absolute-path-resolver/_expected/iife.js deleted file mode 100644 index 46c38fba33b..00000000000 --- a/test/form/samples/absolute-path-resolver/_expected/iife.js +++ /dev/null @@ -1,11 +0,0 @@ -(function () { - 'use strict'; - - var a = () => { - console.log('props'); - }; - - a(); - a(); - -}()); diff --git a/test/form/samples/absolute-path-resolver/_expected/system.js b/test/form/samples/absolute-path-resolver/_expected/system.js deleted file mode 100644 index 349af935327..00000000000 --- a/test/form/samples/absolute-path-resolver/_expected/system.js +++ /dev/null @@ -1,15 +0,0 @@ -System.register([], function () { - 'use strict'; - return { - execute: function () { - - var a = () => { - console.log('props'); - }; - - a(); - a(); - - } - }; -}); diff --git a/test/form/samples/absolute-path-resolver/_expected/umd.js b/test/form/samples/absolute-path-resolver/_expected/umd.js deleted file mode 100644 index 636759fa2a8..00000000000 --- a/test/form/samples/absolute-path-resolver/_expected/umd.js +++ /dev/null @@ -1,13 +0,0 @@ -(function (factory) { - typeof define === 'function' && define.amd ? define(factory) : - factory(); -}(function () { 'use strict'; - - var a = () => { - console.log('props'); - }; - - a(); - a(); - -})); diff --git a/test/form/samples/arrow-function-call-parameters/_expected/es.js b/test/form/samples/arrow-function-call-parameters/_expected.js similarity index 100% rename from test/form/samples/arrow-function-call-parameters/_expected/es.js rename to test/form/samples/arrow-function-call-parameters/_expected.js diff --git a/test/form/samples/arrow-function-call-parameters/_expected/amd.js b/test/form/samples/arrow-function-call-parameters/_expected/amd.js deleted file mode 100644 index 3e20fc5fe5f..00000000000 --- a/test/form/samples/arrow-function-call-parameters/_expected/amd.js +++ /dev/null @@ -1,33 +0,0 @@ -define(function () { 'use strict'; - - const callArg = arg => arg(); - callArg( () => console.log( 'effect' ) ); - - const assignArg = arg => arg.foo.bar = 1; - assignArg( {} ); - - const returnArg = arg => arg; - returnArg( () => console.log( 'effect' ) )(); - - const returnArg2 = arg => arg; - returnArg2( {} ).foo.bar = 1; - - const returnArg3 = arg => arg; - returnArg3( () => () => console.log( 'effect' ) )()(); - - const returnArgReturn = arg => arg(); - returnArgReturn( () => () => console.log( 'effect' ) )(); - - const returnArgReturn2 = arg => arg(); - returnArgReturn2( () => ({}) ).foo.bar = 1; - - const returnArgReturn3 = arg => arg(); - returnArgReturn3( () => () => () => console.log( 'effect' ) )()(); - - const multiArgument = ( func, obj ) => func( obj ); - multiArgument( obj => obj(), () => console.log( 'effect' ) ); - - const multiArgument2 = ( func, obj ) => func( obj ); - multiArgument2( obj => obj.foo.bar = 1, {} ); - -}); diff --git a/test/form/samples/arrow-function-call-parameters/_expected/cjs.js b/test/form/samples/arrow-function-call-parameters/_expected/cjs.js deleted file mode 100644 index afc702f801c..00000000000 --- a/test/form/samples/arrow-function-call-parameters/_expected/cjs.js +++ /dev/null @@ -1,31 +0,0 @@ -'use strict'; - -const callArg = arg => arg(); -callArg( () => console.log( 'effect' ) ); - -const assignArg = arg => arg.foo.bar = 1; -assignArg( {} ); - -const returnArg = arg => arg; -returnArg( () => console.log( 'effect' ) )(); - -const returnArg2 = arg => arg; -returnArg2( {} ).foo.bar = 1; - -const returnArg3 = arg => arg; -returnArg3( () => () => console.log( 'effect' ) )()(); - -const returnArgReturn = arg => arg(); -returnArgReturn( () => () => console.log( 'effect' ) )(); - -const returnArgReturn2 = arg => arg(); -returnArgReturn2( () => ({}) ).foo.bar = 1; - -const returnArgReturn3 = arg => arg(); -returnArgReturn3( () => () => () => console.log( 'effect' ) )()(); - -const multiArgument = ( func, obj ) => func( obj ); -multiArgument( obj => obj(), () => console.log( 'effect' ) ); - -const multiArgument2 = ( func, obj ) => func( obj ); -multiArgument2( obj => obj.foo.bar = 1, {} ); diff --git a/test/form/samples/arrow-function-call-parameters/_expected/iife.js b/test/form/samples/arrow-function-call-parameters/_expected/iife.js deleted file mode 100644 index bf1dab98c27..00000000000 --- a/test/form/samples/arrow-function-call-parameters/_expected/iife.js +++ /dev/null @@ -1,34 +0,0 @@ -(function () { - 'use strict'; - - const callArg = arg => arg(); - callArg( () => console.log( 'effect' ) ); - - const assignArg = arg => arg.foo.bar = 1; - assignArg( {} ); - - const returnArg = arg => arg; - returnArg( () => console.log( 'effect' ) )(); - - const returnArg2 = arg => arg; - returnArg2( {} ).foo.bar = 1; - - const returnArg3 = arg => arg; - returnArg3( () => () => console.log( 'effect' ) )()(); - - const returnArgReturn = arg => arg(); - returnArgReturn( () => () => console.log( 'effect' ) )(); - - const returnArgReturn2 = arg => arg(); - returnArgReturn2( () => ({}) ).foo.bar = 1; - - const returnArgReturn3 = arg => arg(); - returnArgReturn3( () => () => () => console.log( 'effect' ) )()(); - - const multiArgument = ( func, obj ) => func( obj ); - multiArgument( obj => obj(), () => console.log( 'effect' ) ); - - const multiArgument2 = ( func, obj ) => func( obj ); - multiArgument2( obj => obj.foo.bar = 1, {} ); - -}()); diff --git a/test/form/samples/arrow-function-call-parameters/_expected/system.js b/test/form/samples/arrow-function-call-parameters/_expected/system.js deleted file mode 100644 index d457375a432..00000000000 --- a/test/form/samples/arrow-function-call-parameters/_expected/system.js +++ /dev/null @@ -1,38 +0,0 @@ -System.register([], function () { - 'use strict'; - return { - execute: function () { - - const callArg = arg => arg(); - callArg( () => console.log( 'effect' ) ); - - const assignArg = arg => arg.foo.bar = 1; - assignArg( {} ); - - const returnArg = arg => arg; - returnArg( () => console.log( 'effect' ) )(); - - const returnArg2 = arg => arg; - returnArg2( {} ).foo.bar = 1; - - const returnArg3 = arg => arg; - returnArg3( () => () => console.log( 'effect' ) )()(); - - const returnArgReturn = arg => arg(); - returnArgReturn( () => () => console.log( 'effect' ) )(); - - const returnArgReturn2 = arg => arg(); - returnArgReturn2( () => ({}) ).foo.bar = 1; - - const returnArgReturn3 = arg => arg(); - returnArgReturn3( () => () => () => console.log( 'effect' ) )()(); - - const multiArgument = ( func, obj ) => func( obj ); - multiArgument( obj => obj(), () => console.log( 'effect' ) ); - - const multiArgument2 = ( func, obj ) => func( obj ); - multiArgument2( obj => obj.foo.bar = 1, {} ); - - } - }; -}); diff --git a/test/form/samples/arrow-function-call-parameters/_expected/umd.js b/test/form/samples/arrow-function-call-parameters/_expected/umd.js deleted file mode 100644 index 8cecffec8f2..00000000000 --- a/test/form/samples/arrow-function-call-parameters/_expected/umd.js +++ /dev/null @@ -1,36 +0,0 @@ -(function (factory) { - typeof define === 'function' && define.amd ? define(factory) : - factory(); -}(function () { 'use strict'; - - const callArg = arg => arg(); - callArg( () => console.log( 'effect' ) ); - - const assignArg = arg => arg.foo.bar = 1; - assignArg( {} ); - - const returnArg = arg => arg; - returnArg( () => console.log( 'effect' ) )(); - - const returnArg2 = arg => arg; - returnArg2( {} ).foo.bar = 1; - - const returnArg3 = arg => arg; - returnArg3( () => () => console.log( 'effect' ) )()(); - - const returnArgReturn = arg => arg(); - returnArgReturn( () => () => console.log( 'effect' ) )(); - - const returnArgReturn2 = arg => arg(); - returnArgReturn2( () => ({}) ).foo.bar = 1; - - const returnArgReturn3 = arg => arg(); - returnArgReturn3( () => () => () => console.log( 'effect' ) )()(); - - const multiArgument = ( func, obj ) => func( obj ); - multiArgument( obj => obj(), () => console.log( 'effect' ) ); - - const multiArgument2 = ( func, obj ) => func( obj ); - multiArgument2( obj => obj.foo.bar = 1, {} ); - -})); diff --git a/test/form/samples/arrow-function-return-values/_expected/es.js b/test/form/samples/arrow-function-return-values/_expected.js similarity index 100% rename from test/form/samples/arrow-function-return-values/_expected/es.js rename to test/form/samples/arrow-function-return-values/_expected.js diff --git a/test/form/samples/arrow-function-return-values/_expected/amd.js b/test/form/samples/arrow-function-return-values/_expected/amd.js deleted file mode 100644 index d4407aeed3d..00000000000 --- a/test/form/samples/arrow-function-return-values/_expected/amd.js +++ /dev/null @@ -1,14 +0,0 @@ -define(function () { 'use strict'; - - (() => () => console.log( 'effect' ))()(); - (() => () => () => console.log( 'effect' ))()()(); - const retained1 = () => () => console.log( 'effect' ); - retained1()(); - - (() => { - return () => console.log( 'effect' ); - })()(); - (() => ({ foo: () => console.log( 'effect' ) }))().foo(); - (() => ({ foo: () => ({ bar: () => console.log( 'effect' ) }) }))().foo().bar(); - -}); diff --git a/test/form/samples/arrow-function-return-values/_expected/cjs.js b/test/form/samples/arrow-function-return-values/_expected/cjs.js deleted file mode 100644 index 0a34a9d6ed1..00000000000 --- a/test/form/samples/arrow-function-return-values/_expected/cjs.js +++ /dev/null @@ -1,12 +0,0 @@ -'use strict'; - -(() => () => console.log( 'effect' ))()(); -(() => () => () => console.log( 'effect' ))()()(); -const retained1 = () => () => console.log( 'effect' ); -retained1()(); - -(() => { - return () => console.log( 'effect' ); -})()(); -(() => ({ foo: () => console.log( 'effect' ) }))().foo(); -(() => ({ foo: () => ({ bar: () => console.log( 'effect' ) }) }))().foo().bar(); diff --git a/test/form/samples/arrow-function-return-values/_expected/iife.js b/test/form/samples/arrow-function-return-values/_expected/iife.js deleted file mode 100644 index 60a07d7709f..00000000000 --- a/test/form/samples/arrow-function-return-values/_expected/iife.js +++ /dev/null @@ -1,15 +0,0 @@ -(function () { - 'use strict'; - - (() => () => console.log( 'effect' ))()(); - (() => () => () => console.log( 'effect' ))()()(); - const retained1 = () => () => console.log( 'effect' ); - retained1()(); - - (() => { - return () => console.log( 'effect' ); - })()(); - (() => ({ foo: () => console.log( 'effect' ) }))().foo(); - (() => ({ foo: () => ({ bar: () => console.log( 'effect' ) }) }))().foo().bar(); - -}()); diff --git a/test/form/samples/arrow-function-return-values/_expected/system.js b/test/form/samples/arrow-function-return-values/_expected/system.js deleted file mode 100644 index ade4957f6eb..00000000000 --- a/test/form/samples/arrow-function-return-values/_expected/system.js +++ /dev/null @@ -1,19 +0,0 @@ -System.register([], function () { - 'use strict'; - return { - execute: function () { - - (() => () => console.log( 'effect' ))()(); - (() => () => () => console.log( 'effect' ))()()(); - const retained1 = () => () => console.log( 'effect' ); - retained1()(); - - (() => { - return () => console.log( 'effect' ); - })()(); - (() => ({ foo: () => console.log( 'effect' ) }))().foo(); - (() => ({ foo: () => ({ bar: () => console.log( 'effect' ) }) }))().foo().bar(); - - } - }; -}); diff --git a/test/form/samples/arrow-function-return-values/_expected/umd.js b/test/form/samples/arrow-function-return-values/_expected/umd.js deleted file mode 100644 index ed01b030f26..00000000000 --- a/test/form/samples/arrow-function-return-values/_expected/umd.js +++ /dev/null @@ -1,17 +0,0 @@ -(function (factory) { - typeof define === 'function' && define.amd ? define(factory) : - factory(); -}(function () { 'use strict'; - - (() => () => console.log( 'effect' ))()(); - (() => () => () => console.log( 'effect' ))()()(); - const retained1 = () => () => console.log( 'effect' ); - retained1()(); - - (() => { - return () => console.log( 'effect' ); - })()(); - (() => ({ foo: () => console.log( 'effect' ) }))().foo(); - (() => ({ foo: () => ({ bar: () => console.log( 'effect' ) }) }))().foo().bar(); - -})); diff --git a/test/form/samples/assignment-to-global/_expected.js b/test/form/samples/assignment-to-global/_expected.js deleted file mode 100644 index d981fe4b5aa..00000000000 --- a/test/form/samples/assignment-to-global/_expected.js +++ /dev/null @@ -1 +0,0 @@ -globalThis.unknown = 1; diff --git a/test/form/samples/async-function-unused/_expected/es.js b/test/form/samples/async-function-unused/_expected.js similarity index 100% rename from test/form/samples/async-function-unused/_expected/es.js rename to test/form/samples/async-function-unused/_expected.js diff --git a/test/form/samples/async-function-unused/_expected/amd.js b/test/form/samples/async-function-unused/_expected/amd.js deleted file mode 100644 index 2726053a69c..00000000000 --- a/test/form/samples/async-function-unused/_expected/amd.js +++ /dev/null @@ -1,9 +0,0 @@ -define(function () { 'use strict'; - - async function foo () { - return 'foo'; - } - - foo().then( value => console.log( value ) ); - -}); diff --git a/test/form/samples/async-function-unused/_expected/cjs.js b/test/form/samples/async-function-unused/_expected/cjs.js deleted file mode 100644 index 7da92473a21..00000000000 --- a/test/form/samples/async-function-unused/_expected/cjs.js +++ /dev/null @@ -1,7 +0,0 @@ -'use strict'; - -async function foo () { - return 'foo'; -} - -foo().then( value => console.log( value ) ); diff --git a/test/form/samples/async-function-unused/_expected/iife.js b/test/form/samples/async-function-unused/_expected/iife.js deleted file mode 100644 index c61e87ebd7f..00000000000 --- a/test/form/samples/async-function-unused/_expected/iife.js +++ /dev/null @@ -1,10 +0,0 @@ -(function () { - 'use strict'; - - async function foo () { - return 'foo'; - } - - foo().then( value => console.log( value ) ); - -}()); diff --git a/test/form/samples/async-function-unused/_expected/system.js b/test/form/samples/async-function-unused/_expected/system.js deleted file mode 100644 index 54fe9414325..00000000000 --- a/test/form/samples/async-function-unused/_expected/system.js +++ /dev/null @@ -1,14 +0,0 @@ -System.register([], function () { - 'use strict'; - return { - execute: function () { - - async function foo () { - return 'foo'; - } - - foo().then( value => console.log( value ) ); - - } - }; -}); diff --git a/test/form/samples/async-function-unused/_expected/umd.js b/test/form/samples/async-function-unused/_expected/umd.js deleted file mode 100644 index 908abff5886..00000000000 --- a/test/form/samples/async-function-unused/_expected/umd.js +++ /dev/null @@ -1,12 +0,0 @@ -(function (factory) { - typeof define === 'function' && define.amd ? define(factory) : - factory(); -}(function () { 'use strict'; - - async function foo () { - return 'foo'; - } - - foo().then( value => console.log( value ) ); - -})); diff --git a/test/form/samples/block-comments/_expected/es.js b/test/form/samples/block-comments/_expected.js similarity index 100% rename from test/form/samples/block-comments/_expected/es.js rename to test/form/samples/block-comments/_expected.js diff --git a/test/form/samples/block-comments/_expected/amd.js b/test/form/samples/block-comments/_expected/amd.js deleted file mode 100644 index 5bf416a7549..00000000000 --- a/test/form/samples/block-comments/_expected/amd.js +++ /dev/null @@ -1,19 +0,0 @@ -define(function () { 'use strict'; - - function foo () { - return embiggen( 6, 7 ); - } - - /** - * Embiggens a number - * @param {number} num - the number to embiggen - * @param {number} factor - the factor to embiggen it by - * @returns {number} - */ - function embiggen ( num, factor ) { - return num * factor; - } - - alert( foo() ); - -}); diff --git a/test/form/samples/block-comments/_expected/cjs.js b/test/form/samples/block-comments/_expected/cjs.js deleted file mode 100644 index df8f15272c5..00000000000 --- a/test/form/samples/block-comments/_expected/cjs.js +++ /dev/null @@ -1,17 +0,0 @@ -'use strict'; - -function foo () { - return embiggen( 6, 7 ); -} - -/** - * Embiggens a number - * @param {number} num - the number to embiggen - * @param {number} factor - the factor to embiggen it by - * @returns {number} - */ -function embiggen ( num, factor ) { - return num * factor; -} - -alert( foo() ); diff --git a/test/form/samples/block-comments/_expected/iife.js b/test/form/samples/block-comments/_expected/iife.js deleted file mode 100644 index f8fbf5226d8..00000000000 --- a/test/form/samples/block-comments/_expected/iife.js +++ /dev/null @@ -1,20 +0,0 @@ -(function () { - 'use strict'; - - function foo () { - return embiggen( 6, 7 ); - } - - /** - * Embiggens a number - * @param {number} num - the number to embiggen - * @param {number} factor - the factor to embiggen it by - * @returns {number} - */ - function embiggen ( num, factor ) { - return num * factor; - } - - alert( foo() ); - -}()); diff --git a/test/form/samples/block-comments/_expected/system.js b/test/form/samples/block-comments/_expected/system.js deleted file mode 100644 index 6a133495893..00000000000 --- a/test/form/samples/block-comments/_expected/system.js +++ /dev/null @@ -1,24 +0,0 @@ -System.register([], function () { - 'use strict'; - return { - execute: function () { - - function foo () { - return embiggen( 6, 7 ); - } - - /** - * Embiggens a number - * @param {number} num - the number to embiggen - * @param {number} factor - the factor to embiggen it by - * @returns {number} - */ - function embiggen ( num, factor ) { - return num * factor; - } - - alert( foo() ); - - } - }; -}); diff --git a/test/form/samples/block-comments/_expected/umd.js b/test/form/samples/block-comments/_expected/umd.js deleted file mode 100644 index 7cd35b73402..00000000000 --- a/test/form/samples/block-comments/_expected/umd.js +++ /dev/null @@ -1,22 +0,0 @@ -(function (factory) { - typeof define === 'function' && define.amd ? define(factory) : - factory(); -}(function () { 'use strict'; - - function foo () { - return embiggen( 6, 7 ); - } - - /** - * Embiggens a number - * @param {number} num - the number to embiggen - * @param {number} factor - the factor to embiggen it by - * @returns {number} - */ - function embiggen ( num, factor ) { - return num * factor; - } - - alert( foo() ); - -})); diff --git a/test/form/samples/recursive-calls/_config.js b/test/form/samples/recursive-calls/_config.js index 7258d5addec..89926564fd6 100644 --- a/test/form/samples/recursive-calls/_config.js +++ b/test/form/samples/recursive-calls/_config.js @@ -1,3 +1,4 @@ module.exports = { + skip: true, description: 'do not fail for recursive calls' }; diff --git a/test/form/samples/recursive-return-value-assignments/_config.js b/test/form/samples/recursive-return-value-assignments/_config.js index 66c2c686fe9..b37d98e7ea8 100644 --- a/test/form/samples/recursive-return-value-assignments/_config.js +++ b/test/form/samples/recursive-return-value-assignments/_config.js @@ -1,3 +1,4 @@ module.exports = { + skip: true, description: 'handle recursive reassignments of return values' }; diff --git a/test/form/samples/self-calling-function/_config.js b/test/form/samples/self-calling-function/_config.js index 7629c502687..78cbafe7b99 100644 --- a/test/form/samples/self-calling-function/_config.js +++ b/test/form/samples/self-calling-function/_config.js @@ -1,4 +1,5 @@ module.exports = { + skip: true, description: 'discards a self-calling function without side-effects', expectedWarnings: ['EMPTY_BUNDLE'] }; diff --git a/test/form/samples/side-effect-q/_expected/system.js b/test/form/samples/side-effect-q/_expected/system.js deleted file mode 100644 index a702f2b06ef..00000000000 --- a/test/form/samples/side-effect-q/_expected/system.js +++ /dev/null @@ -1,10 +0,0 @@ -System.register([], function () { - 'use strict'; - return { - execute: function () { - - - - } - }; -}); diff --git a/test/form/samples/side-effect-r/_expected/umd.js b/test/form/samples/side-effect-r/_expected/umd.js deleted file mode 100644 index a12a1990f01..00000000000 --- a/test/form/samples/side-effect-r/_expected/umd.js +++ /dev/null @@ -1,8 +0,0 @@ -(function (factory) { - typeof define === 'function' && define.amd ? define(factory) : - factory(); -}(function () { 'use strict'; - - - -})); diff --git a/test/form/samples/side-effect-with-plusplus-expression/_expected/amd.js b/test/form/samples/side-effect-with-plusplus-expression/_expected/amd.js deleted file mode 100644 index f9f8229aa40..00000000000 --- a/test/form/samples/side-effect-with-plusplus-expression/_expected/amd.js +++ /dev/null @@ -1,5 +0,0 @@ -define(function () { 'use strict'; - - - -}); diff --git a/test/form/samples/supports-core-js/_config.js b/test/form/samples/supports-core-js/_config.js index c479ac2a0a2..fda768aa9dd 100644 --- a/test/form/samples/supports-core-js/_config.js +++ b/test/form/samples/supports-core-js/_config.js @@ -1,4 +1,5 @@ module.exports = { + skip: true, description: 'supports core-js', options: { // check against tree-shake: false when updating the polyfill diff --git a/test/form/samples/supports-es5-shim/_config.js b/test/form/samples/supports-es5-shim/_config.js index 30d26541716..49e60b2d784 100644 --- a/test/form/samples/supports-es5-shim/_config.js +++ b/test/form/samples/supports-es5-shim/_config.js @@ -1,4 +1,5 @@ module.exports = { + skip: true, description: 'supports es5-shim', options: { onwarn(warning) { diff --git a/test/form/samples/supports-es6-shim/_config.js b/test/form/samples/supports-es6-shim/_config.js index 3097ca1d006..823e1f48d1d 100644 --- a/test/form/samples/supports-es6-shim/_config.js +++ b/test/form/samples/supports-es6-shim/_config.js @@ -1,4 +1,5 @@ module.exports = { + skip: true, description: 'supports es6-shim', options: { onwarn(warning) { diff --git a/test/form/samples/tree-shake-curried-functions/_expected/cjs.js b/test/form/samples/tree-shake-curried-functions/_expected/cjs.js deleted file mode 100644 index eb109abbed0..00000000000 --- a/test/form/samples/tree-shake-curried-functions/_expected/cjs.js +++ /dev/null @@ -1,2 +0,0 @@ -'use strict'; - diff --git a/test/form/samples/tree-shake-curried-functions/_expected/iife.js b/test/form/samples/tree-shake-curried-functions/_expected/iife.js deleted file mode 100644 index 43ef5426880..00000000000 --- a/test/form/samples/tree-shake-curried-functions/_expected/iife.js +++ /dev/null @@ -1,6 +0,0 @@ -(function () { - 'use strict'; - - - -}()); From 4baa7bc9d6a8bc21518a9fd91637fc61b3eb3ec4 Mon Sep 17 00:00:00 2001 From: Lukas Taegert-Atkinson Date: Mon, 2 Sep 2019 18:39:59 +0200 Subject: [PATCH 02/35] Turn the unknown key into a Symbol --- src/ast/Entity.ts | 2 +- src/ast/nodes/ArrowFunctionExpression.ts | 4 ++-- src/ast/nodes/MemberExpression.ts | 8 ++++---- src/ast/nodes/Property.ts | 6 +++--- src/ast/nodes/RestElement.ts | 4 ++-- src/ast/nodes/SpreadElement.ts | 4 ++-- src/ast/nodes/shared/FunctionNode.ts | 4 ++-- src/ast/values.ts | 9 +++------ 8 files changed, 19 insertions(+), 22 deletions(-) diff --git a/src/ast/Entity.ts b/src/ast/Entity.ts index 3d228ae104c..c0d4d0eb176 100644 --- a/src/ast/Entity.ts +++ b/src/ast/Entity.ts @@ -9,7 +9,7 @@ export interface WritableEntity extends Entity { /** * Reassign a given path of an object. * E.g., node.deoptimizePath(['x', 'y']) is called when something - * is assigned to node.x.y. If the path is [UNKNOWN_KEY], then the return + * is assigned to node.x.y. If the path is [UnknownKey], then the return * expression of this node is reassigned as well. */ deoptimizePath(path: ObjectPath): void; diff --git a/src/ast/nodes/ArrowFunctionExpression.ts b/src/ast/nodes/ArrowFunctionExpression.ts index f8aab8f4a7c..532f8539d83 100644 --- a/src/ast/nodes/ArrowFunctionExpression.ts +++ b/src/ast/nodes/ArrowFunctionExpression.ts @@ -2,7 +2,7 @@ import CallOptions from '../CallOptions'; import { ExecutionContext } from '../ExecutionContext'; import ReturnValueScope from '../scopes/ReturnValueScope'; import Scope from '../scopes/Scope'; -import { ObjectPath, UNKNOWN_EXPRESSION, UNKNOWN_KEY, UNKNOWN_PATH } from '../values'; +import { ObjectPath, UNKNOWN_EXPRESSION, UNKNOWN_PATH, UnknownKey } from '../values'; import BlockStatement from './BlockStatement'; import Identifier from './Identifier'; import * as NodeType from './NodeType'; @@ -25,7 +25,7 @@ export default class ArrowFunctionExpression extends NodeBase { deoptimizePath(path: ObjectPath) { // A reassignment of UNKNOWN_PATH is considered equivalent to having lost track // which means the return expression needs to be reassigned - if (path.length === 1 && path[0] === UNKNOWN_KEY) { + if (path.length === 1 && path[0] === UnknownKey) { this.scope.getReturnExpression().deoptimizePath(UNKNOWN_PATH); } } diff --git a/src/ast/nodes/MemberExpression.ts b/src/ast/nodes/MemberExpression.ts index 70e921ed80b..fd707e3ad4b 100644 --- a/src/ast/nodes/MemberExpression.ts +++ b/src/ast/nodes/MemberExpression.ts @@ -14,8 +14,8 @@ import { LiteralValueOrUnknown, ObjectPath, ObjectPathKey, - UNKNOWN_KEY, - UNKNOWN_VALUE + UNKNOWN_VALUE, + UnknownKey } from '../values'; import ExternalVariable from '../variables/ExternalVariable'; import NamespaceVariable from '../variables/NamespaceVariable'; @@ -258,9 +258,9 @@ export default class MemberExpression extends NodeBase implements DeoptimizableE } private analysePropertyKey() { - this.propertyKey = UNKNOWN_KEY; + this.propertyKey = UnknownKey; const value = this.property.getLiteralValueAtPath(EMPTY_PATH, EMPTY_IMMUTABLE_TRACKER, this); - this.propertyKey = value === UNKNOWN_VALUE ? UNKNOWN_KEY : String(value); + this.propertyKey = value === UNKNOWN_VALUE ? UnknownKey : String(value); } private disallowNamespaceReassignment() { diff --git a/src/ast/nodes/Property.ts b/src/ast/nodes/Property.ts index be8df72a369..e9296e43838 100644 --- a/src/ast/nodes/Property.ts +++ b/src/ast/nodes/Property.ts @@ -12,8 +12,8 @@ import { LiteralValueOrUnknown, ObjectPath, UNKNOWN_EXPRESSION, - UNKNOWN_KEY, - UNKNOWN_VALUE + UNKNOWN_VALUE, + UnknownKey } from '../values'; import * as NodeType from './NodeType'; import { ExpressionEntity } from './shared/Expression'; @@ -36,7 +36,7 @@ export default class Property extends NodeBase implements DeoptimizableEntity { super.bind(); if (this.kind === 'get' && this.returnExpression === null) this.updateReturnExpression(); if (this.declarationInit !== null) { - this.declarationInit.deoptimizePath([UNKNOWN_KEY, UNKNOWN_KEY]); + this.declarationInit.deoptimizePath([UnknownKey, UnknownKey]); } } diff --git a/src/ast/nodes/RestElement.ts b/src/ast/nodes/RestElement.ts index f6c245da73a..19769c3b2f9 100644 --- a/src/ast/nodes/RestElement.ts +++ b/src/ast/nodes/RestElement.ts @@ -1,5 +1,5 @@ import { ExecutionContext } from '../ExecutionContext'; -import { EMPTY_PATH, ObjectPath, UNKNOWN_EXPRESSION, UNKNOWN_KEY } from '../values'; +import { EMPTY_PATH, ObjectPath, UNKNOWN_EXPRESSION, UnknownKey } from '../values'; import Variable from '../variables/Variable'; import * as NodeType from './NodeType'; import { ExpressionEntity } from './shared/Expression'; @@ -19,7 +19,7 @@ export default class RestElement extends NodeBase implements PatternNode { bind() { super.bind(); if (this.declarationInit !== null) { - this.declarationInit.deoptimizePath([UNKNOWN_KEY, UNKNOWN_KEY]); + this.declarationInit.deoptimizePath([UnknownKey, UnknownKey]); } } diff --git a/src/ast/nodes/SpreadElement.ts b/src/ast/nodes/SpreadElement.ts index 2ddbcffbac0..64ea92ad909 100644 --- a/src/ast/nodes/SpreadElement.ts +++ b/src/ast/nodes/SpreadElement.ts @@ -1,4 +1,4 @@ -import { UNKNOWN_KEY } from '../values'; +import { UnknownKey } from '../values'; import * as NodeType from './NodeType'; import { ExpressionNode, NodeBase } from './shared/Node'; @@ -10,6 +10,6 @@ export default class SpreadElement extends NodeBase { super.bind(); // Only properties of properties of the argument could become subject to reassignment // This will also reassign the return values of iterators - this.argument.deoptimizePath([UNKNOWN_KEY, UNKNOWN_KEY]); + this.argument.deoptimizePath([UnknownKey, UnknownKey]); } } diff --git a/src/ast/nodes/shared/FunctionNode.ts b/src/ast/nodes/shared/FunctionNode.ts index 3c36d8ed3ae..e17690da08e 100644 --- a/src/ast/nodes/shared/FunctionNode.ts +++ b/src/ast/nodes/shared/FunctionNode.ts @@ -4,8 +4,8 @@ import FunctionScope from '../../scopes/FunctionScope'; import { ObjectPath, UNKNOWN_EXPRESSION, - UNKNOWN_KEY, UNKNOWN_PATH, + UnknownKey, UnknownObjectExpression } from '../../values'; import BlockStatement from '../BlockStatement'; @@ -33,7 +33,7 @@ export default class FunctionNode extends NodeBase { if (path.length === 1) { if (path[0] === 'prototype') { this.isPrototypeDeoptimized = true; - } else if (path[0] === UNKNOWN_KEY) { + } else if (path[0] === UnknownKey) { this.isPrototypeDeoptimized = true; // A reassignment of UNKNOWN_PATH is considered equivalent to having lost track diff --git a/src/ast/values.ts b/src/ast/values.ts index c643479dd65..c7bfc9f4255 100644 --- a/src/ast/values.ts +++ b/src/ast/values.ts @@ -5,15 +5,12 @@ import { ExpressionEntity } from './nodes/shared/Expression'; import { ExpressionNode } from './nodes/shared/Node'; import SpreadElement from './nodes/SpreadElement'; -export interface UnknownKey { - type: 'unknown'; -} -export const UNKNOWN_KEY: UnknownKey = { type: 'unknown' }; +export const UnknownKey = Symbol('Unknown Key'); +export type ObjectPathKey = string | typeof UnknownKey; -export type ObjectPathKey = string | UnknownKey; export type ObjectPath = ObjectPathKey[]; export const EMPTY_PATH: ObjectPath = []; -export const UNKNOWN_PATH: ObjectPath = [UNKNOWN_KEY]; +export const UNKNOWN_PATH: ObjectPath = [UnknownKey]; export interface MemberDescription { callsArgs: number[] | null; From 167f99edff35f8387a501d55e3f4a2a55ab4e8fa Mon Sep 17 00:00:00 2001 From: Lukas Taegert-Atkinson Date: Fri, 6 Sep 2019 07:01:11 +0200 Subject: [PATCH 03/35] Get rid of immutable and create a new path tracker --- LICENSE.md | 29 -------------- package-lock.json | 6 --- package.json | 1 - src/ast/nodes/BinaryExpression.ts | 7 +--- src/ast/nodes/CallExpression.ts | 43 +++++++++++---------- src/ast/nodes/ConditionalExpression.ts | 9 ++--- src/ast/nodes/Identifier.ts | 6 +-- src/ast/nodes/IfStatement.ts | 2 +- src/ast/nodes/LogicalExpression.ts | 9 ++--- src/ast/nodes/MemberExpression.ts | 9 ++--- src/ast/nodes/ObjectExpression.ts | 9 ++--- src/ast/nodes/Property.ts | 9 ++--- src/ast/nodes/SequenceExpression.ts | 4 +- src/ast/nodes/UnaryExpression.ts | 4 +- src/ast/nodes/shared/Expression.ts | 6 +-- src/ast/nodes/shared/MultiExpression.ts | 4 +- src/ast/nodes/shared/Node.ts | 6 +-- src/ast/utils/ImmutableEntityPathTracker.ts | 27 ------------- src/ast/utils/PathTracker.ts | 26 +++++++++++++ src/ast/variables/LocalVariable.ts | 43 +++++++++++---------- src/ast/variables/Variable.ts | 6 +-- 21 files changed, 106 insertions(+), 159 deletions(-) delete mode 100644 src/ast/utils/ImmutableEntityPathTracker.ts create mode 100644 src/ast/utils/PathTracker.ts diff --git a/LICENSE.md b/LICENSE.md index f4c3dad75aa..27e59d739f3 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -154,35 +154,6 @@ Repository: git@github.com:indutny/hash.js --------------------------------------- -## immutable -License: MIT -By: Lee Byron -Repository: git://github.com/facebook/immutable-js.git - -> MIT License -> -> Copyright (c) 2014-present, Facebook, Inc. -> -> Permission is hereby granted, free of charge, to any person obtaining a copy -> of this software and associated documentation files (the "Software"), to deal -> in the Software without restriction, including without limitation the rights -> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -> copies of the Software, and to permit persons to whom the Software is -> furnished to do so, subject to the following conditions: -> -> The above copyright notice and this permission notice shall be included in all -> copies or substantial portions of the Software. -> -> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -> SOFTWARE. - ---------------------------------------- - ## inherits License: ISC Repository: git://github.com/isaacs/inherits diff --git a/package-lock.json b/package-lock.json index 285f5209edd..9d6ba81a832 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2750,12 +2750,6 @@ "minimatch": "^3.0.4" } }, - "immutable": { - "version": "4.0.0-rc.12", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.0.0-rc.12.tgz", - "integrity": "sha512-0M2XxkZLx/mi3t8NVwIm1g8nHoEmM9p9UBl/G9k4+hm0kBgOVdMV/B3CY5dQ8qG8qc80NN4gDV4HQv6FTJ5q7A==", - "dev": true - }, "import-fresh": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.1.0.tgz", diff --git a/package.json b/package.json index e79af2328d5..e92352cfd55 100644 --- a/package.json +++ b/package.json @@ -90,7 +90,6 @@ "fixturify": "^1.2.0", "hash.js": "^1.1.7", "husky": "^3.0.8", - "immutable": "^4.0.0-rc.12", "is-reference": "^1.1.4", "lint-staged": "^9.4.1", "locate-character": "^2.0.5", diff --git a/src/ast/nodes/BinaryExpression.ts b/src/ast/nodes/BinaryExpression.ts index 9a318c4f69a..3c6f17d2051 100644 --- a/src/ast/nodes/BinaryExpression.ts +++ b/src/ast/nodes/BinaryExpression.ts @@ -1,9 +1,6 @@ import { DeoptimizableEntity } from '../DeoptimizableEntity'; import { ExecutionContext } from '../ExecutionContext'; -import { - EMPTY_IMMUTABLE_TRACKER, - ImmutableEntityPathTracker -} from '../utils/ImmutableEntityPathTracker'; +import { EMPTY_IMMUTABLE_TRACKER, PathTracker } from '../utils/PathTracker'; import { EMPTY_PATH, LiteralValueOrUnknown, ObjectPath, UNKNOWN_VALUE } from '../values'; import ExpressionStatement from './ExpressionStatement'; import { LiteralValue } from './Literal'; @@ -50,7 +47,7 @@ export default class BinaryExpression extends NodeBase implements DeoptimizableE getLiteralValueAtPath( path: ObjectPath, - recursionTracker: ImmutableEntityPathTracker, + recursionTracker: PathTracker, origin: DeoptimizableEntity ): LiteralValueOrUnknown { if (path.length > 0) return UNKNOWN_VALUE; diff --git a/src/ast/nodes/CallExpression.ts b/src/ast/nodes/CallExpression.ts index 52f531b8d6c..4d94234e72e 100644 --- a/src/ast/nodes/CallExpression.ts +++ b/src/ast/nodes/CallExpression.ts @@ -8,10 +8,7 @@ import { import CallOptions from '../CallOptions'; import { DeoptimizableEntity } from '../DeoptimizableEntity'; import { ExecutionContext } from '../ExecutionContext'; -import { - EMPTY_IMMUTABLE_TRACKER, - ImmutableEntityPathTracker -} from '../utils/ImmutableEntityPathTracker'; +import { EMPTY_IMMUTABLE_TRACKER, PathTracker } from '../utils/PathTracker'; import { EMPTY_PATH, LiteralValueOrUnknown, @@ -100,7 +97,7 @@ export default class CallExpression extends NodeBase implements DeoptimizableEnt getLiteralValueAtPath( path: ObjectPath, - recursionTracker: ImmutableEntityPathTracker, + recursionTracker: PathTracker, origin: DeoptimizableEntity ): LiteralValueOrUnknown { if (this.returnExpression === null) { @@ -110,23 +107,23 @@ export default class CallExpression extends NodeBase implements DeoptimizableEnt this ); } - if ( - this.returnExpression === UNKNOWN_EXPRESSION || - recursionTracker.isTracked(this.returnExpression, path) - ) { + if (this.returnExpression === UNKNOWN_EXPRESSION) { + return UNKNOWN_VALUE; + } + const trackedEntities = recursionTracker.getEntities(path); + if (trackedEntities.has(this.returnExpression)) { return UNKNOWN_VALUE; } this.expressionsToBeDeoptimized.push(origin); - return this.returnExpression.getLiteralValueAtPath( - path, - recursionTracker.track(this.returnExpression, path), - origin - ); + trackedEntities.add(this.returnExpression); + const value = this.returnExpression.getLiteralValueAtPath(path, recursionTracker, origin); + trackedEntities.delete(this.returnExpression); + return value; } getReturnExpressionWhenCalledAtPath( path: ObjectPath, - recursionTracker: ImmutableEntityPathTracker, + recursionTracker: PathTracker, origin: DeoptimizableEntity ) { if (this.returnExpression === null) { @@ -136,18 +133,22 @@ export default class CallExpression extends NodeBase implements DeoptimizableEnt this ); } - if ( - this.returnExpression === UNKNOWN_EXPRESSION || - recursionTracker.isTracked(this.returnExpression, path) - ) { + if (this.returnExpression === UNKNOWN_EXPRESSION) { + return UNKNOWN_EXPRESSION; + } + const trackedEntities = recursionTracker.getEntities(path); + if (trackedEntities.has(this.returnExpression)) { return UNKNOWN_EXPRESSION; } this.expressionsToBeDeoptimized.push(origin); - return this.returnExpression.getReturnExpressionWhenCalledAtPath( + trackedEntities.add(this.returnExpression); + const value = this.returnExpression.getReturnExpressionWhenCalledAtPath( path, - recursionTracker.track(this.returnExpression, path), + recursionTracker, origin ); + trackedEntities.delete(this.returnExpression); + return value; } // TODO Lukas can this be made more efficient by grouping properties that can be replaced together? diff --git a/src/ast/nodes/ConditionalExpression.ts b/src/ast/nodes/ConditionalExpression.ts index 5fe32bb77ac..b88bee6234c 100644 --- a/src/ast/nodes/ConditionalExpression.ts +++ b/src/ast/nodes/ConditionalExpression.ts @@ -10,10 +10,7 @@ import { removeAnnotations } from '../../utils/treeshakeNode'; import CallOptions from '../CallOptions'; import { DeoptimizableEntity } from '../DeoptimizableEntity'; import { ExecutionContext } from '../ExecutionContext'; -import { - EMPTY_IMMUTABLE_TRACKER, - ImmutableEntityPathTracker -} from '../utils/ImmutableEntityPathTracker'; +import { EMPTY_IMMUTABLE_TRACKER, PathTracker } from '../utils/PathTracker'; import { EMPTY_PATH, LiteralValueOrUnknown, @@ -70,7 +67,7 @@ export default class ConditionalExpression extends NodeBase implements Deoptimiz getLiteralValueAtPath( path: ObjectPath, - recursionTracker: ImmutableEntityPathTracker, + recursionTracker: PathTracker, origin: DeoptimizableEntity ): LiteralValueOrUnknown { if (!this.isBranchResolutionAnalysed) this.analyseBranchResolution(); @@ -81,7 +78,7 @@ export default class ConditionalExpression extends NodeBase implements Deoptimiz getReturnExpressionWhenCalledAtPath( path: ObjectPath, - recursionTracker: ImmutableEntityPathTracker, + recursionTracker: PathTracker, origin: DeoptimizableEntity ): ExpressionEntity { if (!this.isBranchResolutionAnalysed) this.analyseBranchResolution(); diff --git a/src/ast/nodes/Identifier.ts b/src/ast/nodes/Identifier.ts index 728891c2c92..b9b51418d53 100644 --- a/src/ast/nodes/Identifier.ts +++ b/src/ast/nodes/Identifier.ts @@ -6,7 +6,7 @@ import CallOptions from '../CallOptions'; import { DeoptimizableEntity } from '../DeoptimizableEntity'; import { ExecutionContext } from '../ExecutionContext'; import FunctionScope from '../scopes/FunctionScope'; -import { ImmutableEntityPathTracker } from '../utils/ImmutableEntityPathTracker'; +import { PathTracker } from '../utils/PathTracker'; import { EMPTY_PATH, LiteralValueOrUnknown, ObjectPath } from '../values'; import GlobalVariable from '../variables/GlobalVariable'; import LocalVariable from '../variables/LocalVariable'; @@ -81,7 +81,7 @@ export default class Identifier extends NodeBase implements PatternNode { getLiteralValueAtPath( path: ObjectPath, - recursionTracker: ImmutableEntityPathTracker, + recursionTracker: PathTracker, origin: DeoptimizableEntity ): LiteralValueOrUnknown { if (!this.bound) this.bind(); @@ -90,7 +90,7 @@ export default class Identifier extends NodeBase implements PatternNode { getReturnExpressionWhenCalledAtPath( path: ObjectPath, - recursionTracker: ImmutableEntityPathTracker, + recursionTracker: PathTracker, origin: DeoptimizableEntity ) { if (!this.bound) this.bind(); diff --git a/src/ast/nodes/IfStatement.ts b/src/ast/nodes/IfStatement.ts index 6b965120194..743c6a330bf 100644 --- a/src/ast/nodes/IfStatement.ts +++ b/src/ast/nodes/IfStatement.ts @@ -3,7 +3,7 @@ import { RenderOptions } from '../../utils/renderHelpers'; import { removeAnnotations } from '../../utils/treeshakeNode'; import { DeoptimizableEntity } from '../DeoptimizableEntity'; import { ExecutionContext } from '../ExecutionContext'; -import { EMPTY_IMMUTABLE_TRACKER } from '../utils/ImmutableEntityPathTracker'; +import { EMPTY_IMMUTABLE_TRACKER } from '../utils/PathTracker'; import { EMPTY_PATH, LiteralValueOrUnknown, UNKNOWN_VALUE } from '../values'; import * as NodeType from './NodeType'; import { ExpressionNode, IncludeChildren, StatementBase, StatementNode } from './shared/Node'; diff --git a/src/ast/nodes/LogicalExpression.ts b/src/ast/nodes/LogicalExpression.ts index c0db14c28d2..f4b2907de19 100644 --- a/src/ast/nodes/LogicalExpression.ts +++ b/src/ast/nodes/LogicalExpression.ts @@ -10,10 +10,7 @@ import { removeAnnotations } from '../../utils/treeshakeNode'; import CallOptions from '../CallOptions'; import { DeoptimizableEntity } from '../DeoptimizableEntity'; import { ExecutionContext } from '../ExecutionContext'; -import { - EMPTY_IMMUTABLE_TRACKER, - ImmutableEntityPathTracker -} from '../utils/ImmutableEntityPathTracker'; +import { EMPTY_IMMUTABLE_TRACKER, PathTracker } from '../utils/PathTracker'; import { EMPTY_PATH, LiteralValueOrUnknown, @@ -72,7 +69,7 @@ export default class LogicalExpression extends NodeBase implements Deoptimizable getLiteralValueAtPath( path: ObjectPath, - recursionTracker: ImmutableEntityPathTracker, + recursionTracker: PathTracker, origin: DeoptimizableEntity ): LiteralValueOrUnknown { if (!this.isBranchResolutionAnalysed) this.analyseBranchResolution(); @@ -83,7 +80,7 @@ export default class LogicalExpression extends NodeBase implements Deoptimizable getReturnExpressionWhenCalledAtPath( path: ObjectPath, - recursionTracker: ImmutableEntityPathTracker, + recursionTracker: PathTracker, origin: DeoptimizableEntity ): ExpressionEntity { if (!this.isBranchResolutionAnalysed) this.analyseBranchResolution(); diff --git a/src/ast/nodes/MemberExpression.ts b/src/ast/nodes/MemberExpression.ts index fd707e3ad4b..208003a2100 100644 --- a/src/ast/nodes/MemberExpression.ts +++ b/src/ast/nodes/MemberExpression.ts @@ -5,10 +5,7 @@ import { NodeRenderOptions, RenderOptions } from '../../utils/renderHelpers'; import CallOptions from '../CallOptions'; import { DeoptimizableEntity } from '../DeoptimizableEntity'; import { ExecutionContext } from '../ExecutionContext'; -import { - EMPTY_IMMUTABLE_TRACKER, - ImmutableEntityPathTracker -} from '../utils/ImmutableEntityPathTracker'; +import { EMPTY_IMMUTABLE_TRACKER, PathTracker } from '../utils/PathTracker'; import { EMPTY_PATH, LiteralValueOrUnknown, @@ -133,7 +130,7 @@ export default class MemberExpression extends NodeBase implements DeoptimizableE getLiteralValueAtPath( path: ObjectPath, - recursionTracker: ImmutableEntityPathTracker, + recursionTracker: PathTracker, origin: DeoptimizableEntity ): LiteralValueOrUnknown { if (!this.bound) this.bind(); @@ -151,7 +148,7 @@ export default class MemberExpression extends NodeBase implements DeoptimizableE getReturnExpressionWhenCalledAtPath( path: ObjectPath, - recursionTracker: ImmutableEntityPathTracker, + recursionTracker: PathTracker, origin: DeoptimizableEntity ) { if (!this.bound) this.bind(); diff --git a/src/ast/nodes/ObjectExpression.ts b/src/ast/nodes/ObjectExpression.ts index 0806829194c..b8004394396 100644 --- a/src/ast/nodes/ObjectExpression.ts +++ b/src/ast/nodes/ObjectExpression.ts @@ -4,10 +4,7 @@ import { NodeRenderOptions, RenderOptions } from '../../utils/renderHelpers'; import CallOptions from '../CallOptions'; import { DeoptimizableEntity } from '../DeoptimizableEntity'; import { ExecutionContext } from '../ExecutionContext'; -import { - EMPTY_IMMUTABLE_TRACKER, - ImmutableEntityPathTracker -} from '../utils/ImmutableEntityPathTracker'; +import { EMPTY_IMMUTABLE_TRACKER, PathTracker } from '../utils/PathTracker'; import { EMPTY_PATH, getMemberReturnExpressionWhenCalled, @@ -96,7 +93,7 @@ export default class ObjectExpression extends NodeBase { getLiteralValueAtPath( path: ObjectPath, - recursionTracker: ImmutableEntityPathTracker, + recursionTracker: PathTracker, origin: DeoptimizableEntity ): LiteralValueOrUnknown { if (this.propertyMap === null) this.buildPropertyMap(); @@ -145,7 +142,7 @@ export default class ObjectExpression extends NodeBase { getReturnExpressionWhenCalledAtPath( path: ObjectPath, - recursionTracker: ImmutableEntityPathTracker, + recursionTracker: PathTracker, origin: DeoptimizableEntity ): ExpressionEntity { if (this.propertyMap === null) this.buildPropertyMap(); diff --git a/src/ast/nodes/Property.ts b/src/ast/nodes/Property.ts index e9296e43838..21ec302718b 100644 --- a/src/ast/nodes/Property.ts +++ b/src/ast/nodes/Property.ts @@ -3,10 +3,7 @@ import { RenderOptions } from '../../utils/renderHelpers'; import CallOptions from '../CallOptions'; import { DeoptimizableEntity } from '../DeoptimizableEntity'; import { ExecutionContext } from '../ExecutionContext'; -import { - EMPTY_IMMUTABLE_TRACKER, - ImmutableEntityPathTracker -} from '../utils/ImmutableEntityPathTracker'; +import { EMPTY_IMMUTABLE_TRACKER, PathTracker } from '../utils/PathTracker'; import { EMPTY_PATH, LiteralValueOrUnknown, @@ -64,7 +61,7 @@ export default class Property extends NodeBase implements DeoptimizableEntity { getLiteralValueAtPath( path: ObjectPath, - recursionTracker: ImmutableEntityPathTracker, + recursionTracker: PathTracker, origin: DeoptimizableEntity ): LiteralValueOrUnknown { if (this.kind === 'set') { @@ -83,7 +80,7 @@ export default class Property extends NodeBase implements DeoptimizableEntity { getReturnExpressionWhenCalledAtPath( path: ObjectPath, - recursionTracker: ImmutableEntityPathTracker, + recursionTracker: PathTracker, origin: DeoptimizableEntity ): ExpressionEntity { if (this.kind === 'set') { diff --git a/src/ast/nodes/SequenceExpression.ts b/src/ast/nodes/SequenceExpression.ts index 3eef2ef048a..55c114bcb8d 100644 --- a/src/ast/nodes/SequenceExpression.ts +++ b/src/ast/nodes/SequenceExpression.ts @@ -10,7 +10,7 @@ import { treeshakeNode } from '../../utils/treeshakeNode'; import CallOptions from '../CallOptions'; import { DeoptimizableEntity } from '../DeoptimizableEntity'; import { ExecutionContext } from '../ExecutionContext'; -import { ImmutableEntityPathTracker } from '../utils/ImmutableEntityPathTracker'; +import { PathTracker } from '../utils/PathTracker'; import { LiteralValueOrUnknown, ObjectPath } from '../values'; import CallExpression from './CallExpression'; import * as NodeType from './NodeType'; @@ -26,7 +26,7 @@ export default class SequenceExpression extends NodeBase { getLiteralValueAtPath( path: ObjectPath, - recursionTracker: ImmutableEntityPathTracker, + recursionTracker: PathTracker, origin: DeoptimizableEntity ): LiteralValueOrUnknown { return this.expressions[this.expressions.length - 1].getLiteralValueAtPath( diff --git a/src/ast/nodes/UnaryExpression.ts b/src/ast/nodes/UnaryExpression.ts index d4ec6e092a9..e294f6e8cf3 100644 --- a/src/ast/nodes/UnaryExpression.ts +++ b/src/ast/nodes/UnaryExpression.ts @@ -1,6 +1,6 @@ import { DeoptimizableEntity } from '../DeoptimizableEntity'; import { ExecutionContext } from '../ExecutionContext'; -import { ImmutableEntityPathTracker } from '../utils/ImmutableEntityPathTracker'; +import { PathTracker } from '../utils/PathTracker'; import { EMPTY_PATH, LiteralValueOrUnknown, ObjectPath, UNKNOWN_VALUE } from '../values'; import Identifier from './Identifier'; import { LiteralValue } from './Literal'; @@ -34,7 +34,7 @@ export default class UnaryExpression extends NodeBase { getLiteralValueAtPath( path: ObjectPath, - recursionTracker: ImmutableEntityPathTracker, + recursionTracker: PathTracker, origin: DeoptimizableEntity ): LiteralValueOrUnknown { if (path.length > 0) return UNKNOWN_VALUE; diff --git a/src/ast/nodes/shared/Expression.ts b/src/ast/nodes/shared/Expression.ts index 38781587f0a..af762330ee2 100644 --- a/src/ast/nodes/shared/Expression.ts +++ b/src/ast/nodes/shared/Expression.ts @@ -2,7 +2,7 @@ import CallOptions from '../../CallOptions'; import { DeoptimizableEntity } from '../../DeoptimizableEntity'; import { WritableEntity } from '../../Entity'; import { ExecutionContext } from '../../ExecutionContext'; -import { ImmutableEntityPathTracker } from '../../utils/ImmutableEntityPathTracker'; +import { PathTracker } from '../../utils/PathTracker'; import { LiteralValueOrUnknown, ObjectPath } from '../../values'; import SpreadElement from '../SpreadElement'; import { ExpressionNode, IncludeChildren } from './Node'; @@ -17,12 +17,12 @@ export interface ExpressionEntity extends WritableEntity { */ getLiteralValueAtPath( path: ObjectPath, - recursionTracker: ImmutableEntityPathTracker, + recursionTracker: PathTracker, origin: DeoptimizableEntity ): LiteralValueOrUnknown; getReturnExpressionWhenCalledAtPath( path: ObjectPath, - recursionTracker: ImmutableEntityPathTracker, + recursionTracker: PathTracker, origin: DeoptimizableEntity ): ExpressionEntity; hasEffectsWhenAccessedAtPath(path: ObjectPath, context: ExecutionContext): boolean; diff --git a/src/ast/nodes/shared/MultiExpression.ts b/src/ast/nodes/shared/MultiExpression.ts index 69e6bf9150e..71ca0c8d784 100644 --- a/src/ast/nodes/shared/MultiExpression.ts +++ b/src/ast/nodes/shared/MultiExpression.ts @@ -1,7 +1,7 @@ import CallOptions from '../../CallOptions'; import { DeoptimizableEntity } from '../../DeoptimizableEntity'; import { ExecutionContext } from '../../ExecutionContext'; -import { ImmutableEntityPathTracker } from '../../utils/ImmutableEntityPathTracker'; +import { PathTracker } from '../../utils/PathTracker'; import { LiteralValueOrUnknown, ObjectPath, UNKNOWN_VALUE } from '../../values'; import SpreadElement from '../SpreadElement'; import { ExpressionEntity } from './Expression'; @@ -28,7 +28,7 @@ export class MultiExpression implements ExpressionEntity { getReturnExpressionWhenCalledAtPath( path: ObjectPath, - recursionTracker: ImmutableEntityPathTracker, + recursionTracker: PathTracker, origin: DeoptimizableEntity ): ExpressionEntity { return new MultiExpression( diff --git a/src/ast/nodes/shared/Node.ts b/src/ast/nodes/shared/Node.ts index 0242d8754b2..10375e5f03c 100644 --- a/src/ast/nodes/shared/Node.ts +++ b/src/ast/nodes/shared/Node.ts @@ -8,7 +8,7 @@ import { Entity } from '../../Entity'; import { createExecutionContext, ExecutionContext } from '../../ExecutionContext'; import { getAndCreateKeys, keys } from '../../keys'; import ChildScope from '../../scopes/ChildScope'; -import { ImmutableEntityPathTracker } from '../../utils/ImmutableEntityPathTracker'; +import { PathTracker } from '../../utils/PathTracker'; import { LiteralValueOrUnknown, ObjectPath, UNKNOWN_EXPRESSION, UNKNOWN_VALUE } from '../../values'; import LocalVariable from '../../variables/LocalVariable'; import Variable from '../../variables/Variable'; @@ -140,7 +140,7 @@ export class NodeBase implements ExpressionNode { getLiteralValueAtPath( _path: ObjectPath, - _recursionTracker: ImmutableEntityPathTracker, + _recursionTracker: PathTracker, _origin: DeoptimizableEntity ): LiteralValueOrUnknown { return UNKNOWN_VALUE; @@ -148,7 +148,7 @@ export class NodeBase implements ExpressionNode { getReturnExpressionWhenCalledAtPath( _path: ObjectPath, - _recursionTracker: ImmutableEntityPathTracker, + _recursionTracker: PathTracker, _origin: DeoptimizableEntity ): ExpressionEntity { return UNKNOWN_EXPRESSION; diff --git a/src/ast/utils/ImmutableEntityPathTracker.ts b/src/ast/utils/ImmutableEntityPathTracker.ts deleted file mode 100644 index 9126ac3adac..00000000000 --- a/src/ast/utils/ImmutableEntityPathTracker.ts +++ /dev/null @@ -1,27 +0,0 @@ -import Immutable from 'immutable'; -import { Entity } from '../Entity'; -import { ObjectPath } from '../values'; - -interface RESULT_KEY {} -const RESULT_KEY: RESULT_KEY = {}; -type MapType = Immutable.Map; - -export class ImmutableEntityPathTracker { - entityPaths: MapType; - - constructor(existingEntityPaths: MapType = Immutable.Map()) { - this.entityPaths = existingEntityPaths; - } - - isTracked(entity: Entity, path: ObjectPath): boolean { - return this.entityPaths.getIn([entity, ...path, RESULT_KEY]); - } - - track(entity: Entity, path: ObjectPath): ImmutableEntityPathTracker { - return new ImmutableEntityPathTracker( - this.entityPaths.setIn([entity, ...path, RESULT_KEY], true) - ); - } -} - -export const EMPTY_IMMUTABLE_TRACKER = new ImmutableEntityPathTracker(); diff --git a/src/ast/utils/PathTracker.ts b/src/ast/utils/PathTracker.ts new file mode 100644 index 00000000000..487069ccb60 --- /dev/null +++ b/src/ast/utils/PathTracker.ts @@ -0,0 +1,26 @@ +import { Entity } from '../Entity'; +import { ObjectPath, UnknownKey } from '../values'; + +const EntitiesKey = Symbol('Entities'); +interface EntityPaths { + [EntitiesKey]: Set; + [UnknownKey]?: EntityPaths; + [pathSegment: string]: EntityPaths; +} + +// TODO Lukas replace other? +export class PathTracker { + entityPaths: EntityPaths = Object.create(null, { [EntitiesKey]: { value: new Set() } }); + + getEntities(path: ObjectPath) { + let currentPaths = this.entityPaths; + for (const pathSegment of path) { + currentPaths = currentPaths[pathSegment] = + currentPaths[pathSegment] || + Object.create(null, { [EntitiesKey]: { value: new Set() } }); + } + return currentPaths[EntitiesKey]; + } +} + +export const EMPTY_IMMUTABLE_TRACKER = new PathTracker(); diff --git a/src/ast/variables/LocalVariable.ts b/src/ast/variables/LocalVariable.ts index 399ef253dd2..263871ad7f2 100644 --- a/src/ast/variables/LocalVariable.ts +++ b/src/ast/variables/LocalVariable.ts @@ -10,7 +10,7 @@ import { ExpressionEntity } from '../nodes/shared/Expression'; import { ExpressionNode, Node } from '../nodes/shared/Node'; import SpreadElement from '../nodes/SpreadElement'; import { EntityPathTracker } from '../utils/EntityPathTracker'; -import { ImmutableEntityPathTracker } from '../utils/ImmutableEntityPathTracker'; +import { PathTracker } from '../utils/PathTracker'; import { LiteralValueOrUnknown, ObjectPath, @@ -88,42 +88,43 @@ export default class LocalVariable extends Variable { } } + // TODO Lukas make bailout more efficient getLiteralValueAtPath( path: ObjectPath, - recursionTracker: ImmutableEntityPathTracker, + recursionTracker: PathTracker, origin: DeoptimizableEntity ): LiteralValueOrUnknown { - if ( - this.isReassigned || - !this.init || - path.length > MAX_PATH_DEPTH || - recursionTracker.isTracked(this.init, path) - ) { + if (this.isReassigned || !this.init || path.length > MAX_PATH_DEPTH) { + return UNKNOWN_VALUE; + } + const trackedEntities = recursionTracker.getEntities(path); + if (trackedEntities.has(this.init)) { return UNKNOWN_VALUE; } this.expressionsToBeDeoptimized.push(origin); - return this.init.getLiteralValueAtPath(path, recursionTracker.track(this.init, path), origin); + trackedEntities.add(this.init); + const value = this.init.getLiteralValueAtPath(path, recursionTracker, origin); + trackedEntities.delete(this.init); + return value; } getReturnExpressionWhenCalledAtPath( path: ObjectPath, - recursionTracker: ImmutableEntityPathTracker, + recursionTracker: PathTracker, origin: DeoptimizableEntity ): ExpressionEntity { - if ( - this.isReassigned || - !this.init || - path.length > MAX_PATH_DEPTH || - recursionTracker.isTracked(this.init, path) - ) { + if (this.isReassigned || !this.init || path.length > MAX_PATH_DEPTH) { + return UNKNOWN_EXPRESSION; + } + const trackedEntities = recursionTracker.getEntities(path); + if (trackedEntities.has(this.init)) { return UNKNOWN_EXPRESSION; } this.expressionsToBeDeoptimized.push(origin); - return this.init.getReturnExpressionWhenCalledAtPath( - path, - recursionTracker.track(this.init, path), - origin - ); + trackedEntities.add(this.init); + const value = this.init.getReturnExpressionWhenCalledAtPath(path, recursionTracker, origin); + trackedEntities.delete(this.init); + return value; } hasEffectsWhenAccessedAtPath(path: ObjectPath, context: ExecutionContext) { diff --git a/src/ast/variables/Variable.ts b/src/ast/variables/Variable.ts index c327fcdf9a6..5fbf7f530d7 100644 --- a/src/ast/variables/Variable.ts +++ b/src/ast/variables/Variable.ts @@ -7,7 +7,7 @@ import Identifier from '../nodes/Identifier'; import { ExpressionEntity } from '../nodes/shared/Expression'; import { ExpressionNode } from '../nodes/shared/Node'; import SpreadElement from '../nodes/SpreadElement'; -import { ImmutableEntityPathTracker } from '../utils/ImmutableEntityPathTracker'; +import { PathTracker } from '../utils/PathTracker'; import { LiteralValueOrUnknown, ObjectPath, UNKNOWN_EXPRESSION, UNKNOWN_VALUE } from '../values'; export default class Variable implements ExpressionEntity { @@ -41,7 +41,7 @@ export default class Variable implements ExpressionEntity { getLiteralValueAtPath( _path: ObjectPath, - _recursionTracker: ImmutableEntityPathTracker, + _recursionTracker: PathTracker, _origin: DeoptimizableEntity ): LiteralValueOrUnknown { return UNKNOWN_VALUE; @@ -54,7 +54,7 @@ export default class Variable implements ExpressionEntity { getReturnExpressionWhenCalledAtPath( _path: ObjectPath, - _recursionTracker: ImmutableEntityPathTracker, + _recursionTracker: PathTracker, _origin: DeoptimizableEntity ): ExpressionEntity { return UNKNOWN_EXPRESSION; From fa7a030bb0ff06cebb1556e7b76f756c50121331 Mon Sep 17 00:00:00 2001 From: Lukas Taegert-Atkinson Date: Fri, 6 Sep 2019 07:09:43 +0200 Subject: [PATCH 04/35] Replace mutable entity tracker with the new simple path tracker --- src/Graph.ts | 6 ++-- src/Module.ts | 4 +-- src/ast/nodes/CallExpression.ts | 20 ++++++------ src/ast/utils/EntityPathTracker.ts | 50 ------------------------------ src/ast/utils/PathTracker.ts | 1 - src/ast/variables/LocalVariable.ts | 31 +++++++++--------- 6 files changed, 31 insertions(+), 81 deletions(-) delete mode 100644 src/ast/utils/EntityPathTracker.ts diff --git a/src/Graph.ts b/src/Graph.ts index 7c928a052b1..6585379e529 100644 --- a/src/Graph.ts +++ b/src/Graph.ts @@ -2,7 +2,7 @@ import * as acorn from 'acorn'; import injectImportMeta from 'acorn-import-meta'; import * as ESTree from 'estree'; import GlobalScope from './ast/scopes/GlobalScope'; -import { EntityPathTracker } from './ast/utils/EntityPathTracker'; +import { PathTracker } from './ast/utils/PathTracker'; import Chunk, { isChunkRendered } from './Chunk'; import ExternalModule from './ExternalModule'; import Module, { defaultAcornOptions } from './Module'; @@ -64,7 +64,7 @@ export default class Graph { acornParser: typeof acorn.Parser; cachedModules: Map; contextParse: (code: string, acornOptions?: acorn.Options) => ESTree.Program; - deoptimizationTracker: EntityPathTracker; + deoptimizationTracker: PathTracker; getModuleContext: (id: string) => string; moduleById = new Map(); moduleLoader: ModuleLoader; @@ -87,7 +87,7 @@ export default class Graph { constructor(options: InputOptions, watcher?: RollupWatcher) { this.onwarn = (options.onwarn as WarningHandler) || makeOnwarn(); - this.deoptimizationTracker = new EntityPathTracker(); + this.deoptimizationTracker = new PathTracker(); this.cachedModules = new Map(); if (options.cache) { if (options.cache.modules) diff --git a/src/Module.ts b/src/Module.ts index 38f4e6643b3..c7c94783ff7 100644 --- a/src/Module.ts +++ b/src/Module.ts @@ -21,7 +21,7 @@ import { Node, NodeBase } from './ast/nodes/shared/Node'; import TemplateLiteral from './ast/nodes/TemplateLiteral'; import VariableDeclaration from './ast/nodes/VariableDeclaration'; import ModuleScope from './ast/scopes/ModuleScope'; -import { EntityPathTracker } from './ast/utils/EntityPathTracker'; +import { PathTracker } from './ast/utils/PathTracker'; import { UNKNOWN_PATH } from './ast/values'; import ExportShimVariable from './ast/variables/ExportShimVariable'; import ExternalVariable from './ast/variables/ExternalVariable'; @@ -88,7 +88,7 @@ export interface AstContext { addImportMeta: (node: MetaProperty) => void; annotations: boolean; code: string; - deoptimizationTracker: EntityPathTracker; + deoptimizationTracker: PathTracker; error: (props: RollupError, pos: number) => void; fileName: string; getExports: () => string[]; diff --git a/src/ast/nodes/CallExpression.ts b/src/ast/nodes/CallExpression.ts index 4d94234e72e..649a6465bcb 100644 --- a/src/ast/nodes/CallExpression.ts +++ b/src/ast/nodes/CallExpression.ts @@ -83,16 +83,18 @@ export default class CallExpression extends NodeBase implements DeoptimizableEnt } deoptimizePath(path: ObjectPath) { - if (path.length > 0 && !this.context.deoptimizationTracker.track(this, path)) { - if (this.returnExpression === null) { - this.returnExpression = this.callee.getReturnExpressionWhenCalledAtPath( - EMPTY_PATH, - EMPTY_IMMUTABLE_TRACKER, - this - ); - } - this.returnExpression.deoptimizePath(path); + if (path.length === 0) return; + const trackedEntities = this.context.deoptimizationTracker.getEntities(path); + if (trackedEntities.has(this)) return; + trackedEntities.add(this); + if (this.returnExpression === null) { + this.returnExpression = this.callee.getReturnExpressionWhenCalledAtPath( + EMPTY_PATH, + EMPTY_IMMUTABLE_TRACKER, + this + ); } + this.returnExpression.deoptimizePath(path); } getLiteralValueAtPath( diff --git a/src/ast/utils/EntityPathTracker.ts b/src/ast/utils/EntityPathTracker.ts deleted file mode 100644 index a0929fe0753..00000000000 --- a/src/ast/utils/EntityPathTracker.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { Entity } from '../Entity'; -import { ObjectPath } from '../values'; - -interface TrackedPaths { - paths: { [key: string]: TrackedPaths }; - tracked: boolean; - unknownPath: TrackedPaths | null; -} - -const getNewTrackedPaths = (): TrackedPaths => ({ - paths: Object.create(null), - tracked: false, - unknownPath: null -}); - -export class EntityPathTracker { - entityPaths: Map = new Map(); - - track(entity: Entity, path: ObjectPath): boolean { - let trackedPaths = this.entityPaths.get(entity); - if (!trackedPaths) { - trackedPaths = getNewTrackedPaths(); - this.entityPaths.set(entity, trackedPaths); - } - - let pathIndex = 0, - trackedSubPaths; - while (pathIndex < path.length) { - const key = path[pathIndex]; - if (typeof key === 'string') { - trackedSubPaths = trackedPaths.paths[key]; - if (!trackedSubPaths) { - trackedSubPaths = getNewTrackedPaths(); - trackedPaths.paths[key] = trackedSubPaths; - } - } else { - trackedSubPaths = trackedPaths.unknownPath; - if (!trackedSubPaths) { - trackedSubPaths = getNewTrackedPaths(); - trackedPaths.unknownPath = trackedSubPaths; - } - } - trackedPaths = trackedSubPaths; - pathIndex++; - } - const found = trackedPaths.tracked; - trackedPaths.tracked = true; - return found; - } -} diff --git a/src/ast/utils/PathTracker.ts b/src/ast/utils/PathTracker.ts index 487069ccb60..121ee713fbc 100644 --- a/src/ast/utils/PathTracker.ts +++ b/src/ast/utils/PathTracker.ts @@ -8,7 +8,6 @@ interface EntityPaths { [pathSegment: string]: EntityPaths; } -// TODO Lukas replace other? export class PathTracker { entityPaths: EntityPaths = Object.create(null, { [EntitiesKey]: { value: new Set() } }); diff --git a/src/ast/variables/LocalVariable.ts b/src/ast/variables/LocalVariable.ts index 263871ad7f2..4ba7bf8f367 100644 --- a/src/ast/variables/LocalVariable.ts +++ b/src/ast/variables/LocalVariable.ts @@ -9,7 +9,6 @@ import * as NodeType from '../nodes/NodeType'; import { ExpressionEntity } from '../nodes/shared/Expression'; import { ExpressionNode, Node } from '../nodes/shared/Node'; import SpreadElement from '../nodes/SpreadElement'; -import { EntityPathTracker } from '../utils/EntityPathTracker'; import { PathTracker } from '../utils/PathTracker'; import { LiteralValueOrUnknown, @@ -32,7 +31,7 @@ export default class LocalVariable extends Variable { // Caching and deoptimization: // We track deoptimization when we do not return something unknown - private deoptimizationTracker: EntityPathTracker; + private deoptimizationTracker: PathTracker; private expressionsToBeDeoptimized: DeoptimizableEntity[] = []; constructor( @@ -70,25 +69,25 @@ export default class LocalVariable extends Variable { } deoptimizePath(path: ObjectPath) { - if (path.length > MAX_PATH_DEPTH) return; - if (!(this.isReassigned || this.deoptimizationTracker.track(this, path))) { - if (path.length === 0) { - if (!this.isReassigned) { - this.isReassigned = true; - for (const expression of this.expressionsToBeDeoptimized) { - expression.deoptimizeCache(); - } - if (this.init) { - this.init.deoptimizePath(UNKNOWN_PATH); - } + if (path.length > MAX_PATH_DEPTH || this.isReassigned) return; + const trackedEntities = this.deoptimizationTracker.getEntities(path); + if (trackedEntities.has(this)) return; + trackedEntities.add(this); + if (path.length === 0) { + if (!this.isReassigned) { + this.isReassigned = true; + for (const expression of this.expressionsToBeDeoptimized) { + expression.deoptimizeCache(); + } + if (this.init) { + this.init.deoptimizePath(UNKNOWN_PATH); } - } else if (this.init) { - this.init.deoptimizePath(path); } + } else if (this.init) { + this.init.deoptimizePath(path); } } - // TODO Lukas make bailout more efficient getLiteralValueAtPath( path: ObjectPath, recursionTracker: PathTracker, From 844c8ec197545dfa8b92d78f218f654654214465 Mon Sep 17 00:00:00 2001 From: Lukas Taegert-Atkinson Date: Fri, 6 Sep 2019 13:53:01 +0200 Subject: [PATCH 05/35] Refactor context to align ignore properties --- src/ast/ExecutionContext.ts | 28 ++++++++++++++++++----- src/ast/nodes/AwaitExpression.ts | 2 +- src/ast/nodes/BreakStatement.ts | 4 ++-- src/ast/nodes/CallExpression.ts | 12 +++------- src/ast/nodes/DoWhileStatement.ts | 8 ++++--- src/ast/nodes/ForInStatement.ts | 8 ++++--- src/ast/nodes/ForStatement.ts | 8 ++++--- src/ast/nodes/LabeledStatement.ts | 12 ++++++---- src/ast/nodes/NewExpression.ts | 11 +++------ src/ast/nodes/Property.ts | 20 ++++------------ src/ast/nodes/ReturnStatement.ts | 2 +- src/ast/nodes/SwitchStatement.ts | 6 ++--- src/ast/nodes/TaggedTemplateExpression.ts | 11 +++------ src/ast/nodes/WhileStatement.ts | 8 ++++--- src/ast/nodes/YieldExpression.ts | 2 +- src/ast/values.ts | 11 +++------ 16 files changed, 74 insertions(+), 79 deletions(-) diff --git a/src/ast/ExecutionContext.ts b/src/ast/ExecutionContext.ts index 5463397f063..a48c926a03d 100644 --- a/src/ast/ExecutionContext.ts +++ b/src/ast/ExecutionContext.ts @@ -1,18 +1,34 @@ import { ExpressionEntity } from './nodes/shared/Expression'; import ThisVariable from './variables/ThisVariable'; +interface ExecutionContextIgnore { + breakStatements: boolean; + labels: Set; + returnAwaitYield: boolean; +} + export interface ExecutionContext { - ignoreBreakStatements: boolean; - ignoredLabels: Set; - ignoreReturnAwaitYield: boolean; + ignore: ExecutionContextIgnore; replacedVariableInits: Map; } export function createExecutionContext(): ExecutionContext { return { - ignoreBreakStatements: false, - ignoredLabels: new Set(), - ignoreReturnAwaitYield: false, + ignore: { + breakStatements: false, + labels: new Set(), + returnAwaitYield: false + }, replacedVariableInits: new Map() }; } + +export function resetIgnoreForCall(context: ExecutionContext): ExecutionContextIgnore { + const ignore = context.ignore; + context.ignore = { + breakStatements: false, + labels: new Set(), + returnAwaitYield: true + }; + return ignore; +} diff --git a/src/ast/nodes/AwaitExpression.ts b/src/ast/nodes/AwaitExpression.ts index 8815d0b0f87..88ae3de393d 100644 --- a/src/ast/nodes/AwaitExpression.ts +++ b/src/ast/nodes/AwaitExpression.ts @@ -11,7 +11,7 @@ export default class AwaitExpression extends NodeBase { type!: NodeType.tAwaitExpression; hasEffects(context: ExecutionContext) { - return super.hasEffects(context) || !context.ignoreReturnAwaitYield; + return super.hasEffects(context) || !context.ignore.returnAwaitYield; } include(includeChildrenRecursively: IncludeChildren) { diff --git a/src/ast/nodes/BreakStatement.ts b/src/ast/nodes/BreakStatement.ts index 34320458b73..dff69c9699d 100644 --- a/src/ast/nodes/BreakStatement.ts +++ b/src/ast/nodes/BreakStatement.ts @@ -10,8 +10,8 @@ export default class BreakStatement extends StatementBase { hasEffects(context: ExecutionContext) { return ( super.hasEffects(context) || - !context.ignoreBreakStatements || - (this.label !== null && !context.ignoredLabels.has(this.label.name)) + !context.ignore.breakStatements || + (this.label !== null && !context.ignore.labels.has(this.label.name)) ); } } diff --git a/src/ast/nodes/CallExpression.ts b/src/ast/nodes/CallExpression.ts index 649a6465bcb..d02a7091a19 100644 --- a/src/ast/nodes/CallExpression.ts +++ b/src/ast/nodes/CallExpression.ts @@ -7,7 +7,7 @@ import { } from '../../utils/renderHelpers'; import CallOptions from '../CallOptions'; import { DeoptimizableEntity } from '../DeoptimizableEntity'; -import { ExecutionContext } from '../ExecutionContext'; +import { ExecutionContext, resetIgnoreForCall } from '../ExecutionContext'; import { EMPTY_IMMUTABLE_TRACKER, PathTracker } from '../utils/PathTracker'; import { EMPTY_PATH, @@ -153,21 +153,15 @@ export default class CallExpression extends NodeBase implements DeoptimizableEnt return value; } - // TODO Lukas can this be made more efficient by grouping properties that can be replaced together? hasEffects(context: ExecutionContext): boolean { for (const argument of this.arguments) { if (argument.hasEffects(context)) return true; } if (this.context.annotations && this.annotatedPure) return false; if (this.callee.hasEffects(context)) return true; - const { ignoreBreakStatements, ignoredLabels, ignoreReturnAwaitYield } = context; - Object.assign(context, { - ignoreBreakStatements: false, - ignoredLabels: new Set(), - ignoreReturnAwaitYield: true - }); + const ignore = resetIgnoreForCall(context); if (this.callee.hasEffectsWhenCalledAtPath(EMPTY_PATH, this.callOptions, context)) return true; - Object.assign(context, { ignoreBreakStatements, ignoredLabels, ignoreReturnAwaitYield }); + context.ignore = ignore; return false; } diff --git a/src/ast/nodes/DoWhileStatement.ts b/src/ast/nodes/DoWhileStatement.ts index bef34d2f307..26e748073d1 100644 --- a/src/ast/nodes/DoWhileStatement.ts +++ b/src/ast/nodes/DoWhileStatement.ts @@ -9,10 +9,12 @@ export default class DoWhileStatement extends StatementBase { hasEffects(context: ExecutionContext): boolean { if (this.test.hasEffects(context)) return true; - const { ignoreBreakStatements } = context; - context.ignoreBreakStatements = true; + const { + ignore: { breakStatements } + } = context; + context.ignore.breakStatements = true; if (this.body.hasEffects(context)) return true; - context.ignoreBreakStatements = ignoreBreakStatements; + context.ignore.breakStatements = breakStatements; return false; } } diff --git a/src/ast/nodes/ForInStatement.ts b/src/ast/nodes/ForInStatement.ts index 99669e68e89..c5368fff01c 100644 --- a/src/ast/nodes/ForInStatement.ts +++ b/src/ast/nodes/ForInStatement.ts @@ -34,10 +34,12 @@ export default class ForInStatement extends StatementBase { (this.right && this.right.hasEffects(context)) ) return true; - const { ignoreBreakStatements } = context; - context.ignoreBreakStatements = true; + const { + ignore: { breakStatements } + } = context; + context.ignore.breakStatements = true; if (this.body.hasEffects(context)) return true; - context.ignoreBreakStatements = ignoreBreakStatements; + context.ignore.breakStatements = breakStatements; return false; } diff --git a/src/ast/nodes/ForStatement.ts b/src/ast/nodes/ForStatement.ts index bdec5db9581..11d3c7404d6 100644 --- a/src/ast/nodes/ForStatement.ts +++ b/src/ast/nodes/ForStatement.ts @@ -25,10 +25,12 @@ export default class ForStatement extends StatementBase { (this.update && this.update.hasEffects(context)) ) return true; - const { ignoreBreakStatements } = context; - context.ignoreBreakStatements = true; + const { + ignore: { breakStatements } + } = context; + context.ignore.breakStatements = true; if (this.body.hasEffects(context)) return true; - context.ignoreBreakStatements = ignoreBreakStatements; + context.ignore.breakStatements = breakStatements; return false; } diff --git a/src/ast/nodes/LabeledStatement.ts b/src/ast/nodes/LabeledStatement.ts index 12c082baf3b..be1d68415b9 100644 --- a/src/ast/nodes/LabeledStatement.ts +++ b/src/ast/nodes/LabeledStatement.ts @@ -9,12 +9,14 @@ export default class LabeledStatement extends StatementBase { type!: NodeType.tLabeledStatement; hasEffects(context: ExecutionContext) { - const { ignoreBreakStatements } = context; - context.ignoreBreakStatements = true; - context.ignoredLabels.add(this.label.name); + const { + ignore: { breakStatements } + } = context; + context.ignore.breakStatements = true; + context.ignore.labels.add(this.label.name); if (this.body.hasEffects(context)) return true; - context.ignoreBreakStatements = ignoreBreakStatements; - context.ignoredLabels.delete(this.label.name); + context.ignore.breakStatements = breakStatements; + context.ignore.labels.delete(this.label.name); return false; } } diff --git a/src/ast/nodes/NewExpression.ts b/src/ast/nodes/NewExpression.ts index ca2a20a1382..2f696b57072 100644 --- a/src/ast/nodes/NewExpression.ts +++ b/src/ast/nodes/NewExpression.ts @@ -1,5 +1,5 @@ import CallOptions from '../CallOptions'; -import { ExecutionContext } from '../ExecutionContext'; +import { ExecutionContext, resetIgnoreForCall } from '../ExecutionContext'; import { EMPTY_PATH, ObjectPath, UNKNOWN_PATH } from '../values'; import * as NodeType from './NodeType'; import { ExpressionNode, NodeBase } from './shared/Node'; @@ -25,14 +25,9 @@ export default class NewExpression extends NodeBase { if (argument.hasEffects(context)) return true; } if (this.annotatedPure) return false; - const { ignoreBreakStatements, ignoredLabels, ignoreReturnAwaitYield } = context; - Object.assign(context, { - ignoreBreakStatements: false, - ignoredLabels: new Set(), - ignoreReturnAwaitYield: true - }); + const ignore = resetIgnoreForCall(context); if (this.callee.hasEffectsWhenCalledAtPath(EMPTY_PATH, this.callOptions, context)) return true; - Object.assign(context, { ignoreBreakStatements, ignoredLabels, ignoreReturnAwaitYield }); + context.ignore = ignore; return false; } diff --git a/src/ast/nodes/Property.ts b/src/ast/nodes/Property.ts index 21ec302718b..a6c5b6c5bce 100644 --- a/src/ast/nodes/Property.ts +++ b/src/ast/nodes/Property.ts @@ -2,7 +2,7 @@ import MagicString from 'magic-string'; import { RenderOptions } from '../../utils/renderHelpers'; import CallOptions from '../CallOptions'; import { DeoptimizableEntity } from '../DeoptimizableEntity'; -import { ExecutionContext } from '../ExecutionContext'; +import { ExecutionContext, resetIgnoreForCall } from '../ExecutionContext'; import { EMPTY_IMMUTABLE_TRACKER, PathTracker } from '../utils/PathTracker'; import { EMPTY_PATH, @@ -103,15 +103,10 @@ export default class Property extends NodeBase implements DeoptimizableEntity { hasEffectsWhenAccessedAtPath(path: ObjectPath, context: ExecutionContext): boolean { if (this.kind === 'get') { - const { ignoreBreakStatements, ignoredLabels, ignoreReturnAwaitYield } = context; - Object.assign(context, { - ignoreBreakStatements: false, - ignoredLabels: new Set(), - ignoreReturnAwaitYield: true - }); + const ignore = resetIgnoreForCall(context); if (this.value.hasEffectsWhenCalledAtPath(EMPTY_PATH, this.accessorCallOptions, context)) return true; - Object.assign(context, { ignoreBreakStatements, ignoredLabels, ignoreReturnAwaitYield }); + context.ignore = ignore; return ( path.length > 0 && (this.returnExpression as ExpressionEntity).hasEffectsWhenAccessedAtPath(path, context) @@ -129,15 +124,10 @@ export default class Property extends NodeBase implements DeoptimizableEntity { } if (this.kind === 'set') { if (path.length > 0) return true; - const { ignoreBreakStatements, ignoredLabels, ignoreReturnAwaitYield } = context; - Object.assign(context, { - ignoreBreakStatements: false, - ignoredLabels: new Set(), - ignoreReturnAwaitYield: true - }); + const ignore = resetIgnoreForCall(context); if (this.value.hasEffectsWhenCalledAtPath(EMPTY_PATH, this.accessorCallOptions, context)) return true; - Object.assign(context, { ignoreBreakStatements, ignoredLabels, ignoreReturnAwaitYield }); + context.ignore = ignore; return false; } return this.value.hasEffectsWhenAssignedAtPath(path, context); diff --git a/src/ast/nodes/ReturnStatement.ts b/src/ast/nodes/ReturnStatement.ts index 67fa52a17c1..19ff7b806a9 100644 --- a/src/ast/nodes/ReturnStatement.ts +++ b/src/ast/nodes/ReturnStatement.ts @@ -11,7 +11,7 @@ export default class ReturnStatement extends StatementBase { hasEffects(context: ExecutionContext) { return ( - !context.ignoreReturnAwaitYield || + !context.ignore.returnAwaitYield || (this.argument !== null && this.argument.hasEffects(context)) ); } diff --git a/src/ast/nodes/SwitchStatement.ts b/src/ast/nodes/SwitchStatement.ts index 09b32a96166..9b2fe2a6452 100644 --- a/src/ast/nodes/SwitchStatement.ts +++ b/src/ast/nodes/SwitchStatement.ts @@ -15,10 +15,10 @@ export default class SwitchStatement extends StatementBase { } hasEffects(context: ExecutionContext) { - const ignoreBreakStatements = context.ignoreBreakStatements; - context.ignoreBreakStatements = true; + const ignoreBreakStatements = context.ignore.breakStatements; + context.ignore.breakStatements = true; if (super.hasEffects(context)) return true; - context.ignoreBreakStatements = ignoreBreakStatements; + context.ignore.breakStatements = ignoreBreakStatements; return false; } } diff --git a/src/ast/nodes/TaggedTemplateExpression.ts b/src/ast/nodes/TaggedTemplateExpression.ts index c1f97929a65..c3d5c3fa087 100644 --- a/src/ast/nodes/TaggedTemplateExpression.ts +++ b/src/ast/nodes/TaggedTemplateExpression.ts @@ -1,5 +1,5 @@ import CallOptions from '../CallOptions'; -import { ExecutionContext } from '../ExecutionContext'; +import { ExecutionContext, resetIgnoreForCall } from '../ExecutionContext'; import { EMPTY_PATH } from '../values'; import Identifier from './Identifier'; import * as NodeType from './NodeType'; @@ -43,14 +43,9 @@ export default class TaggedTemplateExpression extends NodeBase { hasEffects(context: ExecutionContext) { if (super.hasEffects(context)) return true; - const { ignoreBreakStatements, ignoredLabels, ignoreReturnAwaitYield } = context; - Object.assign(context, { - ignoreBreakStatements: false, - ignoredLabels: new Set(), - ignoreReturnAwaitYield: true - }); + const ignore = resetIgnoreForCall(context); if (this.tag.hasEffectsWhenCalledAtPath(EMPTY_PATH, this.callOptions, context)) return true; - Object.assign(context, { ignoreBreakStatements, ignoredLabels, ignoreReturnAwaitYield }); + context.ignore = ignore; return false; } diff --git a/src/ast/nodes/WhileStatement.ts b/src/ast/nodes/WhileStatement.ts index 3cb8de83f20..65f04b4c811 100644 --- a/src/ast/nodes/WhileStatement.ts +++ b/src/ast/nodes/WhileStatement.ts @@ -9,10 +9,12 @@ export default class WhileStatement extends StatementBase { hasEffects(context: ExecutionContext): boolean { if (this.test.hasEffects(context)) return true; - const { ignoreBreakStatements } = context; - context.ignoreBreakStatements = true; + const { + ignore: { breakStatements } + } = context; + context.ignore.breakStatements = true; if (this.body.hasEffects(context)) return true; - context.ignoreBreakStatements = ignoreBreakStatements; + context.ignore.breakStatements = breakStatements; return false; } } diff --git a/src/ast/nodes/YieldExpression.ts b/src/ast/nodes/YieldExpression.ts index dcd4b724eb2..b1dd417485b 100644 --- a/src/ast/nodes/YieldExpression.ts +++ b/src/ast/nodes/YieldExpression.ts @@ -19,7 +19,7 @@ export default class YieldExpression extends NodeBase { hasEffects(context: ExecutionContext) { return ( - !context.ignoreReturnAwaitYield || + !context.ignore.returnAwaitYield || (this.argument !== null && this.argument.hasEffects(context)) ); } diff --git a/src/ast/values.ts b/src/ast/values.ts index c7bfc9f4255..d0ac233ca29 100644 --- a/src/ast/values.ts +++ b/src/ast/values.ts @@ -1,5 +1,5 @@ import CallOptions from './CallOptions'; -import { ExecutionContext } from './ExecutionContext'; +import { ExecutionContext, resetIgnoreForCall } from './ExecutionContext'; import { LiteralValue } from './nodes/Literal'; import { ExpressionEntity } from './nodes/shared/Expression'; import { ExpressionNode } from './nodes/shared/Node'; @@ -476,12 +476,7 @@ export function hasMemberEffectWhenCalled( if (!members[memberName].callsArgs) return false; const calledArgs = members[memberName].callsArgs as number[]; if (calledArgs.length > 0) { - const { ignoreBreakStatements, ignoredLabels, ignoreReturnAwaitYield } = context; - Object.assign(context, { - ignoreBreakStatements: false, - ignoredLabels: new Set(), - ignoreReturnAwaitYield: true - }); + const ignore = resetIgnoreForCall(context); for (const argIndex of members[memberName].callsArgs as number[]) { if (callOptions.args[argIndex]) { if ( @@ -499,7 +494,7 @@ export function hasMemberEffectWhenCalled( } } } - Object.assign(context, { ignoreBreakStatements, ignoredLabels, ignoreReturnAwaitYield }); + context.ignore = ignore; } return false; } From 91603b273c4ff9fdd6ef5360d1b83937d4020062 Mon Sep 17 00:00:00 2001 From: Lukas Taegert-Atkinson Date: Fri, 6 Sep 2019 14:23:19 +0200 Subject: [PATCH 06/35] Move function ignore reset to actual functions --- src/ast/ExecutionContext.ts | 10 ------ src/ast/nodes/ArrowFunctionExpression.ts | 10 +++++- src/ast/nodes/CallExpression.ts | 11 +++---- src/ast/nodes/NewExpression.ts | 7 ++--- src/ast/nodes/Property.ts | 21 +++++-------- src/ast/nodes/TaggedTemplateExpression.ts | 11 +++---- src/ast/nodes/shared/FunctionNode.ts | 13 ++++++-- src/ast/values.ts | 37 +++++++++-------------- 8 files changed, 54 insertions(+), 66 deletions(-) diff --git a/src/ast/ExecutionContext.ts b/src/ast/ExecutionContext.ts index a48c926a03d..1239c2ac664 100644 --- a/src/ast/ExecutionContext.ts +++ b/src/ast/ExecutionContext.ts @@ -22,13 +22,3 @@ export function createExecutionContext(): ExecutionContext { replacedVariableInits: new Map() }; } - -export function resetIgnoreForCall(context: ExecutionContext): ExecutionContextIgnore { - const ignore = context.ignore; - context.ignore = { - breakStatements: false, - labels: new Set(), - returnAwaitYield: true - }; - return ignore; -} diff --git a/src/ast/nodes/ArrowFunctionExpression.ts b/src/ast/nodes/ArrowFunctionExpression.ts index 532f8539d83..5024c56d703 100644 --- a/src/ast/nodes/ArrowFunctionExpression.ts +++ b/src/ast/nodes/ArrowFunctionExpression.ts @@ -55,7 +55,15 @@ export default class ArrowFunctionExpression extends NodeBase { for (const param of this.params) { if (param.hasEffects(context)) return true; } - return this.body.hasEffects(context); + const ignore = context.ignore; + context.ignore = { + breakStatements: false, + labels: new Set(), + returnAwaitYield: true + }; + if (this.body.hasEffects(context)) return true; + context.ignore = ignore; + return false; } include(includeChildrenRecursively: boolean | 'variables') { diff --git a/src/ast/nodes/CallExpression.ts b/src/ast/nodes/CallExpression.ts index d02a7091a19..3510d976a34 100644 --- a/src/ast/nodes/CallExpression.ts +++ b/src/ast/nodes/CallExpression.ts @@ -7,7 +7,7 @@ import { } from '../../utils/renderHelpers'; import CallOptions from '../CallOptions'; import { DeoptimizableEntity } from '../DeoptimizableEntity'; -import { ExecutionContext, resetIgnoreForCall } from '../ExecutionContext'; +import { ExecutionContext } from '../ExecutionContext'; import { EMPTY_IMMUTABLE_TRACKER, PathTracker } from '../utils/PathTracker'; import { EMPTY_PATH, @@ -158,11 +158,10 @@ export default class CallExpression extends NodeBase implements DeoptimizableEnt if (argument.hasEffects(context)) return true; } if (this.context.annotations && this.annotatedPure) return false; - if (this.callee.hasEffects(context)) return true; - const ignore = resetIgnoreForCall(context); - if (this.callee.hasEffectsWhenCalledAtPath(EMPTY_PATH, this.callOptions, context)) return true; - context.ignore = ignore; - return false; + return ( + this.callee.hasEffects(context) || + this.callee.hasEffectsWhenCalledAtPath(EMPTY_PATH, this.callOptions, context) + ); } hasEffectsWhenAccessedAtPath(path: ObjectPath, context: ExecutionContext): boolean { diff --git a/src/ast/nodes/NewExpression.ts b/src/ast/nodes/NewExpression.ts index 2f696b57072..d695703290e 100644 --- a/src/ast/nodes/NewExpression.ts +++ b/src/ast/nodes/NewExpression.ts @@ -1,5 +1,5 @@ import CallOptions from '../CallOptions'; -import { ExecutionContext, resetIgnoreForCall } from '../ExecutionContext'; +import { ExecutionContext } from '../ExecutionContext'; import { EMPTY_PATH, ObjectPath, UNKNOWN_PATH } from '../values'; import * as NodeType from './NodeType'; import { ExpressionNode, NodeBase } from './shared/Node'; @@ -25,10 +25,7 @@ export default class NewExpression extends NodeBase { if (argument.hasEffects(context)) return true; } if (this.annotatedPure) return false; - const ignore = resetIgnoreForCall(context); - if (this.callee.hasEffectsWhenCalledAtPath(EMPTY_PATH, this.callOptions, context)) return true; - context.ignore = ignore; - return false; + return this.callee.hasEffectsWhenCalledAtPath(EMPTY_PATH, this.callOptions, context); } hasEffectsWhenAccessedAtPath(path: ObjectPath, _context: ExecutionContext) { diff --git a/src/ast/nodes/Property.ts b/src/ast/nodes/Property.ts index a6c5b6c5bce..4b603644a70 100644 --- a/src/ast/nodes/Property.ts +++ b/src/ast/nodes/Property.ts @@ -2,7 +2,7 @@ import MagicString from 'magic-string'; import { RenderOptions } from '../../utils/renderHelpers'; import CallOptions from '../CallOptions'; import { DeoptimizableEntity } from '../DeoptimizableEntity'; -import { ExecutionContext, resetIgnoreForCall } from '../ExecutionContext'; +import { ExecutionContext } from '../ExecutionContext'; import { EMPTY_IMMUTABLE_TRACKER, PathTracker } from '../utils/PathTracker'; import { EMPTY_PATH, @@ -103,13 +103,10 @@ export default class Property extends NodeBase implements DeoptimizableEntity { hasEffectsWhenAccessedAtPath(path: ObjectPath, context: ExecutionContext): boolean { if (this.kind === 'get') { - const ignore = resetIgnoreForCall(context); - if (this.value.hasEffectsWhenCalledAtPath(EMPTY_PATH, this.accessorCallOptions, context)) - return true; - context.ignore = ignore; return ( - path.length > 0 && - (this.returnExpression as ExpressionEntity).hasEffectsWhenAccessedAtPath(path, context) + this.value.hasEffectsWhenCalledAtPath(EMPTY_PATH, this.accessorCallOptions, context) || + (path.length > 0 && + (this.returnExpression as ExpressionEntity).hasEffectsWhenAccessedAtPath(path, context)) ); } return this.value.hasEffectsWhenAccessedAtPath(path, context); @@ -123,12 +120,10 @@ export default class Property extends NodeBase implements DeoptimizableEntity { ); } if (this.kind === 'set') { - if (path.length > 0) return true; - const ignore = resetIgnoreForCall(context); - if (this.value.hasEffectsWhenCalledAtPath(EMPTY_PATH, this.accessorCallOptions, context)) - return true; - context.ignore = ignore; - return false; + return ( + path.length > 0 || + this.value.hasEffectsWhenCalledAtPath(EMPTY_PATH, this.accessorCallOptions, context) + ); } return this.value.hasEffectsWhenAssignedAtPath(path, context); } diff --git a/src/ast/nodes/TaggedTemplateExpression.ts b/src/ast/nodes/TaggedTemplateExpression.ts index c3d5c3fa087..3c0fc473f39 100644 --- a/src/ast/nodes/TaggedTemplateExpression.ts +++ b/src/ast/nodes/TaggedTemplateExpression.ts @@ -1,5 +1,5 @@ import CallOptions from '../CallOptions'; -import { ExecutionContext, resetIgnoreForCall } from '../ExecutionContext'; +import { ExecutionContext } from '../ExecutionContext'; import { EMPTY_PATH } from '../values'; import Identifier from './Identifier'; import * as NodeType from './NodeType'; @@ -42,11 +42,10 @@ export default class TaggedTemplateExpression extends NodeBase { } hasEffects(context: ExecutionContext) { - if (super.hasEffects(context)) return true; - const ignore = resetIgnoreForCall(context); - if (this.tag.hasEffectsWhenCalledAtPath(EMPTY_PATH, this.callOptions, context)) return true; - context.ignore = ignore; - return false; + return ( + super.hasEffects(context) || + this.tag.hasEffectsWhenCalledAtPath(EMPTY_PATH, this.callOptions, context) + ); } initialise() { diff --git a/src/ast/nodes/shared/FunctionNode.ts b/src/ast/nodes/shared/FunctionNode.ts index e17690da08e..bf12be00a43 100644 --- a/src/ast/nodes/shared/FunctionNode.ts +++ b/src/ast/nodes/shared/FunctionNode.ts @@ -69,20 +69,27 @@ export default class FunctionNode extends NodeBase { context: ExecutionContext ) { if (path.length > 0) return true; + for (const param of this.params) { + if (param.hasEffects(context)) return true; + } const thisInit = context.replacedVariableInits.get(this.scope.thisVariable); context.replacedVariableInits.set( this.scope.thisVariable, callOptions.withNew ? new UnknownObjectExpression() : UNKNOWN_EXPRESSION ); - for (const param of this.params) { - if (param.hasEffects(context)) return true; - } + const ignore = context.ignore; + context.ignore = { + breakStatements: false, + labels: new Set(), + returnAwaitYield: true + }; if (this.body.hasEffects(context)) return true; if (thisInit) { context.replacedVariableInits.set(this.scope.thisVariable, thisInit); } else { context.replacedVariableInits.delete(this.scope.thisVariable); } + context.ignore = ignore; return false; } diff --git a/src/ast/values.ts b/src/ast/values.ts index d0ac233ca29..4e9dc381db4 100644 --- a/src/ast/values.ts +++ b/src/ast/values.ts @@ -1,5 +1,5 @@ import CallOptions from './CallOptions'; -import { ExecutionContext, resetIgnoreForCall } from './ExecutionContext'; +import { ExecutionContext } from './ExecutionContext'; import { LiteralValue } from './nodes/Literal'; import { ExpressionEntity } from './nodes/shared/Expression'; import { ExpressionNode } from './nodes/shared/Node'; @@ -474,27 +474,20 @@ export function hasMemberEffectWhenCalled( ) return true; if (!members[memberName].callsArgs) return false; - const calledArgs = members[memberName].callsArgs as number[]; - if (calledArgs.length > 0) { - const ignore = resetIgnoreForCall(context); - for (const argIndex of members[memberName].callsArgs as number[]) { - if (callOptions.args[argIndex]) { - if ( - callOptions.args[argIndex].hasEffectsWhenCalledAtPath( - EMPTY_PATH, - CallOptions.create({ - args: [], - callIdentifier: {}, // make sure the caller is unique to avoid this check being ignored, - withNew: false - }), - context - ) - ) { - return true; - } - } - } - context.ignore = ignore; + for (const argIndex of members[memberName].callsArgs as number[]) { + if ( + callOptions.args[argIndex] && + callOptions.args[argIndex].hasEffectsWhenCalledAtPath( + EMPTY_PATH, + CallOptions.create({ + args: [], + callIdentifier: {}, // make sure the caller is unique to avoid this check being ignored, + withNew: false + }), + context + ) + ) + return true; } return false; } From 190074cb414c2365445036bf45462db80ce4281b Mon Sep 17 00:00:00 2001 From: Lukas Taegert-Atkinson Date: Fri, 6 Sep 2019 14:40:01 +0200 Subject: [PATCH 07/35] Prevent recursions on call expression level --- src/ast/ExecutionContext.ts | 3 +++ src/ast/nodes/CallExpression.ts | 8 ++++---- .../samples/recursive-return-value-assignments/_config.js | 1 - test/form/samples/self-calling-function/_config.js | 1 - test/form/samples/supports-es5-shim/_config.js | 1 - 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/ast/ExecutionContext.ts b/src/ast/ExecutionContext.ts index 1239c2ac664..1da99538f5c 100644 --- a/src/ast/ExecutionContext.ts +++ b/src/ast/ExecutionContext.ts @@ -1,3 +1,4 @@ +import CallExpression from './nodes/CallExpression'; import { ExpressionEntity } from './nodes/shared/Expression'; import ThisVariable from './variables/ThisVariable'; @@ -8,12 +9,14 @@ interface ExecutionContextIgnore { } export interface ExecutionContext { + calledExpressions: Set; ignore: ExecutionContextIgnore; replacedVariableInits: Map; } export function createExecutionContext(): ExecutionContext { return { + calledExpressions: new Set(), ignore: { breakStatements: false, labels: new Set(), diff --git a/src/ast/nodes/CallExpression.ts b/src/ast/nodes/CallExpression.ts index 3510d976a34..a71ff2e2d65 100644 --- a/src/ast/nodes/CallExpression.ts +++ b/src/ast/nodes/CallExpression.ts @@ -158,10 +158,10 @@ export default class CallExpression extends NodeBase implements DeoptimizableEnt if (argument.hasEffects(context)) return true; } if (this.context.annotations && this.annotatedPure) return false; - return ( - this.callee.hasEffects(context) || - this.callee.hasEffectsWhenCalledAtPath(EMPTY_PATH, this.callOptions, context) - ); + if (this.callee.hasEffects(context)) return true; + if (context.calledExpressions.has(this)) return false; + context.calledExpressions.add(this); + return this.callee.hasEffectsWhenCalledAtPath(EMPTY_PATH, this.callOptions, context); } hasEffectsWhenAccessedAtPath(path: ObjectPath, context: ExecutionContext): boolean { diff --git a/test/form/samples/recursive-return-value-assignments/_config.js b/test/form/samples/recursive-return-value-assignments/_config.js index b37d98e7ea8..66c2c686fe9 100644 --- a/test/form/samples/recursive-return-value-assignments/_config.js +++ b/test/form/samples/recursive-return-value-assignments/_config.js @@ -1,4 +1,3 @@ module.exports = { - skip: true, description: 'handle recursive reassignments of return values' }; diff --git a/test/form/samples/self-calling-function/_config.js b/test/form/samples/self-calling-function/_config.js index 78cbafe7b99..7629c502687 100644 --- a/test/form/samples/self-calling-function/_config.js +++ b/test/form/samples/self-calling-function/_config.js @@ -1,5 +1,4 @@ module.exports = { - skip: true, description: 'discards a self-calling function without side-effects', expectedWarnings: ['EMPTY_BUNDLE'] }; diff --git a/test/form/samples/supports-es5-shim/_config.js b/test/form/samples/supports-es5-shim/_config.js index 49e60b2d784..30d26541716 100644 --- a/test/form/samples/supports-es5-shim/_config.js +++ b/test/form/samples/supports-es5-shim/_config.js @@ -1,5 +1,4 @@ module.exports = { - skip: true, description: 'supports es5-shim', options: { onwarn(warning) { From 3d4a331087d87d34cd354229a96647b1de2333a3 Mon Sep 17 00:00:00 2001 From: Lukas Taegert-Atkinson Date: Fri, 6 Sep 2019 16:16:07 +0200 Subject: [PATCH 08/35] Add infinite recursion protection again --- src/ast/ExecutionContext.ts | 12 +++++-- src/ast/nodes/CallExpression.ts | 32 +++++++++++------- src/ast/nodes/Property.ts | 28 ++++++++++++---- src/ast/variables/LocalVariable.ts | 33 +++++++++++-------- test/form/index.js | 2 +- test/form/samples/recursive-calls/_config.js | 1 - .../recursive-multi-expressions/_config.js | 3 ++ .../recursive-multi-expressions/_expected.js | 19 +++++++++++ .../recursive-multi-expressions/main.js | 21 ++++++++++++ test/form/samples/supports-core-js/_config.js | 1 - .../form/samples/supports-es6-shim/_config.js | 1 - 11 files changed, 113 insertions(+), 40 deletions(-) create mode 100644 test/form/samples/recursive-multi-expressions/_config.js create mode 100644 test/form/samples/recursive-multi-expressions/_expected.js create mode 100644 test/form/samples/recursive-multi-expressions/main.js diff --git a/src/ast/ExecutionContext.ts b/src/ast/ExecutionContext.ts index 1da99538f5c..7c3e89ea1e4 100644 --- a/src/ast/ExecutionContext.ts +++ b/src/ast/ExecutionContext.ts @@ -1,5 +1,5 @@ -import CallExpression from './nodes/CallExpression'; import { ExpressionEntity } from './nodes/shared/Expression'; +import { PathTracker } from './utils/PathTracker'; import ThisVariable from './variables/ThisVariable'; interface ExecutionContextIgnore { @@ -9,19 +9,25 @@ interface ExecutionContextIgnore { } export interface ExecutionContext { - calledExpressions: Set; + accessed: PathTracker; + assigned: PathTracker; + called: PathTracker; ignore: ExecutionContextIgnore; + instantiated: PathTracker; replacedVariableInits: Map; } export function createExecutionContext(): ExecutionContext { return { - calledExpressions: new Set(), + accessed: new PathTracker(), + assigned: new PathTracker(), + called: new PathTracker(), ignore: { breakStatements: false, labels: new Set(), returnAwaitYield: false }, + instantiated: new PathTracker(), replacedVariableInits: new Map() }; } diff --git a/src/ast/nodes/CallExpression.ts b/src/ast/nodes/CallExpression.ts index a71ff2e2d65..ee64ee90c12 100644 --- a/src/ast/nodes/CallExpression.ts +++ b/src/ast/nodes/CallExpression.ts @@ -158,24 +158,26 @@ export default class CallExpression extends NodeBase implements DeoptimizableEnt if (argument.hasEffects(context)) return true; } if (this.context.annotations && this.annotatedPure) return false; - if (this.callee.hasEffects(context)) return true; - if (context.calledExpressions.has(this)) return false; - context.calledExpressions.add(this); - return this.callee.hasEffectsWhenCalledAtPath(EMPTY_PATH, this.callOptions, context); + return ( + this.callee.hasEffects(context) || + this.callee.hasEffectsWhenCalledAtPath(EMPTY_PATH, this.callOptions, context) + ); } hasEffectsWhenAccessedAtPath(path: ObjectPath, context: ExecutionContext): boolean { - return ( - path.length > 0 && - (this.returnExpression as ExpressionEntity).hasEffectsWhenAccessedAtPath(path, context) - ); + if (path.length === 0) return false; + const trackedExpressions = context.accessed.getEntities(path); + if (trackedExpressions.has(this)) return false; + trackedExpressions.add(this); + return (this.returnExpression as ExpressionEntity).hasEffectsWhenAccessedAtPath(path, context); } hasEffectsWhenAssignedAtPath(path: ObjectPath, context: ExecutionContext): boolean { - return ( - path.length === 0 || - (this.returnExpression as ExpressionEntity).hasEffectsWhenAssignedAtPath(path, context) - ); + if (path.length === 0) return true; + const trackedExpressions = context.assigned.getEntities(path); + if (trackedExpressions.has(this)) return false; + trackedExpressions.add(this); + return (this.returnExpression as ExpressionEntity).hasEffectsWhenAssignedAtPath(path, context); } hasEffectsWhenCalledAtPath( @@ -183,6 +185,12 @@ export default class CallExpression extends NodeBase implements DeoptimizableEnt callOptions: CallOptions, context: ExecutionContext ): boolean { + const trackedExpressions = (callOptions.withNew + ? context.instantiated + : context.called + ).getEntities(path); + if (trackedExpressions.has(this)) return false; + trackedExpressions.add(this); return (this.returnExpression as ExpressionEntity).hasEffectsWhenCalledAtPath( path, callOptions, diff --git a/src/ast/nodes/Property.ts b/src/ast/nodes/Property.ts index 4b603644a70..c3e9d7d30a2 100644 --- a/src/ast/nodes/Property.ts +++ b/src/ast/nodes/Property.ts @@ -103,6 +103,9 @@ export default class Property extends NodeBase implements DeoptimizableEntity { hasEffectsWhenAccessedAtPath(path: ObjectPath, context: ExecutionContext): boolean { if (this.kind === 'get') { + const trackedExpressions = context.accessed.getEntities(path); + if (trackedExpressions.has(this)) return false; + trackedExpressions.add(this); return ( this.value.hasEffectsWhenCalledAtPath(EMPTY_PATH, this.accessorCallOptions, context) || (path.length > 0 && @@ -114,16 +117,21 @@ export default class Property extends NodeBase implements DeoptimizableEntity { hasEffectsWhenAssignedAtPath(path: ObjectPath, context: ExecutionContext): boolean { if (this.kind === 'get') { - return ( - path.length === 0 || - (this.returnExpression as ExpressionEntity).hasEffectsWhenAssignedAtPath(path, context) + if (path.length === 0) return true; + const trackedExpressions = context.assigned.getEntities(path); + if (trackedExpressions.has(this)) return false; + trackedExpressions.add(this); + return (this.returnExpression as ExpressionEntity).hasEffectsWhenAssignedAtPath( + path, + context ); } if (this.kind === 'set') { - return ( - path.length > 0 || - this.value.hasEffectsWhenCalledAtPath(EMPTY_PATH, this.accessorCallOptions, context) - ); + if (path.length > 0) return true; + const trackedExpressions = context.assigned.getEntities(path); + if (trackedExpressions.has(this)) return false; + trackedExpressions.add(this); + return this.value.hasEffectsWhenCalledAtPath(EMPTY_PATH, this.accessorCallOptions, context); } return this.value.hasEffectsWhenAssignedAtPath(path, context); } @@ -134,6 +142,12 @@ export default class Property extends NodeBase implements DeoptimizableEntity { context: ExecutionContext ) { if (this.kind === 'get') { + const trackedExpressions = (callOptions.withNew + ? context.instantiated + : context.called + ).getEntities(path); + if (trackedExpressions.has(this)) return false; + trackedExpressions.add(this); return (this.returnExpression as ExpressionEntity).hasEffectsWhenCalledAtPath( path, callOptions, diff --git a/src/ast/variables/LocalVariable.ts b/src/ast/variables/LocalVariable.ts index 4ba7bf8f367..95646cf8049 100644 --- a/src/ast/variables/LocalVariable.ts +++ b/src/ast/variables/LocalVariable.ts @@ -128,20 +128,21 @@ export default class LocalVariable extends Variable { hasEffectsWhenAccessedAtPath(path: ObjectPath, context: ExecutionContext) { if (path.length === 0) return false; - return ( - this.isReassigned || - path.length > MAX_PATH_DEPTH || - ((this.init && this.init.hasEffectsWhenAccessedAtPath(path, context)) as boolean) - ); + if (this.isReassigned || path.length > MAX_PATH_DEPTH) return true; + const trackedExpressions = context.accessed.getEntities(path); + if (trackedExpressions.has(this)) return false; + trackedExpressions.add(this); + return (this.init && this.init.hasEffectsWhenAccessedAtPath(path, context)) as boolean; } hasEffectsWhenAssignedAtPath(path: ObjectPath, context: ExecutionContext) { if (this.included || path.length > MAX_PATH_DEPTH) return true; if (path.length === 0) return false; - return ( - this.isReassigned || - ((this.init && this.init.hasEffectsWhenAssignedAtPath(path, context)) as boolean) - ); + if (this.isReassigned) return true; + const trackedExpressions = context.assigned.getEntities(path); + if (trackedExpressions.has(this)) return false; + trackedExpressions.add(this); + return (this.init && this.init.hasEffectsWhenAssignedAtPath(path, context)) as boolean; } hasEffectsWhenCalledAtPath( @@ -149,11 +150,15 @@ export default class LocalVariable extends Variable { callOptions: CallOptions, context: ExecutionContext ) { - if (path.length > MAX_PATH_DEPTH) return true; - return ( - this.isReassigned || - ((this.init && this.init.hasEffectsWhenCalledAtPath(path, callOptions, context)) as boolean) - ); + if (path.length > MAX_PATH_DEPTH || this.isReassigned) return true; + const trackedExpressions = (callOptions.withNew + ? context.instantiated + : context.called + ).getEntities(path); + if (trackedExpressions.has(this)) return false; + trackedExpressions.add(this); + return (this.init && + this.init.hasEffectsWhenCalledAtPath(path, callOptions, context)) as boolean; } include() { diff --git a/test/form/index.js b/test/form/index.js index 3b63e733172..56eac6ecfe6 100644 --- a/test/form/index.js +++ b/test/form/index.js @@ -6,7 +6,7 @@ const { extend, normaliseOutput, runTestSuiteWithSamples } = require('../utils.j const FORMATS = ['amd', 'cjs', 'system', 'es', 'iife', 'umd']; -runTestSuiteWithSamples.only('form', path.resolve(__dirname, 'samples'), (dir, config) => { +runTestSuiteWithSamples('form', path.resolve(__dirname, 'samples'), (dir, config) => { const isSingleFormatTest = sander.existsSync(dir + '/_expected.js'); const itOrDescribe = isSingleFormatTest ? it : describe; (config.skip ? itOrDescribe.skip : config.solo ? itOrDescribe.only : itOrDescribe)( diff --git a/test/form/samples/recursive-calls/_config.js b/test/form/samples/recursive-calls/_config.js index 89926564fd6..7258d5addec 100644 --- a/test/form/samples/recursive-calls/_config.js +++ b/test/form/samples/recursive-calls/_config.js @@ -1,4 +1,3 @@ module.exports = { - skip: true, description: 'do not fail for recursive calls' }; diff --git a/test/form/samples/recursive-multi-expressions/_config.js b/test/form/samples/recursive-multi-expressions/_config.js new file mode 100644 index 00000000000..44fd55b6d27 --- /dev/null +++ b/test/form/samples/recursive-multi-expressions/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'handles recursive multi-expressions' +}; diff --git a/test/form/samples/recursive-multi-expressions/_expected.js b/test/form/samples/recursive-multi-expressions/_expected.js new file mode 100644 index 00000000000..7e29ee70457 --- /dev/null +++ b/test/form/samples/recursive-multi-expressions/_expected.js @@ -0,0 +1,19 @@ +const unknown = globalThis.unknown; + +var logical1 = logical1 || (() => {}); +logical1()(); +logical1.x = 1; +logical1().x = 1; +logical1()().x = 1; + +var logical2 = logical2 || console.log; +logical2(); + +var conditional1 = unknown ? conditional1 : () => {}; +conditional1()(); +conditional1.x = 1; +conditional1().x = 1; +conditional1()().x = 1; + +var conditional2 = unknown ? conditional1 : console.log; +conditional2(); diff --git a/test/form/samples/recursive-multi-expressions/main.js b/test/form/samples/recursive-multi-expressions/main.js new file mode 100644 index 00000000000..3b959d2d3b7 --- /dev/null +++ b/test/form/samples/recursive-multi-expressions/main.js @@ -0,0 +1,21 @@ +const unknown = globalThis.unknown; + +var logical1 = logical1 || (() => {}); +logical1(); // removed +logical1()(); +logical1.x = 1; +logical1().x = 1; +logical1()().x = 1; + +var logical2 = logical2 || console.log; +logical2(); + +var conditional1 = unknown ? conditional1 : () => {}; +conditional1(); // removed +conditional1()(); +conditional1.x = 1; +conditional1().x = 1; +conditional1()().x = 1; + +var conditional2 = unknown ? conditional1 : console.log; +conditional2(); diff --git a/test/form/samples/supports-core-js/_config.js b/test/form/samples/supports-core-js/_config.js index fda768aa9dd..c479ac2a0a2 100644 --- a/test/form/samples/supports-core-js/_config.js +++ b/test/form/samples/supports-core-js/_config.js @@ -1,5 +1,4 @@ module.exports = { - skip: true, description: 'supports core-js', options: { // check against tree-shake: false when updating the polyfill diff --git a/test/form/samples/supports-es6-shim/_config.js b/test/form/samples/supports-es6-shim/_config.js index 823e1f48d1d..3097ca1d006 100644 --- a/test/form/samples/supports-es6-shim/_config.js +++ b/test/form/samples/supports-es6-shim/_config.js @@ -1,5 +1,4 @@ module.exports = { - skip: true, description: 'supports es6-shim', options: { onwarn(warning) { From 30016d41cfe5fe1ee0cae9c2df7fa5918d934202 Mon Sep 17 00:00:00 2001 From: Lukas Taegert-Atkinson Date: Fri, 6 Sep 2019 16:56:03 +0200 Subject: [PATCH 09/35] Fix perf script --- scripts/load-perf-config.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/scripts/load-perf-config.js b/scripts/load-perf-config.js index a043e4649bb..8784f9c5071 100644 --- a/scripts/load-perf-config.js +++ b/scripts/load-perf-config.js @@ -8,7 +8,9 @@ const configFile = path.resolve(exports.targetDir, 'rollup.config.js'); try { fs.accessSync(configFile, fs.constants.R_OK); } catch (e) { - console.error(`No valid "rollup.config.js" in ${exports.targetDir}. Did you "npm run perf:init"?`); + console.error( + `No valid "rollup.config.js" in ${exports.targetDir}. Did you "npm run perf:init"?` + ); process.exit(1); } @@ -18,8 +20,9 @@ exports.loadPerfConfig = async () => { external: id => (id[0] !== '.' && !path.isAbsolute(id)) || id.slice(-5, id.length) === '.json', onwarn: warning => console.error(warning.message) }); - const configs = loadConfigFromCode((await bundle.generate({ format: 'cjs' })).code); - return Array.isArray(configs) ? configs[0] : configs; + let config = loadConfigFromCode((await bundle.generate({ format: 'cjs' })).output[0].code); + config = typeof config === 'function' ? config({}) : config; + return Array.isArray(config) ? config[0] : config; }; function loadConfigFromCode(code) { From 5a2da60d9e6915c4ad870a3303d93e4bf4d928f8 Mon Sep 17 00:00:00 2001 From: Lukas Taegert-Atkinson Date: Sat, 7 Sep 2019 08:34:19 +0200 Subject: [PATCH 10/35] Use a Symbol for unknown values as well --- src/ast/nodes/BinaryExpression.ts | 16 ++++++++-------- src/ast/nodes/CallExpression.ts | 6 +++--- src/ast/nodes/ConditionalExpression.ts | 6 +++--- src/ast/nodes/IfStatement.ts | 10 +++++----- src/ast/nodes/Literal.ts | 4 ++-- src/ast/nodes/LogicalExpression.ts | 6 +++--- src/ast/nodes/MemberExpression.ts | 6 +++--- src/ast/nodes/ObjectExpression.ts | 8 ++++---- src/ast/nodes/Property.ts | 6 +++--- src/ast/nodes/TemplateLiteral.ts | 4 ++-- src/ast/nodes/UnaryExpression.ts | 10 +++++----- src/ast/nodes/shared/Expression.ts | 2 +- src/ast/nodes/shared/MultiExpression.ts | 4 ++-- src/ast/nodes/shared/Node.ts | 4 ++-- src/ast/values.ts | 23 ++++++++++------------- src/ast/variables/LocalVariable.ts | 6 +++--- src/ast/variables/ThisVariable.ts | 4 ++-- src/ast/variables/Variable.ts | 4 ++-- 18 files changed, 63 insertions(+), 66 deletions(-) diff --git a/src/ast/nodes/BinaryExpression.ts b/src/ast/nodes/BinaryExpression.ts index 3c6f17d2051..7b6fe45ccb5 100644 --- a/src/ast/nodes/BinaryExpression.ts +++ b/src/ast/nodes/BinaryExpression.ts @@ -1,7 +1,7 @@ import { DeoptimizableEntity } from '../DeoptimizableEntity'; import { ExecutionContext } from '../ExecutionContext'; import { EMPTY_IMMUTABLE_TRACKER, PathTracker } from '../utils/PathTracker'; -import { EMPTY_PATH, LiteralValueOrUnknown, ObjectPath, UNKNOWN_VALUE } from '../values'; +import { EMPTY_PATH, LiteralValueOrUnknown, ObjectPath, UnknownValue } from '../values'; import ExpressionStatement from './ExpressionStatement'; import { LiteralValue } from './Literal'; import * as NodeType from './NodeType'; @@ -32,8 +32,8 @@ const binaryOperators: { '>>': (left: any, right: any) => left >> right, '>>>': (left: any, right: any) => left >>> right, '^': (left: any, right: any) => left ^ right, - in: () => UNKNOWN_VALUE, - instanceof: () => UNKNOWN_VALUE, + in: () => UnknownValue, + instanceof: () => UnknownValue, '|': (left: any, right: any) => left | right }; @@ -50,17 +50,17 @@ export default class BinaryExpression extends NodeBase implements DeoptimizableE recursionTracker: PathTracker, origin: DeoptimizableEntity ): LiteralValueOrUnknown { - if (path.length > 0) return UNKNOWN_VALUE; + if (path.length > 0) return UnknownValue; const leftValue = this.left.getLiteralValueAtPath(EMPTY_PATH, recursionTracker, origin); - if (leftValue === UNKNOWN_VALUE) return UNKNOWN_VALUE; + if (leftValue === UnknownValue) return UnknownValue; const rightValue = this.right.getLiteralValueAtPath(EMPTY_PATH, recursionTracker, origin); - if (rightValue === UNKNOWN_VALUE) return UNKNOWN_VALUE; + if (rightValue === UnknownValue) return UnknownValue; const operatorFn = binaryOperators[this.operator]; - if (!operatorFn) return UNKNOWN_VALUE; + if (!operatorFn) return UnknownValue; - return operatorFn(leftValue as LiteralValue, rightValue as LiteralValue); + return operatorFn(leftValue, rightValue); } hasEffects(context: ExecutionContext): boolean { diff --git a/src/ast/nodes/CallExpression.ts b/src/ast/nodes/CallExpression.ts index ee64ee90c12..81c1456a2c3 100644 --- a/src/ast/nodes/CallExpression.ts +++ b/src/ast/nodes/CallExpression.ts @@ -15,7 +15,7 @@ import { ObjectPath, UNKNOWN_EXPRESSION, UNKNOWN_PATH, - UNKNOWN_VALUE + UnknownValue } from '../values'; import Identifier from './Identifier'; import * as NodeType from './NodeType'; @@ -110,11 +110,11 @@ export default class CallExpression extends NodeBase implements DeoptimizableEnt ); } if (this.returnExpression === UNKNOWN_EXPRESSION) { - return UNKNOWN_VALUE; + return UnknownValue; } const trackedEntities = recursionTracker.getEntities(path); if (trackedEntities.has(this.returnExpression)) { - return UNKNOWN_VALUE; + return UnknownValue; } this.expressionsToBeDeoptimized.push(origin); trackedEntities.add(this.returnExpression); diff --git a/src/ast/nodes/ConditionalExpression.ts b/src/ast/nodes/ConditionalExpression.ts index b88bee6234c..e0ebfb5f0ec 100644 --- a/src/ast/nodes/ConditionalExpression.ts +++ b/src/ast/nodes/ConditionalExpression.ts @@ -16,7 +16,7 @@ import { LiteralValueOrUnknown, ObjectPath, UNKNOWN_PATH, - UNKNOWN_VALUE + UnknownValue } from '../values'; import CallExpression from './CallExpression'; import * as NodeType from './NodeType'; @@ -71,7 +71,7 @@ export default class ConditionalExpression extends NodeBase implements Deoptimiz origin: DeoptimizableEntity ): LiteralValueOrUnknown { if (!this.isBranchResolutionAnalysed) this.analyseBranchResolution(); - if (this.usedBranch === null) return UNKNOWN_VALUE; + if (this.usedBranch === null) return UnknownValue; this.expressionsToBeDeoptimized.push(origin); return this.usedBranch.getLiteralValueAtPath(path, recursionTracker, origin); } @@ -179,7 +179,7 @@ export default class ConditionalExpression extends NodeBase implements Deoptimiz private analyseBranchResolution() { this.isBranchResolutionAnalysed = true; const testValue = this.test.getLiteralValueAtPath(EMPTY_PATH, EMPTY_IMMUTABLE_TRACKER, this); - if (testValue !== UNKNOWN_VALUE) { + if (testValue !== UnknownValue) { if (testValue) { this.usedBranch = this.consequent; this.unusedBranch = this.alternate; diff --git a/src/ast/nodes/IfStatement.ts b/src/ast/nodes/IfStatement.ts index 743c6a330bf..5d73bf233cc 100644 --- a/src/ast/nodes/IfStatement.ts +++ b/src/ast/nodes/IfStatement.ts @@ -4,7 +4,7 @@ import { removeAnnotations } from '../../utils/treeshakeNode'; import { DeoptimizableEntity } from '../DeoptimizableEntity'; import { ExecutionContext } from '../ExecutionContext'; import { EMPTY_IMMUTABLE_TRACKER } from '../utils/PathTracker'; -import { EMPTY_PATH, LiteralValueOrUnknown, UNKNOWN_VALUE } from '../values'; +import { EMPTY_PATH, LiteralValueOrUnknown, UnknownValue } from '../values'; import * as NodeType from './NodeType'; import { ExpressionNode, IncludeChildren, StatementBase, StatementNode } from './shared/Node'; @@ -20,19 +20,19 @@ export default class IfStatement extends StatementBase implements DeoptimizableE bind() { super.bind(); if (!this.isTestValueAnalysed) { - this.testValue = UNKNOWN_VALUE; + this.testValue = UnknownValue; this.isTestValueAnalysed = true; this.testValue = this.test.getLiteralValueAtPath(EMPTY_PATH, EMPTY_IMMUTABLE_TRACKER, this); } } deoptimizeCache() { - this.testValue = UNKNOWN_VALUE; + this.testValue = UnknownValue; } hasEffects(context: ExecutionContext): boolean { if (this.test.hasEffects(context)) return true; - if (this.testValue === UNKNOWN_VALUE) { + if (this.testValue === UnknownValue) { return ( this.consequent.hasEffects(context) || (this.alternate !== null && this.alternate.hasEffects(context)) @@ -53,7 +53,7 @@ export default class IfStatement extends StatementBase implements DeoptimizableE } return; } - const hasUnknownTest = this.testValue === UNKNOWN_VALUE; + const hasUnknownTest = this.testValue === UnknownValue; if (hasUnknownTest || this.test.shouldBeIncluded()) { this.test.include(false); } diff --git a/src/ast/nodes/Literal.ts b/src/ast/nodes/Literal.ts index 05731488909..b23e40f09f0 100644 --- a/src/ast/nodes/Literal.ts +++ b/src/ast/nodes/Literal.ts @@ -10,7 +10,7 @@ import { MemberDescription, ObjectPath, UNKNOWN_EXPRESSION, - UNKNOWN_VALUE + UnknownValue } from '../values'; import * as NodeType from './NodeType'; import { NodeBase } from './shared/Node'; @@ -32,7 +32,7 @@ export default class Literal extends NodeBase { // to support shims for regular expressions this.context.code.charCodeAt(this.start) === 47 ) { - return UNKNOWN_VALUE; + return UnknownValue; } return this.value as any; } diff --git a/src/ast/nodes/LogicalExpression.ts b/src/ast/nodes/LogicalExpression.ts index f4b2907de19..ad1c16a7ccf 100644 --- a/src/ast/nodes/LogicalExpression.ts +++ b/src/ast/nodes/LogicalExpression.ts @@ -16,7 +16,7 @@ import { LiteralValueOrUnknown, ObjectPath, UNKNOWN_PATH, - UNKNOWN_VALUE + UnknownValue } from '../values'; import CallExpression from './CallExpression'; import * as NodeType from './NodeType'; @@ -73,7 +73,7 @@ export default class LogicalExpression extends NodeBase implements Deoptimizable origin: DeoptimizableEntity ): LiteralValueOrUnknown { if (!this.isBranchResolutionAnalysed) this.analyseBranchResolution(); - if (this.usedBranch === null) return UNKNOWN_VALUE; + if (this.usedBranch === null) return UnknownValue; this.expressionsToBeDeoptimized.push(origin); return this.usedBranch.getLiteralValueAtPath(path, recursionTracker, origin); } @@ -184,7 +184,7 @@ export default class LogicalExpression extends NodeBase implements Deoptimizable private analyseBranchResolution() { this.isBranchResolutionAnalysed = true; const leftValue = this.left.getLiteralValueAtPath(EMPTY_PATH, EMPTY_IMMUTABLE_TRACKER, this); - if (leftValue !== UNKNOWN_VALUE) { + if (leftValue !== UnknownValue) { if (this.operator === '||' ? leftValue : !leftValue) { this.usedBranch = this.left; this.unusedBranch = this.right; diff --git a/src/ast/nodes/MemberExpression.ts b/src/ast/nodes/MemberExpression.ts index 208003a2100..8f564cf1103 100644 --- a/src/ast/nodes/MemberExpression.ts +++ b/src/ast/nodes/MemberExpression.ts @@ -11,8 +11,8 @@ import { LiteralValueOrUnknown, ObjectPath, ObjectPathKey, - UNKNOWN_VALUE, - UnknownKey + UnknownKey, + UnknownValue } from '../values'; import ExternalVariable from '../variables/ExternalVariable'; import NamespaceVariable from '../variables/NamespaceVariable'; @@ -257,7 +257,7 @@ export default class MemberExpression extends NodeBase implements DeoptimizableE private analysePropertyKey() { this.propertyKey = UnknownKey; const value = this.property.getLiteralValueAtPath(EMPTY_PATH, EMPTY_IMMUTABLE_TRACKER, this); - this.propertyKey = value === UNKNOWN_VALUE ? UnknownKey : String(value); + this.propertyKey = value === UnknownValue ? UnknownKey : String(value); } private disallowNamespaceReassignment() { diff --git a/src/ast/nodes/ObjectExpression.ts b/src/ast/nodes/ObjectExpression.ts index b8004394396..3dd11910a3d 100644 --- a/src/ast/nodes/ObjectExpression.ts +++ b/src/ast/nodes/ObjectExpression.ts @@ -14,7 +14,7 @@ import { ObjectPath, UNKNOWN_EXPRESSION, UNKNOWN_PATH, - UNKNOWN_VALUE + UnknownValue } from '../values'; import Identifier from './Identifier'; import Literal from './Literal'; @@ -105,7 +105,7 @@ export default class ObjectExpression extends NodeBase { typeof key !== 'string' || this.deoptimizedPaths.has(key) ) - return UNKNOWN_VALUE; + return UnknownValue; if ( path.length === 1 && @@ -127,7 +127,7 @@ export default class ObjectExpression extends NodeBase { (this.propertyMap as PropertyMap)[key].exactMatchRead === null || (this.propertyMap as PropertyMap)[key].propertiesRead.length > 1 ) { - return UNKNOWN_VALUE; + return UnknownValue; } const expressionsToBeDeoptimized = this.expressionsToBeDeoptimized.get(key); @@ -292,7 +292,7 @@ export default class ObjectExpression extends NodeBase { EMPTY_IMMUTABLE_TRACKER, this ); - if (keyValue === UNKNOWN_VALUE) { + if (keyValue === UnknownValue) { if (isRead) { this.unmatchablePropertiesRead.push(property); } else { diff --git a/src/ast/nodes/Property.ts b/src/ast/nodes/Property.ts index c3e9d7d30a2..ce0ae9a73fe 100644 --- a/src/ast/nodes/Property.ts +++ b/src/ast/nodes/Property.ts @@ -9,8 +9,8 @@ import { LiteralValueOrUnknown, ObjectPath, UNKNOWN_EXPRESSION, - UNKNOWN_VALUE, - UnknownKey + UnknownKey, + UnknownValue } from '../values'; import * as NodeType from './NodeType'; import { ExpressionEntity } from './shared/Expression'; @@ -65,7 +65,7 @@ export default class Property extends NodeBase implements DeoptimizableEntity { origin: DeoptimizableEntity ): LiteralValueOrUnknown { if (this.kind === 'set') { - return UNKNOWN_VALUE; + return UnknownValue; } if (this.kind === 'get') { if (this.returnExpression === null) this.updateReturnExpression(); diff --git a/src/ast/nodes/TemplateLiteral.ts b/src/ast/nodes/TemplateLiteral.ts index f628bd25af2..93e199cefb6 100644 --- a/src/ast/nodes/TemplateLiteral.ts +++ b/src/ast/nodes/TemplateLiteral.ts @@ -1,6 +1,6 @@ import MagicString from 'magic-string'; import { RenderOptions } from '../../utils/renderHelpers'; -import { LiteralValueOrUnknown, ObjectPath, UNKNOWN_VALUE } from '../values'; +import { LiteralValueOrUnknown, ObjectPath, UnknownValue } from '../values'; import * as NodeType from './NodeType'; import { ExpressionNode, NodeBase } from './shared/Node'; import TemplateElement from './TemplateElement'; @@ -12,7 +12,7 @@ export default class TemplateLiteral extends NodeBase { getLiteralValueAtPath(path: ObjectPath): LiteralValueOrUnknown { if (path.length > 0 || this.quasis.length !== 1) { - return UNKNOWN_VALUE; + return UnknownValue; } return this.quasis[0].value.cooked; } diff --git a/src/ast/nodes/UnaryExpression.ts b/src/ast/nodes/UnaryExpression.ts index e294f6e8cf3..4607adf0667 100644 --- a/src/ast/nodes/UnaryExpression.ts +++ b/src/ast/nodes/UnaryExpression.ts @@ -1,7 +1,7 @@ import { DeoptimizableEntity } from '../DeoptimizableEntity'; import { ExecutionContext } from '../ExecutionContext'; import { PathTracker } from '../utils/PathTracker'; -import { EMPTY_PATH, LiteralValueOrUnknown, ObjectPath, UNKNOWN_VALUE } from '../values'; +import { EMPTY_PATH, LiteralValueOrUnknown, ObjectPath, UnknownValue } from '../values'; import Identifier from './Identifier'; import { LiteralValue } from './Literal'; import * as NodeType from './NodeType'; @@ -13,7 +13,7 @@ const unaryOperators: { '!': value => !value, '+': value => +(value as NonNullable), '-': value => -(value as NonNullable), - delete: () => UNKNOWN_VALUE, + delete: () => UnknownValue, typeof: value => typeof value, void: () => undefined, '~': value => ~(value as NonNullable) @@ -37,11 +37,11 @@ export default class UnaryExpression extends NodeBase { recursionTracker: PathTracker, origin: DeoptimizableEntity ): LiteralValueOrUnknown { - if (path.length > 0) return UNKNOWN_VALUE; + if (path.length > 0) return UnknownValue; const argumentValue = this.argument.getLiteralValueAtPath(EMPTY_PATH, recursionTracker, origin); - if (argumentValue === UNKNOWN_VALUE) return UNKNOWN_VALUE; + if (argumentValue === UnknownValue) return UnknownValue; - return unaryOperators[this.operator](argumentValue as LiteralValue); + return unaryOperators[this.operator](argumentValue); } hasEffects(context: ExecutionContext): boolean { diff --git a/src/ast/nodes/shared/Expression.ts b/src/ast/nodes/shared/Expression.ts index af762330ee2..953493090b4 100644 --- a/src/ast/nodes/shared/Expression.ts +++ b/src/ast/nodes/shared/Expression.ts @@ -13,7 +13,7 @@ export interface ExpressionEntity extends WritableEntity { /** * If possible it returns a stringifyable literal value for this node that can be used * for inlining or comparing values. - * Otherwise it should return UNKNOWN_VALUE. + * Otherwise it should return UnknownValue. */ getLiteralValueAtPath( path: ObjectPath, diff --git a/src/ast/nodes/shared/MultiExpression.ts b/src/ast/nodes/shared/MultiExpression.ts index 71ca0c8d784..09db2a05a12 100644 --- a/src/ast/nodes/shared/MultiExpression.ts +++ b/src/ast/nodes/shared/MultiExpression.ts @@ -2,7 +2,7 @@ import CallOptions from '../../CallOptions'; import { DeoptimizableEntity } from '../../DeoptimizableEntity'; import { ExecutionContext } from '../../ExecutionContext'; import { PathTracker } from '../../utils/PathTracker'; -import { LiteralValueOrUnknown, ObjectPath, UNKNOWN_VALUE } from '../../values'; +import { LiteralValueOrUnknown, ObjectPath, UnknownValue } from '../../values'; import SpreadElement from '../SpreadElement'; import { ExpressionEntity } from './Expression'; import { ExpressionNode } from './Node'; @@ -23,7 +23,7 @@ export class MultiExpression implements ExpressionEntity { } getLiteralValueAtPath(): LiteralValueOrUnknown { - return UNKNOWN_VALUE; + return UnknownValue; } getReturnExpressionWhenCalledAtPath( diff --git a/src/ast/nodes/shared/Node.ts b/src/ast/nodes/shared/Node.ts index 10375e5f03c..e767f6dad5f 100644 --- a/src/ast/nodes/shared/Node.ts +++ b/src/ast/nodes/shared/Node.ts @@ -9,7 +9,7 @@ import { createExecutionContext, ExecutionContext } from '../../ExecutionContext import { getAndCreateKeys, keys } from '../../keys'; import ChildScope from '../../scopes/ChildScope'; import { PathTracker } from '../../utils/PathTracker'; -import { LiteralValueOrUnknown, ObjectPath, UNKNOWN_EXPRESSION, UNKNOWN_VALUE } from '../../values'; +import { LiteralValueOrUnknown, ObjectPath, UNKNOWN_EXPRESSION, UnknownValue } from '../../values'; import LocalVariable from '../../variables/LocalVariable'; import Variable from '../../variables/Variable'; import SpreadElement from '../SpreadElement'; @@ -143,7 +143,7 @@ export class NodeBase implements ExpressionNode { _recursionTracker: PathTracker, _origin: DeoptimizableEntity ): LiteralValueOrUnknown { - return UNKNOWN_VALUE; + return UnknownValue; } getReturnExpressionWhenCalledAtPath( diff --git a/src/ast/values.ts b/src/ast/values.ts index 4e9dc381db4..a43fee22450 100644 --- a/src/ast/values.ts +++ b/src/ast/values.ts @@ -34,15 +34,12 @@ function assembleMemberDescriptions( return Object.create(inheritedDescriptions, memberDescriptions); } -export interface UnknownValue { - UNKNOWN_VALUE: true; -} -export const UNKNOWN_VALUE: UnknownValue = { UNKNOWN_VALUE: true }; -export type LiteralValueOrUnknown = LiteralValue | UnknownValue; +export const UnknownValue = Symbol('Unknown Value'); +export type LiteralValueOrUnknown = LiteralValue | typeof UnknownValue; export const UNKNOWN_EXPRESSION: ExpressionEntity = { deoptimizePath: () => {}, - getLiteralValueAtPath: () => UNKNOWN_VALUE, + getLiteralValueAtPath: () => UnknownValue, getReturnExpressionWhenCalledAtPath: () => UNKNOWN_EXPRESSION, hasEffectsWhenAccessedAtPath: path => path.length > 0, hasEffectsWhenAssignedAtPath: path => path.length > 0, @@ -88,8 +85,8 @@ export class UnknownArrayExpression implements ExpressionEntity { deoptimizePath() {} - getLiteralValueAtPath() { - return UNKNOWN_VALUE; + getLiteralValueAtPath(): LiteralValueOrUnknown { + return UnknownValue; } getReturnExpressionWhenCalledAtPath(path: ObjectPath) { @@ -168,7 +165,7 @@ const callsArgMutatesSelfReturnsArray: RawMemberDescription = { const UNKNOWN_LITERAL_BOOLEAN: ExpressionEntity = { deoptimizePath: () => {}, - getLiteralValueAtPath: () => UNKNOWN_VALUE, + getLiteralValueAtPath: () => UnknownValue, getReturnExpressionWhenCalledAtPath: path => { if (path.length === 1) { return getMemberReturnExpressionWhenCalled(literalBooleanMembers, path[0]); @@ -213,7 +210,7 @@ const callsArgReturnsBoolean: RawMemberDescription = { const UNKNOWN_LITERAL_NUMBER: ExpressionEntity = { deoptimizePath: () => {}, - getLiteralValueAtPath: () => UNKNOWN_VALUE, + getLiteralValueAtPath: () => UnknownValue, getReturnExpressionWhenCalledAtPath: path => { if (path.length === 1) { return getMemberReturnExpressionWhenCalled(literalNumberMembers, path[0]); @@ -266,7 +263,7 @@ const callsArgReturnsNumber: RawMemberDescription = { const UNKNOWN_LITERAL_STRING: ExpressionEntity = { deoptimizePath: () => {}, - getLiteralValueAtPath: () => UNKNOWN_VALUE, + getLiteralValueAtPath: () => UnknownValue, getReturnExpressionWhenCalledAtPath: path => { if (path.length === 1) { return getMemberReturnExpressionWhenCalled(literalStringMembers, path[0]); @@ -305,8 +302,8 @@ export class UnknownObjectExpression implements ExpressionEntity { deoptimizePath() {} - getLiteralValueAtPath() { - return UNKNOWN_VALUE; + getLiteralValueAtPath(): LiteralValueOrUnknown { + return UnknownValue; } getReturnExpressionWhenCalledAtPath(path: ObjectPath) { diff --git a/src/ast/variables/LocalVariable.ts b/src/ast/variables/LocalVariable.ts index 95646cf8049..2d0110f043f 100644 --- a/src/ast/variables/LocalVariable.ts +++ b/src/ast/variables/LocalVariable.ts @@ -15,7 +15,7 @@ import { ObjectPath, UNKNOWN_EXPRESSION, UNKNOWN_PATH, - UNKNOWN_VALUE + UnknownValue } from '../values'; import Variable from './Variable'; @@ -94,11 +94,11 @@ export default class LocalVariable extends Variable { origin: DeoptimizableEntity ): LiteralValueOrUnknown { if (this.isReassigned || !this.init || path.length > MAX_PATH_DEPTH) { - return UNKNOWN_VALUE; + return UnknownValue; } const trackedEntities = recursionTracker.getEntities(path); if (trackedEntities.has(this.init)) { - return UNKNOWN_VALUE; + return UnknownValue; } this.expressionsToBeDeoptimized.push(origin); trackedEntities.add(this.init); diff --git a/src/ast/variables/ThisVariable.ts b/src/ast/variables/ThisVariable.ts index 4fe0d7d2c3d..b3ddc66c90c 100644 --- a/src/ast/variables/ThisVariable.ts +++ b/src/ast/variables/ThisVariable.ts @@ -2,7 +2,7 @@ import { AstContext } from '../../Module'; import CallOptions from '../CallOptions'; import { ExecutionContext } from '../ExecutionContext'; import { ExpressionEntity } from '../nodes/shared/Expression'; -import { LiteralValueOrUnknown, ObjectPath, UNKNOWN_EXPRESSION, UNKNOWN_VALUE } from '../values'; +import { LiteralValueOrUnknown, ObjectPath, UNKNOWN_EXPRESSION, UnknownValue } from '../values'; import LocalVariable from './LocalVariable'; export default class ThisVariable extends LocalVariable { @@ -11,7 +11,7 @@ export default class ThisVariable extends LocalVariable { } getLiteralValueAtPath(): LiteralValueOrUnknown { - return UNKNOWN_VALUE; + return UnknownValue; } hasEffectsWhenAccessedAtPath(path: ObjectPath, context: ExecutionContext) { diff --git a/src/ast/variables/Variable.ts b/src/ast/variables/Variable.ts index 5fbf7f530d7..ad1e971312b 100644 --- a/src/ast/variables/Variable.ts +++ b/src/ast/variables/Variable.ts @@ -8,7 +8,7 @@ import { ExpressionEntity } from '../nodes/shared/Expression'; import { ExpressionNode } from '../nodes/shared/Node'; import SpreadElement from '../nodes/SpreadElement'; import { PathTracker } from '../utils/PathTracker'; -import { LiteralValueOrUnknown, ObjectPath, UNKNOWN_EXPRESSION, UNKNOWN_VALUE } from '../values'; +import { LiteralValueOrUnknown, ObjectPath, UNKNOWN_EXPRESSION, UnknownValue } from '../values'; export default class Variable implements ExpressionEntity { alwaysRendered = false; @@ -44,7 +44,7 @@ export default class Variable implements ExpressionEntity { _recursionTracker: PathTracker, _origin: DeoptimizableEntity ): LiteralValueOrUnknown { - return UNKNOWN_VALUE; + return UnknownValue; } getName(): string { From 3f05ab792bb6f5ec84d1c24a694f9d0847213155 Mon Sep 17 00:00:00 2001 From: Lukas Taegert-Atkinson Date: Fri, 4 Oct 2019 20:05:38 +0200 Subject: [PATCH 11/35] Fix tests after merge --- LICENSE.md | 2 +- package-lock.json | 8 ++++---- package.json | 2 +- test/form/samples/assignment-to-global/_expected.js | 1 + .../side-effect-q/{_expected/es.js => _expected.js} | 0 test/form/samples/side-effect-q/_expected/amd.js | 5 ----- test/form/samples/side-effect-q/_expected/cjs.js | 2 -- test/form/samples/side-effect-q/_expected/iife.js | 6 ------ test/form/samples/side-effect-q/_expected/umd.js | 8 -------- test/form/samples/side-effect-q/main.js | 4 ++-- .../side-effect-r/{_expected/es.js => _expected.js} | 0 test/form/samples/side-effect-r/_expected/amd.js | 5 ----- test/form/samples/side-effect-r/_expected/cjs.js | 2 -- test/form/samples/side-effect-r/_expected/iife.js | 6 ------ test/form/samples/side-effect-r/_expected/system.js | 10 ---------- .../{_expected/es.js => _expected.js} | 0 .../_expected/cjs.js | 2 -- .../_expected/iife.js | 6 ------ .../_expected/system.js | 10 ---------- .../_expected/umd.js | 8 -------- .../{_expected/es.js => _expected.js} | 0 .../tree-shake-curried-functions/_expected/amd.js | 5 ----- .../tree-shake-curried-functions/_expected/system.js | 10 ---------- .../tree-shake-curried-functions/_expected/umd.js | 8 -------- 24 files changed, 9 insertions(+), 101 deletions(-) create mode 100644 test/form/samples/assignment-to-global/_expected.js rename test/form/samples/side-effect-q/{_expected/es.js => _expected.js} (100%) delete mode 100644 test/form/samples/side-effect-q/_expected/amd.js delete mode 100644 test/form/samples/side-effect-q/_expected/cjs.js delete mode 100644 test/form/samples/side-effect-q/_expected/iife.js delete mode 100644 test/form/samples/side-effect-q/_expected/umd.js rename test/form/samples/side-effect-r/{_expected/es.js => _expected.js} (100%) delete mode 100644 test/form/samples/side-effect-r/_expected/amd.js delete mode 100644 test/form/samples/side-effect-r/_expected/cjs.js delete mode 100644 test/form/samples/side-effect-r/_expected/iife.js delete mode 100644 test/form/samples/side-effect-r/_expected/system.js rename test/form/samples/side-effect-with-plusplus-expression/{_expected/es.js => _expected.js} (100%) delete mode 100644 test/form/samples/side-effect-with-plusplus-expression/_expected/cjs.js delete mode 100644 test/form/samples/side-effect-with-plusplus-expression/_expected/iife.js delete mode 100644 test/form/samples/side-effect-with-plusplus-expression/_expected/system.js delete mode 100644 test/form/samples/side-effect-with-plusplus-expression/_expected/umd.js rename test/form/samples/tree-shake-curried-functions/{_expected/es.js => _expected.js} (100%) delete mode 100644 test/form/samples/tree-shake-curried-functions/_expected/amd.js delete mode 100644 test/form/samples/tree-shake-curried-functions/_expected/system.js delete mode 100644 test/form/samples/tree-shake-curried-functions/_expected/umd.js diff --git a/LICENSE.md b/LICENSE.md index 27e59d739f3..9f1eeef1d01 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -369,7 +369,7 @@ Repository: git://github.com/kamicane/require-relative.git ## rollup-pluginutils License: MIT By: Rich Harris -Repository: git+https://github.com/rollup/rollup-pluginutils.git +Repository: rollup/rollup-pluginutils --------------------------------------- diff --git a/package-lock.json b/package-lock.json index 9d6ba81a832..e89dc861aa5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5157,13 +5157,13 @@ } }, "rollup": { - "version": "1.23.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-1.23.0.tgz", - "integrity": "sha512-/p72Z3NbHWV+Vi1p2X+BmPA3WqlZxpUqCy6E8U4crMohZnI+j9Ob8ZAfFyNfddT0LxgnJM0olO4mg+noH4SFbg==", + "version": "1.23.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-1.23.1.tgz", + "integrity": "sha512-95C1GZQpr/NIA0kMUQmSjuMDQ45oZfPgDBcN0yZwBG7Kee//m7H68vgIyg+SPuyrTZ5PrXfyLK80OzXeKG5dAA==", "dev": true, "requires": { "@types/estree": "*", - "@types/node": "^12.7.10", + "@types/node": "*", "acorn": "^7.1.0" } }, diff --git a/package.json b/package.json index e92352cfd55..7d2e5b2267c 100644 --- a/package.json +++ b/package.json @@ -104,7 +104,7 @@ "pretty-ms": "^5.0.0", "require-relative": "^0.8.7", "requirejs": "^2.3.6", - "rollup": "^1.23.0", + "rollup": "^1.23.1", "rollup-plugin-alias": "^2.0.1", "rollup-plugin-buble": "^0.19.8", "rollup-plugin-commonjs": "^10.1.0", diff --git a/test/form/samples/assignment-to-global/_expected.js b/test/form/samples/assignment-to-global/_expected.js new file mode 100644 index 00000000000..d981fe4b5aa --- /dev/null +++ b/test/form/samples/assignment-to-global/_expected.js @@ -0,0 +1 @@ +globalThis.unknown = 1; diff --git a/test/form/samples/side-effect-q/_expected/es.js b/test/form/samples/side-effect-q/_expected.js similarity index 100% rename from test/form/samples/side-effect-q/_expected/es.js rename to test/form/samples/side-effect-q/_expected.js diff --git a/test/form/samples/side-effect-q/_expected/amd.js b/test/form/samples/side-effect-q/_expected/amd.js deleted file mode 100644 index f9f8229aa40..00000000000 --- a/test/form/samples/side-effect-q/_expected/amd.js +++ /dev/null @@ -1,5 +0,0 @@ -define(function () { 'use strict'; - - - -}); diff --git a/test/form/samples/side-effect-q/_expected/cjs.js b/test/form/samples/side-effect-q/_expected/cjs.js deleted file mode 100644 index eb109abbed0..00000000000 --- a/test/form/samples/side-effect-q/_expected/cjs.js +++ /dev/null @@ -1,2 +0,0 @@ -'use strict'; - diff --git a/test/form/samples/side-effect-q/_expected/iife.js b/test/form/samples/side-effect-q/_expected/iife.js deleted file mode 100644 index 43ef5426880..00000000000 --- a/test/form/samples/side-effect-q/_expected/iife.js +++ /dev/null @@ -1,6 +0,0 @@ -(function () { - 'use strict'; - - - -}()); diff --git a/test/form/samples/side-effect-q/_expected/umd.js b/test/form/samples/side-effect-q/_expected/umd.js deleted file mode 100644 index a12a1990f01..00000000000 --- a/test/form/samples/side-effect-q/_expected/umd.js +++ /dev/null @@ -1,8 +0,0 @@ -(function (factory) { - typeof define === 'function' && define.amd ? define(factory) : - factory(); -}(function () { 'use strict'; - - - -})); diff --git a/test/form/samples/side-effect-q/main.js b/test/form/samples/side-effect-q/main.js index 2272edb8bfa..32e0019af04 100644 --- a/test/form/samples/side-effect-q/main.js +++ b/test/form/samples/side-effect-q/main.js @@ -1,5 +1,5 @@ -var x = true ? foo () : bar(); +var x = true ? foo() : bar(); -function foo () { +function foo() { return 'should be removed, because x is unused'; } diff --git a/test/form/samples/side-effect-r/_expected/es.js b/test/form/samples/side-effect-r/_expected.js similarity index 100% rename from test/form/samples/side-effect-r/_expected/es.js rename to test/form/samples/side-effect-r/_expected.js diff --git a/test/form/samples/side-effect-r/_expected/amd.js b/test/form/samples/side-effect-r/_expected/amd.js deleted file mode 100644 index f9f8229aa40..00000000000 --- a/test/form/samples/side-effect-r/_expected/amd.js +++ /dev/null @@ -1,5 +0,0 @@ -define(function () { 'use strict'; - - - -}); diff --git a/test/form/samples/side-effect-r/_expected/cjs.js b/test/form/samples/side-effect-r/_expected/cjs.js deleted file mode 100644 index eb109abbed0..00000000000 --- a/test/form/samples/side-effect-r/_expected/cjs.js +++ /dev/null @@ -1,2 +0,0 @@ -'use strict'; - diff --git a/test/form/samples/side-effect-r/_expected/iife.js b/test/form/samples/side-effect-r/_expected/iife.js deleted file mode 100644 index 43ef5426880..00000000000 --- a/test/form/samples/side-effect-r/_expected/iife.js +++ /dev/null @@ -1,6 +0,0 @@ -(function () { - 'use strict'; - - - -}()); diff --git a/test/form/samples/side-effect-r/_expected/system.js b/test/form/samples/side-effect-r/_expected/system.js deleted file mode 100644 index a702f2b06ef..00000000000 --- a/test/form/samples/side-effect-r/_expected/system.js +++ /dev/null @@ -1,10 +0,0 @@ -System.register([], function () { - 'use strict'; - return { - execute: function () { - - - - } - }; -}); diff --git a/test/form/samples/side-effect-with-plusplus-expression/_expected/es.js b/test/form/samples/side-effect-with-plusplus-expression/_expected.js similarity index 100% rename from test/form/samples/side-effect-with-plusplus-expression/_expected/es.js rename to test/form/samples/side-effect-with-plusplus-expression/_expected.js diff --git a/test/form/samples/side-effect-with-plusplus-expression/_expected/cjs.js b/test/form/samples/side-effect-with-plusplus-expression/_expected/cjs.js deleted file mode 100644 index eb109abbed0..00000000000 --- a/test/form/samples/side-effect-with-plusplus-expression/_expected/cjs.js +++ /dev/null @@ -1,2 +0,0 @@ -'use strict'; - diff --git a/test/form/samples/side-effect-with-plusplus-expression/_expected/iife.js b/test/form/samples/side-effect-with-plusplus-expression/_expected/iife.js deleted file mode 100644 index 43ef5426880..00000000000 --- a/test/form/samples/side-effect-with-plusplus-expression/_expected/iife.js +++ /dev/null @@ -1,6 +0,0 @@ -(function () { - 'use strict'; - - - -}()); diff --git a/test/form/samples/side-effect-with-plusplus-expression/_expected/system.js b/test/form/samples/side-effect-with-plusplus-expression/_expected/system.js deleted file mode 100644 index a702f2b06ef..00000000000 --- a/test/form/samples/side-effect-with-plusplus-expression/_expected/system.js +++ /dev/null @@ -1,10 +0,0 @@ -System.register([], function () { - 'use strict'; - return { - execute: function () { - - - - } - }; -}); diff --git a/test/form/samples/side-effect-with-plusplus-expression/_expected/umd.js b/test/form/samples/side-effect-with-plusplus-expression/_expected/umd.js deleted file mode 100644 index a12a1990f01..00000000000 --- a/test/form/samples/side-effect-with-plusplus-expression/_expected/umd.js +++ /dev/null @@ -1,8 +0,0 @@ -(function (factory) { - typeof define === 'function' && define.amd ? define(factory) : - factory(); -}(function () { 'use strict'; - - - -})); diff --git a/test/form/samples/tree-shake-curried-functions/_expected/es.js b/test/form/samples/tree-shake-curried-functions/_expected.js similarity index 100% rename from test/form/samples/tree-shake-curried-functions/_expected/es.js rename to test/form/samples/tree-shake-curried-functions/_expected.js diff --git a/test/form/samples/tree-shake-curried-functions/_expected/amd.js b/test/form/samples/tree-shake-curried-functions/_expected/amd.js deleted file mode 100644 index f9f8229aa40..00000000000 --- a/test/form/samples/tree-shake-curried-functions/_expected/amd.js +++ /dev/null @@ -1,5 +0,0 @@ -define(function () { 'use strict'; - - - -}); diff --git a/test/form/samples/tree-shake-curried-functions/_expected/system.js b/test/form/samples/tree-shake-curried-functions/_expected/system.js deleted file mode 100644 index a702f2b06ef..00000000000 --- a/test/form/samples/tree-shake-curried-functions/_expected/system.js +++ /dev/null @@ -1,10 +0,0 @@ -System.register([], function () { - 'use strict'; - return { - execute: function () { - - - - } - }; -}); diff --git a/test/form/samples/tree-shake-curried-functions/_expected/umd.js b/test/form/samples/tree-shake-curried-functions/_expected/umd.js deleted file mode 100644 index a12a1990f01..00000000000 --- a/test/form/samples/tree-shake-curried-functions/_expected/umd.js +++ /dev/null @@ -1,8 +0,0 @@ -(function (factory) { - typeof define === 'function' && define.amd ? define(factory) : - factory(); -}(function () { 'use strict'; - - - -})); From b9a080c7d72092f543da9356fd74cfee717621ce Mon Sep 17 00:00:00 2001 From: Lukas Taegert-Atkinson Date: Sat, 5 Oct 2019 12:01:32 +0200 Subject: [PATCH 12/35] Add separate context for inclusion --- src/Module.ts | 9 +++-- src/ast/Entity.ts | 6 +-- src/ast/ExecutionContext.ts | 9 +++++ src/ast/nodes/ArrayExpression.ts | 9 ++--- src/ast/nodes/ArrayPattern.ts | 7 ++-- src/ast/nodes/ArrowFunctionExpression.ts | 15 +++---- src/ast/nodes/AssignmentExpression.ts | 8 ++-- src/ast/nodes/AssignmentPattern.ts | 6 +-- src/ast/nodes/AwaitExpression.ts | 8 ++-- src/ast/nodes/BinaryExpression.ts | 8 ++-- src/ast/nodes/BlockStatement.ts | 10 ++--- src/ast/nodes/BreakStatement.ts | 4 +- src/ast/nodes/CallExpression.ts | 29 +++++++------- src/ast/nodes/ClassBody.ts | 6 +-- src/ast/nodes/ConditionalExpression.ts | 36 +++++++++-------- src/ast/nodes/DoWhileStatement.ts | 4 +- src/ast/nodes/ExportDefaultDeclaration.ts | 5 ++- src/ast/nodes/ExportNamedDeclaration.ts | 4 +- src/ast/nodes/ExpressionStatement.ts | 9 ++--- src/ast/nodes/ForInStatement.ts | 14 +++---- src/ast/nodes/ForOfStatement.ts | 11 ++--- src/ast/nodes/ForStatement.ts | 4 +- src/ast/nodes/Identifier.ts | 4 +- src/ast/nodes/IfStatement.ts | 28 ++++++------- src/ast/nodes/ImportExpression.ts | 5 ++- src/ast/nodes/LabeledStatement.ts | 4 +- src/ast/nodes/Literal.ts | 6 +-- src/ast/nodes/LogicalExpression.ts | 30 +++++++------- src/ast/nodes/MemberExpression.ts | 26 ++++++------ src/ast/nodes/MetaProperty.ts | 2 +- src/ast/nodes/MethodDefinition.ts | 8 ++-- src/ast/nodes/NewExpression.ts | 6 +-- src/ast/nodes/ObjectExpression.ts | 17 ++++---- src/ast/nodes/ObjectPattern.ts | 6 +-- src/ast/nodes/Program.ts | 10 ++--- src/ast/nodes/Property.ts | 21 +++++----- src/ast/nodes/RestElement.ts | 7 ++-- src/ast/nodes/ReturnStatement.ts | 4 +- src/ast/nodes/SequenceExpression.ts | 22 +++++----- src/ast/nodes/SpreadElement.ts | 2 +- src/ast/nodes/SwitchCase.ts | 9 +++-- src/ast/nodes/SwitchStatement.ts | 4 +- src/ast/nodes/TaggedTemplateExpression.ts | 6 +-- src/ast/nodes/TemplateLiteral.ts | 3 +- src/ast/nodes/ThisExpression.ts | 8 ++-- src/ast/nodes/TryStatement.ts | 13 +++--- src/ast/nodes/UnaryExpression.ts | 10 ++--- src/ast/nodes/UnknownNode.ts | 6 +-- src/ast/nodes/UpdateExpression.ts | 8 ++-- src/ast/nodes/VariableDeclaration.ts | 15 ++++--- src/ast/nodes/VariableDeclarator.ts | 3 +- src/ast/nodes/WhileStatement.ts | 4 +- src/ast/nodes/YieldExpression.ts | 6 +-- src/ast/nodes/shared/ClassNode.ts | 6 +-- src/ast/nodes/shared/Expression.ts | 12 +++--- src/ast/nodes/shared/FunctionNode.ts | 21 ++++------ src/ast/nodes/shared/MultiExpression.ts | 12 +++--- src/ast/nodes/shared/Node.ts | 49 ++++++++++++++--------- src/ast/nodes/shared/knownGlobals.ts | 2 +- src/ast/scopes/FunctionScope.ts | 3 +- src/ast/scopes/ParameterScope.ts | 5 ++- src/ast/scopes/ReturnValueScope.ts | 3 +- src/ast/utils/PathTracker.ts | 8 +++- src/ast/values.ts | 32 ++++++--------- src/ast/variables/ArgumentsVariable.ts | 3 +- src/ast/variables/GlobalVariable.ts | 2 +- src/ast/variables/LocalVariable.ts | 22 ++++------ src/ast/variables/NamespaceVariable.ts | 4 +- src/ast/variables/ThisVariable.ts | 13 +++--- src/ast/variables/Variable.ts | 8 ++-- 70 files changed, 380 insertions(+), 349 deletions(-) diff --git a/src/Module.ts b/src/Module.ts index c7c94783ff7..7b1e9f0af29 100644 --- a/src/Module.ts +++ b/src/Module.ts @@ -3,6 +3,7 @@ import * as ESTree from 'estree'; import { locate } from 'locate-character'; import MagicString from 'magic-string'; import extractAssignedNames from 'rollup-pluginutils/src/extractAssignedNames'; +import { createExecutionContext } from './ast/ExecutionContext'; import ClassDeclaration from './ast/nodes/ClassDeclaration'; import ExportAllDeclaration from './ast/nodes/ExportAllDeclaration'; import ExportDefaultDeclaration from './ast/nodes/ExportDefaultDeclaration'; @@ -21,8 +22,7 @@ import { Node, NodeBase } from './ast/nodes/shared/Node'; import TemplateLiteral from './ast/nodes/TemplateLiteral'; import VariableDeclaration from './ast/nodes/VariableDeclaration'; import ModuleScope from './ast/scopes/ModuleScope'; -import { PathTracker } from './ast/utils/PathTracker'; -import { UNKNOWN_PATH } from './ast/values'; +import { PathTracker, UNKNOWN_PATH } from './ast/utils/PathTracker'; import ExportShimVariable from './ast/variables/ExportShimVariable'; import ExternalVariable from './ast/variables/ExternalVariable'; import NamespaceVariable from './ast/variables/NamespaceVariable'; @@ -450,7 +450,8 @@ export default class Module { } include(): void { - if (this.ast.shouldBeIncluded()) this.ast.include(false); + if (this.ast.shouldBeIncluded(createExecutionContext())) + this.ast.include(false, createExecutionContext()); } includeAllExports() { @@ -482,7 +483,7 @@ export default class Module { } includeAllInBundle() { - this.ast.include(true); + this.ast.include(true, createExecutionContext()); } isIncluded() { diff --git a/src/ast/Entity.ts b/src/ast/Entity.ts index c0d4d0eb176..6aeeaa222eb 100644 --- a/src/ast/Entity.ts +++ b/src/ast/Entity.ts @@ -1,5 +1,5 @@ -import { ExecutionContext } from './ExecutionContext'; -import { ObjectPath } from './values'; +import { EffectsExecutionContext } from './ExecutionContext'; +import { ObjectPath } from './utils/PathTracker'; export interface Entity { toString: () => string; @@ -13,5 +13,5 @@ export interface WritableEntity extends Entity { * expression of this node is reassigned as well. */ deoptimizePath(path: ObjectPath): void; - hasEffectsWhenAssignedAtPath(path: ObjectPath, execution: ExecutionContext): boolean; + hasEffectsWhenAssignedAtPath(path: ObjectPath, execution: EffectsExecutionContext): boolean; } diff --git a/src/ast/ExecutionContext.ts b/src/ast/ExecutionContext.ts index 7c3e89ea1e4..fea50d7d402 100644 --- a/src/ast/ExecutionContext.ts +++ b/src/ast/ExecutionContext.ts @@ -9,6 +9,11 @@ interface ExecutionContextIgnore { } export interface ExecutionContext { + // TODO Lukas remove + TODO?: boolean; +} + +export interface EffectsExecutionContext extends ExecutionContext { accessed: PathTracker; assigned: PathTracker; called: PathTracker; @@ -18,6 +23,10 @@ export interface ExecutionContext { } export function createExecutionContext(): ExecutionContext { + return {}; +} + +export function createEffectsExecutionContext(): EffectsExecutionContext { return { accessed: new PathTracker(), assigned: new PathTracker(), diff --git a/src/ast/nodes/ArrayExpression.ts b/src/ast/nodes/ArrayExpression.ts index ef7a29f74b7..8b8b40c64c2 100644 --- a/src/ast/nodes/ArrayExpression.ts +++ b/src/ast/nodes/ArrayExpression.ts @@ -1,12 +1,11 @@ import CallOptions from '../CallOptions'; -import { ExecutionContext } from '../ExecutionContext'; +import { EffectsExecutionContext } from '../ExecutionContext'; +import { ObjectPath, UNKNOWN_PATH } from '../utils/PathTracker'; import { arrayMembers, getMemberReturnExpressionWhenCalled, hasMemberEffectWhenCalled, - ObjectPath, - UNKNOWN_EXPRESSION, - UNKNOWN_PATH + UNKNOWN_EXPRESSION } from '../values'; import * as NodeType from './NodeType'; import { ExpressionNode, NodeBase } from './shared/Node'; @@ -35,7 +34,7 @@ export default class ArrayExpression extends NodeBase { hasEffectsWhenCalledAtPath( path: ObjectPath, callOptions: CallOptions, - context: ExecutionContext + context: EffectsExecutionContext ): boolean { if (path.length === 1) { return hasMemberEffectWhenCalled(arrayMembers, path[0], this.included, callOptions, context); diff --git a/src/ast/nodes/ArrayPattern.ts b/src/ast/nodes/ArrayPattern.ts index b27b1827728..7a8d33d2164 100644 --- a/src/ast/nodes/ArrayPattern.ts +++ b/src/ast/nodes/ArrayPattern.ts @@ -1,5 +1,6 @@ -import { ExecutionContext } from '../ExecutionContext'; -import { EMPTY_PATH, ObjectPath, UNKNOWN_EXPRESSION } from '../values'; +import { EffectsExecutionContext } from '../ExecutionContext'; +import { EMPTY_PATH, ObjectPath } from '../utils/PathTracker'; +import { UNKNOWN_EXPRESSION } from '../values'; import Variable from '../variables/Variable'; import * as NodeType from './NodeType'; import { ExpressionEntity } from './shared/Expression'; @@ -38,7 +39,7 @@ export default class ArrayPattern extends NodeBase implements PatternNode { } } - hasEffectsWhenAssignedAtPath(path: ObjectPath, context: ExecutionContext) { + hasEffectsWhenAssignedAtPath(path: ObjectPath, context: EffectsExecutionContext) { if (path.length > 0) return true; for (const element of this.elements) { if (element !== null && element.hasEffectsWhenAssignedAtPath(EMPTY_PATH, context)) diff --git a/src/ast/nodes/ArrowFunctionExpression.ts b/src/ast/nodes/ArrowFunctionExpression.ts index 5024c56d703..325fcbdc843 100644 --- a/src/ast/nodes/ArrowFunctionExpression.ts +++ b/src/ast/nodes/ArrowFunctionExpression.ts @@ -1,13 +1,14 @@ import CallOptions from '../CallOptions'; -import { ExecutionContext } from '../ExecutionContext'; +import { EffectsExecutionContext, ExecutionContext } from '../ExecutionContext'; import ReturnValueScope from '../scopes/ReturnValueScope'; import Scope from '../scopes/Scope'; -import { ObjectPath, UNKNOWN_EXPRESSION, UNKNOWN_PATH, UnknownKey } from '../values'; +import { ObjectPath, UNKNOWN_PATH, UnknownKey } from '../utils/PathTracker'; +import { UNKNOWN_EXPRESSION } from '../values'; import BlockStatement from './BlockStatement'; import Identifier from './Identifier'; import * as NodeType from './NodeType'; import RestElement from './RestElement'; -import { ExpressionNode, GenericEsTreeNode, NodeBase } from './shared/Node'; +import { ExpressionNode, GenericEsTreeNode, IncludeChildren, NodeBase } from './shared/Node'; import { PatternNode } from './shared/Pattern'; import SpreadElement from './SpreadElement'; @@ -49,7 +50,7 @@ export default class ArrowFunctionExpression extends NodeBase { hasEffectsWhenCalledAtPath( path: ObjectPath, _callOptions: CallOptions, - context: ExecutionContext + context: EffectsExecutionContext ): boolean { if (path.length > 0) return true; for (const param of this.params) { @@ -66,12 +67,12 @@ export default class ArrowFunctionExpression extends NodeBase { return false; } - include(includeChildrenRecursively: boolean | 'variables') { + include(includeChildrenRecursively: IncludeChildren, context: ExecutionContext) { this.included = true; - this.body.include(includeChildrenRecursively); + this.body.include(includeChildrenRecursively, context); for (const param of this.params) { if (!(param instanceof Identifier)) { - param.include(includeChildrenRecursively); + param.include(includeChildrenRecursively, context); } } } diff --git a/src/ast/nodes/AssignmentExpression.ts b/src/ast/nodes/AssignmentExpression.ts index 64329f691f8..70eb9345628 100644 --- a/src/ast/nodes/AssignmentExpression.ts +++ b/src/ast/nodes/AssignmentExpression.ts @@ -1,8 +1,8 @@ import MagicString from 'magic-string'; import { RenderOptions } from '../../utils/renderHelpers'; import { getSystemExportStatement } from '../../utils/systemJsRendering'; -import { ExecutionContext } from '../ExecutionContext'; -import { EMPTY_PATH, ObjectPath, UNKNOWN_PATH } from '../values'; +import { EffectsExecutionContext } from '../ExecutionContext'; +import { EMPTY_PATH, ObjectPath, UNKNOWN_PATH } from '../utils/PathTracker'; import Variable from '../variables/Variable'; import * as NodeType from './NodeType'; import { ExpressionNode, NodeBase } from './shared/Node'; @@ -34,7 +34,7 @@ export default class AssignmentExpression extends NodeBase { this.right.deoptimizePath(UNKNOWN_PATH); } - hasEffects(context: ExecutionContext): boolean { + hasEffects(context: EffectsExecutionContext): boolean { return ( this.right.hasEffects(context) || this.left.hasEffects(context) || @@ -42,7 +42,7 @@ export default class AssignmentExpression extends NodeBase { ); } - hasEffectsWhenAccessedAtPath(path: ObjectPath, context: ExecutionContext): boolean { + hasEffectsWhenAccessedAtPath(path: ObjectPath, context: EffectsExecutionContext): boolean { return path.length > 0 && this.right.hasEffectsWhenAccessedAtPath(path, context); } diff --git a/src/ast/nodes/AssignmentPattern.ts b/src/ast/nodes/AssignmentPattern.ts index 9808e8c3187..95eada265ea 100644 --- a/src/ast/nodes/AssignmentPattern.ts +++ b/src/ast/nodes/AssignmentPattern.ts @@ -1,8 +1,8 @@ import MagicString from 'magic-string'; import { BLANK } from '../../utils/blank'; import { NodeRenderOptions, RenderOptions } from '../../utils/renderHelpers'; -import { ExecutionContext } from '../ExecutionContext'; -import { EMPTY_PATH, ObjectPath, UNKNOWN_PATH } from '../values'; +import { EffectsExecutionContext } from '../ExecutionContext'; +import { EMPTY_PATH, ObjectPath, UNKNOWN_PATH } from '../utils/PathTracker'; import Variable from '../variables/Variable'; import * as NodeType from './NodeType'; import { ExpressionEntity } from './shared/Expression'; @@ -32,7 +32,7 @@ export default class AssignmentPattern extends NodeBase implements PatternNode { path.length === 0 && this.left.deoptimizePath(path); } - hasEffectsWhenAssignedAtPath(path: ObjectPath, context: ExecutionContext): boolean { + hasEffectsWhenAssignedAtPath(path: ObjectPath, context: EffectsExecutionContext): boolean { return path.length > 0 || this.left.hasEffectsWhenAssignedAtPath(EMPTY_PATH, context); } diff --git a/src/ast/nodes/AwaitExpression.ts b/src/ast/nodes/AwaitExpression.ts index 88ae3de393d..6bb887dcd4a 100644 --- a/src/ast/nodes/AwaitExpression.ts +++ b/src/ast/nodes/AwaitExpression.ts @@ -1,6 +1,6 @@ import MagicString from 'magic-string'; import { RenderOptions } from '../../utils/renderHelpers'; -import { ExecutionContext } from '../ExecutionContext'; +import { EffectsExecutionContext, ExecutionContext } from '../ExecutionContext'; import ArrowFunctionExpression from './ArrowFunctionExpression'; import * as NodeType from './NodeType'; import FunctionNode from './shared/FunctionNode'; @@ -10,11 +10,11 @@ export default class AwaitExpression extends NodeBase { argument!: ExpressionNode; type!: NodeType.tAwaitExpression; - hasEffects(context: ExecutionContext) { + hasEffects(context: EffectsExecutionContext) { return super.hasEffects(context) || !context.ignore.returnAwaitYield; } - include(includeChildrenRecursively: IncludeChildren) { + include(includeChildrenRecursively: IncludeChildren, context: ExecutionContext) { checkTopLevelAwait: if (!this.included && !this.context.usesTopLevelAwait) { let parent = this.parent; do { @@ -23,7 +23,7 @@ export default class AwaitExpression extends NodeBase { } while ((parent = (parent as Node).parent as Node)); this.context.usesTopLevelAwait = true; } - super.include(includeChildrenRecursively); + super.include(includeChildrenRecursively, context); } render(code: MagicString, options: RenderOptions) { diff --git a/src/ast/nodes/BinaryExpression.ts b/src/ast/nodes/BinaryExpression.ts index 7b6fe45ccb5..a71be3881a7 100644 --- a/src/ast/nodes/BinaryExpression.ts +++ b/src/ast/nodes/BinaryExpression.ts @@ -1,7 +1,7 @@ import { DeoptimizableEntity } from '../DeoptimizableEntity'; -import { ExecutionContext } from '../ExecutionContext'; -import { EMPTY_IMMUTABLE_TRACKER, PathTracker } from '../utils/PathTracker'; -import { EMPTY_PATH, LiteralValueOrUnknown, ObjectPath, UnknownValue } from '../values'; +import { EffectsExecutionContext, ExecutionContext } from '../ExecutionContext'; +import { EMPTY_IMMUTABLE_TRACKER, EMPTY_PATH, ObjectPath, PathTracker } from '../utils/PathTracker'; +import { LiteralValueOrUnknown, UnknownValue } from '../values'; import ExpressionStatement from './ExpressionStatement'; import { LiteralValue } from './Literal'; import * as NodeType from './NodeType'; @@ -63,7 +63,7 @@ export default class BinaryExpression extends NodeBase implements DeoptimizableE return operatorFn(leftValue, rightValue); } - hasEffects(context: ExecutionContext): boolean { + hasEffects(context: EffectsExecutionContext): boolean { // support some implicit type coercion runtime errors if ( this.operator === '+' && diff --git a/src/ast/nodes/BlockStatement.ts b/src/ast/nodes/BlockStatement.ts index c34808ff484..f13ba8dff60 100644 --- a/src/ast/nodes/BlockStatement.ts +++ b/src/ast/nodes/BlockStatement.ts @@ -1,6 +1,6 @@ import MagicString from 'magic-string'; import { RenderOptions, renderStatementList } from '../../utils/renderHelpers'; -import { ExecutionContext } from '../ExecutionContext'; +import { EffectsExecutionContext, ExecutionContext } from '../ExecutionContext'; import BlockScope from '../scopes/BlockScope'; import ChildScope from '../scopes/ChildScope'; import Scope from '../scopes/Scope'; @@ -25,18 +25,18 @@ export default class BlockStatement extends StatementBase { : new BlockScope(parentScope); } - hasEffects(context: ExecutionContext) { + hasEffects(context: EffectsExecutionContext) { for (const node of this.body) { if (node.hasEffects(context)) return true; } return false; } - include(includeChildrenRecursively: IncludeChildren) { + include(includeChildrenRecursively: IncludeChildren, context: ExecutionContext) { this.included = true; for (const node of this.body) { - if (includeChildrenRecursively || node.shouldBeIncluded()) - node.include(includeChildrenRecursively); + if (includeChildrenRecursively || node.shouldBeIncluded(context)) + node.include(includeChildrenRecursively, context); } } diff --git a/src/ast/nodes/BreakStatement.ts b/src/ast/nodes/BreakStatement.ts index dff69c9699d..3fa85546cb2 100644 --- a/src/ast/nodes/BreakStatement.ts +++ b/src/ast/nodes/BreakStatement.ts @@ -1,4 +1,4 @@ -import { ExecutionContext } from '../ExecutionContext'; +import { EffectsExecutionContext } from '../ExecutionContext'; import Identifier from './Identifier'; import * as NodeType from './NodeType'; import { StatementBase } from './shared/Node'; @@ -7,7 +7,7 @@ export default class BreakStatement extends StatementBase { label!: Identifier | null; type!: NodeType.tBreakStatement; - hasEffects(context: ExecutionContext) { + hasEffects(context: EffectsExecutionContext) { return ( super.hasEffects(context) || !context.ignore.breakStatements || diff --git a/src/ast/nodes/CallExpression.ts b/src/ast/nodes/CallExpression.ts index 81c1456a2c3..10670f09597 100644 --- a/src/ast/nodes/CallExpression.ts +++ b/src/ast/nodes/CallExpression.ts @@ -7,16 +7,15 @@ import { } from '../../utils/renderHelpers'; import CallOptions from '../CallOptions'; import { DeoptimizableEntity } from '../DeoptimizableEntity'; -import { ExecutionContext } from '../ExecutionContext'; -import { EMPTY_IMMUTABLE_TRACKER, PathTracker } from '../utils/PathTracker'; +import { EffectsExecutionContext, ExecutionContext } from '../ExecutionContext'; import { + EMPTY_IMMUTABLE_TRACKER, EMPTY_PATH, - LiteralValueOrUnknown, ObjectPath, - UNKNOWN_EXPRESSION, - UNKNOWN_PATH, - UnknownValue -} from '../values'; + PathTracker, + UNKNOWN_PATH +} from '../utils/PathTracker'; +import { LiteralValueOrUnknown, UNKNOWN_EXPRESSION, UnknownValue } from '../values'; import Identifier from './Identifier'; import * as NodeType from './NodeType'; import { ExpressionEntity } from './shared/Expression'; @@ -153,7 +152,7 @@ export default class CallExpression extends NodeBase implements DeoptimizableEnt return value; } - hasEffects(context: ExecutionContext): boolean { + hasEffects(context: EffectsExecutionContext): boolean { for (const argument of this.arguments) { if (argument.hasEffects(context)) return true; } @@ -164,7 +163,7 @@ export default class CallExpression extends NodeBase implements DeoptimizableEnt ); } - hasEffectsWhenAccessedAtPath(path: ObjectPath, context: ExecutionContext): boolean { + hasEffectsWhenAccessedAtPath(path: ObjectPath, context: EffectsExecutionContext): boolean { if (path.length === 0) return false; const trackedExpressions = context.accessed.getEntities(path); if (trackedExpressions.has(this)) return false; @@ -172,7 +171,7 @@ export default class CallExpression extends NodeBase implements DeoptimizableEnt return (this.returnExpression as ExpressionEntity).hasEffectsWhenAccessedAtPath(path, context); } - hasEffectsWhenAssignedAtPath(path: ObjectPath, context: ExecutionContext): boolean { + hasEffectsWhenAssignedAtPath(path: ObjectPath, context: EffectsExecutionContext): boolean { if (path.length === 0) return true; const trackedExpressions = context.assigned.getEntities(path); if (trackedExpressions.has(this)) return false; @@ -183,7 +182,7 @@ export default class CallExpression extends NodeBase implements DeoptimizableEnt hasEffectsWhenCalledAtPath( path: ObjectPath, callOptions: CallOptions, - context: ExecutionContext + context: EffectsExecutionContext ): boolean { const trackedExpressions = (callOptions.withNew ? context.instantiated @@ -198,9 +197,9 @@ export default class CallExpression extends NodeBase implements DeoptimizableEnt ); } - include(includeChildrenRecursively: IncludeChildren) { + include(includeChildrenRecursively: IncludeChildren, context: ExecutionContext) { if (includeChildrenRecursively) { - super.include(includeChildrenRecursively); + super.include(includeChildrenRecursively, context); if ( includeChildrenRecursively === INCLUDE_PARAMETERS && this.callee instanceof Identifier && @@ -210,11 +209,11 @@ export default class CallExpression extends NodeBase implements DeoptimizableEnt } } else { this.included = true; - this.callee.include(false); + this.callee.include(false, context); } this.callee.includeCallArguments(this.arguments); if (!(this.returnExpression as ExpressionEntity).included) { - (this.returnExpression as ExpressionEntity).include(false); + (this.returnExpression as ExpressionEntity).include(false, context); } } diff --git a/src/ast/nodes/ClassBody.ts b/src/ast/nodes/ClassBody.ts index 0a892fd79f6..419da45f1db 100644 --- a/src/ast/nodes/ClassBody.ts +++ b/src/ast/nodes/ClassBody.ts @@ -1,6 +1,6 @@ import CallOptions from '../CallOptions'; -import { ExecutionContext } from '../ExecutionContext'; -import { EMPTY_PATH, ObjectPath } from '../values'; +import { EffectsExecutionContext } from '../ExecutionContext'; +import { EMPTY_PATH, ObjectPath } from '../utils/PathTracker'; import MethodDefinition from './MethodDefinition'; import * as NodeType from './NodeType'; import { NodeBase } from './shared/Node'; @@ -14,7 +14,7 @@ export default class ClassBody extends NodeBase { hasEffectsWhenCalledAtPath( path: ObjectPath, callOptions: CallOptions, - context: ExecutionContext + context: EffectsExecutionContext ) { if (path.length > 0) return true; return ( diff --git a/src/ast/nodes/ConditionalExpression.ts b/src/ast/nodes/ConditionalExpression.ts index e0ebfb5f0ec..14ef0d4fb71 100644 --- a/src/ast/nodes/ConditionalExpression.ts +++ b/src/ast/nodes/ConditionalExpression.ts @@ -9,15 +9,15 @@ import { import { removeAnnotations } from '../../utils/treeshakeNode'; import CallOptions from '../CallOptions'; import { DeoptimizableEntity } from '../DeoptimizableEntity'; -import { ExecutionContext } from '../ExecutionContext'; -import { EMPTY_IMMUTABLE_TRACKER, PathTracker } from '../utils/PathTracker'; +import { EffectsExecutionContext, ExecutionContext } from '../ExecutionContext'; import { + EMPTY_IMMUTABLE_TRACKER, EMPTY_PATH, - LiteralValueOrUnknown, ObjectPath, - UNKNOWN_PATH, - UnknownValue -} from '../values'; + PathTracker, + UNKNOWN_PATH +} from '../utils/PathTracker'; +import { LiteralValueOrUnknown, UnknownValue } from '../values'; import CallExpression from './CallExpression'; import * as NodeType from './NodeType'; import { ExpressionEntity } from './shared/Expression'; @@ -91,7 +91,7 @@ export default class ConditionalExpression extends NodeBase implements Deoptimiz return this.usedBranch.getReturnExpressionWhenCalledAtPath(path, recursionTracker, origin); } - hasEffects(context: ExecutionContext): boolean { + hasEffects(context: EffectsExecutionContext): boolean { if (this.test.hasEffects(context)) return true; if (this.usedBranch === null) { return this.consequent.hasEffects(context) || this.alternate.hasEffects(context); @@ -99,7 +99,7 @@ export default class ConditionalExpression extends NodeBase implements Deoptimiz return this.usedBranch.hasEffects(context); } - hasEffectsWhenAccessedAtPath(path: ObjectPath, context: ExecutionContext): boolean { + hasEffectsWhenAccessedAtPath(path: ObjectPath, context: EffectsExecutionContext): boolean { if (path.length === 0) return false; if (this.usedBranch === null) { return ( @@ -110,7 +110,7 @@ export default class ConditionalExpression extends NodeBase implements Deoptimiz return this.usedBranch.hasEffectsWhenAccessedAtPath(path, context); } - hasEffectsWhenAssignedAtPath(path: ObjectPath, context: ExecutionContext): boolean { + hasEffectsWhenAssignedAtPath(path: ObjectPath, context: EffectsExecutionContext): boolean { if (path.length === 0) return true; if (this.usedBranch === null) { return ( @@ -124,7 +124,7 @@ export default class ConditionalExpression extends NodeBase implements Deoptimiz hasEffectsWhenCalledAtPath( path: ObjectPath, callOptions: CallOptions, - context: ExecutionContext + context: EffectsExecutionContext ): boolean { if (this.usedBranch === null) { return ( @@ -135,14 +135,18 @@ export default class ConditionalExpression extends NodeBase implements Deoptimiz return this.usedBranch.hasEffectsWhenCalledAtPath(path, callOptions, context); } - include(includeChildrenRecursively: IncludeChildren) { + include(includeChildrenRecursively: IncludeChildren, context: ExecutionContext) { this.included = true; - if (includeChildrenRecursively || this.usedBranch === null || this.test.shouldBeIncluded()) { - this.test.include(includeChildrenRecursively); - this.consequent.include(includeChildrenRecursively); - this.alternate.include(includeChildrenRecursively); + if ( + includeChildrenRecursively || + this.usedBranch === null || + this.test.shouldBeIncluded(context) + ) { + this.test.include(includeChildrenRecursively, context); + this.consequent.include(includeChildrenRecursively, context); + this.alternate.include(includeChildrenRecursively, context); } else { - this.usedBranch.include(includeChildrenRecursively); + this.usedBranch.include(includeChildrenRecursively, context); } } diff --git a/src/ast/nodes/DoWhileStatement.ts b/src/ast/nodes/DoWhileStatement.ts index 26e748073d1..e34c423fae0 100644 --- a/src/ast/nodes/DoWhileStatement.ts +++ b/src/ast/nodes/DoWhileStatement.ts @@ -1,4 +1,4 @@ -import { ExecutionContext } from '../ExecutionContext'; +import { EffectsExecutionContext } from '../ExecutionContext'; import * as NodeType from './NodeType'; import { ExpressionNode, StatementBase, StatementNode } from './shared/Node'; @@ -7,7 +7,7 @@ export default class DoWhileStatement extends StatementBase { test!: ExpressionNode; type!: NodeType.tDoWhileStatement; - hasEffects(context: ExecutionContext): boolean { + hasEffects(context: EffectsExecutionContext): boolean { if (this.test.hasEffects(context)) return true; const { ignore: { breakStatements } diff --git a/src/ast/nodes/ExportDefaultDeclaration.ts b/src/ast/nodes/ExportDefaultDeclaration.ts index 32dcac1a977..44513fd4f4d 100644 --- a/src/ast/nodes/ExportDefaultDeclaration.ts +++ b/src/ast/nodes/ExportDefaultDeclaration.ts @@ -6,6 +6,7 @@ import { RenderOptions } from '../../utils/renderHelpers'; import { treeshakeNode } from '../../utils/treeshakeNode'; +import { ExecutionContext } from '../ExecutionContext'; import ModuleScope from '../scopes/ModuleScope'; import ExportDefaultVariable from '../variables/ExportDefaultVariable'; import ClassDeclaration from './ClassDeclaration'; @@ -43,8 +44,8 @@ export default class ExportDefaultDeclaration extends NodeBase { private declarationName: string | undefined; - include(includeChildrenRecursively: IncludeChildren) { - super.include(includeChildrenRecursively); + include(includeChildrenRecursively: IncludeChildren, context: ExecutionContext) { + super.include(includeChildrenRecursively, context); if (includeChildrenRecursively) { this.context.includeVariable(this.variable); } diff --git a/src/ast/nodes/ExportNamedDeclaration.ts b/src/ast/nodes/ExportNamedDeclaration.ts index c0bf829f970..bae519bee7c 100644 --- a/src/ast/nodes/ExportNamedDeclaration.ts +++ b/src/ast/nodes/ExportNamedDeclaration.ts @@ -1,7 +1,7 @@ import MagicString from 'magic-string'; import { BLANK } from '../../utils/blank'; import { NodeRenderOptions, RenderOptions } from '../../utils/renderHelpers'; -import { ExecutionContext } from '../ExecutionContext'; +import { EffectsExecutionContext } from '../ExecutionContext'; import ClassDeclaration from './ClassDeclaration'; import ExportSpecifier from './ExportSpecifier'; import FunctionDeclaration from './FunctionDeclaration'; @@ -22,7 +22,7 @@ export default class ExportNamedDeclaration extends NodeBase { if (this.declaration !== null) this.declaration.bind(); } - hasEffects(context: ExecutionContext) { + hasEffects(context: EffectsExecutionContext) { return this.declaration !== null && this.declaration.hasEffects(context); } diff --git a/src/ast/nodes/ExpressionStatement.ts b/src/ast/nodes/ExpressionStatement.ts index de4aa1d8a7d..3e5692472f6 100644 --- a/src/ast/nodes/ExpressionStatement.ts +++ b/src/ast/nodes/ExpressionStatement.ts @@ -1,5 +1,6 @@ import MagicString from 'magic-string'; import { RenderOptions } from '../../utils/renderHelpers'; +import { ExecutionContext } from '../ExecutionContext'; import * as NodeType from './NodeType'; import { ExpressionNode, StatementBase } from './shared/Node'; @@ -17,9 +18,7 @@ export default class ExpressionStatement extends StatementBase { // This is necessary, because either way (deleting or not) can lead to errors. { code: 'MODULE_LEVEL_DIRECTIVE', - message: `Module level directives cause errors when bundled, '${ - this.directive - }' was ignored.` + message: `Module level directives cause errors when bundled, '${this.directive}' was ignored.` }, this.start ); @@ -31,10 +30,10 @@ export default class ExpressionStatement extends StatementBase { if (this.included) this.insertSemicolon(code); } - shouldBeIncluded() { + shouldBeIncluded(context: ExecutionContext) { if (this.directive && this.directive !== 'use strict') return this.parent.type !== NodeType.Program; - return super.shouldBeIncluded(); + return super.shouldBeIncluded(context); } } diff --git a/src/ast/nodes/ForInStatement.ts b/src/ast/nodes/ForInStatement.ts index c5368fff01c..dab9dc85f43 100644 --- a/src/ast/nodes/ForInStatement.ts +++ b/src/ast/nodes/ForInStatement.ts @@ -1,9 +1,9 @@ import MagicString from 'magic-string'; import { NO_SEMICOLON, RenderOptions } from '../../utils/renderHelpers'; -import { ExecutionContext } from '../ExecutionContext'; +import { EffectsExecutionContext, ExecutionContext } from '../ExecutionContext'; import BlockScope from '../scopes/BlockScope'; import Scope from '../scopes/Scope'; -import { EMPTY_PATH } from '../values'; +import { EMPTY_PATH } from '../utils/PathTracker'; import * as NodeType from './NodeType'; import { ExpressionNode, IncludeChildren, StatementBase, StatementNode } from './shared/Node'; import { PatternNode } from './shared/Pattern'; @@ -26,7 +26,7 @@ export default class ForInStatement extends StatementBase { this.scope = new BlockScope(parentScope); } - hasEffects(context: ExecutionContext): boolean { + hasEffects(context: EffectsExecutionContext): boolean { if ( (this.left && (this.left.hasEffects(context) || @@ -43,12 +43,12 @@ export default class ForInStatement extends StatementBase { return false; } - include(includeChildrenRecursively: IncludeChildren) { + include(includeChildrenRecursively: IncludeChildren, context: ExecutionContext) { this.included = true; - this.left.includeWithAllDeclaredVariables(includeChildrenRecursively); + this.left.includeWithAllDeclaredVariables(includeChildrenRecursively, context); this.left.deoptimizePath(EMPTY_PATH); - this.right.include(includeChildrenRecursively); - this.body.include(includeChildrenRecursively); + this.right.include(includeChildrenRecursively, context); + this.body.include(includeChildrenRecursively, context); } render(code: MagicString, options: RenderOptions) { diff --git a/src/ast/nodes/ForOfStatement.ts b/src/ast/nodes/ForOfStatement.ts index 23874656dc7..9ad7b4ea08b 100644 --- a/src/ast/nodes/ForOfStatement.ts +++ b/src/ast/nodes/ForOfStatement.ts @@ -1,8 +1,9 @@ import MagicString from 'magic-string'; import { NO_SEMICOLON, RenderOptions } from '../../utils/renderHelpers'; +import { ExecutionContext } from '../ExecutionContext'; import BlockScope from '../scopes/BlockScope'; import Scope from '../scopes/Scope'; -import { EMPTY_PATH } from '../values'; +import { EMPTY_PATH } from '../utils/PathTracker'; import * as NodeType from './NodeType'; import { ExpressionNode, IncludeChildren, StatementBase, StatementNode } from './shared/Node'; import { PatternNode } from './shared/Pattern'; @@ -31,12 +32,12 @@ export default class ForOfStatement extends StatementBase { return true; } - include(includeChildrenRecursively: IncludeChildren) { + include(includeChildrenRecursively: IncludeChildren, context: ExecutionContext) { this.included = true; - this.left.includeWithAllDeclaredVariables(includeChildrenRecursively); + this.left.includeWithAllDeclaredVariables(includeChildrenRecursively, context); this.left.deoptimizePath(EMPTY_PATH); - this.right.include(includeChildrenRecursively); - this.body.include(includeChildrenRecursively); + this.right.include(includeChildrenRecursively, context); + this.body.include(includeChildrenRecursively, context); } render(code: MagicString, options: RenderOptions) { diff --git a/src/ast/nodes/ForStatement.ts b/src/ast/nodes/ForStatement.ts index 11d3c7404d6..7b4687f1939 100644 --- a/src/ast/nodes/ForStatement.ts +++ b/src/ast/nodes/ForStatement.ts @@ -1,6 +1,6 @@ import MagicString from 'magic-string'; import { NO_SEMICOLON, RenderOptions } from '../../utils/renderHelpers'; -import { ExecutionContext } from '../ExecutionContext'; +import { EffectsExecutionContext } from '../ExecutionContext'; import BlockScope from '../scopes/BlockScope'; import Scope from '../scopes/Scope'; import * as NodeType from './NodeType'; @@ -18,7 +18,7 @@ export default class ForStatement extends StatementBase { this.scope = new BlockScope(parentScope); } - hasEffects(context: ExecutionContext): boolean { + hasEffects(context: EffectsExecutionContext): boolean { if ( (this.init && this.init.hasEffects(context)) || (this.test && this.test.hasEffects(context)) || diff --git a/src/ast/nodes/Identifier.ts b/src/ast/nodes/Identifier.ts index b9b51418d53..c01d45a829a 100644 --- a/src/ast/nodes/Identifier.ts +++ b/src/ast/nodes/Identifier.ts @@ -6,8 +6,8 @@ import CallOptions from '../CallOptions'; import { DeoptimizableEntity } from '../DeoptimizableEntity'; import { ExecutionContext } from '../ExecutionContext'; import FunctionScope from '../scopes/FunctionScope'; -import { PathTracker } from '../utils/PathTracker'; -import { EMPTY_PATH, LiteralValueOrUnknown, ObjectPath } from '../values'; +import { EMPTY_PATH, ObjectPath, PathTracker } from '../utils/PathTracker'; +import { LiteralValueOrUnknown } from '../values'; import GlobalVariable from '../variables/GlobalVariable'; import LocalVariable from '../variables/LocalVariable'; import Variable from '../variables/Variable'; diff --git a/src/ast/nodes/IfStatement.ts b/src/ast/nodes/IfStatement.ts index 5d73bf233cc..3e2d720605a 100644 --- a/src/ast/nodes/IfStatement.ts +++ b/src/ast/nodes/IfStatement.ts @@ -2,9 +2,9 @@ import MagicString from 'magic-string'; import { RenderOptions } from '../../utils/renderHelpers'; import { removeAnnotations } from '../../utils/treeshakeNode'; import { DeoptimizableEntity } from '../DeoptimizableEntity'; -import { ExecutionContext } from '../ExecutionContext'; -import { EMPTY_IMMUTABLE_TRACKER } from '../utils/PathTracker'; -import { EMPTY_PATH, LiteralValueOrUnknown, UnknownValue } from '../values'; +import { EffectsExecutionContext, ExecutionContext } from '../ExecutionContext'; +import { EMPTY_IMMUTABLE_TRACKER, EMPTY_PATH } from '../utils/PathTracker'; +import { LiteralValueOrUnknown, UnknownValue } from '../values'; import * as NodeType from './NodeType'; import { ExpressionNode, IncludeChildren, StatementBase, StatementNode } from './shared/Node'; @@ -30,7 +30,7 @@ export default class IfStatement extends StatementBase implements DeoptimizableE this.testValue = UnknownValue; } - hasEffects(context: ExecutionContext): boolean { + hasEffects(context: EffectsExecutionContext): boolean { if (this.test.hasEffects(context)) return true; if (this.testValue === UnknownValue) { return ( @@ -43,28 +43,28 @@ export default class IfStatement extends StatementBase implements DeoptimizableE : this.alternate !== null && this.alternate.hasEffects(context); } - include(includeChildrenRecursively: IncludeChildren) { + include(includeChildrenRecursively: IncludeChildren, context: ExecutionContext) { this.included = true; if (includeChildrenRecursively) { - this.test.include(includeChildrenRecursively); - this.consequent.include(includeChildrenRecursively); + this.test.include(includeChildrenRecursively, context); + this.consequent.include(includeChildrenRecursively, context); if (this.alternate !== null) { - this.alternate.include(includeChildrenRecursively); + this.alternate.include(includeChildrenRecursively, context); } return; } const hasUnknownTest = this.testValue === UnknownValue; - if (hasUnknownTest || this.test.shouldBeIncluded()) { - this.test.include(false); + if (hasUnknownTest || this.test.shouldBeIncluded(context)) { + this.test.include(false, context); } - if ((hasUnknownTest || this.testValue) && this.consequent.shouldBeIncluded()) { - this.consequent.include(false); + if ((hasUnknownTest || this.testValue) && this.consequent.shouldBeIncluded(context)) { + this.consequent.include(false, context); } if ( this.alternate !== null && - ((hasUnknownTest || !this.testValue) && this.alternate.shouldBeIncluded()) + ((hasUnknownTest || !this.testValue) && this.alternate.shouldBeIncluded(context)) ) { - this.alternate.include(false); + this.alternate.include(false, context); } } diff --git a/src/ast/nodes/ImportExpression.ts b/src/ast/nodes/ImportExpression.ts index 55188c2fd89..927f8abb68c 100644 --- a/src/ast/nodes/ImportExpression.ts +++ b/src/ast/nodes/ImportExpression.ts @@ -1,6 +1,7 @@ import MagicString from 'magic-string'; import { findFirstOccurrenceOutsideComment, RenderOptions } from '../../utils/renderHelpers'; import { INTEROP_NAMESPACE_VARIABLE } from '../../utils/variableNames'; +import { ExecutionContext } from '../ExecutionContext'; import NamespaceVariable from '../variables/NamespaceVariable'; import * as NodeType from './NodeType'; import { ExpressionNode, IncludeChildren, NodeBase } from './shared/Node'; @@ -21,12 +22,12 @@ export default class Import extends NodeBase { return true; } - include(includeChildrenRecursively: IncludeChildren) { + include(includeChildrenRecursively: IncludeChildren, context: ExecutionContext) { if (!this.included) { this.included = true; this.context.includeDynamicImport(this); } - this.source.include(includeChildrenRecursively); + this.source.include(includeChildrenRecursively, context); } initialise() { diff --git a/src/ast/nodes/LabeledStatement.ts b/src/ast/nodes/LabeledStatement.ts index be1d68415b9..a17a33ef40f 100644 --- a/src/ast/nodes/LabeledStatement.ts +++ b/src/ast/nodes/LabeledStatement.ts @@ -1,4 +1,4 @@ -import { ExecutionContext } from '../ExecutionContext'; +import { EffectsExecutionContext } from '../ExecutionContext'; import Identifier from './Identifier'; import * as NodeType from './NodeType'; import { StatementBase, StatementNode } from './shared/Node'; @@ -8,7 +8,7 @@ export default class LabeledStatement extends StatementBase { label!: Identifier; type!: NodeType.tLabeledStatement; - hasEffects(context: ExecutionContext) { + hasEffects(context: EffectsExecutionContext) { const { ignore: { breakStatements } } = context; diff --git a/src/ast/nodes/Literal.ts b/src/ast/nodes/Literal.ts index b23e40f09f0..7e3840eac58 100644 --- a/src/ast/nodes/Literal.ts +++ b/src/ast/nodes/Literal.ts @@ -1,14 +1,14 @@ import MagicString from 'magic-string'; import { RenderOptions } from '../../utils/renderHelpers'; import CallOptions from '../CallOptions'; -import { ExecutionContext } from '../ExecutionContext'; +import { EffectsExecutionContext } from '../ExecutionContext'; +import { ObjectPath } from '../utils/PathTracker'; import { getLiteralMembersForValue, getMemberReturnExpressionWhenCalled, hasMemberEffectWhenCalled, LiteralValueOrUnknown, MemberDescription, - ObjectPath, UNKNOWN_EXPRESSION, UnknownValue } from '../values'; @@ -56,7 +56,7 @@ export default class Literal extends NodeBase { hasEffectsWhenCalledAtPath( path: ObjectPath, callOptions: CallOptions, - context: ExecutionContext + context: EffectsExecutionContext ): boolean { if (path.length === 1) { return hasMemberEffectWhenCalled(this.members, path[0], this.included, callOptions, context); diff --git a/src/ast/nodes/LogicalExpression.ts b/src/ast/nodes/LogicalExpression.ts index ad1c16a7ccf..3f35850406b 100644 --- a/src/ast/nodes/LogicalExpression.ts +++ b/src/ast/nodes/LogicalExpression.ts @@ -9,15 +9,15 @@ import { import { removeAnnotations } from '../../utils/treeshakeNode'; import CallOptions from '../CallOptions'; import { DeoptimizableEntity } from '../DeoptimizableEntity'; -import { ExecutionContext } from '../ExecutionContext'; -import { EMPTY_IMMUTABLE_TRACKER, PathTracker } from '../utils/PathTracker'; +import { EffectsExecutionContext, ExecutionContext } from '../ExecutionContext'; import { + EMPTY_IMMUTABLE_TRACKER, EMPTY_PATH, - LiteralValueOrUnknown, ObjectPath, - UNKNOWN_PATH, - UnknownValue -} from '../values'; + PathTracker, + UNKNOWN_PATH +} from '../utils/PathTracker'; +import { LiteralValueOrUnknown, UnknownValue } from '../values'; import CallExpression from './CallExpression'; import * as NodeType from './NodeType'; import { ExpressionEntity } from './shared/Expression'; @@ -93,14 +93,14 @@ export default class LogicalExpression extends NodeBase implements Deoptimizable return this.usedBranch.getReturnExpressionWhenCalledAtPath(path, recursionTracker, origin); } - hasEffects(context: ExecutionContext): boolean { + hasEffects(context: EffectsExecutionContext): boolean { if (this.usedBranch === null) { return this.left.hasEffects(context) || this.right.hasEffects(context); } return this.usedBranch.hasEffects(context); } - hasEffectsWhenAccessedAtPath(path: ObjectPath, context: ExecutionContext): boolean { + hasEffectsWhenAccessedAtPath(path: ObjectPath, context: EffectsExecutionContext): boolean { if (path.length === 0) return false; if (this.usedBranch === null) { return ( @@ -111,7 +111,7 @@ export default class LogicalExpression extends NodeBase implements Deoptimizable return this.usedBranch.hasEffectsWhenAccessedAtPath(path, context); } - hasEffectsWhenAssignedAtPath(path: ObjectPath, context: ExecutionContext): boolean { + hasEffectsWhenAssignedAtPath(path: ObjectPath, context: EffectsExecutionContext): boolean { if (path.length === 0) return true; if (this.usedBranch === null) { return ( @@ -125,7 +125,7 @@ export default class LogicalExpression extends NodeBase implements Deoptimizable hasEffectsWhenCalledAtPath( path: ObjectPath, callOptions: CallOptions, - context: ExecutionContext + context: EffectsExecutionContext ): boolean { if (this.usedBranch === null) { return ( @@ -136,17 +136,17 @@ export default class LogicalExpression extends NodeBase implements Deoptimizable return this.usedBranch.hasEffectsWhenCalledAtPath(path, callOptions, context); } - include(includeChildrenRecursively: IncludeChildren) { + include(includeChildrenRecursively: IncludeChildren, context: ExecutionContext) { this.included = true; if ( includeChildrenRecursively || this.usedBranch === null || - (this.unusedBranch as ExpressionNode).shouldBeIncluded() + (this.unusedBranch as ExpressionNode).shouldBeIncluded(context) ) { - this.left.include(includeChildrenRecursively); - this.right.include(includeChildrenRecursively); + this.left.include(includeChildrenRecursively, context); + this.right.include(includeChildrenRecursively, context); } else { - this.usedBranch.include(includeChildrenRecursively); + this.usedBranch.include(includeChildrenRecursively, context); } } diff --git a/src/ast/nodes/MemberExpression.ts b/src/ast/nodes/MemberExpression.ts index 8f564cf1103..8e2187c3bd9 100644 --- a/src/ast/nodes/MemberExpression.ts +++ b/src/ast/nodes/MemberExpression.ts @@ -4,16 +4,16 @@ import relativeId from '../../utils/relativeId'; import { NodeRenderOptions, RenderOptions } from '../../utils/renderHelpers'; import CallOptions from '../CallOptions'; import { DeoptimizableEntity } from '../DeoptimizableEntity'; -import { ExecutionContext } from '../ExecutionContext'; -import { EMPTY_IMMUTABLE_TRACKER, PathTracker } from '../utils/PathTracker'; +import { EffectsExecutionContext, ExecutionContext } from '../ExecutionContext'; import { + EMPTY_IMMUTABLE_TRACKER, EMPTY_PATH, - LiteralValueOrUnknown, ObjectPath, ObjectPathKey, - UnknownKey, - UnknownValue -} from '../values'; + PathTracker, + UnknownKey +} from '../utils/PathTracker'; +import { LiteralValueOrUnknown, UnknownValue } from '../values'; import ExternalVariable from '../variables/ExternalVariable'; import NamespaceVariable from '../variables/NamespaceVariable'; import Variable from '../variables/Variable'; @@ -164,7 +164,7 @@ export default class MemberExpression extends NodeBase implements DeoptimizableE ); } - hasEffects(context: ExecutionContext): boolean { + hasEffects(context: EffectsExecutionContext): boolean { return ( this.property.hasEffects(context) || this.object.hasEffects(context) || @@ -173,7 +173,7 @@ export default class MemberExpression extends NodeBase implements DeoptimizableE ); } - hasEffectsWhenAccessedAtPath(path: ObjectPath, context: ExecutionContext): boolean { + hasEffectsWhenAccessedAtPath(path: ObjectPath, context: EffectsExecutionContext): boolean { if (path.length === 0) return false; if (this.variable !== null) { return this.variable.hasEffectsWhenAccessedAtPath(path, context); @@ -184,7 +184,7 @@ export default class MemberExpression extends NodeBase implements DeoptimizableE ); } - hasEffectsWhenAssignedAtPath(path: ObjectPath, context: ExecutionContext): boolean { + hasEffectsWhenAssignedAtPath(path: ObjectPath, context: EffectsExecutionContext): boolean { if (this.variable !== null) { return this.variable.hasEffectsWhenAssignedAtPath(path, context); } @@ -197,7 +197,7 @@ export default class MemberExpression extends NodeBase implements DeoptimizableE hasEffectsWhenCalledAtPath( path: ObjectPath, callOptions: CallOptions, - context: ExecutionContext + context: EffectsExecutionContext ): boolean { if (this.variable !== null) { return this.variable.hasEffectsWhenCalledAtPath(path, callOptions, context); @@ -209,15 +209,15 @@ export default class MemberExpression extends NodeBase implements DeoptimizableE ); } - include(includeChildrenRecursively: IncludeChildren) { + include(includeChildrenRecursively: IncludeChildren, context: ExecutionContext) { if (!this.included) { this.included = true; if (this.variable !== null) { this.context.includeVariable(this.variable); } } - this.object.include(includeChildrenRecursively); - this.property.include(includeChildrenRecursively); + this.object.include(includeChildrenRecursively, context); + this.property.include(includeChildrenRecursively, context); } includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void { diff --git a/src/ast/nodes/MetaProperty.ts b/src/ast/nodes/MetaProperty.ts index c30b1bc8566..cc3e2de28b2 100644 --- a/src/ast/nodes/MetaProperty.ts +++ b/src/ast/nodes/MetaProperty.ts @@ -2,7 +2,7 @@ import MagicString from 'magic-string'; import { accessedFileUrlGlobals, accessedMetaUrlGlobals } from '../../utils/defaultPlugin'; import { dirname, normalize, relative } from '../../utils/path'; import { PluginDriver } from '../../utils/pluginDriver'; -import { ObjectPathKey } from '../values'; +import { ObjectPathKey } from '../utils/PathTracker'; import Identifier from './Identifier'; import MemberExpression from './MemberExpression'; import * as NodeType from './NodeType'; diff --git a/src/ast/nodes/MethodDefinition.ts b/src/ast/nodes/MethodDefinition.ts index af5bdc92c35..50090131deb 100644 --- a/src/ast/nodes/MethodDefinition.ts +++ b/src/ast/nodes/MethodDefinition.ts @@ -1,6 +1,6 @@ import CallOptions from '../CallOptions'; -import { ExecutionContext } from '../ExecutionContext'; -import { EMPTY_PATH, ObjectPath } from '../values'; +import { EffectsExecutionContext } from '../ExecutionContext'; +import { EMPTY_PATH, ObjectPath } from '../utils/PathTracker'; import FunctionExpression from './FunctionExpression'; import * as NodeType from './NodeType'; import { ExpressionNode, NodeBase } from './shared/Node'; @@ -13,14 +13,14 @@ export default class MethodDefinition extends NodeBase { type!: NodeType.tMethodDefinition; value!: FunctionExpression; - hasEffects(context: ExecutionContext) { + hasEffects(context: EffectsExecutionContext) { return this.key.hasEffects(context); } hasEffectsWhenCalledAtPath( path: ObjectPath, callOptions: CallOptions, - context: ExecutionContext + context: EffectsExecutionContext ) { return ( path.length > 0 || this.value.hasEffectsWhenCalledAtPath(EMPTY_PATH, callOptions, context) diff --git a/src/ast/nodes/NewExpression.ts b/src/ast/nodes/NewExpression.ts index d695703290e..08cb5d8f59e 100644 --- a/src/ast/nodes/NewExpression.ts +++ b/src/ast/nodes/NewExpression.ts @@ -1,6 +1,6 @@ import CallOptions from '../CallOptions'; -import { ExecutionContext } from '../ExecutionContext'; -import { EMPTY_PATH, ObjectPath, UNKNOWN_PATH } from '../values'; +import { EffectsExecutionContext, ExecutionContext } from '../ExecutionContext'; +import { EMPTY_PATH, ObjectPath, UNKNOWN_PATH } from '../utils/PathTracker'; import * as NodeType from './NodeType'; import { ExpressionNode, NodeBase } from './shared/Node'; @@ -20,7 +20,7 @@ export default class NewExpression extends NodeBase { } } - hasEffects(context: ExecutionContext): boolean { + hasEffects(context: EffectsExecutionContext): boolean { for (const argument of this.arguments) { if (argument.hasEffects(context)) return true; } diff --git a/src/ast/nodes/ObjectExpression.ts b/src/ast/nodes/ObjectExpression.ts index 3dd11910a3d..c397f340ed4 100644 --- a/src/ast/nodes/ObjectExpression.ts +++ b/src/ast/nodes/ObjectExpression.ts @@ -3,17 +3,20 @@ import { BLANK } from '../../utils/blank'; import { NodeRenderOptions, RenderOptions } from '../../utils/renderHelpers'; import CallOptions from '../CallOptions'; import { DeoptimizableEntity } from '../DeoptimizableEntity'; -import { ExecutionContext } from '../ExecutionContext'; -import { EMPTY_IMMUTABLE_TRACKER, PathTracker } from '../utils/PathTracker'; +import { EffectsExecutionContext } from '../ExecutionContext'; import { + EMPTY_IMMUTABLE_TRACKER, EMPTY_PATH, + ObjectPath, + PathTracker, + UNKNOWN_PATH +} from '../utils/PathTracker'; +import { getMemberReturnExpressionWhenCalled, hasMemberEffectWhenCalled, LiteralValueOrUnknown, objectMembers, - ObjectPath, UNKNOWN_EXPRESSION, - UNKNOWN_PATH, UnknownValue } from '../values'; import Identifier from './Identifier'; @@ -186,7 +189,7 @@ export default class ObjectExpression extends NodeBase { ); } - hasEffectsWhenAccessedAtPath(path: ObjectPath, context: ExecutionContext) { + hasEffectsWhenAccessedAtPath(path: ObjectPath, context: EffectsExecutionContext) { if (path.length === 0) return false; const key = path[0]; if ( @@ -210,7 +213,7 @@ export default class ObjectExpression extends NodeBase { return false; } - hasEffectsWhenAssignedAtPath(path: ObjectPath, context: ExecutionContext) { + hasEffectsWhenAssignedAtPath(path: ObjectPath, context: EffectsExecutionContext) { if (path.length === 0) return false; const key = path[0]; if ( @@ -239,7 +242,7 @@ export default class ObjectExpression extends NodeBase { hasEffectsWhenCalledAtPath( path: ObjectPath, callOptions: CallOptions, - context: ExecutionContext + context: EffectsExecutionContext ): boolean { const key = path[0]; if ( diff --git a/src/ast/nodes/ObjectPattern.ts b/src/ast/nodes/ObjectPattern.ts index 562277d728b..d40c5b0a9a3 100644 --- a/src/ast/nodes/ObjectPattern.ts +++ b/src/ast/nodes/ObjectPattern.ts @@ -1,5 +1,5 @@ -import { ExecutionContext } from '../ExecutionContext'; -import { EMPTY_PATH, ObjectPath } from '../values'; +import { EffectsExecutionContext } from '../ExecutionContext'; +import { EMPTY_PATH, ObjectPath } from '../utils/PathTracker'; import Variable from '../variables/Variable'; import * as NodeType from './NodeType'; import Property from './Property'; @@ -38,7 +38,7 @@ export default class ObjectPattern extends NodeBase implements PatternNode { } } - hasEffectsWhenAssignedAtPath(path: ObjectPath, context: ExecutionContext) { + hasEffectsWhenAssignedAtPath(path: ObjectPath, context: EffectsExecutionContext) { if (path.length > 0) return true; for (const property of this.properties) { if (property.hasEffectsWhenAssignedAtPath(EMPTY_PATH, context)) return true; diff --git a/src/ast/nodes/Program.ts b/src/ast/nodes/Program.ts index c8e22eab59e..c9c8cd4bdea 100644 --- a/src/ast/nodes/Program.ts +++ b/src/ast/nodes/Program.ts @@ -1,6 +1,6 @@ import MagicString from 'magic-string'; import { RenderOptions, renderStatementList } from '../../utils/renderHelpers'; -import { ExecutionContext } from '../ExecutionContext'; +import { EffectsExecutionContext, ExecutionContext } from '../ExecutionContext'; import * as NodeType from './NodeType'; import { IncludeChildren, NodeBase, StatementNode } from './shared/Node'; @@ -9,18 +9,18 @@ export default class Program extends NodeBase { sourceType!: 'module'; type!: NodeType.tProgram; - hasEffects(context: ExecutionContext) { + hasEffects(context: EffectsExecutionContext) { for (const node of this.body) { if (node.hasEffects(context)) return true; } return false; } - include(includeChildrenRecursively: IncludeChildren) { + include(includeChildrenRecursively: IncludeChildren, context: ExecutionContext) { this.included = true; for (const node of this.body) { - if (includeChildrenRecursively || node.shouldBeIncluded()) { - node.include(includeChildrenRecursively); + if (includeChildrenRecursively || node.shouldBeIncluded(context)) { + node.include(includeChildrenRecursively, context); } } } diff --git a/src/ast/nodes/Property.ts b/src/ast/nodes/Property.ts index ce0ae9a73fe..b20b7902b19 100644 --- a/src/ast/nodes/Property.ts +++ b/src/ast/nodes/Property.ts @@ -2,16 +2,15 @@ import MagicString from 'magic-string'; import { RenderOptions } from '../../utils/renderHelpers'; import CallOptions from '../CallOptions'; import { DeoptimizableEntity } from '../DeoptimizableEntity'; -import { ExecutionContext } from '../ExecutionContext'; -import { EMPTY_IMMUTABLE_TRACKER, PathTracker } from '../utils/PathTracker'; +import { EffectsExecutionContext } from '../ExecutionContext'; import { + EMPTY_IMMUTABLE_TRACKER, EMPTY_PATH, - LiteralValueOrUnknown, ObjectPath, - UNKNOWN_EXPRESSION, - UnknownKey, - UnknownValue -} from '../values'; + PathTracker, + UnknownKey +} from '../utils/PathTracker'; +import { LiteralValueOrUnknown, UNKNOWN_EXPRESSION, UnknownValue } from '../values'; import * as NodeType from './NodeType'; import { ExpressionEntity } from './shared/Expression'; import { ExpressionNode, NodeBase } from './shared/Node'; @@ -97,11 +96,11 @@ export default class Property extends NodeBase implements DeoptimizableEntity { return this.value.getReturnExpressionWhenCalledAtPath(path, recursionTracker, origin); } - hasEffects(context: ExecutionContext): boolean { + hasEffects(context: EffectsExecutionContext): boolean { return this.key.hasEffects(context) || this.value.hasEffects(context); } - hasEffectsWhenAccessedAtPath(path: ObjectPath, context: ExecutionContext): boolean { + hasEffectsWhenAccessedAtPath(path: ObjectPath, context: EffectsExecutionContext): boolean { if (this.kind === 'get') { const trackedExpressions = context.accessed.getEntities(path); if (trackedExpressions.has(this)) return false; @@ -115,7 +114,7 @@ export default class Property extends NodeBase implements DeoptimizableEntity { return this.value.hasEffectsWhenAccessedAtPath(path, context); } - hasEffectsWhenAssignedAtPath(path: ObjectPath, context: ExecutionContext): boolean { + hasEffectsWhenAssignedAtPath(path: ObjectPath, context: EffectsExecutionContext): boolean { if (this.kind === 'get') { if (path.length === 0) return true; const trackedExpressions = context.assigned.getEntities(path); @@ -139,7 +138,7 @@ export default class Property extends NodeBase implements DeoptimizableEntity { hasEffectsWhenCalledAtPath( path: ObjectPath, callOptions: CallOptions, - context: ExecutionContext + context: EffectsExecutionContext ) { if (this.kind === 'get') { const trackedExpressions = (callOptions.withNew diff --git a/src/ast/nodes/RestElement.ts b/src/ast/nodes/RestElement.ts index 19769c3b2f9..6cf6147bb97 100644 --- a/src/ast/nodes/RestElement.ts +++ b/src/ast/nodes/RestElement.ts @@ -1,5 +1,6 @@ -import { ExecutionContext } from '../ExecutionContext'; -import { EMPTY_PATH, ObjectPath, UNKNOWN_EXPRESSION, UnknownKey } from '../values'; +import { EffectsExecutionContext } from '../ExecutionContext'; +import { EMPTY_PATH, ObjectPath, UnknownKey } from '../utils/PathTracker'; +import { UNKNOWN_EXPRESSION } from '../values'; import Variable from '../variables/Variable'; import * as NodeType from './NodeType'; import { ExpressionEntity } from './shared/Expression'; @@ -32,7 +33,7 @@ export default class RestElement extends NodeBase implements PatternNode { path.length === 0 && this.argument.deoptimizePath(EMPTY_PATH); } - hasEffectsWhenAssignedAtPath(path: ObjectPath, context: ExecutionContext): boolean { + hasEffectsWhenAssignedAtPath(path: ObjectPath, context: EffectsExecutionContext): boolean { return path.length > 0 || this.argument.hasEffectsWhenAssignedAtPath(EMPTY_PATH, context); } } diff --git a/src/ast/nodes/ReturnStatement.ts b/src/ast/nodes/ReturnStatement.ts index 19ff7b806a9..b7f04bd37c4 100644 --- a/src/ast/nodes/ReturnStatement.ts +++ b/src/ast/nodes/ReturnStatement.ts @@ -1,6 +1,6 @@ import MagicString from 'magic-string'; import { RenderOptions } from '../../utils/renderHelpers'; -import { ExecutionContext } from '../ExecutionContext'; +import { EffectsExecutionContext } from '../ExecutionContext'; import { UNKNOWN_EXPRESSION } from '../values'; import * as NodeType from './NodeType'; import { ExpressionNode, StatementBase } from './shared/Node'; @@ -9,7 +9,7 @@ export default class ReturnStatement extends StatementBase { argument!: ExpressionNode | null; type!: NodeType.tReturnStatement; - hasEffects(context: ExecutionContext) { + hasEffects(context: EffectsExecutionContext) { return ( !context.ignore.returnAwaitYield || (this.argument !== null && this.argument.hasEffects(context)) diff --git a/src/ast/nodes/SequenceExpression.ts b/src/ast/nodes/SequenceExpression.ts index 55c114bcb8d..21c6222ba97 100644 --- a/src/ast/nodes/SequenceExpression.ts +++ b/src/ast/nodes/SequenceExpression.ts @@ -9,9 +9,9 @@ import { import { treeshakeNode } from '../../utils/treeshakeNode'; import CallOptions from '../CallOptions'; import { DeoptimizableEntity } from '../DeoptimizableEntity'; -import { ExecutionContext } from '../ExecutionContext'; -import { PathTracker } from '../utils/PathTracker'; -import { LiteralValueOrUnknown, ObjectPath } from '../values'; +import { EffectsExecutionContext, ExecutionContext } from '../ExecutionContext'; +import { ObjectPath, PathTracker } from '../utils/PathTracker'; +import { LiteralValueOrUnknown } from '../values'; import CallExpression from './CallExpression'; import * as NodeType from './NodeType'; import { ExpressionNode, IncludeChildren, NodeBase } from './shared/Node'; @@ -36,21 +36,21 @@ export default class SequenceExpression extends NodeBase { ); } - hasEffects(context: ExecutionContext): boolean { + hasEffects(context: EffectsExecutionContext): boolean { for (const expression of this.expressions) { if (expression.hasEffects(context)) return true; } return false; } - hasEffectsWhenAccessedAtPath(path: ObjectPath, context: ExecutionContext): boolean { + hasEffectsWhenAccessedAtPath(path: ObjectPath, context: EffectsExecutionContext): boolean { return ( path.length > 0 && this.expressions[this.expressions.length - 1].hasEffectsWhenAccessedAtPath(path, context) ); } - hasEffectsWhenAssignedAtPath(path: ObjectPath, context: ExecutionContext): boolean { + hasEffectsWhenAssignedAtPath(path: ObjectPath, context: EffectsExecutionContext): boolean { return ( path.length === 0 || this.expressions[this.expressions.length - 1].hasEffectsWhenAssignedAtPath(path, context) @@ -60,7 +60,7 @@ export default class SequenceExpression extends NodeBase { hasEffectsWhenCalledAtPath( path: ObjectPath, callOptions: CallOptions, - context: ExecutionContext + context: EffectsExecutionContext ): boolean { return this.expressions[this.expressions.length - 1].hasEffectsWhenCalledAtPath( path, @@ -69,14 +69,14 @@ export default class SequenceExpression extends NodeBase { ); } - include(includeChildrenRecursively: IncludeChildren) { + include(includeChildrenRecursively: IncludeChildren, context: ExecutionContext) { this.included = true; for (let i = 0; i < this.expressions.length - 1; i++) { const node = this.expressions[i]; - if (includeChildrenRecursively || node.shouldBeIncluded()) - node.include(includeChildrenRecursively); + if (includeChildrenRecursively || node.shouldBeIncluded(context)) + node.include(includeChildrenRecursively, context); } - this.expressions[this.expressions.length - 1].include(includeChildrenRecursively); + this.expressions[this.expressions.length - 1].include(includeChildrenRecursively, context); } render( diff --git a/src/ast/nodes/SpreadElement.ts b/src/ast/nodes/SpreadElement.ts index 64ea92ad909..cc0807079c4 100644 --- a/src/ast/nodes/SpreadElement.ts +++ b/src/ast/nodes/SpreadElement.ts @@ -1,4 +1,4 @@ -import { UnknownKey } from '../values'; +import { UnknownKey } from '../utils/PathTracker'; import * as NodeType from './NodeType'; import { ExpressionNode, NodeBase } from './shared/Node'; diff --git a/src/ast/nodes/SwitchCase.ts b/src/ast/nodes/SwitchCase.ts index fae1a539978..60100bd787b 100644 --- a/src/ast/nodes/SwitchCase.ts +++ b/src/ast/nodes/SwitchCase.ts @@ -4,6 +4,7 @@ import { RenderOptions, renderStatementList } from '../../utils/renderHelpers'; +import { ExecutionContext } from '../ExecutionContext'; import * as NodeType from './NodeType'; import { ExpressionNode, IncludeChildren, NodeBase, StatementNode } from './shared/Node'; @@ -12,12 +13,12 @@ export default class SwitchCase extends NodeBase { test!: ExpressionNode | null; type!: NodeType.tSwitchCase; - include(includeChildrenRecursively: IncludeChildren) { + include(includeChildrenRecursively: IncludeChildren, context: ExecutionContext) { this.included = true; - if (this.test) this.test.include(includeChildrenRecursively); + if (this.test) this.test.include(includeChildrenRecursively, context); for (const node of this.consequent) { - if (includeChildrenRecursively || node.shouldBeIncluded()) - node.include(includeChildrenRecursively); + if (includeChildrenRecursively || node.shouldBeIncluded(context)) + node.include(includeChildrenRecursively, context); } } diff --git a/src/ast/nodes/SwitchStatement.ts b/src/ast/nodes/SwitchStatement.ts index 9b2fe2a6452..9c849bfedae 100644 --- a/src/ast/nodes/SwitchStatement.ts +++ b/src/ast/nodes/SwitchStatement.ts @@ -1,4 +1,4 @@ -import { ExecutionContext } from '../ExecutionContext'; +import { EffectsExecutionContext } from '../ExecutionContext'; import BlockScope from '../scopes/BlockScope'; import Scope from '../scopes/Scope'; import * as NodeType from './NodeType'; @@ -14,7 +14,7 @@ export default class SwitchStatement extends StatementBase { this.scope = new BlockScope(parentScope); } - hasEffects(context: ExecutionContext) { + hasEffects(context: EffectsExecutionContext) { const ignoreBreakStatements = context.ignore.breakStatements; context.ignore.breakStatements = true; if (super.hasEffects(context)) return true; diff --git a/src/ast/nodes/TaggedTemplateExpression.ts b/src/ast/nodes/TaggedTemplateExpression.ts index 3c0fc473f39..a03e58dd1e2 100644 --- a/src/ast/nodes/TaggedTemplateExpression.ts +++ b/src/ast/nodes/TaggedTemplateExpression.ts @@ -1,6 +1,6 @@ import CallOptions from '../CallOptions'; -import { ExecutionContext } from '../ExecutionContext'; -import { EMPTY_PATH } from '../values'; +import { EffectsExecutionContext } from '../ExecutionContext'; +import { EMPTY_PATH } from '../utils/PathTracker'; import Identifier from './Identifier'; import * as NodeType from './NodeType'; import { ExpressionNode, NodeBase } from './shared/Node'; @@ -41,7 +41,7 @@ export default class TaggedTemplateExpression extends NodeBase { } } - hasEffects(context: ExecutionContext) { + hasEffects(context: EffectsExecutionContext) { return ( super.hasEffects(context) || this.tag.hasEffectsWhenCalledAtPath(EMPTY_PATH, this.callOptions, context) diff --git a/src/ast/nodes/TemplateLiteral.ts b/src/ast/nodes/TemplateLiteral.ts index 93e199cefb6..fbc02c708ee 100644 --- a/src/ast/nodes/TemplateLiteral.ts +++ b/src/ast/nodes/TemplateLiteral.ts @@ -1,6 +1,7 @@ import MagicString from 'magic-string'; import { RenderOptions } from '../../utils/renderHelpers'; -import { LiteralValueOrUnknown, ObjectPath, UnknownValue } from '../values'; +import { ObjectPath } from '../utils/PathTracker'; +import { LiteralValueOrUnknown, UnknownValue } from '../values'; import * as NodeType from './NodeType'; import { ExpressionNode, NodeBase } from './shared/Node'; import TemplateElement from './TemplateElement'; diff --git a/src/ast/nodes/ThisExpression.ts b/src/ast/nodes/ThisExpression.ts index 31d139436c3..efdd4b66b3f 100644 --- a/src/ast/nodes/ThisExpression.ts +++ b/src/ast/nodes/ThisExpression.ts @@ -1,8 +1,8 @@ import MagicString from 'magic-string'; import { RenderOptions } from '../../utils/renderHelpers'; -import { ExecutionContext } from '../ExecutionContext'; +import { EffectsExecutionContext } from '../ExecutionContext'; import ModuleScope from '../scopes/ModuleScope'; -import { ObjectPath } from '../values'; +import { ObjectPath } from '../utils/PathTracker'; import ThisVariable from '../variables/ThisVariable'; import * as NodeType from './NodeType'; import { NodeBase } from './shared/Node'; @@ -18,11 +18,11 @@ export default class ThisExpression extends NodeBase { this.variable = this.scope.findVariable('this') as ThisVariable; } - hasEffectsWhenAccessedAtPath(path: ObjectPath, context: ExecutionContext): boolean { + hasEffectsWhenAccessedAtPath(path: ObjectPath, context: EffectsExecutionContext): boolean { return path.length > 0 && this.variable.hasEffectsWhenAccessedAtPath(path, context); } - hasEffectsWhenAssignedAtPath(path: ObjectPath, context: ExecutionContext): boolean { + hasEffectsWhenAssignedAtPath(path: ObjectPath, context: EffectsExecutionContext): boolean { return this.variable.hasEffectsWhenAssignedAtPath(path, context); } diff --git a/src/ast/nodes/TryStatement.ts b/src/ast/nodes/TryStatement.ts index 71c63648a37..7ae90e21867 100644 --- a/src/ast/nodes/TryStatement.ts +++ b/src/ast/nodes/TryStatement.ts @@ -1,4 +1,4 @@ -import { ExecutionContext } from '../ExecutionContext'; +import { EffectsExecutionContext, ExecutionContext } from '../ExecutionContext'; import BlockStatement from './BlockStatement'; import CatchClause from './CatchClause'; import * as NodeType from './NodeType'; @@ -12,7 +12,7 @@ export default class TryStatement extends StatementBase { private directlyIncluded = false; - hasEffects(context: ExecutionContext): boolean { + hasEffects(context: EffectsExecutionContext): boolean { return ( this.block.body.length > 0 || (this.handler !== null && this.handler.hasEffects(context)) || @@ -20,19 +20,20 @@ export default class TryStatement extends StatementBase { ); } - include(includeChildrenRecursively: IncludeChildren) { + include(includeChildrenRecursively: IncludeChildren, context: ExecutionContext) { if (!this.directlyIncluded || !this.context.tryCatchDeoptimization) { this.included = true; this.directlyIncluded = true; this.block.include( - this.context.tryCatchDeoptimization ? INCLUDE_PARAMETERS : includeChildrenRecursively + this.context.tryCatchDeoptimization ? INCLUDE_PARAMETERS : includeChildrenRecursively, + context ); } if (this.handler !== null) { - this.handler.include(includeChildrenRecursively); + this.handler.include(includeChildrenRecursively, context); } if (this.finalizer !== null) { - this.finalizer.include(includeChildrenRecursively); + this.finalizer.include(includeChildrenRecursively, context); } } } diff --git a/src/ast/nodes/UnaryExpression.ts b/src/ast/nodes/UnaryExpression.ts index 4607adf0667..5910d116737 100644 --- a/src/ast/nodes/UnaryExpression.ts +++ b/src/ast/nodes/UnaryExpression.ts @@ -1,7 +1,7 @@ import { DeoptimizableEntity } from '../DeoptimizableEntity'; -import { ExecutionContext } from '../ExecutionContext'; -import { PathTracker } from '../utils/PathTracker'; -import { EMPTY_PATH, LiteralValueOrUnknown, ObjectPath, UnknownValue } from '../values'; +import { EffectsExecutionContext } from '../ExecutionContext'; +import { EMPTY_PATH, ObjectPath, PathTracker } from '../utils/PathTracker'; +import { LiteralValueOrUnknown, UnknownValue } from '../values'; import Identifier from './Identifier'; import { LiteralValue } from './Literal'; import * as NodeType from './NodeType'; @@ -44,7 +44,7 @@ export default class UnaryExpression extends NodeBase { return unaryOperators[this.operator](argumentValue); } - hasEffects(context: ExecutionContext): boolean { + hasEffects(context: EffectsExecutionContext): boolean { if (this.operator === 'typeof' && this.argument instanceof Identifier) return false; return ( this.argument.hasEffects(context) || @@ -53,7 +53,7 @@ export default class UnaryExpression extends NodeBase { ); } - hasEffectsWhenAccessedAtPath(path: ObjectPath, _context: ExecutionContext) { + hasEffectsWhenAccessedAtPath(path: ObjectPath) { if (this.operator === 'void') { return path.length > 0; } diff --git a/src/ast/nodes/UnknownNode.ts b/src/ast/nodes/UnknownNode.ts index c5ee5a2f8db..c08c96b81bd 100644 --- a/src/ast/nodes/UnknownNode.ts +++ b/src/ast/nodes/UnknownNode.ts @@ -1,12 +1,12 @@ import { ExecutionContext } from '../ExecutionContext'; -import { NodeBase } from './shared/Node'; +import { IncludeChildren, NodeBase } from './shared/Node'; export default class UnknownNode extends NodeBase { hasEffects(_context: ExecutionContext) { return true; } - include() { - super.include(true); + include(_includeChildrenRecursively: IncludeChildren, context: ExecutionContext) { + super.include(true, context); } } diff --git a/src/ast/nodes/UpdateExpression.ts b/src/ast/nodes/UpdateExpression.ts index 656363c1f69..341213ac498 100644 --- a/src/ast/nodes/UpdateExpression.ts +++ b/src/ast/nodes/UpdateExpression.ts @@ -1,7 +1,7 @@ import MagicString from 'magic-string'; import { RenderOptions } from '../../utils/renderHelpers'; -import { ExecutionContext } from '../ExecutionContext'; -import { EMPTY_PATH, ObjectPath } from '../values'; +import { EffectsExecutionContext } from '../ExecutionContext'; +import { EMPTY_PATH, ObjectPath } from '../utils/PathTracker'; import Identifier from './Identifier'; import * as NodeType from './NodeType'; import { ExpressionNode, NodeBase } from './shared/Node'; @@ -21,14 +21,14 @@ export default class UpdateExpression extends NodeBase { } } - hasEffects(context: ExecutionContext): boolean { + hasEffects(context: EffectsExecutionContext): boolean { return ( this.argument.hasEffects(context) || this.argument.hasEffectsWhenAssignedAtPath(EMPTY_PATH, context) ); } - hasEffectsWhenAccessedAtPath(path: ObjectPath, _context: ExecutionContext) { + hasEffectsWhenAccessedAtPath(path: ObjectPath) { return path.length > 1; } diff --git a/src/ast/nodes/VariableDeclaration.ts b/src/ast/nodes/VariableDeclaration.ts index 7dbffb400ad..f3c52804ec0 100644 --- a/src/ast/nodes/VariableDeclaration.ts +++ b/src/ast/nodes/VariableDeclaration.ts @@ -7,7 +7,7 @@ import { } from '../../utils/renderHelpers'; import { getSystemExportStatement } from '../../utils/systemJsRendering'; import { ExecutionContext } from '../ExecutionContext'; -import { EMPTY_PATH, ObjectPath } from '../values'; +import { EMPTY_PATH, ObjectPath } from '../utils/PathTracker'; import Variable from '../variables/Variable'; import Identifier, { IdentifierWithVariable } from './Identifier'; import * as NodeType from './NodeType'; @@ -47,18 +47,21 @@ export default class VariableDeclaration extends NodeBase { return false; } - include(includeChildrenRecursively: IncludeChildren) { + include(includeChildrenRecursively: IncludeChildren, context: ExecutionContext) { this.included = true; for (const declarator of this.declarations) { - if (includeChildrenRecursively || declarator.shouldBeIncluded()) - declarator.include(includeChildrenRecursively); + if (includeChildrenRecursively || declarator.shouldBeIncluded(context)) + declarator.include(includeChildrenRecursively, context); } } - includeWithAllDeclaredVariables(includeChildrenRecursively: IncludeChildren) { + includeWithAllDeclaredVariables( + includeChildrenRecursively: IncludeChildren, + context: ExecutionContext + ) { this.included = true; for (const declarator of this.declarations) { - declarator.include(includeChildrenRecursively); + declarator.include(includeChildrenRecursively, context); } } diff --git a/src/ast/nodes/VariableDeclarator.ts b/src/ast/nodes/VariableDeclarator.ts index 292401b4f69..4c50807afaf 100644 --- a/src/ast/nodes/VariableDeclarator.ts +++ b/src/ast/nodes/VariableDeclarator.ts @@ -1,6 +1,7 @@ import MagicString from 'magic-string'; import { RenderOptions } from '../../utils/renderHelpers'; -import { ObjectPath, UNDEFINED_EXPRESSION } from '../values'; +import { ObjectPath } from '../utils/PathTracker'; +import { UNDEFINED_EXPRESSION } from '../values'; import * as NodeType from './NodeType'; import { ExpressionNode, NodeBase } from './shared/Node'; import { PatternNode } from './shared/Pattern'; diff --git a/src/ast/nodes/WhileStatement.ts b/src/ast/nodes/WhileStatement.ts index 65f04b4c811..da81690ce86 100644 --- a/src/ast/nodes/WhileStatement.ts +++ b/src/ast/nodes/WhileStatement.ts @@ -1,4 +1,4 @@ -import { ExecutionContext } from '../ExecutionContext'; +import { EffectsExecutionContext } from '../ExecutionContext'; import * as NodeType from './NodeType'; import { ExpressionNode, StatementBase, StatementNode } from './shared/Node'; @@ -7,7 +7,7 @@ export default class WhileStatement extends StatementBase { test!: ExpressionNode; type!: NodeType.tWhileStatement; - hasEffects(context: ExecutionContext): boolean { + hasEffects(context: EffectsExecutionContext): boolean { if (this.test.hasEffects(context)) return true; const { ignore: { breakStatements } diff --git a/src/ast/nodes/YieldExpression.ts b/src/ast/nodes/YieldExpression.ts index b1dd417485b..c3b949fec37 100644 --- a/src/ast/nodes/YieldExpression.ts +++ b/src/ast/nodes/YieldExpression.ts @@ -1,7 +1,7 @@ import MagicString from 'magic-string'; import { RenderOptions } from '../../utils/renderHelpers'; -import { ExecutionContext } from '../ExecutionContext'; -import { UNKNOWN_PATH } from '../values'; +import { EffectsExecutionContext } from '../ExecutionContext'; +import { UNKNOWN_PATH } from '../utils/PathTracker'; import * as NodeType from './NodeType'; import { ExpressionNode, NodeBase } from './shared/Node'; @@ -17,7 +17,7 @@ export default class YieldExpression extends NodeBase { } } - hasEffects(context: ExecutionContext) { + hasEffects(context: EffectsExecutionContext) { return ( !context.ignore.returnAwaitYield || (this.argument !== null && this.argument.hasEffects(context)) diff --git a/src/ast/nodes/shared/ClassNode.ts b/src/ast/nodes/shared/ClassNode.ts index b05ad6faa79..d8865d4bc6c 100644 --- a/src/ast/nodes/shared/ClassNode.ts +++ b/src/ast/nodes/shared/ClassNode.ts @@ -1,8 +1,8 @@ import CallOptions from '../../CallOptions'; -import { ExecutionContext } from '../../ExecutionContext'; +import { EffectsExecutionContext, ExecutionContext } from '../../ExecutionContext'; import ChildScope from '../../scopes/ChildScope'; import Scope from '../../scopes/Scope'; -import { ObjectPath } from '../../values'; +import { ObjectPath } from '../../utils/PathTracker'; import ClassBody from '../ClassBody'; import Identifier from '../Identifier'; import { ExpressionNode, NodeBase } from './Node'; @@ -27,7 +27,7 @@ export default class ClassNode extends NodeBase { hasEffectsWhenCalledAtPath( path: ObjectPath, callOptions: CallOptions, - context: ExecutionContext + context: EffectsExecutionContext ) { return ( this.body.hasEffectsWhenCalledAtPath(path, callOptions, context) || diff --git a/src/ast/nodes/shared/Expression.ts b/src/ast/nodes/shared/Expression.ts index 953493090b4..17a1a8da89f 100644 --- a/src/ast/nodes/shared/Expression.ts +++ b/src/ast/nodes/shared/Expression.ts @@ -1,9 +1,9 @@ import CallOptions from '../../CallOptions'; import { DeoptimizableEntity } from '../../DeoptimizableEntity'; import { WritableEntity } from '../../Entity'; -import { ExecutionContext } from '../../ExecutionContext'; -import { PathTracker } from '../../utils/PathTracker'; -import { LiteralValueOrUnknown, ObjectPath } from '../../values'; +import { EffectsExecutionContext, ExecutionContext } from '../../ExecutionContext'; +import { ObjectPath, PathTracker } from '../../utils/PathTracker'; +import { LiteralValueOrUnknown } from '../../values'; import SpreadElement from '../SpreadElement'; import { ExpressionNode, IncludeChildren } from './Node'; @@ -25,12 +25,12 @@ export interface ExpressionEntity extends WritableEntity { recursionTracker: PathTracker, origin: DeoptimizableEntity ): ExpressionEntity; - hasEffectsWhenAccessedAtPath(path: ObjectPath, context: ExecutionContext): boolean; + hasEffectsWhenAccessedAtPath(path: ObjectPath, context: EffectsExecutionContext): boolean; hasEffectsWhenCalledAtPath( path: ObjectPath, callOptions: CallOptions, - context: ExecutionContext + context: EffectsExecutionContext ): boolean; - include(includeChildrenRecursively: IncludeChildren): void; + include(includeChildrenRecursively: IncludeChildren, context: ExecutionContext): void; includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void; } diff --git a/src/ast/nodes/shared/FunctionNode.ts b/src/ast/nodes/shared/FunctionNode.ts index bf12be00a43..a93c88899e0 100644 --- a/src/ast/nodes/shared/FunctionNode.ts +++ b/src/ast/nodes/shared/FunctionNode.ts @@ -1,18 +1,13 @@ import CallOptions from '../../CallOptions'; -import { ExecutionContext } from '../../ExecutionContext'; +import { EffectsExecutionContext, ExecutionContext } from '../../ExecutionContext'; import FunctionScope from '../../scopes/FunctionScope'; -import { - ObjectPath, - UNKNOWN_EXPRESSION, - UNKNOWN_PATH, - UnknownKey, - UnknownObjectExpression -} from '../../values'; +import { ObjectPath, UNKNOWN_PATH, UnknownKey } from '../../utils/PathTracker'; +import { UNKNOWN_EXPRESSION, UnknownObjectExpression } from '../../values'; import BlockStatement from '../BlockStatement'; import Identifier, { IdentifierWithVariable } from '../Identifier'; import RestElement from '../RestElement'; import SpreadElement from '../SpreadElement'; -import { ExpressionNode, GenericEsTreeNode, NodeBase } from './Node'; +import { ExpressionNode, GenericEsTreeNode, IncludeChildren, NodeBase } from './Node'; import { PatternNode } from './Pattern'; export default class FunctionNode extends NodeBase { @@ -66,7 +61,7 @@ export default class FunctionNode extends NodeBase { hasEffectsWhenCalledAtPath( path: ObjectPath, callOptions: CallOptions, - context: ExecutionContext + context: EffectsExecutionContext ) { if (path.length > 0) return true; for (const param of this.params) { @@ -93,16 +88,16 @@ export default class FunctionNode extends NodeBase { return false; } - include(includeChildrenRecursively: boolean | 'variables') { + include(includeChildrenRecursively: IncludeChildren, context: ExecutionContext) { this.included = true; - this.body.include(includeChildrenRecursively); + this.body.include(includeChildrenRecursively, context); if (this.id) { this.id.include(); } const hasArguments = this.scope.argumentsVariable.included; for (const param of this.params) { if (!(param instanceof Identifier) || hasArguments) { - param.include(includeChildrenRecursively); + param.include(includeChildrenRecursively, context); } } } diff --git a/src/ast/nodes/shared/MultiExpression.ts b/src/ast/nodes/shared/MultiExpression.ts index 09db2a05a12..34861d742a3 100644 --- a/src/ast/nodes/shared/MultiExpression.ts +++ b/src/ast/nodes/shared/MultiExpression.ts @@ -1,8 +1,8 @@ import CallOptions from '../../CallOptions'; import { DeoptimizableEntity } from '../../DeoptimizableEntity'; -import { ExecutionContext } from '../../ExecutionContext'; -import { PathTracker } from '../../utils/PathTracker'; -import { LiteralValueOrUnknown, ObjectPath, UnknownValue } from '../../values'; +import { EffectsExecutionContext } from '../../ExecutionContext'; +import { ObjectPath, PathTracker } from '../../utils/PathTracker'; +import { LiteralValueOrUnknown, UnknownValue } from '../../values'; import SpreadElement from '../SpreadElement'; import { ExpressionEntity } from './Expression'; import { ExpressionNode } from './Node'; @@ -38,14 +38,14 @@ export class MultiExpression implements ExpressionEntity { ); } - hasEffectsWhenAccessedAtPath(path: ObjectPath, context: ExecutionContext): boolean { + hasEffectsWhenAccessedAtPath(path: ObjectPath, context: EffectsExecutionContext): boolean { for (const expression of this.expressions) { if (expression.hasEffectsWhenAccessedAtPath(path, context)) return true; } return false; } - hasEffectsWhenAssignedAtPath(path: ObjectPath, context: ExecutionContext): boolean { + hasEffectsWhenAssignedAtPath(path: ObjectPath, context: EffectsExecutionContext): boolean { for (const expression of this.expressions) { if (expression.hasEffectsWhenAssignedAtPath(path, context)) return true; } @@ -55,7 +55,7 @@ export class MultiExpression implements ExpressionEntity { hasEffectsWhenCalledAtPath( path: ObjectPath, callOptions: CallOptions, - context: ExecutionContext + context: EffectsExecutionContext ): boolean { for (const expression of this.expressions) { if (expression.hasEffectsWhenCalledAtPath(path, callOptions, context)) return true; diff --git a/src/ast/nodes/shared/Node.ts b/src/ast/nodes/shared/Node.ts index e767f6dad5f..5131e88c596 100644 --- a/src/ast/nodes/shared/Node.ts +++ b/src/ast/nodes/shared/Node.ts @@ -5,11 +5,16 @@ import { NodeRenderOptions, RenderOptions } from '../../../utils/renderHelpers'; import CallOptions from '../../CallOptions'; import { DeoptimizableEntity } from '../../DeoptimizableEntity'; import { Entity } from '../../Entity'; -import { createExecutionContext, ExecutionContext } from '../../ExecutionContext'; +import { + createEffectsExecutionContext, + createExecutionContext, + EffectsExecutionContext, + ExecutionContext +} from '../../ExecutionContext'; import { getAndCreateKeys, keys } from '../../keys'; import ChildScope from '../../scopes/ChildScope'; -import { PathTracker } from '../../utils/PathTracker'; -import { LiteralValueOrUnknown, ObjectPath, UNKNOWN_EXPRESSION, UnknownValue } from '../../values'; +import { ObjectPath, PathTracker } from '../../utils/PathTracker'; +import { LiteralValueOrUnknown, UNKNOWN_EXPRESSION, UnknownValue } from '../../values'; import LocalVariable from '../../variables/LocalVariable'; import Variable from '../../variables/Variable'; import SpreadElement from '../SpreadElement'; @@ -52,21 +57,24 @@ export interface Node extends Entity { * which only have an effect if their surrounding loop or switch statement is included. * The options pass on information like this about the current execution path. */ - hasEffects(context: ExecutionContext): boolean; + hasEffects(context: EffectsExecutionContext): boolean; /** * Includes the node in the bundle. If the flag is not set, children are usually included * if they are necessary for this node (e.g. a function body) or if they have effects. * Necessary variables need to be included as well. */ - include(includeChildrenRecursively: IncludeChildren): void; + include(includeChildrenRecursively: IncludeChildren, context: ExecutionContext): void; /** * Alternative version of include to override the default behaviour of * declarations to only include nodes for declarators that have an effect. Necessary * for for-loops that do not use a declared loop variable. */ - includeWithAllDeclaredVariables(includeChildrenRecursively: IncludeChildren): void; + includeWithAllDeclaredVariables( + includeChildrenRecursively: IncludeChildren, + context: ExecutionContext + ): void; render(code: MagicString, options: RenderOptions, nodeRenderOptions?: NodeRenderOptions): void; /** @@ -75,7 +83,7 @@ export interface Node extends Entity { * visits as the inclusion of additional variables may require the inclusion of more child * nodes in e.g. block statements. */ - shouldBeIncluded(): boolean; + shouldBeIncluded(context: ExecutionContext): boolean; } export interface StatementNode extends Node {} @@ -154,7 +162,7 @@ export class NodeBase implements ExpressionNode { return UNKNOWN_EXPRESSION; } - hasEffects(context: ExecutionContext): boolean { + hasEffects(context: EffectsExecutionContext): boolean { for (const key of this.keys) { const value = (this as GenericEsTreeNode)[key]; if (value === null || key === 'annotations') continue; @@ -167,45 +175,48 @@ export class NodeBase implements ExpressionNode { return false; } - hasEffectsWhenAccessedAtPath(path: ObjectPath, _context: ExecutionContext) { + hasEffectsWhenAccessedAtPath(path: ObjectPath, _context: EffectsExecutionContext) { return path.length > 0; } - hasEffectsWhenAssignedAtPath(_path: ObjectPath, _context: ExecutionContext) { + hasEffectsWhenAssignedAtPath(_path: ObjectPath, _context: EffectsExecutionContext) { return true; } hasEffectsWhenCalledAtPath( _path: ObjectPath, _callOptions: CallOptions, - _context: ExecutionContext + _context: EffectsExecutionContext ) { return true; } - include(includeChildrenRecursively: IncludeChildren) { + include(includeChildrenRecursively: IncludeChildren, context: ExecutionContext) { this.included = true; for (const key of this.keys) { const value = (this as GenericEsTreeNode)[key]; if (value === null || key === 'annotations') continue; if (Array.isArray(value)) { for (const child of value) { - if (child !== null) child.include(includeChildrenRecursively); + if (child !== null) child.include(includeChildrenRecursively, context); } } else { - value.include(includeChildrenRecursively); + value.include(includeChildrenRecursively, context); } } } includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void { for (const arg of args) { - arg.include(false); + arg.include(false, createExecutionContext()); } } - includeWithAllDeclaredVariables(includeChildrenRecursively: IncludeChildren) { - this.include(includeChildrenRecursively); + includeWithAllDeclaredVariables( + includeChildrenRecursively: IncludeChildren, + context: ExecutionContext + ) { + this.include(includeChildrenRecursively, context); } /** @@ -266,8 +277,8 @@ export class NodeBase implements ExpressionNode { } } - shouldBeIncluded(): boolean { - return this.included || this.hasEffects(createExecutionContext()); + shouldBeIncluded(_context: ExecutionContext): boolean { + return this.included || this.hasEffects(createEffectsExecutionContext()); } toString() { diff --git a/src/ast/nodes/shared/knownGlobals.ts b/src/ast/nodes/shared/knownGlobals.ts index 7bb41b60b81..85fe83b4a91 100644 --- a/src/ast/nodes/shared/knownGlobals.ts +++ b/src/ast/nodes/shared/knownGlobals.ts @@ -1,4 +1,4 @@ -import { ObjectPath } from '../../values'; +import { ObjectPath } from '../../utils/PathTracker'; const ValueProperties = Symbol('Value Properties'); diff --git a/src/ast/scopes/FunctionScope.ts b/src/ast/scopes/FunctionScope.ts index a321c613c27..50503bb9bb0 100644 --- a/src/ast/scopes/FunctionScope.ts +++ b/src/ast/scopes/FunctionScope.ts @@ -1,4 +1,5 @@ import { AstContext } from '../../Module'; +import { createExecutionContext } from '../ExecutionContext'; import { ExpressionNode } from '../nodes/shared/Node'; import SpreadElement from '../nodes/SpreadElement'; import ArgumentsVariable from '../variables/ArgumentsVariable'; @@ -25,7 +26,7 @@ export default class FunctionScope extends ReturnValueScope { if (this.argumentsVariable.included) { for (const arg of args) { if (!arg.included) { - arg.include(false); + arg.include(false, createExecutionContext()); } } } diff --git a/src/ast/scopes/ParameterScope.ts b/src/ast/scopes/ParameterScope.ts index 316129816a1..0b698a6fc86 100644 --- a/src/ast/scopes/ParameterScope.ts +++ b/src/ast/scopes/ParameterScope.ts @@ -1,4 +1,5 @@ import { AstContext } from '../../Module'; +import { createExecutionContext } from '../ExecutionContext'; import Identifier from '../nodes/Identifier'; import { ExpressionNode } from '../nodes/shared/Node'; import SpreadElement from '../nodes/SpreadElement'; @@ -64,11 +65,11 @@ export default class ParameterScope extends ChildScope { } } } - if (!argIncluded && arg.shouldBeIncluded()) { + if (!argIncluded && arg.shouldBeIncluded(createExecutionContext())) { argIncluded = true; } if (argIncluded) { - arg.include(calledFromTryStatement); + arg.include(calledFromTryStatement, createExecutionContext()); } } } diff --git a/src/ast/scopes/ReturnValueScope.ts b/src/ast/scopes/ReturnValueScope.ts index 030ee03a417..51975004fec 100644 --- a/src/ast/scopes/ReturnValueScope.ts +++ b/src/ast/scopes/ReturnValueScope.ts @@ -1,5 +1,6 @@ import { ExpressionEntity } from '../nodes/shared/Expression'; -import { UNKNOWN_EXPRESSION, UNKNOWN_PATH } from '../values'; +import { UNKNOWN_PATH } from '../utils/PathTracker'; +import { UNKNOWN_EXPRESSION } from '../values'; import ParameterScope from './ParameterScope'; export default class ReturnValueScope extends ParameterScope { diff --git a/src/ast/utils/PathTracker.ts b/src/ast/utils/PathTracker.ts index 121ee713fbc..7deba3346d5 100644 --- a/src/ast/utils/PathTracker.ts +++ b/src/ast/utils/PathTracker.ts @@ -1,5 +1,11 @@ import { Entity } from '../Entity'; -import { ObjectPath, UnknownKey } from '../values'; + +export const UnknownKey = Symbol('Unknown Key'); +export type ObjectPathKey = string | typeof UnknownKey; + +export type ObjectPath = ObjectPathKey[]; +export const EMPTY_PATH: ObjectPath = []; +export const UNKNOWN_PATH: ObjectPath = [UnknownKey]; const EntitiesKey = Symbol('Entities'); interface EntityPaths { diff --git a/src/ast/values.ts b/src/ast/values.ts index a43fee22450..886053c203a 100644 --- a/src/ast/values.ts +++ b/src/ast/values.ts @@ -1,16 +1,10 @@ import CallOptions from './CallOptions'; -import { ExecutionContext } from './ExecutionContext'; +import { createExecutionContext, EffectsExecutionContext } from './ExecutionContext'; import { LiteralValue } from './nodes/Literal'; import { ExpressionEntity } from './nodes/shared/Expression'; import { ExpressionNode } from './nodes/shared/Node'; import SpreadElement from './nodes/SpreadElement'; - -export const UnknownKey = Symbol('Unknown Key'); -export type ObjectPathKey = string | typeof UnknownKey; - -export type ObjectPath = ObjectPathKey[]; -export const EMPTY_PATH: ObjectPath = []; -export const UNKNOWN_PATH: ObjectPath = [UnknownKey]; +import { EMPTY_PATH, ObjectPath, ObjectPathKey } from './utils/PathTracker'; export interface MemberDescription { callsArgs: number[] | null; @@ -47,7 +41,7 @@ export const UNKNOWN_EXPRESSION: ExpressionEntity = { include: () => {}, includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void { for (const arg of args) { - arg.include(false); + arg.include(false, createExecutionContext()); } }, included: true, @@ -107,7 +101,7 @@ export class UnknownArrayExpression implements ExpressionEntity { hasEffectsWhenCalledAtPath( path: ObjectPath, callOptions: CallOptions, - context: ExecutionContext + context: EffectsExecutionContext ) { if (path.length === 1) { return hasMemberEffectWhenCalled(arrayMembers, path[0], this.included, callOptions, context); @@ -121,7 +115,7 @@ export class UnknownArrayExpression implements ExpressionEntity { includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void { for (const arg of args) { - arg.include(false); + arg.include(false, createExecutionContext()); } } @@ -184,7 +178,7 @@ const UNKNOWN_LITERAL_BOOLEAN: ExpressionEntity = { include: () => {}, includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void { for (const arg of args) { - arg.include(false); + arg.include(false, createExecutionContext()); } }, included: true, @@ -229,7 +223,7 @@ const UNKNOWN_LITERAL_NUMBER: ExpressionEntity = { include: () => {}, includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void { for (const arg of args) { - arg.include(false); + arg.include(false, createExecutionContext()); } }, included: true, @@ -272,16 +266,16 @@ const UNKNOWN_LITERAL_STRING: ExpressionEntity = { }, hasEffectsWhenAccessedAtPath: path => path.length > 1, hasEffectsWhenAssignedAtPath: path => path.length > 0, - hasEffectsWhenCalledAtPath: (path, callOptions, options) => { + hasEffectsWhenCalledAtPath: (path, callOptions, context) => { if (path.length === 1) { - return hasMemberEffectWhenCalled(literalStringMembers, path[0], true, callOptions, options); + return hasMemberEffectWhenCalled(literalStringMembers, path[0], true, callOptions, context); } return true; }, include: () => {}, includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void { for (const arg of args) { - arg.include(false); + arg.include(false, createExecutionContext()); } }, included: true, @@ -324,7 +318,7 @@ export class UnknownObjectExpression implements ExpressionEntity { hasEffectsWhenCalledAtPath( path: ObjectPath, callOptions: CallOptions, - context: ExecutionContext + context: EffectsExecutionContext ) { if (path.length === 1) { return hasMemberEffectWhenCalled(objectMembers, path[0], this.included, callOptions, context); @@ -338,7 +332,7 @@ export class UnknownObjectExpression implements ExpressionEntity { includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void { for (const arg of args) { - arg.include(false); + arg.include(false, createExecutionContext()); } } @@ -462,7 +456,7 @@ export function hasMemberEffectWhenCalled( memberName: ObjectPathKey, parentIncluded: boolean, callOptions: CallOptions, - context: ExecutionContext + context: EffectsExecutionContext ) { if ( typeof memberName !== 'string' || diff --git a/src/ast/variables/ArgumentsVariable.ts b/src/ast/variables/ArgumentsVariable.ts index 05ac3f84a37..b67ff2f0de4 100644 --- a/src/ast/variables/ArgumentsVariable.ts +++ b/src/ast/variables/ArgumentsVariable.ts @@ -1,5 +1,6 @@ import { AstContext } from '../../Module'; -import { ObjectPath, UNKNOWN_EXPRESSION } from '../values'; +import { ObjectPath } from '../utils/PathTracker'; +import { UNKNOWN_EXPRESSION } from '../values'; import LocalVariable from './LocalVariable'; export default class ArgumentsVariable extends LocalVariable { diff --git a/src/ast/variables/GlobalVariable.ts b/src/ast/variables/GlobalVariable.ts index 6aa5d5b9561..a70665c4244 100644 --- a/src/ast/variables/GlobalVariable.ts +++ b/src/ast/variables/GlobalVariable.ts @@ -1,5 +1,5 @@ import { isGlobalMember, isPureGlobal } from '../nodes/shared/knownGlobals'; -import { ObjectPath } from '../values'; +import { ObjectPath } from '../utils/PathTracker'; import Variable from './Variable'; export default class GlobalVariable extends Variable { diff --git a/src/ast/variables/LocalVariable.ts b/src/ast/variables/LocalVariable.ts index 2d0110f043f..b7cbfc63d34 100644 --- a/src/ast/variables/LocalVariable.ts +++ b/src/ast/variables/LocalVariable.ts @@ -2,21 +2,15 @@ import Module, { AstContext } from '../../Module'; import { markModuleAndImpureDependenciesAsExecuted } from '../../utils/traverseStaticDependencies'; import CallOptions from '../CallOptions'; import { DeoptimizableEntity } from '../DeoptimizableEntity'; -import { ExecutionContext } from '../ExecutionContext'; +import { createExecutionContext, EffectsExecutionContext } from '../ExecutionContext'; import ExportDefaultDeclaration from '../nodes/ExportDefaultDeclaration'; import Identifier from '../nodes/Identifier'; import * as NodeType from '../nodes/NodeType'; import { ExpressionEntity } from '../nodes/shared/Expression'; import { ExpressionNode, Node } from '../nodes/shared/Node'; import SpreadElement from '../nodes/SpreadElement'; -import { PathTracker } from '../utils/PathTracker'; -import { - LiteralValueOrUnknown, - ObjectPath, - UNKNOWN_EXPRESSION, - UNKNOWN_PATH, - UnknownValue -} from '../values'; +import { ObjectPath, PathTracker, UNKNOWN_PATH } from '../utils/PathTracker'; +import { LiteralValueOrUnknown, UNKNOWN_EXPRESSION, UnknownValue } from '../values'; import Variable from './Variable'; // To avoid infinite recursions @@ -126,7 +120,7 @@ export default class LocalVariable extends Variable { return value; } - hasEffectsWhenAccessedAtPath(path: ObjectPath, context: ExecutionContext) { + hasEffectsWhenAccessedAtPath(path: ObjectPath, context: EffectsExecutionContext) { if (path.length === 0) return false; if (this.isReassigned || path.length > MAX_PATH_DEPTH) return true; const trackedExpressions = context.accessed.getEntities(path); @@ -135,7 +129,7 @@ export default class LocalVariable extends Variable { return (this.init && this.init.hasEffectsWhenAccessedAtPath(path, context)) as boolean; } - hasEffectsWhenAssignedAtPath(path: ObjectPath, context: ExecutionContext) { + hasEffectsWhenAssignedAtPath(path: ObjectPath, context: EffectsExecutionContext) { if (this.included || path.length > MAX_PATH_DEPTH) return true; if (path.length === 0) return false; if (this.isReassigned) return true; @@ -148,7 +142,7 @@ export default class LocalVariable extends Variable { hasEffectsWhenCalledAtPath( path: ObjectPath, callOptions: CallOptions, - context: ExecutionContext + context: EffectsExecutionContext ) { if (path.length > MAX_PATH_DEPTH || this.isReassigned) return true; const trackedExpressions = (callOptions.withNew @@ -169,7 +163,7 @@ export default class LocalVariable extends Variable { } for (const declaration of this.declarations) { // If node is a default export, it can save a tree-shaking run to include the full declaration now - if (!declaration.included) declaration.include(false); + if (!declaration.included) declaration.include(false, createExecutionContext()); let node = declaration.parent as Node; while (!node.included) { // We do not want to properly include parents in case they are part of a dead branch @@ -185,7 +179,7 @@ export default class LocalVariable extends Variable { includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void { if (this.isReassigned) { for (const arg of args) { - arg.include(false); + arg.include(false, createExecutionContext()); } } else if (this.init) { this.init.includeCallArguments(args); diff --git a/src/ast/variables/NamespaceVariable.ts b/src/ast/variables/NamespaceVariable.ts index 10b36bdc9fb..697ab5b9cae 100644 --- a/src/ast/variables/NamespaceVariable.ts +++ b/src/ast/variables/NamespaceVariable.ts @@ -2,7 +2,7 @@ import Module, { AstContext } from '../../Module'; import { RenderOptions } from '../../utils/renderHelpers'; import { RESERVED_NAMES } from '../../utils/reservedNames'; import Identifier from '../nodes/Identifier'; -import { UNKNOWN_PATH } from '../values'; +import { UNKNOWN_PATH } from '../utils/PathTracker'; import Variable from './Variable'; export default class NamespaceVariable extends Variable { @@ -92,7 +92,7 @@ export default class NamespaceVariable extends Variable { }); members.unshift(`${t}__proto__:${_}null`); - + if (options.namespaceToStringTag) { members.unshift(`${t}[Symbol.toStringTag]:${_}'Module'`); } diff --git a/src/ast/variables/ThisVariable.ts b/src/ast/variables/ThisVariable.ts index b3ddc66c90c..d1dc14c1d01 100644 --- a/src/ast/variables/ThisVariable.ts +++ b/src/ast/variables/ThisVariable.ts @@ -1,8 +1,9 @@ import { AstContext } from '../../Module'; import CallOptions from '../CallOptions'; -import { ExecutionContext } from '../ExecutionContext'; +import { EffectsExecutionContext } from '../ExecutionContext'; import { ExpressionEntity } from '../nodes/shared/Expression'; -import { LiteralValueOrUnknown, ObjectPath, UNKNOWN_EXPRESSION, UnknownValue } from '../values'; +import { ObjectPath } from '../utils/PathTracker'; +import { LiteralValueOrUnknown, UNKNOWN_EXPRESSION, UnknownValue } from '../values'; import LocalVariable from './LocalVariable'; export default class ThisVariable extends LocalVariable { @@ -14,14 +15,14 @@ export default class ThisVariable extends LocalVariable { return UnknownValue; } - hasEffectsWhenAccessedAtPath(path: ObjectPath, context: ExecutionContext) { + hasEffectsWhenAccessedAtPath(path: ObjectPath, context: EffectsExecutionContext) { return ( this.getInit(context).hasEffectsWhenAccessedAtPath(path, context) || super.hasEffectsWhenAccessedAtPath(path, context) ); } - hasEffectsWhenAssignedAtPath(path: ObjectPath, context: ExecutionContext) { + hasEffectsWhenAssignedAtPath(path: ObjectPath, context: EffectsExecutionContext) { return ( this.getInit(context).hasEffectsWhenAssignedAtPath(path, context) || super.hasEffectsWhenAssignedAtPath(path, context) @@ -31,7 +32,7 @@ export default class ThisVariable extends LocalVariable { hasEffectsWhenCalledAtPath( path: ObjectPath, callOptions: CallOptions, - context: ExecutionContext + context: EffectsExecutionContext ) { return ( this.getInit(context).hasEffectsWhenCalledAtPath(path, callOptions, context) || @@ -39,7 +40,7 @@ export default class ThisVariable extends LocalVariable { ); } - private getInit(context: ExecutionContext): ExpressionEntity { + private getInit(context: EffectsExecutionContext): ExpressionEntity { return context.replacedVariableInits.get(this) || UNKNOWN_EXPRESSION; } } diff --git a/src/ast/variables/Variable.ts b/src/ast/variables/Variable.ts index ad1e971312b..a34a5ed7033 100644 --- a/src/ast/variables/Variable.ts +++ b/src/ast/variables/Variable.ts @@ -2,13 +2,13 @@ import ExternalModule from '../../ExternalModule'; import Module from '../../Module'; import CallOptions from '../CallOptions'; import { DeoptimizableEntity } from '../DeoptimizableEntity'; -import { ExecutionContext } from '../ExecutionContext'; +import { createExecutionContext, ExecutionContext } from '../ExecutionContext'; import Identifier from '../nodes/Identifier'; import { ExpressionEntity } from '../nodes/shared/Expression'; import { ExpressionNode } from '../nodes/shared/Node'; import SpreadElement from '../nodes/SpreadElement'; -import { PathTracker } from '../utils/PathTracker'; -import { LiteralValueOrUnknown, ObjectPath, UNKNOWN_EXPRESSION, UnknownValue } from '../values'; +import { ObjectPath, PathTracker } from '../utils/PathTracker'; +import { LiteralValueOrUnknown, UNKNOWN_EXPRESSION, UnknownValue } from '../values'; export default class Variable implements ExpressionEntity { alwaysRendered = false; @@ -88,7 +88,7 @@ export default class Variable implements ExpressionEntity { includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void { for (const arg of args) { - arg.include(false); + arg.include(false, createExecutionContext()); } } From 6cceafe704cb531bdde7053885e9a0f898d87604 Mon Sep 17 00:00:00 2001 From: Lukas Taegert-Atkinson Date: Sun, 6 Oct 2019 09:10:06 +0200 Subject: [PATCH 13/35] Handle broken control flow due to errors --- src/ExternalModule.ts | 2 +- src/ast/ExecutionContext.ts | 20 +++- src/ast/nodes/ArrayPattern.ts | 3 +- src/ast/nodes/ArrowFunctionExpression.ts | 9 +- src/ast/nodes/BinaryExpression.ts | 4 +- src/ast/nodes/ForInStatement.ts | 1 + src/ast/nodes/ForOfStatement.ts | 1 + src/ast/nodes/ForStatement.ts | 13 ++- src/ast/nodes/Identifier.ts | 8 +- src/ast/nodes/IfStatement.ts | 12 +- src/ast/nodes/Literal.ts | 3 +- src/ast/nodes/NewExpression.ts | 4 +- src/ast/nodes/SwitchCase.ts | 1 + src/ast/nodes/TemplateElement.ts | 3 +- src/ast/nodes/ThisExpression.ts | 3 +- src/ast/nodes/ThrowStatement.ts | 10 +- src/ast/nodes/TryStatement.ts | 2 + src/ast/nodes/UnknownNode.ts | 2 +- src/ast/nodes/VariableDeclaration.ts | 6 +- src/ast/nodes/WhileStatement.ts | 9 +- src/ast/nodes/shared/ClassNode.ts | 6 +- src/ast/nodes/shared/FunctionNode.ts | 3 + src/ast/nodes/shared/Node.ts | 6 +- src/ast/variables/Variable.ts | 8 +- .../caught-errors/_config.js | 6 + .../caught-errors/_expected.js | 45 ++++++++ .../break-control-flow/caught-errors/main.js | 50 ++++++++ .../hoisted-declarations/_config.js | 3 + .../hoisted-declarations/_expected.js | 21 ++++ .../hoisted-declarations/main.js | 29 +++++ .../if-statement-errors/_config.js | 3 + .../if-statement-errors/_expected.js | 90 +++++++++++++++ .../if-statement-errors/main.js | 108 ++++++++++++++++++ .../break-control-flow/loop-errors/_config.js | 3 + .../loop-errors/_expected.js | 53 +++++++++ .../break-control-flow/loop-errors/main.js | 59 ++++++++++ .../switch-errors/_config.js | 3 + .../switch-errors/_expected.js | 13 +++ .../break-control-flow/switch-errors/main.js | 13 +++ .../thrown-errors/_config.js | 3 + .../thrown-errors/_expected.js | 37 ++++++ .../break-control-flow/thrown-errors/main.js | 47 ++++++++ 42 files changed, 682 insertions(+), 43 deletions(-) create mode 100644 test/form/samples/break-control-flow/caught-errors/_config.js create mode 100644 test/form/samples/break-control-flow/caught-errors/_expected.js create mode 100644 test/form/samples/break-control-flow/caught-errors/main.js create mode 100644 test/form/samples/break-control-flow/hoisted-declarations/_config.js create mode 100644 test/form/samples/break-control-flow/hoisted-declarations/_expected.js create mode 100644 test/form/samples/break-control-flow/hoisted-declarations/main.js create mode 100644 test/form/samples/break-control-flow/if-statement-errors/_config.js create mode 100644 test/form/samples/break-control-flow/if-statement-errors/_expected.js create mode 100644 test/form/samples/break-control-flow/if-statement-errors/main.js create mode 100644 test/form/samples/break-control-flow/loop-errors/_config.js create mode 100644 test/form/samples/break-control-flow/loop-errors/_expected.js create mode 100644 test/form/samples/break-control-flow/loop-errors/main.js create mode 100644 test/form/samples/break-control-flow/switch-errors/_config.js create mode 100644 test/form/samples/break-control-flow/switch-errors/_expected.js create mode 100644 test/form/samples/break-control-flow/switch-errors/main.js create mode 100644 test/form/samples/break-control-flow/thrown-errors/_config.js create mode 100644 test/form/samples/break-control-flow/thrown-errors/_expected.js create mode 100644 test/form/samples/break-control-flow/thrown-errors/main.js diff --git a/src/ExternalModule.ts b/src/ExternalModule.ts index d1f406a0e8c..5511a26d84f 100644 --- a/src/ExternalModule.ts +++ b/src/ExternalModule.ts @@ -37,7 +37,7 @@ export default class ExternalModule { this.exportedVariables = new Map(); } - getVariableForExportName(name: string, _isExportAllSearch?: boolean): ExternalVariable { + getVariableForExportName(name: string): ExternalVariable { if (name === '*') { this.exportsNamespace = true; } else if (name !== 'default') { diff --git a/src/ast/ExecutionContext.ts b/src/ast/ExecutionContext.ts index fea50d7d402..51df0766fcf 100644 --- a/src/ast/ExecutionContext.ts +++ b/src/ast/ExecutionContext.ts @@ -8,9 +8,20 @@ interface ExecutionContextIgnore { returnAwaitYield: boolean; } +export enum BreakFlowType { + None = 0, + BreakContinue, + Return, + Error +} + +export interface BreakFlow { + label: string | null; + type: BreakFlowType; +} + export interface ExecutionContext { - // TODO Lukas remove - TODO?: boolean; + breakFlow: false | BreakFlow; } export interface EffectsExecutionContext extends ExecutionContext { @@ -23,13 +34,16 @@ export interface EffectsExecutionContext extends ExecutionContext { } export function createExecutionContext(): ExecutionContext { - return {}; + return { + breakFlow: false + }; } export function createEffectsExecutionContext(): EffectsExecutionContext { return { accessed: new PathTracker(), assigned: new PathTracker(), + breakFlow: false, called: new PathTracker(), ignore: { breakStatements: false, diff --git a/src/ast/nodes/ArrayPattern.ts b/src/ast/nodes/ArrayPattern.ts index 7a8d33d2164..b367e049a0a 100644 --- a/src/ast/nodes/ArrayPattern.ts +++ b/src/ast/nodes/ArrayPattern.ts @@ -3,7 +3,6 @@ import { EMPTY_PATH, ObjectPath } from '../utils/PathTracker'; import { UNKNOWN_EXPRESSION } from '../values'; import Variable from '../variables/Variable'; import * as NodeType from './NodeType'; -import { ExpressionEntity } from './shared/Expression'; import { NodeBase } from './shared/Node'; import { PatternNode } from './shared/Pattern'; @@ -19,7 +18,7 @@ export default class ArrayPattern extends NodeBase implements PatternNode { } } - declare(kind: string, _init: ExpressionEntity) { + declare(kind: string) { const variables = []; for (const element of this.elements) { if (element !== null) { diff --git a/src/ast/nodes/ArrowFunctionExpression.ts b/src/ast/nodes/ArrowFunctionExpression.ts index 325fcbdc843..9915bca81d2 100644 --- a/src/ast/nodes/ArrowFunctionExpression.ts +++ b/src/ast/nodes/ArrowFunctionExpression.ts @@ -35,15 +35,15 @@ export default class ArrowFunctionExpression extends NodeBase { return path.length === 0 ? this.scope.getReturnExpression() : UNKNOWN_EXPRESSION; } - hasEffects(_context: ExecutionContext) { + hasEffects() { return false; } - hasEffectsWhenAccessedAtPath(path: ObjectPath, _context: ExecutionContext) { + hasEffectsWhenAccessedAtPath(path: ObjectPath) { return path.length > 1; } - hasEffectsWhenAssignedAtPath(path: ObjectPath, _context: ExecutionContext) { + hasEffectsWhenAssignedAtPath(path: ObjectPath) { return path.length > 1; } @@ -69,7 +69,10 @@ export default class ArrowFunctionExpression extends NodeBase { include(includeChildrenRecursively: IncludeChildren, context: ExecutionContext) { this.included = true; + const breakFlow = context.breakFlow; + context.breakFlow = false; this.body.include(includeChildrenRecursively, context); + context.breakFlow = breakFlow; for (const param of this.params) { if (!(param instanceof Identifier)) { param.include(includeChildrenRecursively, context); diff --git a/src/ast/nodes/BinaryExpression.ts b/src/ast/nodes/BinaryExpression.ts index a71be3881a7..05c5d33f670 100644 --- a/src/ast/nodes/BinaryExpression.ts +++ b/src/ast/nodes/BinaryExpression.ts @@ -1,5 +1,5 @@ import { DeoptimizableEntity } from '../DeoptimizableEntity'; -import { EffectsExecutionContext, ExecutionContext } from '../ExecutionContext'; +import { EffectsExecutionContext } from '../ExecutionContext'; import { EMPTY_IMMUTABLE_TRACKER, EMPTY_PATH, ObjectPath, PathTracker } from '../utils/PathTracker'; import { LiteralValueOrUnknown, UnknownValue } from '../values'; import ExpressionStatement from './ExpressionStatement'; @@ -74,7 +74,7 @@ export default class BinaryExpression extends NodeBase implements DeoptimizableE return super.hasEffects(context); } - hasEffectsWhenAccessedAtPath(path: ObjectPath, _context: ExecutionContext) { + hasEffectsWhenAccessedAtPath(path: ObjectPath) { return path.length > 1; } } diff --git a/src/ast/nodes/ForInStatement.ts b/src/ast/nodes/ForInStatement.ts index dab9dc85f43..49af31e6061 100644 --- a/src/ast/nodes/ForInStatement.ts +++ b/src/ast/nodes/ForInStatement.ts @@ -49,6 +49,7 @@ export default class ForInStatement extends StatementBase { this.left.deoptimizePath(EMPTY_PATH); this.right.include(includeChildrenRecursively, context); this.body.include(includeChildrenRecursively, context); + context.breakFlow = false; } render(code: MagicString, options: RenderOptions) { diff --git a/src/ast/nodes/ForOfStatement.ts b/src/ast/nodes/ForOfStatement.ts index 9ad7b4ea08b..65b1d5b0ade 100644 --- a/src/ast/nodes/ForOfStatement.ts +++ b/src/ast/nodes/ForOfStatement.ts @@ -38,6 +38,7 @@ export default class ForOfStatement extends StatementBase { this.left.deoptimizePath(EMPTY_PATH); this.right.include(includeChildrenRecursively, context); this.body.include(includeChildrenRecursively, context); + context.breakFlow = false; } render(code: MagicString, options: RenderOptions) { diff --git a/src/ast/nodes/ForStatement.ts b/src/ast/nodes/ForStatement.ts index 7b4687f1939..bc926760549 100644 --- a/src/ast/nodes/ForStatement.ts +++ b/src/ast/nodes/ForStatement.ts @@ -1,10 +1,10 @@ import MagicString from 'magic-string'; import { NO_SEMICOLON, RenderOptions } from '../../utils/renderHelpers'; -import { EffectsExecutionContext } from '../ExecutionContext'; +import { EffectsExecutionContext, ExecutionContext } from '../ExecutionContext'; import BlockScope from '../scopes/BlockScope'; import Scope from '../scopes/Scope'; import * as NodeType from './NodeType'; -import { ExpressionNode, StatementBase, StatementNode } from './shared/Node'; +import { ExpressionNode, IncludeChildren, StatementBase, StatementNode } from './shared/Node'; import VariableDeclaration from './VariableDeclaration'; export default class ForStatement extends StatementBase { @@ -34,6 +34,15 @@ export default class ForStatement extends StatementBase { return false; } + include(includeChildrenRecursively: IncludeChildren, context: ExecutionContext) { + this.included = true; + if (this.init) this.init.include(includeChildrenRecursively, context); + if (this.test) this.test.include(includeChildrenRecursively, context); + if (this.update) this.update.include(includeChildrenRecursively, context); + if (this.body) this.body.include(includeChildrenRecursively, context); + context.breakFlow = false; + } + render(code: MagicString, options: RenderOptions) { if (this.init) this.init.render(code, options, NO_SEMICOLON); if (this.test) this.test.render(code, options, NO_SEMICOLON); diff --git a/src/ast/nodes/Identifier.ts b/src/ast/nodes/Identifier.ts index c01d45a829a..8ed212e902e 100644 --- a/src/ast/nodes/Identifier.ts +++ b/src/ast/nodes/Identifier.ts @@ -4,7 +4,7 @@ import { BLANK } from '../../utils/blank'; import { NodeRenderOptions, RenderOptions } from '../../utils/renderHelpers'; import CallOptions from '../CallOptions'; import { DeoptimizableEntity } from '../DeoptimizableEntity'; -import { ExecutionContext } from '../ExecutionContext'; +import { EffectsExecutionContext } from '../ExecutionContext'; import FunctionScope from '../scopes/FunctionScope'; import { EMPTY_PATH, ObjectPath, PathTracker } from '../utils/PathTracker'; import { LiteralValueOrUnknown } from '../values'; @@ -109,18 +109,18 @@ export default class Identifier extends NodeBase implements PatternNode { ); } - hasEffectsWhenAccessedAtPath(path: ObjectPath, context: ExecutionContext): boolean { + hasEffectsWhenAccessedAtPath(path: ObjectPath, context: EffectsExecutionContext): boolean { return this.variable !== null && this.variable.hasEffectsWhenAccessedAtPath(path, context); } - hasEffectsWhenAssignedAtPath(path: ObjectPath, context: ExecutionContext): boolean { + hasEffectsWhenAssignedAtPath(path: ObjectPath, context: EffectsExecutionContext): boolean { return !this.variable || this.variable.hasEffectsWhenAssignedAtPath(path, context); } hasEffectsWhenCalledAtPath( path: ObjectPath, callOptions: CallOptions, - context: ExecutionContext + context: EffectsExecutionContext ) { return !this.variable || this.variable.hasEffectsWhenCalledAtPath(path, callOptions, context); } diff --git a/src/ast/nodes/IfStatement.ts b/src/ast/nodes/IfStatement.ts index 3e2d720605a..2d954942857 100644 --- a/src/ast/nodes/IfStatement.ts +++ b/src/ast/nodes/IfStatement.ts @@ -2,7 +2,7 @@ import MagicString from 'magic-string'; import { RenderOptions } from '../../utils/renderHelpers'; import { removeAnnotations } from '../../utils/treeshakeNode'; import { DeoptimizableEntity } from '../DeoptimizableEntity'; -import { EffectsExecutionContext, ExecutionContext } from '../ExecutionContext'; +import { BreakFlow, EffectsExecutionContext, ExecutionContext } from '../ExecutionContext'; import { EMPTY_IMMUTABLE_TRACKER, EMPTY_PATH } from '../utils/PathTracker'; import { LiteralValueOrUnknown, UnknownValue } from '../values'; import * as NodeType from './NodeType'; @@ -43,6 +43,8 @@ export default class IfStatement extends StatementBase implements DeoptimizableE : this.alternate !== null && this.alternate.hasEffects(context); } + // TODO Lukas simplify type for BreakFlow to Symbol or Set of labels + // TODO Lukas change logic to handle unknown test separately include(includeChildrenRecursively: IncludeChildren, context: ExecutionContext) { this.included = true; if (includeChildrenRecursively) { @@ -60,12 +62,20 @@ export default class IfStatement extends StatementBase implements DeoptimizableE if ((hasUnknownTest || this.testValue) && this.consequent.shouldBeIncluded(context)) { this.consequent.include(false, context); } + let consequentBreakFlow: BreakFlow | false = false; + if (hasUnknownTest) { + consequentBreakFlow = context.breakFlow; + context.breakFlow = false; + } if ( this.alternate !== null && ((hasUnknownTest || !this.testValue) && this.alternate.shouldBeIncluded(context)) ) { this.alternate.include(false, context); } + if (hasUnknownTest && !consequentBreakFlow) { + context.breakFlow = false; + } } render(code: MagicString, options: RenderOptions) { diff --git a/src/ast/nodes/Literal.ts b/src/ast/nodes/Literal.ts index 7e3840eac58..6aabba859c0 100644 --- a/src/ast/nodes/Literal.ts +++ b/src/ast/nodes/Literal.ts @@ -1,5 +1,4 @@ import MagicString from 'magic-string'; -import { RenderOptions } from '../../utils/renderHelpers'; import CallOptions from '../CallOptions'; import { EffectsExecutionContext } from '../ExecutionContext'; import { ObjectPath } from '../utils/PathTracker'; @@ -68,7 +67,7 @@ export default class Literal extends NodeBase { this.members = getLiteralMembersForValue(this.value); } - render(code: MagicString, _options: RenderOptions) { + render(code: MagicString) { if (typeof this.value === 'string') { (code.indentExclusionRanges as [number, number][]).push([this.start + 1, this.end - 1]); } diff --git a/src/ast/nodes/NewExpression.ts b/src/ast/nodes/NewExpression.ts index 08cb5d8f59e..91cf5b2c03d 100644 --- a/src/ast/nodes/NewExpression.ts +++ b/src/ast/nodes/NewExpression.ts @@ -1,5 +1,5 @@ import CallOptions from '../CallOptions'; -import { EffectsExecutionContext, ExecutionContext } from '../ExecutionContext'; +import { EffectsExecutionContext } from '../ExecutionContext'; import { EMPTY_PATH, ObjectPath, UNKNOWN_PATH } from '../utils/PathTracker'; import * as NodeType from './NodeType'; import { ExpressionNode, NodeBase } from './shared/Node'; @@ -28,7 +28,7 @@ export default class NewExpression extends NodeBase { return this.callee.hasEffectsWhenCalledAtPath(EMPTY_PATH, this.callOptions, context); } - hasEffectsWhenAccessedAtPath(path: ObjectPath, _context: ExecutionContext) { + hasEffectsWhenAccessedAtPath(path: ObjectPath) { return path.length > 1; } diff --git a/src/ast/nodes/SwitchCase.ts b/src/ast/nodes/SwitchCase.ts index 60100bd787b..9d2aa50624b 100644 --- a/src/ast/nodes/SwitchCase.ts +++ b/src/ast/nodes/SwitchCase.ts @@ -20,6 +20,7 @@ export default class SwitchCase extends NodeBase { if (includeChildrenRecursively || node.shouldBeIncluded(context)) node.include(includeChildrenRecursively, context); } + context.breakFlow = false; } render(code: MagicString, options: RenderOptions) { diff --git a/src/ast/nodes/TemplateElement.ts b/src/ast/nodes/TemplateElement.ts index 1a44715520e..800062a4e25 100644 --- a/src/ast/nodes/TemplateElement.ts +++ b/src/ast/nodes/TemplateElement.ts @@ -1,4 +1,3 @@ -import { ExecutionContext } from '../ExecutionContext'; import * as NodeType from './NodeType'; import { NodeBase } from './shared/Node'; @@ -10,7 +9,7 @@ export default class TemplateElement extends NodeBase { raw: string; }; - hasEffects(_context: ExecutionContext) { + hasEffects() { return false; } } diff --git a/src/ast/nodes/ThisExpression.ts b/src/ast/nodes/ThisExpression.ts index efdd4b66b3f..cbb6761aa05 100644 --- a/src/ast/nodes/ThisExpression.ts +++ b/src/ast/nodes/ThisExpression.ts @@ -1,5 +1,4 @@ import MagicString from 'magic-string'; -import { RenderOptions } from '../../utils/renderHelpers'; import { EffectsExecutionContext } from '../ExecutionContext'; import ModuleScope from '../scopes/ModuleScope'; import { ObjectPath } from '../utils/PathTracker'; @@ -41,7 +40,7 @@ export default class ThisExpression extends NodeBase { } } - render(code: MagicString, _options: RenderOptions) { + render(code: MagicString) { if (this.alias !== null) { code.overwrite(this.start, this.end, this.alias, { contentOnly: false, diff --git a/src/ast/nodes/ThrowStatement.ts b/src/ast/nodes/ThrowStatement.ts index dc786795e29..1fa8b2668b8 100644 --- a/src/ast/nodes/ThrowStatement.ts +++ b/src/ast/nodes/ThrowStatement.ts @@ -1,6 +1,6 @@ import MagicString from 'magic-string'; import { RenderOptions } from '../../utils/renderHelpers'; -import { ExecutionContext } from '../ExecutionContext'; +import { BreakFlowType, ExecutionContext } from '../ExecutionContext'; import * as NodeType from './NodeType'; import { ExpressionNode, StatementBase } from './shared/Node'; @@ -8,11 +8,17 @@ export default class ThrowStatement extends StatementBase { argument!: ExpressionNode; type!: NodeType.tThrowStatement; - hasEffects(_context: ExecutionContext) { + hasEffects() { return true; } render(code: MagicString, options: RenderOptions) { this.argument.render(code, options, { preventASI: true }); } + + shouldBeIncluded(context: ExecutionContext): boolean { + if (context.breakFlow) return false; + context.breakFlow = { type: BreakFlowType.Error, label: null }; + return true; + } } diff --git a/src/ast/nodes/TryStatement.ts b/src/ast/nodes/TryStatement.ts index 7ae90e21867..2dd70b97170 100644 --- a/src/ast/nodes/TryStatement.ts +++ b/src/ast/nodes/TryStatement.ts @@ -28,9 +28,11 @@ export default class TryStatement extends StatementBase { this.context.tryCatchDeoptimization ? INCLUDE_PARAMETERS : includeChildrenRecursively, context ); + context.breakFlow = false; } if (this.handler !== null) { this.handler.include(includeChildrenRecursively, context); + context.breakFlow = false; } if (this.finalizer !== null) { this.finalizer.include(includeChildrenRecursively, context); diff --git a/src/ast/nodes/UnknownNode.ts b/src/ast/nodes/UnknownNode.ts index c08c96b81bd..945e279cfc1 100644 --- a/src/ast/nodes/UnknownNode.ts +++ b/src/ast/nodes/UnknownNode.ts @@ -2,7 +2,7 @@ import { ExecutionContext } from '../ExecutionContext'; import { IncludeChildren, NodeBase } from './shared/Node'; export default class UnknownNode extends NodeBase { - hasEffects(_context: ExecutionContext) { + hasEffects() { return true; } diff --git a/src/ast/nodes/VariableDeclaration.ts b/src/ast/nodes/VariableDeclaration.ts index f3c52804ec0..0d62722eb51 100644 --- a/src/ast/nodes/VariableDeclaration.ts +++ b/src/ast/nodes/VariableDeclaration.ts @@ -7,7 +7,7 @@ import { } from '../../utils/renderHelpers'; import { getSystemExportStatement } from '../../utils/systemJsRendering'; import { ExecutionContext } from '../ExecutionContext'; -import { EMPTY_PATH, ObjectPath } from '../utils/PathTracker'; +import { EMPTY_PATH } from '../utils/PathTracker'; import Variable from '../variables/Variable'; import Identifier, { IdentifierWithVariable } from './Identifier'; import * as NodeType from './NodeType'; @@ -37,13 +37,13 @@ export default class VariableDeclaration extends NodeBase { kind!: 'var' | 'let' | 'const'; type!: NodeType.tVariableDeclaration; - deoptimizePath(_path: ObjectPath) { + deoptimizePath() { for (const declarator of this.declarations) { declarator.deoptimizePath(EMPTY_PATH); } } - hasEffectsWhenAssignedAtPath(_path: ObjectPath, _context: ExecutionContext) { + hasEffectsWhenAssignedAtPath() { return false; } diff --git a/src/ast/nodes/WhileStatement.ts b/src/ast/nodes/WhileStatement.ts index da81690ce86..183a59a1d35 100644 --- a/src/ast/nodes/WhileStatement.ts +++ b/src/ast/nodes/WhileStatement.ts @@ -1,6 +1,6 @@ -import { EffectsExecutionContext } from '../ExecutionContext'; +import { EffectsExecutionContext, ExecutionContext } from '../ExecutionContext'; import * as NodeType from './NodeType'; -import { ExpressionNode, StatementBase, StatementNode } from './shared/Node'; +import { ExpressionNode, IncludeChildren, StatementBase, StatementNode } from './shared/Node'; export default class WhileStatement extends StatementBase { body!: StatementNode; @@ -17,4 +17,9 @@ export default class WhileStatement extends StatementBase { context.ignore.breakStatements = breakStatements; return false; } + + include(includeChildrenRecursively: IncludeChildren, context: ExecutionContext) { + super.include(includeChildrenRecursively, context); + context.breakFlow = false; + } } diff --git a/src/ast/nodes/shared/ClassNode.ts b/src/ast/nodes/shared/ClassNode.ts index d8865d4bc6c..fd5f5cc73cb 100644 --- a/src/ast/nodes/shared/ClassNode.ts +++ b/src/ast/nodes/shared/ClassNode.ts @@ -1,5 +1,5 @@ import CallOptions from '../../CallOptions'; -import { EffectsExecutionContext, ExecutionContext } from '../../ExecutionContext'; +import { EffectsExecutionContext } from '../../ExecutionContext'; import ChildScope from '../../scopes/ChildScope'; import Scope from '../../scopes/Scope'; import { ObjectPath } from '../../utils/PathTracker'; @@ -16,11 +16,11 @@ export default class ClassNode extends NodeBase { this.scope = new ChildScope(parentScope); } - hasEffectsWhenAccessedAtPath(path: ObjectPath, _context: ExecutionContext) { + hasEffectsWhenAccessedAtPath(path: ObjectPath) { return path.length > 1; } - hasEffectsWhenAssignedAtPath(path: ObjectPath, _context: ExecutionContext) { + hasEffectsWhenAssignedAtPath(path: ObjectPath) { return path.length > 1; } diff --git a/src/ast/nodes/shared/FunctionNode.ts b/src/ast/nodes/shared/FunctionNode.ts index a93c88899e0..cb5834caeff 100644 --- a/src/ast/nodes/shared/FunctionNode.ts +++ b/src/ast/nodes/shared/FunctionNode.ts @@ -90,7 +90,10 @@ export default class FunctionNode extends NodeBase { include(includeChildrenRecursively: IncludeChildren, context: ExecutionContext) { this.included = true; + const breakFlow = context.breakFlow; + context.breakFlow = false; this.body.include(includeChildrenRecursively, context); + context.breakFlow = breakFlow; if (this.id) { this.id.include(); } diff --git a/src/ast/nodes/shared/Node.ts b/src/ast/nodes/shared/Node.ts index 5131e88c596..d7afed326ea 100644 --- a/src/ast/nodes/shared/Node.ts +++ b/src/ast/nodes/shared/Node.ts @@ -277,8 +277,10 @@ export class NodeBase implements ExpressionNode { } } - shouldBeIncluded(_context: ExecutionContext): boolean { - return this.included || this.hasEffects(createEffectsExecutionContext()); + shouldBeIncluded(context: ExecutionContext): boolean { + return ( + this.included || (!context.breakFlow && this.hasEffects(createEffectsExecutionContext())) + ); } toString() { diff --git a/src/ast/variables/Variable.ts b/src/ast/variables/Variable.ts index a34a5ed7033..1d7ea8e6398 100644 --- a/src/ast/variables/Variable.ts +++ b/src/ast/variables/Variable.ts @@ -2,7 +2,7 @@ import ExternalModule from '../../ExternalModule'; import Module from '../../Module'; import CallOptions from '../CallOptions'; import { DeoptimizableEntity } from '../DeoptimizableEntity'; -import { createExecutionContext, ExecutionContext } from '../ExecutionContext'; +import { createExecutionContext, EffectsExecutionContext } from '../ExecutionContext'; import Identifier from '../nodes/Identifier'; import { ExpressionEntity } from '../nodes/shared/Expression'; import { ExpressionNode } from '../nodes/shared/Node'; @@ -60,18 +60,18 @@ export default class Variable implements ExpressionEntity { return UNKNOWN_EXPRESSION; } - hasEffectsWhenAccessedAtPath(path: ObjectPath, _context: ExecutionContext) { + hasEffectsWhenAccessedAtPath(path: ObjectPath, _context: EffectsExecutionContext) { return path.length > 0; } - hasEffectsWhenAssignedAtPath(_path: ObjectPath, _context: ExecutionContext) { + hasEffectsWhenAssignedAtPath(_path: ObjectPath, _context: EffectsExecutionContext) { return true; } hasEffectsWhenCalledAtPath( _path: ObjectPath, _callOptions: CallOptions, - _context: ExecutionContext + _context: EffectsExecutionContext ) { return true; } diff --git a/test/form/samples/break-control-flow/caught-errors/_config.js b/test/form/samples/break-control-flow/caught-errors/_config.js new file mode 100644 index 00000000000..618e294de16 --- /dev/null +++ b/test/form/samples/break-control-flow/caught-errors/_config.js @@ -0,0 +1,6 @@ +module.exports = { + description: 'breaks control flow when an error is thrown inside a catch block', + options: { + treeshake: { tryCatchDeoptimization: false } + } +}; diff --git a/test/form/samples/break-control-flow/caught-errors/_expected.js b/test/form/samples/break-control-flow/caught-errors/_expected.js new file mode 100644 index 00000000000..9a4f52330c8 --- /dev/null +++ b/test/form/samples/break-control-flow/caught-errors/_expected.js @@ -0,0 +1,45 @@ +function errorTry() { + try { + throw new Error('Break'); + } catch { + console.log('retained'); + } finally { + console.log('retained'); + } + + console.log('retained'); +} + +try { + errorTry(); +} catch {} + +function errorCatch() { + try { + console.log('retained'); + } catch { + throw new Error('Break'); + } finally { + console.log('retained'); + } + + console.log('retained'); +} + +try { + errorCatch(); +} catch {} + +function errorFinally() { + try { + console.log('retained'); + } catch { + console.log('retained'); + } finally { + throw new Error('Break'); + } +} + +try { + errorFinally(); +} catch {} diff --git a/test/form/samples/break-control-flow/caught-errors/main.js b/test/form/samples/break-control-flow/caught-errors/main.js new file mode 100644 index 00000000000..cb0b8ec3009 --- /dev/null +++ b/test/form/samples/break-control-flow/caught-errors/main.js @@ -0,0 +1,50 @@ +function errorTry() { + try { + throw new Error('Break'); + console.log('removed'); + } catch { + console.log('retained'); + } finally { + console.log('retained'); + } + + console.log('retained'); +} + +try { + errorTry(); +} catch {} + +function errorCatch() { + try { + console.log('retained'); + } catch { + throw new Error('Break'); + console.log('removed'); + } finally { + console.log('retained'); + } + + console.log('retained'); +} + +try { + errorCatch(); +} catch {} + +function errorFinally() { + try { + console.log('retained'); + } catch { + console.log('retained'); + } finally { + throw new Error('Break'); + console.log('removed'); + } + + console.log('removed'); +} + +try { + errorFinally(); +} catch {} diff --git a/test/form/samples/break-control-flow/hoisted-declarations/_config.js b/test/form/samples/break-control-flow/hoisted-declarations/_config.js new file mode 100644 index 00000000000..6fddb43932a --- /dev/null +++ b/test/form/samples/break-control-flow/hoisted-declarations/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'includes hoisted declarations when control flow is broken' +}; diff --git a/test/form/samples/break-control-flow/hoisted-declarations/_expected.js b/test/form/samples/break-control-flow/hoisted-declarations/_expected.js new file mode 100644 index 00000000000..e9a2fe30c30 --- /dev/null +++ b/test/form/samples/break-control-flow/hoisted-declarations/_expected.js @@ -0,0 +1,21 @@ +try { + nested(); +} catch {} + +function nested() { + hoisted(); + + throw new Error(); + + function hoisted() { + console.log('included'); + } +} + +hoisted(); + +throw new Error(); + +function hoisted() { + console.log('included'); +} diff --git a/test/form/samples/break-control-flow/hoisted-declarations/main.js b/test/form/samples/break-control-flow/hoisted-declarations/main.js new file mode 100644 index 00000000000..97b19312169 --- /dev/null +++ b/test/form/samples/break-control-flow/hoisted-declarations/main.js @@ -0,0 +1,29 @@ +try { + nested(); +} catch {} + +function nested() { + hoisted(); + + throw new Error(); + + console.log('removed'); + + function hoisted() { + console.log('included'); + } + + console.log('removed'); +} + +hoisted(); + +throw new Error(); + +console.log('removed'); + +function hoisted() { + console.log('included'); +} + +console.log('removed'); diff --git a/test/form/samples/break-control-flow/if-statement-errors/_config.js b/test/form/samples/break-control-flow/if-statement-errors/_config.js new file mode 100644 index 00000000000..5b83386fecd --- /dev/null +++ b/test/form/samples/break-control-flow/if-statement-errors/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'handles conditionally thrown errors' +}; diff --git a/test/form/samples/break-control-flow/if-statement-errors/_expected.js b/test/form/samples/break-control-flow/if-statement-errors/_expected.js new file mode 100644 index 00000000000..aed68ea2b61 --- /dev/null +++ b/test/form/samples/break-control-flow/if-statement-errors/_expected.js @@ -0,0 +1,90 @@ +function unknownValueConsequent() { + if (globalThis.unknownValue) { + throw new Error(); + } else { + console.log('retained'); + } + console.log('retained'); +} + +try { + unknownValueConsequent(); +} catch {} + +function unknownValueOnlyConsequent() { + if (globalThis.unknownValue) { + throw new Error(); + } + console.log('retained'); +} + +try { + unknownValueOnlyConsequent(); +} catch {} + +function unknownValueAlternate() { + if (globalThis.unknownValue) { + console.log('retained'); + } else { + throw new Error(); + } + console.log('retained'); +} + +try { + unknownValueAlternate(); +} catch {} + +function unknownValueBoth() { + if (globalThis.unknownValue) { + throw new Error(); + } else { + throw new Error(); + } +} + +try { + unknownValueBoth(); +} catch {} + +function truthyValueConsequent() { + { + throw new Error(); + } +} + +try { + truthyValueConsequent(); +} catch {} + +function truthyValueAlternate() { + { + console.log('retained'); + } + console.log('retained'); +} + +try { + truthyValueAlternate(); +} catch {} + +function falsyValueConsequent() { + { + console.log('retained'); + } + console.log('retained'); +} + +try { + falsyValueConsequent(); +} catch {} + +function falsyValueAlternate() { + { + throw new Error(); + } +} + +try { + falsyValueAlternate(); +} catch {} diff --git a/test/form/samples/break-control-flow/if-statement-errors/main.js b/test/form/samples/break-control-flow/if-statement-errors/main.js new file mode 100644 index 00000000000..4ba50d20d9d --- /dev/null +++ b/test/form/samples/break-control-flow/if-statement-errors/main.js @@ -0,0 +1,108 @@ +function unknownValueConsequent() { + if (globalThis.unknownValue) { + throw new Error(); + console.log('removed'); + } else { + console.log('retained'); + } + console.log('retained'); +} + +try { + unknownValueConsequent(); +} catch {} + +function unknownValueOnlyConsequent() { + if (globalThis.unknownValue) { + throw new Error(); + console.log('removed'); + } + console.log('retained'); +} + +try { + unknownValueOnlyConsequent(); +} catch {} + +function unknownValueAlternate() { + if (globalThis.unknownValue) { + console.log('retained'); + } else { + throw new Error(); + console.log('removed'); + } + console.log('retained'); +} + +try { + unknownValueAlternate(); +} catch {} + +function unknownValueBoth() { + if (globalThis.unknownValue) { + throw new Error(); + console.log('removed'); + } else { + throw new Error(); + console.log('removed'); + } + console.log('removed'); +} + +try { + unknownValueBoth(); +} catch {} + +function truthyValueConsequent() { + if (true) { + throw new Error(); + console.log('removed'); + } else { + console.log('removed'); + } + console.log('removed'); +} + +try { + truthyValueConsequent(); +} catch {} + +function truthyValueAlternate() { + if (true) { + console.log('retained'); + } else { + throw new Error(); + } + console.log('retained'); +} + +try { + truthyValueAlternate(); +} catch {} + +function falsyValueConsequent() { + if (false) { + throw new Error(); + } else { + console.log('retained'); + } + console.log('retained'); +} + +try { + falsyValueConsequent(); +} catch {} + +function falsyValueAlternate() { + if (false) { + console.log('removed'); + } else { + throw new Error(); + console.log('removed'); + } + console.log('removed'); +} + +try { + falsyValueAlternate(); +} catch {} diff --git a/test/form/samples/break-control-flow/loop-errors/_config.js b/test/form/samples/break-control-flow/loop-errors/_config.js new file mode 100644 index 00000000000..bd5c24a0782 --- /dev/null +++ b/test/form/samples/break-control-flow/loop-errors/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'does not break flow from inside loops that may not have executed' +}; diff --git a/test/form/samples/break-control-flow/loop-errors/_expected.js b/test/form/samples/break-control-flow/loop-errors/_expected.js new file mode 100644 index 00000000000..7207d1df48d --- /dev/null +++ b/test/form/samples/break-control-flow/loop-errors/_expected.js @@ -0,0 +1,53 @@ +function whileLoop() { + while (globalThis.unknown) { + throw new Error(); + } + console.log('retained'); +} + +try { + whileLoop(); +} catch {} + +function doWhileLoop() { + do { + throw new Error(); + } while (globalThis.unknown); +} + +try { + doWhileLoop(); +} catch {} + +function forLoop() { + for (let i = 0; i < globalThis.unknown; i++) { + throw new Error(); + } + console.log('retained'); +} + +try { + forLoop(); +} catch {} + +function forOfLoop() { + for (const foo of globalThis.unknown) { + throw new Error(); + } + console.log('retained'); +} + +try { + forOfLoop(); +} catch {} + +function forInLoop() { + for (const foo in globalThis.unknown) { + throw new Error(); + } + console.log('retained'); +} + +try { + forInLoop(); +} catch {} diff --git a/test/form/samples/break-control-flow/loop-errors/main.js b/test/form/samples/break-control-flow/loop-errors/main.js new file mode 100644 index 00000000000..faea99774fd --- /dev/null +++ b/test/form/samples/break-control-flow/loop-errors/main.js @@ -0,0 +1,59 @@ +function whileLoop() { + while (globalThis.unknown) { + throw new Error(); + console.log('removed'); + } + console.log('retained'); +} + +try { + whileLoop(); +} catch {} + +function doWhileLoop() { + do { + throw new Error(); + console.log('removed'); + } while (globalThis.unknown); + console.log('removed'); +} + +try { + doWhileLoop(); +} catch {} + +function forLoop() { + for (let i = 0; i < globalThis.unknown; i++) { + throw new Error(); + console.log('removed'); + } + console.log('retained'); +} + +try { + forLoop(); +} catch {} + +function forOfLoop() { + for (const foo of globalThis.unknown) { + throw new Error(); + console.log('removed'); + } + console.log('retained'); +} + +try { + forOfLoop(); +} catch {} + +function forInLoop() { + for (const foo in globalThis.unknown) { + throw new Error(); + console.log('removed'); + } + console.log('retained'); +} + +try { + forInLoop(); +} catch {} diff --git a/test/form/samples/break-control-flow/switch-errors/_config.js b/test/form/samples/break-control-flow/switch-errors/_config.js new file mode 100644 index 00000000000..441dcd44f44 --- /dev/null +++ b/test/form/samples/break-control-flow/switch-errors/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'handles errors in switch statements' +}; diff --git a/test/form/samples/break-control-flow/switch-errors/_expected.js b/test/form/samples/break-control-flow/switch-errors/_expected.js new file mode 100644 index 00000000000..4ea781f39d0 --- /dev/null +++ b/test/form/samples/break-control-flow/switch-errors/_expected.js @@ -0,0 +1,13 @@ +switch (globalThis.unknown) { + case 1: + throw new Error(); + + case 2: + throw new Error(); + + case 3: + console.log('retained'); + default: + console.log('retained'); +} +console.log('retained'); diff --git a/test/form/samples/break-control-flow/switch-errors/main.js b/test/form/samples/break-control-flow/switch-errors/main.js new file mode 100644 index 00000000000..0b072c19bc2 --- /dev/null +++ b/test/form/samples/break-control-flow/switch-errors/main.js @@ -0,0 +1,13 @@ +switch (globalThis.unknown) { + case 1: + throw new Error(); + console.log('removed'); + case 2: + throw new Error(); + console.log('removed'); + case 3: + console.log('retained'); + default: + console.log('retained'); +} +console.log('retained'); diff --git a/test/form/samples/break-control-flow/thrown-errors/_config.js b/test/form/samples/break-control-flow/thrown-errors/_config.js new file mode 100644 index 00000000000..6ce1e181f39 --- /dev/null +++ b/test/form/samples/break-control-flow/thrown-errors/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'breaks control flow when an error is thrown' +}; diff --git a/test/form/samples/break-control-flow/thrown-errors/_expected.js b/test/form/samples/break-control-flow/thrown-errors/_expected.js new file mode 100644 index 00000000000..cb39bb4141f --- /dev/null +++ b/test/form/samples/break-control-flow/thrown-errors/_expected.js @@ -0,0 +1,37 @@ +function brokenFunction() { + console.log('start'); + throw new Error(); +} + +try { + brokenFunction(); +} catch {} + +const brokenFunctionExpression = function() { + console.log('start'); + throw new Error(); +}; + +try { + brokenFunctionExpression(); +} catch {} + +const brokenArrow = () => { + console.log('start'); + throw new Error(); +}; + +try { + brokenArrow(); +} catch {} + +function brokenFunction2() { + console.log('start'); + throw new Error(); +} + +try { + brokenFunction2(); +} catch {} + +throw new Error(); diff --git a/test/form/samples/break-control-flow/thrown-errors/main.js b/test/form/samples/break-control-flow/thrown-errors/main.js new file mode 100644 index 00000000000..9b366207399 --- /dev/null +++ b/test/form/samples/break-control-flow/thrown-errors/main.js @@ -0,0 +1,47 @@ +function brokenFunction() { + console.log('start'); + throw new Error(); + console.log('removed'); + throw new Error('removed'); +} + +try { + brokenFunction(); +} catch {} + +const brokenFunctionExpression = function() { + console.log('start'); + throw new Error(); + console.log('removed'); + throw new Error('removed'); +}; + +try { + brokenFunctionExpression(); +} catch {} + +const brokenArrow = () => { + console.log('start'); + throw new Error(); + console.log('removed'); + throw new Error('removed'); +}; + +try { + brokenArrow(); +} catch {} + +function brokenFunction2() { + console.log('start'); + throw new Error(); + console.log('removed'); + throw new Error('removed'); +} + +try { + brokenFunction2(); +} catch {} + +throw new Error(); +console.log('removed'); +throw new Error('removed'); From 17be1a4f9e4a5c6ca062ed1f9f5c3110ba1a3167 Mon Sep 17 00:00:00 2001 From: Lukas Taegert-Atkinson Date: Sun, 6 Oct 2019 09:23:25 +0200 Subject: [PATCH 14/35] Make if-statement logic more clear --- src/ast/nodes/IfStatement.ts | 45 ++++++++++++++++++------------------ 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/src/ast/nodes/IfStatement.ts b/src/ast/nodes/IfStatement.ts index 2d954942857..3697501721f 100644 --- a/src/ast/nodes/IfStatement.ts +++ b/src/ast/nodes/IfStatement.ts @@ -44,7 +44,6 @@ export default class IfStatement extends StatementBase implements DeoptimizableE } // TODO Lukas simplify type for BreakFlow to Symbol or Set of labels - // TODO Lukas change logic to handle unknown test separately include(includeChildrenRecursively: IncludeChildren, context: ExecutionContext) { this.included = true; if (includeChildrenRecursively) { @@ -53,28 +52,30 @@ export default class IfStatement extends StatementBase implements DeoptimizableE if (this.alternate !== null) { this.alternate.include(includeChildrenRecursively, context); } - return; - } - const hasUnknownTest = this.testValue === UnknownValue; - if (hasUnknownTest || this.test.shouldBeIncluded(context)) { + } else if (this.testValue === UnknownValue) { this.test.include(false, context); - } - if ((hasUnknownTest || this.testValue) && this.consequent.shouldBeIncluded(context)) { - this.consequent.include(false, context); - } - let consequentBreakFlow: BreakFlow | false = false; - if (hasUnknownTest) { - consequentBreakFlow = context.breakFlow; - context.breakFlow = false; - } - if ( - this.alternate !== null && - ((hasUnknownTest || !this.testValue) && this.alternate.shouldBeIncluded(context)) - ) { - this.alternate.include(false, context); - } - if (hasUnknownTest && !consequentBreakFlow) { - context.breakFlow = false; + let consequentBreakFlow: BreakFlow | false = false; + if (this.consequent.shouldBeIncluded(context)) { + this.consequent.include(false, context); + consequentBreakFlow = context.breakFlow; + context.breakFlow = false; + } + if (this.alternate !== null && this.alternate.shouldBeIncluded(context)) { + this.alternate.include(false, context); + if (!consequentBreakFlow) { + context.breakFlow = false; + } + } + } else { + if (this.test.shouldBeIncluded(context)) { + this.test.include(false, context); + } + if (this.testValue && this.consequent.shouldBeIncluded(context)) { + this.consequent.include(false, context); + } + if (this.alternate !== null && !this.testValue && this.alternate.shouldBeIncluded(context)) { + this.alternate.include(false, context); + } } } From 63e07358711ecda1e1d37f1da4e8b8b040e3732c Mon Sep 17 00:00:00 2001 From: Lukas Taegert-Atkinson Date: Sun, 6 Oct 2019 14:38:22 +0200 Subject: [PATCH 15/35] Simplify BreakFlow type --- src/ast/ExecutionContext.ts | 16 ++++------------ src/ast/nodes/ArrowFunctionExpression.ts | 4 ++-- src/ast/nodes/ForInStatement.ts | 4 ++-- src/ast/nodes/ForOfStatement.ts | 4 ++-- src/ast/nodes/ForStatement.ts | 4 ++-- src/ast/nodes/IfStatement.ts | 5 ++--- src/ast/nodes/SwitchCase.ts | 4 ++-- src/ast/nodes/ThrowStatement.ts | 4 ++-- src/ast/nodes/TryStatement.ts | 6 +++--- src/ast/nodes/WhileStatement.ts | 4 ++-- src/ast/nodes/shared/FunctionNode.ts | 4 ++-- 11 files changed, 25 insertions(+), 34 deletions(-) diff --git a/src/ast/ExecutionContext.ts b/src/ast/ExecutionContext.ts index 51df0766fcf..bafbacb6b46 100644 --- a/src/ast/ExecutionContext.ts +++ b/src/ast/ExecutionContext.ts @@ -8,23 +8,16 @@ interface ExecutionContextIgnore { returnAwaitYield: boolean; } -export enum BreakFlowType { +export enum BreakFlow { None = 0, - BreakContinue, - Return, Error } -export interface BreakFlow { - label: string | null; - type: BreakFlowType; -} - export interface ExecutionContext { - breakFlow: false | BreakFlow; + breakFlow: BreakFlow; } -export interface EffectsExecutionContext extends ExecutionContext { +export interface EffectsExecutionContext { accessed: PathTracker; assigned: PathTracker; called: PathTracker; @@ -35,7 +28,7 @@ export interface EffectsExecutionContext extends ExecutionContext { export function createExecutionContext(): ExecutionContext { return { - breakFlow: false + breakFlow: BreakFlow.None }; } @@ -43,7 +36,6 @@ export function createEffectsExecutionContext(): EffectsExecutionContext { return { accessed: new PathTracker(), assigned: new PathTracker(), - breakFlow: false, called: new PathTracker(), ignore: { breakStatements: false, diff --git a/src/ast/nodes/ArrowFunctionExpression.ts b/src/ast/nodes/ArrowFunctionExpression.ts index 9915bca81d2..0f245d1cdd1 100644 --- a/src/ast/nodes/ArrowFunctionExpression.ts +++ b/src/ast/nodes/ArrowFunctionExpression.ts @@ -1,5 +1,5 @@ import CallOptions from '../CallOptions'; -import { EffectsExecutionContext, ExecutionContext } from '../ExecutionContext'; +import { BreakFlow, EffectsExecutionContext, ExecutionContext } from '../ExecutionContext'; import ReturnValueScope from '../scopes/ReturnValueScope'; import Scope from '../scopes/Scope'; import { ObjectPath, UNKNOWN_PATH, UnknownKey } from '../utils/PathTracker'; @@ -70,7 +70,7 @@ export default class ArrowFunctionExpression extends NodeBase { include(includeChildrenRecursively: IncludeChildren, context: ExecutionContext) { this.included = true; const breakFlow = context.breakFlow; - context.breakFlow = false; + context.breakFlow = BreakFlow.None; this.body.include(includeChildrenRecursively, context); context.breakFlow = breakFlow; for (const param of this.params) { diff --git a/src/ast/nodes/ForInStatement.ts b/src/ast/nodes/ForInStatement.ts index 49af31e6061..faf10619995 100644 --- a/src/ast/nodes/ForInStatement.ts +++ b/src/ast/nodes/ForInStatement.ts @@ -1,6 +1,6 @@ import MagicString from 'magic-string'; import { NO_SEMICOLON, RenderOptions } from '../../utils/renderHelpers'; -import { EffectsExecutionContext, ExecutionContext } from '../ExecutionContext'; +import { BreakFlow, EffectsExecutionContext, ExecutionContext } from '../ExecutionContext'; import BlockScope from '../scopes/BlockScope'; import Scope from '../scopes/Scope'; import { EMPTY_PATH } from '../utils/PathTracker'; @@ -49,7 +49,7 @@ export default class ForInStatement extends StatementBase { this.left.deoptimizePath(EMPTY_PATH); this.right.include(includeChildrenRecursively, context); this.body.include(includeChildrenRecursively, context); - context.breakFlow = false; + context.breakFlow = BreakFlow.None; } render(code: MagicString, options: RenderOptions) { diff --git a/src/ast/nodes/ForOfStatement.ts b/src/ast/nodes/ForOfStatement.ts index 65b1d5b0ade..882ef19b69b 100644 --- a/src/ast/nodes/ForOfStatement.ts +++ b/src/ast/nodes/ForOfStatement.ts @@ -1,6 +1,6 @@ import MagicString from 'magic-string'; import { NO_SEMICOLON, RenderOptions } from '../../utils/renderHelpers'; -import { ExecutionContext } from '../ExecutionContext'; +import { BreakFlow, ExecutionContext } from '../ExecutionContext'; import BlockScope from '../scopes/BlockScope'; import Scope from '../scopes/Scope'; import { EMPTY_PATH } from '../utils/PathTracker'; @@ -38,7 +38,7 @@ export default class ForOfStatement extends StatementBase { this.left.deoptimizePath(EMPTY_PATH); this.right.include(includeChildrenRecursively, context); this.body.include(includeChildrenRecursively, context); - context.breakFlow = false; + context.breakFlow = BreakFlow.None; } render(code: MagicString, options: RenderOptions) { diff --git a/src/ast/nodes/ForStatement.ts b/src/ast/nodes/ForStatement.ts index bc926760549..554f964d4e5 100644 --- a/src/ast/nodes/ForStatement.ts +++ b/src/ast/nodes/ForStatement.ts @@ -1,6 +1,6 @@ import MagicString from 'magic-string'; import { NO_SEMICOLON, RenderOptions } from '../../utils/renderHelpers'; -import { EffectsExecutionContext, ExecutionContext } from '../ExecutionContext'; +import { BreakFlow, EffectsExecutionContext, ExecutionContext } from '../ExecutionContext'; import BlockScope from '../scopes/BlockScope'; import Scope from '../scopes/Scope'; import * as NodeType from './NodeType'; @@ -40,7 +40,7 @@ export default class ForStatement extends StatementBase { if (this.test) this.test.include(includeChildrenRecursively, context); if (this.update) this.update.include(includeChildrenRecursively, context); if (this.body) this.body.include(includeChildrenRecursively, context); - context.breakFlow = false; + context.breakFlow = BreakFlow.None; } render(code: MagicString, options: RenderOptions) { diff --git a/src/ast/nodes/IfStatement.ts b/src/ast/nodes/IfStatement.ts index 3697501721f..66c8fe1aeac 100644 --- a/src/ast/nodes/IfStatement.ts +++ b/src/ast/nodes/IfStatement.ts @@ -43,7 +43,6 @@ export default class IfStatement extends StatementBase implements DeoptimizableE : this.alternate !== null && this.alternate.hasEffects(context); } - // TODO Lukas simplify type for BreakFlow to Symbol or Set of labels include(includeChildrenRecursively: IncludeChildren, context: ExecutionContext) { this.included = true; if (includeChildrenRecursively) { @@ -58,12 +57,12 @@ export default class IfStatement extends StatementBase implements DeoptimizableE if (this.consequent.shouldBeIncluded(context)) { this.consequent.include(false, context); consequentBreakFlow = context.breakFlow; - context.breakFlow = false; + context.breakFlow = BreakFlow.None; } if (this.alternate !== null && this.alternate.shouldBeIncluded(context)) { this.alternate.include(false, context); if (!consequentBreakFlow) { - context.breakFlow = false; + context.breakFlow = BreakFlow.None; } } } else { diff --git a/src/ast/nodes/SwitchCase.ts b/src/ast/nodes/SwitchCase.ts index 9d2aa50624b..00c507f6eff 100644 --- a/src/ast/nodes/SwitchCase.ts +++ b/src/ast/nodes/SwitchCase.ts @@ -4,7 +4,7 @@ import { RenderOptions, renderStatementList } from '../../utils/renderHelpers'; -import { ExecutionContext } from '../ExecutionContext'; +import { BreakFlow, ExecutionContext } from '../ExecutionContext'; import * as NodeType from './NodeType'; import { ExpressionNode, IncludeChildren, NodeBase, StatementNode } from './shared/Node'; @@ -20,7 +20,7 @@ export default class SwitchCase extends NodeBase { if (includeChildrenRecursively || node.shouldBeIncluded(context)) node.include(includeChildrenRecursively, context); } - context.breakFlow = false; + context.breakFlow = BreakFlow.None; } render(code: MagicString, options: RenderOptions) { diff --git a/src/ast/nodes/ThrowStatement.ts b/src/ast/nodes/ThrowStatement.ts index 1fa8b2668b8..6981edf841f 100644 --- a/src/ast/nodes/ThrowStatement.ts +++ b/src/ast/nodes/ThrowStatement.ts @@ -1,6 +1,6 @@ import MagicString from 'magic-string'; import { RenderOptions } from '../../utils/renderHelpers'; -import { BreakFlowType, ExecutionContext } from '../ExecutionContext'; +import { BreakFlow, ExecutionContext } from '../ExecutionContext'; import * as NodeType from './NodeType'; import { ExpressionNode, StatementBase } from './shared/Node'; @@ -18,7 +18,7 @@ export default class ThrowStatement extends StatementBase { shouldBeIncluded(context: ExecutionContext): boolean { if (context.breakFlow) return false; - context.breakFlow = { type: BreakFlowType.Error, label: null }; + context.breakFlow = BreakFlow.Error; return true; } } diff --git a/src/ast/nodes/TryStatement.ts b/src/ast/nodes/TryStatement.ts index 2dd70b97170..1941e155e6d 100644 --- a/src/ast/nodes/TryStatement.ts +++ b/src/ast/nodes/TryStatement.ts @@ -1,4 +1,4 @@ -import { EffectsExecutionContext, ExecutionContext } from '../ExecutionContext'; +import { BreakFlow, EffectsExecutionContext, ExecutionContext } from '../ExecutionContext'; import BlockStatement from './BlockStatement'; import CatchClause from './CatchClause'; import * as NodeType from './NodeType'; @@ -28,11 +28,11 @@ export default class TryStatement extends StatementBase { this.context.tryCatchDeoptimization ? INCLUDE_PARAMETERS : includeChildrenRecursively, context ); - context.breakFlow = false; + context.breakFlow = BreakFlow.None; } if (this.handler !== null) { this.handler.include(includeChildrenRecursively, context); - context.breakFlow = false; + context.breakFlow = BreakFlow.None; } if (this.finalizer !== null) { this.finalizer.include(includeChildrenRecursively, context); diff --git a/src/ast/nodes/WhileStatement.ts b/src/ast/nodes/WhileStatement.ts index 183a59a1d35..c700016d1d0 100644 --- a/src/ast/nodes/WhileStatement.ts +++ b/src/ast/nodes/WhileStatement.ts @@ -1,4 +1,4 @@ -import { EffectsExecutionContext, ExecutionContext } from '../ExecutionContext'; +import { BreakFlow, EffectsExecutionContext, ExecutionContext } from '../ExecutionContext'; import * as NodeType from './NodeType'; import { ExpressionNode, IncludeChildren, StatementBase, StatementNode } from './shared/Node'; @@ -20,6 +20,6 @@ export default class WhileStatement extends StatementBase { include(includeChildrenRecursively: IncludeChildren, context: ExecutionContext) { super.include(includeChildrenRecursively, context); - context.breakFlow = false; + context.breakFlow = BreakFlow.None; } } diff --git a/src/ast/nodes/shared/FunctionNode.ts b/src/ast/nodes/shared/FunctionNode.ts index cb5834caeff..80cf0678294 100644 --- a/src/ast/nodes/shared/FunctionNode.ts +++ b/src/ast/nodes/shared/FunctionNode.ts @@ -1,5 +1,5 @@ import CallOptions from '../../CallOptions'; -import { EffectsExecutionContext, ExecutionContext } from '../../ExecutionContext'; +import { BreakFlow, EffectsExecutionContext, ExecutionContext } from '../../ExecutionContext'; import FunctionScope from '../../scopes/FunctionScope'; import { ObjectPath, UNKNOWN_PATH, UnknownKey } from '../../utils/PathTracker'; import { UNKNOWN_EXPRESSION, UnknownObjectExpression } from '../../values'; @@ -91,7 +91,7 @@ export default class FunctionNode extends NodeBase { include(includeChildrenRecursively: IncludeChildren, context: ExecutionContext) { this.included = true; const breakFlow = context.breakFlow; - context.breakFlow = false; + context.breakFlow = BreakFlow.None; this.body.include(includeChildrenRecursively, context); context.breakFlow = breakFlow; if (this.id) { From 2cc9e6ac035ad68da00002a340c28f13c076cb5e Mon Sep 17 00:00:00 2001 From: Lukas Taegert-Atkinson Date: Mon, 7 Oct 2019 07:50:15 +0200 Subject: [PATCH 16/35] Restore broken flow after conditional statements --- src/Module.ts | 8 ++--- src/ast/Entity.ts | 4 +-- src/ast/ExecutionContext.ts | 8 ++--- src/ast/nodes/ArrayExpression.ts | 4 +-- src/ast/nodes/ArrayPattern.ts | 4 +-- src/ast/nodes/ArrowFunctionExpression.ts | 11 +++--- src/ast/nodes/AssignmentExpression.ts | 6 ++-- src/ast/nodes/AssignmentPattern.ts | 4 +-- src/ast/nodes/AwaitExpression.ts | 6 ++-- src/ast/nodes/BinaryExpression.ts | 4 +-- src/ast/nodes/BlockStatement.ts | 6 ++-- src/ast/nodes/BreakStatement.ts | 4 +-- src/ast/nodes/CallExpression.ts | 12 +++---- src/ast/nodes/ClassBody.ts | 4 +-- src/ast/nodes/ConditionalExpression.ts | 12 +++---- src/ast/nodes/DoWhileStatement.ts | 4 +-- src/ast/nodes/ExportDefaultDeclaration.ts | 4 +-- src/ast/nodes/ExportNamedDeclaration.ts | 4 +-- src/ast/nodes/ExpressionStatement.ts | 4 +-- src/ast/nodes/ForInStatement.ts | 9 ++--- src/ast/nodes/ForOfStatement.ts | 7 ++-- src/ast/nodes/ForStatement.ts | 9 ++--- src/ast/nodes/Identifier.ts | 8 ++--- src/ast/nodes/IfStatement.ts | 9 ++--- src/ast/nodes/ImportExpression.ts | 4 +-- src/ast/nodes/LabeledStatement.ts | 4 +-- src/ast/nodes/Literal.ts | 4 +-- src/ast/nodes/LogicalExpression.ts | 12 +++---- src/ast/nodes/MemberExpression.ts | 12 +++---- src/ast/nodes/MethodDefinition.ts | 6 ++-- src/ast/nodes/NewExpression.ts | 4 +-- src/ast/nodes/ObjectExpression.ts | 8 ++--- src/ast/nodes/ObjectPattern.ts | 4 +-- src/ast/nodes/Program.ts | 6 ++-- src/ast/nodes/Property.ts | 10 +++--- src/ast/nodes/RestElement.ts | 4 +-- src/ast/nodes/ReturnStatement.ts | 4 +-- src/ast/nodes/SequenceExpression.ts | 12 +++---- src/ast/nodes/SwitchCase.ts | 5 ++- src/ast/nodes/SwitchStatement.ts | 16 +++++++-- src/ast/nodes/TaggedTemplateExpression.ts | 4 +-- src/ast/nodes/ThisExpression.ts | 6 ++-- src/ast/nodes/ThrowStatement.ts | 4 +-- src/ast/nodes/TryStatement.ts | 11 +++--- src/ast/nodes/UnaryExpression.ts | 4 +-- src/ast/nodes/UnknownNode.ts | 4 +-- src/ast/nodes/UpdateExpression.ts | 4 +-- src/ast/nodes/VariableDeclaration.ts | 6 ++-- src/ast/nodes/WhileStatement.ts | 13 ++++--- src/ast/nodes/YieldExpression.ts | 4 +-- src/ast/nodes/shared/ClassNode.ts | 4 +-- src/ast/nodes/shared/Expression.ts | 8 ++--- src/ast/nodes/shared/FunctionNode.ts | 19 +++++----- src/ast/nodes/shared/MultiExpression.ts | 8 ++--- src/ast/nodes/shared/Node.ts | 36 +++++++++---------- src/ast/scopes/FunctionScope.ts | 4 +-- src/ast/scopes/ParameterScope.ts | 6 ++-- src/ast/values.ts | 20 +++++------ src/ast/variables/LocalVariable.ts | 12 +++---- src/ast/variables/ThisVariable.ts | 10 +++--- src/ast/variables/Variable.ts | 10 +++--- .../caught-errors/_expected.js | 16 +++++++++ .../break-control-flow/caught-errors/main.js | 20 +++++++++++ .../if-statement-errors/_expected.js | 14 ++++++++ .../if-statement-errors/main.js | 17 +++++++++ .../loop-errors/_expected.js | 24 +++++++++++++ .../break-control-flow/loop-errors/main.js | 34 ++++++++++++++++++ .../switch-errors/_expected.js | 13 +++++++ .../break-control-flow/switch-errors/main.js | 17 +++++++++ 69 files changed, 394 insertions(+), 228 deletions(-) diff --git a/src/Module.ts b/src/Module.ts index 7b1e9f0af29..8f60673a2f9 100644 --- a/src/Module.ts +++ b/src/Module.ts @@ -3,7 +3,7 @@ import * as ESTree from 'estree'; import { locate } from 'locate-character'; import MagicString from 'magic-string'; import extractAssignedNames from 'rollup-pluginutils/src/extractAssignedNames'; -import { createExecutionContext } from './ast/ExecutionContext'; +import { createInclusionContext } from './ast/ExecutionContext'; import ClassDeclaration from './ast/nodes/ClassDeclaration'; import ExportAllDeclaration from './ast/nodes/ExportAllDeclaration'; import ExportDefaultDeclaration from './ast/nodes/ExportDefaultDeclaration'; @@ -450,8 +450,8 @@ export default class Module { } include(): void { - if (this.ast.shouldBeIncluded(createExecutionContext())) - this.ast.include(false, createExecutionContext()); + if (this.ast.shouldBeIncluded(createInclusionContext())) + this.ast.include(false, createInclusionContext()); } includeAllExports() { @@ -483,7 +483,7 @@ export default class Module { } includeAllInBundle() { - this.ast.include(true, createExecutionContext()); + this.ast.include(true, createInclusionContext()); } isIncluded() { diff --git a/src/ast/Entity.ts b/src/ast/Entity.ts index 6aeeaa222eb..f67667c74c4 100644 --- a/src/ast/Entity.ts +++ b/src/ast/Entity.ts @@ -1,4 +1,4 @@ -import { EffectsExecutionContext } from './ExecutionContext'; +import { HasEffectsContext } from './ExecutionContext'; import { ObjectPath } from './utils/PathTracker'; export interface Entity { @@ -13,5 +13,5 @@ export interface WritableEntity extends Entity { * expression of this node is reassigned as well. */ deoptimizePath(path: ObjectPath): void; - hasEffectsWhenAssignedAtPath(path: ObjectPath, execution: EffectsExecutionContext): boolean; + hasEffectsWhenAssignedAtPath(path: ObjectPath, execution: HasEffectsContext): boolean; } diff --git a/src/ast/ExecutionContext.ts b/src/ast/ExecutionContext.ts index bafbacb6b46..48dbc8520ef 100644 --- a/src/ast/ExecutionContext.ts +++ b/src/ast/ExecutionContext.ts @@ -13,11 +13,11 @@ export enum BreakFlow { Error } -export interface ExecutionContext { +export interface InclusionContext { breakFlow: BreakFlow; } -export interface EffectsExecutionContext { +export interface HasEffectsContext { accessed: PathTracker; assigned: PathTracker; called: PathTracker; @@ -26,13 +26,13 @@ export interface EffectsExecutionContext { replacedVariableInits: Map; } -export function createExecutionContext(): ExecutionContext { +export function createInclusionContext(): InclusionContext { return { breakFlow: BreakFlow.None }; } -export function createEffectsExecutionContext(): EffectsExecutionContext { +export function createHasEffectsContext(): HasEffectsContext { return { accessed: new PathTracker(), assigned: new PathTracker(), diff --git a/src/ast/nodes/ArrayExpression.ts b/src/ast/nodes/ArrayExpression.ts index 8b8b40c64c2..c0d54511328 100644 --- a/src/ast/nodes/ArrayExpression.ts +++ b/src/ast/nodes/ArrayExpression.ts @@ -1,5 +1,5 @@ import CallOptions from '../CallOptions'; -import { EffectsExecutionContext } from '../ExecutionContext'; +import { HasEffectsContext } from '../ExecutionContext'; import { ObjectPath, UNKNOWN_PATH } from '../utils/PathTracker'; import { arrayMembers, @@ -34,7 +34,7 @@ export default class ArrayExpression extends NodeBase { hasEffectsWhenCalledAtPath( path: ObjectPath, callOptions: CallOptions, - context: EffectsExecutionContext + context: HasEffectsContext ): boolean { if (path.length === 1) { return hasMemberEffectWhenCalled(arrayMembers, path[0], this.included, callOptions, context); diff --git a/src/ast/nodes/ArrayPattern.ts b/src/ast/nodes/ArrayPattern.ts index b367e049a0a..3336d6bfa75 100644 --- a/src/ast/nodes/ArrayPattern.ts +++ b/src/ast/nodes/ArrayPattern.ts @@ -1,4 +1,4 @@ -import { EffectsExecutionContext } from '../ExecutionContext'; +import { HasEffectsContext } from '../ExecutionContext'; import { EMPTY_PATH, ObjectPath } from '../utils/PathTracker'; import { UNKNOWN_EXPRESSION } from '../values'; import Variable from '../variables/Variable'; @@ -38,7 +38,7 @@ export default class ArrayPattern extends NodeBase implements PatternNode { } } - hasEffectsWhenAssignedAtPath(path: ObjectPath, context: EffectsExecutionContext) { + hasEffectsWhenAssignedAtPath(path: ObjectPath, context: HasEffectsContext) { if (path.length > 0) return true; for (const element of this.elements) { if (element !== null && element.hasEffectsWhenAssignedAtPath(EMPTY_PATH, context)) diff --git a/src/ast/nodes/ArrowFunctionExpression.ts b/src/ast/nodes/ArrowFunctionExpression.ts index 0f245d1cdd1..b1f265d2c65 100644 --- a/src/ast/nodes/ArrowFunctionExpression.ts +++ b/src/ast/nodes/ArrowFunctionExpression.ts @@ -1,5 +1,5 @@ import CallOptions from '../CallOptions'; -import { BreakFlow, EffectsExecutionContext, ExecutionContext } from '../ExecutionContext'; +import { createInclusionContext, HasEffectsContext, InclusionContext } from '../ExecutionContext'; import ReturnValueScope from '../scopes/ReturnValueScope'; import Scope from '../scopes/Scope'; import { ObjectPath, UNKNOWN_PATH, UnknownKey } from '../utils/PathTracker'; @@ -50,7 +50,7 @@ export default class ArrowFunctionExpression extends NodeBase { hasEffectsWhenCalledAtPath( path: ObjectPath, _callOptions: CallOptions, - context: EffectsExecutionContext + context: HasEffectsContext ): boolean { if (path.length > 0) return true; for (const param of this.params) { @@ -67,12 +67,9 @@ export default class ArrowFunctionExpression extends NodeBase { return false; } - include(includeChildrenRecursively: IncludeChildren, context: ExecutionContext) { + include(includeChildrenRecursively: IncludeChildren, context: InclusionContext) { this.included = true; - const breakFlow = context.breakFlow; - context.breakFlow = BreakFlow.None; - this.body.include(includeChildrenRecursively, context); - context.breakFlow = breakFlow; + this.body.include(includeChildrenRecursively, createInclusionContext()); for (const param of this.params) { if (!(param instanceof Identifier)) { param.include(includeChildrenRecursively, context); diff --git a/src/ast/nodes/AssignmentExpression.ts b/src/ast/nodes/AssignmentExpression.ts index 70eb9345628..98a539d3db5 100644 --- a/src/ast/nodes/AssignmentExpression.ts +++ b/src/ast/nodes/AssignmentExpression.ts @@ -1,7 +1,7 @@ import MagicString from 'magic-string'; import { RenderOptions } from '../../utils/renderHelpers'; import { getSystemExportStatement } from '../../utils/systemJsRendering'; -import { EffectsExecutionContext } from '../ExecutionContext'; +import { HasEffectsContext } from '../ExecutionContext'; import { EMPTY_PATH, ObjectPath, UNKNOWN_PATH } from '../utils/PathTracker'; import Variable from '../variables/Variable'; import * as NodeType from './NodeType'; @@ -34,7 +34,7 @@ export default class AssignmentExpression extends NodeBase { this.right.deoptimizePath(UNKNOWN_PATH); } - hasEffects(context: EffectsExecutionContext): boolean { + hasEffects(context: HasEffectsContext): boolean { return ( this.right.hasEffects(context) || this.left.hasEffects(context) || @@ -42,7 +42,7 @@ export default class AssignmentExpression extends NodeBase { ); } - hasEffectsWhenAccessedAtPath(path: ObjectPath, context: EffectsExecutionContext): boolean { + hasEffectsWhenAccessedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { return path.length > 0 && this.right.hasEffectsWhenAccessedAtPath(path, context); } diff --git a/src/ast/nodes/AssignmentPattern.ts b/src/ast/nodes/AssignmentPattern.ts index 95eada265ea..c9651fe5bb5 100644 --- a/src/ast/nodes/AssignmentPattern.ts +++ b/src/ast/nodes/AssignmentPattern.ts @@ -1,7 +1,7 @@ import MagicString from 'magic-string'; import { BLANK } from '../../utils/blank'; import { NodeRenderOptions, RenderOptions } from '../../utils/renderHelpers'; -import { EffectsExecutionContext } from '../ExecutionContext'; +import { HasEffectsContext } from '../ExecutionContext'; import { EMPTY_PATH, ObjectPath, UNKNOWN_PATH } from '../utils/PathTracker'; import Variable from '../variables/Variable'; import * as NodeType from './NodeType'; @@ -32,7 +32,7 @@ export default class AssignmentPattern extends NodeBase implements PatternNode { path.length === 0 && this.left.deoptimizePath(path); } - hasEffectsWhenAssignedAtPath(path: ObjectPath, context: EffectsExecutionContext): boolean { + hasEffectsWhenAssignedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { return path.length > 0 || this.left.hasEffectsWhenAssignedAtPath(EMPTY_PATH, context); } diff --git a/src/ast/nodes/AwaitExpression.ts b/src/ast/nodes/AwaitExpression.ts index 6bb887dcd4a..011dfa74cba 100644 --- a/src/ast/nodes/AwaitExpression.ts +++ b/src/ast/nodes/AwaitExpression.ts @@ -1,6 +1,6 @@ import MagicString from 'magic-string'; import { RenderOptions } from '../../utils/renderHelpers'; -import { EffectsExecutionContext, ExecutionContext } from '../ExecutionContext'; +import { HasEffectsContext, InclusionContext } from '../ExecutionContext'; import ArrowFunctionExpression from './ArrowFunctionExpression'; import * as NodeType from './NodeType'; import FunctionNode from './shared/FunctionNode'; @@ -10,11 +10,11 @@ export default class AwaitExpression extends NodeBase { argument!: ExpressionNode; type!: NodeType.tAwaitExpression; - hasEffects(context: EffectsExecutionContext) { + hasEffects(context: HasEffectsContext) { return super.hasEffects(context) || !context.ignore.returnAwaitYield; } - include(includeChildrenRecursively: IncludeChildren, context: ExecutionContext) { + include(includeChildrenRecursively: IncludeChildren, context: InclusionContext) { checkTopLevelAwait: if (!this.included && !this.context.usesTopLevelAwait) { let parent = this.parent; do { diff --git a/src/ast/nodes/BinaryExpression.ts b/src/ast/nodes/BinaryExpression.ts index 05c5d33f670..9cf3f5d76bf 100644 --- a/src/ast/nodes/BinaryExpression.ts +++ b/src/ast/nodes/BinaryExpression.ts @@ -1,5 +1,5 @@ import { DeoptimizableEntity } from '../DeoptimizableEntity'; -import { EffectsExecutionContext } from '../ExecutionContext'; +import { HasEffectsContext } from '../ExecutionContext'; import { EMPTY_IMMUTABLE_TRACKER, EMPTY_PATH, ObjectPath, PathTracker } from '../utils/PathTracker'; import { LiteralValueOrUnknown, UnknownValue } from '../values'; import ExpressionStatement from './ExpressionStatement'; @@ -63,7 +63,7 @@ export default class BinaryExpression extends NodeBase implements DeoptimizableE return operatorFn(leftValue, rightValue); } - hasEffects(context: EffectsExecutionContext): boolean { + hasEffects(context: HasEffectsContext): boolean { // support some implicit type coercion runtime errors if ( this.operator === '+' && diff --git a/src/ast/nodes/BlockStatement.ts b/src/ast/nodes/BlockStatement.ts index f13ba8dff60..51eb133fc87 100644 --- a/src/ast/nodes/BlockStatement.ts +++ b/src/ast/nodes/BlockStatement.ts @@ -1,6 +1,6 @@ import MagicString from 'magic-string'; import { RenderOptions, renderStatementList } from '../../utils/renderHelpers'; -import { EffectsExecutionContext, ExecutionContext } from '../ExecutionContext'; +import { HasEffectsContext, InclusionContext } from '../ExecutionContext'; import BlockScope from '../scopes/BlockScope'; import ChildScope from '../scopes/ChildScope'; import Scope from '../scopes/Scope'; @@ -25,14 +25,14 @@ export default class BlockStatement extends StatementBase { : new BlockScope(parentScope); } - hasEffects(context: EffectsExecutionContext) { + hasEffects(context: HasEffectsContext) { for (const node of this.body) { if (node.hasEffects(context)) return true; } return false; } - include(includeChildrenRecursively: IncludeChildren, context: ExecutionContext) { + include(includeChildrenRecursively: IncludeChildren, context: InclusionContext) { this.included = true; for (const node of this.body) { if (includeChildrenRecursively || node.shouldBeIncluded(context)) diff --git a/src/ast/nodes/BreakStatement.ts b/src/ast/nodes/BreakStatement.ts index 3fa85546cb2..f63fa23b2b4 100644 --- a/src/ast/nodes/BreakStatement.ts +++ b/src/ast/nodes/BreakStatement.ts @@ -1,4 +1,4 @@ -import { EffectsExecutionContext } from '../ExecutionContext'; +import { HasEffectsContext } from '../ExecutionContext'; import Identifier from './Identifier'; import * as NodeType from './NodeType'; import { StatementBase } from './shared/Node'; @@ -7,7 +7,7 @@ export default class BreakStatement extends StatementBase { label!: Identifier | null; type!: NodeType.tBreakStatement; - hasEffects(context: EffectsExecutionContext) { + hasEffects(context: HasEffectsContext) { return ( super.hasEffects(context) || !context.ignore.breakStatements || diff --git a/src/ast/nodes/CallExpression.ts b/src/ast/nodes/CallExpression.ts index 10670f09597..c8b1e1640d0 100644 --- a/src/ast/nodes/CallExpression.ts +++ b/src/ast/nodes/CallExpression.ts @@ -7,7 +7,7 @@ import { } from '../../utils/renderHelpers'; import CallOptions from '../CallOptions'; import { DeoptimizableEntity } from '../DeoptimizableEntity'; -import { EffectsExecutionContext, ExecutionContext } from '../ExecutionContext'; +import { HasEffectsContext, InclusionContext } from '../ExecutionContext'; import { EMPTY_IMMUTABLE_TRACKER, EMPTY_PATH, @@ -152,7 +152,7 @@ export default class CallExpression extends NodeBase implements DeoptimizableEnt return value; } - hasEffects(context: EffectsExecutionContext): boolean { + hasEffects(context: HasEffectsContext): boolean { for (const argument of this.arguments) { if (argument.hasEffects(context)) return true; } @@ -163,7 +163,7 @@ export default class CallExpression extends NodeBase implements DeoptimizableEnt ); } - hasEffectsWhenAccessedAtPath(path: ObjectPath, context: EffectsExecutionContext): boolean { + hasEffectsWhenAccessedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { if (path.length === 0) return false; const trackedExpressions = context.accessed.getEntities(path); if (trackedExpressions.has(this)) return false; @@ -171,7 +171,7 @@ export default class CallExpression extends NodeBase implements DeoptimizableEnt return (this.returnExpression as ExpressionEntity).hasEffectsWhenAccessedAtPath(path, context); } - hasEffectsWhenAssignedAtPath(path: ObjectPath, context: EffectsExecutionContext): boolean { + hasEffectsWhenAssignedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { if (path.length === 0) return true; const trackedExpressions = context.assigned.getEntities(path); if (trackedExpressions.has(this)) return false; @@ -182,7 +182,7 @@ export default class CallExpression extends NodeBase implements DeoptimizableEnt hasEffectsWhenCalledAtPath( path: ObjectPath, callOptions: CallOptions, - context: EffectsExecutionContext + context: HasEffectsContext ): boolean { const trackedExpressions = (callOptions.withNew ? context.instantiated @@ -197,7 +197,7 @@ export default class CallExpression extends NodeBase implements DeoptimizableEnt ); } - include(includeChildrenRecursively: IncludeChildren, context: ExecutionContext) { + include(includeChildrenRecursively: IncludeChildren, context: InclusionContext) { if (includeChildrenRecursively) { super.include(includeChildrenRecursively, context); if ( diff --git a/src/ast/nodes/ClassBody.ts b/src/ast/nodes/ClassBody.ts index 419da45f1db..decb6af0d5f 100644 --- a/src/ast/nodes/ClassBody.ts +++ b/src/ast/nodes/ClassBody.ts @@ -1,5 +1,5 @@ import CallOptions from '../CallOptions'; -import { EffectsExecutionContext } from '../ExecutionContext'; +import { HasEffectsContext } from '../ExecutionContext'; import { EMPTY_PATH, ObjectPath } from '../utils/PathTracker'; import MethodDefinition from './MethodDefinition'; import * as NodeType from './NodeType'; @@ -14,7 +14,7 @@ export default class ClassBody extends NodeBase { hasEffectsWhenCalledAtPath( path: ObjectPath, callOptions: CallOptions, - context: EffectsExecutionContext + context: HasEffectsContext ) { if (path.length > 0) return true; return ( diff --git a/src/ast/nodes/ConditionalExpression.ts b/src/ast/nodes/ConditionalExpression.ts index 14ef0d4fb71..aba82a5fcf9 100644 --- a/src/ast/nodes/ConditionalExpression.ts +++ b/src/ast/nodes/ConditionalExpression.ts @@ -9,7 +9,7 @@ import { import { removeAnnotations } from '../../utils/treeshakeNode'; import CallOptions from '../CallOptions'; import { DeoptimizableEntity } from '../DeoptimizableEntity'; -import { EffectsExecutionContext, ExecutionContext } from '../ExecutionContext'; +import { HasEffectsContext, InclusionContext } from '../ExecutionContext'; import { EMPTY_IMMUTABLE_TRACKER, EMPTY_PATH, @@ -91,7 +91,7 @@ export default class ConditionalExpression extends NodeBase implements Deoptimiz return this.usedBranch.getReturnExpressionWhenCalledAtPath(path, recursionTracker, origin); } - hasEffects(context: EffectsExecutionContext): boolean { + hasEffects(context: HasEffectsContext): boolean { if (this.test.hasEffects(context)) return true; if (this.usedBranch === null) { return this.consequent.hasEffects(context) || this.alternate.hasEffects(context); @@ -99,7 +99,7 @@ export default class ConditionalExpression extends NodeBase implements Deoptimiz return this.usedBranch.hasEffects(context); } - hasEffectsWhenAccessedAtPath(path: ObjectPath, context: EffectsExecutionContext): boolean { + hasEffectsWhenAccessedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { if (path.length === 0) return false; if (this.usedBranch === null) { return ( @@ -110,7 +110,7 @@ export default class ConditionalExpression extends NodeBase implements Deoptimiz return this.usedBranch.hasEffectsWhenAccessedAtPath(path, context); } - hasEffectsWhenAssignedAtPath(path: ObjectPath, context: EffectsExecutionContext): boolean { + hasEffectsWhenAssignedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { if (path.length === 0) return true; if (this.usedBranch === null) { return ( @@ -124,7 +124,7 @@ export default class ConditionalExpression extends NodeBase implements Deoptimiz hasEffectsWhenCalledAtPath( path: ObjectPath, callOptions: CallOptions, - context: EffectsExecutionContext + context: HasEffectsContext ): boolean { if (this.usedBranch === null) { return ( @@ -135,7 +135,7 @@ export default class ConditionalExpression extends NodeBase implements Deoptimiz return this.usedBranch.hasEffectsWhenCalledAtPath(path, callOptions, context); } - include(includeChildrenRecursively: IncludeChildren, context: ExecutionContext) { + include(includeChildrenRecursively: IncludeChildren, context: InclusionContext) { this.included = true; if ( includeChildrenRecursively || diff --git a/src/ast/nodes/DoWhileStatement.ts b/src/ast/nodes/DoWhileStatement.ts index e34c423fae0..3cd36e809b0 100644 --- a/src/ast/nodes/DoWhileStatement.ts +++ b/src/ast/nodes/DoWhileStatement.ts @@ -1,4 +1,4 @@ -import { EffectsExecutionContext } from '../ExecutionContext'; +import { HasEffectsContext } from '../ExecutionContext'; import * as NodeType from './NodeType'; import { ExpressionNode, StatementBase, StatementNode } from './shared/Node'; @@ -7,7 +7,7 @@ export default class DoWhileStatement extends StatementBase { test!: ExpressionNode; type!: NodeType.tDoWhileStatement; - hasEffects(context: EffectsExecutionContext): boolean { + hasEffects(context: HasEffectsContext): boolean { if (this.test.hasEffects(context)) return true; const { ignore: { breakStatements } diff --git a/src/ast/nodes/ExportDefaultDeclaration.ts b/src/ast/nodes/ExportDefaultDeclaration.ts index 44513fd4f4d..6420c476b44 100644 --- a/src/ast/nodes/ExportDefaultDeclaration.ts +++ b/src/ast/nodes/ExportDefaultDeclaration.ts @@ -6,7 +6,7 @@ import { RenderOptions } from '../../utils/renderHelpers'; import { treeshakeNode } from '../../utils/treeshakeNode'; -import { ExecutionContext } from '../ExecutionContext'; +import { InclusionContext } from '../ExecutionContext'; import ModuleScope from '../scopes/ModuleScope'; import ExportDefaultVariable from '../variables/ExportDefaultVariable'; import ClassDeclaration from './ClassDeclaration'; @@ -44,7 +44,7 @@ export default class ExportDefaultDeclaration extends NodeBase { private declarationName: string | undefined; - include(includeChildrenRecursively: IncludeChildren, context: ExecutionContext) { + include(includeChildrenRecursively: IncludeChildren, context: InclusionContext) { super.include(includeChildrenRecursively, context); if (includeChildrenRecursively) { this.context.includeVariable(this.variable); diff --git a/src/ast/nodes/ExportNamedDeclaration.ts b/src/ast/nodes/ExportNamedDeclaration.ts index bae519bee7c..0cdb997ba27 100644 --- a/src/ast/nodes/ExportNamedDeclaration.ts +++ b/src/ast/nodes/ExportNamedDeclaration.ts @@ -1,7 +1,7 @@ import MagicString from 'magic-string'; import { BLANK } from '../../utils/blank'; import { NodeRenderOptions, RenderOptions } from '../../utils/renderHelpers'; -import { EffectsExecutionContext } from '../ExecutionContext'; +import { HasEffectsContext } from '../ExecutionContext'; import ClassDeclaration from './ClassDeclaration'; import ExportSpecifier from './ExportSpecifier'; import FunctionDeclaration from './FunctionDeclaration'; @@ -22,7 +22,7 @@ export default class ExportNamedDeclaration extends NodeBase { if (this.declaration !== null) this.declaration.bind(); } - hasEffects(context: EffectsExecutionContext) { + hasEffects(context: HasEffectsContext) { return this.declaration !== null && this.declaration.hasEffects(context); } diff --git a/src/ast/nodes/ExpressionStatement.ts b/src/ast/nodes/ExpressionStatement.ts index 3e5692472f6..163954231d8 100644 --- a/src/ast/nodes/ExpressionStatement.ts +++ b/src/ast/nodes/ExpressionStatement.ts @@ -1,6 +1,6 @@ import MagicString from 'magic-string'; import { RenderOptions } from '../../utils/renderHelpers'; -import { ExecutionContext } from '../ExecutionContext'; +import { InclusionContext } from '../ExecutionContext'; import * as NodeType from './NodeType'; import { ExpressionNode, StatementBase } from './shared/Node'; @@ -30,7 +30,7 @@ export default class ExpressionStatement extends StatementBase { if (this.included) this.insertSemicolon(code); } - shouldBeIncluded(context: ExecutionContext) { + shouldBeIncluded(context: InclusionContext) { if (this.directive && this.directive !== 'use strict') return this.parent.type !== NodeType.Program; diff --git a/src/ast/nodes/ForInStatement.ts b/src/ast/nodes/ForInStatement.ts index faf10619995..55ef4136be1 100644 --- a/src/ast/nodes/ForInStatement.ts +++ b/src/ast/nodes/ForInStatement.ts @@ -1,6 +1,6 @@ import MagicString from 'magic-string'; import { NO_SEMICOLON, RenderOptions } from '../../utils/renderHelpers'; -import { BreakFlow, EffectsExecutionContext, ExecutionContext } from '../ExecutionContext'; +import { HasEffectsContext, InclusionContext } from '../ExecutionContext'; import BlockScope from '../scopes/BlockScope'; import Scope from '../scopes/Scope'; import { EMPTY_PATH } from '../utils/PathTracker'; @@ -26,7 +26,7 @@ export default class ForInStatement extends StatementBase { this.scope = new BlockScope(parentScope); } - hasEffects(context: EffectsExecutionContext): boolean { + hasEffects(context: HasEffectsContext): boolean { if ( (this.left && (this.left.hasEffects(context) || @@ -43,13 +43,14 @@ export default class ForInStatement extends StatementBase { return false; } - include(includeChildrenRecursively: IncludeChildren, context: ExecutionContext) { + include(includeChildrenRecursively: IncludeChildren, context: InclusionContext) { this.included = true; + const breakFlow = context.breakFlow; this.left.includeWithAllDeclaredVariables(includeChildrenRecursively, context); this.left.deoptimizePath(EMPTY_PATH); this.right.include(includeChildrenRecursively, context); this.body.include(includeChildrenRecursively, context); - context.breakFlow = BreakFlow.None; + context.breakFlow = breakFlow; } render(code: MagicString, options: RenderOptions) { diff --git a/src/ast/nodes/ForOfStatement.ts b/src/ast/nodes/ForOfStatement.ts index 882ef19b69b..ab2ebf18688 100644 --- a/src/ast/nodes/ForOfStatement.ts +++ b/src/ast/nodes/ForOfStatement.ts @@ -1,6 +1,6 @@ import MagicString from 'magic-string'; import { NO_SEMICOLON, RenderOptions } from '../../utils/renderHelpers'; -import { BreakFlow, ExecutionContext } from '../ExecutionContext'; +import { InclusionContext } from '../ExecutionContext'; import BlockScope from '../scopes/BlockScope'; import Scope from '../scopes/Scope'; import { EMPTY_PATH } from '../utils/PathTracker'; @@ -32,13 +32,14 @@ export default class ForOfStatement extends StatementBase { return true; } - include(includeChildrenRecursively: IncludeChildren, context: ExecutionContext) { + include(includeChildrenRecursively: IncludeChildren, context: InclusionContext) { this.included = true; + const breakFlow = context.breakFlow; this.left.includeWithAllDeclaredVariables(includeChildrenRecursively, context); this.left.deoptimizePath(EMPTY_PATH); this.right.include(includeChildrenRecursively, context); this.body.include(includeChildrenRecursively, context); - context.breakFlow = BreakFlow.None; + context.breakFlow = breakFlow; } render(code: MagicString, options: RenderOptions) { diff --git a/src/ast/nodes/ForStatement.ts b/src/ast/nodes/ForStatement.ts index 554f964d4e5..a5bac7b19ce 100644 --- a/src/ast/nodes/ForStatement.ts +++ b/src/ast/nodes/ForStatement.ts @@ -1,6 +1,6 @@ import MagicString from 'magic-string'; import { NO_SEMICOLON, RenderOptions } from '../../utils/renderHelpers'; -import { BreakFlow, EffectsExecutionContext, ExecutionContext } from '../ExecutionContext'; +import { HasEffectsContext, InclusionContext } from '../ExecutionContext'; import BlockScope from '../scopes/BlockScope'; import Scope from '../scopes/Scope'; import * as NodeType from './NodeType'; @@ -18,7 +18,7 @@ export default class ForStatement extends StatementBase { this.scope = new BlockScope(parentScope); } - hasEffects(context: EffectsExecutionContext): boolean { + hasEffects(context: HasEffectsContext): boolean { if ( (this.init && this.init.hasEffects(context)) || (this.test && this.test.hasEffects(context)) || @@ -34,13 +34,14 @@ export default class ForStatement extends StatementBase { return false; } - include(includeChildrenRecursively: IncludeChildren, context: ExecutionContext) { + include(includeChildrenRecursively: IncludeChildren, context: InclusionContext) { this.included = true; + const breakFlow = context.breakFlow; if (this.init) this.init.include(includeChildrenRecursively, context); if (this.test) this.test.include(includeChildrenRecursively, context); if (this.update) this.update.include(includeChildrenRecursively, context); if (this.body) this.body.include(includeChildrenRecursively, context); - context.breakFlow = BreakFlow.None; + context.breakFlow = breakFlow; } render(code: MagicString, options: RenderOptions) { diff --git a/src/ast/nodes/Identifier.ts b/src/ast/nodes/Identifier.ts index 8ed212e902e..5b922f2194c 100644 --- a/src/ast/nodes/Identifier.ts +++ b/src/ast/nodes/Identifier.ts @@ -4,7 +4,7 @@ import { BLANK } from '../../utils/blank'; import { NodeRenderOptions, RenderOptions } from '../../utils/renderHelpers'; import CallOptions from '../CallOptions'; import { DeoptimizableEntity } from '../DeoptimizableEntity'; -import { EffectsExecutionContext } from '../ExecutionContext'; +import { HasEffectsContext } from '../ExecutionContext'; import FunctionScope from '../scopes/FunctionScope'; import { EMPTY_PATH, ObjectPath, PathTracker } from '../utils/PathTracker'; import { LiteralValueOrUnknown } from '../values'; @@ -109,18 +109,18 @@ export default class Identifier extends NodeBase implements PatternNode { ); } - hasEffectsWhenAccessedAtPath(path: ObjectPath, context: EffectsExecutionContext): boolean { + hasEffectsWhenAccessedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { return this.variable !== null && this.variable.hasEffectsWhenAccessedAtPath(path, context); } - hasEffectsWhenAssignedAtPath(path: ObjectPath, context: EffectsExecutionContext): boolean { + hasEffectsWhenAssignedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { return !this.variable || this.variable.hasEffectsWhenAssignedAtPath(path, context); } hasEffectsWhenCalledAtPath( path: ObjectPath, callOptions: CallOptions, - context: EffectsExecutionContext + context: HasEffectsContext ) { return !this.variable || this.variable.hasEffectsWhenCalledAtPath(path, callOptions, context); } diff --git a/src/ast/nodes/IfStatement.ts b/src/ast/nodes/IfStatement.ts index 66c8fe1aeac..b76f9d3b89a 100644 --- a/src/ast/nodes/IfStatement.ts +++ b/src/ast/nodes/IfStatement.ts @@ -2,7 +2,7 @@ import MagicString from 'magic-string'; import { RenderOptions } from '../../utils/renderHelpers'; import { removeAnnotations } from '../../utils/treeshakeNode'; import { DeoptimizableEntity } from '../DeoptimizableEntity'; -import { BreakFlow, EffectsExecutionContext, ExecutionContext } from '../ExecutionContext'; +import { BreakFlow, HasEffectsContext, InclusionContext } from '../ExecutionContext'; import { EMPTY_IMMUTABLE_TRACKER, EMPTY_PATH } from '../utils/PathTracker'; import { LiteralValueOrUnknown, UnknownValue } from '../values'; import * as NodeType from './NodeType'; @@ -30,7 +30,7 @@ export default class IfStatement extends StatementBase implements DeoptimizableE this.testValue = UnknownValue; } - hasEffects(context: EffectsExecutionContext): boolean { + hasEffects(context: HasEffectsContext): boolean { if (this.test.hasEffects(context)) return true; if (this.testValue === UnknownValue) { return ( @@ -43,7 +43,7 @@ export default class IfStatement extends StatementBase implements DeoptimizableE : this.alternate !== null && this.alternate.hasEffects(context); } - include(includeChildrenRecursively: IncludeChildren, context: ExecutionContext) { + include(includeChildrenRecursively: IncludeChildren, context: InclusionContext) { this.included = true; if (includeChildrenRecursively) { this.test.include(includeChildrenRecursively, context); @@ -53,11 +53,12 @@ export default class IfStatement extends StatementBase implements DeoptimizableE } } else if (this.testValue === UnknownValue) { this.test.include(false, context); + const breakFlow = context.breakFlow; let consequentBreakFlow: BreakFlow | false = false; if (this.consequent.shouldBeIncluded(context)) { this.consequent.include(false, context); consequentBreakFlow = context.breakFlow; - context.breakFlow = BreakFlow.None; + context.breakFlow = breakFlow; } if (this.alternate !== null && this.alternate.shouldBeIncluded(context)) { this.alternate.include(false, context); diff --git a/src/ast/nodes/ImportExpression.ts b/src/ast/nodes/ImportExpression.ts index 927f8abb68c..f322bbdc3da 100644 --- a/src/ast/nodes/ImportExpression.ts +++ b/src/ast/nodes/ImportExpression.ts @@ -1,7 +1,7 @@ import MagicString from 'magic-string'; import { findFirstOccurrenceOutsideComment, RenderOptions } from '../../utils/renderHelpers'; import { INTEROP_NAMESPACE_VARIABLE } from '../../utils/variableNames'; -import { ExecutionContext } from '../ExecutionContext'; +import { InclusionContext } from '../ExecutionContext'; import NamespaceVariable from '../variables/NamespaceVariable'; import * as NodeType from './NodeType'; import { ExpressionNode, IncludeChildren, NodeBase } from './shared/Node'; @@ -22,7 +22,7 @@ export default class Import extends NodeBase { return true; } - include(includeChildrenRecursively: IncludeChildren, context: ExecutionContext) { + include(includeChildrenRecursively: IncludeChildren, context: InclusionContext) { if (!this.included) { this.included = true; this.context.includeDynamicImport(this); diff --git a/src/ast/nodes/LabeledStatement.ts b/src/ast/nodes/LabeledStatement.ts index a17a33ef40f..1c94d68b6f8 100644 --- a/src/ast/nodes/LabeledStatement.ts +++ b/src/ast/nodes/LabeledStatement.ts @@ -1,4 +1,4 @@ -import { EffectsExecutionContext } from '../ExecutionContext'; +import { HasEffectsContext } from '../ExecutionContext'; import Identifier from './Identifier'; import * as NodeType from './NodeType'; import { StatementBase, StatementNode } from './shared/Node'; @@ -8,7 +8,7 @@ export default class LabeledStatement extends StatementBase { label!: Identifier; type!: NodeType.tLabeledStatement; - hasEffects(context: EffectsExecutionContext) { + hasEffects(context: HasEffectsContext) { const { ignore: { breakStatements } } = context; diff --git a/src/ast/nodes/Literal.ts b/src/ast/nodes/Literal.ts index 6aabba859c0..e992fa55ced 100644 --- a/src/ast/nodes/Literal.ts +++ b/src/ast/nodes/Literal.ts @@ -1,6 +1,6 @@ import MagicString from 'magic-string'; import CallOptions from '../CallOptions'; -import { EffectsExecutionContext } from '../ExecutionContext'; +import { HasEffectsContext } from '../ExecutionContext'; import { ObjectPath } from '../utils/PathTracker'; import { getLiteralMembersForValue, @@ -55,7 +55,7 @@ export default class Literal extends NodeBase { hasEffectsWhenCalledAtPath( path: ObjectPath, callOptions: CallOptions, - context: EffectsExecutionContext + context: HasEffectsContext ): boolean { if (path.length === 1) { return hasMemberEffectWhenCalled(this.members, path[0], this.included, callOptions, context); diff --git a/src/ast/nodes/LogicalExpression.ts b/src/ast/nodes/LogicalExpression.ts index 3f35850406b..d6e7a0c6a65 100644 --- a/src/ast/nodes/LogicalExpression.ts +++ b/src/ast/nodes/LogicalExpression.ts @@ -9,7 +9,7 @@ import { import { removeAnnotations } from '../../utils/treeshakeNode'; import CallOptions from '../CallOptions'; import { DeoptimizableEntity } from '../DeoptimizableEntity'; -import { EffectsExecutionContext, ExecutionContext } from '../ExecutionContext'; +import { HasEffectsContext, InclusionContext } from '../ExecutionContext'; import { EMPTY_IMMUTABLE_TRACKER, EMPTY_PATH, @@ -93,14 +93,14 @@ export default class LogicalExpression extends NodeBase implements Deoptimizable return this.usedBranch.getReturnExpressionWhenCalledAtPath(path, recursionTracker, origin); } - hasEffects(context: EffectsExecutionContext): boolean { + hasEffects(context: HasEffectsContext): boolean { if (this.usedBranch === null) { return this.left.hasEffects(context) || this.right.hasEffects(context); } return this.usedBranch.hasEffects(context); } - hasEffectsWhenAccessedAtPath(path: ObjectPath, context: EffectsExecutionContext): boolean { + hasEffectsWhenAccessedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { if (path.length === 0) return false; if (this.usedBranch === null) { return ( @@ -111,7 +111,7 @@ export default class LogicalExpression extends NodeBase implements Deoptimizable return this.usedBranch.hasEffectsWhenAccessedAtPath(path, context); } - hasEffectsWhenAssignedAtPath(path: ObjectPath, context: EffectsExecutionContext): boolean { + hasEffectsWhenAssignedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { if (path.length === 0) return true; if (this.usedBranch === null) { return ( @@ -125,7 +125,7 @@ export default class LogicalExpression extends NodeBase implements Deoptimizable hasEffectsWhenCalledAtPath( path: ObjectPath, callOptions: CallOptions, - context: EffectsExecutionContext + context: HasEffectsContext ): boolean { if (this.usedBranch === null) { return ( @@ -136,7 +136,7 @@ export default class LogicalExpression extends NodeBase implements Deoptimizable return this.usedBranch.hasEffectsWhenCalledAtPath(path, callOptions, context); } - include(includeChildrenRecursively: IncludeChildren, context: ExecutionContext) { + include(includeChildrenRecursively: IncludeChildren, context: InclusionContext) { this.included = true; if ( includeChildrenRecursively || diff --git a/src/ast/nodes/MemberExpression.ts b/src/ast/nodes/MemberExpression.ts index 8e2187c3bd9..6c84ed84ffb 100644 --- a/src/ast/nodes/MemberExpression.ts +++ b/src/ast/nodes/MemberExpression.ts @@ -4,7 +4,7 @@ import relativeId from '../../utils/relativeId'; import { NodeRenderOptions, RenderOptions } from '../../utils/renderHelpers'; import CallOptions from '../CallOptions'; import { DeoptimizableEntity } from '../DeoptimizableEntity'; -import { EffectsExecutionContext, ExecutionContext } from '../ExecutionContext'; +import { HasEffectsContext, InclusionContext } from '../ExecutionContext'; import { EMPTY_IMMUTABLE_TRACKER, EMPTY_PATH, @@ -164,7 +164,7 @@ export default class MemberExpression extends NodeBase implements DeoptimizableE ); } - hasEffects(context: EffectsExecutionContext): boolean { + hasEffects(context: HasEffectsContext): boolean { return ( this.property.hasEffects(context) || this.object.hasEffects(context) || @@ -173,7 +173,7 @@ export default class MemberExpression extends NodeBase implements DeoptimizableE ); } - hasEffectsWhenAccessedAtPath(path: ObjectPath, context: EffectsExecutionContext): boolean { + hasEffectsWhenAccessedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { if (path.length === 0) return false; if (this.variable !== null) { return this.variable.hasEffectsWhenAccessedAtPath(path, context); @@ -184,7 +184,7 @@ export default class MemberExpression extends NodeBase implements DeoptimizableE ); } - hasEffectsWhenAssignedAtPath(path: ObjectPath, context: EffectsExecutionContext): boolean { + hasEffectsWhenAssignedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { if (this.variable !== null) { return this.variable.hasEffectsWhenAssignedAtPath(path, context); } @@ -197,7 +197,7 @@ export default class MemberExpression extends NodeBase implements DeoptimizableE hasEffectsWhenCalledAtPath( path: ObjectPath, callOptions: CallOptions, - context: EffectsExecutionContext + context: HasEffectsContext ): boolean { if (this.variable !== null) { return this.variable.hasEffectsWhenCalledAtPath(path, callOptions, context); @@ -209,7 +209,7 @@ export default class MemberExpression extends NodeBase implements DeoptimizableE ); } - include(includeChildrenRecursively: IncludeChildren, context: ExecutionContext) { + include(includeChildrenRecursively: IncludeChildren, context: InclusionContext) { if (!this.included) { this.included = true; if (this.variable !== null) { diff --git a/src/ast/nodes/MethodDefinition.ts b/src/ast/nodes/MethodDefinition.ts index 50090131deb..b9e2a8749b4 100644 --- a/src/ast/nodes/MethodDefinition.ts +++ b/src/ast/nodes/MethodDefinition.ts @@ -1,5 +1,5 @@ import CallOptions from '../CallOptions'; -import { EffectsExecutionContext } from '../ExecutionContext'; +import { HasEffectsContext } from '../ExecutionContext'; import { EMPTY_PATH, ObjectPath } from '../utils/PathTracker'; import FunctionExpression from './FunctionExpression'; import * as NodeType from './NodeType'; @@ -13,14 +13,14 @@ export default class MethodDefinition extends NodeBase { type!: NodeType.tMethodDefinition; value!: FunctionExpression; - hasEffects(context: EffectsExecutionContext) { + hasEffects(context: HasEffectsContext) { return this.key.hasEffects(context); } hasEffectsWhenCalledAtPath( path: ObjectPath, callOptions: CallOptions, - context: EffectsExecutionContext + context: HasEffectsContext ) { return ( path.length > 0 || this.value.hasEffectsWhenCalledAtPath(EMPTY_PATH, callOptions, context) diff --git a/src/ast/nodes/NewExpression.ts b/src/ast/nodes/NewExpression.ts index 91cf5b2c03d..f7b9d4fa43a 100644 --- a/src/ast/nodes/NewExpression.ts +++ b/src/ast/nodes/NewExpression.ts @@ -1,5 +1,5 @@ import CallOptions from '../CallOptions'; -import { EffectsExecutionContext } from '../ExecutionContext'; +import { HasEffectsContext } from '../ExecutionContext'; import { EMPTY_PATH, ObjectPath, UNKNOWN_PATH } from '../utils/PathTracker'; import * as NodeType from './NodeType'; import { ExpressionNode, NodeBase } from './shared/Node'; @@ -20,7 +20,7 @@ export default class NewExpression extends NodeBase { } } - hasEffects(context: EffectsExecutionContext): boolean { + hasEffects(context: HasEffectsContext): boolean { for (const argument of this.arguments) { if (argument.hasEffects(context)) return true; } diff --git a/src/ast/nodes/ObjectExpression.ts b/src/ast/nodes/ObjectExpression.ts index c397f340ed4..7d8fe0a4ef3 100644 --- a/src/ast/nodes/ObjectExpression.ts +++ b/src/ast/nodes/ObjectExpression.ts @@ -3,7 +3,7 @@ import { BLANK } from '../../utils/blank'; import { NodeRenderOptions, RenderOptions } from '../../utils/renderHelpers'; import CallOptions from '../CallOptions'; import { DeoptimizableEntity } from '../DeoptimizableEntity'; -import { EffectsExecutionContext } from '../ExecutionContext'; +import { HasEffectsContext } from '../ExecutionContext'; import { EMPTY_IMMUTABLE_TRACKER, EMPTY_PATH, @@ -189,7 +189,7 @@ export default class ObjectExpression extends NodeBase { ); } - hasEffectsWhenAccessedAtPath(path: ObjectPath, context: EffectsExecutionContext) { + hasEffectsWhenAccessedAtPath(path: ObjectPath, context: HasEffectsContext) { if (path.length === 0) return false; const key = path[0]; if ( @@ -213,7 +213,7 @@ export default class ObjectExpression extends NodeBase { return false; } - hasEffectsWhenAssignedAtPath(path: ObjectPath, context: EffectsExecutionContext) { + hasEffectsWhenAssignedAtPath(path: ObjectPath, context: HasEffectsContext) { if (path.length === 0) return false; const key = path[0]; if ( @@ -242,7 +242,7 @@ export default class ObjectExpression extends NodeBase { hasEffectsWhenCalledAtPath( path: ObjectPath, callOptions: CallOptions, - context: EffectsExecutionContext + context: HasEffectsContext ): boolean { const key = path[0]; if ( diff --git a/src/ast/nodes/ObjectPattern.ts b/src/ast/nodes/ObjectPattern.ts index d40c5b0a9a3..e4d68fbe6d7 100644 --- a/src/ast/nodes/ObjectPattern.ts +++ b/src/ast/nodes/ObjectPattern.ts @@ -1,4 +1,4 @@ -import { EffectsExecutionContext } from '../ExecutionContext'; +import { HasEffectsContext } from '../ExecutionContext'; import { EMPTY_PATH, ObjectPath } from '../utils/PathTracker'; import Variable from '../variables/Variable'; import * as NodeType from './NodeType'; @@ -38,7 +38,7 @@ export default class ObjectPattern extends NodeBase implements PatternNode { } } - hasEffectsWhenAssignedAtPath(path: ObjectPath, context: EffectsExecutionContext) { + hasEffectsWhenAssignedAtPath(path: ObjectPath, context: HasEffectsContext) { if (path.length > 0) return true; for (const property of this.properties) { if (property.hasEffectsWhenAssignedAtPath(EMPTY_PATH, context)) return true; diff --git a/src/ast/nodes/Program.ts b/src/ast/nodes/Program.ts index c9c8cd4bdea..1dcd75f9bc7 100644 --- a/src/ast/nodes/Program.ts +++ b/src/ast/nodes/Program.ts @@ -1,6 +1,6 @@ import MagicString from 'magic-string'; import { RenderOptions, renderStatementList } from '../../utils/renderHelpers'; -import { EffectsExecutionContext, ExecutionContext } from '../ExecutionContext'; +import { HasEffectsContext, InclusionContext } from '../ExecutionContext'; import * as NodeType from './NodeType'; import { IncludeChildren, NodeBase, StatementNode } from './shared/Node'; @@ -9,14 +9,14 @@ export default class Program extends NodeBase { sourceType!: 'module'; type!: NodeType.tProgram; - hasEffects(context: EffectsExecutionContext) { + hasEffects(context: HasEffectsContext) { for (const node of this.body) { if (node.hasEffects(context)) return true; } return false; } - include(includeChildrenRecursively: IncludeChildren, context: ExecutionContext) { + include(includeChildrenRecursively: IncludeChildren, context: InclusionContext) { this.included = true; for (const node of this.body) { if (includeChildrenRecursively || node.shouldBeIncluded(context)) { diff --git a/src/ast/nodes/Property.ts b/src/ast/nodes/Property.ts index b20b7902b19..5d4f688ea45 100644 --- a/src/ast/nodes/Property.ts +++ b/src/ast/nodes/Property.ts @@ -2,7 +2,7 @@ import MagicString from 'magic-string'; import { RenderOptions } from '../../utils/renderHelpers'; import CallOptions from '../CallOptions'; import { DeoptimizableEntity } from '../DeoptimizableEntity'; -import { EffectsExecutionContext } from '../ExecutionContext'; +import { HasEffectsContext } from '../ExecutionContext'; import { EMPTY_IMMUTABLE_TRACKER, EMPTY_PATH, @@ -96,11 +96,11 @@ export default class Property extends NodeBase implements DeoptimizableEntity { return this.value.getReturnExpressionWhenCalledAtPath(path, recursionTracker, origin); } - hasEffects(context: EffectsExecutionContext): boolean { + hasEffects(context: HasEffectsContext): boolean { return this.key.hasEffects(context) || this.value.hasEffects(context); } - hasEffectsWhenAccessedAtPath(path: ObjectPath, context: EffectsExecutionContext): boolean { + hasEffectsWhenAccessedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { if (this.kind === 'get') { const trackedExpressions = context.accessed.getEntities(path); if (trackedExpressions.has(this)) return false; @@ -114,7 +114,7 @@ export default class Property extends NodeBase implements DeoptimizableEntity { return this.value.hasEffectsWhenAccessedAtPath(path, context); } - hasEffectsWhenAssignedAtPath(path: ObjectPath, context: EffectsExecutionContext): boolean { + hasEffectsWhenAssignedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { if (this.kind === 'get') { if (path.length === 0) return true; const trackedExpressions = context.assigned.getEntities(path); @@ -138,7 +138,7 @@ export default class Property extends NodeBase implements DeoptimizableEntity { hasEffectsWhenCalledAtPath( path: ObjectPath, callOptions: CallOptions, - context: EffectsExecutionContext + context: HasEffectsContext ) { if (this.kind === 'get') { const trackedExpressions = (callOptions.withNew diff --git a/src/ast/nodes/RestElement.ts b/src/ast/nodes/RestElement.ts index 6cf6147bb97..82d3b50a5af 100644 --- a/src/ast/nodes/RestElement.ts +++ b/src/ast/nodes/RestElement.ts @@ -1,4 +1,4 @@ -import { EffectsExecutionContext } from '../ExecutionContext'; +import { HasEffectsContext } from '../ExecutionContext'; import { EMPTY_PATH, ObjectPath, UnknownKey } from '../utils/PathTracker'; import { UNKNOWN_EXPRESSION } from '../values'; import Variable from '../variables/Variable'; @@ -33,7 +33,7 @@ export default class RestElement extends NodeBase implements PatternNode { path.length === 0 && this.argument.deoptimizePath(EMPTY_PATH); } - hasEffectsWhenAssignedAtPath(path: ObjectPath, context: EffectsExecutionContext): boolean { + hasEffectsWhenAssignedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { return path.length > 0 || this.argument.hasEffectsWhenAssignedAtPath(EMPTY_PATH, context); } } diff --git a/src/ast/nodes/ReturnStatement.ts b/src/ast/nodes/ReturnStatement.ts index b7f04bd37c4..0b66db6186e 100644 --- a/src/ast/nodes/ReturnStatement.ts +++ b/src/ast/nodes/ReturnStatement.ts @@ -1,6 +1,6 @@ import MagicString from 'magic-string'; import { RenderOptions } from '../../utils/renderHelpers'; -import { EffectsExecutionContext } from '../ExecutionContext'; +import { HasEffectsContext } from '../ExecutionContext'; import { UNKNOWN_EXPRESSION } from '../values'; import * as NodeType from './NodeType'; import { ExpressionNode, StatementBase } from './shared/Node'; @@ -9,7 +9,7 @@ export default class ReturnStatement extends StatementBase { argument!: ExpressionNode | null; type!: NodeType.tReturnStatement; - hasEffects(context: EffectsExecutionContext) { + hasEffects(context: HasEffectsContext) { return ( !context.ignore.returnAwaitYield || (this.argument !== null && this.argument.hasEffects(context)) diff --git a/src/ast/nodes/SequenceExpression.ts b/src/ast/nodes/SequenceExpression.ts index 21c6222ba97..819a3de9490 100644 --- a/src/ast/nodes/SequenceExpression.ts +++ b/src/ast/nodes/SequenceExpression.ts @@ -9,7 +9,7 @@ import { import { treeshakeNode } from '../../utils/treeshakeNode'; import CallOptions from '../CallOptions'; import { DeoptimizableEntity } from '../DeoptimizableEntity'; -import { EffectsExecutionContext, ExecutionContext } from '../ExecutionContext'; +import { HasEffectsContext, InclusionContext } from '../ExecutionContext'; import { ObjectPath, PathTracker } from '../utils/PathTracker'; import { LiteralValueOrUnknown } from '../values'; import CallExpression from './CallExpression'; @@ -36,21 +36,21 @@ export default class SequenceExpression extends NodeBase { ); } - hasEffects(context: EffectsExecutionContext): boolean { + hasEffects(context: HasEffectsContext): boolean { for (const expression of this.expressions) { if (expression.hasEffects(context)) return true; } return false; } - hasEffectsWhenAccessedAtPath(path: ObjectPath, context: EffectsExecutionContext): boolean { + hasEffectsWhenAccessedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { return ( path.length > 0 && this.expressions[this.expressions.length - 1].hasEffectsWhenAccessedAtPath(path, context) ); } - hasEffectsWhenAssignedAtPath(path: ObjectPath, context: EffectsExecutionContext): boolean { + hasEffectsWhenAssignedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { return ( path.length === 0 || this.expressions[this.expressions.length - 1].hasEffectsWhenAssignedAtPath(path, context) @@ -60,7 +60,7 @@ export default class SequenceExpression extends NodeBase { hasEffectsWhenCalledAtPath( path: ObjectPath, callOptions: CallOptions, - context: EffectsExecutionContext + context: HasEffectsContext ): boolean { return this.expressions[this.expressions.length - 1].hasEffectsWhenCalledAtPath( path, @@ -69,7 +69,7 @@ export default class SequenceExpression extends NodeBase { ); } - include(includeChildrenRecursively: IncludeChildren, context: ExecutionContext) { + include(includeChildrenRecursively: IncludeChildren, context: InclusionContext) { this.included = true; for (let i = 0; i < this.expressions.length - 1; i++) { const node = this.expressions[i]; diff --git a/src/ast/nodes/SwitchCase.ts b/src/ast/nodes/SwitchCase.ts index 00c507f6eff..ab1ddd3e83e 100644 --- a/src/ast/nodes/SwitchCase.ts +++ b/src/ast/nodes/SwitchCase.ts @@ -4,7 +4,7 @@ import { RenderOptions, renderStatementList } from '../../utils/renderHelpers'; -import { BreakFlow, ExecutionContext } from '../ExecutionContext'; +import { InclusionContext } from '../ExecutionContext'; import * as NodeType from './NodeType'; import { ExpressionNode, IncludeChildren, NodeBase, StatementNode } from './shared/Node'; @@ -13,14 +13,13 @@ export default class SwitchCase extends NodeBase { test!: ExpressionNode | null; type!: NodeType.tSwitchCase; - include(includeChildrenRecursively: IncludeChildren, context: ExecutionContext) { + include(includeChildrenRecursively: IncludeChildren, context: InclusionContext) { this.included = true; if (this.test) this.test.include(includeChildrenRecursively, context); for (const node of this.consequent) { if (includeChildrenRecursively || node.shouldBeIncluded(context)) node.include(includeChildrenRecursively, context); } - context.breakFlow = BreakFlow.None; } render(code: MagicString, options: RenderOptions) { diff --git a/src/ast/nodes/SwitchStatement.ts b/src/ast/nodes/SwitchStatement.ts index 9c849bfedae..ef6f3ed23fb 100644 --- a/src/ast/nodes/SwitchStatement.ts +++ b/src/ast/nodes/SwitchStatement.ts @@ -1,8 +1,8 @@ -import { EffectsExecutionContext } from '../ExecutionContext'; +import { HasEffectsContext, InclusionContext } from '../ExecutionContext'; import BlockScope from '../scopes/BlockScope'; import Scope from '../scopes/Scope'; import * as NodeType from './NodeType'; -import { ExpressionNode, StatementBase } from './shared/Node'; +import { ExpressionNode, IncludeChildren, StatementBase } from './shared/Node'; import SwitchCase from './SwitchCase'; export default class SwitchStatement extends StatementBase { @@ -14,11 +14,21 @@ export default class SwitchStatement extends StatementBase { this.scope = new BlockScope(parentScope); } - hasEffects(context: EffectsExecutionContext) { + hasEffects(context: HasEffectsContext) { const ignoreBreakStatements = context.ignore.breakStatements; context.ignore.breakStatements = true; if (super.hasEffects(context)) return true; context.ignore.breakStatements = ignoreBreakStatements; return false; } + + include(includeChildrenRecursively: IncludeChildren, context: InclusionContext) { + this.included = true; + this.discriminant.include(includeChildrenRecursively, context); + const breakFlow = context.breakFlow; + for (const switchCase of this.cases) { + switchCase.include(includeChildrenRecursively, context); + context.breakFlow = breakFlow; + } + } } diff --git a/src/ast/nodes/TaggedTemplateExpression.ts b/src/ast/nodes/TaggedTemplateExpression.ts index a03e58dd1e2..e28414677a1 100644 --- a/src/ast/nodes/TaggedTemplateExpression.ts +++ b/src/ast/nodes/TaggedTemplateExpression.ts @@ -1,5 +1,5 @@ import CallOptions from '../CallOptions'; -import { EffectsExecutionContext } from '../ExecutionContext'; +import { HasEffectsContext } from '../ExecutionContext'; import { EMPTY_PATH } from '../utils/PathTracker'; import Identifier from './Identifier'; import * as NodeType from './NodeType'; @@ -41,7 +41,7 @@ export default class TaggedTemplateExpression extends NodeBase { } } - hasEffects(context: EffectsExecutionContext) { + hasEffects(context: HasEffectsContext) { return ( super.hasEffects(context) || this.tag.hasEffectsWhenCalledAtPath(EMPTY_PATH, this.callOptions, context) diff --git a/src/ast/nodes/ThisExpression.ts b/src/ast/nodes/ThisExpression.ts index cbb6761aa05..512ac7b933b 100644 --- a/src/ast/nodes/ThisExpression.ts +++ b/src/ast/nodes/ThisExpression.ts @@ -1,5 +1,5 @@ import MagicString from 'magic-string'; -import { EffectsExecutionContext } from '../ExecutionContext'; +import { HasEffectsContext } from '../ExecutionContext'; import ModuleScope from '../scopes/ModuleScope'; import { ObjectPath } from '../utils/PathTracker'; import ThisVariable from '../variables/ThisVariable'; @@ -17,11 +17,11 @@ export default class ThisExpression extends NodeBase { this.variable = this.scope.findVariable('this') as ThisVariable; } - hasEffectsWhenAccessedAtPath(path: ObjectPath, context: EffectsExecutionContext): boolean { + hasEffectsWhenAccessedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { return path.length > 0 && this.variable.hasEffectsWhenAccessedAtPath(path, context); } - hasEffectsWhenAssignedAtPath(path: ObjectPath, context: EffectsExecutionContext): boolean { + hasEffectsWhenAssignedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { return this.variable.hasEffectsWhenAssignedAtPath(path, context); } diff --git a/src/ast/nodes/ThrowStatement.ts b/src/ast/nodes/ThrowStatement.ts index 6981edf841f..93ffeabcf7c 100644 --- a/src/ast/nodes/ThrowStatement.ts +++ b/src/ast/nodes/ThrowStatement.ts @@ -1,6 +1,6 @@ import MagicString from 'magic-string'; import { RenderOptions } from '../../utils/renderHelpers'; -import { BreakFlow, ExecutionContext } from '../ExecutionContext'; +import { BreakFlow, InclusionContext } from '../ExecutionContext'; import * as NodeType from './NodeType'; import { ExpressionNode, StatementBase } from './shared/Node'; @@ -16,7 +16,7 @@ export default class ThrowStatement extends StatementBase { this.argument.render(code, options, { preventASI: true }); } - shouldBeIncluded(context: ExecutionContext): boolean { + shouldBeIncluded(context: InclusionContext): boolean { if (context.breakFlow) return false; context.breakFlow = BreakFlow.Error; return true; diff --git a/src/ast/nodes/TryStatement.ts b/src/ast/nodes/TryStatement.ts index 1941e155e6d..4c0e550b32b 100644 --- a/src/ast/nodes/TryStatement.ts +++ b/src/ast/nodes/TryStatement.ts @@ -1,4 +1,4 @@ -import { BreakFlow, EffectsExecutionContext, ExecutionContext } from '../ExecutionContext'; +import { HasEffectsContext, InclusionContext } from '../ExecutionContext'; import BlockStatement from './BlockStatement'; import CatchClause from './CatchClause'; import * as NodeType from './NodeType'; @@ -12,7 +12,7 @@ export default class TryStatement extends StatementBase { private directlyIncluded = false; - hasEffects(context: EffectsExecutionContext): boolean { + hasEffects(context: HasEffectsContext): boolean { return ( this.block.body.length > 0 || (this.handler !== null && this.handler.hasEffects(context)) || @@ -20,7 +20,8 @@ export default class TryStatement extends StatementBase { ); } - include(includeChildrenRecursively: IncludeChildren, context: ExecutionContext) { + include(includeChildrenRecursively: IncludeChildren, context: InclusionContext) { + const breakFlow = context.breakFlow; if (!this.directlyIncluded || !this.context.tryCatchDeoptimization) { this.included = true; this.directlyIncluded = true; @@ -28,11 +29,11 @@ export default class TryStatement extends StatementBase { this.context.tryCatchDeoptimization ? INCLUDE_PARAMETERS : includeChildrenRecursively, context ); - context.breakFlow = BreakFlow.None; + context.breakFlow = breakFlow; } if (this.handler !== null) { this.handler.include(includeChildrenRecursively, context); - context.breakFlow = BreakFlow.None; + context.breakFlow = breakFlow; } if (this.finalizer !== null) { this.finalizer.include(includeChildrenRecursively, context); diff --git a/src/ast/nodes/UnaryExpression.ts b/src/ast/nodes/UnaryExpression.ts index 5910d116737..b80b70474ea 100644 --- a/src/ast/nodes/UnaryExpression.ts +++ b/src/ast/nodes/UnaryExpression.ts @@ -1,5 +1,5 @@ import { DeoptimizableEntity } from '../DeoptimizableEntity'; -import { EffectsExecutionContext } from '../ExecutionContext'; +import { HasEffectsContext } from '../ExecutionContext'; import { EMPTY_PATH, ObjectPath, PathTracker } from '../utils/PathTracker'; import { LiteralValueOrUnknown, UnknownValue } from '../values'; import Identifier from './Identifier'; @@ -44,7 +44,7 @@ export default class UnaryExpression extends NodeBase { return unaryOperators[this.operator](argumentValue); } - hasEffects(context: EffectsExecutionContext): boolean { + hasEffects(context: HasEffectsContext): boolean { if (this.operator === 'typeof' && this.argument instanceof Identifier) return false; return ( this.argument.hasEffects(context) || diff --git a/src/ast/nodes/UnknownNode.ts b/src/ast/nodes/UnknownNode.ts index 945e279cfc1..66c4d948242 100644 --- a/src/ast/nodes/UnknownNode.ts +++ b/src/ast/nodes/UnknownNode.ts @@ -1,4 +1,4 @@ -import { ExecutionContext } from '../ExecutionContext'; +import { InclusionContext } from '../ExecutionContext'; import { IncludeChildren, NodeBase } from './shared/Node'; export default class UnknownNode extends NodeBase { @@ -6,7 +6,7 @@ export default class UnknownNode extends NodeBase { return true; } - include(_includeChildrenRecursively: IncludeChildren, context: ExecutionContext) { + include(_includeChildrenRecursively: IncludeChildren, context: InclusionContext) { super.include(true, context); } } diff --git a/src/ast/nodes/UpdateExpression.ts b/src/ast/nodes/UpdateExpression.ts index 341213ac498..5ea6204f942 100644 --- a/src/ast/nodes/UpdateExpression.ts +++ b/src/ast/nodes/UpdateExpression.ts @@ -1,6 +1,6 @@ import MagicString from 'magic-string'; import { RenderOptions } from '../../utils/renderHelpers'; -import { EffectsExecutionContext } from '../ExecutionContext'; +import { HasEffectsContext } from '../ExecutionContext'; import { EMPTY_PATH, ObjectPath } from '../utils/PathTracker'; import Identifier from './Identifier'; import * as NodeType from './NodeType'; @@ -21,7 +21,7 @@ export default class UpdateExpression extends NodeBase { } } - hasEffects(context: EffectsExecutionContext): boolean { + hasEffects(context: HasEffectsContext): boolean { return ( this.argument.hasEffects(context) || this.argument.hasEffectsWhenAssignedAtPath(EMPTY_PATH, context) diff --git a/src/ast/nodes/VariableDeclaration.ts b/src/ast/nodes/VariableDeclaration.ts index 0d62722eb51..34321325bd6 100644 --- a/src/ast/nodes/VariableDeclaration.ts +++ b/src/ast/nodes/VariableDeclaration.ts @@ -6,7 +6,7 @@ import { RenderOptions } from '../../utils/renderHelpers'; import { getSystemExportStatement } from '../../utils/systemJsRendering'; -import { ExecutionContext } from '../ExecutionContext'; +import { InclusionContext } from '../ExecutionContext'; import { EMPTY_PATH } from '../utils/PathTracker'; import Variable from '../variables/Variable'; import Identifier, { IdentifierWithVariable } from './Identifier'; @@ -47,7 +47,7 @@ export default class VariableDeclaration extends NodeBase { return false; } - include(includeChildrenRecursively: IncludeChildren, context: ExecutionContext) { + include(includeChildrenRecursively: IncludeChildren, context: InclusionContext) { this.included = true; for (const declarator of this.declarations) { if (includeChildrenRecursively || declarator.shouldBeIncluded(context)) @@ -57,7 +57,7 @@ export default class VariableDeclaration extends NodeBase { includeWithAllDeclaredVariables( includeChildrenRecursively: IncludeChildren, - context: ExecutionContext + context: InclusionContext ) { this.included = true; for (const declarator of this.declarations) { diff --git a/src/ast/nodes/WhileStatement.ts b/src/ast/nodes/WhileStatement.ts index c700016d1d0..b9004a49479 100644 --- a/src/ast/nodes/WhileStatement.ts +++ b/src/ast/nodes/WhileStatement.ts @@ -1,4 +1,4 @@ -import { BreakFlow, EffectsExecutionContext, ExecutionContext } from '../ExecutionContext'; +import { HasEffectsContext, InclusionContext } from '../ExecutionContext'; import * as NodeType from './NodeType'; import { ExpressionNode, IncludeChildren, StatementBase, StatementNode } from './shared/Node'; @@ -7,7 +7,7 @@ export default class WhileStatement extends StatementBase { test!: ExpressionNode; type!: NodeType.tWhileStatement; - hasEffects(context: EffectsExecutionContext): boolean { + hasEffects(context: HasEffectsContext): boolean { if (this.test.hasEffects(context)) return true; const { ignore: { breakStatements } @@ -18,8 +18,11 @@ export default class WhileStatement extends StatementBase { return false; } - include(includeChildrenRecursively: IncludeChildren, context: ExecutionContext) { - super.include(includeChildrenRecursively, context); - context.breakFlow = BreakFlow.None; + include(includeChildrenRecursively: IncludeChildren, context: InclusionContext) { + this.included = true; + const breakFlow = context.breakFlow; + this.test.include(includeChildrenRecursively, context); + this.body.include(includeChildrenRecursively, context); + context.breakFlow = breakFlow; } } diff --git a/src/ast/nodes/YieldExpression.ts b/src/ast/nodes/YieldExpression.ts index c3b949fec37..ff0a96c3a6d 100644 --- a/src/ast/nodes/YieldExpression.ts +++ b/src/ast/nodes/YieldExpression.ts @@ -1,6 +1,6 @@ import MagicString from 'magic-string'; import { RenderOptions } from '../../utils/renderHelpers'; -import { EffectsExecutionContext } from '../ExecutionContext'; +import { HasEffectsContext } from '../ExecutionContext'; import { UNKNOWN_PATH } from '../utils/PathTracker'; import * as NodeType from './NodeType'; import { ExpressionNode, NodeBase } from './shared/Node'; @@ -17,7 +17,7 @@ export default class YieldExpression extends NodeBase { } } - hasEffects(context: EffectsExecutionContext) { + hasEffects(context: HasEffectsContext) { return ( !context.ignore.returnAwaitYield || (this.argument !== null && this.argument.hasEffects(context)) diff --git a/src/ast/nodes/shared/ClassNode.ts b/src/ast/nodes/shared/ClassNode.ts index fd5f5cc73cb..f9ae4865357 100644 --- a/src/ast/nodes/shared/ClassNode.ts +++ b/src/ast/nodes/shared/ClassNode.ts @@ -1,5 +1,5 @@ import CallOptions from '../../CallOptions'; -import { EffectsExecutionContext } from '../../ExecutionContext'; +import { HasEffectsContext } from '../../ExecutionContext'; import ChildScope from '../../scopes/ChildScope'; import Scope from '../../scopes/Scope'; import { ObjectPath } from '../../utils/PathTracker'; @@ -27,7 +27,7 @@ export default class ClassNode extends NodeBase { hasEffectsWhenCalledAtPath( path: ObjectPath, callOptions: CallOptions, - context: EffectsExecutionContext + context: HasEffectsContext ) { return ( this.body.hasEffectsWhenCalledAtPath(path, callOptions, context) || diff --git a/src/ast/nodes/shared/Expression.ts b/src/ast/nodes/shared/Expression.ts index 17a1a8da89f..7d96566ecff 100644 --- a/src/ast/nodes/shared/Expression.ts +++ b/src/ast/nodes/shared/Expression.ts @@ -1,7 +1,7 @@ import CallOptions from '../../CallOptions'; import { DeoptimizableEntity } from '../../DeoptimizableEntity'; import { WritableEntity } from '../../Entity'; -import { EffectsExecutionContext, ExecutionContext } from '../../ExecutionContext'; +import { HasEffectsContext, InclusionContext } from '../../ExecutionContext'; import { ObjectPath, PathTracker } from '../../utils/PathTracker'; import { LiteralValueOrUnknown } from '../../values'; import SpreadElement from '../SpreadElement'; @@ -25,12 +25,12 @@ export interface ExpressionEntity extends WritableEntity { recursionTracker: PathTracker, origin: DeoptimizableEntity ): ExpressionEntity; - hasEffectsWhenAccessedAtPath(path: ObjectPath, context: EffectsExecutionContext): boolean; + hasEffectsWhenAccessedAtPath(path: ObjectPath, context: HasEffectsContext): boolean; hasEffectsWhenCalledAtPath( path: ObjectPath, callOptions: CallOptions, - context: EffectsExecutionContext + context: HasEffectsContext ): boolean; - include(includeChildrenRecursively: IncludeChildren, context: ExecutionContext): void; + include(includeChildrenRecursively: IncludeChildren, context: InclusionContext): void; includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void; } diff --git a/src/ast/nodes/shared/FunctionNode.ts b/src/ast/nodes/shared/FunctionNode.ts index 80cf0678294..15c9c54a40d 100644 --- a/src/ast/nodes/shared/FunctionNode.ts +++ b/src/ast/nodes/shared/FunctionNode.ts @@ -1,5 +1,9 @@ import CallOptions from '../../CallOptions'; -import { BreakFlow, EffectsExecutionContext, ExecutionContext } from '../../ExecutionContext'; +import { + createInclusionContext, + HasEffectsContext, + InclusionContext +} from '../../ExecutionContext'; import FunctionScope from '../../scopes/FunctionScope'; import { ObjectPath, UNKNOWN_PATH, UnknownKey } from '../../utils/PathTracker'; import { UNKNOWN_EXPRESSION, UnknownObjectExpression } from '../../values'; @@ -61,7 +65,7 @@ export default class FunctionNode extends NodeBase { hasEffectsWhenCalledAtPath( path: ObjectPath, callOptions: CallOptions, - context: EffectsExecutionContext + context: HasEffectsContext ) { if (path.length > 0) return true; for (const param of this.params) { @@ -88,15 +92,10 @@ export default class FunctionNode extends NodeBase { return false; } - include(includeChildrenRecursively: IncludeChildren, context: ExecutionContext) { + include(includeChildrenRecursively: IncludeChildren, context: InclusionContext) { this.included = true; - const breakFlow = context.breakFlow; - context.breakFlow = BreakFlow.None; - this.body.include(includeChildrenRecursively, context); - context.breakFlow = breakFlow; - if (this.id) { - this.id.include(); - } + this.body.include(includeChildrenRecursively, createInclusionContext()); + if (this.id) this.id.include(); const hasArguments = this.scope.argumentsVariable.included; for (const param of this.params) { if (!(param instanceof Identifier) || hasArguments) { diff --git a/src/ast/nodes/shared/MultiExpression.ts b/src/ast/nodes/shared/MultiExpression.ts index 34861d742a3..7e1ab1f4042 100644 --- a/src/ast/nodes/shared/MultiExpression.ts +++ b/src/ast/nodes/shared/MultiExpression.ts @@ -1,6 +1,6 @@ import CallOptions from '../../CallOptions'; import { DeoptimizableEntity } from '../../DeoptimizableEntity'; -import { EffectsExecutionContext } from '../../ExecutionContext'; +import { HasEffectsContext } from '../../ExecutionContext'; import { ObjectPath, PathTracker } from '../../utils/PathTracker'; import { LiteralValueOrUnknown, UnknownValue } from '../../values'; import SpreadElement from '../SpreadElement'; @@ -38,14 +38,14 @@ export class MultiExpression implements ExpressionEntity { ); } - hasEffectsWhenAccessedAtPath(path: ObjectPath, context: EffectsExecutionContext): boolean { + hasEffectsWhenAccessedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { for (const expression of this.expressions) { if (expression.hasEffectsWhenAccessedAtPath(path, context)) return true; } return false; } - hasEffectsWhenAssignedAtPath(path: ObjectPath, context: EffectsExecutionContext): boolean { + hasEffectsWhenAssignedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { for (const expression of this.expressions) { if (expression.hasEffectsWhenAssignedAtPath(path, context)) return true; } @@ -55,7 +55,7 @@ export class MultiExpression implements ExpressionEntity { hasEffectsWhenCalledAtPath( path: ObjectPath, callOptions: CallOptions, - context: EffectsExecutionContext + context: HasEffectsContext ): boolean { for (const expression of this.expressions) { if (expression.hasEffectsWhenCalledAtPath(path, callOptions, context)) return true; diff --git a/src/ast/nodes/shared/Node.ts b/src/ast/nodes/shared/Node.ts index d7afed326ea..ade408f6512 100644 --- a/src/ast/nodes/shared/Node.ts +++ b/src/ast/nodes/shared/Node.ts @@ -6,10 +6,10 @@ import CallOptions from '../../CallOptions'; import { DeoptimizableEntity } from '../../DeoptimizableEntity'; import { Entity } from '../../Entity'; import { - createEffectsExecutionContext, - createExecutionContext, - EffectsExecutionContext, - ExecutionContext + createHasEffectsContext, + createInclusionContext, + HasEffectsContext, + InclusionContext } from '../../ExecutionContext'; import { getAndCreateKeys, keys } from '../../keys'; import ChildScope from '../../scopes/ChildScope'; @@ -57,14 +57,14 @@ export interface Node extends Entity { * which only have an effect if their surrounding loop or switch statement is included. * The options pass on information like this about the current execution path. */ - hasEffects(context: EffectsExecutionContext): boolean; + hasEffects(context: HasEffectsContext): boolean; /** * Includes the node in the bundle. If the flag is not set, children are usually included * if they are necessary for this node (e.g. a function body) or if they have effects. * Necessary variables need to be included as well. */ - include(includeChildrenRecursively: IncludeChildren, context: ExecutionContext): void; + include(includeChildrenRecursively: IncludeChildren, context: InclusionContext): void; /** * Alternative version of include to override the default behaviour of @@ -73,7 +73,7 @@ export interface Node extends Entity { */ includeWithAllDeclaredVariables( includeChildrenRecursively: IncludeChildren, - context: ExecutionContext + context: InclusionContext ): void; render(code: MagicString, options: RenderOptions, nodeRenderOptions?: NodeRenderOptions): void; @@ -83,7 +83,7 @@ export interface Node extends Entity { * visits as the inclusion of additional variables may require the inclusion of more child * nodes in e.g. block statements. */ - shouldBeIncluded(context: ExecutionContext): boolean; + shouldBeIncluded(context: InclusionContext): boolean; } export interface StatementNode extends Node {} @@ -162,7 +162,7 @@ export class NodeBase implements ExpressionNode { return UNKNOWN_EXPRESSION; } - hasEffects(context: EffectsExecutionContext): boolean { + hasEffects(context: HasEffectsContext): boolean { for (const key of this.keys) { const value = (this as GenericEsTreeNode)[key]; if (value === null || key === 'annotations') continue; @@ -175,23 +175,23 @@ export class NodeBase implements ExpressionNode { return false; } - hasEffectsWhenAccessedAtPath(path: ObjectPath, _context: EffectsExecutionContext) { + hasEffectsWhenAccessedAtPath(path: ObjectPath, _context: HasEffectsContext) { return path.length > 0; } - hasEffectsWhenAssignedAtPath(_path: ObjectPath, _context: EffectsExecutionContext) { + hasEffectsWhenAssignedAtPath(_path: ObjectPath, _context: HasEffectsContext) { return true; } hasEffectsWhenCalledAtPath( _path: ObjectPath, _callOptions: CallOptions, - _context: EffectsExecutionContext + _context: HasEffectsContext ) { return true; } - include(includeChildrenRecursively: IncludeChildren, context: ExecutionContext) { + include(includeChildrenRecursively: IncludeChildren, context: InclusionContext) { this.included = true; for (const key of this.keys) { const value = (this as GenericEsTreeNode)[key]; @@ -208,13 +208,13 @@ export class NodeBase implements ExpressionNode { includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void { for (const arg of args) { - arg.include(false, createExecutionContext()); + arg.include(false, createInclusionContext()); } } includeWithAllDeclaredVariables( includeChildrenRecursively: IncludeChildren, - context: ExecutionContext + context: InclusionContext ) { this.include(includeChildrenRecursively, context); } @@ -277,10 +277,8 @@ export class NodeBase implements ExpressionNode { } } - shouldBeIncluded(context: ExecutionContext): boolean { - return ( - this.included || (!context.breakFlow && this.hasEffects(createEffectsExecutionContext())) - ); + shouldBeIncluded(context: InclusionContext): boolean { + return this.included || (!context.breakFlow && this.hasEffects(createHasEffectsContext())); } toString() { diff --git a/src/ast/scopes/FunctionScope.ts b/src/ast/scopes/FunctionScope.ts index 50503bb9bb0..598391d2300 100644 --- a/src/ast/scopes/FunctionScope.ts +++ b/src/ast/scopes/FunctionScope.ts @@ -1,5 +1,5 @@ import { AstContext } from '../../Module'; -import { createExecutionContext } from '../ExecutionContext'; +import { createInclusionContext } from '../ExecutionContext'; import { ExpressionNode } from '../nodes/shared/Node'; import SpreadElement from '../nodes/SpreadElement'; import ArgumentsVariable from '../variables/ArgumentsVariable'; @@ -26,7 +26,7 @@ export default class FunctionScope extends ReturnValueScope { if (this.argumentsVariable.included) { for (const arg of args) { if (!arg.included) { - arg.include(false, createExecutionContext()); + arg.include(false, createInclusionContext()); } } } diff --git a/src/ast/scopes/ParameterScope.ts b/src/ast/scopes/ParameterScope.ts index 0b698a6fc86..77952cd3df5 100644 --- a/src/ast/scopes/ParameterScope.ts +++ b/src/ast/scopes/ParameterScope.ts @@ -1,5 +1,5 @@ import { AstContext } from '../../Module'; -import { createExecutionContext } from '../ExecutionContext'; +import { createInclusionContext } from '../ExecutionContext'; import Identifier from '../nodes/Identifier'; import { ExpressionNode } from '../nodes/shared/Node'; import SpreadElement from '../nodes/SpreadElement'; @@ -65,11 +65,11 @@ export default class ParameterScope extends ChildScope { } } } - if (!argIncluded && arg.shouldBeIncluded(createExecutionContext())) { + if (!argIncluded && arg.shouldBeIncluded(createInclusionContext())) { argIncluded = true; } if (argIncluded) { - arg.include(calledFromTryStatement, createExecutionContext()); + arg.include(calledFromTryStatement, createInclusionContext()); } } } diff --git a/src/ast/values.ts b/src/ast/values.ts index 886053c203a..d696649ffd9 100644 --- a/src/ast/values.ts +++ b/src/ast/values.ts @@ -1,5 +1,5 @@ import CallOptions from './CallOptions'; -import { createExecutionContext, EffectsExecutionContext } from './ExecutionContext'; +import { createInclusionContext, HasEffectsContext } from './ExecutionContext'; import { LiteralValue } from './nodes/Literal'; import { ExpressionEntity } from './nodes/shared/Expression'; import { ExpressionNode } from './nodes/shared/Node'; @@ -41,7 +41,7 @@ export const UNKNOWN_EXPRESSION: ExpressionEntity = { include: () => {}, includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void { for (const arg of args) { - arg.include(false, createExecutionContext()); + arg.include(false, createInclusionContext()); } }, included: true, @@ -101,7 +101,7 @@ export class UnknownArrayExpression implements ExpressionEntity { hasEffectsWhenCalledAtPath( path: ObjectPath, callOptions: CallOptions, - context: EffectsExecutionContext + context: HasEffectsContext ) { if (path.length === 1) { return hasMemberEffectWhenCalled(arrayMembers, path[0], this.included, callOptions, context); @@ -115,7 +115,7 @@ export class UnknownArrayExpression implements ExpressionEntity { includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void { for (const arg of args) { - arg.include(false, createExecutionContext()); + arg.include(false, createInclusionContext()); } } @@ -178,7 +178,7 @@ const UNKNOWN_LITERAL_BOOLEAN: ExpressionEntity = { include: () => {}, includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void { for (const arg of args) { - arg.include(false, createExecutionContext()); + arg.include(false, createInclusionContext()); } }, included: true, @@ -223,7 +223,7 @@ const UNKNOWN_LITERAL_NUMBER: ExpressionEntity = { include: () => {}, includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void { for (const arg of args) { - arg.include(false, createExecutionContext()); + arg.include(false, createInclusionContext()); } }, included: true, @@ -275,7 +275,7 @@ const UNKNOWN_LITERAL_STRING: ExpressionEntity = { include: () => {}, includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void { for (const arg of args) { - arg.include(false, createExecutionContext()); + arg.include(false, createInclusionContext()); } }, included: true, @@ -318,7 +318,7 @@ export class UnknownObjectExpression implements ExpressionEntity { hasEffectsWhenCalledAtPath( path: ObjectPath, callOptions: CallOptions, - context: EffectsExecutionContext + context: HasEffectsContext ) { if (path.length === 1) { return hasMemberEffectWhenCalled(objectMembers, path[0], this.included, callOptions, context); @@ -332,7 +332,7 @@ export class UnknownObjectExpression implements ExpressionEntity { includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void { for (const arg of args) { - arg.include(false, createExecutionContext()); + arg.include(false, createInclusionContext()); } } @@ -456,7 +456,7 @@ export function hasMemberEffectWhenCalled( memberName: ObjectPathKey, parentIncluded: boolean, callOptions: CallOptions, - context: EffectsExecutionContext + context: HasEffectsContext ) { if ( typeof memberName !== 'string' || diff --git a/src/ast/variables/LocalVariable.ts b/src/ast/variables/LocalVariable.ts index b7cbfc63d34..3ca5ac7e19e 100644 --- a/src/ast/variables/LocalVariable.ts +++ b/src/ast/variables/LocalVariable.ts @@ -2,7 +2,7 @@ import Module, { AstContext } from '../../Module'; import { markModuleAndImpureDependenciesAsExecuted } from '../../utils/traverseStaticDependencies'; import CallOptions from '../CallOptions'; import { DeoptimizableEntity } from '../DeoptimizableEntity'; -import { createExecutionContext, EffectsExecutionContext } from '../ExecutionContext'; +import { createInclusionContext, HasEffectsContext } from '../ExecutionContext'; import ExportDefaultDeclaration from '../nodes/ExportDefaultDeclaration'; import Identifier from '../nodes/Identifier'; import * as NodeType from '../nodes/NodeType'; @@ -120,7 +120,7 @@ export default class LocalVariable extends Variable { return value; } - hasEffectsWhenAccessedAtPath(path: ObjectPath, context: EffectsExecutionContext) { + hasEffectsWhenAccessedAtPath(path: ObjectPath, context: HasEffectsContext) { if (path.length === 0) return false; if (this.isReassigned || path.length > MAX_PATH_DEPTH) return true; const trackedExpressions = context.accessed.getEntities(path); @@ -129,7 +129,7 @@ export default class LocalVariable extends Variable { return (this.init && this.init.hasEffectsWhenAccessedAtPath(path, context)) as boolean; } - hasEffectsWhenAssignedAtPath(path: ObjectPath, context: EffectsExecutionContext) { + hasEffectsWhenAssignedAtPath(path: ObjectPath, context: HasEffectsContext) { if (this.included || path.length > MAX_PATH_DEPTH) return true; if (path.length === 0) return false; if (this.isReassigned) return true; @@ -142,7 +142,7 @@ export default class LocalVariable extends Variable { hasEffectsWhenCalledAtPath( path: ObjectPath, callOptions: CallOptions, - context: EffectsExecutionContext + context: HasEffectsContext ) { if (path.length > MAX_PATH_DEPTH || this.isReassigned) return true; const trackedExpressions = (callOptions.withNew @@ -163,7 +163,7 @@ export default class LocalVariable extends Variable { } for (const declaration of this.declarations) { // If node is a default export, it can save a tree-shaking run to include the full declaration now - if (!declaration.included) declaration.include(false, createExecutionContext()); + if (!declaration.included) declaration.include(false, createInclusionContext()); let node = declaration.parent as Node; while (!node.included) { // We do not want to properly include parents in case they are part of a dead branch @@ -179,7 +179,7 @@ export default class LocalVariable extends Variable { includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void { if (this.isReassigned) { for (const arg of args) { - arg.include(false, createExecutionContext()); + arg.include(false, createInclusionContext()); } } else if (this.init) { this.init.includeCallArguments(args); diff --git a/src/ast/variables/ThisVariable.ts b/src/ast/variables/ThisVariable.ts index d1dc14c1d01..144b6baa75a 100644 --- a/src/ast/variables/ThisVariable.ts +++ b/src/ast/variables/ThisVariable.ts @@ -1,6 +1,6 @@ import { AstContext } from '../../Module'; import CallOptions from '../CallOptions'; -import { EffectsExecutionContext } from '../ExecutionContext'; +import { HasEffectsContext } from '../ExecutionContext'; import { ExpressionEntity } from '../nodes/shared/Expression'; import { ObjectPath } from '../utils/PathTracker'; import { LiteralValueOrUnknown, UNKNOWN_EXPRESSION, UnknownValue } from '../values'; @@ -15,14 +15,14 @@ export default class ThisVariable extends LocalVariable { return UnknownValue; } - hasEffectsWhenAccessedAtPath(path: ObjectPath, context: EffectsExecutionContext) { + hasEffectsWhenAccessedAtPath(path: ObjectPath, context: HasEffectsContext) { return ( this.getInit(context).hasEffectsWhenAccessedAtPath(path, context) || super.hasEffectsWhenAccessedAtPath(path, context) ); } - hasEffectsWhenAssignedAtPath(path: ObjectPath, context: EffectsExecutionContext) { + hasEffectsWhenAssignedAtPath(path: ObjectPath, context: HasEffectsContext) { return ( this.getInit(context).hasEffectsWhenAssignedAtPath(path, context) || super.hasEffectsWhenAssignedAtPath(path, context) @@ -32,7 +32,7 @@ export default class ThisVariable extends LocalVariable { hasEffectsWhenCalledAtPath( path: ObjectPath, callOptions: CallOptions, - context: EffectsExecutionContext + context: HasEffectsContext ) { return ( this.getInit(context).hasEffectsWhenCalledAtPath(path, callOptions, context) || @@ -40,7 +40,7 @@ export default class ThisVariable extends LocalVariable { ); } - private getInit(context: EffectsExecutionContext): ExpressionEntity { + private getInit(context: HasEffectsContext): ExpressionEntity { return context.replacedVariableInits.get(this) || UNKNOWN_EXPRESSION; } } diff --git a/src/ast/variables/Variable.ts b/src/ast/variables/Variable.ts index 1d7ea8e6398..9638fde713d 100644 --- a/src/ast/variables/Variable.ts +++ b/src/ast/variables/Variable.ts @@ -2,7 +2,7 @@ import ExternalModule from '../../ExternalModule'; import Module from '../../Module'; import CallOptions from '../CallOptions'; import { DeoptimizableEntity } from '../DeoptimizableEntity'; -import { createExecutionContext, EffectsExecutionContext } from '../ExecutionContext'; +import { createInclusionContext, HasEffectsContext } from '../ExecutionContext'; import Identifier from '../nodes/Identifier'; import { ExpressionEntity } from '../nodes/shared/Expression'; import { ExpressionNode } from '../nodes/shared/Node'; @@ -60,18 +60,18 @@ export default class Variable implements ExpressionEntity { return UNKNOWN_EXPRESSION; } - hasEffectsWhenAccessedAtPath(path: ObjectPath, _context: EffectsExecutionContext) { + hasEffectsWhenAccessedAtPath(path: ObjectPath, _context: HasEffectsContext) { return path.length > 0; } - hasEffectsWhenAssignedAtPath(_path: ObjectPath, _context: EffectsExecutionContext) { + hasEffectsWhenAssignedAtPath(_path: ObjectPath, _context: HasEffectsContext) { return true; } hasEffectsWhenCalledAtPath( _path: ObjectPath, _callOptions: CallOptions, - _context: EffectsExecutionContext + _context: HasEffectsContext ) { return true; } @@ -88,7 +88,7 @@ export default class Variable implements ExpressionEntity { includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void { for (const arg of args) { - arg.include(false, createExecutionContext()); + arg.include(false, createInclusionContext()); } } diff --git a/test/form/samples/break-control-flow/caught-errors/_expected.js b/test/form/samples/break-control-flow/caught-errors/_expected.js index 9a4f52330c8..db3d5a2dc25 100644 --- a/test/form/samples/break-control-flow/caught-errors/_expected.js +++ b/test/form/samples/break-control-flow/caught-errors/_expected.js @@ -43,3 +43,19 @@ function errorFinally() { try { errorFinally(); } catch {} + +function tryAfterError() { + console.log(hoisted1, hoisted2, hoisted3); + throw new Error(); + try { + var hoisted1; + } catch { + var hoisted2; + } finally { + var hoisted3; + } +} + +try { + tryAfterError(); +} catch {} diff --git a/test/form/samples/break-control-flow/caught-errors/main.js b/test/form/samples/break-control-flow/caught-errors/main.js index cb0b8ec3009..432952087f8 100644 --- a/test/form/samples/break-control-flow/caught-errors/main.js +++ b/test/form/samples/break-control-flow/caught-errors/main.js @@ -48,3 +48,23 @@ function errorFinally() { try { errorFinally(); } catch {} + +function tryAfterError() { + console.log(hoisted1, hoisted2, hoisted3); + throw new Error(); + try { + console.log('removed'); + var hoisted1; + } catch { + console.log('removed'); + var hoisted2; + } finally { + console.log('removed'); + var hoisted3; + } + console.log('removed'); +} + +try { + tryAfterError(); +} catch {} diff --git a/test/form/samples/break-control-flow/if-statement-errors/_expected.js b/test/form/samples/break-control-flow/if-statement-errors/_expected.js index aed68ea2b61..cc62b9924d5 100644 --- a/test/form/samples/break-control-flow/if-statement-errors/_expected.js +++ b/test/form/samples/break-control-flow/if-statement-errors/_expected.js @@ -47,6 +47,20 @@ try { unknownValueBoth(); } catch {} +function unknownValueAfterError() { + console.log(hoisted1, hoisted2); + throw new Error(); + if (globalThis.unknownValue) { + var hoisted1; + } else { + var hoisted2; + } +} + +try { + unknownValueAfterError(); +} catch {} + function truthyValueConsequent() { { throw new Error(); diff --git a/test/form/samples/break-control-flow/if-statement-errors/main.js b/test/form/samples/break-control-flow/if-statement-errors/main.js index 4ba50d20d9d..804d058fa8e 100644 --- a/test/form/samples/break-control-flow/if-statement-errors/main.js +++ b/test/form/samples/break-control-flow/if-statement-errors/main.js @@ -53,6 +53,23 @@ try { unknownValueBoth(); } catch {} +function unknownValueAfterError() { + console.log(hoisted1, hoisted2); + throw new Error(); + if (globalThis.unknownValue) { + console.log('removed'); + var hoisted1; + } else { + console.log('removed'); + var hoisted2; + } + console.log('removed'); +} + +try { + unknownValueAfterError(); +} catch {} + function truthyValueConsequent() { if (true) { throw new Error(); diff --git a/test/form/samples/break-control-flow/loop-errors/_expected.js b/test/form/samples/break-control-flow/loop-errors/_expected.js index 7207d1df48d..34484b2f1c7 100644 --- a/test/form/samples/break-control-flow/loop-errors/_expected.js +++ b/test/form/samples/break-control-flow/loop-errors/_expected.js @@ -1,8 +1,13 @@ function whileLoop() { + console.log(hoisted); while (globalThis.unknown) { throw new Error(); } console.log('retained'); + throw new Error(); + while (globalThis.unknown) { + var hoisted; + } } try { @@ -10,9 +15,13 @@ try { } catch {} function doWhileLoop() { + console.log(hoisted); do { throw new Error(); } while (globalThis.unknown); + do { + var hoisted; + } while (globalThis.unknown); } try { @@ -20,10 +29,15 @@ try { } catch {} function forLoop() { + console.log(hoisted); for (let i = 0; i < globalThis.unknown; i++) { throw new Error(); } console.log('retained'); + throw new Error(); + for (let i = 0; i < globalThis.unknown; i++) { + var hoisted; + } } try { @@ -31,10 +45,15 @@ try { } catch {} function forOfLoop() { + console.log(hoisted); for (const foo of globalThis.unknown) { throw new Error(); } console.log('retained'); + throw new Error(); + for (const foo of globalThis.unknown) { + var hoisted; + } } try { @@ -42,10 +61,15 @@ try { } catch {} function forInLoop() { + console.log(hoisted); for (const foo in globalThis.unknown) { throw new Error(); } console.log('retained'); + throw new Error(); + for (const foo in globalThis.unknown) { + var hoisted; + } } try { diff --git a/test/form/samples/break-control-flow/loop-errors/main.js b/test/form/samples/break-control-flow/loop-errors/main.js index faea99774fd..b60cfb9ec62 100644 --- a/test/form/samples/break-control-flow/loop-errors/main.js +++ b/test/form/samples/break-control-flow/loop-errors/main.js @@ -1,9 +1,16 @@ function whileLoop() { + console.log(hoisted); while (globalThis.unknown) { throw new Error(); console.log('removed'); } console.log('retained'); + throw new Error(); + while (globalThis.unknown) { + var hoisted; + console.log('removed'); + } + console.log('removed'); } try { @@ -11,11 +18,17 @@ try { } catch {} function doWhileLoop() { + console.log(hoisted); do { throw new Error(); console.log('removed'); } while (globalThis.unknown); console.log('removed'); + do { + var hoisted; + console.log('removed'); + } while (globalThis.unknown); + console.log('removed'); } try { @@ -23,11 +36,18 @@ try { } catch {} function forLoop() { + console.log(hoisted); for (let i = 0; i < globalThis.unknown; i++) { throw new Error(); console.log('removed'); } console.log('retained'); + throw new Error(); + for (let i = 0; i < globalThis.unknown; i++) { + var hoisted; + console.log('removed'); + } + console.log('removed'); } try { @@ -35,11 +55,18 @@ try { } catch {} function forOfLoop() { + console.log(hoisted); for (const foo of globalThis.unknown) { throw new Error(); console.log('removed'); } console.log('retained'); + throw new Error(); + for (const foo of globalThis.unknown) { + var hoisted; + console.log('removed'); + } + console.log('removed'); } try { @@ -47,11 +74,18 @@ try { } catch {} function forInLoop() { + console.log(hoisted); for (const foo in globalThis.unknown) { throw new Error(); console.log('removed'); } console.log('retained'); + throw new Error(); + for (const foo in globalThis.unknown) { + var hoisted; + console.log('removed'); + } + console.log('removed'); } try { diff --git a/test/form/samples/break-control-flow/switch-errors/_expected.js b/test/form/samples/break-control-flow/switch-errors/_expected.js index 4ea781f39d0..011035aae86 100644 --- a/test/form/samples/break-control-flow/switch-errors/_expected.js +++ b/test/form/samples/break-control-flow/switch-errors/_expected.js @@ -11,3 +11,16 @@ switch (globalThis.unknown) { console.log('retained'); } console.log('retained'); + +console.log(hoisted1, hoisted2, hoisted3); +throw new Error(); +switch (globalThis.unknown) { + case 1: + var hoisted1; + case 2: + var hoisted2; + case 3: + var hoisted3; + default: + +} diff --git a/test/form/samples/break-control-flow/switch-errors/main.js b/test/form/samples/break-control-flow/switch-errors/main.js index 0b072c19bc2..f381f6309cf 100644 --- a/test/form/samples/break-control-flow/switch-errors/main.js +++ b/test/form/samples/break-control-flow/switch-errors/main.js @@ -11,3 +11,20 @@ switch (globalThis.unknown) { console.log('retained'); } console.log('retained'); + +console.log(hoisted1, hoisted2, hoisted3); +throw new Error(); +switch (globalThis.unknown) { + case 1: + console.log('removed'); + var hoisted1; + case 2: + console.log('removed'); + var hoisted2; + case 3: + console.log('removed'); + var hoisted3; + default: + console.log('removed'); +} +console.log('removed'); From dc3a70679d4a2a43d044bd333aa19eacf94ece24 Mon Sep 17 00:00:00 2001 From: Lukas Taegert-Atkinson Date: Mon, 7 Oct 2019 08:33:27 +0200 Subject: [PATCH 17/35] Also break on return statements --- src/ast/ExecutionContext.ts | 10 ++++---- src/ast/nodes/IfStatement.ts | 9 ++++++-- src/ast/nodes/ReturnStatement.ts | 10 +++++++- src/ast/nodes/ThrowStatement.ts | 14 +++++------ .../return-statements/_config.js | 3 +++ .../return-statements/_expected.js | 20 ++++++++++++++++ .../return-statements/main.js | 23 +++++++++++++++++++ 7 files changed, 74 insertions(+), 15 deletions(-) create mode 100644 test/form/samples/break-control-flow/return-statements/_config.js create mode 100644 test/form/samples/break-control-flow/return-statements/_expected.js create mode 100644 test/form/samples/break-control-flow/return-statements/main.js diff --git a/src/ast/ExecutionContext.ts b/src/ast/ExecutionContext.ts index 48dbc8520ef..1e37b1d4e16 100644 --- a/src/ast/ExecutionContext.ts +++ b/src/ast/ExecutionContext.ts @@ -8,10 +8,10 @@ interface ExecutionContextIgnore { returnAwaitYield: boolean; } -export enum BreakFlow { - None = 0, - Error -} +export const BREAKFLOW_NONE: false = false; +export const BREAKFLOW_ERROR_RETURN: true = true; + +export type BreakFlow = typeof BREAKFLOW_NONE | typeof BREAKFLOW_ERROR_RETURN; export interface InclusionContext { breakFlow: BreakFlow; @@ -28,7 +28,7 @@ export interface HasEffectsContext { export function createInclusionContext(): InclusionContext { return { - breakFlow: BreakFlow.None + breakFlow: BREAKFLOW_NONE }; } diff --git a/src/ast/nodes/IfStatement.ts b/src/ast/nodes/IfStatement.ts index b76f9d3b89a..73bc29fb8cc 100644 --- a/src/ast/nodes/IfStatement.ts +++ b/src/ast/nodes/IfStatement.ts @@ -2,7 +2,12 @@ import MagicString from 'magic-string'; import { RenderOptions } from '../../utils/renderHelpers'; import { removeAnnotations } from '../../utils/treeshakeNode'; import { DeoptimizableEntity } from '../DeoptimizableEntity'; -import { BreakFlow, HasEffectsContext, InclusionContext } from '../ExecutionContext'; +import { + BreakFlow, + BREAKFLOW_NONE, + HasEffectsContext, + InclusionContext +} from '../ExecutionContext'; import { EMPTY_IMMUTABLE_TRACKER, EMPTY_PATH } from '../utils/PathTracker'; import { LiteralValueOrUnknown, UnknownValue } from '../values'; import * as NodeType from './NodeType'; @@ -63,7 +68,7 @@ export default class IfStatement extends StatementBase implements DeoptimizableE if (this.alternate !== null && this.alternate.shouldBeIncluded(context)) { this.alternate.include(false, context); if (!consequentBreakFlow) { - context.breakFlow = BreakFlow.None; + context.breakFlow = BREAKFLOW_NONE; } } } else { diff --git a/src/ast/nodes/ReturnStatement.ts b/src/ast/nodes/ReturnStatement.ts index 0b66db6186e..8826cf9aedd 100644 --- a/src/ast/nodes/ReturnStatement.ts +++ b/src/ast/nodes/ReturnStatement.ts @@ -1,6 +1,6 @@ import MagicString from 'magic-string'; import { RenderOptions } from '../../utils/renderHelpers'; -import { HasEffectsContext } from '../ExecutionContext'; +import { BREAKFLOW_ERROR_RETURN, HasEffectsContext, InclusionContext } from '../ExecutionContext'; import { UNKNOWN_EXPRESSION } from '../values'; import * as NodeType from './NodeType'; import { ExpressionNode, StatementBase } from './shared/Node'; @@ -16,6 +16,14 @@ export default class ReturnStatement extends StatementBase { ); } + include(includeChildrenRecursively: boolean | 'variables', context: InclusionContext) { + this.included = true; + if (this.argument) { + this.argument.include(includeChildrenRecursively, context); + } + context.breakFlow = BREAKFLOW_ERROR_RETURN; + } + initialise() { this.scope.addReturnExpression(this.argument || UNKNOWN_EXPRESSION); } diff --git a/src/ast/nodes/ThrowStatement.ts b/src/ast/nodes/ThrowStatement.ts index 93ffeabcf7c..4216c1e992d 100644 --- a/src/ast/nodes/ThrowStatement.ts +++ b/src/ast/nodes/ThrowStatement.ts @@ -1,6 +1,6 @@ import MagicString from 'magic-string'; import { RenderOptions } from '../../utils/renderHelpers'; -import { BreakFlow, InclusionContext } from '../ExecutionContext'; +import { BREAKFLOW_ERROR_RETURN, InclusionContext } from '../ExecutionContext'; import * as NodeType from './NodeType'; import { ExpressionNode, StatementBase } from './shared/Node'; @@ -12,13 +12,13 @@ export default class ThrowStatement extends StatementBase { return true; } - render(code: MagicString, options: RenderOptions) { - this.argument.render(code, options, { preventASI: true }); + include(includeChildrenRecursively: boolean | 'variables', context: InclusionContext) { + this.included = true; + this.argument.include(includeChildrenRecursively, context); + context.breakFlow = BREAKFLOW_ERROR_RETURN; } - shouldBeIncluded(context: InclusionContext): boolean { - if (context.breakFlow) return false; - context.breakFlow = BreakFlow.Error; - return true; + render(code: MagicString, options: RenderOptions) { + this.argument.render(code, options, { preventASI: true }); } } diff --git a/test/form/samples/break-control-flow/return-statements/_config.js b/test/form/samples/break-control-flow/return-statements/_config.js new file mode 100644 index 00000000000..f5f2cb40612 --- /dev/null +++ b/test/form/samples/break-control-flow/return-statements/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'breaks control flow when a return statement is encountered' +}; diff --git a/test/form/samples/break-control-flow/return-statements/_expected.js b/test/form/samples/break-control-flow/return-statements/_expected.js new file mode 100644 index 00000000000..afeab87c15b --- /dev/null +++ b/test/form/samples/break-control-flow/return-statements/_expected.js @@ -0,0 +1,20 @@ +function brokenFunction() { + console.log('retained'); + return; +} + +brokenFunction(); + +const brokenFunctionExpression = function() { + console.log('retained'); + return; +}; + +brokenFunctionExpression(); + +const brokenArrow = () => { + console.log('retained'); + return; +}; + +brokenArrow(); diff --git a/test/form/samples/break-control-flow/return-statements/main.js b/test/form/samples/break-control-flow/return-statements/main.js new file mode 100644 index 00000000000..a81d68ed135 --- /dev/null +++ b/test/form/samples/break-control-flow/return-statements/main.js @@ -0,0 +1,23 @@ +function brokenFunction() { + console.log('retained'); + return; + console.log('removed'); +} + +brokenFunction(); + +const brokenFunctionExpression = function() { + console.log('retained'); + return; + console.log('removed'); +}; + +brokenFunctionExpression(); + +const brokenArrow = () => { + console.log('retained'); + return; + console.log('removed'); +}; + +brokenArrow(); From 8abdb90f656e7915c2b2240fd7ecc9d732fe35d7 Mon Sep 17 00:00:00 2001 From: Lukas Taegert-Atkinson Date: Mon, 7 Oct 2019 11:04:01 +0200 Subject: [PATCH 18/35] Implement basic break support --- src/ast/ExecutionContext.ts | 2 +- src/ast/nodes/BlockStatement.ts | 5 +++ src/ast/nodes/BreakStatement.ts | 11 +++++- src/ast/nodes/DoWhileStatement.ts | 14 ++++++- .../break-statement-loops/_config.js | 3 ++ .../break-statement-loops/_expected.js | 28 ++++++++++++++ .../break-statement-loops/main.js | 37 +++++++++++++++++++ 7 files changed, 96 insertions(+), 4 deletions(-) create mode 100644 test/form/samples/break-control-flow/break-statement-loops/_config.js create mode 100644 test/form/samples/break-control-flow/break-statement-loops/_expected.js create mode 100644 test/form/samples/break-control-flow/break-statement-loops/main.js diff --git a/src/ast/ExecutionContext.ts b/src/ast/ExecutionContext.ts index 1e37b1d4e16..cd2926ed363 100644 --- a/src/ast/ExecutionContext.ts +++ b/src/ast/ExecutionContext.ts @@ -11,7 +11,7 @@ interface ExecutionContextIgnore { export const BREAKFLOW_NONE: false = false; export const BREAKFLOW_ERROR_RETURN: true = true; -export type BreakFlow = typeof BREAKFLOW_NONE | typeof BREAKFLOW_ERROR_RETURN; +export type BreakFlow = typeof BREAKFLOW_NONE | typeof BREAKFLOW_ERROR_RETURN | Set; export interface InclusionContext { breakFlow: BreakFlow; diff --git a/src/ast/nodes/BlockStatement.ts b/src/ast/nodes/BlockStatement.ts index 51eb133fc87..d4e0fb8b98d 100644 --- a/src/ast/nodes/BlockStatement.ts +++ b/src/ast/nodes/BlockStatement.ts @@ -25,6 +25,11 @@ export default class BlockStatement extends StatementBase { : new BlockScope(parentScope); } + // TODO Lukas check brokenFlow here as well + // simple logic: Break if flow is broken, as we only run this for non-included nodes + // Are there other places where this is relevant, everywhere we are using shouldBeIncluded? + // We definitely need to restore flow everywhere we do this for include + // TODO Lukas we could also eliminate trailing continue statements hasEffects(context: HasEffectsContext) { for (const node of this.body) { if (node.hasEffects(context)) return true; diff --git a/src/ast/nodes/BreakStatement.ts b/src/ast/nodes/BreakStatement.ts index f63fa23b2b4..54d8ca3886e 100644 --- a/src/ast/nodes/BreakStatement.ts +++ b/src/ast/nodes/BreakStatement.ts @@ -1,8 +1,9 @@ -import { HasEffectsContext } from '../ExecutionContext'; +import { HasEffectsContext, InclusionContext } from '../ExecutionContext'; import Identifier from './Identifier'; import * as NodeType from './NodeType'; import { StatementBase } from './shared/Node'; +// TODO Lukas also implement continue statements export default class BreakStatement extends StatementBase { label!: Identifier | null; type!: NodeType.tBreakStatement; @@ -14,4 +15,12 @@ export default class BreakStatement extends StatementBase { (this.label !== null && !context.ignore.labels.has(this.label.name)) ); } + + include(_includeChildrenRecursively: boolean | 'variables', context: InclusionContext) { + this.included = true; + if (this.label) { + this.label.include(); + } + context.breakFlow = new Set(); + } } diff --git a/src/ast/nodes/DoWhileStatement.ts b/src/ast/nodes/DoWhileStatement.ts index 3cd36e809b0..d22ebecfc17 100644 --- a/src/ast/nodes/DoWhileStatement.ts +++ b/src/ast/nodes/DoWhileStatement.ts @@ -1,6 +1,6 @@ -import { HasEffectsContext } from '../ExecutionContext'; +import { HasEffectsContext, InclusionContext } from '../ExecutionContext'; import * as NodeType from './NodeType'; -import { ExpressionNode, StatementBase, StatementNode } from './shared/Node'; +import { ExpressionNode, IncludeChildren, StatementBase, StatementNode } from './shared/Node'; export default class DoWhileStatement extends StatementBase { body!: StatementNode; @@ -17,4 +17,14 @@ export default class DoWhileStatement extends StatementBase { context.ignore.breakStatements = breakStatements; return false; } + + include(includeChildrenRecursively: IncludeChildren, context: InclusionContext) { + this.included = true; + const breakFlow = context.breakFlow; + this.test.include(includeChildrenRecursively, context); + this.body.include(includeChildrenRecursively, context); + if (context.breakFlow instanceof Set) { + context.breakFlow = breakFlow; + } + } } diff --git a/test/form/samples/break-control-flow/break-statement-loops/_config.js b/test/form/samples/break-control-flow/break-statement-loops/_config.js new file mode 100644 index 00000000000..dab03d4632d --- /dev/null +++ b/test/form/samples/break-control-flow/break-statement-loops/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'breaks control flow when a break statement is encountered inside a loop' +}; diff --git a/test/form/samples/break-control-flow/break-statement-loops/_expected.js b/test/form/samples/break-control-flow/break-statement-loops/_expected.js new file mode 100644 index 00000000000..7d2b05777ff --- /dev/null +++ b/test/form/samples/break-control-flow/break-statement-loops/_expected.js @@ -0,0 +1,28 @@ +while (globalThis.unknown) { + console.log('retained'); + break; +} + +while (globalThis.unknown) { + console.log('retained'); +} + +do { + console.log('retained'); + break; +} while (globalThis.unknown); + +for (let i = 0; i < globalThis.unknown; i++) { + console.log('retained'); + break; +} + +for (const foo of globalThis.unknown) { + console.log('retained'); + break; +} + +for (const foo in globalThis.unknown) { + console.log('retained'); + break; +} diff --git a/test/form/samples/break-control-flow/break-statement-loops/main.js b/test/form/samples/break-control-flow/break-statement-loops/main.js new file mode 100644 index 00000000000..2f8e255091a --- /dev/null +++ b/test/form/samples/break-control-flow/break-statement-loops/main.js @@ -0,0 +1,37 @@ +while (globalThis.unknown) { + console.log('retained'); + break; + continue; + console.log('removed'); +} + +while (globalThis.unknown) { + console.log('retained'); + continue; + break; + console.log('removed'); +} + +do { + console.log('retained'); + break; + console.log('removed'); +} while (globalThis.unknown); + +for (let i = 0; i < globalThis.unknown; i++) { + console.log('retained'); + break; + console.log('removed'); +} + +for (const foo of globalThis.unknown) { + console.log('retained'); + break; + console.log('removed'); +} + +for (const foo in globalThis.unknown) { + console.log('retained'); + break; + console.log('removed'); +} From bdbc7fd027dccc0dad26d9da1e1b1fcea7fa8994 Mon Sep 17 00:00:00 2001 From: Lukas Taegert-Atkinson Date: Mon, 7 Oct 2019 13:34:50 +0200 Subject: [PATCH 19/35] Implement continue statements and basic support --- src/ast/nodes/BreakStatement.ts | 2 -- src/ast/nodes/ContinueStatement.ts | 22 ++++++++++++++++++ src/ast/nodes/NodeType.ts | 2 ++ src/ast/nodes/index.ts | 2 ++ .../break-statement-loops/_expected.js | 1 + .../labeled-continue-statements/_config.js | 3 +++ .../labeled-continue-statements/_expected.js | 17 ++++++++++++++ .../labeled-continue-statements/main.js | 23 +++++++++++++++++++ 8 files changed, 70 insertions(+), 2 deletions(-) create mode 100644 src/ast/nodes/ContinueStatement.ts create mode 100644 test/form/samples/labeled-continue-statements/_config.js create mode 100644 test/form/samples/labeled-continue-statements/_expected.js create mode 100644 test/form/samples/labeled-continue-statements/main.js diff --git a/src/ast/nodes/BreakStatement.ts b/src/ast/nodes/BreakStatement.ts index 54d8ca3886e..ba24f2b6698 100644 --- a/src/ast/nodes/BreakStatement.ts +++ b/src/ast/nodes/BreakStatement.ts @@ -3,14 +3,12 @@ import Identifier from './Identifier'; import * as NodeType from './NodeType'; import { StatementBase } from './shared/Node'; -// TODO Lukas also implement continue statements export default class BreakStatement extends StatementBase { label!: Identifier | null; type!: NodeType.tBreakStatement; hasEffects(context: HasEffectsContext) { return ( - super.hasEffects(context) || !context.ignore.breakStatements || (this.label !== null && !context.ignore.labels.has(this.label.name)) ); diff --git a/src/ast/nodes/ContinueStatement.ts b/src/ast/nodes/ContinueStatement.ts new file mode 100644 index 00000000000..4acbc20cf4f --- /dev/null +++ b/src/ast/nodes/ContinueStatement.ts @@ -0,0 +1,22 @@ +import { HasEffectsContext, InclusionContext } from '../ExecutionContext'; +import Identifier from './Identifier'; +import * as NodeType from './NodeType'; +import { StatementBase } from './shared/Node'; + +export default class ContinueStatement extends StatementBase { + label!: Identifier | null; + type!: NodeType.tContinueStatement; + + hasEffects(context: HasEffectsContext) { + return ( + !context.ignore.breakStatements || + (this.label !== null && !context.ignore.labels.has(this.label.name)) + ); + } + + include(_includeChildrenRecursively: boolean | 'variables', context: InclusionContext) { + this.included = true; + if (this.label) this.label.include(); + context.breakFlow = new Set(); + } +} diff --git a/src/ast/nodes/NodeType.ts b/src/ast/nodes/NodeType.ts index 74dc1950b69..adb6a38d0c4 100644 --- a/src/ast/nodes/NodeType.ts +++ b/src/ast/nodes/NodeType.ts @@ -13,6 +13,7 @@ export type tClassBody = 'ClassBody'; export type tClassDeclaration = 'ClassDeclaration'; export type tClassExpression = 'ClassExpression'; export type tConditionalExpression = 'ConditionalExpression'; +export type tContinueStatement = 'ContinueStatement'; export type tDoWhileStatement = 'DoWhileStatement'; export type tEmptyStatement = 'EmptyStatement'; export type tExportAllDeclaration = 'ExportAllDeclaration'; @@ -78,6 +79,7 @@ export const ClassBody: tClassBody = 'ClassBody'; export const ClassDeclaration: tClassDeclaration = 'ClassDeclaration'; export const ClassExpression: tClassExpression = 'ClassExpression'; export const ConditionalExpression: tConditionalExpression = 'ConditionalExpression'; +export const ContinueStatement: tContinueStatement = 'ContinueStatement'; export const DoWhileStatement: tDoWhileStatement = 'DoWhileStatement'; export const EmptyStatement: tEmptyStatement = 'EmptyStatement'; export const ExportAllDeclaration: tExportAllDeclaration = 'ExportAllDeclaration'; diff --git a/src/ast/nodes/index.ts b/src/ast/nodes/index.ts index 6d629d152bb..d84aeaba725 100644 --- a/src/ast/nodes/index.ts +++ b/src/ast/nodes/index.ts @@ -13,6 +13,7 @@ import ClassBody from './ClassBody'; import ClassDeclaration from './ClassDeclaration'; import ClassExpression from './ClassExpression'; import ConditionalExpression from './ConditionalExpression'; +import ContinueStatement from './ContinueStatement'; import DoWhileStatement from './DoWhileStatement'; import EmptyStatement from './EmptyStatement'; import ExportAllDeclaration from './ExportAllDeclaration'; @@ -78,6 +79,7 @@ export const nodeConstructors: { ClassDeclaration, ClassExpression, ConditionalExpression, + ContinueStatement, DoWhileStatement, EmptyStatement, ExportAllDeclaration, diff --git a/test/form/samples/break-control-flow/break-statement-loops/_expected.js b/test/form/samples/break-control-flow/break-statement-loops/_expected.js index 7d2b05777ff..a4c62cab38c 100644 --- a/test/form/samples/break-control-flow/break-statement-loops/_expected.js +++ b/test/form/samples/break-control-flow/break-statement-loops/_expected.js @@ -5,6 +5,7 @@ while (globalThis.unknown) { while (globalThis.unknown) { console.log('retained'); + continue; } do { diff --git a/test/form/samples/labeled-continue-statements/_config.js b/test/form/samples/labeled-continue-statements/_config.js new file mode 100644 index 00000000000..3aa496920c8 --- /dev/null +++ b/test/form/samples/labeled-continue-statements/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'keep continue statements if their label is included' +}; diff --git a/test/form/samples/labeled-continue-statements/_expected.js b/test/form/samples/labeled-continue-statements/_expected.js new file mode 100644 index 00000000000..0be1828f5cf --- /dev/null +++ b/test/form/samples/labeled-continue-statements/_expected.js @@ -0,0 +1,17 @@ +const condition = globalThis.unknown; + +label1: while (condition) { + if (condition) { + continue label1; + } + console.log('effect'); +} + +label2: while (condition) { + while (condition) { + if (condition) { + continue label2; + } + } + console.log('effect'); +} diff --git a/test/form/samples/labeled-continue-statements/main.js b/test/form/samples/labeled-continue-statements/main.js new file mode 100644 index 00000000000..874fc9ec297 --- /dev/null +++ b/test/form/samples/labeled-continue-statements/main.js @@ -0,0 +1,23 @@ +const condition = globalThis.unknown; + +label1: while (condition) { + if (condition) { + continue label1; + } + console.log('effect'); +} + +label1NoEffect: while (condition) { + if (condition) { + continue label1NoEffect; + } +} + +label2: while (condition) { + while (condition) { + if (condition) { + continue label2; + } + } + console.log('effect'); +} From df74d6b79dc3e058ceace8b713e36c66e144d63f Mon Sep 17 00:00:00 2001 From: Lukas Taegert-Atkinson Date: Mon, 7 Oct 2019 17:46:01 +0200 Subject: [PATCH 20/35] Add basic label support --- src/ast/nodes/BreakStatement.ts | 8 +- src/ast/nodes/ContinueStatement.ts | 5 +- src/ast/nodes/IfStatement.ts | 78 ++++++++++++------- src/ast/nodes/LabeledStatement.ts | 13 +++- src/ast/nodes/ReturnStatement.ts | 4 +- src/ast/nodes/ThrowStatement.ts | 4 +- .../break-statement-labels/_config.js | 3 + .../break-statement-labels/_expected.js | 43 ++++++++++ .../break-statement-labels/main.js | 52 +++++++++++++ 9 files changed, 171 insertions(+), 39 deletions(-) create mode 100644 test/form/samples/break-control-flow/break-statement-labels/_config.js create mode 100644 test/form/samples/break-control-flow/break-statement-labels/_expected.js create mode 100644 test/form/samples/break-control-flow/break-statement-labels/main.js diff --git a/src/ast/nodes/BreakStatement.ts b/src/ast/nodes/BreakStatement.ts index ba24f2b6698..ef1c06843c3 100644 --- a/src/ast/nodes/BreakStatement.ts +++ b/src/ast/nodes/BreakStatement.ts @@ -1,7 +1,7 @@ import { HasEffectsContext, InclusionContext } from '../ExecutionContext'; import Identifier from './Identifier'; import * as NodeType from './NodeType'; -import { StatementBase } from './shared/Node'; +import { IncludeChildren, StatementBase } from './shared/Node'; export default class BreakStatement extends StatementBase { label!: Identifier | null; @@ -14,11 +14,13 @@ export default class BreakStatement extends StatementBase { ); } - include(_includeChildrenRecursively: boolean | 'variables', context: InclusionContext) { + include(_includeChildrenRecursively: IncludeChildren, context: InclusionContext) { this.included = true; if (this.label) { this.label.include(); + context.breakFlow = new Set([this.label.name]); + } else { + context.breakFlow = new Set([null]); } - context.breakFlow = new Set(); } } diff --git a/src/ast/nodes/ContinueStatement.ts b/src/ast/nodes/ContinueStatement.ts index 4acbc20cf4f..659221cdbf5 100644 --- a/src/ast/nodes/ContinueStatement.ts +++ b/src/ast/nodes/ContinueStatement.ts @@ -1,7 +1,7 @@ import { HasEffectsContext, InclusionContext } from '../ExecutionContext'; import Identifier from './Identifier'; import * as NodeType from './NodeType'; -import { StatementBase } from './shared/Node'; +import { IncludeChildren, StatementBase } from './shared/Node'; export default class ContinueStatement extends StatementBase { label!: Identifier | null; @@ -14,7 +14,8 @@ export default class ContinueStatement extends StatementBase { ); } - include(_includeChildrenRecursively: boolean | 'variables', context: InclusionContext) { + // TODO Lukas add label logic for continue + include(_includeChildrenRecursively: IncludeChildren, context: InclusionContext) { this.included = true; if (this.label) this.label.include(); context.breakFlow = new Set(); diff --git a/src/ast/nodes/IfStatement.ts b/src/ast/nodes/IfStatement.ts index 73bc29fb8cc..6c523010420 100644 --- a/src/ast/nodes/IfStatement.ts +++ b/src/ast/nodes/IfStatement.ts @@ -51,36 +51,11 @@ export default class IfStatement extends StatementBase implements DeoptimizableE include(includeChildrenRecursively: IncludeChildren, context: InclusionContext) { this.included = true; if (includeChildrenRecursively) { - this.test.include(includeChildrenRecursively, context); - this.consequent.include(includeChildrenRecursively, context); - if (this.alternate !== null) { - this.alternate.include(includeChildrenRecursively, context); - } + this.includeRecursively(includeChildrenRecursively, context); } else if (this.testValue === UnknownValue) { - this.test.include(false, context); - const breakFlow = context.breakFlow; - let consequentBreakFlow: BreakFlow | false = false; - if (this.consequent.shouldBeIncluded(context)) { - this.consequent.include(false, context); - consequentBreakFlow = context.breakFlow; - context.breakFlow = breakFlow; - } - if (this.alternate !== null && this.alternate.shouldBeIncluded(context)) { - this.alternate.include(false, context); - if (!consequentBreakFlow) { - context.breakFlow = BREAKFLOW_NONE; - } - } + this.includeUnknownTest(context); } else { - if (this.test.shouldBeIncluded(context)) { - this.test.include(false, context); - } - if (this.testValue && this.consequent.shouldBeIncluded(context)) { - this.consequent.include(false, context); - } - if (this.alternate !== null && !this.testValue && this.alternate.shouldBeIncluded(context)) { - this.alternate.include(false, context); - } + this.includeKnownTest(context); } } @@ -119,4 +94,51 @@ export default class IfStatement extends StatementBase implements DeoptimizableE } } } + private includeKnownTest(context: InclusionContext) { + if (this.test.shouldBeIncluded(context)) { + this.test.include(false, context); + } + if (this.testValue && this.consequent.shouldBeIncluded(context)) { + this.consequent.include(false, context); + } + if (this.alternate !== null && !this.testValue && this.alternate.shouldBeIncluded(context)) { + this.alternate.include(false, context); + } + } + + private includeRecursively( + includeChildrenRecursively: true | 'variables', + context: InclusionContext + ) { + this.test.include(includeChildrenRecursively, context); + this.consequent.include(includeChildrenRecursively, context); + if (this.alternate !== null) { + this.alternate.include(includeChildrenRecursively, context); + } + } + + private includeUnknownTest(context: InclusionContext) { + this.test.include(false, context); + const breakFlow = context.breakFlow; + let consequentBreakFlow: BreakFlow | false = false; + if (this.consequent.shouldBeIncluded(context)) { + this.consequent.include(false, context); + consequentBreakFlow = context.breakFlow; + context.breakFlow = breakFlow; + } + if (this.alternate !== null && this.alternate.shouldBeIncluded(context)) { + this.alternate.include(false, context); + if (!consequentBreakFlow || !context.breakFlow) { + context.breakFlow = BREAKFLOW_NONE; + } else if (consequentBreakFlow instanceof Set) { + if (context.breakFlow instanceof Set) { + for (const label of consequentBreakFlow) { + context.breakFlow.add(label); + } + } else { + context.breakFlow = consequentBreakFlow; + } + } + } + } } diff --git a/src/ast/nodes/LabeledStatement.ts b/src/ast/nodes/LabeledStatement.ts index 1c94d68b6f8..99c07fcdd82 100644 --- a/src/ast/nodes/LabeledStatement.ts +++ b/src/ast/nodes/LabeledStatement.ts @@ -1,7 +1,7 @@ -import { HasEffectsContext } from '../ExecutionContext'; +import { BREAKFLOW_NONE, HasEffectsContext, InclusionContext } from '../ExecutionContext'; import Identifier from './Identifier'; import * as NodeType from './NodeType'; -import { StatementBase, StatementNode } from './shared/Node'; +import { IncludeChildren, StatementBase, StatementNode } from './shared/Node'; export default class LabeledStatement extends StatementBase { body!: StatementNode; @@ -19,4 +19,13 @@ export default class LabeledStatement extends StatementBase { context.ignore.labels.delete(this.label.name); return false; } + + include(includeChildrenRecursively: IncludeChildren, context: InclusionContext) { + this.included = true; + this.label.include(); + this.body.include(includeChildrenRecursively, context); + if (context.breakFlow instanceof Set && context.breakFlow.has(this.label.name)) { + context.breakFlow = BREAKFLOW_NONE; + } + } } diff --git a/src/ast/nodes/ReturnStatement.ts b/src/ast/nodes/ReturnStatement.ts index 8826cf9aedd..34a57dca961 100644 --- a/src/ast/nodes/ReturnStatement.ts +++ b/src/ast/nodes/ReturnStatement.ts @@ -3,7 +3,7 @@ import { RenderOptions } from '../../utils/renderHelpers'; import { BREAKFLOW_ERROR_RETURN, HasEffectsContext, InclusionContext } from '../ExecutionContext'; import { UNKNOWN_EXPRESSION } from '../values'; import * as NodeType from './NodeType'; -import { ExpressionNode, StatementBase } from './shared/Node'; +import { ExpressionNode, IncludeChildren, StatementBase } from './shared/Node'; export default class ReturnStatement extends StatementBase { argument!: ExpressionNode | null; @@ -16,7 +16,7 @@ export default class ReturnStatement extends StatementBase { ); } - include(includeChildrenRecursively: boolean | 'variables', context: InclusionContext) { + include(includeChildrenRecursively: IncludeChildren, context: InclusionContext) { this.included = true; if (this.argument) { this.argument.include(includeChildrenRecursively, context); diff --git a/src/ast/nodes/ThrowStatement.ts b/src/ast/nodes/ThrowStatement.ts index 4216c1e992d..d4aec963d59 100644 --- a/src/ast/nodes/ThrowStatement.ts +++ b/src/ast/nodes/ThrowStatement.ts @@ -2,7 +2,7 @@ import MagicString from 'magic-string'; import { RenderOptions } from '../../utils/renderHelpers'; import { BREAKFLOW_ERROR_RETURN, InclusionContext } from '../ExecutionContext'; import * as NodeType from './NodeType'; -import { ExpressionNode, StatementBase } from './shared/Node'; +import { ExpressionNode, IncludeChildren, StatementBase } from './shared/Node'; export default class ThrowStatement extends StatementBase { argument!: ExpressionNode; @@ -12,7 +12,7 @@ export default class ThrowStatement extends StatementBase { return true; } - include(includeChildrenRecursively: boolean | 'variables', context: InclusionContext) { + include(includeChildrenRecursively: IncludeChildren, context: InclusionContext) { this.included = true; this.argument.include(includeChildrenRecursively, context); context.breakFlow = BREAKFLOW_ERROR_RETURN; diff --git a/test/form/samples/break-control-flow/break-statement-labels/_config.js b/test/form/samples/break-control-flow/break-statement-labels/_config.js new file mode 100644 index 00000000000..44fe205431f --- /dev/null +++ b/test/form/samples/break-control-flow/break-statement-labels/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'supports labels when breaking control flow' +}; diff --git a/test/form/samples/break-control-flow/break-statement-labels/_expected.js b/test/form/samples/break-control-flow/break-statement-labels/_expected.js new file mode 100644 index 00000000000..eaf303c0eec --- /dev/null +++ b/test/form/samples/break-control-flow/break-statement-labels/_expected.js @@ -0,0 +1,43 @@ +outer: { + inner: { + console.log('retained'); + break inner; + } + console.log('retained'); + break outer; +} + +outer: { + inner: { + console.log('retained'); + break outer; + } +} + +outer: { + inner: { + if (globalThis.unknown) break inner; + else break outer; + } + console.log('retained'); +} + +function withReturn() { + outer: { + inner: { + if (globalThis.unknown) break inner; + else return; + } + console.log('retained'); + } + outer: { + inner: { + return; + } + } +} + +withReturn(); + +// TODO Lukas do-while-loop support +// TODO Lukas switch statements diff --git a/test/form/samples/break-control-flow/break-statement-labels/main.js b/test/form/samples/break-control-flow/break-statement-labels/main.js new file mode 100644 index 00000000000..37b50e18144 --- /dev/null +++ b/test/form/samples/break-control-flow/break-statement-labels/main.js @@ -0,0 +1,52 @@ +outer: { + inner: { + console.log('retained'); + break inner; + console.log('removed'); + } + console.log('retained'); + break outer; + console.log('removed'); +} + +outer: { + inner: { + console.log('retained'); + break outer; + console.log('removed'); + } + console.log('removed'); +} + +outer: { + inner: { + if (globalThis.unknown) break inner; + else break outer; + console.log('removed'); + } + console.log('retained'); +} + +function withReturn() { + outer: { + inner: { + if (globalThis.unknown) break inner; + else return; + console.log('removed'); + } + console.log('retained'); + } + outer: { + inner: { + return; + break inner; + console.log('removed'); + } + console.log('removed'); + } +} + +withReturn(); + +// TODO Lukas do-while-loop support +// TODO Lukas switch statements From 2aa22d896eb11271138683849efc728b347684c5 Mon Sep 17 00:00:00 2001 From: Lukas Taegert-Atkinson Date: Tue, 8 Oct 2019 17:35:21 +0200 Subject: [PATCH 21/35] Refine label support --- src/ast/nodes/ContinueStatement.ts | 9 ++-- src/ast/nodes/DoWhileStatement.ts | 2 +- src/ast/nodes/IfStatement.ts | 1 + .../_config.js | 4 ++ .../_expected.js | 39 +++++++++++++++ .../break-statement-labels-do-while/main.js | 47 +++++++++++++++++++ .../break-statement-labels/_expected.js | 3 -- .../break-statement-labels/main.js | 3 -- 8 files changed, 98 insertions(+), 10 deletions(-) create mode 100644 test/form/samples/break-control-flow/break-statement-labels-do-while/_config.js create mode 100644 test/form/samples/break-control-flow/break-statement-labels-do-while/_expected.js create mode 100644 test/form/samples/break-control-flow/break-statement-labels-do-while/main.js diff --git a/src/ast/nodes/ContinueStatement.ts b/src/ast/nodes/ContinueStatement.ts index 659221cdbf5..ccd5d1ab44f 100644 --- a/src/ast/nodes/ContinueStatement.ts +++ b/src/ast/nodes/ContinueStatement.ts @@ -14,10 +14,13 @@ export default class ContinueStatement extends StatementBase { ); } - // TODO Lukas add label logic for continue include(_includeChildrenRecursively: IncludeChildren, context: InclusionContext) { this.included = true; - if (this.label) this.label.include(); - context.breakFlow = new Set(); + if (this.label) { + this.label.include(); + context.breakFlow = new Set([this.label.name]); + } else { + context.breakFlow = new Set([null]); + } } } diff --git a/src/ast/nodes/DoWhileStatement.ts b/src/ast/nodes/DoWhileStatement.ts index d22ebecfc17..7f6863cad2a 100644 --- a/src/ast/nodes/DoWhileStatement.ts +++ b/src/ast/nodes/DoWhileStatement.ts @@ -23,7 +23,7 @@ export default class DoWhileStatement extends StatementBase { const breakFlow = context.breakFlow; this.test.include(includeChildrenRecursively, context); this.body.include(includeChildrenRecursively, context); - if (context.breakFlow instanceof Set) { + if (context.breakFlow instanceof Set && context.breakFlow.has(null)) { context.breakFlow = breakFlow; } } diff --git a/src/ast/nodes/IfStatement.ts b/src/ast/nodes/IfStatement.ts index 6c523010420..0b12cf54b75 100644 --- a/src/ast/nodes/IfStatement.ts +++ b/src/ast/nodes/IfStatement.ts @@ -94,6 +94,7 @@ export default class IfStatement extends StatementBase implements DeoptimizableE } } } + private includeKnownTest(context: InclusionContext) { if (this.test.shouldBeIncluded(context)) { this.test.include(false, context); diff --git a/test/form/samples/break-control-flow/break-statement-labels-do-while/_config.js b/test/form/samples/break-control-flow/break-statement-labels-do-while/_config.js new file mode 100644 index 00000000000..ce5b2a9897f --- /dev/null +++ b/test/form/samples/break-control-flow/break-statement-labels-do-while/_config.js @@ -0,0 +1,4 @@ +// TODO Lukas switch statements +module.exports = { + description: 'supports labels when breaking control flow from a do-while loop' +}; diff --git a/test/form/samples/break-control-flow/break-statement-labels-do-while/_expected.js b/test/form/samples/break-control-flow/break-statement-labels-do-while/_expected.js new file mode 100644 index 00000000000..2cf01cd7f7b --- /dev/null +++ b/test/form/samples/break-control-flow/break-statement-labels-do-while/_expected.js @@ -0,0 +1,39 @@ +label: do { + do { + break; + } while (globalThis.unknown()); + console.log('retained'); +} while (globalThis.unknown()); + +do { + label: do { + break; + } while (globalThis.unknown()); + console.log('retained'); +} while (globalThis.unknown()); + +label: do { + do { + break label; + } while (globalThis.unknown()); +} while (globalThis.unknown()); + +label: do { + do { + continue; + } while (globalThis.unknown()); + console.log('retained'); +} while (globalThis.unknown()); + +do { + label: do { + continue; + } while (globalThis.unknown()); + console.log('retained'); +} while (globalThis.unknown()); + +label: do { + do { + continue label; + } while (globalThis.unknown()); +} while (globalThis.unknown()); diff --git a/test/form/samples/break-control-flow/break-statement-labels-do-while/main.js b/test/form/samples/break-control-flow/break-statement-labels-do-while/main.js new file mode 100644 index 00000000000..e7af91e67ff --- /dev/null +++ b/test/form/samples/break-control-flow/break-statement-labels-do-while/main.js @@ -0,0 +1,47 @@ +label: do { + do { + break; + console.log('removed'); + } while (globalThis.unknown()); + console.log('retained'); +} while (globalThis.unknown()); + +do { + label: do { + break; + console.log('removed'); + } while (globalThis.unknown()); + console.log('retained'); +} while (globalThis.unknown()); + +label: do { + do { + break label; + console.log('removed'); + } while (globalThis.unknown()); + console.log('removed'); +} while (globalThis.unknown()); + +label: do { + do { + continue; + console.log('removed'); + } while (globalThis.unknown()); + console.log('retained'); +} while (globalThis.unknown()); + +do { + label: do { + continue; + console.log('removed'); + } while (globalThis.unknown()); + console.log('retained'); +} while (globalThis.unknown()); + +label: do { + do { + continue label; + console.log('removed'); + } while (globalThis.unknown()); + console.log('removed'); +} while (globalThis.unknown()); diff --git a/test/form/samples/break-control-flow/break-statement-labels/_expected.js b/test/form/samples/break-control-flow/break-statement-labels/_expected.js index eaf303c0eec..3596b60d120 100644 --- a/test/form/samples/break-control-flow/break-statement-labels/_expected.js +++ b/test/form/samples/break-control-flow/break-statement-labels/_expected.js @@ -38,6 +38,3 @@ function withReturn() { } withReturn(); - -// TODO Lukas do-while-loop support -// TODO Lukas switch statements diff --git a/test/form/samples/break-control-flow/break-statement-labels/main.js b/test/form/samples/break-control-flow/break-statement-labels/main.js index 37b50e18144..d9e181bb685 100644 --- a/test/form/samples/break-control-flow/break-statement-labels/main.js +++ b/test/form/samples/break-control-flow/break-statement-labels/main.js @@ -47,6 +47,3 @@ function withReturn() { } withReturn(); - -// TODO Lukas do-while-loop support -// TODO Lukas switch statements From d888d4fa2f1370c8c456d7ed210cd1eaaa338991 Mon Sep 17 00:00:00 2001 From: Lukas Taegert-Atkinson Date: Wed, 9 Oct 2019 07:04:22 +0200 Subject: [PATCH 22/35] Improve switch-statement handling --- src/ast/nodes/BlockStatement.ts | 1 - src/ast/nodes/IfStatement.ts | 10 +-- src/ast/nodes/SwitchStatement.ts | 25 +++++- .../_config.js | 1 - .../break-statement-labels-switch/_config.js | 3 + .../_expected.js | 85 ++++++++++++++++++ .../break-statement-labels-switch/main.js | 88 +++++++++++++++++++ 7 files changed, 205 insertions(+), 8 deletions(-) create mode 100644 test/form/samples/break-control-flow/break-statement-labels-switch/_config.js create mode 100644 test/form/samples/break-control-flow/break-statement-labels-switch/_expected.js create mode 100644 test/form/samples/break-control-flow/break-statement-labels-switch/main.js diff --git a/src/ast/nodes/BlockStatement.ts b/src/ast/nodes/BlockStatement.ts index d4e0fb8b98d..a7d04203633 100644 --- a/src/ast/nodes/BlockStatement.ts +++ b/src/ast/nodes/BlockStatement.ts @@ -29,7 +29,6 @@ export default class BlockStatement extends StatementBase { // simple logic: Break if flow is broken, as we only run this for non-included nodes // Are there other places where this is relevant, everywhere we are using shouldBeIncluded? // We definitely need to restore flow everywhere we do this for include - // TODO Lukas we could also eliminate trailing continue statements hasEffects(context: HasEffectsContext) { for (const node of this.body) { if (node.hasEffects(context)) return true; diff --git a/src/ast/nodes/IfStatement.ts b/src/ast/nodes/IfStatement.ts index 0b12cf54b75..3b25e151178 100644 --- a/src/ast/nodes/IfStatement.ts +++ b/src/ast/nodes/IfStatement.ts @@ -129,16 +129,16 @@ export default class IfStatement extends StatementBase implements DeoptimizableE } if (this.alternate !== null && this.alternate.shouldBeIncluded(context)) { this.alternate.include(false, context); - if (!consequentBreakFlow || !context.breakFlow) { + if (!(consequentBreakFlow && context.breakFlow)) { context.breakFlow = BREAKFLOW_NONE; - } else if (consequentBreakFlow instanceof Set) { - if (context.breakFlow instanceof Set) { + } else if (context.breakFlow instanceof Set) { + if (consequentBreakFlow instanceof Set) { for (const label of consequentBreakFlow) { context.breakFlow.add(label); } - } else { - context.breakFlow = consequentBreakFlow; } + } else { + context.breakFlow = consequentBreakFlow; } } } diff --git a/src/ast/nodes/SwitchStatement.ts b/src/ast/nodes/SwitchStatement.ts index ef6f3ed23fb..5b5381244c0 100644 --- a/src/ast/nodes/SwitchStatement.ts +++ b/src/ast/nodes/SwitchStatement.ts @@ -1,4 +1,10 @@ -import { HasEffectsContext, InclusionContext } from '../ExecutionContext'; +import { + BreakFlow, + BREAKFLOW_ERROR_RETURN, + BREAKFLOW_NONE, + HasEffectsContext, + InclusionContext +} from '../ExecutionContext'; import BlockScope from '../scopes/BlockScope'; import Scope from '../scopes/Scope'; import * as NodeType from './NodeType'; @@ -26,9 +32,26 @@ export default class SwitchStatement extends StatementBase { this.included = true; this.discriminant.include(includeChildrenRecursively, context); const breakFlow = context.breakFlow; + let hasDefault = false; + let minBreakFlow: BreakFlow | false = BREAKFLOW_ERROR_RETURN; for (const switchCase of this.cases) { + if (switchCase.test === null) hasDefault = true; switchCase.include(includeChildrenRecursively, context); + if (!(minBreakFlow && context.breakFlow)) { + minBreakFlow = BREAKFLOW_NONE; + } else if (minBreakFlow instanceof Set) { + if (context.breakFlow instanceof Set) { + for (const label of context.breakFlow) { + minBreakFlow.add(label); + } + } + } else { + minBreakFlow = context.breakFlow; + } context.breakFlow = breakFlow; } + if (hasDefault && !(minBreakFlow instanceof Set && minBreakFlow.has(null))) { + context.breakFlow = minBreakFlow; + } } } diff --git a/test/form/samples/break-control-flow/break-statement-labels-do-while/_config.js b/test/form/samples/break-control-flow/break-statement-labels-do-while/_config.js index ce5b2a9897f..8b990af0d2f 100644 --- a/test/form/samples/break-control-flow/break-statement-labels-do-while/_config.js +++ b/test/form/samples/break-control-flow/break-statement-labels-do-while/_config.js @@ -1,4 +1,3 @@ -// TODO Lukas switch statements module.exports = { description: 'supports labels when breaking control flow from a do-while loop' }; diff --git a/test/form/samples/break-control-flow/break-statement-labels-switch/_config.js b/test/form/samples/break-control-flow/break-statement-labels-switch/_config.js new file mode 100644 index 00000000000..ffce5a641d6 --- /dev/null +++ b/test/form/samples/break-control-flow/break-statement-labels-switch/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'supports labels when breaking control flow from a switch statement' +}; diff --git a/test/form/samples/break-control-flow/break-statement-labels-switch/_expected.js b/test/form/samples/break-control-flow/break-statement-labels-switch/_expected.js new file mode 100644 index 00000000000..d3c0d4bd128 --- /dev/null +++ b/test/form/samples/break-control-flow/break-statement-labels-switch/_expected.js @@ -0,0 +1,85 @@ +function returnAll() { + switch (globalThis.unknown) { + case 1: + console.log('retained'); + return; + + case 2: + console.log('retained'); + return; + + default: + console.log('retained'); + return; + + } +} + +returnAll(); + +function returnNoDefault() { + switch (globalThis.unknown) { + case 1: + console.log('retained'); + return; + + case 2: + console.log('retained'); + return; + + } + console.log('retained'); +} + +returnNoDefault(); + +function returnSomeBreak() { + switch (globalThis.unknown) { + case 1: + console.log('retained'); + return; + + case 2: + console.log('retained'); + break; + + default: + console.log('retained'); + return; + + } + console.log('retained'); +} + +returnSomeBreak(); + +function returnBreakDifferentLabels() { + outer: { + inner: { + switch (globalThis.unknown) { + case 1: + console.log('retained'); + break outer; + + case 2: + console.log('retained'); + break inner; + + default: + console.log('retained'); + break outer; + + } + } + console.log('retained'); + } + console.log('retained'); +} + +returnBreakDifferentLabels(); + +function empty() { + console.log('retained'); +} + +empty(); diff --git a/test/form/samples/break-control-flow/break-statement-labels-switch/main.js b/test/form/samples/break-control-flow/break-statement-labels-switch/main.js new file mode 100644 index 00000000000..a7b4992a5f6 --- /dev/null +++ b/test/form/samples/break-control-flow/break-statement-labels-switch/main.js @@ -0,0 +1,88 @@ +function returnAll() { + switch (globalThis.unknown) { + case 1: + console.log('retained'); + return; + console.log('removed'); + case 2: + console.log('retained'); + return; + console.log('removed'); + default: + console.log('retained'); + return; + console.log('removed'); + } + console.log('removed'); +} + +returnAll(); + +function returnNoDefault() { + switch (globalThis.unknown) { + case 1: + console.log('retained'); + return; + console.log('removed'); + case 2: + console.log('retained'); + return; + console.log('removed'); + } + console.log('retained'); +} + +returnNoDefault(); + +function returnSomeBreak() { + switch (globalThis.unknown) { + case 1: + console.log('retained'); + return; + console.log('removed'); + case 2: + console.log('retained'); + break; + console.log('removed'); + default: + console.log('retained'); + return; + console.log('removed'); + } + console.log('retained'); +} + +returnSomeBreak(); + +function returnBreakDifferentLabels() { + outer: { + inner: { + switch (globalThis.unknown) { + case 1: + console.log('retained'); + break outer; + console.log('removed'); + case 2: + console.log('retained'); + break inner; + console.log('removed'); + default: + console.log('retained'); + break outer; + console.log('removed'); + } + console.log('removed'); + } + console.log('retained'); + } + console.log('retained'); +} + +returnBreakDifferentLabels(); + +function empty() { + switch (globalThis.unknown) {} + console.log('retained'); +} + +empty(); From 4a207ce804d369fee12b3c61b362a651c3273d2e Mon Sep 17 00:00:00 2001 From: Lukas Taegert-Atkinson Date: Thu, 10 Oct 2019 07:24:29 +0200 Subject: [PATCH 23/35] Ignore side-effects after broken control flow, treat hoisted functions as initialized --- src/ast/ExecutionContext.ts | 3 +- src/ast/nodes/ArrowFunctionExpression.ts | 3 +- src/ast/nodes/BlockStatement.ts | 5 +- src/ast/nodes/BreakStatement.ts | 15 +++--- src/ast/nodes/ContinueStatement.ts | 15 +++--- src/ast/nodes/DoWhileStatement.ts | 6 ++- src/ast/nodes/ForInStatement.ts | 4 +- src/ast/nodes/ForOfStatement.ts | 2 +- src/ast/nodes/ForStatement.ts | 4 +- src/ast/nodes/Identifier.ts | 4 +- src/ast/nodes/IfStatement.ts | 37 +++++++++----- src/ast/nodes/LabeledStatement.ts | 3 ++ src/ast/nodes/ReturnStatement.ts | 7 ++- src/ast/nodes/SwitchCase.ts | 11 +++- src/ast/nodes/SwitchStatement.ts | 51 ++++++++++++++----- src/ast/nodes/WhileStatement.ts | 4 +- src/ast/nodes/shared/FunctionNode.ts | 3 +- src/ast/scopes/BlockScope.ts | 9 +++- src/ast/scopes/CatchScope.ts | 4 +- src/ast/scopes/Scope.ts | 2 +- .../_expected.js | 32 ++---------- .../break-statement-labels-do-while/main.js | 24 ++++----- .../_expected.js | 18 ++++--- .../break-statement-labels-switch/main.js | 48 +++++++++++++---- .../break-statement-labels/_expected.js | 9 ++++ .../break-statement-labels/main.js | 21 ++++++++ .../break-statement-loops/_expected.js | 27 ++++++++++ .../break-statement-loops/main.js | 50 ++++++++++++++++++ .../return-statements/_expected.js | 3 ++ .../return-statements/main.js | 24 +++++++++ 30 files changed, 327 insertions(+), 121 deletions(-) diff --git a/src/ast/ExecutionContext.ts b/src/ast/ExecutionContext.ts index cd2926ed363..dffd20fcc42 100644 --- a/src/ast/ExecutionContext.ts +++ b/src/ast/ExecutionContext.ts @@ -17,7 +17,7 @@ export interface InclusionContext { breakFlow: BreakFlow; } -export interface HasEffectsContext { +export interface HasEffectsContext extends InclusionContext { accessed: PathTracker; assigned: PathTracker; called: PathTracker; @@ -36,6 +36,7 @@ export function createHasEffectsContext(): HasEffectsContext { return { accessed: new PathTracker(), assigned: new PathTracker(), + breakFlow: BREAKFLOW_NONE, called: new PathTracker(), ignore: { breakStatements: false, diff --git a/src/ast/nodes/ArrowFunctionExpression.ts b/src/ast/nodes/ArrowFunctionExpression.ts index b1f265d2c65..68431574379 100644 --- a/src/ast/nodes/ArrowFunctionExpression.ts +++ b/src/ast/nodes/ArrowFunctionExpression.ts @@ -56,7 +56,7 @@ export default class ArrowFunctionExpression extends NodeBase { for (const param of this.params) { if (param.hasEffects(context)) return true; } - const ignore = context.ignore; + const { ignore, breakFlow } = context; context.ignore = { breakStatements: false, labels: new Set(), @@ -64,6 +64,7 @@ export default class ArrowFunctionExpression extends NodeBase { }; if (this.body.hasEffects(context)) return true; context.ignore = ignore; + context.breakFlow = breakFlow; return false; } diff --git a/src/ast/nodes/BlockStatement.ts b/src/ast/nodes/BlockStatement.ts index a7d04203633..5a978290e1f 100644 --- a/src/ast/nodes/BlockStatement.ts +++ b/src/ast/nodes/BlockStatement.ts @@ -25,13 +25,10 @@ export default class BlockStatement extends StatementBase { : new BlockScope(parentScope); } - // TODO Lukas check brokenFlow here as well - // simple logic: Break if flow is broken, as we only run this for non-included nodes - // Are there other places where this is relevant, everywhere we are using shouldBeIncluded? - // We definitely need to restore flow everywhere we do this for include hasEffects(context: HasEffectsContext) { for (const node of this.body) { if (node.hasEffects(context)) return true; + if (context.breakFlow) break; } return false; } diff --git a/src/ast/nodes/BreakStatement.ts b/src/ast/nodes/BreakStatement.ts index ef1c06843c3..04b428aa43e 100644 --- a/src/ast/nodes/BreakStatement.ts +++ b/src/ast/nodes/BreakStatement.ts @@ -8,19 +8,18 @@ export default class BreakStatement extends StatementBase { type!: NodeType.tBreakStatement; hasEffects(context: HasEffectsContext) { - return ( + if ( !context.ignore.breakStatements || (this.label !== null && !context.ignore.labels.has(this.label.name)) - ); + ) + return true; + context.breakFlow = new Set([this.label && this.label.name]); + return false; } include(_includeChildrenRecursively: IncludeChildren, context: InclusionContext) { this.included = true; - if (this.label) { - this.label.include(); - context.breakFlow = new Set([this.label.name]); - } else { - context.breakFlow = new Set([null]); - } + if (this.label) this.label.include(); + context.breakFlow = new Set([this.label && this.label.name]); } } diff --git a/src/ast/nodes/ContinueStatement.ts b/src/ast/nodes/ContinueStatement.ts index ccd5d1ab44f..eeac6a74a36 100644 --- a/src/ast/nodes/ContinueStatement.ts +++ b/src/ast/nodes/ContinueStatement.ts @@ -8,19 +8,18 @@ export default class ContinueStatement extends StatementBase { type!: NodeType.tContinueStatement; hasEffects(context: HasEffectsContext) { - return ( + if ( !context.ignore.breakStatements || (this.label !== null && !context.ignore.labels.has(this.label.name)) - ); + ) + return true; + context.breakFlow = new Set([this.label && this.label.name]); + return false; } include(_includeChildrenRecursively: IncludeChildren, context: InclusionContext) { this.included = true; - if (this.label) { - this.label.include(); - context.breakFlow = new Set([this.label.name]); - } else { - context.breakFlow = new Set([null]); - } + if (this.label) this.label.include(); + context.breakFlow = new Set([this.label && this.label.name]); } } diff --git a/src/ast/nodes/DoWhileStatement.ts b/src/ast/nodes/DoWhileStatement.ts index 7f6863cad2a..8013d7930fc 100644 --- a/src/ast/nodes/DoWhileStatement.ts +++ b/src/ast/nodes/DoWhileStatement.ts @@ -10,18 +10,22 @@ export default class DoWhileStatement extends StatementBase { hasEffects(context: HasEffectsContext): boolean { if (this.test.hasEffects(context)) return true; const { + breakFlow, ignore: { breakStatements } } = context; context.ignore.breakStatements = true; if (this.body.hasEffects(context)) return true; context.ignore.breakStatements = breakStatements; + if (context.breakFlow instanceof Set && context.breakFlow.has(null)) { + context.breakFlow = breakFlow; + } return false; } include(includeChildrenRecursively: IncludeChildren, context: InclusionContext) { this.included = true; - const breakFlow = context.breakFlow; this.test.include(includeChildrenRecursively, context); + const breakFlow = context.breakFlow; this.body.include(includeChildrenRecursively, context); if (context.breakFlow instanceof Set && context.breakFlow.has(null)) { context.breakFlow = breakFlow; diff --git a/src/ast/nodes/ForInStatement.ts b/src/ast/nodes/ForInStatement.ts index 55ef4136be1..cde527b95c5 100644 --- a/src/ast/nodes/ForInStatement.ts +++ b/src/ast/nodes/ForInStatement.ts @@ -35,20 +35,22 @@ export default class ForInStatement extends StatementBase { ) return true; const { + breakFlow, ignore: { breakStatements } } = context; context.ignore.breakStatements = true; if (this.body.hasEffects(context)) return true; context.ignore.breakStatements = breakStatements; + context.breakFlow = breakFlow; return false; } include(includeChildrenRecursively: IncludeChildren, context: InclusionContext) { this.included = true; - const breakFlow = context.breakFlow; this.left.includeWithAllDeclaredVariables(includeChildrenRecursively, context); this.left.deoptimizePath(EMPTY_PATH); this.right.include(includeChildrenRecursively, context); + const breakFlow = context.breakFlow; this.body.include(includeChildrenRecursively, context); context.breakFlow = breakFlow; } diff --git a/src/ast/nodes/ForOfStatement.ts b/src/ast/nodes/ForOfStatement.ts index ab2ebf18688..5dcd246fbe7 100644 --- a/src/ast/nodes/ForOfStatement.ts +++ b/src/ast/nodes/ForOfStatement.ts @@ -34,10 +34,10 @@ export default class ForOfStatement extends StatementBase { include(includeChildrenRecursively: IncludeChildren, context: InclusionContext) { this.included = true; - const breakFlow = context.breakFlow; this.left.includeWithAllDeclaredVariables(includeChildrenRecursively, context); this.left.deoptimizePath(EMPTY_PATH); this.right.include(includeChildrenRecursively, context); + const breakFlow = context.breakFlow; this.body.include(includeChildrenRecursively, context); context.breakFlow = breakFlow; } diff --git a/src/ast/nodes/ForStatement.ts b/src/ast/nodes/ForStatement.ts index a5bac7b19ce..54d3277a648 100644 --- a/src/ast/nodes/ForStatement.ts +++ b/src/ast/nodes/ForStatement.ts @@ -26,19 +26,21 @@ export default class ForStatement extends StatementBase { ) return true; const { + breakFlow, ignore: { breakStatements } } = context; context.ignore.breakStatements = true; if (this.body.hasEffects(context)) return true; context.ignore.breakStatements = breakStatements; + context.breakFlow = breakFlow; return false; } include(includeChildrenRecursively: IncludeChildren, context: InclusionContext) { this.included = true; - const breakFlow = context.breakFlow; if (this.init) this.init.include(includeChildrenRecursively, context); if (this.test) this.test.include(includeChildrenRecursively, context); + const breakFlow = context.breakFlow; if (this.update) this.update.include(includeChildrenRecursively, context); if (this.body) this.body.include(includeChildrenRecursively, context); context.breakFlow = breakFlow; diff --git a/src/ast/nodes/Identifier.ts b/src/ast/nodes/Identifier.ts index 5b922f2194c..0c27acc83fe 100644 --- a/src/ast/nodes/Identifier.ts +++ b/src/ast/nodes/Identifier.ts @@ -52,9 +52,11 @@ export default class Identifier extends NodeBase implements PatternNode { let variable: LocalVariable; switch (kind) { case 'var': - case 'function': variable = this.scope.addDeclaration(this, this.context, init, true); break; + case 'function': + variable = this.scope.addDeclaration(this, this.context, init, 'function'); + break; case 'let': case 'const': case 'class': diff --git a/src/ast/nodes/IfStatement.ts b/src/ast/nodes/IfStatement.ts index 3b25e151178..f6dc382cd6f 100644 --- a/src/ast/nodes/IfStatement.ts +++ b/src/ast/nodes/IfStatement.ts @@ -38,10 +38,14 @@ export default class IfStatement extends StatementBase implements DeoptimizableE hasEffects(context: HasEffectsContext): boolean { if (this.test.hasEffects(context)) return true; if (this.testValue === UnknownValue) { - return ( - this.consequent.hasEffects(context) || - (this.alternate !== null && this.alternate.hasEffects(context)) - ); + const breakFlow = context.breakFlow; + if (this.consequent.hasEffects(context)) return true; + const consequentBreakFlow = context.breakFlow; + context.breakFlow = breakFlow; + if (this.alternate === null) return false; + if (this.alternate.hasEffects(context)) return true; + this.updateBreakFlowUnknownCondition(consequentBreakFlow, context); + return false; } return this.testValue ? this.consequent.hasEffects(context) @@ -129,17 +133,24 @@ export default class IfStatement extends StatementBase implements DeoptimizableE } if (this.alternate !== null && this.alternate.shouldBeIncluded(context)) { this.alternate.include(false, context); - if (!(consequentBreakFlow && context.breakFlow)) { - context.breakFlow = BREAKFLOW_NONE; - } else if (context.breakFlow instanceof Set) { - if (consequentBreakFlow instanceof Set) { - for (const label of consequentBreakFlow) { - context.breakFlow.add(label); - } + this.updateBreakFlowUnknownCondition(consequentBreakFlow, context); + } + } + + private updateBreakFlowUnknownCondition( + consequentBreakFlow: BreakFlow | false, + context: InclusionContext + ) { + if (!(consequentBreakFlow && context.breakFlow)) { + context.breakFlow = BREAKFLOW_NONE; + } else if (context.breakFlow instanceof Set) { + if (consequentBreakFlow instanceof Set) { + for (const label of consequentBreakFlow) { + context.breakFlow.add(label); } - } else { - context.breakFlow = consequentBreakFlow; } + } else { + context.breakFlow = consequentBreakFlow; } } } diff --git a/src/ast/nodes/LabeledStatement.ts b/src/ast/nodes/LabeledStatement.ts index 99c07fcdd82..9fa22c18a49 100644 --- a/src/ast/nodes/LabeledStatement.ts +++ b/src/ast/nodes/LabeledStatement.ts @@ -17,6 +17,9 @@ export default class LabeledStatement extends StatementBase { if (this.body.hasEffects(context)) return true; context.ignore.breakStatements = breakStatements; context.ignore.labels.delete(this.label.name); + if (context.breakFlow instanceof Set && context.breakFlow.has(this.label.name)) { + context.breakFlow = BREAKFLOW_NONE; + } return false; } diff --git a/src/ast/nodes/ReturnStatement.ts b/src/ast/nodes/ReturnStatement.ts index 34a57dca961..3f50b39bedd 100644 --- a/src/ast/nodes/ReturnStatement.ts +++ b/src/ast/nodes/ReturnStatement.ts @@ -10,10 +10,13 @@ export default class ReturnStatement extends StatementBase { type!: NodeType.tReturnStatement; hasEffects(context: HasEffectsContext) { - return ( + if ( !context.ignore.returnAwaitYield || (this.argument !== null && this.argument.hasEffects(context)) - ); + ) + return true; + context.breakFlow = BREAKFLOW_ERROR_RETURN; + return false; } include(includeChildrenRecursively: IncludeChildren, context: InclusionContext) { diff --git a/src/ast/nodes/SwitchCase.ts b/src/ast/nodes/SwitchCase.ts index ab1ddd3e83e..a1cecb38418 100644 --- a/src/ast/nodes/SwitchCase.ts +++ b/src/ast/nodes/SwitchCase.ts @@ -4,7 +4,7 @@ import { RenderOptions, renderStatementList } from '../../utils/renderHelpers'; -import { InclusionContext } from '../ExecutionContext'; +import { HasEffectsContext, InclusionContext } from '../ExecutionContext'; import * as NodeType from './NodeType'; import { ExpressionNode, IncludeChildren, NodeBase, StatementNode } from './shared/Node'; @@ -13,6 +13,15 @@ export default class SwitchCase extends NodeBase { test!: ExpressionNode | null; type!: NodeType.tSwitchCase; + hasEffects(context: HasEffectsContext): boolean { + if (this.test && this.test.hasEffects(context)) return true; + for (const node of this.consequent) { + if (context.breakFlow) break; + if (node.hasEffects(context)) return true; + } + return false; + } + include(includeChildrenRecursively: IncludeChildren, context: InclusionContext) { this.included = true; if (this.test) this.test.include(includeChildrenRecursively, context); diff --git a/src/ast/nodes/SwitchStatement.ts b/src/ast/nodes/SwitchStatement.ts index 5b5381244c0..60fce96f3a7 100644 --- a/src/ast/nodes/SwitchStatement.ts +++ b/src/ast/nodes/SwitchStatement.ts @@ -21,10 +21,25 @@ export default class SwitchStatement extends StatementBase { } hasEffects(context: HasEffectsContext) { - const ignoreBreakStatements = context.ignore.breakStatements; + if (this.discriminant.hasEffects(context)) return true; + const { + breakFlow, + ignore: { breakStatements } + } = context; + let hasDefault = false; + let minBreakFlow: BreakFlow | false = BREAKFLOW_ERROR_RETURN; context.ignore.breakStatements = true; - if (super.hasEffects(context)) return true; - context.ignore.breakStatements = ignoreBreakStatements; + for (const switchCase of this.cases) { + if (switchCase.hasEffects(context)) return true; + if (switchCase.test === null) hasDefault = true; + minBreakFlow = this.getMinBreakflowAfterCase(minBreakFlow, context); + context.breakFlow = breakFlow; + context.breakFlow = breakFlow; + } + if (hasDefault && !(minBreakFlow instanceof Set && minBreakFlow.has(null))) { + context.breakFlow = minBreakFlow; + } + context.ignore.breakStatements = breakStatements; return false; } @@ -37,21 +52,29 @@ export default class SwitchStatement extends StatementBase { for (const switchCase of this.cases) { if (switchCase.test === null) hasDefault = true; switchCase.include(includeChildrenRecursively, context); - if (!(minBreakFlow && context.breakFlow)) { - minBreakFlow = BREAKFLOW_NONE; - } else if (minBreakFlow instanceof Set) { - if (context.breakFlow instanceof Set) { - for (const label of context.breakFlow) { - minBreakFlow.add(label); - } - } - } else { - minBreakFlow = context.breakFlow; - } + minBreakFlow = this.getMinBreakflowAfterCase(minBreakFlow, context); context.breakFlow = breakFlow; } if (hasDefault && !(minBreakFlow instanceof Set && minBreakFlow.has(null))) { context.breakFlow = minBreakFlow; } } + + private getMinBreakflowAfterCase( + minBreakFlow: BreakFlow | false, + context: InclusionContext + ): BreakFlow | false { + if (!(minBreakFlow && context.breakFlow)) { + return BREAKFLOW_NONE; + } + if (minBreakFlow instanceof Set) { + if (context.breakFlow instanceof Set) { + for (const label of context.breakFlow) { + minBreakFlow.add(label); + } + } + return minBreakFlow; + } + return context.breakFlow; + } } diff --git a/src/ast/nodes/WhileStatement.ts b/src/ast/nodes/WhileStatement.ts index b9004a49479..e387000c995 100644 --- a/src/ast/nodes/WhileStatement.ts +++ b/src/ast/nodes/WhileStatement.ts @@ -10,18 +10,20 @@ export default class WhileStatement extends StatementBase { hasEffects(context: HasEffectsContext): boolean { if (this.test.hasEffects(context)) return true; const { + breakFlow, ignore: { breakStatements } } = context; context.ignore.breakStatements = true; if (this.body.hasEffects(context)) return true; context.ignore.breakStatements = breakStatements; + context.breakFlow = breakFlow; return false; } include(includeChildrenRecursively: IncludeChildren, context: InclusionContext) { this.included = true; - const breakFlow = context.breakFlow; this.test.include(includeChildrenRecursively, context); + const breakFlow = context.breakFlow; this.body.include(includeChildrenRecursively, context); context.breakFlow = breakFlow; } diff --git a/src/ast/nodes/shared/FunctionNode.ts b/src/ast/nodes/shared/FunctionNode.ts index 15c9c54a40d..3e978d041f1 100644 --- a/src/ast/nodes/shared/FunctionNode.ts +++ b/src/ast/nodes/shared/FunctionNode.ts @@ -76,13 +76,14 @@ export default class FunctionNode extends NodeBase { this.scope.thisVariable, callOptions.withNew ? new UnknownObjectExpression() : UNKNOWN_EXPRESSION ); - const ignore = context.ignore; + const { breakFlow, ignore } = context; context.ignore = { breakStatements: false, labels: new Set(), returnAwaitYield: true }; if (this.body.hasEffects(context)) return true; + context.breakFlow = breakFlow; if (thisInit) { context.replacedVariableInits.set(this.scope.thisVariable, thisInit); } else { diff --git a/src/ast/scopes/BlockScope.ts b/src/ast/scopes/BlockScope.ts index 494d7979924..cfff83b1f72 100644 --- a/src/ast/scopes/BlockScope.ts +++ b/src/ast/scopes/BlockScope.ts @@ -10,10 +10,15 @@ export default class BlockScope extends ChildScope { identifier: Identifier, context: AstContext, init: ExpressionEntity | null = null, - isHoisted = false + isHoisted: boolean | 'function' = false ): LocalVariable { if (isHoisted) { - return this.parent.addDeclaration(identifier, context, UNKNOWN_EXPRESSION, true); + return this.parent.addDeclaration( + identifier, + context, + isHoisted === 'function' ? init : UNKNOWN_EXPRESSION, + isHoisted + ); } else { return super.addDeclaration(identifier, context, init, false); } diff --git a/src/ast/scopes/CatchScope.ts b/src/ast/scopes/CatchScope.ts index 109d3da8f25..57883f3c15c 100644 --- a/src/ast/scopes/CatchScope.ts +++ b/src/ast/scopes/CatchScope.ts @@ -9,10 +9,10 @@ export default class CatchScope extends ParameterScope { identifier: Identifier, context: AstContext, init: ExpressionEntity | null = null, - isHoisted = false + isHoisted: boolean | 'function' = false ): LocalVariable { if (isHoisted) { - return this.parent.addDeclaration(identifier, context, init, true); + return this.parent.addDeclaration(identifier, context, init, isHoisted); } else { return super.addDeclaration(identifier, context, init, false); } diff --git a/src/ast/scopes/Scope.ts b/src/ast/scopes/Scope.ts index e38a5b068bc..9b5b5bd9739 100644 --- a/src/ast/scopes/Scope.ts +++ b/src/ast/scopes/Scope.ts @@ -14,7 +14,7 @@ export default class Scope { identifier: Identifier, context: AstContext, init: ExpressionEntity | null = null, - _isHoisted: boolean + _isHoisted: boolean | 'function' ) { const name = identifier.name; let variable = this.variables.get(name) as LocalVariable; diff --git a/test/form/samples/break-control-flow/break-statement-labels-do-while/_expected.js b/test/form/samples/break-control-flow/break-statement-labels-do-while/_expected.js index 2cf01cd7f7b..26d7470af53 100644 --- a/test/form/samples/break-control-flow/break-statement-labels-do-while/_expected.js +++ b/test/form/samples/break-control-flow/break-statement-labels-do-while/_expected.js @@ -1,39 +1,15 @@ label: do { - do { - break; - } while (globalThis.unknown()); console.log('retained'); -} while (globalThis.unknown()); +} while (globalThis.unknown); do { - label: do { - break; - } while (globalThis.unknown()); console.log('retained'); -} while (globalThis.unknown()); +} while (globalThis.unknown); label: do { - do { - break label; - } while (globalThis.unknown()); -} while (globalThis.unknown()); - -label: do { - do { - continue; - } while (globalThis.unknown()); console.log('retained'); -} while (globalThis.unknown()); +} while (globalThis.unknown); do { - label: do { - continue; - } while (globalThis.unknown()); console.log('retained'); -} while (globalThis.unknown()); - -label: do { - do { - continue label; - } while (globalThis.unknown()); -} while (globalThis.unknown()); +} while (globalThis.unknown); diff --git a/test/form/samples/break-control-flow/break-statement-labels-do-while/main.js b/test/form/samples/break-control-flow/break-statement-labels-do-while/main.js index e7af91e67ff..d2862b47745 100644 --- a/test/form/samples/break-control-flow/break-statement-labels-do-while/main.js +++ b/test/form/samples/break-control-flow/break-statement-labels-do-while/main.js @@ -2,46 +2,46 @@ label: do { do { break; console.log('removed'); - } while (globalThis.unknown()); + } while (globalThis.unknown); console.log('retained'); -} while (globalThis.unknown()); +} while (globalThis.unknown); do { label: do { break; console.log('removed'); - } while (globalThis.unknown()); + } while (globalThis.unknown); console.log('retained'); -} while (globalThis.unknown()); +} while (globalThis.unknown); label: do { do { break label; console.log('removed'); - } while (globalThis.unknown()); + } while (globalThis.unknown); console.log('removed'); -} while (globalThis.unknown()); +} while (globalThis.unknown); label: do { do { continue; console.log('removed'); - } while (globalThis.unknown()); + } while (globalThis.unknown); console.log('retained'); -} while (globalThis.unknown()); +} while (globalThis.unknown); do { label: do { continue; console.log('removed'); - } while (globalThis.unknown()); + } while (globalThis.unknown); console.log('retained'); -} while (globalThis.unknown()); +} while (globalThis.unknown); label: do { do { continue label; console.log('removed'); - } while (globalThis.unknown()); + } while (globalThis.unknown); console.log('removed'); -} while (globalThis.unknown()); +} while (globalThis.unknown); diff --git a/test/form/samples/break-control-flow/break-statement-labels-switch/_expected.js b/test/form/samples/break-control-flow/break-statement-labels-switch/_expected.js index d3c0d4bd128..40b1a67a1ec 100644 --- a/test/form/samples/break-control-flow/break-statement-labels-switch/_expected.js +++ b/test/form/samples/break-control-flow/break-statement-labels-switch/_expected.js @@ -17,14 +17,16 @@ function returnAll() { returnAll(); +{ + console.log('retained'); +} + function returnNoDefault() { switch (globalThis.unknown) { case 1: - console.log('retained'); return; case 2: - console.log('retained'); return; } @@ -36,15 +38,12 @@ returnNoDefault(); function returnSomeBreak() { switch (globalThis.unknown) { case 1: - console.log('retained'); return; case 2: - console.log('retained'); break; default: - console.log('retained'); return; } @@ -53,20 +52,23 @@ function returnSomeBreak() { returnSomeBreak(); +function allBreak() { + console.log('retained'); +} + +allBreak(); + function returnBreakDifferentLabels() { outer: { inner: { switch (globalThis.unknown) { case 1: - console.log('retained'); break outer; case 2: - console.log('retained'); break inner; default: - console.log('retained'); break outer; } diff --git a/test/form/samples/break-control-flow/break-statement-labels-switch/main.js b/test/form/samples/break-control-flow/break-statement-labels-switch/main.js index a7b4992a5f6..480a547205f 100644 --- a/test/form/samples/break-control-flow/break-statement-labels-switch/main.js +++ b/test/form/samples/break-control-flow/break-statement-labels-switch/main.js @@ -18,14 +18,32 @@ function returnAll() { returnAll(); +{ + function returnAllRemoved() { + switch (globalThis.unknown) { + case 1: + return; + console.log('removed'); + case 2: + return; + console.log('removed'); + default: + return; + console.log('removed'); + } + console.log('removed'); + } + console.log('retained'); + + returnAllRemoved(); +} + function returnNoDefault() { switch (globalThis.unknown) { case 1: - console.log('retained'); return; console.log('removed'); case 2: - console.log('retained'); return; console.log('removed'); } @@ -37,15 +55,12 @@ returnNoDefault(); function returnSomeBreak() { switch (globalThis.unknown) { case 1: - console.log('retained'); return; console.log('removed'); case 2: - console.log('retained'); break; console.log('removed'); default: - console.log('retained'); return; console.log('removed'); } @@ -54,20 +69,34 @@ function returnSomeBreak() { returnSomeBreak(); +function allBreak() { + label: switch (globalThis.unknown) { + case 1: + break label; + console.log('removed'); + case 2: + break; + console.log('removed'); + default: + break label; + console.log('removed'); + } + console.log('retained'); +} + +allBreak(); + function returnBreakDifferentLabels() { outer: { inner: { switch (globalThis.unknown) { case 1: - console.log('retained'); break outer; console.log('removed'); case 2: - console.log('retained'); break inner; console.log('removed'); default: - console.log('retained'); break outer; console.log('removed'); } @@ -81,7 +110,8 @@ function returnBreakDifferentLabels() { returnBreakDifferentLabels(); function empty() { - switch (globalThis.unknown) {} + switch (globalThis.unknown) { + } console.log('retained'); } diff --git a/test/form/samples/break-control-flow/break-statement-labels/_expected.js b/test/form/samples/break-control-flow/break-statement-labels/_expected.js index 3596b60d120..81784a01622 100644 --- a/test/form/samples/break-control-flow/break-statement-labels/_expected.js +++ b/test/form/samples/break-control-flow/break-statement-labels/_expected.js @@ -7,6 +7,11 @@ outer: { break outer; } +outer: { + console.log('retained'); + break outer; +} + outer: { inner: { console.log('retained'); @@ -14,6 +19,10 @@ outer: { } } +{ + console.log('retained'); +} + outer: { inner: { if (globalThis.unknown) break inner; diff --git a/test/form/samples/break-control-flow/break-statement-labels/main.js b/test/form/samples/break-control-flow/break-statement-labels/main.js index d9e181bb685..336ae3797ee 100644 --- a/test/form/samples/break-control-flow/break-statement-labels/main.js +++ b/test/form/samples/break-control-flow/break-statement-labels/main.js @@ -9,6 +9,16 @@ outer: { console.log('removed'); } +outer: { + inner: { + break inner; + console.log('removed'); + } + console.log('retained'); + break outer; + console.log('removed'); +} + outer: { inner: { console.log('retained'); @@ -18,6 +28,17 @@ outer: { console.log('removed'); } +{ + outer: { + inner: { + break outer; + console.log('removed'); + } + console.log('removed'); + } + console.log('retained'); +} + outer: { inner: { if (globalThis.unknown) break inner; diff --git a/test/form/samples/break-control-flow/break-statement-loops/_expected.js b/test/form/samples/break-control-flow/break-statement-loops/_expected.js index a4c62cab38c..6db3aa1b163 100644 --- a/test/form/samples/break-control-flow/break-statement-loops/_expected.js +++ b/test/form/samples/break-control-flow/break-statement-loops/_expected.js @@ -3,27 +3,54 @@ while (globalThis.unknown) { break; } +{ + console.log('retained'); +} + while (globalThis.unknown) { console.log('retained'); continue; } +{ + console.log('retained'); +} + do { console.log('retained'); break; } while (globalThis.unknown); +{ + console.log('retained'); +} + for (let i = 0; i < globalThis.unknown; i++) { console.log('retained'); break; } +{ + console.log('retained'); +} + for (const foo of globalThis.unknown) { console.log('retained'); break; } +{ + for (const foo of globalThis.unknown) { + break; + } + console.log('retained'); +} + for (const foo in globalThis.unknown) { console.log('retained'); break; } + +{ + console.log('retained'); +} diff --git a/test/form/samples/break-control-flow/break-statement-loops/main.js b/test/form/samples/break-control-flow/break-statement-loops/main.js index 2f8e255091a..080ea651e48 100644 --- a/test/form/samples/break-control-flow/break-statement-loops/main.js +++ b/test/form/samples/break-control-flow/break-statement-loops/main.js @@ -5,6 +5,15 @@ while (globalThis.unknown) { console.log('removed'); } +{ + while (globalThis.unknown) { + break; + continue; + console.log('removed'); + } + console.log('retained'); +} + while (globalThis.unknown) { console.log('retained'); continue; @@ -12,26 +21,67 @@ while (globalThis.unknown) { console.log('removed'); } +{ + while (globalThis.unknown) { + continue; + break; + console.log('removed'); + } + console.log('retained'); +} + do { console.log('retained'); break; console.log('removed'); } while (globalThis.unknown); +{ + do { + break; + console.log('removed'); + } while (globalThis.unknown); + console.log('retained'); +} + for (let i = 0; i < globalThis.unknown; i++) { console.log('retained'); break; console.log('removed'); } +{ + for (let i = 0; i < globalThis.unknown; i++) { + break; + console.log('removed'); + } + console.log('retained'); +} + for (const foo of globalThis.unknown) { console.log('retained'); break; console.log('removed'); } +{ + for (const foo of globalThis.unknown) { + break; + console.log('removed'); + } + console.log('retained'); +} + for (const foo in globalThis.unknown) { console.log('retained'); break; console.log('removed'); } + +{ + for (const foo in globalThis.unknown) { + break; + console.log('removed'); + } + console.log('retained'); +} diff --git a/test/form/samples/break-control-flow/return-statements/_expected.js b/test/form/samples/break-control-flow/return-statements/_expected.js index afeab87c15b..08f55dfbcb4 100644 --- a/test/form/samples/break-control-flow/return-statements/_expected.js +++ b/test/form/samples/break-control-flow/return-statements/_expected.js @@ -18,3 +18,6 @@ const brokenArrow = () => { }; brokenArrow(); +console.log('retained'); +console.log('retained'); +console.log('retained'); diff --git a/test/form/samples/break-control-flow/return-statements/main.js b/test/form/samples/break-control-flow/return-statements/main.js index a81d68ed135..adab939b120 100644 --- a/test/form/samples/break-control-flow/return-statements/main.js +++ b/test/form/samples/break-control-flow/return-statements/main.js @@ -21,3 +21,27 @@ const brokenArrow = () => { }; brokenArrow(); + +function brokenFunctionRemoved() { + return; + console.log('removed'); +} + +brokenFunctionRemoved(); +console.log('retained'); + +const brokenFunctionExpressionRemoved = function() { + return; + console.log('removed'); +}; + +brokenFunctionExpressionRemoved(); +console.log('retained'); + +const brokenArrowRemoved = () => { + return; + console.log('removed'); +}; + +brokenArrowRemoved(); +console.log('retained'); From da66c108a5d92b7c59705f77d6817998ae333ed3 Mon Sep 17 00:00:00 2001 From: Lukas Taegert-Atkinson Date: Fri, 11 Oct 2019 07:19:10 +0200 Subject: [PATCH 24/35] Make sure labeled statements do not swallow other break statements --- src/ast/nodes/LabeledStatement.ts | 5 -- .../_config.js | 3 ++ .../_expected.js | 35 +++++++++++++ .../break-statement-labels-in-loops/main.js | 49 +++++++++++++++++++ 4 files changed, 87 insertions(+), 5 deletions(-) create mode 100644 test/form/samples/break-control-flow/break-statement-labels-in-loops/_config.js create mode 100644 test/form/samples/break-control-flow/break-statement-labels-in-loops/_expected.js create mode 100644 test/form/samples/break-control-flow/break-statement-labels-in-loops/main.js diff --git a/src/ast/nodes/LabeledStatement.ts b/src/ast/nodes/LabeledStatement.ts index 9fa22c18a49..19776e0c5dc 100644 --- a/src/ast/nodes/LabeledStatement.ts +++ b/src/ast/nodes/LabeledStatement.ts @@ -9,13 +9,8 @@ export default class LabeledStatement extends StatementBase { type!: NodeType.tLabeledStatement; hasEffects(context: HasEffectsContext) { - const { - ignore: { breakStatements } - } = context; - context.ignore.breakStatements = true; context.ignore.labels.add(this.label.name); if (this.body.hasEffects(context)) return true; - context.ignore.breakStatements = breakStatements; context.ignore.labels.delete(this.label.name); if (context.breakFlow instanceof Set && context.breakFlow.has(this.label.name)) { context.breakFlow = BREAKFLOW_NONE; diff --git a/test/form/samples/break-control-flow/break-statement-labels-in-loops/_config.js b/test/form/samples/break-control-flow/break-statement-labels-in-loops/_config.js new file mode 100644 index 00000000000..08ea0622523 --- /dev/null +++ b/test/form/samples/break-control-flow/break-statement-labels-in-loops/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'handles breaking to loops inside labeled statements' +}; diff --git a/test/form/samples/break-control-flow/break-statement-labels-in-loops/_expected.js b/test/form/samples/break-control-flow/break-statement-labels-in-loops/_expected.js new file mode 100644 index 00000000000..f435f1aa7d2 --- /dev/null +++ b/test/form/samples/break-control-flow/break-statement-labels-in-loops/_expected.js @@ -0,0 +1,35 @@ +while (globalThis.unknown) { + console.log('retained'); + label: { + break; + } +} + +{ + console.log('retained'); +} + +while (globalThis.unknown) { + label: { + break label; + } + console.log('retained'); +} + +label: { + while (globalThis.unknown) { + console.log('retained'); + break label; + } + console.log('retained'); +} + +while (globalThis.unknown) { + console.log('retained'); + outer: { + label: { + break outer; + } + } + console.log('retained'); +} diff --git a/test/form/samples/break-control-flow/break-statement-labels-in-loops/main.js b/test/form/samples/break-control-flow/break-statement-labels-in-loops/main.js new file mode 100644 index 00000000000..091368bcb91 --- /dev/null +++ b/test/form/samples/break-control-flow/break-statement-labels-in-loops/main.js @@ -0,0 +1,49 @@ +while (globalThis.unknown) { + console.log('retained'); + label: { + break; + console.log('removed'); + } + console.log('removed'); +} + +{ + while (globalThis.unknown) { + label: { + break; + console.log('removed'); + } + console.log('removed'); + } + console.log('retained'); +} + +while (globalThis.unknown) { + label: { + break label; + console.log('removed'); + } + console.log('retained'); +} + +label: { + while (globalThis.unknown) { + console.log('retained'); + break label; + console.log('removed'); + } + console.log('retained'); +} + +while (globalThis.unknown) { + console.log('retained'); + outer: { + label: { + break outer; + console.log('removed'); + } + console.log('removed'); + } + console.log('retained'); +} + From b13b42395bc4734b9d76524ba575fd5fd374e009 Mon Sep 17 00:00:00 2001 From: Lukas Taegert-Atkinson Date: Fri, 11 Oct 2019 07:32:42 +0200 Subject: [PATCH 25/35] Refactor switch statement slightly --- src/ast/nodes/SwitchStatement.ts | 41 ++++++++++++++++---------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/src/ast/nodes/SwitchStatement.ts b/src/ast/nodes/SwitchStatement.ts index 60fce96f3a7..86eae93a21a 100644 --- a/src/ast/nodes/SwitchStatement.ts +++ b/src/ast/nodes/SwitchStatement.ts @@ -11,6 +11,24 @@ import * as NodeType from './NodeType'; import { ExpressionNode, IncludeChildren, StatementBase } from './shared/Node'; import SwitchCase from './SwitchCase'; +function getMinBreakflowAfterCase( + minBreakFlow: BreakFlow | false, + context: InclusionContext +): BreakFlow | false { + if (!(minBreakFlow && context.breakFlow)) { + return BREAKFLOW_NONE; + } + if (minBreakFlow instanceof Set) { + if (context.breakFlow instanceof Set) { + for (const label of context.breakFlow) { + minBreakFlow.add(label); + } + } + return minBreakFlow; + } + return context.breakFlow; +} + export default class SwitchStatement extends StatementBase { cases!: SwitchCase[]; discriminant!: ExpressionNode; @@ -32,8 +50,7 @@ export default class SwitchStatement extends StatementBase { for (const switchCase of this.cases) { if (switchCase.hasEffects(context)) return true; if (switchCase.test === null) hasDefault = true; - minBreakFlow = this.getMinBreakflowAfterCase(minBreakFlow, context); - context.breakFlow = breakFlow; + minBreakFlow = getMinBreakflowAfterCase(minBreakFlow, context); context.breakFlow = breakFlow; } if (hasDefault && !(minBreakFlow instanceof Set && minBreakFlow.has(null))) { @@ -52,29 +69,11 @@ export default class SwitchStatement extends StatementBase { for (const switchCase of this.cases) { if (switchCase.test === null) hasDefault = true; switchCase.include(includeChildrenRecursively, context); - minBreakFlow = this.getMinBreakflowAfterCase(minBreakFlow, context); + minBreakFlow = getMinBreakflowAfterCase(minBreakFlow, context); context.breakFlow = breakFlow; } if (hasDefault && !(minBreakFlow instanceof Set && minBreakFlow.has(null))) { context.breakFlow = minBreakFlow; } } - - private getMinBreakflowAfterCase( - minBreakFlow: BreakFlow | false, - context: InclusionContext - ): BreakFlow | false { - if (!(minBreakFlow && context.breakFlow)) { - return BREAKFLOW_NONE; - } - if (minBreakFlow instanceof Set) { - if (context.breakFlow instanceof Set) { - for (const label of context.breakFlow) { - minBreakFlow.add(label); - } - } - return minBreakFlow; - } - return context.breakFlow; - } } From bba01fd6add3f876e9c492cf0975f7776b0113e3 Mon Sep 17 00:00:00 2001 From: Lukas Taegert-Atkinson Date: Fri, 11 Oct 2019 07:33:41 +0200 Subject: [PATCH 26/35] Update dependencies and fix tests --- package-lock.json | 142 ++++++++++++------ package.json | 10 +- src/ast/nodes/BreakStatement.ts | 5 +- src/ast/nodes/ContinueStatement.ts | 5 +- .../_expected.js | 8 - 5 files changed, 101 insertions(+), 69 deletions(-) diff --git a/package-lock.json b/package-lock.json index e89dc861aa5..043724ed0de 100644 --- a/package-lock.json +++ b/package-lock.json @@ -143,28 +143,28 @@ } }, "@nodelib/fs.scandir": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.2.tgz", - "integrity": "sha512-wrIBsjA5pl13f0RN4Zx4FNWmU71lv03meGKnqRUoCyan17s4V3WL92f3w3AIuWbNnpcrQyFBU5qMavJoB8d27w==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz", + "integrity": "sha512-eGmwYQn3gxo4r7jdQnkrrN6bY478C3P+a/y72IJukF8LjB6ZHeB3c+Ehacj3sYeSmUXGlnA67/PmbM9CVwL7Dw==", "dev": true, "requires": { - "@nodelib/fs.stat": "2.0.2", + "@nodelib/fs.stat": "2.0.3", "run-parallel": "^1.1.9" } }, "@nodelib/fs.stat": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.2.tgz", - "integrity": "sha512-z8+wGWV2dgUhLqrtRYa03yDx4HWMvXKi1z8g3m2JyxAx8F7xk74asqPk5LAETjqDSGLFML/6CDl0+yFunSYicw==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz", + "integrity": "sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA==", "dev": true }, "@nodelib/fs.walk": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.3.tgz", - "integrity": "sha512-l6t8xEhfK9Sa4YO5mIRdau7XSOADfmh3jCr0evNHdY+HNkW6xuQhgMH7D73VV6WpZOagrW0UludvMTiifiwTfA==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz", + "integrity": "sha512-1V9XOY4rDW0rehzbrcqAmHnz8e7SKvX27gh8Gt2WgB0+pdzdiLV83p72kZPU+jvMbS1qU5mauP2iOvO8rhmurQ==", "dev": true, "requires": { - "@nodelib/fs.scandir": "2.1.2", + "@nodelib/fs.scandir": "2.1.3", "fastq": "^1.6.0" } }, @@ -245,9 +245,9 @@ "dev": true }, "@types/node": { - "version": "12.7.11", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.7.11.tgz", - "integrity": "sha512-Otxmr2rrZLKRYIybtdG/sgeO+tHY20GxeDjcGmUnmmlCWyEnv2a2x1ZXBo3BTec4OiTXMQCiazB8NMBf0iRlFw==" + "version": "12.7.12", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.7.12.tgz", + "integrity": "sha512-KPYGmfD0/b1eXurQ59fXD1GBzhSQfz6/lKBxkaHX9dKTzjXbK68Zt7yGUxUsCS1jeTy/8aL+d9JEr+S54mpkWQ==" }, "@types/normalize-package-data": { "version": "2.4.0", @@ -320,13 +320,13 @@ } }, "aggregate-error": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.0.0.tgz", - "integrity": "sha512-yKD9kEoJIR+2IFqhMwayIBgheLYbB3PS2OBhWae1L/ODTd/JF/30cW0bc9TqzRL3k4U41Dieu3BF4I29p8xesA==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.0.1.tgz", + "integrity": "sha512-quoaXsZ9/BLNae5yiNoUz+Nhkwz83GhWwtYFglcjEQB2NDHCIpApbqXxIFnm4Pq/Nvhrsq5sYJFyohrrxnTGAA==", "dev": true, "requires": { "clean-stack": "^2.0.0", - "indent-string": "^3.2.0" + "indent-string": "^4.0.0" } }, "ajv": { @@ -1699,12 +1699,12 @@ "dev": true }, "execa": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/execa/-/execa-2.0.5.tgz", - "integrity": "sha512-SwmwZZyJjflcqLSgllk4EQlMLst2p9muyzwNugKGFlpAz6rZ7M+s2nBR97GAq4Vzjwx2y9rcMcmqzojwN+xwNA==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-2.1.0.tgz", + "integrity": "sha512-Y/URAVapfbYy2Xp/gb6A0E7iR8xeqOCXsuuaoMn7A5PzrXUK84E1gyiEfq0wQd/GHA6GsoHWwhNq8anb0mleIw==", "dev": true, "requires": { - "cross-spawn": "^6.0.5", + "cross-spawn": "^7.0.0", "get-stream": "^5.0.0", "is-stream": "^2.0.0", "merge-stream": "^2.0.0", @@ -1715,6 +1715,17 @@ "strip-final-newline": "^2.0.0" }, "dependencies": { + "cross-spawn": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.1.tgz", + "integrity": "sha512-u7v4o84SwFpD32Z8IIcPZ6z1/ie24O6RU3RbtL5Y316l3KuHVPx9ItBgWQ6VlfAFnRnTtMUrsQ9MUUTuEZjogg==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, "mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", @@ -1729,6 +1740,36 @@ "requires": { "mimic-fn": "^2.1.0" } + }, + "path-key": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.0.tgz", + "integrity": "sha512-8cChqz0RP6SHJkMt48FW0A7+qUOn+OsnOsVtzI59tZ8m+5bCSk7hzwET0pulwOM2YMn9J1efb07KB9l9f30SGg==", + "dev": true + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "which": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.1.tgz", + "integrity": "sha512-N7GBZOTswtB9lkQBZA4+zAXrjEIWAUOB93AvzUiudRzRxhUdLURQ7D/gAIMY1gatT/LTbmbcv8SiYazy3eYB7w==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } } } }, @@ -1871,16 +1912,15 @@ "dev": true }, "fast-glob": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.0.4.tgz", - "integrity": "sha512-wkIbV6qg37xTJwqSsdnIphL1e+LaGz4AIQqr00mIubMaEhv1/HEmJ0uuCGZRNRUkZZmOB5mJKO0ZUTVq+SxMQg==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.1.0.tgz", + "integrity": "sha512-TrUz3THiq2Vy3bjfQUB2wNyPdGBeGmdjbzzBLhfHN4YFurYptCKwGq/TfiRavbGywFRzY6U2CdmQ1zmsY5yYaw==", "dev": true, "requires": { - "@nodelib/fs.stat": "^2.0.1", - "@nodelib/fs.walk": "^1.2.1", - "glob-parent": "^5.0.0", - "is-glob": "^4.0.1", - "merge2": "^1.2.3", + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.0", + "merge2": "^1.3.0", "micromatch": "^4.0.2" }, "dependencies": { @@ -2304,9 +2344,9 @@ "dev": true }, "get-own-enumerable-property-symbols": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.0.tgz", - "integrity": "sha512-CIJYJC4GGF06TakLg8z4GQKvDsx9EMspVxOYih7LerEL/WosUnFIww45CGfxfeKHqlg3twgUrYRT1O3WQqjGCg==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.1.tgz", + "integrity": "sha512-09/VS4iek66Dh2bctjRkowueRJbY1JDGR1L/zRxO1Qk8Uxs6PnqaNSqalpizPT+CDjre3hnEsuzvhgomz9qYrA==", "dev": true }, "get-stdin": { @@ -2767,9 +2807,9 @@ "dev": true }, "indent-string": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", - "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", "dev": true }, "inflight": { @@ -3337,9 +3377,9 @@ } }, "lint-staged": { - "version": "9.4.1", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-9.4.1.tgz", - "integrity": "sha512-zFRbo1bAJEVf1m33paTTjDVfy2v3lICCqHfmQSgNoI+lWpi7HPG5y/R2Y7Whdce+FKxlZYs/U1sDSx8+nmQdDA==", + "version": "9.4.2", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-9.4.2.tgz", + "integrity": "sha512-OFyGokJSWTn2M6vngnlLXjaHhi8n83VIZZ5/1Z26SULRUWgR3ITWpAEQC9Pnm3MC/EpCxlwts/mQWDHNji2+zA==", "dev": true, "requires": { "chalk": "^2.4.2", @@ -3474,6 +3514,12 @@ "object-assign": "^4.1.0" } }, + "indent-string": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", + "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=", + "dev": true + }, "log-symbols": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-1.0.2.tgz", @@ -5847,9 +5893,9 @@ "dev": true }, "systemjs": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/systemjs/-/systemjs-6.1.2.tgz", - "integrity": "sha512-QN0E62xEGCjp4+Wwg5o50CgdlMkQdUkiw5rHKQNJ454iKJCsFmQjPBoCEs1xq8mM/J+eYtLkZS0JCcay/gaEFw==", + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/systemjs/-/systemjs-6.1.3.tgz", + "integrity": "sha512-gchy3HVD7U7frun2RT3ApxjLtekG39KQXpLhNxRR5HSWqA1+p3dxPXrmvoevEsKAhCEPi2MjZ75zPBDiftic8A==", "dev": true }, "table": { @@ -5929,9 +5975,9 @@ } }, "terser": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/terser/-/terser-4.3.6.tgz", - "integrity": "sha512-QQXGTgXT7zET9IbGSdRvExcL+rFZGiOxMDbPg1W0tc5gqbX6m7J6Eu0W3fQ2bK5Dks1WSvC2xAKOH+mzAuMLcg==", + "version": "4.3.8", + "resolved": "https://registry.npmjs.org/terser/-/terser-4.3.8.tgz", + "integrity": "sha512-otmIRlRVmLChAWsnSFNO0Bfk6YySuBp6G9qrHiJwlLDd4mxe2ta4sjI7TzIR+W1nBMjilzrMcPOz9pSusgx3hQ==", "dev": true, "requires": { "commander": "^2.20.0", @@ -6207,9 +6253,9 @@ "dev": true }, "typescript": { - "version": "3.6.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.6.3.tgz", - "integrity": "sha512-N7bceJL1CtRQ2RiG0AQME13ksR7DiuQh/QehubYcghzv20tnh+MQnQIuJddTmsbqYj+dztchykemz0zFzlvdQw==", + "version": "3.6.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.6.4.tgz", + "integrity": "sha512-unoCll1+l+YK4i4F8f22TaNVPRHcD9PA3yCuZ8g5e0qGqlVlJ/8FSateOLLSagn+Yg5+ZwuPkL8LFUc0Jcvksg==", "dev": true }, "uc.micro": { diff --git a/package.json b/package.json index 7d2e5b2267c..21734de35f6 100644 --- a/package.json +++ b/package.json @@ -86,12 +86,12 @@ "es6-shim": "^0.35.5", "eslint": "^6.5.1", "eslint-plugin-import": "^2.18.2", - "execa": "^2.0.5", + "execa": "^2.1.0", "fixturify": "^1.2.0", "hash.js": "^1.1.7", "husky": "^3.0.8", "is-reference": "^1.1.4", - "lint-staged": "^9.4.1", + "lint-staged": "^9.4.2", "locate-character": "^2.0.5", "magic-string": "^0.25.4", "markdownlint-cli": "^0.18.0", @@ -122,12 +122,12 @@ "source-map": "^0.6.1", "source-map-support": "^0.5.13", "sourcemap-codec": "^1.4.6", - "systemjs": "^6.1.2", - "terser": "^4.3.6", + "systemjs": "^6.1.3", + "terser": "^4.3.8", "tslib": "^1.10.0", "tslint": "^5.20.0", "turbocolor": "^2.6.1", - "typescript": "^3.6.3", + "typescript": "^3.6.4", "url-parse": "^1.4.7" }, "files": [ diff --git a/src/ast/nodes/BreakStatement.ts b/src/ast/nodes/BreakStatement.ts index 04b428aa43e..93256eb57de 100644 --- a/src/ast/nodes/BreakStatement.ts +++ b/src/ast/nodes/BreakStatement.ts @@ -8,10 +8,7 @@ export default class BreakStatement extends StatementBase { type!: NodeType.tBreakStatement; hasEffects(context: HasEffectsContext) { - if ( - !context.ignore.breakStatements || - (this.label !== null && !context.ignore.labels.has(this.label.name)) - ) + if (!(this.label ? context.ignore.labels.has(this.label.name) : context.ignore.breakStatements)) return true; context.breakFlow = new Set([this.label && this.label.name]); return false; diff --git a/src/ast/nodes/ContinueStatement.ts b/src/ast/nodes/ContinueStatement.ts index eeac6a74a36..0373e9d95ff 100644 --- a/src/ast/nodes/ContinueStatement.ts +++ b/src/ast/nodes/ContinueStatement.ts @@ -8,10 +8,7 @@ export default class ContinueStatement extends StatementBase { type!: NodeType.tContinueStatement; hasEffects(context: HasEffectsContext) { - if ( - !context.ignore.breakStatements || - (this.label !== null && !context.ignore.labels.has(this.label.name)) - ) + if (!(this.label ? context.ignore.labels.has(this.label.name) : context.ignore.breakStatements)) return true; context.breakFlow = new Set([this.label && this.label.name]); return false; diff --git a/test/form/samples/break-control-flow/break-statement-labels-in-loops/_expected.js b/test/form/samples/break-control-flow/break-statement-labels-in-loops/_expected.js index f435f1aa7d2..61e440a1a43 100644 --- a/test/form/samples/break-control-flow/break-statement-labels-in-loops/_expected.js +++ b/test/form/samples/break-control-flow/break-statement-labels-in-loops/_expected.js @@ -10,9 +10,6 @@ while (globalThis.unknown) { } while (globalThis.unknown) { - label: { - break label; - } console.log('retained'); } @@ -26,10 +23,5 @@ label: { while (globalThis.unknown) { console.log('retained'); - outer: { - label: { - break outer; - } - } console.log('retained'); } From 18cdfeaa71adc6cd44ebe972f5075ed4a39c7563 Mon Sep 17 00:00:00 2001 From: Lukas Taegert-Atkinson Date: Sun, 13 Oct 2019 08:24:55 +0200 Subject: [PATCH 27/35] Make the context the first include argument --- src/Module.ts | 4 ++-- src/ast/nodes/ArrowFunctionExpression.ts | 6 +++--- src/ast/nodes/AwaitExpression.ts | 4 ++-- src/ast/nodes/BlockStatement.ts | 4 ++-- src/ast/nodes/BreakStatement.ts | 4 ++-- src/ast/nodes/CallExpression.ts | 8 ++++---- src/ast/nodes/ConditionalExpression.ts | 10 +++++----- src/ast/nodes/ContinueStatement.ts | 4 ++-- src/ast/nodes/DoWhileStatement.ts | 6 +++--- src/ast/nodes/ExportDefaultDeclaration.ts | 4 ++-- src/ast/nodes/ForInStatement.ts | 6 +++--- src/ast/nodes/ForOfStatement.ts | 6 +++--- src/ast/nodes/ForStatement.ts | 10 +++++----- src/ast/nodes/IfStatement.ts | 20 ++++++++++---------- src/ast/nodes/ImportExpression.ts | 4 ++-- src/ast/nodes/LabeledStatement.ts | 4 ++-- src/ast/nodes/LogicalExpression.ts | 8 ++++---- src/ast/nodes/MemberExpression.ts | 6 +++--- src/ast/nodes/Program.ts | 4 ++-- src/ast/nodes/ReturnStatement.ts | 4 ++-- src/ast/nodes/SequenceExpression.ts | 6 +++--- src/ast/nodes/SwitchCase.ts | 6 +++--- src/ast/nodes/SwitchStatement.ts | 6 +++--- src/ast/nodes/ThrowStatement.ts | 4 ++-- src/ast/nodes/TryStatement.ts | 10 +++++----- src/ast/nodes/UnknownNode.ts | 6 +++--- src/ast/nodes/VariableDeclaration.ts | 6 +++--- src/ast/nodes/WhileStatement.ts | 6 +++--- src/ast/nodes/shared/Expression.ts | 2 +- src/ast/nodes/shared/FunctionNode.ts | 6 +++--- src/ast/nodes/shared/Node.ts | 12 ++++++------ src/ast/scopes/FunctionScope.ts | 2 +- src/ast/scopes/ParameterScope.ts | 2 +- src/ast/values.ts | 12 ++++++------ src/ast/variables/LocalVariable.ts | 4 ++-- src/ast/variables/Variable.ts | 2 +- 36 files changed, 109 insertions(+), 109 deletions(-) diff --git a/src/Module.ts b/src/Module.ts index 8f60673a2f9..15ed49721f3 100644 --- a/src/Module.ts +++ b/src/Module.ts @@ -451,7 +451,7 @@ export default class Module { include(): void { if (this.ast.shouldBeIncluded(createInclusionContext())) - this.ast.include(false, createInclusionContext()); + this.ast.include(createInclusionContext(), false); } includeAllExports() { @@ -483,7 +483,7 @@ export default class Module { } includeAllInBundle() { - this.ast.include(true, createInclusionContext()); + this.ast.include(createInclusionContext(), true); } isIncluded() { diff --git a/src/ast/nodes/ArrowFunctionExpression.ts b/src/ast/nodes/ArrowFunctionExpression.ts index 68431574379..123db3939bf 100644 --- a/src/ast/nodes/ArrowFunctionExpression.ts +++ b/src/ast/nodes/ArrowFunctionExpression.ts @@ -68,12 +68,12 @@ export default class ArrowFunctionExpression extends NodeBase { return false; } - include(includeChildrenRecursively: IncludeChildren, context: InclusionContext) { + include(context: InclusionContext, includeChildrenRecursively: IncludeChildren) { this.included = true; - this.body.include(includeChildrenRecursively, createInclusionContext()); + this.body.include(createInclusionContext(), includeChildrenRecursively); for (const param of this.params) { if (!(param instanceof Identifier)) { - param.include(includeChildrenRecursively, context); + param.include(context, includeChildrenRecursively); } } } diff --git a/src/ast/nodes/AwaitExpression.ts b/src/ast/nodes/AwaitExpression.ts index 011dfa74cba..2240c4f1321 100644 --- a/src/ast/nodes/AwaitExpression.ts +++ b/src/ast/nodes/AwaitExpression.ts @@ -14,7 +14,7 @@ export default class AwaitExpression extends NodeBase { return super.hasEffects(context) || !context.ignore.returnAwaitYield; } - include(includeChildrenRecursively: IncludeChildren, context: InclusionContext) { + include(context: InclusionContext, includeChildrenRecursively: IncludeChildren) { checkTopLevelAwait: if (!this.included && !this.context.usesTopLevelAwait) { let parent = this.parent; do { @@ -23,7 +23,7 @@ export default class AwaitExpression extends NodeBase { } while ((parent = (parent as Node).parent as Node)); this.context.usesTopLevelAwait = true; } - super.include(includeChildrenRecursively, context); + super.include(context, includeChildrenRecursively); } render(code: MagicString, options: RenderOptions) { diff --git a/src/ast/nodes/BlockStatement.ts b/src/ast/nodes/BlockStatement.ts index 5a978290e1f..1752d5907dd 100644 --- a/src/ast/nodes/BlockStatement.ts +++ b/src/ast/nodes/BlockStatement.ts @@ -33,11 +33,11 @@ export default class BlockStatement extends StatementBase { return false; } - include(includeChildrenRecursively: IncludeChildren, context: InclusionContext) { + include(context: InclusionContext, includeChildrenRecursively: IncludeChildren) { this.included = true; for (const node of this.body) { if (includeChildrenRecursively || node.shouldBeIncluded(context)) - node.include(includeChildrenRecursively, context); + node.include(context, includeChildrenRecursively); } } diff --git a/src/ast/nodes/BreakStatement.ts b/src/ast/nodes/BreakStatement.ts index 93256eb57de..d23ba875780 100644 --- a/src/ast/nodes/BreakStatement.ts +++ b/src/ast/nodes/BreakStatement.ts @@ -1,7 +1,7 @@ import { HasEffectsContext, InclusionContext } from '../ExecutionContext'; import Identifier from './Identifier'; import * as NodeType from './NodeType'; -import { IncludeChildren, StatementBase } from './shared/Node'; +import { StatementBase } from './shared/Node'; export default class BreakStatement extends StatementBase { label!: Identifier | null; @@ -14,7 +14,7 @@ export default class BreakStatement extends StatementBase { return false; } - include(_includeChildrenRecursively: IncludeChildren, context: InclusionContext) { + include(context: InclusionContext) { this.included = true; if (this.label) this.label.include(); context.breakFlow = new Set([this.label && this.label.name]); diff --git a/src/ast/nodes/CallExpression.ts b/src/ast/nodes/CallExpression.ts index c8b1e1640d0..8f5ad86c287 100644 --- a/src/ast/nodes/CallExpression.ts +++ b/src/ast/nodes/CallExpression.ts @@ -197,9 +197,9 @@ export default class CallExpression extends NodeBase implements DeoptimizableEnt ); } - include(includeChildrenRecursively: IncludeChildren, context: InclusionContext) { + include(context: InclusionContext, includeChildrenRecursively: IncludeChildren) { if (includeChildrenRecursively) { - super.include(includeChildrenRecursively, context); + super.include(context, includeChildrenRecursively); if ( includeChildrenRecursively === INCLUDE_PARAMETERS && this.callee instanceof Identifier && @@ -209,11 +209,11 @@ export default class CallExpression extends NodeBase implements DeoptimizableEnt } } else { this.included = true; - this.callee.include(false, context); + this.callee.include(context, false); } this.callee.includeCallArguments(this.arguments); if (!(this.returnExpression as ExpressionEntity).included) { - (this.returnExpression as ExpressionEntity).include(false, context); + (this.returnExpression as ExpressionEntity).include(context, false); } } diff --git a/src/ast/nodes/ConditionalExpression.ts b/src/ast/nodes/ConditionalExpression.ts index aba82a5fcf9..8e5c1a4a12a 100644 --- a/src/ast/nodes/ConditionalExpression.ts +++ b/src/ast/nodes/ConditionalExpression.ts @@ -135,18 +135,18 @@ export default class ConditionalExpression extends NodeBase implements Deoptimiz return this.usedBranch.hasEffectsWhenCalledAtPath(path, callOptions, context); } - include(includeChildrenRecursively: IncludeChildren, context: InclusionContext) { + include(context: InclusionContext, includeChildrenRecursively: IncludeChildren) { this.included = true; if ( includeChildrenRecursively || this.usedBranch === null || this.test.shouldBeIncluded(context) ) { - this.test.include(includeChildrenRecursively, context); - this.consequent.include(includeChildrenRecursively, context); - this.alternate.include(includeChildrenRecursively, context); + this.test.include(context, includeChildrenRecursively); + this.consequent.include(context, includeChildrenRecursively); + this.alternate.include(context, includeChildrenRecursively); } else { - this.usedBranch.include(includeChildrenRecursively, context); + this.usedBranch.include(context, includeChildrenRecursively); } } diff --git a/src/ast/nodes/ContinueStatement.ts b/src/ast/nodes/ContinueStatement.ts index 0373e9d95ff..cc15954000a 100644 --- a/src/ast/nodes/ContinueStatement.ts +++ b/src/ast/nodes/ContinueStatement.ts @@ -1,7 +1,7 @@ import { HasEffectsContext, InclusionContext } from '../ExecutionContext'; import Identifier from './Identifier'; import * as NodeType from './NodeType'; -import { IncludeChildren, StatementBase } from './shared/Node'; +import { StatementBase } from './shared/Node'; export default class ContinueStatement extends StatementBase { label!: Identifier | null; @@ -14,7 +14,7 @@ export default class ContinueStatement extends StatementBase { return false; } - include(_includeChildrenRecursively: IncludeChildren, context: InclusionContext) { + include(context: InclusionContext) { this.included = true; if (this.label) this.label.include(); context.breakFlow = new Set([this.label && this.label.name]); diff --git a/src/ast/nodes/DoWhileStatement.ts b/src/ast/nodes/DoWhileStatement.ts index 8013d7930fc..151a036ed91 100644 --- a/src/ast/nodes/DoWhileStatement.ts +++ b/src/ast/nodes/DoWhileStatement.ts @@ -22,11 +22,11 @@ export default class DoWhileStatement extends StatementBase { return false; } - include(includeChildrenRecursively: IncludeChildren, context: InclusionContext) { + include(context: InclusionContext, includeChildrenRecursively: IncludeChildren) { this.included = true; - this.test.include(includeChildrenRecursively, context); + this.test.include(context, includeChildrenRecursively); const breakFlow = context.breakFlow; - this.body.include(includeChildrenRecursively, context); + this.body.include(context, includeChildrenRecursively); if (context.breakFlow instanceof Set && context.breakFlow.has(null)) { context.breakFlow = breakFlow; } diff --git a/src/ast/nodes/ExportDefaultDeclaration.ts b/src/ast/nodes/ExportDefaultDeclaration.ts index 6420c476b44..5718d78b313 100644 --- a/src/ast/nodes/ExportDefaultDeclaration.ts +++ b/src/ast/nodes/ExportDefaultDeclaration.ts @@ -44,8 +44,8 @@ export default class ExportDefaultDeclaration extends NodeBase { private declarationName: string | undefined; - include(includeChildrenRecursively: IncludeChildren, context: InclusionContext) { - super.include(includeChildrenRecursively, context); + include(context: InclusionContext, includeChildrenRecursively: IncludeChildren) { + super.include(context, includeChildrenRecursively); if (includeChildrenRecursively) { this.context.includeVariable(this.variable); } diff --git a/src/ast/nodes/ForInStatement.ts b/src/ast/nodes/ForInStatement.ts index cde527b95c5..28c9805e7a6 100644 --- a/src/ast/nodes/ForInStatement.ts +++ b/src/ast/nodes/ForInStatement.ts @@ -45,13 +45,13 @@ export default class ForInStatement extends StatementBase { return false; } - include(includeChildrenRecursively: IncludeChildren, context: InclusionContext) { + include(context: InclusionContext, includeChildrenRecursively: IncludeChildren) { this.included = true; this.left.includeWithAllDeclaredVariables(includeChildrenRecursively, context); this.left.deoptimizePath(EMPTY_PATH); - this.right.include(includeChildrenRecursively, context); + this.right.include(context, includeChildrenRecursively); const breakFlow = context.breakFlow; - this.body.include(includeChildrenRecursively, context); + this.body.include(context, includeChildrenRecursively); context.breakFlow = breakFlow; } diff --git a/src/ast/nodes/ForOfStatement.ts b/src/ast/nodes/ForOfStatement.ts index 5dcd246fbe7..8385c1fab33 100644 --- a/src/ast/nodes/ForOfStatement.ts +++ b/src/ast/nodes/ForOfStatement.ts @@ -32,13 +32,13 @@ export default class ForOfStatement extends StatementBase { return true; } - include(includeChildrenRecursively: IncludeChildren, context: InclusionContext) { + include(context: InclusionContext, includeChildrenRecursively: IncludeChildren) { this.included = true; this.left.includeWithAllDeclaredVariables(includeChildrenRecursively, context); this.left.deoptimizePath(EMPTY_PATH); - this.right.include(includeChildrenRecursively, context); + this.right.include(context, includeChildrenRecursively); const breakFlow = context.breakFlow; - this.body.include(includeChildrenRecursively, context); + this.body.include(context, includeChildrenRecursively); context.breakFlow = breakFlow; } diff --git a/src/ast/nodes/ForStatement.ts b/src/ast/nodes/ForStatement.ts index 54d3277a648..3e526bed54c 100644 --- a/src/ast/nodes/ForStatement.ts +++ b/src/ast/nodes/ForStatement.ts @@ -36,13 +36,13 @@ export default class ForStatement extends StatementBase { return false; } - include(includeChildrenRecursively: IncludeChildren, context: InclusionContext) { + include(context: InclusionContext, includeChildrenRecursively: IncludeChildren) { this.included = true; - if (this.init) this.init.include(includeChildrenRecursively, context); - if (this.test) this.test.include(includeChildrenRecursively, context); + if (this.init) this.init.include(context, includeChildrenRecursively); + if (this.test) this.test.include(context, includeChildrenRecursively); const breakFlow = context.breakFlow; - if (this.update) this.update.include(includeChildrenRecursively, context); - if (this.body) this.body.include(includeChildrenRecursively, context); + if (this.update) this.update.include(context, includeChildrenRecursively); + if (this.body) this.body.include(context, includeChildrenRecursively); context.breakFlow = breakFlow; } diff --git a/src/ast/nodes/IfStatement.ts b/src/ast/nodes/IfStatement.ts index f6dc382cd6f..e1b6661d330 100644 --- a/src/ast/nodes/IfStatement.ts +++ b/src/ast/nodes/IfStatement.ts @@ -52,7 +52,7 @@ export default class IfStatement extends StatementBase implements DeoptimizableE : this.alternate !== null && this.alternate.hasEffects(context); } - include(includeChildrenRecursively: IncludeChildren, context: InclusionContext) { + include(context: InclusionContext, includeChildrenRecursively: IncludeChildren) { this.included = true; if (includeChildrenRecursively) { this.includeRecursively(includeChildrenRecursively, context); @@ -101,13 +101,13 @@ export default class IfStatement extends StatementBase implements DeoptimizableE private includeKnownTest(context: InclusionContext) { if (this.test.shouldBeIncluded(context)) { - this.test.include(false, context); + this.test.include(context, false); } if (this.testValue && this.consequent.shouldBeIncluded(context)) { - this.consequent.include(false, context); + this.consequent.include(context, false); } if (this.alternate !== null && !this.testValue && this.alternate.shouldBeIncluded(context)) { - this.alternate.include(false, context); + this.alternate.include(context, false); } } @@ -115,24 +115,24 @@ export default class IfStatement extends StatementBase implements DeoptimizableE includeChildrenRecursively: true | 'variables', context: InclusionContext ) { - this.test.include(includeChildrenRecursively, context); - this.consequent.include(includeChildrenRecursively, context); + this.test.include(context, includeChildrenRecursively); + this.consequent.include(context, includeChildrenRecursively); if (this.alternate !== null) { - this.alternate.include(includeChildrenRecursively, context); + this.alternate.include(context, includeChildrenRecursively); } } private includeUnknownTest(context: InclusionContext) { - this.test.include(false, context); + this.test.include(context, false); const breakFlow = context.breakFlow; let consequentBreakFlow: BreakFlow | false = false; if (this.consequent.shouldBeIncluded(context)) { - this.consequent.include(false, context); + this.consequent.include(context, false); consequentBreakFlow = context.breakFlow; context.breakFlow = breakFlow; } if (this.alternate !== null && this.alternate.shouldBeIncluded(context)) { - this.alternate.include(false, context); + this.alternate.include(context, false); this.updateBreakFlowUnknownCondition(consequentBreakFlow, context); } } diff --git a/src/ast/nodes/ImportExpression.ts b/src/ast/nodes/ImportExpression.ts index f322bbdc3da..32c9bbe91c2 100644 --- a/src/ast/nodes/ImportExpression.ts +++ b/src/ast/nodes/ImportExpression.ts @@ -22,12 +22,12 @@ export default class Import extends NodeBase { return true; } - include(includeChildrenRecursively: IncludeChildren, context: InclusionContext) { + include(context: InclusionContext, includeChildrenRecursively: IncludeChildren) { if (!this.included) { this.included = true; this.context.includeDynamicImport(this); } - this.source.include(includeChildrenRecursively, context); + this.source.include(context, includeChildrenRecursively); } initialise() { diff --git a/src/ast/nodes/LabeledStatement.ts b/src/ast/nodes/LabeledStatement.ts index 19776e0c5dc..895796928b8 100644 --- a/src/ast/nodes/LabeledStatement.ts +++ b/src/ast/nodes/LabeledStatement.ts @@ -18,10 +18,10 @@ export default class LabeledStatement extends StatementBase { return false; } - include(includeChildrenRecursively: IncludeChildren, context: InclusionContext) { + include(context: InclusionContext, includeChildrenRecursively: IncludeChildren) { this.included = true; this.label.include(); - this.body.include(includeChildrenRecursively, context); + this.body.include(context, includeChildrenRecursively); if (context.breakFlow instanceof Set && context.breakFlow.has(this.label.name)) { context.breakFlow = BREAKFLOW_NONE; } diff --git a/src/ast/nodes/LogicalExpression.ts b/src/ast/nodes/LogicalExpression.ts index d6e7a0c6a65..0c80347b21d 100644 --- a/src/ast/nodes/LogicalExpression.ts +++ b/src/ast/nodes/LogicalExpression.ts @@ -136,17 +136,17 @@ export default class LogicalExpression extends NodeBase implements Deoptimizable return this.usedBranch.hasEffectsWhenCalledAtPath(path, callOptions, context); } - include(includeChildrenRecursively: IncludeChildren, context: InclusionContext) { + include(context: InclusionContext, includeChildrenRecursively: IncludeChildren) { this.included = true; if ( includeChildrenRecursively || this.usedBranch === null || (this.unusedBranch as ExpressionNode).shouldBeIncluded(context) ) { - this.left.include(includeChildrenRecursively, context); - this.right.include(includeChildrenRecursively, context); + this.left.include(context, includeChildrenRecursively); + this.right.include(context, includeChildrenRecursively); } else { - this.usedBranch.include(includeChildrenRecursively, context); + this.usedBranch.include(context, includeChildrenRecursively); } } diff --git a/src/ast/nodes/MemberExpression.ts b/src/ast/nodes/MemberExpression.ts index 6c84ed84ffb..cddea3beb9b 100644 --- a/src/ast/nodes/MemberExpression.ts +++ b/src/ast/nodes/MemberExpression.ts @@ -209,15 +209,15 @@ export default class MemberExpression extends NodeBase implements DeoptimizableE ); } - include(includeChildrenRecursively: IncludeChildren, context: InclusionContext) { + include(context: InclusionContext, includeChildrenRecursively: IncludeChildren) { if (!this.included) { this.included = true; if (this.variable !== null) { this.context.includeVariable(this.variable); } } - this.object.include(includeChildrenRecursively, context); - this.property.include(includeChildrenRecursively, context); + this.object.include(context, includeChildrenRecursively); + this.property.include(context, includeChildrenRecursively); } includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void { diff --git a/src/ast/nodes/Program.ts b/src/ast/nodes/Program.ts index 1dcd75f9bc7..8b5351c9b19 100644 --- a/src/ast/nodes/Program.ts +++ b/src/ast/nodes/Program.ts @@ -16,11 +16,11 @@ export default class Program extends NodeBase { return false; } - include(includeChildrenRecursively: IncludeChildren, context: InclusionContext) { + include(context: InclusionContext, includeChildrenRecursively: IncludeChildren) { this.included = true; for (const node of this.body) { if (includeChildrenRecursively || node.shouldBeIncluded(context)) { - node.include(includeChildrenRecursively, context); + node.include(context, includeChildrenRecursively); } } } diff --git a/src/ast/nodes/ReturnStatement.ts b/src/ast/nodes/ReturnStatement.ts index 3f50b39bedd..9b434540943 100644 --- a/src/ast/nodes/ReturnStatement.ts +++ b/src/ast/nodes/ReturnStatement.ts @@ -19,10 +19,10 @@ export default class ReturnStatement extends StatementBase { return false; } - include(includeChildrenRecursively: IncludeChildren, context: InclusionContext) { + include(context: InclusionContext, includeChildrenRecursively: IncludeChildren) { this.included = true; if (this.argument) { - this.argument.include(includeChildrenRecursively, context); + this.argument.include(context, includeChildrenRecursively); } context.breakFlow = BREAKFLOW_ERROR_RETURN; } diff --git a/src/ast/nodes/SequenceExpression.ts b/src/ast/nodes/SequenceExpression.ts index 819a3de9490..4f4c8aee168 100644 --- a/src/ast/nodes/SequenceExpression.ts +++ b/src/ast/nodes/SequenceExpression.ts @@ -69,14 +69,14 @@ export default class SequenceExpression extends NodeBase { ); } - include(includeChildrenRecursively: IncludeChildren, context: InclusionContext) { + include(context: InclusionContext, includeChildrenRecursively: IncludeChildren) { this.included = true; for (let i = 0; i < this.expressions.length - 1; i++) { const node = this.expressions[i]; if (includeChildrenRecursively || node.shouldBeIncluded(context)) - node.include(includeChildrenRecursively, context); + node.include(context, includeChildrenRecursively); } - this.expressions[this.expressions.length - 1].include(includeChildrenRecursively, context); + this.expressions[this.expressions.length - 1].include(context, includeChildrenRecursively); } render( diff --git a/src/ast/nodes/SwitchCase.ts b/src/ast/nodes/SwitchCase.ts index a1cecb38418..4306b66eac8 100644 --- a/src/ast/nodes/SwitchCase.ts +++ b/src/ast/nodes/SwitchCase.ts @@ -22,12 +22,12 @@ export default class SwitchCase extends NodeBase { return false; } - include(includeChildrenRecursively: IncludeChildren, context: InclusionContext) { + include(context: InclusionContext, includeChildrenRecursively: IncludeChildren) { this.included = true; - if (this.test) this.test.include(includeChildrenRecursively, context); + if (this.test) this.test.include(context, includeChildrenRecursively); for (const node of this.consequent) { if (includeChildrenRecursively || node.shouldBeIncluded(context)) - node.include(includeChildrenRecursively, context); + node.include(context, includeChildrenRecursively); } } diff --git a/src/ast/nodes/SwitchStatement.ts b/src/ast/nodes/SwitchStatement.ts index 86eae93a21a..fb7e02769d5 100644 --- a/src/ast/nodes/SwitchStatement.ts +++ b/src/ast/nodes/SwitchStatement.ts @@ -60,15 +60,15 @@ export default class SwitchStatement extends StatementBase { return false; } - include(includeChildrenRecursively: IncludeChildren, context: InclusionContext) { + include(context: InclusionContext, includeChildrenRecursively: IncludeChildren) { this.included = true; - this.discriminant.include(includeChildrenRecursively, context); + this.discriminant.include(context, includeChildrenRecursively); const breakFlow = context.breakFlow; let hasDefault = false; let minBreakFlow: BreakFlow | false = BREAKFLOW_ERROR_RETURN; for (const switchCase of this.cases) { if (switchCase.test === null) hasDefault = true; - switchCase.include(includeChildrenRecursively, context); + switchCase.include(context, includeChildrenRecursively); minBreakFlow = getMinBreakflowAfterCase(minBreakFlow, context); context.breakFlow = breakFlow; } diff --git a/src/ast/nodes/ThrowStatement.ts b/src/ast/nodes/ThrowStatement.ts index d4aec963d59..aab2d944b34 100644 --- a/src/ast/nodes/ThrowStatement.ts +++ b/src/ast/nodes/ThrowStatement.ts @@ -12,9 +12,9 @@ export default class ThrowStatement extends StatementBase { return true; } - include(includeChildrenRecursively: IncludeChildren, context: InclusionContext) { + include(context: InclusionContext, includeChildrenRecursively: IncludeChildren) { this.included = true; - this.argument.include(includeChildrenRecursively, context); + this.argument.include(context, includeChildrenRecursively); context.breakFlow = BREAKFLOW_ERROR_RETURN; } diff --git a/src/ast/nodes/TryStatement.ts b/src/ast/nodes/TryStatement.ts index 4c0e550b32b..5936860ebc6 100644 --- a/src/ast/nodes/TryStatement.ts +++ b/src/ast/nodes/TryStatement.ts @@ -20,23 +20,23 @@ export default class TryStatement extends StatementBase { ); } - include(includeChildrenRecursively: IncludeChildren, context: InclusionContext) { + include(context: InclusionContext, includeChildrenRecursively: IncludeChildren) { const breakFlow = context.breakFlow; if (!this.directlyIncluded || !this.context.tryCatchDeoptimization) { this.included = true; this.directlyIncluded = true; this.block.include( - this.context.tryCatchDeoptimization ? INCLUDE_PARAMETERS : includeChildrenRecursively, - context + context, + this.context.tryCatchDeoptimization ? INCLUDE_PARAMETERS : includeChildrenRecursively ); context.breakFlow = breakFlow; } if (this.handler !== null) { - this.handler.include(includeChildrenRecursively, context); + this.handler.include(context, includeChildrenRecursively); context.breakFlow = breakFlow; } if (this.finalizer !== null) { - this.finalizer.include(includeChildrenRecursively, context); + this.finalizer.include(context, includeChildrenRecursively); } } } diff --git a/src/ast/nodes/UnknownNode.ts b/src/ast/nodes/UnknownNode.ts index 66c4d948242..cdbd09ef538 100644 --- a/src/ast/nodes/UnknownNode.ts +++ b/src/ast/nodes/UnknownNode.ts @@ -1,12 +1,12 @@ import { InclusionContext } from '../ExecutionContext'; -import { IncludeChildren, NodeBase } from './shared/Node'; +import { NodeBase } from './shared/Node'; export default class UnknownNode extends NodeBase { hasEffects() { return true; } - include(_includeChildrenRecursively: IncludeChildren, context: InclusionContext) { - super.include(true, context); + include(context: InclusionContext) { + super.include(context, true); } } diff --git a/src/ast/nodes/VariableDeclaration.ts b/src/ast/nodes/VariableDeclaration.ts index 34321325bd6..88208238f17 100644 --- a/src/ast/nodes/VariableDeclaration.ts +++ b/src/ast/nodes/VariableDeclaration.ts @@ -47,11 +47,11 @@ export default class VariableDeclaration extends NodeBase { return false; } - include(includeChildrenRecursively: IncludeChildren, context: InclusionContext) { + include(context: InclusionContext, includeChildrenRecursively: IncludeChildren) { this.included = true; for (const declarator of this.declarations) { if (includeChildrenRecursively || declarator.shouldBeIncluded(context)) - declarator.include(includeChildrenRecursively, context); + declarator.include(context, includeChildrenRecursively); } } @@ -61,7 +61,7 @@ export default class VariableDeclaration extends NodeBase { ) { this.included = true; for (const declarator of this.declarations) { - declarator.include(includeChildrenRecursively, context); + declarator.include(context, includeChildrenRecursively); } } diff --git a/src/ast/nodes/WhileStatement.ts b/src/ast/nodes/WhileStatement.ts index e387000c995..4e44c9e3d72 100644 --- a/src/ast/nodes/WhileStatement.ts +++ b/src/ast/nodes/WhileStatement.ts @@ -20,11 +20,11 @@ export default class WhileStatement extends StatementBase { return false; } - include(includeChildrenRecursively: IncludeChildren, context: InclusionContext) { + include(context: InclusionContext, includeChildrenRecursively: IncludeChildren) { this.included = true; - this.test.include(includeChildrenRecursively, context); + this.test.include(context, includeChildrenRecursively); const breakFlow = context.breakFlow; - this.body.include(includeChildrenRecursively, context); + this.body.include(context, includeChildrenRecursively); context.breakFlow = breakFlow; } } diff --git a/src/ast/nodes/shared/Expression.ts b/src/ast/nodes/shared/Expression.ts index 7d96566ecff..9e0d4d0391e 100644 --- a/src/ast/nodes/shared/Expression.ts +++ b/src/ast/nodes/shared/Expression.ts @@ -31,6 +31,6 @@ export interface ExpressionEntity extends WritableEntity { callOptions: CallOptions, context: HasEffectsContext ): boolean; - include(includeChildrenRecursively: IncludeChildren, context: InclusionContext): void; + include(context: InclusionContext, includeChildrenRecursively: IncludeChildren): void; includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void; } diff --git a/src/ast/nodes/shared/FunctionNode.ts b/src/ast/nodes/shared/FunctionNode.ts index 3e978d041f1..83f1dfafd08 100644 --- a/src/ast/nodes/shared/FunctionNode.ts +++ b/src/ast/nodes/shared/FunctionNode.ts @@ -93,14 +93,14 @@ export default class FunctionNode extends NodeBase { return false; } - include(includeChildrenRecursively: IncludeChildren, context: InclusionContext) { + include(context: InclusionContext, includeChildrenRecursively: IncludeChildren) { this.included = true; - this.body.include(includeChildrenRecursively, createInclusionContext()); + this.body.include(createInclusionContext(), includeChildrenRecursively); if (this.id) this.id.include(); const hasArguments = this.scope.argumentsVariable.included; for (const param of this.params) { if (!(param instanceof Identifier) || hasArguments) { - param.include(includeChildrenRecursively, context); + param.include(context, includeChildrenRecursively); } } } diff --git a/src/ast/nodes/shared/Node.ts b/src/ast/nodes/shared/Node.ts index ade408f6512..d1e15e295d3 100644 --- a/src/ast/nodes/shared/Node.ts +++ b/src/ast/nodes/shared/Node.ts @@ -64,7 +64,7 @@ export interface Node extends Entity { * if they are necessary for this node (e.g. a function body) or if they have effects. * Necessary variables need to be included as well. */ - include(includeChildrenRecursively: IncludeChildren, context: InclusionContext): void; + include(context: InclusionContext, includeChildrenRecursively: IncludeChildren): void; /** * Alternative version of include to override the default behaviour of @@ -191,24 +191,24 @@ export class NodeBase implements ExpressionNode { return true; } - include(includeChildrenRecursively: IncludeChildren, context: InclusionContext) { + include(context: InclusionContext, includeChildrenRecursively: IncludeChildren) { this.included = true; for (const key of this.keys) { const value = (this as GenericEsTreeNode)[key]; if (value === null || key === 'annotations') continue; if (Array.isArray(value)) { for (const child of value) { - if (child !== null) child.include(includeChildrenRecursively, context); + if (child !== null) child.include(context, includeChildrenRecursively); } } else { - value.include(includeChildrenRecursively, context); + value.include(context, includeChildrenRecursively); } } } includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void { for (const arg of args) { - arg.include(false, createInclusionContext()); + arg.include(createInclusionContext(), false); } } @@ -216,7 +216,7 @@ export class NodeBase implements ExpressionNode { includeChildrenRecursively: IncludeChildren, context: InclusionContext ) { - this.include(includeChildrenRecursively, context); + this.include(context, includeChildrenRecursively); } /** diff --git a/src/ast/scopes/FunctionScope.ts b/src/ast/scopes/FunctionScope.ts index 598391d2300..d6ef25dee89 100644 --- a/src/ast/scopes/FunctionScope.ts +++ b/src/ast/scopes/FunctionScope.ts @@ -26,7 +26,7 @@ export default class FunctionScope extends ReturnValueScope { if (this.argumentsVariable.included) { for (const arg of args) { if (!arg.included) { - arg.include(false, createInclusionContext()); + arg.include(createInclusionContext(), false); } } } diff --git a/src/ast/scopes/ParameterScope.ts b/src/ast/scopes/ParameterScope.ts index 77952cd3df5..014625a6826 100644 --- a/src/ast/scopes/ParameterScope.ts +++ b/src/ast/scopes/ParameterScope.ts @@ -69,7 +69,7 @@ export default class ParameterScope extends ChildScope { argIncluded = true; } if (argIncluded) { - arg.include(calledFromTryStatement, createInclusionContext()); + arg.include(createInclusionContext(), calledFromTryStatement); } } } diff --git a/src/ast/values.ts b/src/ast/values.ts index d696649ffd9..6aceeaff4f2 100644 --- a/src/ast/values.ts +++ b/src/ast/values.ts @@ -41,7 +41,7 @@ export const UNKNOWN_EXPRESSION: ExpressionEntity = { include: () => {}, includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void { for (const arg of args) { - arg.include(false, createInclusionContext()); + arg.include(createInclusionContext(), false); } }, included: true, @@ -115,7 +115,7 @@ export class UnknownArrayExpression implements ExpressionEntity { includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void { for (const arg of args) { - arg.include(false, createInclusionContext()); + arg.include(createInclusionContext(), false); } } @@ -178,7 +178,7 @@ const UNKNOWN_LITERAL_BOOLEAN: ExpressionEntity = { include: () => {}, includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void { for (const arg of args) { - arg.include(false, createInclusionContext()); + arg.include(createInclusionContext(), false); } }, included: true, @@ -223,7 +223,7 @@ const UNKNOWN_LITERAL_NUMBER: ExpressionEntity = { include: () => {}, includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void { for (const arg of args) { - arg.include(false, createInclusionContext()); + arg.include(createInclusionContext(), false); } }, included: true, @@ -275,7 +275,7 @@ const UNKNOWN_LITERAL_STRING: ExpressionEntity = { include: () => {}, includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void { for (const arg of args) { - arg.include(false, createInclusionContext()); + arg.include(createInclusionContext(), false); } }, included: true, @@ -332,7 +332,7 @@ export class UnknownObjectExpression implements ExpressionEntity { includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void { for (const arg of args) { - arg.include(false, createInclusionContext()); + arg.include(createInclusionContext(), false); } } diff --git a/src/ast/variables/LocalVariable.ts b/src/ast/variables/LocalVariable.ts index 3ca5ac7e19e..c6e6356dcd1 100644 --- a/src/ast/variables/LocalVariable.ts +++ b/src/ast/variables/LocalVariable.ts @@ -163,7 +163,7 @@ export default class LocalVariable extends Variable { } for (const declaration of this.declarations) { // If node is a default export, it can save a tree-shaking run to include the full declaration now - if (!declaration.included) declaration.include(false, createInclusionContext()); + if (!declaration.included) declaration.include(createInclusionContext(), false); let node = declaration.parent as Node; while (!node.included) { // We do not want to properly include parents in case they are part of a dead branch @@ -179,7 +179,7 @@ export default class LocalVariable extends Variable { includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void { if (this.isReassigned) { for (const arg of args) { - arg.include(false, createInclusionContext()); + arg.include(createInclusionContext(), false); } } else if (this.init) { this.init.includeCallArguments(args); diff --git a/src/ast/variables/Variable.ts b/src/ast/variables/Variable.ts index 9638fde713d..81f23614320 100644 --- a/src/ast/variables/Variable.ts +++ b/src/ast/variables/Variable.ts @@ -88,7 +88,7 @@ export default class Variable implements ExpressionEntity { includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void { for (const arg of args) { - arg.include(false, createInclusionContext()); + arg.include(createInclusionContext(), false); } } From c1364c73ac1a25c5b5794d30b0dc716511694a03 Mon Sep 17 00:00:00 2001 From: Lukas Taegert-Atkinson Date: Sun, 13 Oct 2019 08:31:37 +0200 Subject: [PATCH 28/35] Reuse inclusion context when including functions --- src/ast/nodes/ArrowFunctionExpression.ts | 7 +++++-- src/ast/nodes/DoWhileStatement.ts | 2 +- src/ast/nodes/ForInStatement.ts | 2 +- src/ast/nodes/ForOfStatement.ts | 2 +- src/ast/nodes/ForStatement.ts | 2 +- src/ast/nodes/IfStatement.ts | 4 ++-- src/ast/nodes/SwitchStatement.ts | 2 +- src/ast/nodes/TryStatement.ts | 2 +- src/ast/nodes/WhileStatement.ts | 2 +- src/ast/nodes/shared/FunctionNode.ts | 11 +++++------ 10 files changed, 19 insertions(+), 17 deletions(-) diff --git a/src/ast/nodes/ArrowFunctionExpression.ts b/src/ast/nodes/ArrowFunctionExpression.ts index 123db3939bf..b242c8aa63c 100644 --- a/src/ast/nodes/ArrowFunctionExpression.ts +++ b/src/ast/nodes/ArrowFunctionExpression.ts @@ -1,5 +1,5 @@ import CallOptions from '../CallOptions'; -import { createInclusionContext, HasEffectsContext, InclusionContext } from '../ExecutionContext'; +import { BREAKFLOW_NONE, HasEffectsContext, InclusionContext } from '../ExecutionContext'; import ReturnValueScope from '../scopes/ReturnValueScope'; import Scope from '../scopes/Scope'; import { ObjectPath, UNKNOWN_PATH, UnknownKey } from '../utils/PathTracker'; @@ -70,12 +70,15 @@ export default class ArrowFunctionExpression extends NodeBase { include(context: InclusionContext, includeChildrenRecursively: IncludeChildren) { this.included = true; - this.body.include(createInclusionContext(), includeChildrenRecursively); for (const param of this.params) { if (!(param instanceof Identifier)) { param.include(context, includeChildrenRecursively); } } + const { breakFlow } = context; + context.breakFlow = BREAKFLOW_NONE; + this.body.include(context, includeChildrenRecursively); + context.breakFlow = breakFlow; } includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void { diff --git a/src/ast/nodes/DoWhileStatement.ts b/src/ast/nodes/DoWhileStatement.ts index 151a036ed91..6dbed258576 100644 --- a/src/ast/nodes/DoWhileStatement.ts +++ b/src/ast/nodes/DoWhileStatement.ts @@ -25,7 +25,7 @@ export default class DoWhileStatement extends StatementBase { include(context: InclusionContext, includeChildrenRecursively: IncludeChildren) { this.included = true; this.test.include(context, includeChildrenRecursively); - const breakFlow = context.breakFlow; + const { breakFlow } = context; this.body.include(context, includeChildrenRecursively); if (context.breakFlow instanceof Set && context.breakFlow.has(null)) { context.breakFlow = breakFlow; diff --git a/src/ast/nodes/ForInStatement.ts b/src/ast/nodes/ForInStatement.ts index 28c9805e7a6..e078f79f6a8 100644 --- a/src/ast/nodes/ForInStatement.ts +++ b/src/ast/nodes/ForInStatement.ts @@ -50,7 +50,7 @@ export default class ForInStatement extends StatementBase { this.left.includeWithAllDeclaredVariables(includeChildrenRecursively, context); this.left.deoptimizePath(EMPTY_PATH); this.right.include(context, includeChildrenRecursively); - const breakFlow = context.breakFlow; + const { breakFlow } = context; this.body.include(context, includeChildrenRecursively); context.breakFlow = breakFlow; } diff --git a/src/ast/nodes/ForOfStatement.ts b/src/ast/nodes/ForOfStatement.ts index 8385c1fab33..71cd0e0b1bc 100644 --- a/src/ast/nodes/ForOfStatement.ts +++ b/src/ast/nodes/ForOfStatement.ts @@ -37,7 +37,7 @@ export default class ForOfStatement extends StatementBase { this.left.includeWithAllDeclaredVariables(includeChildrenRecursively, context); this.left.deoptimizePath(EMPTY_PATH); this.right.include(context, includeChildrenRecursively); - const breakFlow = context.breakFlow; + const { breakFlow } = context; this.body.include(context, includeChildrenRecursively); context.breakFlow = breakFlow; } diff --git a/src/ast/nodes/ForStatement.ts b/src/ast/nodes/ForStatement.ts index 3e526bed54c..6def8e46003 100644 --- a/src/ast/nodes/ForStatement.ts +++ b/src/ast/nodes/ForStatement.ts @@ -40,7 +40,7 @@ export default class ForStatement extends StatementBase { this.included = true; if (this.init) this.init.include(context, includeChildrenRecursively); if (this.test) this.test.include(context, includeChildrenRecursively); - const breakFlow = context.breakFlow; + const { breakFlow } = context; if (this.update) this.update.include(context, includeChildrenRecursively); if (this.body) this.body.include(context, includeChildrenRecursively); context.breakFlow = breakFlow; diff --git a/src/ast/nodes/IfStatement.ts b/src/ast/nodes/IfStatement.ts index e1b6661d330..f9e9b0daa2f 100644 --- a/src/ast/nodes/IfStatement.ts +++ b/src/ast/nodes/IfStatement.ts @@ -38,7 +38,7 @@ export default class IfStatement extends StatementBase implements DeoptimizableE hasEffects(context: HasEffectsContext): boolean { if (this.test.hasEffects(context)) return true; if (this.testValue === UnknownValue) { - const breakFlow = context.breakFlow; + const { breakFlow } = context; if (this.consequent.hasEffects(context)) return true; const consequentBreakFlow = context.breakFlow; context.breakFlow = breakFlow; @@ -124,7 +124,7 @@ export default class IfStatement extends StatementBase implements DeoptimizableE private includeUnknownTest(context: InclusionContext) { this.test.include(context, false); - const breakFlow = context.breakFlow; + const { breakFlow } = context; let consequentBreakFlow: BreakFlow | false = false; if (this.consequent.shouldBeIncluded(context)) { this.consequent.include(context, false); diff --git a/src/ast/nodes/SwitchStatement.ts b/src/ast/nodes/SwitchStatement.ts index fb7e02769d5..072630b8977 100644 --- a/src/ast/nodes/SwitchStatement.ts +++ b/src/ast/nodes/SwitchStatement.ts @@ -63,7 +63,7 @@ export default class SwitchStatement extends StatementBase { include(context: InclusionContext, includeChildrenRecursively: IncludeChildren) { this.included = true; this.discriminant.include(context, includeChildrenRecursively); - const breakFlow = context.breakFlow; + const { breakFlow } = context; let hasDefault = false; let minBreakFlow: BreakFlow | false = BREAKFLOW_ERROR_RETURN; for (const switchCase of this.cases) { diff --git a/src/ast/nodes/TryStatement.ts b/src/ast/nodes/TryStatement.ts index 5936860ebc6..67182e53790 100644 --- a/src/ast/nodes/TryStatement.ts +++ b/src/ast/nodes/TryStatement.ts @@ -21,7 +21,7 @@ export default class TryStatement extends StatementBase { } include(context: InclusionContext, includeChildrenRecursively: IncludeChildren) { - const breakFlow = context.breakFlow; + const { breakFlow } = context; if (!this.directlyIncluded || !this.context.tryCatchDeoptimization) { this.included = true; this.directlyIncluded = true; diff --git a/src/ast/nodes/WhileStatement.ts b/src/ast/nodes/WhileStatement.ts index 4e44c9e3d72..3779f10650e 100644 --- a/src/ast/nodes/WhileStatement.ts +++ b/src/ast/nodes/WhileStatement.ts @@ -23,7 +23,7 @@ export default class WhileStatement extends StatementBase { include(context: InclusionContext, includeChildrenRecursively: IncludeChildren) { this.included = true; this.test.include(context, includeChildrenRecursively); - const breakFlow = context.breakFlow; + const { breakFlow } = context; this.body.include(context, includeChildrenRecursively); context.breakFlow = breakFlow; } diff --git a/src/ast/nodes/shared/FunctionNode.ts b/src/ast/nodes/shared/FunctionNode.ts index 83f1dfafd08..6a1fe8f52da 100644 --- a/src/ast/nodes/shared/FunctionNode.ts +++ b/src/ast/nodes/shared/FunctionNode.ts @@ -1,9 +1,5 @@ import CallOptions from '../../CallOptions'; -import { - createInclusionContext, - HasEffectsContext, - InclusionContext -} from '../../ExecutionContext'; +import { BREAKFLOW_NONE, HasEffectsContext, InclusionContext } from '../../ExecutionContext'; import FunctionScope from '../../scopes/FunctionScope'; import { ObjectPath, UNKNOWN_PATH, UnknownKey } from '../../utils/PathTracker'; import { UNKNOWN_EXPRESSION, UnknownObjectExpression } from '../../values'; @@ -95,7 +91,6 @@ export default class FunctionNode extends NodeBase { include(context: InclusionContext, includeChildrenRecursively: IncludeChildren) { this.included = true; - this.body.include(createInclusionContext(), includeChildrenRecursively); if (this.id) this.id.include(); const hasArguments = this.scope.argumentsVariable.included; for (const param of this.params) { @@ -103,6 +98,10 @@ export default class FunctionNode extends NodeBase { param.include(context, includeChildrenRecursively); } } + const { breakFlow } = context; + context.breakFlow = BREAKFLOW_NONE; + this.body.include(context, includeChildrenRecursively); + context.breakFlow = breakFlow; } includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void { From 7190eaf115443f094cd9bd50345bb4e2d6ae8c82 Mon Sep 17 00:00:00 2001 From: Lukas Taegert-Atkinson Date: Sun, 13 Oct 2019 08:41:01 +0200 Subject: [PATCH 29/35] Reuse inclusion context when including call arguments --- src/ast/nodes/ArrowFunctionExpression.ts | 4 ++-- src/ast/nodes/CallExpression.ts | 2 +- src/ast/nodes/Identifier.ts | 6 +++--- src/ast/nodes/MemberExpression.ts | 6 +++--- src/ast/nodes/shared/Expression.ts | 2 +- src/ast/nodes/shared/FunctionNode.ts | 4 ++-- src/ast/nodes/shared/MultiExpression.ts | 6 +++--- src/ast/nodes/shared/Node.ts | 5 ++--- src/ast/scopes/FunctionScope.ts | 8 ++++---- src/ast/scopes/ParameterScope.ts | 8 ++++---- src/ast/values.ts | 26 ++++++++++++------------ src/ast/variables/LocalVariable.ts | 8 ++++---- src/ast/variables/Variable.ts | 6 +++--- 13 files changed, 45 insertions(+), 46 deletions(-) diff --git a/src/ast/nodes/ArrowFunctionExpression.ts b/src/ast/nodes/ArrowFunctionExpression.ts index b242c8aa63c..104bce3b19f 100644 --- a/src/ast/nodes/ArrowFunctionExpression.ts +++ b/src/ast/nodes/ArrowFunctionExpression.ts @@ -81,8 +81,8 @@ export default class ArrowFunctionExpression extends NodeBase { context.breakFlow = breakFlow; } - includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void { - this.scope.includeCallArguments(args); + includeCallArguments(context: InclusionContext, args: (ExpressionNode | SpreadElement)[]): void { + this.scope.includeCallArguments(context, args); } initialise() { diff --git a/src/ast/nodes/CallExpression.ts b/src/ast/nodes/CallExpression.ts index 8f5ad86c287..6ca79fd110e 100644 --- a/src/ast/nodes/CallExpression.ts +++ b/src/ast/nodes/CallExpression.ts @@ -211,7 +211,7 @@ export default class CallExpression extends NodeBase implements DeoptimizableEnt this.included = true; this.callee.include(context, false); } - this.callee.includeCallArguments(this.arguments); + this.callee.includeCallArguments(context, this.arguments); if (!(this.returnExpression as ExpressionEntity).included) { (this.returnExpression as ExpressionEntity).include(context, false); } diff --git a/src/ast/nodes/Identifier.ts b/src/ast/nodes/Identifier.ts index 0c27acc83fe..00c4def11d9 100644 --- a/src/ast/nodes/Identifier.ts +++ b/src/ast/nodes/Identifier.ts @@ -4,7 +4,7 @@ import { BLANK } from '../../utils/blank'; import { NodeRenderOptions, RenderOptions } from '../../utils/renderHelpers'; import CallOptions from '../CallOptions'; import { DeoptimizableEntity } from '../DeoptimizableEntity'; -import { HasEffectsContext } from '../ExecutionContext'; +import { HasEffectsContext, InclusionContext } from '../ExecutionContext'; import FunctionScope from '../scopes/FunctionScope'; import { EMPTY_PATH, ObjectPath, PathTracker } from '../utils/PathTracker'; import { LiteralValueOrUnknown } from '../values'; @@ -136,8 +136,8 @@ export default class Identifier extends NodeBase implements PatternNode { } } - includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void { - (this.variable as Variable).includeCallArguments(args); + includeCallArguments(context: InclusionContext, args: (ExpressionNode | SpreadElement)[]): void { + (this.variable as Variable).includeCallArguments(context, args); } render( diff --git a/src/ast/nodes/MemberExpression.ts b/src/ast/nodes/MemberExpression.ts index cddea3beb9b..ea9719b1688 100644 --- a/src/ast/nodes/MemberExpression.ts +++ b/src/ast/nodes/MemberExpression.ts @@ -220,11 +220,11 @@ export default class MemberExpression extends NodeBase implements DeoptimizableE this.property.include(context, includeChildrenRecursively); } - includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void { + includeCallArguments(context: InclusionContext, args: (ExpressionNode | SpreadElement)[]): void { if (this.variable) { - this.variable.includeCallArguments(args); + this.variable.includeCallArguments(context, args); } else { - super.includeCallArguments(args); + super.includeCallArguments(context, args); } } diff --git a/src/ast/nodes/shared/Expression.ts b/src/ast/nodes/shared/Expression.ts index 9e0d4d0391e..7dbea2c98a7 100644 --- a/src/ast/nodes/shared/Expression.ts +++ b/src/ast/nodes/shared/Expression.ts @@ -32,5 +32,5 @@ export interface ExpressionEntity extends WritableEntity { context: HasEffectsContext ): boolean; include(context: InclusionContext, includeChildrenRecursively: IncludeChildren): void; - includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void; + includeCallArguments(context: InclusionContext, args: (ExpressionNode | SpreadElement)[]): void; } diff --git a/src/ast/nodes/shared/FunctionNode.ts b/src/ast/nodes/shared/FunctionNode.ts index 6a1fe8f52da..6a9d3d9da6f 100644 --- a/src/ast/nodes/shared/FunctionNode.ts +++ b/src/ast/nodes/shared/FunctionNode.ts @@ -104,8 +104,8 @@ export default class FunctionNode extends NodeBase { context.breakFlow = breakFlow; } - includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void { - this.scope.includeCallArguments(args); + includeCallArguments(context: InclusionContext, args: (ExpressionNode | SpreadElement)[]): void { + this.scope.includeCallArguments(context, args); } initialise() { diff --git a/src/ast/nodes/shared/MultiExpression.ts b/src/ast/nodes/shared/MultiExpression.ts index 7e1ab1f4042..d9cd9342fdb 100644 --- a/src/ast/nodes/shared/MultiExpression.ts +++ b/src/ast/nodes/shared/MultiExpression.ts @@ -1,6 +1,6 @@ import CallOptions from '../../CallOptions'; import { DeoptimizableEntity } from '../../DeoptimizableEntity'; -import { HasEffectsContext } from '../../ExecutionContext'; +import { HasEffectsContext, InclusionContext } from '../../ExecutionContext'; import { ObjectPath, PathTracker } from '../../utils/PathTracker'; import { LiteralValueOrUnknown, UnknownValue } from '../../values'; import SpreadElement from '../SpreadElement'; @@ -65,9 +65,9 @@ export class MultiExpression implements ExpressionEntity { include(): void {} - includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void { + includeCallArguments(context: InclusionContext, args: (ExpressionNode | SpreadElement)[]): void { for (const expression of this.expressions) { - expression.includeCallArguments(args); + expression.includeCallArguments(context, args); } } } diff --git a/src/ast/nodes/shared/Node.ts b/src/ast/nodes/shared/Node.ts index d1e15e295d3..d3680a29a65 100644 --- a/src/ast/nodes/shared/Node.ts +++ b/src/ast/nodes/shared/Node.ts @@ -7,7 +7,6 @@ import { DeoptimizableEntity } from '../../DeoptimizableEntity'; import { Entity } from '../../Entity'; import { createHasEffectsContext, - createInclusionContext, HasEffectsContext, InclusionContext } from '../../ExecutionContext'; @@ -206,9 +205,9 @@ export class NodeBase implements ExpressionNode { } } - includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void { + includeCallArguments(context: InclusionContext, args: (ExpressionNode | SpreadElement)[]): void { for (const arg of args) { - arg.include(createInclusionContext(), false); + arg.include(context, false); } } diff --git a/src/ast/scopes/FunctionScope.ts b/src/ast/scopes/FunctionScope.ts index d6ef25dee89..7519f8036ff 100644 --- a/src/ast/scopes/FunctionScope.ts +++ b/src/ast/scopes/FunctionScope.ts @@ -1,5 +1,5 @@ import { AstContext } from '../../Module'; -import { createInclusionContext } from '../ExecutionContext'; +import { InclusionContext } from '../ExecutionContext'; import { ExpressionNode } from '../nodes/shared/Node'; import SpreadElement from '../nodes/SpreadElement'; import ArgumentsVariable from '../variables/ArgumentsVariable'; @@ -21,12 +21,12 @@ export default class FunctionScope extends ReturnValueScope { return this; } - includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void { - super.includeCallArguments(args); + includeCallArguments(context: InclusionContext, args: (ExpressionNode | SpreadElement)[]): void { + super.includeCallArguments(context, args); if (this.argumentsVariable.included) { for (const arg of args) { if (!arg.included) { - arg.include(createInclusionContext(), false); + arg.include(context, false); } } } diff --git a/src/ast/scopes/ParameterScope.ts b/src/ast/scopes/ParameterScope.ts index 014625a6826..1faecfddef7 100644 --- a/src/ast/scopes/ParameterScope.ts +++ b/src/ast/scopes/ParameterScope.ts @@ -1,5 +1,5 @@ import { AstContext } from '../../Module'; -import { createInclusionContext } from '../ExecutionContext'; +import { InclusionContext } from '../ExecutionContext'; import Identifier from '../nodes/Identifier'; import { ExpressionNode } from '../nodes/shared/Node'; import SpreadElement from '../nodes/SpreadElement'; @@ -47,7 +47,7 @@ export default class ParameterScope extends ChildScope { this.hasRest = hasRest; } - includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void { + includeCallArguments(context: InclusionContext, args: (ExpressionNode | SpreadElement)[]): void { let calledFromTryStatement = false; let argIncluded = false; const restParam = this.hasRest && this.parameters[this.parameters.length - 1]; @@ -65,11 +65,11 @@ export default class ParameterScope extends ChildScope { } } } - if (!argIncluded && arg.shouldBeIncluded(createInclusionContext())) { + if (!argIncluded && arg.shouldBeIncluded(context)) { argIncluded = true; } if (argIncluded) { - arg.include(createInclusionContext(), calledFromTryStatement); + arg.include(context, calledFromTryStatement); } } } diff --git a/src/ast/values.ts b/src/ast/values.ts index 6aceeaff4f2..6d86bc58bf6 100644 --- a/src/ast/values.ts +++ b/src/ast/values.ts @@ -1,5 +1,5 @@ import CallOptions from './CallOptions'; -import { createInclusionContext, HasEffectsContext } from './ExecutionContext'; +import { HasEffectsContext, InclusionContext } from './ExecutionContext'; import { LiteralValue } from './nodes/Literal'; import { ExpressionEntity } from './nodes/shared/Expression'; import { ExpressionNode } from './nodes/shared/Node'; @@ -39,9 +39,9 @@ export const UNKNOWN_EXPRESSION: ExpressionEntity = { hasEffectsWhenAssignedAtPath: path => path.length > 0, hasEffectsWhenCalledAtPath: () => true, include: () => {}, - includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void { + includeCallArguments(context: InclusionContext, args: (ExpressionNode | SpreadElement)[]): void { for (const arg of args) { - arg.include(createInclusionContext(), false); + arg.include(context, false); } }, included: true, @@ -113,9 +113,9 @@ export class UnknownArrayExpression implements ExpressionEntity { this.included = true; } - includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void { + includeCallArguments(context: InclusionContext, args: (ExpressionNode | SpreadElement)[]): void { for (const arg of args) { - arg.include(createInclusionContext(), false); + arg.include(context, false); } } @@ -176,9 +176,9 @@ const UNKNOWN_LITERAL_BOOLEAN: ExpressionEntity = { return true; }, include: () => {}, - includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void { + includeCallArguments(context: InclusionContext, args: (ExpressionNode | SpreadElement)[]): void { for (const arg of args) { - arg.include(createInclusionContext(), false); + arg.include(context, false); } }, included: true, @@ -221,9 +221,9 @@ const UNKNOWN_LITERAL_NUMBER: ExpressionEntity = { return true; }, include: () => {}, - includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void { + includeCallArguments(context: InclusionContext, args: (ExpressionNode | SpreadElement)[]): void { for (const arg of args) { - arg.include(createInclusionContext(), false); + arg.include(context, false); } }, included: true, @@ -273,9 +273,9 @@ const UNKNOWN_LITERAL_STRING: ExpressionEntity = { return true; }, include: () => {}, - includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void { + includeCallArguments(context: InclusionContext, args: (ExpressionNode | SpreadElement)[]): void { for (const arg of args) { - arg.include(createInclusionContext(), false); + arg.include(context, false); } }, included: true, @@ -330,9 +330,9 @@ export class UnknownObjectExpression implements ExpressionEntity { this.included = true; } - includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void { + includeCallArguments(context: InclusionContext, args: (ExpressionNode | SpreadElement)[]): void { for (const arg of args) { - arg.include(createInclusionContext(), false); + arg.include(context, false); } } diff --git a/src/ast/variables/LocalVariable.ts b/src/ast/variables/LocalVariable.ts index c6e6356dcd1..e662e4cce18 100644 --- a/src/ast/variables/LocalVariable.ts +++ b/src/ast/variables/LocalVariable.ts @@ -2,7 +2,7 @@ import Module, { AstContext } from '../../Module'; import { markModuleAndImpureDependenciesAsExecuted } from '../../utils/traverseStaticDependencies'; import CallOptions from '../CallOptions'; import { DeoptimizableEntity } from '../DeoptimizableEntity'; -import { createInclusionContext, HasEffectsContext } from '../ExecutionContext'; +import { createInclusionContext, HasEffectsContext, InclusionContext } from '../ExecutionContext'; import ExportDefaultDeclaration from '../nodes/ExportDefaultDeclaration'; import Identifier from '../nodes/Identifier'; import * as NodeType from '../nodes/NodeType'; @@ -176,13 +176,13 @@ export default class LocalVariable extends Variable { } } - includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void { + includeCallArguments(context: InclusionContext, args: (ExpressionNode | SpreadElement)[]): void { if (this.isReassigned) { for (const arg of args) { - arg.include(createInclusionContext(), false); + arg.include(context, false); } } else if (this.init) { - this.init.includeCallArguments(args); + this.init.includeCallArguments(context, args); } } diff --git a/src/ast/variables/Variable.ts b/src/ast/variables/Variable.ts index 81f23614320..7bfab8d38d5 100644 --- a/src/ast/variables/Variable.ts +++ b/src/ast/variables/Variable.ts @@ -2,7 +2,7 @@ import ExternalModule from '../../ExternalModule'; import Module from '../../Module'; import CallOptions from '../CallOptions'; import { DeoptimizableEntity } from '../DeoptimizableEntity'; -import { createInclusionContext, HasEffectsContext } from '../ExecutionContext'; +import { HasEffectsContext, InclusionContext } from '../ExecutionContext'; import Identifier from '../nodes/Identifier'; import { ExpressionEntity } from '../nodes/shared/Expression'; import { ExpressionNode } from '../nodes/shared/Node'; @@ -86,9 +86,9 @@ export default class Variable implements ExpressionEntity { this.included = true; } - includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void { + includeCallArguments(context: InclusionContext, args: (ExpressionNode | SpreadElement)[]): void { for (const arg of args) { - arg.include(createInclusionContext(), false); + arg.include(context, false); } } From 145dfd77a27bf228edd69c22eeafb7a0f1441cb7 Mon Sep 17 00:00:00 2001 From: Lukas Taegert-Atkinson Date: Sun, 13 Oct 2019 10:12:16 +0200 Subject: [PATCH 30/35] Reuse inclusion context when including local variables --- src/Chunk.ts | 4 +++- src/Module.ts | 17 +++++++++-------- src/ast/nodes/BreakStatement.ts | 2 +- src/ast/nodes/ContinueStatement.ts | 2 +- src/ast/nodes/ExportDefaultDeclaration.ts | 2 +- src/ast/nodes/Identifier.ts | 4 ++-- src/ast/nodes/LabeledStatement.ts | 2 +- src/ast/nodes/MemberExpression.ts | 2 +- src/ast/nodes/shared/FunctionNode.ts | 2 +- src/ast/variables/LocalVariable.ts | 6 +++--- src/ast/variables/NamespaceVariable.ts | 7 ++++--- src/ast/variables/Variable.ts | 2 +- 12 files changed, 28 insertions(+), 24 deletions(-) diff --git a/src/Chunk.ts b/src/Chunk.ts index 2a9b94aa76f..beb66769f48 100644 --- a/src/Chunk.ts +++ b/src/Chunk.ts @@ -1,6 +1,7 @@ import sha256 from 'hash.js/lib/hash/sha/256'; import MagicString, { Bundle as MagicStringBundle, SourceMap } from 'magic-string'; import { relative } from '../browser/path'; +import { createInclusionContext } from './ast/ExecutionContext'; import ExportDefaultDeclaration from './ast/nodes/ExportDefaultDeclaration'; import FunctionDeclaration from './ast/nodes/FunctionDeclaration'; import { UNDEFINED_EXPRESSION } from './ast/values'; @@ -1190,9 +1191,10 @@ export default class Chunk { } } } + const context = createInclusionContext(); for (const { node, resolution } of module.dynamicImports) { if (node.included && resolution instanceof Module && resolution.chunk === this) - resolution.getOrCreateNamespace().include(); + resolution.getOrCreateNamespace().include(context); } } } diff --git a/src/Module.ts b/src/Module.ts index 15ed49721f3..bd675619b1a 100644 --- a/src/Module.ts +++ b/src/Module.ts @@ -3,7 +3,7 @@ import * as ESTree from 'estree'; import { locate } from 'locate-character'; import MagicString from 'magic-string'; import extractAssignedNames from 'rollup-pluginutils/src/extractAssignedNames'; -import { createInclusionContext } from './ast/ExecutionContext'; +import { createInclusionContext, InclusionContext } from './ast/ExecutionContext'; import ClassDeclaration from './ast/nodes/ClassDeclaration'; import ExportAllDeclaration from './ast/nodes/ExportAllDeclaration'; import ExportDefaultDeclaration from './ast/nodes/ExportDefaultDeclaration'; @@ -98,7 +98,7 @@ export interface AstContext { getReexports: () => string[]; importDescriptions: { [name: string]: ImportDescription }; includeDynamicImport: (node: ImportExpression) => void; - includeVariable: (variable: Variable) => void; + includeVariable: (context: InclusionContext, variable: Variable) => void; isCrossChunkImport: (importDescription: ImportDescription) => boolean; magicString: MagicString; module: Module; // not to be used for tree-shaking @@ -450,8 +450,8 @@ export default class Module { } include(): void { - if (this.ast.shouldBeIncluded(createInclusionContext())) - this.ast.include(createInclusionContext(), false); + const context = createInclusionContext(); + if (this.ast.shouldBeIncluded(context)) this.ast.include(context, false); } includeAllExports() { @@ -460,11 +460,12 @@ export default class Module { markModuleAndImpureDependenciesAsExecuted(this); } + const context = createInclusionContext(); for (const exportName of this.getExports()) { const variable = this.getVariableForExportName(exportName); variable.deoptimizePath(UNKNOWN_PATH); if (!variable.included) { - variable.include(); + variable.include(context); this.graph.needsTreeshakingPass = true; } } @@ -473,7 +474,7 @@ export default class Module { const variable = this.getVariableForExportName(name); variable.deoptimizePath(UNKNOWN_PATH); if (!variable.included) { - variable.include(); + variable.include(context); this.graph.needsTreeshakingPass = true; } if (variable instanceof ExternalVariable) { @@ -836,10 +837,10 @@ export default class Module { } } - private includeVariable(variable: Variable) { + private includeVariable(context: InclusionContext, variable: Variable) { const variableModule = variable.module; if (!variable.included) { - variable.include(); + variable.include(context); this.graph.needsTreeshakingPass = true; } if (variableModule && variableModule !== this) { diff --git a/src/ast/nodes/BreakStatement.ts b/src/ast/nodes/BreakStatement.ts index d23ba875780..d1c24b2a799 100644 --- a/src/ast/nodes/BreakStatement.ts +++ b/src/ast/nodes/BreakStatement.ts @@ -16,7 +16,7 @@ export default class BreakStatement extends StatementBase { include(context: InclusionContext) { this.included = true; - if (this.label) this.label.include(); + if (this.label) this.label.include(context); context.breakFlow = new Set([this.label && this.label.name]); } } diff --git a/src/ast/nodes/ContinueStatement.ts b/src/ast/nodes/ContinueStatement.ts index cc15954000a..93cfec4d6f8 100644 --- a/src/ast/nodes/ContinueStatement.ts +++ b/src/ast/nodes/ContinueStatement.ts @@ -16,7 +16,7 @@ export default class ContinueStatement extends StatementBase { include(context: InclusionContext) { this.included = true; - if (this.label) this.label.include(); + if (this.label) this.label.include(context); context.breakFlow = new Set([this.label && this.label.name]); } } diff --git a/src/ast/nodes/ExportDefaultDeclaration.ts b/src/ast/nodes/ExportDefaultDeclaration.ts index 5718d78b313..cc7a068f7ea 100644 --- a/src/ast/nodes/ExportDefaultDeclaration.ts +++ b/src/ast/nodes/ExportDefaultDeclaration.ts @@ -47,7 +47,7 @@ export default class ExportDefaultDeclaration extends NodeBase { include(context: InclusionContext, includeChildrenRecursively: IncludeChildren) { super.include(context, includeChildrenRecursively); if (includeChildrenRecursively) { - this.context.includeVariable(this.variable); + this.context.includeVariable(context, this.variable); } } diff --git a/src/ast/nodes/Identifier.ts b/src/ast/nodes/Identifier.ts index 00c4def11d9..e6c349a9244 100644 --- a/src/ast/nodes/Identifier.ts +++ b/src/ast/nodes/Identifier.ts @@ -127,11 +127,11 @@ export default class Identifier extends NodeBase implements PatternNode { return !this.variable || this.variable.hasEffectsWhenCalledAtPath(path, callOptions, context); } - include() { + include(context: InclusionContext) { if (!this.included) { this.included = true; if (this.variable !== null) { - this.context.includeVariable(this.variable); + this.context.includeVariable(context, this.variable); } } } diff --git a/src/ast/nodes/LabeledStatement.ts b/src/ast/nodes/LabeledStatement.ts index 895796928b8..1abb654f568 100644 --- a/src/ast/nodes/LabeledStatement.ts +++ b/src/ast/nodes/LabeledStatement.ts @@ -20,7 +20,7 @@ export default class LabeledStatement extends StatementBase { include(context: InclusionContext, includeChildrenRecursively: IncludeChildren) { this.included = true; - this.label.include(); + this.label.include(context); this.body.include(context, includeChildrenRecursively); if (context.breakFlow instanceof Set && context.breakFlow.has(this.label.name)) { context.breakFlow = BREAKFLOW_NONE; diff --git a/src/ast/nodes/MemberExpression.ts b/src/ast/nodes/MemberExpression.ts index ea9719b1688..1991ecb82e0 100644 --- a/src/ast/nodes/MemberExpression.ts +++ b/src/ast/nodes/MemberExpression.ts @@ -213,7 +213,7 @@ export default class MemberExpression extends NodeBase implements DeoptimizableE if (!this.included) { this.included = true; if (this.variable !== null) { - this.context.includeVariable(this.variable); + this.context.includeVariable(context, this.variable); } } this.object.include(context, includeChildrenRecursively); diff --git a/src/ast/nodes/shared/FunctionNode.ts b/src/ast/nodes/shared/FunctionNode.ts index 6a9d3d9da6f..4b6d3898a51 100644 --- a/src/ast/nodes/shared/FunctionNode.ts +++ b/src/ast/nodes/shared/FunctionNode.ts @@ -91,7 +91,7 @@ export default class FunctionNode extends NodeBase { include(context: InclusionContext, includeChildrenRecursively: IncludeChildren) { this.included = true; - if (this.id) this.id.include(); + if (this.id) this.id.include(context); const hasArguments = this.scope.argumentsVariable.included; for (const param of this.params) { if (!(param instanceof Identifier) || hasArguments) { diff --git a/src/ast/variables/LocalVariable.ts b/src/ast/variables/LocalVariable.ts index e662e4cce18..1fe9e980391 100644 --- a/src/ast/variables/LocalVariable.ts +++ b/src/ast/variables/LocalVariable.ts @@ -2,7 +2,7 @@ import Module, { AstContext } from '../../Module'; import { markModuleAndImpureDependenciesAsExecuted } from '../../utils/traverseStaticDependencies'; import CallOptions from '../CallOptions'; import { DeoptimizableEntity } from '../DeoptimizableEntity'; -import { createInclusionContext, HasEffectsContext, InclusionContext } from '../ExecutionContext'; +import { HasEffectsContext, InclusionContext } from '../ExecutionContext'; import ExportDefaultDeclaration from '../nodes/ExportDefaultDeclaration'; import Identifier from '../nodes/Identifier'; import * as NodeType from '../nodes/NodeType'; @@ -155,7 +155,7 @@ export default class LocalVariable extends Variable { this.init.hasEffectsWhenCalledAtPath(path, callOptions, context)) as boolean; } - include() { + include(context: InclusionContext) { if (!this.included) { this.included = true; if (!this.module.isExecuted) { @@ -163,7 +163,7 @@ export default class LocalVariable extends Variable { } for (const declaration of this.declarations) { // If node is a default export, it can save a tree-shaking run to include the full declaration now - if (!declaration.included) declaration.include(createInclusionContext(), false); + if (!declaration.included) declaration.include(context, false); let node = declaration.parent as Node; while (!node.included) { // We do not want to properly include parents in case they are part of a dead branch diff --git a/src/ast/variables/NamespaceVariable.ts b/src/ast/variables/NamespaceVariable.ts index 697ab5b9cae..b72eee28c16 100644 --- a/src/ast/variables/NamespaceVariable.ts +++ b/src/ast/variables/NamespaceVariable.ts @@ -1,6 +1,7 @@ import Module, { AstContext } from '../../Module'; import { RenderOptions } from '../../utils/renderHelpers'; import { RESERVED_NAMES } from '../../utils/reservedNames'; +import { InclusionContext } from '../ExecutionContext'; import Identifier from '../nodes/Identifier'; import { UNKNOWN_PATH } from '../utils/PathTracker'; import Variable from './Variable'; @@ -36,7 +37,7 @@ export default class NamespaceVariable extends Variable { } } - include() { + include(context: InclusionContext) { if (!this.included) { if (this.containsExternalNamespace) { this.context.error( @@ -57,10 +58,10 @@ export default class NamespaceVariable extends Variable { } if (this.context.preserveModules) { for (const memberName of Object.keys(this.memberVariables)) - this.memberVariables[memberName].include(); + this.memberVariables[memberName].include(context); } else { for (const memberName of Object.keys(this.memberVariables)) - this.context.includeVariable(this.memberVariables[memberName]); + this.context.includeVariable(context, this.memberVariables[memberName]); } } } diff --git a/src/ast/variables/Variable.ts b/src/ast/variables/Variable.ts index 7bfab8d38d5..b9a6777b0e2 100644 --- a/src/ast/variables/Variable.ts +++ b/src/ast/variables/Variable.ts @@ -82,7 +82,7 @@ export default class Variable implements ExpressionEntity { * previously. * Once a variable is included, it should take care all its declarations are included. */ - include() { + include(_context: InclusionContext) { this.included = true; } From c7969ca4a21f123db889395e4664a1fcc4fb955b Mon Sep 17 00:00:00 2001 From: Lukas Taegert-Atkinson Date: Sun, 13 Oct 2019 10:33:45 +0200 Subject: [PATCH 31/35] Improve coverage for catch scopes --- src/ast/scopes/BlockScope.ts | 2 +- src/ast/scopes/CatchScope.ts | 2 +- .../samples/catch-scope-variables/_config.js | 3 +++ .../function/samples/catch-scope-variables/main.js | 14 ++++++++++++++ 4 files changed, 19 insertions(+), 2 deletions(-) create mode 100644 test/function/samples/catch-scope-variables/_config.js create mode 100644 test/function/samples/catch-scope-variables/main.js diff --git a/src/ast/scopes/BlockScope.ts b/src/ast/scopes/BlockScope.ts index cfff83b1f72..d08b6767751 100644 --- a/src/ast/scopes/BlockScope.ts +++ b/src/ast/scopes/BlockScope.ts @@ -10,7 +10,7 @@ export default class BlockScope extends ChildScope { identifier: Identifier, context: AstContext, init: ExpressionEntity | null = null, - isHoisted: boolean | 'function' = false + isHoisted: boolean | 'function' ): LocalVariable { if (isHoisted) { return this.parent.addDeclaration( diff --git a/src/ast/scopes/CatchScope.ts b/src/ast/scopes/CatchScope.ts index 57883f3c15c..6bbaa797632 100644 --- a/src/ast/scopes/CatchScope.ts +++ b/src/ast/scopes/CatchScope.ts @@ -9,7 +9,7 @@ export default class CatchScope extends ParameterScope { identifier: Identifier, context: AstContext, init: ExpressionEntity | null = null, - isHoisted: boolean | 'function' = false + isHoisted: boolean | 'function' ): LocalVariable { if (isHoisted) { return this.parent.addDeclaration(identifier, context, init, isHoisted); diff --git a/test/function/samples/catch-scope-variables/_config.js b/test/function/samples/catch-scope-variables/_config.js new file mode 100644 index 00000000000..b902e7168d5 --- /dev/null +++ b/test/function/samples/catch-scope-variables/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'handles variable declarations in catch scopes' +}; diff --git a/test/function/samples/catch-scope-variables/main.js b/test/function/samples/catch-scope-variables/main.js new file mode 100644 index 00000000000..bcb85c6a2c0 --- /dev/null +++ b/test/function/samples/catch-scope-variables/main.js @@ -0,0 +1,14 @@ +var outsideVar = 'outside'; +let outsideLet = 'outside'; + +try { + throw new Error(); +} catch { + var outsideVar = 'inside'; + let outsideLet = 'inside'; + var insideVar = 'inside'; +} + +assert.equal(outsideVar, 'inside'); +assert.equal(outsideLet, 'outside'); +assert.equal(insideVar, 'inside'); From 96c6fdca02f3b2eb00311771aaaf38da5e1ceb62 Mon Sep 17 00:00:00 2001 From: Lukas Taegert-Atkinson Date: Mon, 14 Oct 2019 07:28:48 +0200 Subject: [PATCH 32/35] Improve coverage, check access side-effects for instantiation, respect annotations in new expressions --- src/ast/CallOptions.ts | 25 +----------- src/ast/nodes/ArrayExpression.ts | 2 +- src/ast/nodes/ArrowFunctionExpression.ts | 2 +- src/ast/nodes/CallExpression.ts | 7 ++-- src/ast/nodes/ClassBody.ts | 2 +- src/ast/nodes/ConditionalExpression.ts | 2 +- src/ast/nodes/Identifier.ts | 2 +- src/ast/nodes/Literal.ts | 2 +- src/ast/nodes/LogicalExpression.ts | 2 +- src/ast/nodes/MemberExpression.ts | 2 +- src/ast/nodes/MethodDefinition.ts | 2 +- src/ast/nodes/NewExpression.ts | 14 ++++--- src/ast/nodes/ObjectExpression.ts | 2 +- src/ast/nodes/Property.ts | 13 ++----- src/ast/nodes/SequenceExpression.ts | 2 +- src/ast/nodes/TaggedTemplateExpression.ts | 8 ++-- src/ast/nodes/shared/ClassNode.ts | 3 +- src/ast/nodes/shared/Expression.ts | 2 +- src/ast/nodes/shared/FunctionNode.ts | 2 +- src/ast/nodes/shared/MultiExpression.ts | 2 +- src/ast/nodes/shared/Node.ts | 2 +- src/ast/values.ts | 9 ++--- src/ast/variables/LocalVariable.ts | 2 +- src/ast/variables/ThisVariable.ts | 2 +- src/ast/variables/Variable.ts | 2 +- .../form/samples/class-without-new/_config.js | 3 ++ .../samples/class-without-new/_expected.js | 3 ++ test/form/samples/class-without-new/main.js | 3 ++ .../access-when-called-effect/_config.js | 3 ++ .../access-when-called-effect/_expected.js | 39 +++++++++++++++++++ .../access-when-called-effect/main.js | 39 +++++++++++++++++++ .../early-access-getter-return/_config.js | 3 ++ .../early-access-getter-return/_expected.js | 7 ++++ .../early-access-getter-return/main.js | 16 ++++++++ .../early-access-getter-value/_config.js | 3 ++ .../early-access-getter-value/_expected.js | 7 ++++ .../early-access-getter-value/main.js | 16 ++++++++ .../pure-comments-disabled/_expected.js | 1 + .../samples/pure-comments-disabled/main.js | 1 + 39 files changed, 189 insertions(+), 70 deletions(-) create mode 100644 test/form/samples/class-without-new/_config.js create mode 100644 test/form/samples/class-without-new/_expected.js create mode 100644 test/form/samples/class-without-new/main.js create mode 100644 test/form/samples/property-setters-and-getters/access-when-called-effect/_config.js create mode 100644 test/form/samples/property-setters-and-getters/access-when-called-effect/_expected.js create mode 100644 test/form/samples/property-setters-and-getters/access-when-called-effect/main.js create mode 100644 test/form/samples/property-setters-and-getters/early-access-getter-return/_config.js create mode 100644 test/form/samples/property-setters-and-getters/early-access-getter-return/_expected.js create mode 100644 test/form/samples/property-setters-and-getters/early-access-getter-return/main.js create mode 100644 test/form/samples/property-setters-and-getters/early-access-getter-value/_config.js create mode 100644 test/form/samples/property-setters-and-getters/early-access-getter-value/_expected.js create mode 100644 test/form/samples/property-setters-and-getters/early-access-getter-value/main.js diff --git a/src/ast/CallOptions.ts b/src/ast/CallOptions.ts index c33e7c50596..ad368d69951 100644 --- a/src/ast/CallOptions.ts +++ b/src/ast/CallOptions.ts @@ -1,30 +1,9 @@ import { ExpressionEntity } from './nodes/shared/Expression'; import SpreadElement from './nodes/SpreadElement'; -export interface CallCreateOptions { - args?: (ExpressionEntity | SpreadElement)[]; - callIdentifier: Object; - withNew: boolean; -} - -export default class CallOptions implements CallCreateOptions { - static create(callOptions: CallCreateOptions) { - return new this(callOptions); - } +export const NO_ARGS = []; +export interface CallOptions { args: (ExpressionEntity | SpreadElement)[]; - callIdentifier: Object; withNew: boolean; - - constructor( - { withNew = false, args = [], callIdentifier = undefined as any }: CallCreateOptions = {} as any - ) { - this.withNew = withNew; - this.args = args; - this.callIdentifier = callIdentifier; - } - - equals(callOptions: CallOptions) { - return callOptions && this.callIdentifier === callOptions.callIdentifier; - } } diff --git a/src/ast/nodes/ArrayExpression.ts b/src/ast/nodes/ArrayExpression.ts index c0d54511328..f37b81dbbc5 100644 --- a/src/ast/nodes/ArrayExpression.ts +++ b/src/ast/nodes/ArrayExpression.ts @@ -1,4 +1,4 @@ -import CallOptions from '../CallOptions'; +import { CallOptions } from '../CallOptions'; import { HasEffectsContext } from '../ExecutionContext'; import { ObjectPath, UNKNOWN_PATH } from '../utils/PathTracker'; import { diff --git a/src/ast/nodes/ArrowFunctionExpression.ts b/src/ast/nodes/ArrowFunctionExpression.ts index 104bce3b19f..4d344c934c1 100644 --- a/src/ast/nodes/ArrowFunctionExpression.ts +++ b/src/ast/nodes/ArrowFunctionExpression.ts @@ -1,4 +1,4 @@ -import CallOptions from '../CallOptions'; +import { CallOptions } from '../CallOptions'; import { BREAKFLOW_NONE, HasEffectsContext, InclusionContext } from '../ExecutionContext'; import ReturnValueScope from '../scopes/ReturnValueScope'; import Scope from '../scopes/Scope'; diff --git a/src/ast/nodes/CallExpression.ts b/src/ast/nodes/CallExpression.ts index 6ca79fd110e..68daeb86768 100644 --- a/src/ast/nodes/CallExpression.ts +++ b/src/ast/nodes/CallExpression.ts @@ -5,7 +5,7 @@ import { NodeRenderOptions, RenderOptions } from '../../utils/renderHelpers'; -import CallOptions from '../CallOptions'; +import { CallOptions } from '../CallOptions'; import { DeoptimizableEntity } from '../DeoptimizableEntity'; import { HasEffectsContext, InclusionContext } from '../ExecutionContext'; import { @@ -218,11 +218,10 @@ export default class CallExpression extends NodeBase implements DeoptimizableEnt } initialise() { - this.callOptions = CallOptions.create({ + this.callOptions = { args: this.arguments, - callIdentifier: this, withNew: false - }); + }; } render( diff --git a/src/ast/nodes/ClassBody.ts b/src/ast/nodes/ClassBody.ts index decb6af0d5f..1c341665489 100644 --- a/src/ast/nodes/ClassBody.ts +++ b/src/ast/nodes/ClassBody.ts @@ -1,4 +1,4 @@ -import CallOptions from '../CallOptions'; +import { CallOptions } from '../CallOptions'; import { HasEffectsContext } from '../ExecutionContext'; import { EMPTY_PATH, ObjectPath } from '../utils/PathTracker'; import MethodDefinition from './MethodDefinition'; diff --git a/src/ast/nodes/ConditionalExpression.ts b/src/ast/nodes/ConditionalExpression.ts index 8e5c1a4a12a..630cc08754f 100644 --- a/src/ast/nodes/ConditionalExpression.ts +++ b/src/ast/nodes/ConditionalExpression.ts @@ -7,7 +7,7 @@ import { RenderOptions } from '../../utils/renderHelpers'; import { removeAnnotations } from '../../utils/treeshakeNode'; -import CallOptions from '../CallOptions'; +import { CallOptions } from '../CallOptions'; import { DeoptimizableEntity } from '../DeoptimizableEntity'; import { HasEffectsContext, InclusionContext } from '../ExecutionContext'; import { diff --git a/src/ast/nodes/Identifier.ts b/src/ast/nodes/Identifier.ts index e6c349a9244..9a94c48f8e1 100644 --- a/src/ast/nodes/Identifier.ts +++ b/src/ast/nodes/Identifier.ts @@ -2,7 +2,7 @@ import isReference from 'is-reference'; import MagicString from 'magic-string'; import { BLANK } from '../../utils/blank'; import { NodeRenderOptions, RenderOptions } from '../../utils/renderHelpers'; -import CallOptions from '../CallOptions'; +import { CallOptions } from '../CallOptions'; import { DeoptimizableEntity } from '../DeoptimizableEntity'; import { HasEffectsContext, InclusionContext } from '../ExecutionContext'; import FunctionScope from '../scopes/FunctionScope'; diff --git a/src/ast/nodes/Literal.ts b/src/ast/nodes/Literal.ts index e992fa55ced..29f575f52bb 100644 --- a/src/ast/nodes/Literal.ts +++ b/src/ast/nodes/Literal.ts @@ -1,5 +1,5 @@ import MagicString from 'magic-string'; -import CallOptions from '../CallOptions'; +import { CallOptions } from '../CallOptions'; import { HasEffectsContext } from '../ExecutionContext'; import { ObjectPath } from '../utils/PathTracker'; import { diff --git a/src/ast/nodes/LogicalExpression.ts b/src/ast/nodes/LogicalExpression.ts index 0c80347b21d..b0c52d59a14 100644 --- a/src/ast/nodes/LogicalExpression.ts +++ b/src/ast/nodes/LogicalExpression.ts @@ -7,7 +7,7 @@ import { RenderOptions } from '../../utils/renderHelpers'; import { removeAnnotations } from '../../utils/treeshakeNode'; -import CallOptions from '../CallOptions'; +import { CallOptions } from '../CallOptions'; import { DeoptimizableEntity } from '../DeoptimizableEntity'; import { HasEffectsContext, InclusionContext } from '../ExecutionContext'; import { diff --git a/src/ast/nodes/MemberExpression.ts b/src/ast/nodes/MemberExpression.ts index 1991ecb82e0..fc292214745 100644 --- a/src/ast/nodes/MemberExpression.ts +++ b/src/ast/nodes/MemberExpression.ts @@ -2,7 +2,7 @@ import MagicString from 'magic-string'; import { BLANK } from '../../utils/blank'; import relativeId from '../../utils/relativeId'; import { NodeRenderOptions, RenderOptions } from '../../utils/renderHelpers'; -import CallOptions from '../CallOptions'; +import { CallOptions } from '../CallOptions'; import { DeoptimizableEntity } from '../DeoptimizableEntity'; import { HasEffectsContext, InclusionContext } from '../ExecutionContext'; import { diff --git a/src/ast/nodes/MethodDefinition.ts b/src/ast/nodes/MethodDefinition.ts index b9e2a8749b4..ec8de25f92e 100644 --- a/src/ast/nodes/MethodDefinition.ts +++ b/src/ast/nodes/MethodDefinition.ts @@ -1,4 +1,4 @@ -import CallOptions from '../CallOptions'; +import { CallOptions } from '../CallOptions'; import { HasEffectsContext } from '../ExecutionContext'; import { EMPTY_PATH, ObjectPath } from '../utils/PathTracker'; import FunctionExpression from './FunctionExpression'; diff --git a/src/ast/nodes/NewExpression.ts b/src/ast/nodes/NewExpression.ts index f7b9d4fa43a..d7acdfd276d 100644 --- a/src/ast/nodes/NewExpression.ts +++ b/src/ast/nodes/NewExpression.ts @@ -1,4 +1,4 @@ -import CallOptions from '../CallOptions'; +import { CallOptions } from '../CallOptions'; import { HasEffectsContext } from '../ExecutionContext'; import { EMPTY_PATH, ObjectPath, UNKNOWN_PATH } from '../utils/PathTracker'; import * as NodeType from './NodeType'; @@ -24,8 +24,11 @@ export default class NewExpression extends NodeBase { for (const argument of this.arguments) { if (argument.hasEffects(context)) return true; } - if (this.annotatedPure) return false; - return this.callee.hasEffectsWhenCalledAtPath(EMPTY_PATH, this.callOptions, context); + if (this.context.annotations && this.annotatedPure) return false; + return ( + this.callee.hasEffects(context) || + this.callee.hasEffectsWhenCalledAtPath(EMPTY_PATH, this.callOptions, context) + ); } hasEffectsWhenAccessedAtPath(path: ObjectPath) { @@ -33,10 +36,9 @@ export default class NewExpression extends NodeBase { } initialise() { - this.callOptions = CallOptions.create({ + this.callOptions = { args: this.arguments, - callIdentifier: this, withNew: true - }); + }; } } diff --git a/src/ast/nodes/ObjectExpression.ts b/src/ast/nodes/ObjectExpression.ts index 7d8fe0a4ef3..0d8fb2b61de 100644 --- a/src/ast/nodes/ObjectExpression.ts +++ b/src/ast/nodes/ObjectExpression.ts @@ -1,7 +1,7 @@ import MagicString from 'magic-string'; import { BLANK } from '../../utils/blank'; import { NodeRenderOptions, RenderOptions } from '../../utils/renderHelpers'; -import CallOptions from '../CallOptions'; +import { CallOptions } from '../CallOptions'; import { DeoptimizableEntity } from '../DeoptimizableEntity'; import { HasEffectsContext } from '../ExecutionContext'; import { diff --git a/src/ast/nodes/Property.ts b/src/ast/nodes/Property.ts index 5d4f688ea45..3cd1d9c4391 100644 --- a/src/ast/nodes/Property.ts +++ b/src/ast/nodes/Property.ts @@ -1,6 +1,6 @@ import MagicString from 'magic-string'; import { RenderOptions } from '../../utils/renderHelpers'; -import CallOptions from '../CallOptions'; +import { CallOptions, NO_ARGS } from '../CallOptions'; import { DeoptimizableEntity } from '../DeoptimizableEntity'; import { HasEffectsContext } from '../ExecutionContext'; import { @@ -82,9 +82,6 @@ export default class Property extends NodeBase implements DeoptimizableEntity { recursionTracker: PathTracker, origin: DeoptimizableEntity ): ExpressionEntity { - if (this.kind === 'set') { - return UNKNOWN_EXPRESSION; - } if (this.kind === 'get') { if (this.returnExpression === null) this.updateReturnExpression(); return (this.returnExpression as ExpressionEntity).getReturnExpressionWhenCalledAtPath( @@ -116,7 +113,6 @@ export default class Property extends NodeBase implements DeoptimizableEntity { hasEffectsWhenAssignedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { if (this.kind === 'get') { - if (path.length === 0) return true; const trackedExpressions = context.assigned.getEntities(path); if (trackedExpressions.has(this)) return false; trackedExpressions.add(this); @@ -126,7 +122,6 @@ export default class Property extends NodeBase implements DeoptimizableEntity { ); } if (this.kind === 'set') { - if (path.length > 0) return true; const trackedExpressions = context.assigned.getEntities(path); if (trackedExpressions.has(this)) return false; trackedExpressions.add(this); @@ -157,10 +152,10 @@ export default class Property extends NodeBase implements DeoptimizableEntity { } initialise() { - this.accessorCallOptions = CallOptions.create({ - callIdentifier: this, + this.accessorCallOptions = { + args: NO_ARGS, withNew: false - }); + }; } render(code: MagicString, options: RenderOptions) { diff --git a/src/ast/nodes/SequenceExpression.ts b/src/ast/nodes/SequenceExpression.ts index 4f4c8aee168..13930c336f7 100644 --- a/src/ast/nodes/SequenceExpression.ts +++ b/src/ast/nodes/SequenceExpression.ts @@ -7,7 +7,7 @@ import { RenderOptions } from '../../utils/renderHelpers'; import { treeshakeNode } from '../../utils/treeshakeNode'; -import CallOptions from '../CallOptions'; +import { CallOptions } from '../CallOptions'; import { DeoptimizableEntity } from '../DeoptimizableEntity'; import { HasEffectsContext, InclusionContext } from '../ExecutionContext'; import { ObjectPath, PathTracker } from '../utils/PathTracker'; diff --git a/src/ast/nodes/TaggedTemplateExpression.ts b/src/ast/nodes/TaggedTemplateExpression.ts index e28414677a1..079b883e955 100644 --- a/src/ast/nodes/TaggedTemplateExpression.ts +++ b/src/ast/nodes/TaggedTemplateExpression.ts @@ -1,4 +1,4 @@ -import CallOptions from '../CallOptions'; +import { CallOptions, NO_ARGS } from '../CallOptions'; import { HasEffectsContext } from '../ExecutionContext'; import { EMPTY_PATH } from '../utils/PathTracker'; import Identifier from './Identifier'; @@ -49,9 +49,9 @@ export default class TaggedTemplateExpression extends NodeBase { } initialise() { - this.callOptions = CallOptions.create({ - callIdentifier: this, + this.callOptions = { + args: NO_ARGS, withNew: false - }); + }; } } diff --git a/src/ast/nodes/shared/ClassNode.ts b/src/ast/nodes/shared/ClassNode.ts index f9ae4865357..5acec68cd4f 100644 --- a/src/ast/nodes/shared/ClassNode.ts +++ b/src/ast/nodes/shared/ClassNode.ts @@ -1,4 +1,4 @@ -import CallOptions from '../../CallOptions'; +import { CallOptions } from '../../CallOptions'; import { HasEffectsContext } from '../../ExecutionContext'; import ChildScope from '../../scopes/ChildScope'; import Scope from '../../scopes/Scope'; @@ -29,6 +29,7 @@ export default class ClassNode extends NodeBase { callOptions: CallOptions, context: HasEffectsContext ) { + if (!callOptions.withNew) return true; return ( this.body.hasEffectsWhenCalledAtPath(path, callOptions, context) || (this.superClass !== null && diff --git a/src/ast/nodes/shared/Expression.ts b/src/ast/nodes/shared/Expression.ts index 7dbea2c98a7..08b03ae8e1a 100644 --- a/src/ast/nodes/shared/Expression.ts +++ b/src/ast/nodes/shared/Expression.ts @@ -1,4 +1,4 @@ -import CallOptions from '../../CallOptions'; +import { CallOptions } from '../../CallOptions'; import { DeoptimizableEntity } from '../../DeoptimizableEntity'; import { WritableEntity } from '../../Entity'; import { HasEffectsContext, InclusionContext } from '../../ExecutionContext'; diff --git a/src/ast/nodes/shared/FunctionNode.ts b/src/ast/nodes/shared/FunctionNode.ts index 4b6d3898a51..e3d7ca49b8e 100644 --- a/src/ast/nodes/shared/FunctionNode.ts +++ b/src/ast/nodes/shared/FunctionNode.ts @@ -1,4 +1,4 @@ -import CallOptions from '../../CallOptions'; +import { CallOptions } from '../../CallOptions'; import { BREAKFLOW_NONE, HasEffectsContext, InclusionContext } from '../../ExecutionContext'; import FunctionScope from '../../scopes/FunctionScope'; import { ObjectPath, UNKNOWN_PATH, UnknownKey } from '../../utils/PathTracker'; diff --git a/src/ast/nodes/shared/MultiExpression.ts b/src/ast/nodes/shared/MultiExpression.ts index d9cd9342fdb..d44eed0aef5 100644 --- a/src/ast/nodes/shared/MultiExpression.ts +++ b/src/ast/nodes/shared/MultiExpression.ts @@ -1,4 +1,4 @@ -import CallOptions from '../../CallOptions'; +import { CallOptions } from '../../CallOptions'; import { DeoptimizableEntity } from '../../DeoptimizableEntity'; import { HasEffectsContext, InclusionContext } from '../../ExecutionContext'; import { ObjectPath, PathTracker } from '../../utils/PathTracker'; diff --git a/src/ast/nodes/shared/Node.ts b/src/ast/nodes/shared/Node.ts index d3680a29a65..6e5842aabd3 100644 --- a/src/ast/nodes/shared/Node.ts +++ b/src/ast/nodes/shared/Node.ts @@ -2,7 +2,7 @@ import { locate } from 'locate-character'; import MagicString from 'magic-string'; import { AstContext, CommentDescription } from '../../../Module'; import { NodeRenderOptions, RenderOptions } from '../../../utils/renderHelpers'; -import CallOptions from '../../CallOptions'; +import { CallOptions } from '../../CallOptions'; import { DeoptimizableEntity } from '../../DeoptimizableEntity'; import { Entity } from '../../Entity'; import { diff --git a/src/ast/values.ts b/src/ast/values.ts index 6d86bc58bf6..df024efe881 100644 --- a/src/ast/values.ts +++ b/src/ast/values.ts @@ -1,4 +1,4 @@ -import CallOptions from './CallOptions'; +import { CallOptions, NO_ARGS } from './CallOptions'; import { HasEffectsContext, InclusionContext } from './ExecutionContext'; import { LiteralValue } from './nodes/Literal'; import { ExpressionEntity } from './nodes/shared/Expression'; @@ -470,11 +470,10 @@ export function hasMemberEffectWhenCalled( callOptions.args[argIndex] && callOptions.args[argIndex].hasEffectsWhenCalledAtPath( EMPTY_PATH, - CallOptions.create({ - args: [], - callIdentifier: {}, // make sure the caller is unique to avoid this check being ignored, + { + args: NO_ARGS, withNew: false - }), + }, context ) ) diff --git a/src/ast/variables/LocalVariable.ts b/src/ast/variables/LocalVariable.ts index 1fe9e980391..cd4cbd681e6 100644 --- a/src/ast/variables/LocalVariable.ts +++ b/src/ast/variables/LocalVariable.ts @@ -1,6 +1,6 @@ import Module, { AstContext } from '../../Module'; import { markModuleAndImpureDependenciesAsExecuted } from '../../utils/traverseStaticDependencies'; -import CallOptions from '../CallOptions'; +import { CallOptions } from '../CallOptions'; import { DeoptimizableEntity } from '../DeoptimizableEntity'; import { HasEffectsContext, InclusionContext } from '../ExecutionContext'; import ExportDefaultDeclaration from '../nodes/ExportDefaultDeclaration'; diff --git a/src/ast/variables/ThisVariable.ts b/src/ast/variables/ThisVariable.ts index 144b6baa75a..f9ec5a0d4eb 100644 --- a/src/ast/variables/ThisVariable.ts +++ b/src/ast/variables/ThisVariable.ts @@ -1,5 +1,5 @@ import { AstContext } from '../../Module'; -import CallOptions from '../CallOptions'; +import { CallOptions } from '../CallOptions'; import { HasEffectsContext } from '../ExecutionContext'; import { ExpressionEntity } from '../nodes/shared/Expression'; import { ObjectPath } from '../utils/PathTracker'; diff --git a/src/ast/variables/Variable.ts b/src/ast/variables/Variable.ts index b9a6777b0e2..323059f48f3 100644 --- a/src/ast/variables/Variable.ts +++ b/src/ast/variables/Variable.ts @@ -1,6 +1,6 @@ import ExternalModule from '../../ExternalModule'; import Module from '../../Module'; -import CallOptions from '../CallOptions'; +import { CallOptions } from '../CallOptions'; import { DeoptimizableEntity } from '../DeoptimizableEntity'; import { HasEffectsContext, InclusionContext } from '../ExecutionContext'; import Identifier from '../nodes/Identifier'; diff --git a/test/form/samples/class-without-new/_config.js b/test/form/samples/class-without-new/_config.js new file mode 100644 index 00000000000..db77abbdd9e --- /dev/null +++ b/test/form/samples/class-without-new/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'instantiating a class without "new" is a side-effect' +}; diff --git a/test/form/samples/class-without-new/_expected.js b/test/form/samples/class-without-new/_expected.js new file mode 100644 index 00000000000..d895adf5b4f --- /dev/null +++ b/test/form/samples/class-without-new/_expected.js @@ -0,0 +1,3 @@ +class foo {} + +foo(); diff --git a/test/form/samples/class-without-new/main.js b/test/form/samples/class-without-new/main.js new file mode 100644 index 00000000000..d895adf5b4f --- /dev/null +++ b/test/form/samples/class-without-new/main.js @@ -0,0 +1,3 @@ +class foo {} + +foo(); diff --git a/test/form/samples/property-setters-and-getters/access-when-called-effect/_config.js b/test/form/samples/property-setters-and-getters/access-when-called-effect/_config.js new file mode 100644 index 00000000000..ae85f1ce6c6 --- /dev/null +++ b/test/form/samples/property-setters-and-getters/access-when-called-effect/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'respects access side-effects when calling a getter' +}; diff --git a/test/form/samples/property-setters-and-getters/access-when-called-effect/_expected.js b/test/form/samples/property-setters-and-getters/access-when-called-effect/_expected.js new file mode 100644 index 00000000000..90418f180ce --- /dev/null +++ b/test/form/samples/property-setters-and-getters/access-when-called-effect/_expected.js @@ -0,0 +1,39 @@ +const called1 = { + get value() { + console.log('retained'); + return function() {}; + } +}; + +called1.value(); + +const instantiated1 = { + get value() { + console.log('retained'); + return class {}; + } +}; + +new instantiated1.value(); + +const called2 = { + get value() { + return function() { + console.log('retained'); + }; + } +}; + +called2.value(); + +const instantiated2 = { + get value() { + return class { + constructor() { + console.log('retained'); + } + }; + } +}; + +new instantiated2.value(); diff --git a/test/form/samples/property-setters-and-getters/access-when-called-effect/main.js b/test/form/samples/property-setters-and-getters/access-when-called-effect/main.js new file mode 100644 index 00000000000..90418f180ce --- /dev/null +++ b/test/form/samples/property-setters-and-getters/access-when-called-effect/main.js @@ -0,0 +1,39 @@ +const called1 = { + get value() { + console.log('retained'); + return function() {}; + } +}; + +called1.value(); + +const instantiated1 = { + get value() { + console.log('retained'); + return class {}; + } +}; + +new instantiated1.value(); + +const called2 = { + get value() { + return function() { + console.log('retained'); + }; + } +}; + +called2.value(); + +const instantiated2 = { + get value() { + return class { + constructor() { + console.log('retained'); + } + }; + } +}; + +new instantiated2.value(); diff --git a/test/form/samples/property-setters-and-getters/early-access-getter-return/_config.js b/test/form/samples/property-setters-and-getters/early-access-getter-return/_config.js new file mode 100644 index 00000000000..bbfa47e1932 --- /dev/null +++ b/test/form/samples/property-setters-and-getters/early-access-getter-return/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'handles accessing the return expression of a getter before it has been bound' +}; diff --git a/test/form/samples/property-setters-and-getters/early-access-getter-return/_expected.js b/test/form/samples/property-setters-and-getters/early-access-getter-return/_expected.js new file mode 100644 index 00000000000..b57b090e68c --- /dev/null +++ b/test/form/samples/property-setters-and-getters/early-access-getter-return/_expected.js @@ -0,0 +1,7 @@ +function getReturnExpressionBeforeInit() { + { + console.log('retained'); + } +} + +getReturnExpressionBeforeInit(); diff --git a/test/form/samples/property-setters-and-getters/early-access-getter-return/main.js b/test/form/samples/property-setters-and-getters/early-access-getter-return/main.js new file mode 100644 index 00000000000..745a1206e6e --- /dev/null +++ b/test/form/samples/property-setters-and-getters/early-access-getter-return/main.js @@ -0,0 +1,16 @@ +function getReturnExpressionBeforeInit() { + const bar = { + [foo.value()]: true + }; + if (bar.baz) { + console.log('retained'); + } +} + +const foo = { + get value() { + return () => 'baz'; + } +}; + +getReturnExpressionBeforeInit(); diff --git a/test/form/samples/property-setters-and-getters/early-access-getter-value/_config.js b/test/form/samples/property-setters-and-getters/early-access-getter-value/_config.js new file mode 100644 index 00000000000..e362145ba90 --- /dev/null +++ b/test/form/samples/property-setters-and-getters/early-access-getter-value/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'handles accessing the value of a getter before it has been bound' +}; diff --git a/test/form/samples/property-setters-and-getters/early-access-getter-value/_expected.js b/test/form/samples/property-setters-and-getters/early-access-getter-value/_expected.js new file mode 100644 index 00000000000..ab4f0e6ebc3 --- /dev/null +++ b/test/form/samples/property-setters-and-getters/early-access-getter-value/_expected.js @@ -0,0 +1,7 @@ +function getLiteralValueBeforeInit() { + { + console.log('retained'); + } +} + +getLiteralValueBeforeInit(); diff --git a/test/form/samples/property-setters-and-getters/early-access-getter-value/main.js b/test/form/samples/property-setters-and-getters/early-access-getter-value/main.js new file mode 100644 index 00000000000..9e1ba279dd0 --- /dev/null +++ b/test/form/samples/property-setters-and-getters/early-access-getter-value/main.js @@ -0,0 +1,16 @@ +function getLiteralValueBeforeInit() { + const bar = { + [foo.value]: true + }; + if (bar.baz) { + console.log('retained'); + } +} + +const foo = { + get value() { + return 'baz'; + } +}; + +getLiteralValueBeforeInit(); diff --git a/test/form/samples/pure-comments-disabled/_expected.js b/test/form/samples/pure-comments-disabled/_expected.js index 069d154290f..dae6dbd4fa4 100644 --- a/test/form/samples/pure-comments-disabled/_expected.js +++ b/test/form/samples/pure-comments-disabled/_expected.js @@ -1,5 +1,6 @@ // should be retained /*@__PURE__*/ a(); +/*@__PURE__*/ new a(); console.log('code'); console.log('should remain impure'); diff --git a/test/form/samples/pure-comments-disabled/main.js b/test/form/samples/pure-comments-disabled/main.js index b3c947ad6dd..bafd2b7cba8 100644 --- a/test/form/samples/pure-comments-disabled/main.js +++ b/test/form/samples/pure-comments-disabled/main.js @@ -1,5 +1,6 @@ // should be retained /*@__PURE__*/ a(); +/*@__PURE__*/ new a(); console.log('code')/*@__PURE__*/; /*@__PURE__*/(() => {})(); From caaa3d47ddd3696023684f9fdbae18557b7fed8e Mon Sep 17 00:00:00 2001 From: Lukas Taegert-Atkinson Date: Mon, 14 Oct 2019 07:33:26 +0200 Subject: [PATCH 33/35] Fix old Node syntax error --- test/function/samples/catch-scope-variables/main.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/function/samples/catch-scope-variables/main.js b/test/function/samples/catch-scope-variables/main.js index bcb85c6a2c0..ea3a0a6aaf5 100644 --- a/test/function/samples/catch-scope-variables/main.js +++ b/test/function/samples/catch-scope-variables/main.js @@ -3,7 +3,7 @@ let outsideLet = 'outside'; try { throw new Error(); -} catch { +} catch (e) { var outsideVar = 'inside'; let outsideLet = 'inside'; var insideVar = 'inside'; From 59b8673df3204e304c317f57d0280c1df804db0f Mon Sep 17 00:00:00 2001 From: Lukas Taegert-Atkinson Date: Mon, 14 Oct 2019 08:52:12 +0200 Subject: [PATCH 34/35] Improve coverage --- src/ast/nodes/Property.ts | 5 +---- src/ast/nodes/shared/MultiExpression.ts | 10 ++-------- test/form/samples/multi-expression-calls/_config.js | 3 +++ test/form/samples/multi-expression-calls/_expected.js | 1 + test/form/samples/multi-expression-calls/main.js | 10 ++++++++++ 5 files changed, 17 insertions(+), 12 deletions(-) create mode 100644 test/form/samples/multi-expression-calls/_config.js create mode 100644 test/form/samples/multi-expression-calls/_expected.js create mode 100644 test/form/samples/multi-expression-calls/main.js diff --git a/src/ast/nodes/Property.ts b/src/ast/nodes/Property.ts index 3cd1d9c4391..290ae0da0de 100644 --- a/src/ast/nodes/Property.ts +++ b/src/ast/nodes/Property.ts @@ -10,7 +10,7 @@ import { PathTracker, UnknownKey } from '../utils/PathTracker'; -import { LiteralValueOrUnknown, UNKNOWN_EXPRESSION, UnknownValue } from '../values'; +import { LiteralValueOrUnknown, UNKNOWN_EXPRESSION } from '../values'; import * as NodeType from './NodeType'; import { ExpressionEntity } from './shared/Expression'; import { ExpressionNode, NodeBase } from './shared/Node'; @@ -63,9 +63,6 @@ export default class Property extends NodeBase implements DeoptimizableEntity { recursionTracker: PathTracker, origin: DeoptimizableEntity ): LiteralValueOrUnknown { - if (this.kind === 'set') { - return UnknownValue; - } if (this.kind === 'get') { if (this.returnExpression === null) this.updateReturnExpression(); return (this.returnExpression as ExpressionEntity).getLiteralValueAtPath( diff --git a/src/ast/nodes/shared/MultiExpression.ts b/src/ast/nodes/shared/MultiExpression.ts index d44eed0aef5..89abd4abb71 100644 --- a/src/ast/nodes/shared/MultiExpression.ts +++ b/src/ast/nodes/shared/MultiExpression.ts @@ -1,11 +1,9 @@ import { CallOptions } from '../../CallOptions'; import { DeoptimizableEntity } from '../../DeoptimizableEntity'; -import { HasEffectsContext, InclusionContext } from '../../ExecutionContext'; +import { HasEffectsContext } from '../../ExecutionContext'; import { ObjectPath, PathTracker } from '../../utils/PathTracker'; import { LiteralValueOrUnknown, UnknownValue } from '../../values'; -import SpreadElement from '../SpreadElement'; import { ExpressionEntity } from './Expression'; -import { ExpressionNode } from './Node'; export class MultiExpression implements ExpressionEntity { included = false; @@ -65,9 +63,5 @@ export class MultiExpression implements ExpressionEntity { include(): void {} - includeCallArguments(context: InclusionContext, args: (ExpressionNode | SpreadElement)[]): void { - for (const expression of this.expressions) { - expression.includeCallArguments(context, args); - } - } + includeCallArguments(): void {} } diff --git a/test/form/samples/multi-expression-calls/_config.js b/test/form/samples/multi-expression-calls/_config.js new file mode 100644 index 00000000000..5ba44790f77 --- /dev/null +++ b/test/form/samples/multi-expression-calls/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'handles side-effect-free multi-expressions' +}; diff --git a/test/form/samples/multi-expression-calls/_expected.js b/test/form/samples/multi-expression-calls/_expected.js new file mode 100644 index 00000000000..c7253d38a1d --- /dev/null +++ b/test/form/samples/multi-expression-calls/_expected.js @@ -0,0 +1 @@ +console.log('retained'); diff --git a/test/form/samples/multi-expression-calls/main.js b/test/form/samples/multi-expression-calls/main.js new file mode 100644 index 00000000000..f2bdd82a57e --- /dev/null +++ b/test/form/samples/multi-expression-calls/main.js @@ -0,0 +1,10 @@ +function noEffect1(x, y) { + return () => x; +} + +function noEffect2() { + return () => {}; +} + +(globalThis.unknown ? noEffect1 : noEffect2)()(); +console.log('retained'); From 8949aaa5d29bc006f046ef8e5a28d972615d008e Mon Sep 17 00:00:00 2001 From: Lukas Taegert-Atkinson Date: Tue, 15 Oct 2019 06:45:58 +0200 Subject: [PATCH 35/35] Improve coverage --- src/ast/nodes/ForStatement.ts | 2 +- src/ast/nodes/IfStatement.ts | 7 +---- src/ast/scopes/CatchScope.ts | 2 +- src/ast/values.ts | 2 ++ .../samples/binary-expressions/_config.js | 3 ++ .../samples/binary-expressions/_expected.js | 25 +++++++++++++++++ test/form/samples/binary-expressions/main.js | 28 +++++++++++++++++++ .../break-statement-labels/_expected.js | 20 ++++++++++--- .../break-statement-labels/main.js | 21 +++++++++++--- .../array-expression/_config.js | 3 +- .../array-expression/_expected.js | 2 ++ .../array-expression/main.js | 2 ++ .../_expected.js | 11 ++++++++ .../_expected/amd.js | 11 -------- .../_expected/cjs.js | 9 ------ .../_expected/es.js | 7 ----- .../_expected/iife.js | 12 -------- .../_expected/system.js | 16 ----------- .../_expected/umd.js | 14 ---------- .../class-constructor-side-effect/main.js | 15 ++++++++-- .../_expected.js | 4 +++ .../side-effects-switch-statements/main.js | 4 +++ .../form/samples/unary-expressions/_config.js | 3 ++ .../samples/unary-expressions/_expected.js | 13 +++++++++ test/form/samples/unary-expressions/main.js | 13 +++++++++ test/form/samples/undefined-var/_expected.js | 14 ++++++---- test/form/samples/undefined-var/main.js | 22 +++++++-------- 27 files changed, 178 insertions(+), 107 deletions(-) create mode 100644 test/form/samples/binary-expressions/_config.js create mode 100644 test/form/samples/binary-expressions/_expected.js create mode 100644 test/form/samples/binary-expressions/main.js create mode 100644 test/form/samples/class-constructor-side-effect/_expected.js delete mode 100644 test/form/samples/class-constructor-side-effect/_expected/amd.js delete mode 100644 test/form/samples/class-constructor-side-effect/_expected/cjs.js delete mode 100644 test/form/samples/class-constructor-side-effect/_expected/es.js delete mode 100644 test/form/samples/class-constructor-side-effect/_expected/iife.js delete mode 100644 test/form/samples/class-constructor-side-effect/_expected/system.js delete mode 100644 test/form/samples/class-constructor-side-effect/_expected/umd.js create mode 100644 test/form/samples/unary-expressions/_config.js create mode 100644 test/form/samples/unary-expressions/_expected.js create mode 100644 test/form/samples/unary-expressions/main.js diff --git a/src/ast/nodes/ForStatement.ts b/src/ast/nodes/ForStatement.ts index 6def8e46003..f07001e3716 100644 --- a/src/ast/nodes/ForStatement.ts +++ b/src/ast/nodes/ForStatement.ts @@ -42,7 +42,7 @@ export default class ForStatement extends StatementBase { if (this.test) this.test.include(context, includeChildrenRecursively); const { breakFlow } = context; if (this.update) this.update.include(context, includeChildrenRecursively); - if (this.body) this.body.include(context, includeChildrenRecursively); + this.body.include(context, includeChildrenRecursively); context.breakFlow = breakFlow; } diff --git a/src/ast/nodes/IfStatement.ts b/src/ast/nodes/IfStatement.ts index f9e9b0daa2f..73b6bb944f6 100644 --- a/src/ast/nodes/IfStatement.ts +++ b/src/ast/nodes/IfStatement.ts @@ -19,16 +19,11 @@ export default class IfStatement extends StatementBase implements DeoptimizableE test!: ExpressionNode; type!: NodeType.tIfStatement; - private isTestValueAnalysed = false; private testValue: LiteralValueOrUnknown; bind() { super.bind(); - if (!this.isTestValueAnalysed) { - this.testValue = UnknownValue; - this.isTestValueAnalysed = true; - this.testValue = this.test.getLiteralValueAtPath(EMPTY_PATH, EMPTY_IMMUTABLE_TRACKER, this); - } + this.testValue = this.test.getLiteralValueAtPath(EMPTY_PATH, EMPTY_IMMUTABLE_TRACKER, this); } deoptimizeCache() { diff --git a/src/ast/scopes/CatchScope.ts b/src/ast/scopes/CatchScope.ts index 6bbaa797632..08e977006d8 100644 --- a/src/ast/scopes/CatchScope.ts +++ b/src/ast/scopes/CatchScope.ts @@ -8,7 +8,7 @@ export default class CatchScope extends ParameterScope { addDeclaration( identifier: Identifier, context: AstContext, - init: ExpressionEntity | null = null, + init: ExpressionEntity | null, isHoisted: boolean | 'function' ): LocalVariable { if (isHoisted) { diff --git a/src/ast/values.ts b/src/ast/values.ts index df024efe881..3f563c5928d 100644 --- a/src/ast/values.ts +++ b/src/ast/values.ts @@ -47,6 +47,7 @@ export const UNKNOWN_EXPRESSION: ExpressionEntity = { included: true, toString: () => '[[UNKNOWN]]' }; + export const UNDEFINED_EXPRESSION: ExpressionEntity = { deoptimizePath: () => {}, getLiteralValueAtPath: () => undefined, @@ -59,6 +60,7 @@ export const UNDEFINED_EXPRESSION: ExpressionEntity = { included: true, toString: () => 'undefined' }; + const returnsUnknown: RawMemberDescription = { value: { callsArgs: null, diff --git a/test/form/samples/binary-expressions/_config.js b/test/form/samples/binary-expressions/_config.js new file mode 100644 index 00000000000..1c861eec011 --- /dev/null +++ b/test/form/samples/binary-expressions/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'handles binary expression side-effects' +}; diff --git a/test/form/samples/binary-expressions/_expected.js b/test/form/samples/binary-expressions/_expected.js new file mode 100644 index 00000000000..5ff02006ea4 --- /dev/null +++ b/test/form/samples/binary-expressions/_expected.js @@ -0,0 +1,25 @@ +if ((1 + 1).unknown) { + console.log('retained'); +} else { + console.log('retained'); +} +console.log('retained 1'); +console.log('retained 2'); +console.log('retained 3'); +console.log('retained 4'); +console.log('retained 5'); +console.log('retained 6'); +console.log('retained 7'); +console.log('retained 8'); +console.log('retained 9'); +console.log('retained 10'); +console.log('retained 11'); +console.log('retained 12'); +console.log('retained 13'); +console.log('retained 14'); +console.log('retained 15'); +console.log('retained 16'); +console.log('retained 17'); +if (1 in 2) console.log('retained 18'); +if (1 instanceof 2) console.log('retained 19'); +console.log('retained 20'); diff --git a/test/form/samples/binary-expressions/main.js b/test/form/samples/binary-expressions/main.js new file mode 100644 index 00000000000..0263ffe9303 --- /dev/null +++ b/test/form/samples/binary-expressions/main.js @@ -0,0 +1,28 @@ +if ((1 + 1).unknown) { + console.log('retained'); +} else { + console.log('retained'); +} + +if (1 != '1') console.log('removed'); +if (1 !== 1) console.log('removed'); +if (4 % 2 === 0) console.log('retained 1'); +if ((6 & 3) === 2) console.log('retained 2'); +if (2 * 3 === 6) console.log('retained 3'); +if (2 ** 3 === 8) console.log('retained 4'); +if (2 + 3 === 5) console.log('retained 5'); +if (3 - 2 === 1) console.log('retained 6'); +if (6 / 3 === 2) console.log('retained 7'); +if (1 < 2 ) console.log('retained 8'); +if (3 << 1 === 6) console.log('retained 9'); +if (3 <= 4) console.log('retained 10'); +if (1 == '1') console.log('retained 11'); +if (1 === 1) console.log('retained 12'); +if (3 > 2) console.log('retained 13'); +if (3 >= 2) console.log('retained 14'); +if (6 >> 1 === 3) console.log('retained 15'); +if (-1 >>> 28 === 15) console.log('retained 16'); +if (3 ^ 5 === 6) console.log('retained 17'); +if (1 in 2) console.log('retained 18'); +if (1 instanceof 2) console.log('retained 19'); +if (2 | 4 === 6) console.log('retained 20'); diff --git a/test/form/samples/break-control-flow/break-statement-labels/_expected.js b/test/form/samples/break-control-flow/break-statement-labels/_expected.js index 81784a01622..d7080a80bb6 100644 --- a/test/form/samples/break-control-flow/break-statement-labels/_expected.js +++ b/test/form/samples/break-control-flow/break-statement-labels/_expected.js @@ -31,11 +31,11 @@ outer: { console.log('retained'); } -function withReturn() { +function withConsequentReturn() { outer: { inner: { - if (globalThis.unknown) break inner; - else return; + if (globalThis.unknown) return; + else break inner; } console.log('retained'); } @@ -46,4 +46,16 @@ function withReturn() { } } -withReturn(); +withConsequentReturn(); + +function withAlternateReturn() { + outer: { + inner: { + if (globalThis.unknown) break inner; + else return; + } + console.log('retained'); + } +} + +withAlternateReturn(); diff --git a/test/form/samples/break-control-flow/break-statement-labels/main.js b/test/form/samples/break-control-flow/break-statement-labels/main.js index 336ae3797ee..b7ac267b7f1 100644 --- a/test/form/samples/break-control-flow/break-statement-labels/main.js +++ b/test/form/samples/break-control-flow/break-statement-labels/main.js @@ -48,11 +48,11 @@ outer: { console.log('retained'); } -function withReturn() { +function withConsequentReturn() { outer: { inner: { - if (globalThis.unknown) break inner; - else return; + if (globalThis.unknown) return; + else break inner; console.log('removed'); } console.log('retained'); @@ -67,4 +67,17 @@ function withReturn() { } } -withReturn(); +withConsequentReturn(); + +function withAlternateReturn() { + outer: { + inner: { + if (globalThis.unknown) break inner; + else return; + console.log('removed'); + } + console.log('retained'); + } +} + +withAlternateReturn(); diff --git a/test/form/samples/builtin-prototypes/array-expression/_config.js b/test/form/samples/builtin-prototypes/array-expression/_config.js index 90f3b19cd39..c18d1c7f176 100644 --- a/test/form/samples/builtin-prototypes/array-expression/_config.js +++ b/test/form/samples/builtin-prototypes/array-expression/_config.js @@ -1,4 +1,3 @@ module.exports = { - description: 'Tree-shake known array prototype functions', - options: { output: { name: 'bundle' } } + description: 'Tree-shake known array prototype functions' }; diff --git a/test/form/samples/builtin-prototypes/array-expression/_expected.js b/test/form/samples/builtin-prototypes/array-expression/_expected.js index effa27f383d..7318911baaa 100644 --- a/test/form/samples/builtin-prototypes/array-expression/_expected.js +++ b/test/form/samples/builtin-prototypes/array-expression/_expected.js @@ -3,6 +3,8 @@ const map4 = [ 1 ].map( x => x ).map( x => console.log( 1 ) ); const map5 = [ 1 ].map( x => console.log( 1 ) ).map( x => x ); const map7 = [ 1 ].map( x => x ).map( x => x ).map( x => console.log( 1 ) ); const map8 = [ 1 ].map( x => x ).map( x => console.log( 1 ) ).map( x => x ); + +[](); const _everyEffect = [ 1 ].every( () => console.log( 1 ) || true ); const _filterEffect = [ 1 ].filter( () => console.log( 1 ) || true ); const _findEffect = [ 1 ].find( () => console.log( 1 ) || true ); diff --git a/test/form/samples/builtin-prototypes/array-expression/main.js b/test/form/samples/builtin-prototypes/array-expression/main.js index 7fa15d0881d..a04bdfa57e3 100644 --- a/test/form/samples/builtin-prototypes/array-expression/main.js +++ b/test/form/samples/builtin-prototypes/array-expression/main.js @@ -12,6 +12,8 @@ const map6 = [ 1 ].map( x => x ).map( x => x ).map( x => x ); const map7 = [ 1 ].map( x => x ).map( x => x ).map( x => console.log( 1 ) ); const map8 = [ 1 ].map( x => x ).map( x => console.log( 1 ) ).map( x => x ); +[](); + // accessor methods const _includes = [].includes( 1 ).valueOf(); const _indexOf = [].indexOf( 1 ).toPrecision( 1 ); diff --git a/test/form/samples/class-constructor-side-effect/_expected.js b/test/form/samples/class-constructor-side-effect/_expected.js new file mode 100644 index 00000000000..469da25d52a --- /dev/null +++ b/test/form/samples/class-constructor-side-effect/_expected.js @@ -0,0 +1,11 @@ +class Effect { + constructor () { + console.log( 'Foo' ); + } +} + +new Effect(); + +class Empty {} + +new Empty.doesNotExist(); diff --git a/test/form/samples/class-constructor-side-effect/_expected/amd.js b/test/form/samples/class-constructor-side-effect/_expected/amd.js deleted file mode 100644 index 1b939aa8b03..00000000000 --- a/test/form/samples/class-constructor-side-effect/_expected/amd.js +++ /dev/null @@ -1,11 +0,0 @@ -define(function () { 'use strict'; - - class Foo { - constructor () { - console.log( 'Foo' ); - } - } - - new Foo; - -}); diff --git a/test/form/samples/class-constructor-side-effect/_expected/cjs.js b/test/form/samples/class-constructor-side-effect/_expected/cjs.js deleted file mode 100644 index ad3db871b73..00000000000 --- a/test/form/samples/class-constructor-side-effect/_expected/cjs.js +++ /dev/null @@ -1,9 +0,0 @@ -'use strict'; - -class Foo { - constructor () { - console.log( 'Foo' ); - } -} - -new Foo; diff --git a/test/form/samples/class-constructor-side-effect/_expected/es.js b/test/form/samples/class-constructor-side-effect/_expected/es.js deleted file mode 100644 index 705e3506882..00000000000 --- a/test/form/samples/class-constructor-side-effect/_expected/es.js +++ /dev/null @@ -1,7 +0,0 @@ -class Foo { - constructor () { - console.log( 'Foo' ); - } -} - -new Foo; diff --git a/test/form/samples/class-constructor-side-effect/_expected/iife.js b/test/form/samples/class-constructor-side-effect/_expected/iife.js deleted file mode 100644 index a047aa4e0d1..00000000000 --- a/test/form/samples/class-constructor-side-effect/_expected/iife.js +++ /dev/null @@ -1,12 +0,0 @@ -(function () { - 'use strict'; - - class Foo { - constructor () { - console.log( 'Foo' ); - } - } - - new Foo; - -}()); diff --git a/test/form/samples/class-constructor-side-effect/_expected/system.js b/test/form/samples/class-constructor-side-effect/_expected/system.js deleted file mode 100644 index c50f93205ef..00000000000 --- a/test/form/samples/class-constructor-side-effect/_expected/system.js +++ /dev/null @@ -1,16 +0,0 @@ -System.register([], function () { - 'use strict'; - return { - execute: function () { - - class Foo { - constructor () { - console.log( 'Foo' ); - } - } - - new Foo; - - } - }; -}); diff --git a/test/form/samples/class-constructor-side-effect/_expected/umd.js b/test/form/samples/class-constructor-side-effect/_expected/umd.js deleted file mode 100644 index 82f801be7d2..00000000000 --- a/test/form/samples/class-constructor-side-effect/_expected/umd.js +++ /dev/null @@ -1,14 +0,0 @@ -(function (factory) { - typeof define === 'function' && define.amd ? define(factory) : - factory(); -}(function () { 'use strict'; - - class Foo { - constructor () { - console.log( 'Foo' ); - } - } - - new Foo; - -})); diff --git a/test/form/samples/class-constructor-side-effect/main.js b/test/form/samples/class-constructor-side-effect/main.js index 705e3506882..2704996853a 100644 --- a/test/form/samples/class-constructor-side-effect/main.js +++ b/test/form/samples/class-constructor-side-effect/main.js @@ -1,7 +1,18 @@ -class Foo { +class Effect { constructor () { console.log( 'Foo' ); } } -new Foo; +new Effect(); + +class NoEffect { + constructor () { + } +} + +new NoEffect(); + +class Empty {} + +new Empty.doesNotExist(); diff --git a/test/form/samples/side-effects-switch-statements/_expected.js b/test/form/samples/side-effects-switch-statements/_expected.js index 1637398c2b5..6b541d9096e 100644 --- a/test/form/samples/side-effects-switch-statements/_expected.js +++ b/test/form/samples/side-effects-switch-statements/_expected.js @@ -30,3 +30,7 @@ switch ( globalThis.unknown ) { effect(); } }()); + +switch ( globalThis.unknown ) { + case effect(): +} diff --git a/test/form/samples/side-effects-switch-statements/main.js b/test/form/samples/side-effects-switch-statements/main.js index 306c82ecf5c..14763c4bd5b 100644 --- a/test/form/samples/side-effects-switch-statements/main.js +++ b/test/form/samples/side-effects-switch-statements/main.js @@ -44,3 +44,7 @@ switch ( globalThis.unknown ) { default: } }()); + +switch ( globalThis.unknown ) { + case effect(): +} diff --git a/test/form/samples/unary-expressions/_config.js b/test/form/samples/unary-expressions/_config.js new file mode 100644 index 00000000000..9e363ae5176 --- /dev/null +++ b/test/form/samples/unary-expressions/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'handles unary expression side-effects' +}; diff --git a/test/form/samples/unary-expressions/_expected.js b/test/form/samples/unary-expressions/_expected.js new file mode 100644 index 00000000000..d51c6bfe41b --- /dev/null +++ b/test/form/samples/unary-expressions/_expected.js @@ -0,0 +1,13 @@ +if ((!true).unknown) { + console.log('retained'); +} else { + console.log('retained'); +} + +console.log('retained 1'); +console.log('retained 2'); +console.log('retained 3'); +if (delete 1) console.log('retained 4'); +console.log('retained 5'); +console.log('retained 6'); +console.log('retained 7'); diff --git a/test/form/samples/unary-expressions/main.js b/test/form/samples/unary-expressions/main.js new file mode 100644 index 00000000000..c22c4ebd151 --- /dev/null +++ b/test/form/samples/unary-expressions/main.js @@ -0,0 +1,13 @@ +if ((!true).unknown) { + console.log('retained'); +} else { + console.log('retained'); +} + +if (!false) console.log('retained 1'); +if (+'1' === 1) console.log('retained 2'); +if (-1 + 2 === 1) console.log('retained 3'); +if (delete 1) console.log('retained 4'); +if (typeof 1 === 'number') console.log('retained 5'); +if (void 1 === undefined) console.log('retained 6'); +if (~1 === -2) console.log('retained 7'); diff --git a/test/form/samples/undefined-var/_expected.js b/test/form/samples/undefined-var/_expected.js index e10c01c8cef..710306c2a45 100644 --- a/test/form/samples/undefined-var/_expected.js +++ b/test/form/samples/undefined-var/_expected.js @@ -1,8 +1,10 @@ var z; -console.log('no'); -console.log('no'); -if (z) - console.log('yes'); -if (!z) - console.log('no'); + +console.log('retained'); + +console.log('retained'); + +if (z) console.log('retained'); +else console.log('retained'); + z = 1; diff --git a/test/form/samples/undefined-var/main.js b/test/form/samples/undefined-var/main.js index d6763907d01..7e219955ebe 100644 --- a/test/form/samples/undefined-var/main.js +++ b/test/form/samples/undefined-var/main.js @@ -1,16 +1,14 @@ var x; var y = undefined; var z; -if (x) - console.log('yes'); -if (!x) - console.log('no'); -if (y) - console.log('yes'); -if (!y) - console.log('no'); -if (z) - console.log('yes'); -if (!z) - console.log('no'); + +if (x) console.log('removed'); +else console.log('retained'); + +if (y) console.log('removed'); +else console.log('retained'); + +if (z) console.log('retained'); +else console.log('retained'); + z = 1;