diff --git a/rollup.config.ts b/rollup.config.ts index 77c5cac5bec..4bf5ee3c383 100644 --- a/rollup.config.ts +++ b/rollup.config.ts @@ -6,7 +6,7 @@ import commonjs from '@rollup/plugin-commonjs'; import json from '@rollup/plugin-json'; import { nodeResolve } from '@rollup/plugin-node-resolve'; import typescript from '@rollup/plugin-typescript'; -import type { Plugin, RollupOptions, WarningHandlerWithDefault } from 'rollup'; +import type { RollupOptions, WarningHandlerWithDefault } from 'rollup'; import { string } from 'rollup-plugin-string'; import { terser } from 'rollup-plugin-terser'; import addCliEntry from './build-plugins/add-cli-entry'; @@ -65,7 +65,7 @@ const treeshake = { tryCatchDeoptimization: false }; -const nodePlugins: Plugin[] = [ +const nodePlugins = [ alias(moduleAliases), nodeResolve(), json(), diff --git a/src/ast/nodes/ArrayExpression.ts b/src/ast/nodes/ArrayExpression.ts index 1a7994f8827..406687d99fb 100644 --- a/src/ast/nodes/ArrayExpression.ts +++ b/src/ast/nodes/ArrayExpression.ts @@ -1,14 +1,8 @@ import type { CallOptions } from '../CallOptions'; import type { DeoptimizableEntity } from '../DeoptimizableEntity'; import type { HasEffectsContext } from '../ExecutionContext'; -import { InclusionContext } from '../ExecutionContext'; import type { NodeEvent } from '../NodeEvents'; -import { - type ObjectPath, - type PathTracker, - UNKNOWN_PATH, - UnknownInteger -} from '../utils/PathTracker'; +import { type ObjectPath, type PathTracker, UnknownInteger } from '../utils/PathTracker'; import { UNDEFINED_EXPRESSION, UNKNOWN_LITERAL_NUMBER } from '../values'; import type * as NodeType from './NodeType'; import SpreadElement from './SpreadElement'; @@ -20,7 +14,6 @@ import { ObjectEntity, type ObjectProperty } from './shared/ObjectEntity'; export default class ArrayExpression extends NodeBase { declare elements: readonly (ExpressionNode | SpreadElement | null)[]; declare type: NodeType.tArrayExpression; - protected deoptimized = false; private objectEntity: ObjectEntity | null = null; deoptimizePath(path: ObjectPath): void { @@ -63,7 +56,7 @@ export default class ArrayExpression extends NodeBase { ); } - hasEffectsWhenAccessedAtPath(path: ObjectPath, context: HasEffectsContext): boolean | undefined { + hasEffectsWhenAccessedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { return this.getObjectEntity().hasEffectsWhenAccessedAtPath(path, context); } @@ -79,29 +72,6 @@ export default class ArrayExpression extends NodeBase { return this.getObjectEntity().hasEffectsWhenCalledAtPath(path, callOptions, context); } - includeArgumentsWhenCalledAtPath( - path: ObjectPath, - context: InclusionContext, - args: readonly (ExpressionEntity | SpreadElement)[] - ) { - this.getObjectEntity().includeArgumentsWhenCalledAtPath(path, context, args); - } - - protected applyDeoptimizations(): void { - this.deoptimized = true; - let hasSpread = false; - for (let index = 0; index < this.elements.length; index++) { - const element = this.elements[index]; - if (hasSpread || element instanceof SpreadElement) { - if (element) { - hasSpread = true; - element.deoptimizePath(UNKNOWN_PATH); - } - } - } - this.context.requestTreeshakingPass(); - } - private getObjectEntity(): ObjectEntity { if (this.objectEntity !== null) { return this.objectEntity; @@ -112,7 +82,7 @@ export default class ArrayExpression extends NodeBase { let hasSpread = false; for (let index = 0; index < this.elements.length; index++) { const element = this.elements[index]; - if (hasSpread || element instanceof SpreadElement) { + if (element instanceof SpreadElement || hasSpread) { if (element) { hasSpread = true; properties.unshift({ key: UnknownInteger, kind: 'init', property: element }); diff --git a/src/ast/nodes/ArrayPattern.ts b/src/ast/nodes/ArrayPattern.ts index aab64901f49..36c98606ddf 100644 --- a/src/ast/nodes/ArrayPattern.ts +++ b/src/ast/nodes/ArrayPattern.ts @@ -16,7 +16,9 @@ export default class ArrayPattern extends NodeBase implements PatternNode { exportNamesByVariable: ReadonlyMap ): void { for (const element of this.elements) { - element?.addExportedVariables(variables, exportNamesByVariable); + if (element !== null) { + element.addExportedVariables(variables, exportNamesByVariable); + } } } @@ -30,24 +32,30 @@ export default class ArrayPattern extends NodeBase implements PatternNode { return variables; } - // Patterns can only be deoptimized at the empty path at the moment - deoptimizePath(): void { - for (const element of this.elements) { - element?.deoptimizePath(EMPTY_PATH); + deoptimizePath(path: ObjectPath): void { + if (path.length === 0) { + for (const element of this.elements) { + if (element !== null) { + element.deoptimizePath(path); + } + } } } - // Patterns are only checked at the emtpy path at the moment - hasEffectsWhenAssignedAtPath(_path: ObjectPath, context: HasEffectsContext): boolean { + hasEffectsWhenAssignedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { + if (path.length > 0) return true; for (const element of this.elements) { - if (element?.hasEffectsWhenAssignedAtPath(EMPTY_PATH, context)) return true; + if (element !== null && element.hasEffectsWhenAssignedAtPath(EMPTY_PATH, context)) + return true; } return false; } markDeclarationReached(): void { for (const element of this.elements) { - element?.markDeclarationReached(); + if (element !== null) { + element.markDeclarationReached(); + } } } } diff --git a/src/ast/nodes/ArrowFunctionExpression.ts b/src/ast/nodes/ArrowFunctionExpression.ts index 095be186886..9f90349b429 100644 --- a/src/ast/nodes/ArrowFunctionExpression.ts +++ b/src/ast/nodes/ArrowFunctionExpression.ts @@ -1,12 +1,13 @@ import { type CallOptions } from '../CallOptions'; -import { type HasEffectsContext } from '../ExecutionContext'; +import { type HasEffectsContext, InclusionContext } from '../ExecutionContext'; import ReturnValueScope from '../scopes/ReturnValueScope'; import type Scope from '../scopes/Scope'; import { type ObjectPath } from '../utils/PathTracker'; import BlockStatement from './BlockStatement'; +import Identifier from './Identifier'; import * as NodeType from './NodeType'; import FunctionBase from './shared/FunctionBase'; -import { type ExpressionNode } from './shared/Node'; +import { type ExpressionNode, IncludeChildren } from './shared/Node'; import { ObjectEntity } from './shared/ObjectEntity'; import { OBJECT_PROTOTYPE } from './shared/ObjectPrototype'; import type { PatternNode } from './shared/Pattern'; @@ -47,6 +48,15 @@ export default class ArrowFunctionExpression extends FunctionBase { return false; } + include(context: InclusionContext, includeChildrenRecursively: IncludeChildren): void { + super.include(context, includeChildrenRecursively); + for (const param of this.params) { + if (!(param instanceof Identifier)) { + param.include(context, includeChildrenRecursively); + } + } + } + protected getObjectEntity(): ObjectEntity { if (this.objectEntity !== null) { return this.objectEntity; diff --git a/src/ast/nodes/AssignmentExpression.ts b/src/ast/nodes/AssignmentExpression.ts index 1a9bcaeb417..b139d1ffe8f 100644 --- a/src/ast/nodes/AssignmentExpression.ts +++ b/src/ast/nodes/AssignmentExpression.ts @@ -54,7 +54,7 @@ export default class AssignmentExpression extends NodeBase { ); } - hasEffectsWhenAccessedAtPath(path: ObjectPath, context: HasEffectsContext): boolean | undefined { + 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 0d2e1907ec2..21459c772a6 100644 --- a/src/ast/nodes/AssignmentPattern.ts +++ b/src/ast/nodes/AssignmentPattern.ts @@ -2,13 +2,12 @@ import type MagicString from 'magic-string'; import { BLANK } from '../../utils/blank'; import type { NodeRenderOptions, RenderOptions } from '../../utils/renderHelpers'; import type { HasEffectsContext } from '../ExecutionContext'; -import { InclusionContext } from '../ExecutionContext'; import { EMPTY_PATH, type ObjectPath, UNKNOWN_PATH } from '../utils/PathTracker'; import type LocalVariable from '../variables/LocalVariable'; import type Variable from '../variables/Variable'; import type * as NodeType from './NodeType'; import type { ExpressionEntity } from './shared/Expression'; -import { type ExpressionNode, IncludeChildren, NodeBase } from './shared/Node'; +import { type ExpressionNode, NodeBase } from './shared/Node'; import type { PatternNode } from './shared/Pattern'; export default class AssignmentPattern extends NodeBase implements PatternNode { @@ -36,12 +35,6 @@ export default class AssignmentPattern extends NodeBase implements PatternNode { return path.length > 0 || this.left.hasEffectsWhenAssignedAtPath(EMPTY_PATH, context); } - include(context: InclusionContext, includeChildrenRecursively: IncludeChildren): void { - this.included = true; - this.left.include(context, includeChildrenRecursively); - this.right.include(context, includeChildrenRecursively); - } - markDeclarationReached(): void { this.left.markDeclarationReached(); } @@ -52,11 +45,7 @@ export default class AssignmentPattern extends NodeBase implements PatternNode { { isShorthandProperty }: NodeRenderOptions = BLANK ): void { this.left.render(code, options, { isShorthandProperty }); - if (this.right.included) { - this.right.render(code, options); - } else { - code.remove(this.left.end, this.end); - } + this.right.render(code, options); } protected applyDeoptimizations(): void { diff --git a/src/ast/nodes/AwaitExpression.ts b/src/ast/nodes/AwaitExpression.ts index 2f27a1b47d4..d0f26fe8b23 100644 --- a/src/ast/nodes/AwaitExpression.ts +++ b/src/ast/nodes/AwaitExpression.ts @@ -1,4 +1,5 @@ import type { InclusionContext } from '../ExecutionContext'; +import { UNKNOWN_PATH } from '../utils/PathTracker'; import ArrowFunctionExpression from './ArrowFunctionExpression'; import type * as NodeType from './NodeType'; import FunctionNode from './shared/FunctionNode'; @@ -29,4 +30,10 @@ export default class AwaitExpression extends NodeBase { } this.argument.include(context, includeChildrenRecursively); } + + protected applyDeoptimizations(): void { + this.deoptimized = true; + this.argument.deoptimizePath(UNKNOWN_PATH); + this.context.requestTreeshakingPass(); + } } diff --git a/src/ast/nodes/BinaryExpression.ts b/src/ast/nodes/BinaryExpression.ts index 0b7a8598a5d..8b1bf5be0cf 100644 --- a/src/ast/nodes/BinaryExpression.ts +++ b/src/ast/nodes/BinaryExpression.ts @@ -60,10 +60,10 @@ export default class BinaryExpression extends NodeBase implements DeoptimizableE ): LiteralValueOrUnknown { if (path.length > 0) return UnknownValue; const leftValue = this.left.getLiteralValueAtPath(EMPTY_PATH, recursionTracker, origin); - if (typeof leftValue === 'symbol') return UnknownValue; + if (leftValue === UnknownValue) return UnknownValue; const rightValue = this.right.getLiteralValueAtPath(EMPTY_PATH, recursionTracker, origin); - if (typeof rightValue === 'symbol') return UnknownValue; + if (rightValue === UnknownValue) return UnknownValue; const operatorFn = binaryOperators[this.operator]; if (!operatorFn) return UnknownValue; @@ -71,7 +71,7 @@ export default class BinaryExpression extends NodeBase implements DeoptimizableE return operatorFn(leftValue, rightValue); } - hasEffects(context: HasEffectsContext): boolean | undefined { + hasEffects(context: HasEffectsContext): boolean { // support some implicit type coercion runtime errors if ( this.operator === '+' && diff --git a/src/ast/nodes/CallExpression.ts b/src/ast/nodes/CallExpression.ts index 6a9e2966561..593146bf9f9 100644 --- a/src/ast/nodes/CallExpression.ts +++ b/src/ast/nodes/CallExpression.ts @@ -6,11 +6,13 @@ import { type NodeRenderOptions, type RenderOptions } from '../../utils/renderHelpers'; +import type { CallOptions } from '../CallOptions'; import type { DeoptimizableEntity } from '../DeoptimizableEntity'; import type { HasEffectsContext, InclusionContext } from '../ExecutionContext'; -import { EVENT_CALLED } from '../NodeEvents'; +import { EVENT_CALLED, type NodeEvent } from '../NodeEvents'; import { EMPTY_PATH, + type ObjectPath, type PathTracker, SHARED_RECURSION_TRACKER, UNKNOWN_PATH @@ -20,15 +22,29 @@ import MemberExpression from './MemberExpression'; import type * as NodeType from './NodeType'; import type SpreadElement from './SpreadElement'; import type Super from './Super'; -import CallExpressionBase from './shared/CallExpressionBase'; -import { type ExpressionEntity, UNKNOWN_EXPRESSION } from './shared/Expression'; -import { type ExpressionNode, INCLUDE_PARAMETERS, type IncludeChildren } from './shared/Node'; +import { + type ExpressionEntity, + type LiteralValueOrUnknown, + UNKNOWN_EXPRESSION, + UnknownValue +} from './shared/Expression'; +import { + type ExpressionNode, + INCLUDE_PARAMETERS, + type IncludeChildren, + NodeBase +} from './shared/Node'; -export default class CallExpression extends CallExpressionBase implements DeoptimizableEntity { +export default class CallExpression extends NodeBase implements DeoptimizableEntity { declare arguments: (ExpressionNode | SpreadElement)[]; declare callee: ExpressionNode | Super; declare optional: boolean; declare type: NodeType.tCallExpression; + protected deoptimized = false; + private declare callOptions: CallOptions; + private readonly deoptimizableDependentExpressions: DeoptimizableEntity[] = []; + private readonly expressionsToBeDeoptimized = new Set(); + private returnExpression: ExpressionEntity | null = null; bind(): void { super.bind(); @@ -66,6 +82,104 @@ export default class CallExpression extends CallExpressionBase implements Deopti }; } + deoptimizeCache(): void { + if (this.returnExpression !== UNKNOWN_EXPRESSION) { + this.returnExpression = UNKNOWN_EXPRESSION; + for (const expression of this.deoptimizableDependentExpressions) { + expression.deoptimizeCache(); + } + for (const expression of this.expressionsToBeDeoptimized) { + expression.deoptimizePath(UNKNOWN_PATH); + } + } + } + + deoptimizePath(path: ObjectPath): void { + if ( + path.length === 0 || + this.context.deoptimizationTracker.trackEntityAtPathAndGetIfTracked(path, this) + ) { + return; + } + const returnExpression = this.getReturnExpression(); + if (returnExpression !== UNKNOWN_EXPRESSION) { + returnExpression.deoptimizePath(path); + } + } + + deoptimizeThisOnEventAtPath( + event: NodeEvent, + path: ObjectPath, + thisParameter: ExpressionEntity, + recursionTracker: PathTracker + ): void { + const returnExpression = this.getReturnExpression(recursionTracker); + if (returnExpression === UNKNOWN_EXPRESSION) { + thisParameter.deoptimizePath(UNKNOWN_PATH); + } else { + recursionTracker.withTrackedEntityAtPath( + path, + returnExpression, + () => { + this.expressionsToBeDeoptimized.add(thisParameter); + returnExpression.deoptimizeThisOnEventAtPath( + event, + path, + thisParameter, + recursionTracker + ); + }, + undefined + ); + } + } + + getLiteralValueAtPath( + path: ObjectPath, + recursionTracker: PathTracker, + origin: DeoptimizableEntity + ): LiteralValueOrUnknown { + const returnExpression = this.getReturnExpression(recursionTracker); + if (returnExpression === UNKNOWN_EXPRESSION) { + return UnknownValue; + } + return recursionTracker.withTrackedEntityAtPath( + path, + returnExpression, + () => { + this.deoptimizableDependentExpressions.push(origin); + return returnExpression.getLiteralValueAtPath(path, recursionTracker, origin); + }, + UnknownValue + ); + } + + getReturnExpressionWhenCalledAtPath( + path: ObjectPath, + callOptions: CallOptions, + recursionTracker: PathTracker, + origin: DeoptimizableEntity + ): ExpressionEntity { + const returnExpression = this.getReturnExpression(recursionTracker); + if (this.returnExpression === UNKNOWN_EXPRESSION) { + return UNKNOWN_EXPRESSION; + } + return recursionTracker.withTrackedEntityAtPath( + path, + returnExpression, + () => { + this.deoptimizableDependentExpressions.push(origin); + return returnExpression.getReturnExpressionWhenCalledAtPath( + path, + callOptions, + recursionTracker, + origin + ); + }, + UNKNOWN_EXPRESSION + ); + } + hasEffects(context: HasEffectsContext): boolean { try { for (const argument of this.arguments) { @@ -85,6 +199,33 @@ export default class CallExpression extends CallExpressionBase implements Deopti } } + hasEffectsWhenAccessedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { + return ( + !context.accessed.trackEntityAtPathAndGetIfTracked(path, this) && + this.getReturnExpression().hasEffectsWhenAccessedAtPath(path, context) + ); + } + + hasEffectsWhenAssignedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { + return ( + !context.assigned.trackEntityAtPathAndGetIfTracked(path, this) && + this.getReturnExpression().hasEffectsWhenAssignedAtPath(path, context) + ); + } + + hasEffectsWhenCalledAtPath( + path: ObjectPath, + callOptions: CallOptions, + context: HasEffectsContext + ): boolean { + return ( + !( + callOptions.withNew ? context.instantiated : context.called + ).trackEntityAtPathAndGetIfTracked(path, callOptions, this) && + this.getReturnExpression().hasEffectsWhenCalledAtPath(path, callOptions, context) + ); + } + include(context: InclusionContext, includeChildrenRecursively: IncludeChildren): void { if (!this.deoptimized) this.applyDeoptimizations(); if (includeChildrenRecursively) { @@ -100,7 +241,7 @@ export default class CallExpression extends CallExpressionBase implements Deopti this.included = true; this.callee.include(context, false); } - this.callee.includeArgumentsWhenCalledAtPath(EMPTY_PATH, context, this.arguments); + this.callee.includeCallArguments(context, this.arguments); const returnExpression = this.getReturnExpression(); if (!returnExpression.included) { returnExpression.include(context, false); @@ -166,7 +307,7 @@ export default class CallExpression extends CallExpressionBase implements Deopti this.context.requestTreeshakingPass(); } - protected getReturnExpression( + private getReturnExpression( recursionTracker: PathTracker = SHARED_RECURSION_TRACKER ): ExpressionEntity { if (this.returnExpression === null) { diff --git a/src/ast/nodes/ConditionalExpression.ts b/src/ast/nodes/ConditionalExpression.ts index edef01a9095..afdc04d8d12 100644 --- a/src/ast/nodes/ConditionalExpression.ts +++ b/src/ast/nodes/ConditionalExpression.ts @@ -108,7 +108,7 @@ export default class ConditionalExpression extends NodeBase implements Deoptimiz ); } - hasEffects(context: HasEffectsContext): boolean | undefined { + hasEffects(context: HasEffectsContext): boolean { if (this.test.hasEffects(context)) return true; const usedBranch = this.getUsedBranch(); if (usedBranch === null) { @@ -117,7 +117,7 @@ export default class ConditionalExpression extends NodeBase implements Deoptimiz return usedBranch.hasEffects(context); } - hasEffectsWhenAccessedAtPath(path: ObjectPath, context: HasEffectsContext): boolean | undefined { + hasEffectsWhenAccessedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { const usedBranch = this.getUsedBranch(); if (usedBranch === null) { return ( @@ -166,17 +166,16 @@ export default class ConditionalExpression extends NodeBase implements Deoptimiz } } - includeArgumentsWhenCalledAtPath( - path: ObjectPath, + includeCallArguments( context: InclusionContext, - args: readonly (ExpressionEntity | SpreadElement)[] + args: readonly (ExpressionNode | SpreadElement)[] ): void { const usedBranch = this.getUsedBranch(); if (usedBranch === null) { - this.consequent.includeArgumentsWhenCalledAtPath(path, context, args); - this.alternate.includeArgumentsWhenCalledAtPath(path, context, args); + this.consequent.includeCallArguments(context, args); + this.alternate.includeCallArguments(context, args); } else { - usedBranch.includeArgumentsWhenCalledAtPath(path, context, args); + usedBranch.includeCallArguments(context, args); } } @@ -226,7 +225,7 @@ export default class ConditionalExpression extends NodeBase implements Deoptimiz } this.isBranchResolutionAnalysed = true; const testValue = this.test.getLiteralValueAtPath(EMPTY_PATH, SHARED_RECURSION_TRACKER, this); - return typeof testValue === 'symbol' + return testValue === UnknownValue ? null : (this.usedBranch = testValue ? this.consequent : this.alternate); } diff --git a/src/ast/nodes/DoWhileStatement.ts b/src/ast/nodes/DoWhileStatement.ts index 4779555f8ea..6218fa03da2 100644 --- a/src/ast/nodes/DoWhileStatement.ts +++ b/src/ast/nodes/DoWhileStatement.ts @@ -31,7 +31,7 @@ export default class DoWhileStatement extends StatementBase { this.included = true; this.test.include(context, includeChildrenRecursively); const { brokenFlow } = context; - this.body.include(context, includeChildrenRecursively, { asSingleStatement: true }); + this.body.includeAsSingleStatement(context, includeChildrenRecursively); context.brokenFlow = brokenFlow; } } diff --git a/src/ast/nodes/ExportNamedDeclaration.ts b/src/ast/nodes/ExportNamedDeclaration.ts index 2accca3bba3..0d09a25e8e2 100644 --- a/src/ast/nodes/ExportNamedDeclaration.ts +++ b/src/ast/nodes/ExportNamedDeclaration.ts @@ -18,11 +18,11 @@ export default class ExportNamedDeclaration extends NodeBase { bind(): void { // Do not bind specifiers - this.declaration?.bind(); + if (this.declaration !== null) this.declaration.bind(); } - hasEffects(context: HasEffectsContext): boolean | undefined { - return this.declaration?.hasEffects(context); + hasEffects(context: HasEffectsContext): boolean { + return this.declaration !== null && this.declaration.hasEffects(context); } initialise(): void { diff --git a/src/ast/nodes/ExpressionStatement.ts b/src/ast/nodes/ExpressionStatement.ts index 5171d5f19a0..f5c141c84d2 100644 --- a/src/ast/nodes/ExpressionStatement.ts +++ b/src/ast/nodes/ExpressionStatement.ts @@ -30,7 +30,7 @@ export default class ExpressionStatement extends StatementBase { if (this.included) this.insertSemicolon(code); } - shouldBeIncluded(context: InclusionContext): boolean | undefined { + shouldBeIncluded(context: InclusionContext): boolean { 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 9669720ba8d..038404ae8d6 100644 --- a/src/ast/nodes/ForInStatement.ts +++ b/src/ast/nodes/ForInStatement.ts @@ -53,7 +53,7 @@ export default class ForInStatement extends StatementBase { this.left.include(context, includeChildrenRecursively || true); this.right.include(context, includeChildrenRecursively); const { brokenFlow } = context; - this.body.include(context, includeChildrenRecursively, { asSingleStatement: true }); + this.body.includeAsSingleStatement(context, includeChildrenRecursively); context.brokenFlow = brokenFlow; } diff --git a/src/ast/nodes/ForOfStatement.ts b/src/ast/nodes/ForOfStatement.ts index 4ece368c04d..c7ba84d6dbd 100644 --- a/src/ast/nodes/ForOfStatement.ts +++ b/src/ast/nodes/ForOfStatement.ts @@ -38,7 +38,7 @@ export default class ForOfStatement extends StatementBase { this.left.include(context, includeChildrenRecursively || true); this.right.include(context, includeChildrenRecursively); const { brokenFlow } = context; - this.body.include(context, includeChildrenRecursively, { asSingleStatement: true }); + this.body.includeAsSingleStatement(context, includeChildrenRecursively); context.brokenFlow = brokenFlow; } diff --git a/src/ast/nodes/ForStatement.ts b/src/ast/nodes/ForStatement.ts index eb228cd6942..25916ad9cd3 100644 --- a/src/ast/nodes/ForStatement.ts +++ b/src/ast/nodes/ForStatement.ts @@ -25,9 +25,9 @@ export default class ForStatement extends StatementBase { hasEffects(context: HasEffectsContext): boolean { if ( - this.init?.hasEffects(context) || - this.test?.hasEffects(context) || - this.update?.hasEffects(context) + (this.init && this.init.hasEffects(context)) || + (this.test && this.test.hasEffects(context)) || + (this.update && this.update.hasEffects(context)) ) return true; const { @@ -45,18 +45,18 @@ export default class ForStatement extends StatementBase { include(context: InclusionContext, includeChildrenRecursively: IncludeChildren): void { this.included = true; - this.init?.include(context, includeChildrenRecursively, { asSingleStatement: true }); - this.test?.include(context, includeChildrenRecursively); + if (this.init) this.init.includeAsSingleStatement(context, includeChildrenRecursively); + if (this.test) this.test.include(context, includeChildrenRecursively); const { brokenFlow } = context; - this.update?.include(context, includeChildrenRecursively); - this.body.include(context, includeChildrenRecursively, { asSingleStatement: true }); + if (this.update) this.update.include(context, includeChildrenRecursively); + this.body.includeAsSingleStatement(context, includeChildrenRecursively); context.brokenFlow = brokenFlow; } render(code: MagicString, options: RenderOptions): void { - this.init?.render(code, options, NO_SEMICOLON); - this.test?.render(code, options, NO_SEMICOLON); - this.update?.render(code, options, NO_SEMICOLON); + if (this.init) this.init.render(code, options, NO_SEMICOLON); + if (this.test) this.test.render(code, options, NO_SEMICOLON); + if (this.update) this.update.render(code, options, NO_SEMICOLON); this.body.render(code, options); } } diff --git a/src/ast/nodes/Identifier.ts b/src/ast/nodes/Identifier.ts index cdf4c21d173..89965f79287 100644 --- a/src/ast/nodes/Identifier.ts +++ b/src/ast/nodes/Identifier.ts @@ -19,7 +19,7 @@ import { type LiteralValueOrUnknown, UNKNOWN_EXPRESSION } from './shared/Expression'; -import { NodeBase } from './shared/Node'; +import { type ExpressionNode, NodeBase } from './shared/Node'; import type { PatternNode } from './shared/Pattern'; export type IdentifierWithVariable = Identifier & { variable: Variable }; @@ -43,13 +43,13 @@ export default class Identifier extends NodeBase implements PatternNode { variables: Variable[], exportNamesByVariable: ReadonlyMap ): void { - if (exportNamesByVariable.has(this.variable!)) { - variables.push(this.variable!); + if (this.variable !== null && exportNamesByVariable.has(this.variable)) { + variables.push(this.variable); } } bind(): void { - if (!this.variable && isReference(this, this.parent as NodeWithFieldDefinition)) { + if (this.variable === null && isReference(this, this.parent as NodeWithFieldDefinition)) { this.variable = this.scope.findVariable(this.name); this.variable.addReference(this); } @@ -108,7 +108,7 @@ export default class Identifier extends NodeBase implements PatternNode { recursionTracker: PathTracker, origin: DeoptimizableEntity ): LiteralValueOrUnknown { - return this.getVariableRespectingTDZ()!.getLiteralValueAtPath(path, recursionTracker, origin); + return this.getVariableRespectingTDZ().getLiteralValueAtPath(path, recursionTracker, origin); } getReturnExpressionWhenCalledAtPath( @@ -117,7 +117,7 @@ export default class Identifier extends NodeBase implements PatternNode { recursionTracker: PathTracker, origin: DeoptimizableEntity ): ExpressionEntity { - return this.getVariableRespectingTDZ()!.getReturnExpressionWhenCalledAtPath( + return this.getVariableRespectingTDZ().getReturnExpressionWhenCalledAtPath( path, callOptions, recursionTracker, @@ -137,14 +137,21 @@ export default class Identifier extends NodeBase implements PatternNode { ); } - hasEffectsWhenAccessedAtPath(path: ObjectPath, context: HasEffectsContext): boolean | undefined { - return this.getVariableRespectingTDZ()?.hasEffectsWhenAccessedAtPath(path, context); + hasEffectsWhenAccessedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { + return ( + this.variable !== null && + this.getVariableRespectingTDZ().hasEffectsWhenAccessedAtPath(path, context) + ); } hasEffectsWhenAssignedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { return ( - path.length > 0 ? this.getVariableRespectingTDZ() : this.variable - )!.hasEffectsWhenAssignedAtPath(path, context); + !this.variable || + (path.length > 0 + ? this.getVariableRespectingTDZ() + : this.variable + ).hasEffectsWhenAssignedAtPath(path, context) + ); } hasEffectsWhenCalledAtPath( @@ -152,7 +159,10 @@ export default class Identifier extends NodeBase implements PatternNode { callOptions: CallOptions, context: HasEffectsContext ): boolean { - return this.getVariableRespectingTDZ()!.hasEffectsWhenCalledAtPath(path, callOptions, context); + return ( + !this.variable || + this.getVariableRespectingTDZ().hasEffectsWhenCalledAtPath(path, callOptions, context) + ); } include(): void { @@ -165,12 +175,11 @@ export default class Identifier extends NodeBase implements PatternNode { } } - includeArgumentsWhenCalledAtPath( - path: ObjectPath, + includeCallArguments( context: InclusionContext, - args: readonly (ExpressionEntity | SpreadElement)[] + args: readonly (ExpressionNode | SpreadElement)[] ): void { - this.variable!.includeArgumentsWhenCalledAtPath(path, context, args); + this.getVariableRespectingTDZ().includeCallArguments(context, args); } isPossibleTDZ(): boolean { @@ -241,7 +250,7 @@ export default class Identifier extends NodeBase implements PatternNode { protected applyDeoptimizations(): void { this.deoptimized = true; - if (this.variable instanceof LocalVariable) { + if (this.variable !== null && this.variable instanceof LocalVariable) { this.variable.consolidateInitializers(); this.context.requestTreeshakingPass(); } @@ -257,11 +266,11 @@ export default class Identifier extends NodeBase implements PatternNode { ); } - private getVariableRespectingTDZ(): ExpressionEntity | null { + private getVariableRespectingTDZ(): ExpressionEntity { if (this.isPossibleTDZ()) { return UNKNOWN_EXPRESSION; } - return this.variable; + return this.variable!; } } diff --git a/src/ast/nodes/IfStatement.ts b/src/ast/nodes/IfStatement.ts index bfc9999ecab..b7ee1385be3 100644 --- a/src/ast/nodes/IfStatement.ts +++ b/src/ast/nodes/IfStatement.ts @@ -36,12 +36,12 @@ export default class IfStatement extends StatementBase implements DeoptimizableE this.testValue = UnknownValue; } - hasEffects(context: HasEffectsContext): boolean | undefined { + hasEffects(context: HasEffectsContext): boolean { if (this.test.hasEffects(context)) { return true; } const testValue = this.getTestValue(); - if (typeof testValue === 'symbol') { + if (testValue === UnknownValue) { const { brokenFlow } = context; if (this.consequent.hasEffects(context)) return true; const consequentBrokenFlow = context.brokenFlow; @@ -52,7 +52,9 @@ export default class IfStatement extends StatementBase implements DeoptimizableE context.brokenFlow < consequentBrokenFlow ? context.brokenFlow : consequentBrokenFlow; return false; } - return testValue ? this.consequent.hasEffects(context) : this.alternate?.hasEffects(context); + return testValue + ? this.consequent.hasEffects(context) + : this.alternate !== null && this.alternate.hasEffects(context); } include(context: InclusionContext, includeChildrenRecursively: IncludeChildren): void { @@ -61,7 +63,7 @@ export default class IfStatement extends StatementBase implements DeoptimizableE this.includeRecursively(includeChildrenRecursively, context); } else { const testValue = this.getTestValue(); - if (typeof testValue === 'symbol') { + if (testValue === UnknownValue) { this.includeUnknownTest(context); } else { this.includeKnownTest(context, testValue); @@ -101,14 +103,14 @@ export default class IfStatement extends StatementBase implements DeoptimizableE } else { code.remove(this.start, this.consequent.start); } - if (this.consequent.included && (noTreeshake || typeof testValue === 'symbol' || testValue)) { + if (this.consequent.included && (noTreeshake || testValue === UnknownValue || testValue)) { this.consequent.render(code, options); } else { code.overwrite(this.consequent.start, this.consequent.end, includesIfElse ? ';' : ''); hoistedDeclarations.push(...this.consequentScope.hoistedDeclarations); } if (this.alternate) { - if (this.alternate.included && (noTreeshake || typeof testValue === 'symbol' || !testValue)) { + if (this.alternate.included && (noTreeshake || testValue === UnknownValue || !testValue)) { if (includesIfElse) { if (code.original.charCodeAt(this.alternate.start - 1) === 101) { code.prependLeft(this.alternate.start, ' '); @@ -145,10 +147,10 @@ export default class IfStatement extends StatementBase implements DeoptimizableE this.test.include(context, false); } if (testValue && this.consequent.shouldBeIncluded(context)) { - this.consequent.include(context, false, { asSingleStatement: true }); + this.consequent.includeAsSingleStatement(context, false); } - if (!testValue && this.alternate?.shouldBeIncluded(context)) { - this.alternate.include(context, false, { asSingleStatement: true }); + if (this.alternate !== null && !testValue && this.alternate.shouldBeIncluded(context)) { + this.alternate.includeAsSingleStatement(context, false); } } @@ -158,7 +160,9 @@ export default class IfStatement extends StatementBase implements DeoptimizableE ) { this.test.include(context, includeChildrenRecursively); this.consequent.include(context, includeChildrenRecursively); - this.alternate?.include(context, includeChildrenRecursively); + if (this.alternate !== null) { + this.alternate.include(context, includeChildrenRecursively); + } } private includeUnknownTest(context: InclusionContext) { @@ -166,12 +170,12 @@ export default class IfStatement extends StatementBase implements DeoptimizableE const { brokenFlow } = context; let consequentBrokenFlow = BROKEN_FLOW_NONE; if (this.consequent.shouldBeIncluded(context)) { - this.consequent.include(context, false, { asSingleStatement: true }); + this.consequent.includeAsSingleStatement(context, false); consequentBrokenFlow = context.brokenFlow; context.brokenFlow = brokenFlow; } - if (this.alternate?.shouldBeIncluded(context)) { - this.alternate.include(context, false, { asSingleStatement: true }); + if (this.alternate !== null && this.alternate.shouldBeIncluded(context)) { + this.alternate.includeAsSingleStatement(context, false); context.brokenFlow = context.brokenFlow < consequentBrokenFlow ? context.brokenFlow : consequentBrokenFlow; } diff --git a/src/ast/nodes/LogicalExpression.ts b/src/ast/nodes/LogicalExpression.ts index cc9ccca0150..571d28a75e2 100644 --- a/src/ast/nodes/LogicalExpression.ts +++ b/src/ast/nodes/LogicalExpression.ts @@ -104,7 +104,7 @@ export default class LogicalExpression extends NodeBase implements Deoptimizable ); } - hasEffects(context: HasEffectsContext): boolean | undefined { + hasEffects(context: HasEffectsContext): boolean { if (this.left.hasEffects(context)) { return true; } @@ -114,7 +114,7 @@ export default class LogicalExpression extends NodeBase implements Deoptimizable return false; } - hasEffectsWhenAccessedAtPath(path: ObjectPath, context: HasEffectsContext): boolean | undefined { + hasEffectsWhenAccessedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { const usedBranch = this.getUsedBranch(); if (usedBranch === null) { return ( @@ -211,7 +211,7 @@ export default class LogicalExpression extends NodeBase implements Deoptimizable if (!this.isBranchResolutionAnalysed) { this.isBranchResolutionAnalysed = true; const leftValue = this.left.getLiteralValueAtPath(EMPTY_PATH, SHARED_RECURSION_TRACKER, this); - if (typeof leftValue === 'symbol') { + if (leftValue === UnknownValue) { return null; } else { this.usedBranch = diff --git a/src/ast/nodes/MemberExpression.ts b/src/ast/nodes/MemberExpression.ts index c8f6a965e30..2d91fa649d5 100644 --- a/src/ast/nodes/MemberExpression.ts +++ b/src/ast/nodes/MemberExpression.ts @@ -211,7 +211,7 @@ export default class MemberExpression extends NodeBase implements DeoptimizableE return UNKNOWN_EXPRESSION; } - hasEffects(context: HasEffectsContext): boolean | undefined { + hasEffects(context: HasEffectsContext): boolean { if (!this.deoptimized) this.applyDeoptimizations(); const { propertyReadSideEffects } = this.context.options .treeshake as NormalizedTreeshakingOptions; @@ -230,7 +230,7 @@ export default class MemberExpression extends NodeBase implements DeoptimizableE ); } - hasEffectsWhenAccessedAtPath(path: ObjectPath, context: HasEffectsContext): boolean | undefined { + hasEffectsWhenAccessedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { if (this.variable !== null) { return this.variable.hasEffectsWhenAccessedAtPath(path, context); } @@ -289,17 +289,14 @@ export default class MemberExpression extends NodeBase implements DeoptimizableE this.property.include(context, includeChildrenRecursively); } - includeArgumentsWhenCalledAtPath( - path: ObjectPath, + includeCallArguments( context: InclusionContext, - args: readonly (ExpressionEntity | SpreadElement)[] + args: readonly (ExpressionNode | SpreadElement)[] ): void { if (this.variable) { - this.variable.includeArgumentsWhenCalledAtPath(path, context, args); - } else if (this.replacement) { - super.includeArgumentsWhenCalledAtPath(path, context, args); - } else if (path.length < MAX_PATH_DEPTH) { - this.object.includeArgumentsWhenCalledAtPath([this.getPropertyKey(), ...path], context, args); + this.variable.includeCallArguments(context, args); + } else { + super.includeCallArguments(context, args); } } @@ -388,7 +385,7 @@ export default class MemberExpression extends NodeBase implements DeoptimizableE if (this.propertyKey === null) { this.propertyKey = UnknownKey; const value = this.property.getLiteralValueAtPath(EMPTY_PATH, SHARED_RECURSION_TRACKER, this); - return (this.propertyKey = typeof value === 'symbol' ? UnknownKey : String(value)); + return (this.propertyKey = value === UnknownValue ? UnknownKey : String(value)); } return this.propertyKey; } diff --git a/src/ast/nodes/NewExpression.ts b/src/ast/nodes/NewExpression.ts index 4c8c0a6c584..abef82ffae0 100644 --- a/src/ast/nodes/NewExpression.ts +++ b/src/ast/nodes/NewExpression.ts @@ -1,10 +1,9 @@ import type { NormalizedTreeshakingOptions } from '../../rollup/types'; import type { CallOptions } from '../CallOptions'; import type { HasEffectsContext } from '../ExecutionContext'; -import { InclusionContext } from '../ExecutionContext'; import { EMPTY_PATH, type ObjectPath, UNKNOWN_PATH } from '../utils/PathTracker'; import type * as NodeType from './NodeType'; -import { type ExpressionNode, IncludeChildren, NodeBase } from './shared/Node'; +import { type ExpressionNode, NodeBase } from './shared/Node'; export default class NewExpression extends NodeBase { declare arguments: ExpressionNode[]; @@ -14,35 +13,25 @@ export default class NewExpression extends NodeBase { private declare callOptions: CallOptions; hasEffects(context: HasEffectsContext): boolean { - try { - for (const argument of this.arguments) { - if (argument.hasEffects(context)) return true; - } - if ( - (this.context.options.treeshake as NormalizedTreeshakingOptions).annotations && - this.annotations - ) - return false; - return ( - this.callee.hasEffects(context) || - this.callee.hasEffectsWhenCalledAtPath(EMPTY_PATH, this.callOptions, context) - ); - } finally { - if (!this.deoptimized) this.applyDeoptimizations(); + if (!this.deoptimized) this.applyDeoptimizations(); + for (const argument of this.arguments) { + if (argument.hasEffects(context)) return true; } + if ( + (this.context.options.treeshake as NormalizedTreeshakingOptions).annotations && + this.annotations + ) + return false; + return ( + this.callee.hasEffects(context) || + this.callee.hasEffectsWhenCalledAtPath(EMPTY_PATH, this.callOptions, context) + ); } hasEffectsWhenAccessedAtPath(path: ObjectPath): boolean { return path.length > 0; } - include(context: InclusionContext, includeChildrenRecursively: IncludeChildren): void { - if (!this.deoptimized) this.applyDeoptimizations(); - this.included = true; - this.callee.include(context, includeChildrenRecursively); - this.callee.includeArgumentsWhenCalledAtPath(EMPTY_PATH, context, this.arguments); - } - initialise(): void { this.callOptions = { args: this.arguments, diff --git a/src/ast/nodes/ObjectExpression.ts b/src/ast/nodes/ObjectExpression.ts index acb84035fee..61ff336a6c1 100644 --- a/src/ast/nodes/ObjectExpression.ts +++ b/src/ast/nodes/ObjectExpression.ts @@ -3,7 +3,7 @@ import { BLANK } from '../../utils/blank'; import type { NodeRenderOptions, RenderOptions } from '../../utils/renderHelpers'; import type { CallOptions } from '../CallOptions'; import type { DeoptimizableEntity } from '../DeoptimizableEntity'; -import type { HasEffectsContext, InclusionContext } from '../ExecutionContext'; +import type { HasEffectsContext } from '../ExecutionContext'; import type { NodeEvent } from '../NodeEvents'; import { EMPTY_PATH, @@ -17,7 +17,11 @@ import Literal from './Literal'; import * as NodeType from './NodeType'; import type Property from './Property'; import SpreadElement from './SpreadElement'; -import { type ExpressionEntity, type LiteralValueOrUnknown } from './shared/Expression'; +import { + type ExpressionEntity, + type LiteralValueOrUnknown, + UnknownValue +} from './shared/Expression'; import { NodeBase } from './shared/Node'; import { ObjectEntity, type ObjectProperty } from './shared/ObjectEntity'; import { OBJECT_PROTOTYPE } from './shared/ObjectPrototype'; @@ -71,7 +75,7 @@ export default class ObjectExpression extends NodeBase implements DeoptimizableE ); } - hasEffectsWhenAccessedAtPath(path: ObjectPath, context: HasEffectsContext): boolean | undefined { + hasEffectsWhenAccessedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { return this.getObjectEntity().hasEffectsWhenAccessedAtPath(path, context); } @@ -87,14 +91,6 @@ export default class ObjectExpression extends NodeBase implements DeoptimizableE return this.getObjectEntity().hasEffectsWhenCalledAtPath(path, callOptions, context); } - includeArgumentsWhenCalledAtPath( - path: ObjectPath, - context: InclusionContext, - args: readonly (ExpressionEntity | SpreadElement)[] - ) { - this.getObjectEntity().includeArgumentsWhenCalledAtPath(path, context, args); - } - render( code: MagicString, options: RenderOptions, @@ -128,7 +124,7 @@ export default class ObjectExpression extends NodeBase implements DeoptimizableE SHARED_RECURSION_TRACKER, this ); - if (typeof keyValue === 'symbol') { + if (keyValue === UnknownValue) { properties.push({ key: UnknownKey, kind: property.kind, property }); continue; } else { diff --git a/src/ast/nodes/Property.ts b/src/ast/nodes/Property.ts index 1695c4a3409..c55fac6e834 100644 --- a/src/ast/nodes/Property.ts +++ b/src/ast/nodes/Property.ts @@ -24,7 +24,7 @@ export default class Property extends MethodBase implements PatternNode { return (this.value as PatternNode).declare(kind, UNKNOWN_EXPRESSION); } - hasEffects(context: HasEffectsContext): boolean | undefined { + hasEffects(context: HasEffectsContext): boolean { if (!this.deoptimized) this.applyDeoptimizations(); const propertyReadSideEffects = (this.context.options.treeshake as NormalizedTreeshakingOptions) .propertyReadSideEffects; diff --git a/src/ast/nodes/PropertyDefinition.ts b/src/ast/nodes/PropertyDefinition.ts index 6c212c67760..36183b9872a 100644 --- a/src/ast/nodes/PropertyDefinition.ts +++ b/src/ast/nodes/PropertyDefinition.ts @@ -1,11 +1,10 @@ import type { CallOptions } from '../CallOptions'; import type { DeoptimizableEntity } from '../DeoptimizableEntity'; -import type { HasEffectsContext, InclusionContext } from '../ExecutionContext'; +import type { HasEffectsContext } from '../ExecutionContext'; import type { NodeEvent } from '../NodeEvents'; import type { ObjectPath, PathTracker } from '../utils/PathTracker'; import type * as NodeType from './NodeType'; import type PrivateIdentifier from './PrivateIdentifier'; -import SpreadElement from './SpreadElement'; import { type ExpressionEntity, type LiteralValueOrUnknown, @@ -55,11 +54,14 @@ export default class PropertyDefinition extends NodeBase { : UNKNOWN_EXPRESSION; } - hasEffects(context: HasEffectsContext): boolean | undefined { - return this.key.hasEffects(context) || (this.static && this.value?.hasEffects(context)); + hasEffects(context: HasEffectsContext): boolean { + return ( + this.key.hasEffects(context) || + (this.static && this.value !== null && this.value.hasEffects(context)) + ); } - hasEffectsWhenAccessedAtPath(path: ObjectPath, context: HasEffectsContext): boolean | undefined { + hasEffectsWhenAccessedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { return !this.value || this.value.hasEffectsWhenAccessedAtPath(path, context); } @@ -74,12 +76,4 @@ export default class PropertyDefinition extends NodeBase { ): boolean { return !this.value || this.value.hasEffectsWhenCalledAtPath(path, callOptions, context); } - - includeArgumentsWhenCalledAtPath( - path: ObjectPath, - context: InclusionContext, - args: readonly (ExpressionEntity | SpreadElement)[] - ) { - this.value?.includeArgumentsWhenCalledAtPath(path, context, args); - } } diff --git a/src/ast/nodes/ReturnStatement.ts b/src/ast/nodes/ReturnStatement.ts index eaa99bab74e..acfdd4d066b 100644 --- a/src/ast/nodes/ReturnStatement.ts +++ b/src/ast/nodes/ReturnStatement.ts @@ -14,14 +14,20 @@ export default class ReturnStatement extends StatementBase { declare type: NodeType.tReturnStatement; hasEffects(context: HasEffectsContext): boolean { - if (!context.ignore.returnYield || this.argument?.hasEffects(context)) return true; + if ( + !context.ignore.returnYield || + (this.argument !== null && this.argument.hasEffects(context)) + ) + return true; context.brokenFlow = BROKEN_FLOW_ERROR_RETURN_LABEL; return false; } include(context: InclusionContext, includeChildrenRecursively: IncludeChildren): void { this.included = true; - this.argument?.include(context, includeChildrenRecursively); + if (this.argument) { + this.argument.include(context, includeChildrenRecursively); + } context.brokenFlow = BROKEN_FLOW_ERROR_RETURN_LABEL; } diff --git a/src/ast/nodes/SequenceExpression.ts b/src/ast/nodes/SequenceExpression.ts index 51031e632ff..1eaca85ad4b 100644 --- a/src/ast/nodes/SequenceExpression.ts +++ b/src/ast/nodes/SequenceExpression.ts @@ -58,7 +58,7 @@ export default class SequenceExpression extends NodeBase { return false; } - hasEffectsWhenAccessedAtPath(path: ObjectPath, context: HasEffectsContext): boolean | undefined { + hasEffectsWhenAccessedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { return ( path.length > 0 && this.expressions[this.expressions.length - 1].hasEffectsWhenAccessedAtPath(path, context) diff --git a/src/ast/nodes/SpreadElement.ts b/src/ast/nodes/SpreadElement.ts index 7e0f38e3872..95145d9c6fd 100644 --- a/src/ast/nodes/SpreadElement.ts +++ b/src/ast/nodes/SpreadElement.ts @@ -27,7 +27,7 @@ export default class SpreadElement extends NodeBase { } } - hasEffects(context: HasEffectsContext): boolean | undefined { + hasEffects(context: HasEffectsContext): boolean { if (!this.deoptimized) this.applyDeoptimizations(); const { propertyReadSideEffects } = this.context.options .treeshake as NormalizedTreeshakingOptions; diff --git a/src/ast/nodes/Super.ts b/src/ast/nodes/Super.ts index b7a2d35e733..f598cbff0b1 100644 --- a/src/ast/nodes/Super.ts +++ b/src/ast/nodes/Super.ts @@ -1,30 +1,19 @@ -import { NodeEvent } from '../NodeEvents'; import type { ObjectPath } from '../utils/PathTracker'; -import { PathTracker } from '../utils/PathTracker'; -import Variable from '../variables/Variable'; +import type ThisVariable from '../variables/ThisVariable'; import type * as NodeType from './NodeType'; -import { ExpressionEntity } from './shared/Expression'; import { NodeBase } from './shared/Node'; export default class Super extends NodeBase { declare type: NodeType.tSuper; - declare variable: Variable; + declare variable: ThisVariable; bind(): void { - this.variable = this.scope.findVariable('this'); + this.variable = this.scope.findVariable('this') as ThisVariable; } deoptimizePath(path: ObjectPath): void { this.variable.deoptimizePath(path); } - deoptimizeThisOnEventAtPath( - event: NodeEvent, - path: ObjectPath, - thisParameter: ExpressionEntity, - recursionTracker: PathTracker - ) { - this.variable.deoptimizeThisOnEventAtPath(event, path, thisParameter, recursionTracker); - } include(): void { if (!this.included) { diff --git a/src/ast/nodes/SwitchCase.ts b/src/ast/nodes/SwitchCase.ts index 7fd0cfe1501..fad874c41d3 100644 --- a/src/ast/nodes/SwitchCase.ts +++ b/src/ast/nodes/SwitchCase.ts @@ -21,7 +21,7 @@ export default class SwitchCase extends NodeBase { declare type: NodeType.tSwitchCase; hasEffects(context: HasEffectsContext): boolean { - if (this.test?.hasEffects(context)) return true; + if (this.test && this.test.hasEffects(context)) return true; for (const node of this.consequent) { if (context.brokenFlow) break; if (node.hasEffects(context)) return true; @@ -31,7 +31,7 @@ export default class SwitchCase extends NodeBase { include(context: InclusionContext, includeChildrenRecursively: IncludeChildren): void { this.included = true; - this.test?.include(context, includeChildrenRecursively); + if (this.test) this.test.include(context, includeChildrenRecursively); for (const node of this.consequent) { if (includeChildrenRecursively || node.shouldBeIncluded(context)) node.include(context, includeChildrenRecursively); diff --git a/src/ast/nodes/TaggedTemplateExpression.ts b/src/ast/nodes/TaggedTemplateExpression.ts index 140f1899e0a..3bb300252e8 100644 --- a/src/ast/nodes/TaggedTemplateExpression.ts +++ b/src/ast/nodes/TaggedTemplateExpression.ts @@ -1,27 +1,20 @@ import type MagicString from 'magic-string'; import { type RenderOptions } from '../../utils/renderHelpers'; +import { type CallOptions, NO_ARGS } from '../CallOptions'; import type { HasEffectsContext } from '../ExecutionContext'; -import { InclusionContext } from '../ExecutionContext'; -import { EVENT_CALLED } from '../NodeEvents'; -import { - EMPTY_PATH, - PathTracker, - SHARED_RECURSION_TRACKER, - UNKNOWN_PATH -} from '../utils/PathTracker'; -import Identifier from './Identifier'; -import MemberExpression from './MemberExpression'; +import { EMPTY_PATH } from '../utils/PathTracker'; +import type Identifier from './Identifier'; import * as NodeType from './NodeType'; import type TemplateLiteral from './TemplateLiteral'; -import CallExpressionBase from './shared/CallExpressionBase'; -import { ExpressionEntity, UNKNOWN_EXPRESSION } from './shared/Expression'; -import { type ExpressionNode, IncludeChildren } from './shared/Node'; +import { type ExpressionNode, NodeBase } from './shared/Node'; -export default class TaggedTemplateExpression extends CallExpressionBase { +export default class TaggedTemplateExpression extends NodeBase { declare quasi: TemplateLiteral; declare tag: ExpressionNode; declare type: NodeType.tTaggedTemplateExpression; + private declare callOptions: CallOptions; + bind(): void { super.bind(); if (this.tag.type === NodeType.Identifier) { @@ -41,36 +34,16 @@ export default class TaggedTemplateExpression extends CallExpressionBase { } hasEffects(context: HasEffectsContext): boolean { - try { - for (const argument of this.quasi.expressions) { - if (argument.hasEffects(context)) return true; - } - return ( - this.tag.hasEffects(context) || - this.tag.hasEffectsWhenCalledAtPath(EMPTY_PATH, this.callOptions, context) - ); - } finally { - if (!this.deoptimized) this.applyDeoptimizations(); - } - } - - include(context: InclusionContext, includeChildrenRecursively: IncludeChildren): void { - if (!this.deoptimized) this.applyDeoptimizations(); - this.included = true; - this.tag.include(context, includeChildrenRecursively); - this.quasi.include(context, includeChildrenRecursively); - this.tag.includeArgumentsWhenCalledAtPath(EMPTY_PATH, context, this.callOptions.args); - const returnExpression = this.getReturnExpression(); - if (!returnExpression.included) { - returnExpression.include(context, false); - } + return ( + super.hasEffects(context) || + this.tag.hasEffectsWhenCalledAtPath(EMPTY_PATH, this.callOptions, context) + ); } initialise(): void { this.callOptions = { - args: [UNKNOWN_EXPRESSION, ...this.quasi.expressions], - thisParam: - this.tag instanceof MemberExpression && !this.tag.variable ? this.tag.object : null, + args: NO_ARGS, + thisParam: null, withNew: false }; } @@ -79,37 +52,4 @@ export default class TaggedTemplateExpression extends CallExpressionBase { this.tag.render(code, options, { isCalleeOfRenderedParent: true }); this.quasi.render(code, options); } - - protected applyDeoptimizations(): void { - this.deoptimized = true; - const { thisParam } = this.callOptions; - if (thisParam) { - this.tag.deoptimizeThisOnEventAtPath( - EVENT_CALLED, - EMPTY_PATH, - thisParam, - SHARED_RECURSION_TRACKER - ); - } - for (const argument of this.quasi.expressions) { - // This will make sure all properties of parameters behave as "unknown" - argument.deoptimizePath(UNKNOWN_PATH); - } - this.context.requestTreeshakingPass(); - } - - protected getReturnExpression( - recursionTracker: PathTracker = SHARED_RECURSION_TRACKER - ): ExpressionEntity { - if (this.returnExpression === null) { - this.returnExpression = UNKNOWN_EXPRESSION; - return (this.returnExpression = this.tag.getReturnExpressionWhenCalledAtPath( - EMPTY_PATH, - this.callOptions, - recursionTracker, - this - )); - } - return this.returnExpression; - } } diff --git a/src/ast/nodes/TryStatement.ts b/src/ast/nodes/TryStatement.ts index ecfc3dd0012..c34a5741d34 100644 --- a/src/ast/nodes/TryStatement.ts +++ b/src/ast/nodes/TryStatement.ts @@ -19,8 +19,7 @@ export default class TryStatement extends StatementBase { ((this.context.options.treeshake as NormalizedTreeshakingOptions).tryCatchDeoptimization ? this.block.body.length > 0 : this.block.hasEffects(context)) || - this.finalizer?.hasEffects(context) || - false + (this.finalizer !== null && this.finalizer.hasEffects(context)) ); } @@ -48,6 +47,8 @@ export default class TryStatement extends StatementBase { this.handler.include(context, includeChildrenRecursively); context.brokenFlow = brokenFlow; } - this.finalizer?.include(context, includeChildrenRecursively); + if (this.finalizer !== null) { + this.finalizer.include(context, includeChildrenRecursively); + } } } diff --git a/src/ast/nodes/UnaryExpression.ts b/src/ast/nodes/UnaryExpression.ts index 6a7ef31bb2d..abe63aa8534 100644 --- a/src/ast/nodes/UnaryExpression.ts +++ b/src/ast/nodes/UnaryExpression.ts @@ -33,7 +33,7 @@ export default class UnaryExpression extends NodeBase { ): LiteralValueOrUnknown { if (path.length > 0) return UnknownValue; const argumentValue = this.argument.getLiteralValueAtPath(EMPTY_PATH, recursionTracker, origin); - if (typeof argumentValue === 'symbol') return UnknownValue; + if (argumentValue === UnknownValue) return UnknownValue; return unaryOperators[this.operator](argumentValue); } diff --git a/src/ast/nodes/VariableDeclaration.ts b/src/ast/nodes/VariableDeclaration.ts index 32b5c850a33..5c56d8b7b5e 100644 --- a/src/ast/nodes/VariableDeclaration.ts +++ b/src/ast/nodes/VariableDeclaration.ts @@ -18,7 +18,6 @@ import type Variable from '../variables/Variable'; import Identifier, { type IdentifierWithVariable } from './Identifier'; import * as NodeType from './NodeType'; import type VariableDeclarator from './VariableDeclarator'; -import { InclusionOptions } from './shared/Expression'; import { type IncludeChildren, NodeBase } from './shared/Node'; function areAllDeclarationsIncludedAndNotExported( @@ -53,16 +52,22 @@ export default class VariableDeclaration extends NodeBase { return false; } - include( + include(context: InclusionContext, includeChildrenRecursively: IncludeChildren): void { + this.included = true; + for (const declarator of this.declarations) { + if (includeChildrenRecursively || declarator.shouldBeIncluded(context)) + declarator.include(context, includeChildrenRecursively); + } + } + + includeAsSingleStatement( context: InclusionContext, - includeChildrenRecursively: IncludeChildren, - { asSingleStatement }: InclusionOptions = BLANK + includeChildrenRecursively: IncludeChildren ): void { this.included = true; for (const declarator of this.declarations) { - if (includeChildrenRecursively || declarator.shouldBeIncluded(context)) + if (includeChildrenRecursively || declarator.shouldBeIncluded(context)) { declarator.include(context, includeChildrenRecursively); - if (asSingleStatement) { declarator.id.include(context, includeChildrenRecursively); } } diff --git a/src/ast/nodes/VariableDeclarator.ts b/src/ast/nodes/VariableDeclarator.ts index 7b67948fcd6..a6499ef4b45 100644 --- a/src/ast/nodes/VariableDeclarator.ts +++ b/src/ast/nodes/VariableDeclarator.ts @@ -27,15 +27,17 @@ export default class VariableDeclarator extends NodeBase { this.id.deoptimizePath(path); } - hasEffects(context: HasEffectsContext): boolean | undefined { - const initEffect = this.init?.hasEffects(context); + hasEffects(context: HasEffectsContext): boolean { + const initEffect = this.init !== null && this.init.hasEffects(context); this.id.markDeclarationReached(); return initEffect || this.id.hasEffects(context); } include(context: InclusionContext, includeChildrenRecursively: IncludeChildren): void { this.included = true; - this.init?.include(context, includeChildrenRecursively); + if (this.init) { + this.init.include(context, includeChildrenRecursively); + } this.id.markDeclarationReached(); if (includeChildrenRecursively || this.id.shouldBeIncluded(context)) { this.id.include(context, includeChildrenRecursively); diff --git a/src/ast/nodes/WhileStatement.ts b/src/ast/nodes/WhileStatement.ts index 95f69e2029d..d3bb193d888 100644 --- a/src/ast/nodes/WhileStatement.ts +++ b/src/ast/nodes/WhileStatement.ts @@ -31,7 +31,7 @@ export default class WhileStatement extends StatementBase { this.included = true; this.test.include(context, includeChildrenRecursively); const { brokenFlow } = context; - this.body.include(context, includeChildrenRecursively, { asSingleStatement: true }); + this.body.includeAsSingleStatement(context, includeChildrenRecursively); context.brokenFlow = brokenFlow; } } diff --git a/src/ast/nodes/YieldExpression.ts b/src/ast/nodes/YieldExpression.ts index 8739cac039f..0d9600f1aee 100644 --- a/src/ast/nodes/YieldExpression.ts +++ b/src/ast/nodes/YieldExpression.ts @@ -1,6 +1,7 @@ import type MagicString from 'magic-string'; import type { RenderOptions } from '../../utils/renderHelpers'; import type { HasEffectsContext } from '../ExecutionContext'; +import { UNKNOWN_PATH } from '../utils/PathTracker'; import type * as NodeType from './NodeType'; import { type ExpressionNode, NodeBase } from './shared/Node'; @@ -10,9 +11,11 @@ export default class YieldExpression extends NodeBase { declare type: NodeType.tYieldExpression; protected deoptimized = false; - hasEffects(context: HasEffectsContext): boolean | undefined { + hasEffects(context: HasEffectsContext): boolean { if (!this.deoptimized) this.applyDeoptimizations(); - return !context.ignore.returnYield || this.argument?.hasEffects(context); + return ( + !context.ignore.returnYield || (this.argument !== null && this.argument.hasEffects(context)) + ); } render(code: MagicString, options: RenderOptions): void { @@ -23,4 +26,13 @@ export default class YieldExpression extends NodeBase { } } } + + protected applyDeoptimizations(): void { + this.deoptimized = true; + const { argument } = this; + if (argument) { + argument.deoptimizePath(UNKNOWN_PATH); + this.context.requestTreeshakingPass(); + } + } } diff --git a/src/ast/nodes/shared/CallExpressionBase.ts b/src/ast/nodes/shared/CallExpressionBase.ts deleted file mode 100644 index 24ac09556ac..00000000000 --- a/src/ast/nodes/shared/CallExpressionBase.ts +++ /dev/null @@ -1,147 +0,0 @@ -import type { CallOptions } from '../../CallOptions'; -import type { DeoptimizableEntity } from '../../DeoptimizableEntity'; -import type { HasEffectsContext } from '../../ExecutionContext'; -import { type NodeEvent } from '../../NodeEvents'; -import { type ObjectPath, type PathTracker, UNKNOWN_PATH } from '../../utils/PathTracker'; -import { - type ExpressionEntity, - type LiteralValueOrUnknown, - UNKNOWN_EXPRESSION, - UnknownValue -} from './Expression'; -import { NodeBase } from './Node'; - -export default abstract class CallExpressionBase extends NodeBase implements DeoptimizableEntity { - protected declare callOptions: CallOptions; - protected deoptimized = false; - protected returnExpression: ExpressionEntity | null = null; - private readonly deoptimizableDependentExpressions: DeoptimizableEntity[] = []; - private readonly expressionsToBeDeoptimized = new Set(); - - deoptimizeCache(): void { - if (this.returnExpression !== UNKNOWN_EXPRESSION) { - this.returnExpression = UNKNOWN_EXPRESSION; - for (const expression of this.deoptimizableDependentExpressions) { - expression.deoptimizeCache(); - } - for (const expression of this.expressionsToBeDeoptimized) { - expression.deoptimizePath(UNKNOWN_PATH); - } - } - } - - deoptimizePath(path: ObjectPath): void { - if ( - path.length === 0 || - this.context.deoptimizationTracker.trackEntityAtPathAndGetIfTracked(path, this) - ) { - return; - } - const returnExpression = this.getReturnExpression(); - if (returnExpression !== UNKNOWN_EXPRESSION) { - returnExpression.deoptimizePath(path); - } - } - - deoptimizeThisOnEventAtPath( - event: NodeEvent, - path: ObjectPath, - thisParameter: ExpressionEntity, - recursionTracker: PathTracker - ): void { - const returnExpression = this.getReturnExpression(recursionTracker); - if (returnExpression === UNKNOWN_EXPRESSION) { - thisParameter.deoptimizePath(UNKNOWN_PATH); - } else { - recursionTracker.withTrackedEntityAtPath( - path, - returnExpression, - () => { - this.expressionsToBeDeoptimized.add(thisParameter); - returnExpression.deoptimizeThisOnEventAtPath( - event, - path, - thisParameter, - recursionTracker - ); - }, - undefined - ); - } - } - - getLiteralValueAtPath( - path: ObjectPath, - recursionTracker: PathTracker, - origin: DeoptimizableEntity - ): LiteralValueOrUnknown { - const returnExpression = this.getReturnExpression(recursionTracker); - if (returnExpression === UNKNOWN_EXPRESSION) { - return UnknownValue; - } - return recursionTracker.withTrackedEntityAtPath( - path, - returnExpression, - () => { - this.deoptimizableDependentExpressions.push(origin); - return returnExpression.getLiteralValueAtPath(path, recursionTracker, origin); - }, - UnknownValue - ); - } - - getReturnExpressionWhenCalledAtPath( - path: ObjectPath, - callOptions: CallOptions, - recursionTracker: PathTracker, - origin: DeoptimizableEntity - ): ExpressionEntity { - const returnExpression = this.getReturnExpression(recursionTracker); - if (this.returnExpression === UNKNOWN_EXPRESSION) { - return UNKNOWN_EXPRESSION; - } - return recursionTracker.withTrackedEntityAtPath( - path, - returnExpression, - () => { - this.deoptimizableDependentExpressions.push(origin); - return returnExpression.getReturnExpressionWhenCalledAtPath( - path, - callOptions, - recursionTracker, - origin - ); - }, - UNKNOWN_EXPRESSION - ); - } - - hasEffectsWhenAccessedAtPath(path: ObjectPath, context: HasEffectsContext): boolean | undefined { - return ( - !context.accessed.trackEntityAtPathAndGetIfTracked(path, this) && - this.getReturnExpression().hasEffectsWhenAccessedAtPath(path, context) - ); - } - - hasEffectsWhenAssignedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { - return ( - !context.assigned.trackEntityAtPathAndGetIfTracked(path, this) && - this.getReturnExpression().hasEffectsWhenAssignedAtPath(path, context) - ); - } - - hasEffectsWhenCalledAtPath( - path: ObjectPath, - callOptions: CallOptions, - context: HasEffectsContext - ): boolean { - return ( - !( - callOptions.withNew ? context.instantiated : context.called - ).trackEntityAtPathAndGetIfTracked(path, callOptions, this) && - this.getReturnExpression().hasEffectsWhenCalledAtPath(path, callOptions, context) - ); - } - - protected abstract getReturnExpression(recursionTracker?: PathTracker): ExpressionEntity; -} diff --git a/src/ast/nodes/shared/ClassNode.ts b/src/ast/nodes/shared/ClassNode.ts index bf7d827956b..f8823593b89 100644 --- a/src/ast/nodes/shared/ClassNode.ts +++ b/src/ast/nodes/shared/ClassNode.ts @@ -9,15 +9,13 @@ import { type ObjectPath, type PathTracker, SHARED_RECURSION_TRACKER, - UNKNOWN_PATH, UnknownKey } from '../../utils/PathTracker'; import type ClassBody from '../ClassBody'; import Identifier from '../Identifier'; import type Literal from '../Literal'; import MethodDefinition from '../MethodDefinition'; -import SpreadElement from '../SpreadElement'; -import { type ExpressionEntity, type LiteralValueOrUnknown } from './Expression'; +import { type ExpressionEntity, type LiteralValueOrUnknown, UnknownValue } from './Expression'; import { type ExpressionNode, type IncludeChildren, NodeBase } from './Node'; import { ObjectEntity, type ObjectProperty } from './ObjectEntity'; import { ObjectMember } from './ObjectMember'; @@ -27,7 +25,6 @@ export default class ClassNode extends NodeBase implements DeoptimizableEntity { declare body: ClassBody; declare id: Identifier | null; declare superClass: ExpressionNode | null; - protected deoptimized = false; private declare classConstructor: MethodDefinition | null; private objectEntity: ObjectEntity | null = null; @@ -41,12 +38,6 @@ export default class ClassNode extends NodeBase implements DeoptimizableEntity { deoptimizePath(path: ObjectPath): void { this.getObjectEntity().deoptimizePath(path); - if (path.length === 1 && path[0] === UnknownKey) { - // A reassignment of UNKNOWN_PATH is considered equivalent to having lost track - // which means the constructor needs to be reassigned - this.classConstructor?.deoptimizePath(UNKNOWN_PATH); - this.superClass?.deoptimizePath(UNKNOWN_PATH); - } } deoptimizeThisOnEventAtPath( @@ -85,14 +76,13 @@ export default class ClassNode extends NodeBase implements DeoptimizableEntity { ); } - hasEffects(context: HasEffectsContext): boolean | undefined { - if (!this.deoptimized) this.applyDeoptimizations(); + hasEffects(context: HasEffectsContext): boolean { const initEffect = this.superClass?.hasEffects(context) || this.body.hasEffects(context); this.id?.markDeclarationReached(); return initEffect || super.hasEffects(context); } - hasEffectsWhenAccessedAtPath(path: ObjectPath, context: HasEffectsContext): boolean | undefined { + hasEffectsWhenAccessedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { return this.getObjectEntity().hasEffectsWhenAccessedAtPath(path, context); } @@ -110,8 +100,8 @@ export default class ClassNode extends NodeBase implements DeoptimizableEntity { !callOptions.withNew || (this.classConstructor !== null ? this.classConstructor.hasEffectsWhenCalledAtPath(EMPTY_PATH, callOptions, context) - : this.superClass?.hasEffectsWhenCalledAtPath(path, callOptions, context)) || - false + : this.superClass !== null && + this.superClass.hasEffectsWhenCalledAtPath(path, callOptions, context)) ); } else { return this.getObjectEntity().hasEffectsWhenCalledAtPath(path, callOptions, context); @@ -119,7 +109,6 @@ export default class ClassNode extends NodeBase implements DeoptimizableEntity { } include(context: InclusionContext, includeChildrenRecursively: IncludeChildren): void { - if (!this.deoptimized) this.applyDeoptimizations(); this.included = true; this.superClass?.include(context, includeChildrenRecursively); this.body.include(context, includeChildrenRecursively); @@ -129,22 +118,6 @@ export default class ClassNode extends NodeBase implements DeoptimizableEntity { } } - includeArgumentsWhenCalledAtPath( - path: ObjectPath, - context: InclusionContext, - args: readonly (ExpressionEntity | SpreadElement)[] - ): void { - if (path.length === 0) { - if (this.classConstructor) { - this.classConstructor.includeArgumentsWhenCalledAtPath(path, context, args); - } else { - this.superClass?.includeArgumentsWhenCalledAtPath(path, context, args); - } - } else { - this.getObjectEntity().includeArgumentsWhenCalledAtPath(path, context, args); - } - } - initialise(): void { this.id?.declare('class', this); for (const method of this.body.body) { @@ -156,23 +129,6 @@ export default class ClassNode extends NodeBase implements DeoptimizableEntity { this.classConstructor = null; } - protected applyDeoptimizations(): void { - this.deoptimized = true; - for (const definition of this.body.body) { - if ( - !( - definition.static || - (definition instanceof MethodDefinition && definition.kind === 'constructor') - ) - ) { - // Calls to methods are not tracked, ensure that parameter defaults are - // included and the return value is deoptimized - definition.deoptimizePath(UNKNOWN_PATH); - } - } - this.context.requestTreeshakingPass(); - } - private getObjectEntity(): ObjectEntity { if (this.objectEntity !== null) { return this.objectEntity; @@ -192,7 +148,7 @@ export default class ClassNode extends NodeBase implements DeoptimizableEntity { SHARED_RECURSION_TRACKER, this ); - if (typeof keyValue === 'symbol') { + if (keyValue === UnknownValue) { properties.push({ key: UnknownKey, kind, property: definition }); continue; } else { diff --git a/src/ast/nodes/shared/Expression.ts b/src/ast/nodes/shared/Expression.ts index a484267e67c..60764af727c 100644 --- a/src/ast/nodes/shared/Expression.ts +++ b/src/ast/nodes/shared/Expression.ts @@ -6,19 +6,11 @@ import { NodeEvent } from '../../NodeEvents'; import { ObjectPath, PathTracker, UNKNOWN_PATH } from '../../utils/PathTracker'; import { LiteralValue } from '../Literal'; import SpreadElement from '../SpreadElement'; -import { IncludeChildren } from './Node'; +import { ExpressionNode, IncludeChildren } from './Node'; export const UnknownValue = Symbol('Unknown Value'); -export const UnknownTruthyValue = Symbol('Unknown Truthy Value'); -export type LiteralValueOrUnknown = LiteralValue | typeof UnknownValue | typeof UnknownTruthyValue; - -export interface InclusionOptions { - /** - * Include the id of a declarator even if unused to ensure it is a valid statement. - */ - asSingleStatement?: boolean; -} +export type LiteralValueOrUnknown = LiteralValue | typeof UnknownValue; export class ExpressionEntity implements WritableEntity { included = false; @@ -56,10 +48,7 @@ export class ExpressionEntity implements WritableEntity { return UNKNOWN_EXPRESSION; } - hasEffectsWhenAccessedAtPath( - _path: ObjectPath, - _context: HasEffectsContext - ): boolean | undefined { + hasEffectsWhenAccessedAtPath(_path: ObjectPath, _context: HasEffectsContext): boolean { return true; } @@ -75,27 +64,18 @@ export class ExpressionEntity implements WritableEntity { return true; } - include( - _context: InclusionContext, - _includeChildrenRecursively: IncludeChildren, - _options?: InclusionOptions - ): void { + include(_context: InclusionContext, _includeChildrenRecursively: IncludeChildren): void { this.included = true; } - includeArgumentsWhenCalledAtPath( - _path: ObjectPath, + includeCallArguments( context: InclusionContext, - args: readonly (ExpressionEntity | SpreadElement)[] + args: readonly (ExpressionNode | SpreadElement)[] ): void { for (const arg of args) { arg.include(context, false); } } - - shouldBeIncluded(_context: InclusionContext): boolean | undefined { - return true; - } } export const UNKNOWN_EXPRESSION: ExpressionEntity = diff --git a/src/ast/nodes/shared/FunctionBase.ts b/src/ast/nodes/shared/FunctionBase.ts index b2d4944362d..38b86e8c92a 100644 --- a/src/ast/nodes/shared/FunctionBase.ts +++ b/src/ast/nodes/shared/FunctionBase.ts @@ -8,26 +8,12 @@ import { } from '../../ExecutionContext'; import { NodeEvent } from '../../NodeEvents'; import ReturnValueScope from '../../scopes/ReturnValueScope'; -import { - EMPTY_PATH, - type ObjectPath, - PathTracker, - SHARED_RECURSION_TRACKER, - UNKNOWN_PATH, - UnknownKey -} from '../../utils/PathTracker'; -import LocalVariable from '../../variables/LocalVariable'; -import AssignmentPattern from '../AssignmentPattern'; +import { type ObjectPath, PathTracker, UNKNOWN_PATH, UnknownKey } from '../../utils/PathTracker'; import BlockStatement from '../BlockStatement'; import * as NodeType from '../NodeType'; import RestElement from '../RestElement'; import type SpreadElement from '../SpreadElement'; -import { - type ExpressionEntity, - LiteralValueOrUnknown, - UNKNOWN_EXPRESSION, - UnknownValue -} from './Expression'; +import { type ExpressionEntity, LiteralValueOrUnknown, UNKNOWN_EXPRESSION } from './Expression'; import { type ExpressionNode, type GenericEsTreeNode, @@ -37,28 +23,20 @@ import { import { ObjectEntity } from './ObjectEntity'; import type { PatternNode } from './Pattern'; -export default abstract class FunctionBase extends NodeBase implements DeoptimizableEntity { +export default abstract class FunctionBase extends NodeBase { declare async: boolean; declare body: BlockStatement | ExpressionNode; declare params: readonly PatternNode[]; declare preventChildBlockScope: true; declare scope: ReturnValueScope; - // By default, parameters are included via includeArgumentsWhenCalledAtPath - protected alwaysIncludeParameters = false; protected objectEntity: ObjectEntity | null = null; private deoptimizedReturn = false; - private declare parameterVariables: LocalVariable[][]; - - deoptimizeCache() { - this.alwaysIncludeParameters = true; - } deoptimizePath(path: ObjectPath): void { this.getObjectEntity().deoptimizePath(path); if (path.length === 1 && path[0] === UnknownKey) { // A reassignment of UNKNOWN_PATH is considered equivalent to having lost track // which means the return expression needs to be reassigned - this.alwaysIncludeParameters = true; this.scope.getReturnExpression().deoptimizePath(UNKNOWN_PATH); } } @@ -112,7 +90,7 @@ export default abstract class FunctionBase extends NodeBase implements Deoptimiz return this.scope.getReturnExpression(); } - hasEffectsWhenAccessedAtPath(path: ObjectPath, context: HasEffectsContext): boolean | undefined { + hasEffectsWhenAccessedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { return this.getObjectEntity().hasEffectsWhenAccessedAtPath(path, context); } @@ -157,55 +135,18 @@ export default abstract class FunctionBase extends NodeBase implements Deoptimiz context.brokenFlow = BROKEN_FLOW_NONE; this.body.include(context, includeChildrenRecursively); context.brokenFlow = brokenFlow; - if (includeChildrenRecursively || this.alwaysIncludeParameters) { - for (const param of this.params) { - param.include(context, includeChildrenRecursively); - } - } } - includeArgumentsWhenCalledAtPath( - path: ObjectPath, + includeCallArguments( context: InclusionContext, - args: readonly (ExpressionEntity | SpreadElement)[] + args: readonly (ExpressionNode | SpreadElement)[] ): void { - if (path.length === 0) { - for (let position = 0; position < this.params.length; position++) { - const parameter = this.params[position]; - if (parameter instanceof AssignmentPattern) { - if (parameter.left.shouldBeIncluded(context)) { - parameter.left.include(context, false); - } - const argumentValue = args[position]?.getLiteralValueAtPath( - EMPTY_PATH, - SHARED_RECURSION_TRACKER, - this - ); - // If argumentValue === UnknownTruthyValue, then we do not need to - // include the default - if ( - (argumentValue === undefined || argumentValue === UnknownValue) && - (this.parameterVariables[position].some(variable => variable.included) || - parameter.right.shouldBeIncluded(context)) - ) { - parameter.right.include(context, false); - } - } else if (parameter.shouldBeIncluded(context)) { - parameter.include(context, false); - } - } - this.scope.includeCallArguments(context, args); - } else { - this.getObjectEntity().includeArgumentsWhenCalledAtPath(path, context, args); - } + this.scope.includeCallArguments(context, args); } initialise(): void { - this.parameterVariables = this.params.map(param => - param.declare('parameter', UNKNOWN_EXPRESSION) - ); this.scope.addParameterVariables( - this.parameterVariables, + this.params.map(param => param.declare('parameter', UNKNOWN_EXPRESSION)), this.params[this.params.length - 1] instanceof RestElement ); if (this.body instanceof BlockStatement) { diff --git a/src/ast/nodes/shared/FunctionNode.ts b/src/ast/nodes/shared/FunctionNode.ts index c54360949eb..c11a007cbc2 100644 --- a/src/ast/nodes/shared/FunctionNode.ts +++ b/src/ast/nodes/shared/FunctionNode.ts @@ -4,7 +4,7 @@ import { EVENT_CALLED, type NodeEvent } from '../../NodeEvents'; import FunctionScope from '../../scopes/FunctionScope'; import { type ObjectPath, PathTracker } from '../../utils/PathTracker'; import BlockStatement from '../BlockStatement'; -import { type IdentifierWithVariable } from '../Identifier'; +import Identifier, { type IdentifierWithVariable } from '../Identifier'; import { type ExpressionEntity, UNKNOWN_EXPRESSION } from './Expression'; import FunctionBase from './FunctionBase'; import { type IncludeChildren } from './Node'; @@ -37,8 +37,8 @@ export default class FunctionNode extends FunctionBase { } } - hasEffects(): boolean | undefined { - return this.id?.hasEffects(); + hasEffects(): boolean { + return this.id !== null && this.id.hasEffects(); } hasEffectsWhenCalledAtPath( @@ -73,12 +73,14 @@ export default class FunctionNode extends FunctionBase { } include(context: InclusionContext, includeChildrenRecursively: IncludeChildren): void { - // This ensures that super.include will also include all parameters - if (this.scope.argumentsVariable.included) { - this.alwaysIncludeParameters = true; - } - this.id?.include(); super.include(context, 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(context, includeChildrenRecursively); + } + } } initialise(): void { diff --git a/src/ast/nodes/shared/MethodBase.ts b/src/ast/nodes/shared/MethodBase.ts index 93d756d094c..8ad74d66d13 100644 --- a/src/ast/nodes/shared/MethodBase.ts +++ b/src/ast/nodes/shared/MethodBase.ts @@ -1,6 +1,6 @@ import { type CallOptions, NO_ARGS } from '../../CallOptions'; import type { DeoptimizableEntity } from '../../DeoptimizableEntity'; -import type { HasEffectsContext, InclusionContext } from '../../ExecutionContext'; +import type { HasEffectsContext } from '../../ExecutionContext'; import { EVENT_ACCESSED, EVENT_ASSIGNED, EVENT_CALLED, type NodeEvent } from '../../NodeEvents'; import { EMPTY_PATH, @@ -9,7 +9,6 @@ import { SHARED_RECURSION_TRACKER } from '../../utils/PathTracker'; import type PrivateIdentifier from '../PrivateIdentifier'; -import SpreadElement from '../SpreadElement'; import { type ExpressionEntity, type LiteralValueOrUnknown, @@ -91,11 +90,11 @@ export default class MethodBase extends NodeBase implements DeoptimizableEntity ); } - hasEffects(context: HasEffectsContext): boolean | undefined { + hasEffects(context: HasEffectsContext): boolean { return this.key.hasEffects(context); } - hasEffectsWhenAccessedAtPath(path: ObjectPath, context: HasEffectsContext): boolean | undefined { + hasEffectsWhenAccessedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { if (this.kind === 'get' && path.length === 0) { return this.value.hasEffectsWhenCalledAtPath(EMPTY_PATH, this.accessorCallOptions, context); } @@ -117,14 +116,6 @@ export default class MethodBase extends NodeBase implements DeoptimizableEntity return this.getAccessedValue().hasEffectsWhenCalledAtPath(path, callOptions, context); } - includeArgumentsWhenCalledAtPath( - path: ObjectPath, - context: InclusionContext, - args: readonly (ExpressionEntity | SpreadElement)[] - ): void { - this.getAccessedValue().includeArgumentsWhenCalledAtPath(path, context, args); - } - protected getAccessedValue(): ExpressionEntity { if (this.accessedValue === null) { if (this.kind === 'get') { diff --git a/src/ast/nodes/shared/MethodTypes.ts b/src/ast/nodes/shared/MethodTypes.ts index 1d10526fdf9..78c061d5d18 100644 --- a/src/ast/nodes/shared/MethodTypes.ts +++ b/src/ast/nodes/shared/MethodTypes.ts @@ -9,6 +9,7 @@ import { } from '../../values'; import type SpreadElement from '../SpreadElement'; import { ExpressionEntity, UNKNOWN_EXPRESSION } from './Expression'; +import type { ExpressionNode } from './Node'; type MethodDescription = { callsArgs: number[] | null; @@ -95,10 +96,9 @@ export class Method extends ExpressionEntity { return false; } - includeArgumentsWhenCalledAtPath( - _path: ObjectPath, + includeCallArguments( context: InclusionContext, - args: readonly (ExpressionEntity | SpreadElement)[] + args: readonly (ExpressionNode | SpreadElement)[] ): void { for (const arg of args) { arg.include(context, false); diff --git a/src/ast/nodes/shared/Node.ts b/src/ast/nodes/shared/Node.ts index 7746099dfd9..656c4ef1c1f 100644 --- a/src/ast/nodes/shared/Node.ts +++ b/src/ast/nodes/shared/Node.ts @@ -12,10 +12,9 @@ import { } from '../../ExecutionContext'; import { getAndCreateKeys, keys } from '../../keys'; import type ChildScope from '../../scopes/ChildScope'; -import { UNKNOWN_PATH } from '../../utils/PathTracker'; import type Variable from '../../variables/Variable'; import * as NodeType from '../NodeType'; -import { ExpressionEntity, InclusionOptions } from './Expression'; +import { ExpressionEntity } from './Expression'; export interface GenericEsTreeNode extends acorn.Node { [key: string]: any; @@ -54,17 +53,22 @@ 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: HasEffectsContext): boolean | undefined; + 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( + include(context: InclusionContext, includeChildrenRecursively: IncludeChildren): void; + + /** + * Alternative version of include to override the default behaviour of + * declarations to not include the id by default if the declarator has an effect. + */ + includeAsSingleStatement( context: InclusionContext, - includeChildrenRecursively: IncludeChildren, - options?: InclusionOptions + includeChildrenRecursively: IncludeChildren ): void; render(code: MagicString, options: RenderOptions, nodeRenderOptions?: NodeRenderOptions): void; @@ -75,7 +79,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: InclusionContext): boolean | undefined; + shouldBeIncluded(context: InclusionContext): boolean; } export type StatementNode = Node; @@ -130,7 +134,7 @@ export class NodeBase extends ExpressionEntity implements ExpressionNode { if (value === null) continue; if (Array.isArray(value)) { for (const child of value) { - child?.bind(); + if (child !== null) child.bind(); } } else { value.bind(); @@ -145,25 +149,21 @@ export class NodeBase extends ExpressionEntity implements ExpressionNode { this.scope = parentScope; } - hasEffects(context: HasEffectsContext): boolean | undefined { + hasEffects(context: HasEffectsContext): boolean { if (this.deoptimized === false) this.applyDeoptimizations(); for (const key of this.keys) { const value = (this as GenericEsTreeNode)[key]; if (value === null) continue; if (Array.isArray(value)) { for (const child of value) { - if (child?.hasEffects(context)) return true; + if (child !== null && child.hasEffects(context)) return true; } } else if (value.hasEffects(context)) return true; } return false; } - include( - context: InclusionContext, - includeChildrenRecursively: IncludeChildren, - _options?: InclusionOptions - ): void { + include(context: InclusionContext, includeChildrenRecursively: IncludeChildren): void { if (this.deoptimized === false) this.applyDeoptimizations(); this.included = true; for (const key of this.keys) { @@ -171,7 +171,7 @@ export class NodeBase extends ExpressionEntity implements ExpressionNode { if (value === null) continue; if (Array.isArray(value)) { for (const child of value) { - child?.include(context, includeChildrenRecursively); + if (child !== null) child.include(context, includeChildrenRecursively); } } else { value.include(context, includeChildrenRecursively); @@ -179,6 +179,13 @@ export class NodeBase extends ExpressionEntity implements ExpressionNode { } } + includeAsSingleStatement( + context: InclusionContext, + includeChildrenRecursively: IncludeChildren + ): void { + this.include(context, includeChildrenRecursively); + } + /** * Override to perform special initialisation steps after the scope is initialised */ @@ -228,7 +235,7 @@ export class NodeBase extends ExpressionEntity implements ExpressionNode { if (value === null) continue; if (Array.isArray(value)) { for (const child of value) { - child?.render(code, options); + if (child !== null) child.render(code, options); } } else { value.render(code, options); @@ -236,39 +243,18 @@ export class NodeBase extends ExpressionEntity implements ExpressionNode { } } - shouldBeIncluded(context: InclusionContext): boolean | undefined { + shouldBeIncluded(context: InclusionContext): boolean { return this.included || (!context.brokenFlow && this.hasEffects(createHasEffectsContext())); } - /** - * Just deoptimize everything by default so that when e.g. we do not track - * something properly, it is deoptimized. - * @protected - */ - protected applyDeoptimizations(): void { - this.deoptimized = true; - for (const key of this.keys) { - const value = (this as GenericEsTreeNode)[key]; - if (value === null) continue; - if (Array.isArray(value)) { - for (const child of value) { - child?.deoptimizePath(UNKNOWN_PATH); - } - } else { - value.deoptimizePath(UNKNOWN_PATH); - } - } - this.context.requestTreeshakingPass(); - } + protected applyDeoptimizations(): void {} } export { NodeBase as StatementBase }; -export function locateNode(node: Node): Location & { file: string } { - const location = locate(node.context.code, node.start, { offsetLine: 1 }) as Location & { - file: string; - }; - location.file = node.context.fileName; +export function locateNode(node: Node): Location { + const location = locate(node.context.code, node.start, { offsetLine: 1 }); + (location as any).file = node.context.fileName; location.toString = () => JSON.stringify(location); return location; diff --git a/src/ast/nodes/shared/ObjectEntity.ts b/src/ast/nodes/shared/ObjectEntity.ts index 475a7ad936b..1aff8dcd598 100644 --- a/src/ast/nodes/shared/ObjectEntity.ts +++ b/src/ast/nodes/shared/ObjectEntity.ts @@ -1,6 +1,6 @@ import { CallOptions } from '../../CallOptions'; import { DeoptimizableEntity } from '../../DeoptimizableEntity'; -import { HasEffectsContext, InclusionContext } from '../../ExecutionContext'; +import { HasEffectsContext } from '../../ExecutionContext'; import { EVENT_ACCESSED, EVENT_CALLED, NodeEvent } from '../../NodeEvents'; import { ObjectPath, @@ -12,12 +12,10 @@ import { UnknownKey, UnknownNonAccessorKey } from '../../utils/PathTracker'; -import SpreadElement from '../SpreadElement'; import { ExpressionEntity, LiteralValueOrUnknown, UNKNOWN_EXPRESSION, - UnknownTruthyValue, UnknownValue } from './Expression'; @@ -227,7 +225,7 @@ export class ObjectEntity extends ExpressionEntity { origin: DeoptimizableEntity ): LiteralValueOrUnknown { if (path.length === 0) { - return UnknownTruthyValue; + return UnknownValue; } const key = path[0]; const expressionAtPath = this.getMemberExpressionAndTrackDeopt(key, origin); @@ -273,7 +271,7 @@ export class ObjectEntity extends ExpressionEntity { return UNKNOWN_EXPRESSION; } - hasEffectsWhenAccessedAtPath(path: ObjectPath, context: HasEffectsContext): boolean | undefined { + hasEffectsWhenAccessedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { const [key, ...subPath] = path; if (path.length > 1) { if (typeof key !== 'string') { @@ -380,21 +378,6 @@ export class ObjectEntity extends ExpressionEntity { return true; } - includeArgumentsWhenCalledAtPath( - path: ObjectPath, - context: InclusionContext, - args: readonly (ExpressionEntity | SpreadElement)[] - ) { - const key = path[0]; - const expressionAtPath = this.getMemberExpression(key); - if (expressionAtPath) { - return expressionAtPath.includeArgumentsWhenCalledAtPath(path.slice(1), context, args); - } - if (this.prototypeExpression) { - return this.prototypeExpression.includeArgumentsWhenCalledAtPath(path, context, args); - } - } - private buildPropertyMaps(properties: readonly ObjectProperty[]): void { const { allProperties, diff --git a/src/ast/nodes/shared/ObjectMember.ts b/src/ast/nodes/shared/ObjectMember.ts index 055192ba8b5..ac6871eb369 100644 --- a/src/ast/nodes/shared/ObjectMember.ts +++ b/src/ast/nodes/shared/ObjectMember.ts @@ -50,7 +50,8 @@ export class ObjectMember extends ExpressionEntity { ); } - hasEffectsWhenAccessedAtPath(path: ObjectPath, context: HasEffectsContext): boolean | undefined { + hasEffectsWhenAccessedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { + if (path.length === 0) return false; return this.object.hasEffectsWhenAccessedAtPath([this.key, ...path], context); } diff --git a/src/ast/scopes/FunctionScope.ts b/src/ast/scopes/FunctionScope.ts index fcc2a872bd6..79daa9ccb96 100644 --- a/src/ast/scopes/FunctionScope.ts +++ b/src/ast/scopes/FunctionScope.ts @@ -1,7 +1,7 @@ import type { AstContext } from '../../Module'; import type { InclusionContext } from '../ExecutionContext'; import type SpreadElement from '../nodes/SpreadElement'; -import { ExpressionEntity } from '../nodes/shared/Expression'; +import type { ExpressionNode } from '../nodes/shared/Node'; import ArgumentsVariable from '../variables/ArgumentsVariable'; import ThisVariable from '../variables/ThisVariable'; import type ChildScope from './ChildScope'; @@ -23,7 +23,7 @@ export default class FunctionScope extends ReturnValueScope { includeCallArguments( context: InclusionContext, - args: readonly (ExpressionEntity | SpreadElement)[] + args: readonly (ExpressionNode | SpreadElement)[] ): void { super.includeCallArguments(context, args); if (this.argumentsVariable.included) { diff --git a/src/ast/scopes/ParameterScope.ts b/src/ast/scopes/ParameterScope.ts index 4d69418aaae..b31d50238aa 100644 --- a/src/ast/scopes/ParameterScope.ts +++ b/src/ast/scopes/ParameterScope.ts @@ -2,7 +2,8 @@ import type { AstContext } from '../../Module'; import type { InclusionContext } from '../ExecutionContext'; import type Identifier from '../nodes/Identifier'; import SpreadElement from '../nodes/SpreadElement'; -import { ExpressionEntity, UNKNOWN_EXPRESSION } from '../nodes/shared/Expression'; +import { UNKNOWN_EXPRESSION } from '../nodes/shared/Expression'; +import type { ExpressionNode } from '../nodes/shared/Node'; import LocalVariable from '../variables/LocalVariable'; import ChildScope from './ChildScope'; import type Scope from './Scope'; @@ -48,7 +49,7 @@ export default class ParameterScope extends ChildScope { includeCallArguments( context: InclusionContext, - args: readonly (ExpressionEntity | SpreadElement)[] + args: readonly (ExpressionNode | SpreadElement)[] ): void { let calledFromTryStatement = false; let argIncluded = false; diff --git a/src/ast/values.ts b/src/ast/values.ts index 1cf037140db..ceda252a22b 100644 --- a/src/ast/values.ts +++ b/src/ast/values.ts @@ -1,7 +1,7 @@ import { type CallOptions, NO_ARGS } from './CallOptions'; import type { HasEffectsContext } from './ExecutionContext'; import type { LiteralValue } from './nodes/Literal'; -import { ExpressionEntity, UNKNOWN_EXPRESSION } from './nodes/shared/Expression'; +import { ExpressionEntity, UNKNOWN_EXPRESSION, UnknownValue } from './nodes/shared/Expression'; import { EMPTY_PATH, type ObjectPath, @@ -145,9 +145,9 @@ const stringReplace: RawMemberDescription = { const arg1 = callOptions.args[1]; return ( callOptions.args.length < 2 || - (typeof arg1.getLiteralValueAtPath(EMPTY_PATH, SHARED_RECURSION_TRACKER, { + (arg1.getLiteralValueAtPath(EMPTY_PATH, SHARED_RECURSION_TRACKER, { deoptimizeCache() {} - }) === 'symbol' && + }) === UnknownValue && arg1.hasEffectsWhenCalledAtPath( EMPTY_PATH, { diff --git a/src/ast/variables/GlobalVariable.ts b/src/ast/variables/GlobalVariable.ts index e79ecd421e5..7d6928c5f9c 100644 --- a/src/ast/variables/GlobalVariable.ts +++ b/src/ast/variables/GlobalVariable.ts @@ -1,14 +1,7 @@ import { CallOptions } from '../CallOptions'; -import { DeoptimizableEntity } from '../DeoptimizableEntity'; import { HasEffectsContext } from '../ExecutionContext'; -import { - LiteralValueOrUnknown, - UnknownTruthyValue, - UnknownValue -} from '../nodes/shared/Expression'; import { getGlobalAtPath } from '../nodes/shared/knownGlobals'; import type { ObjectPath } from '../utils/PathTracker'; -import { PathTracker } from '../utils/PathTracker'; import Variable from './Variable'; export default class GlobalVariable extends Variable { @@ -16,20 +9,12 @@ export default class GlobalVariable extends Variable { // been reassigned isReassigned = true; - getLiteralValueAtPath( - path: ObjectPath, - _recursionTracker: PathTracker, - _origin: DeoptimizableEntity - ): LiteralValueOrUnknown { - return getGlobalAtPath([this.name, ...path]) ? UnknownTruthyValue : UnknownValue; - } - hasEffectsWhenAccessedAtPath(path: ObjectPath): boolean { if (path.length === 0) { // Technically, "undefined" is a global variable of sorts - return this.name !== 'undefined' && !getGlobalAtPath([this.name]); + return this.name !== 'undefined' && getGlobalAtPath([this.name]) === null; } - return !getGlobalAtPath([this.name, ...path].slice(0, -1)); + return getGlobalAtPath([this.name, ...path].slice(0, -1)) === null; } hasEffectsWhenCalledAtPath( @@ -38,6 +23,6 @@ export default class GlobalVariable extends Variable { context: HasEffectsContext ): boolean { const globalAtPath = getGlobalAtPath([this.name, ...path]); - return !globalAtPath || globalAtPath.hasEffectsWhenCalled(callOptions, context); + return globalAtPath === null || globalAtPath.hasEffectsWhenCalled(callOptions, context); } } diff --git a/src/ast/variables/LocalVariable.ts b/src/ast/variables/LocalVariable.ts index 11a6b70d154..a90613a2825 100644 --- a/src/ast/variables/LocalVariable.ts +++ b/src/ast/variables/LocalVariable.ts @@ -1,4 +1,5 @@ -import Module, { AstContext } from '../../Module'; +import type Module from '../../Module'; +import type { AstContext } from '../../Module'; import type { CallOptions } from '../CallOptions'; import type { DeoptimizableEntity } from '../DeoptimizableEntity'; import { createInclusionContext, HasEffectsContext, InclusionContext } from '../ExecutionContext'; @@ -13,7 +14,7 @@ import { UNKNOWN_EXPRESSION, UnknownValue } from '../nodes/shared/Expression'; -import type { Node } from '../nodes/shared/Node'; +import type { ExpressionNode, Node } from '../nodes/shared/Node'; import { type ObjectPath, type PathTracker, UNKNOWN_PATH } from '../utils/PathTracker'; import Variable from './Variable'; @@ -189,10 +190,9 @@ export default class LocalVariable extends Variable { } } - includeArgumentsWhenCalledAtPath( - path: ObjectPath, + includeCallArguments( context: InclusionContext, - args: readonly (ExpressionEntity | SpreadElement)[] + args: readonly (ExpressionNode | SpreadElement)[] ): void { if (this.isReassigned || (this.init && context.includedCallArguments.has(this.init))) { for (const arg of args) { @@ -200,7 +200,7 @@ export default class LocalVariable extends Variable { } } else if (this.init) { context.includedCallArguments.add(this.init); - this.init.includeArgumentsWhenCalledAtPath(path, context, args); + this.init.includeCallArguments(context, args); context.includedCallArguments.delete(this.init); } } diff --git a/test/form/samples/side-effect-e/_expected/amd.js b/test/form/samples/side-effect-e/_expected/amd.js new file mode 100644 index 00000000000..9f4ef975e84 --- /dev/null +++ b/test/form/samples/side-effect-e/_expected/amd.js @@ -0,0 +1,20 @@ +define((function () { 'use strict'; + + function foo () { + var Object = { + keys: function () { + console.log( 'side-effect' ); + } + }; + + var obj = { foo: 1, bar: 2 }; + Object.keys( obj ); + } + + foo(); + + var main = 42; + + return main; + +})); diff --git a/test/form/samples/side-effect-e/_expected/cjs.js b/test/form/samples/side-effect-e/_expected/cjs.js new file mode 100644 index 00000000000..249b9936f03 --- /dev/null +++ b/test/form/samples/side-effect-e/_expected/cjs.js @@ -0,0 +1,18 @@ +'use strict'; + +function foo () { + var Object = { + keys: function () { + console.log( 'side-effect' ); + } + }; + + var obj = { foo: 1, bar: 2 }; + Object.keys( obj ); +} + +foo(); + +var main = 42; + +module.exports = main; diff --git a/test/form/samples/side-effect-e/_expected.js b/test/form/samples/side-effect-e/_expected/es.js similarity index 52% rename from test/form/samples/side-effect-e/_expected.js rename to test/form/samples/side-effect-e/_expected/es.js index 08730030302..3067a16c289 100644 --- a/test/form/samples/side-effect-e/_expected.js +++ b/test/form/samples/side-effect-e/_expected/es.js @@ -4,7 +4,13 @@ function foo () { console.log( 'side-effect' ); } }; - Object.keys(); + + var obj = { foo: 1, bar: 2 }; + Object.keys( obj ); } foo(); + +var main = 42; + +export { main as default }; diff --git a/test/form/samples/side-effect-e/_expected/iife.js b/test/form/samples/side-effect-e/_expected/iife.js new file mode 100644 index 00000000000..de3c6013977 --- /dev/null +++ b/test/form/samples/side-effect-e/_expected/iife.js @@ -0,0 +1,21 @@ +var myBundle = (function () { + 'use strict'; + + function foo () { + var Object = { + keys: function () { + console.log( 'side-effect' ); + } + }; + + var obj = { foo: 1, bar: 2 }; + Object.keys( obj ); + } + + foo(); + + var main = 42; + + return main; + +})(); diff --git a/test/form/samples/side-effect-e/_expected/system.js b/test/form/samples/side-effect-e/_expected/system.js new file mode 100644 index 00000000000..baef598d676 --- /dev/null +++ b/test/form/samples/side-effect-e/_expected/system.js @@ -0,0 +1,23 @@ +System.register('myBundle', [], (function (exports) { + 'use strict'; + return { + execute: (function () { + + function foo () { + var Object = { + keys: function () { + console.log( 'side-effect' ); + } + }; + + var obj = { foo: 1, bar: 2 }; + Object.keys( obj ); + } + + foo(); + + var main = exports('default', 42); + + }) + }; +})); diff --git a/test/form/samples/side-effect-e/_expected/umd.js b/test/form/samples/side-effect-e/_expected/umd.js new file mode 100644 index 00000000000..0ef9db79ea7 --- /dev/null +++ b/test/form/samples/side-effect-e/_expected/umd.js @@ -0,0 +1,24 @@ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : + typeof define === 'function' && define.amd ? define(factory) : + (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.myBundle = factory()); +})(this, (function () { 'use strict'; + + function foo () { + var Object = { + keys: function () { + console.log( 'side-effect' ); + } + }; + + var obj = { foo: 1, bar: 2 }; + Object.keys( obj ); + } + + foo(); + + var main = 42; + + return main; + +})); diff --git a/test/form/samples/side-effect-e/main.js b/test/form/samples/side-effect-e/main.js index 1ac85f8de9e..b96dfdf5d71 100644 --- a/test/form/samples/side-effect-e/main.js +++ b/test/form/samples/side-effect-e/main.js @@ -10,3 +10,5 @@ function foo () { } foo(); + +export default 42; diff --git a/test/form/samples/super-classes/super-class-prototype-access/_expected.js b/test/form/samples/super-classes/super-class-prototype-access/_expected.js index b6203e675ca..936fab7c5a4 100644 --- a/test/form/samples/super-classes/super-class-prototype-access/_expected.js +++ b/test/form/samples/super-classes/super-class-prototype-access/_expected.js @@ -4,4 +4,5 @@ class SuperAccess { } class Access extends SuperAccess {} Access.prototype.doesNoExist.throws; +Access.prototype.method.doesNoExist.throws; Access.prototype.prop.throws; diff --git a/test/form/samples/super-classes/super-class-prototype-access/main.js b/test/form/samples/super-classes/super-class-prototype-access/main.js index 240e4365a28..fcf38d4ead6 100644 --- a/test/form/samples/super-classes/super-class-prototype-access/main.js +++ b/test/form/samples/super-classes/super-class-prototype-access/main.js @@ -6,4 +6,6 @@ class Access extends SuperAccess {} Access.prototype.doesNoExist; Access.prototype.doesNoExist.throws; +Access.prototype.method.doesNoExist; +Access.prototype.method.doesNoExist.throws; Access.prototype.prop.throws; diff --git a/test/form/samples/super-classes/super-class-prototype-assignment/main.js b/test/form/samples/super-classes/super-class-prototype-assignment/main.js index 06ef7c3d692..d47b557daf5 100644 --- a/test/form/samples/super-classes/super-class-prototype-assignment/main.js +++ b/test/form/samples/super-classes/super-class-prototype-assignment/main.js @@ -1,8 +1,10 @@ class SuperRemovedAssign { + method() {} set prop(v) {} } class RemovedAssign extends SuperRemovedAssign {} RemovedAssign.prototype.doesNotExist = 1; +RemovedAssign.prototype.method.doesNotExist = 1; RemovedAssign.prototype.prop = 1; class SuperUsedAssign { diff --git a/test/form/samples/super-classes/super-class-prototype-calls/_expected.js b/test/form/samples/super-classes/super-class-prototype-calls/_expected.js index 9f4197f314d..478810e2b22 100644 --- a/test/form/samples/super-classes/super-class-prototype-calls/_expected.js +++ b/test/form/samples/super-classes/super-class-prototype-calls/_expected.js @@ -4,7 +4,9 @@ class SuperValues { effect(used) { console.log('effect', used); }, - + isTrue() { + return true; + } }; } effect(used) { @@ -16,5 +18,6 @@ class SuperValues { } class Values extends SuperValues {} console.log('retained'); +console.log('retained'); Values.prototype.effect(); Values.prototype.prop.effect(); diff --git a/test/form/samples/super-classes/super-class-prototype-calls/main.js b/test/form/samples/super-classes/super-class-prototype-calls/main.js index 2a66d55579a..43c62658ce6 100644 --- a/test/form/samples/super-classes/super-class-prototype-calls/main.js +++ b/test/form/samples/super-classes/super-class-prototype-calls/main.js @@ -4,7 +4,9 @@ class SuperValues { effect(used) { console.log('effect', used); }, - + isTrue() { + return true; + } }; } effect(used) { @@ -17,5 +19,7 @@ class SuperValues { class Values extends SuperValues {} if (Values.prototype.isTrue()) console.log('retained'); else console.log('removed'); +if (Values.prototype.prop.isTrue()) console.log('retained'); +else console.log('removed'); Values.prototype.effect(); Values.prototype.prop.effect(); diff --git a/test/form/samples/super-classes/super-class-prototype-values/_expected.js b/test/form/samples/super-classes/super-class-prototype-values/_expected.js index 4002b3a03aa..60cdfd88e49 100644 --- a/test/form/samples/super-classes/super-class-prototype-values/_expected.js +++ b/test/form/samples/super-classes/super-class-prototype-values/_expected.js @@ -1,4 +1,5 @@ console.log('retained'); +console.log('retained'); const prop = { isTrue: true }; class SuperDeopt { diff --git a/test/form/samples/super-classes/super-class-prototype-values/main.js b/test/form/samples/super-classes/super-class-prototype-values/main.js index 12003a915de..1fce6f66d3d 100644 --- a/test/form/samples/super-classes/super-class-prototype-values/main.js +++ b/test/form/samples/super-classes/super-class-prototype-values/main.js @@ -9,6 +9,8 @@ class SuperValues { class Values extends SuperValues {} if (Values.prototype.isTrue) console.log('retained'); else console.log('removed'); +if (Values.prototype.prop.isTrue) console.log('retained'); +else console.log('removed'); const prop = { isTrue: true }; class SuperDeopt { diff --git a/test/form/samples/this-in-class-body/_expected.js b/test/form/samples/this-in-class-body/_expected.js index ffc2e565be4..ff7c88e7839 100644 --- a/test/form/samples/this-in-class-body/_expected.js +++ b/test/form/samples/this-in-class-body/_expected.js @@ -1,8 +1,8 @@ class Used { - static flag = false; + static flag = false static mutate = () => { this.flag = true; - }; + } } Used.mutate(); @@ -10,23 +10,23 @@ if (Used.flag) console.log('retained'); else console.log('unimportant'); class InstanceMutation { - static flag = false; - flag = false; + static flag = false + flag = false mutate = () => { this.flag = true; - }; + } } -new InstanceMutation().mutate(); +(new InstanceMutation).mutate(); console.log('retained'); class UsedSuper { - static flag = false; + static flag = false } -class UsedWithSuper extends UsedSuper { +class UsedWithSuper extends UsedSuper{ static mutate = () => { super.flag = true; - }; + } } UsedWithSuper.mutate(); diff --git a/test/form/samples/this-in-class-body/main.js b/test/form/samples/this-in-class-body/main.js index a02119ea207..6fa4fc5ed65 100644 --- a/test/form/samples/this-in-class-body/main.js +++ b/test/form/samples/this-in-class-body/main.js @@ -1,16 +1,16 @@ class Unused { - static flag = false; + static flag = false static mutate = () => { this.flag = true; - }; + } } Unused.mutate(); class Used { - static flag = false; + static flag = false static mutate = () => { this.flag = true; - }; + } } Used.mutate(); @@ -18,24 +18,24 @@ if (Used.flag) console.log('retained'); else console.log('unimportant'); class InstanceMutation { - static flag = false; - flag = false; + static flag = false + flag = false mutate = () => { this.flag = true; - }; + } } -new InstanceMutation().mutate(); +(new InstanceMutation).mutate(); if (InstanceMutation.flag) console.log('removed'); else console.log('retained'); class UsedSuper { - static flag = false; + static flag = false } -class UsedWithSuper extends UsedSuper { +class UsedWithSuper extends UsedSuper{ static mutate = () => { super.flag = true; - }; + } } UsedWithSuper.mutate(); diff --git a/test/form/samples/tree-shake-default-parameters/array-elements/_config.js b/test/form/samples/tree-shake-default-parameters/array-elements/_config.js deleted file mode 100644 index 514bd9b092f..00000000000 --- a/test/form/samples/tree-shake-default-parameters/array-elements/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - description: 'supports tree-shaking for unused default parameter values on arrays' -}; diff --git a/test/form/samples/tree-shake-default-parameters/array-elements/_expected.js b/test/form/samples/tree-shake-default-parameters/array-elements/_expected.js deleted file mode 100644 index 6bf23b83290..00000000000 --- a/test/form/samples/tree-shake-default-parameters/array-elements/_expected.js +++ /dev/null @@ -1,16 +0,0 @@ -var isUndefined; - -const test2 = [ - (a = 'retained') => console.log(a) -]; - -const test = [ - (a = 'retained', b = 'retained', c, d) => console.log(a, b, c), - ...test2, - (a = 'retained') => console.log(a) -]; - -test[0](isUndefined, 'b', 'c'); -test[0]('a', globalThis.unknown, 'c'); -test[1](); -test[2](); diff --git a/test/form/samples/tree-shake-default-parameters/array-elements/main.js b/test/form/samples/tree-shake-default-parameters/array-elements/main.js deleted file mode 100644 index 668a44988e5..00000000000 --- a/test/form/samples/tree-shake-default-parameters/array-elements/main.js +++ /dev/null @@ -1,16 +0,0 @@ -var isUndefined; - -const test2 = [ - (a = 'retained') => console.log(a) -]; - -const test = [ - (a = 'retained', b = 'retained', c = 'removed', d = 'removed') => console.log(a, b, c), - ...test2, - (a = 'retained') => console.log(a) -]; - -test[0](isUndefined, 'b', 'c'); -test[0]('a', globalThis.unknown, 'c'); -test[1](); -test[2](); diff --git a/test/form/samples/tree-shake-default-parameters/class-methods/_config.js b/test/form/samples/tree-shake-default-parameters/class-methods/_config.js deleted file mode 100644 index 39978209649..00000000000 --- a/test/form/samples/tree-shake-default-parameters/class-methods/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - description: 'supports tree-shaking for unused default parameter values on classes' -}; diff --git a/test/form/samples/tree-shake-default-parameters/class-methods/_expected.js b/test/form/samples/tree-shake-default-parameters/class-methods/_expected.js deleted file mode 100644 index 269a1d5ae08..00000000000 --- a/test/form/samples/tree-shake-default-parameters/class-methods/_expected.js +++ /dev/null @@ -1,29 +0,0 @@ -var isUndefined; - -class Test { - constructor(a = 'retained', b = 'retained', c, d) { - console.log(a, b, c); - } - - method(a = 'retained') { - console.log(a); - } - - prop = (a = 'retained') => console.log(a); - - static staticMethod(a = 'retained', b = 'retained', c, d) { - console.log(a, b, c); - } - - static staticProp = (a = 'retained', b = 'retained', c, d) => - console.log(a, b, c); -} - -new Test(isUndefined, 'b', 'c'); -new Test('a', globalThis.unknown, 'c').method(); - -Test.staticMethod(isUndefined, 'b', 'c'); -Test.staticMethod('a', globalThis.unknown, 'c'); - -Test.staticProp(isUndefined, 'b', 'c'); -Test.staticProp('a', globalThis.unknown, 'c'); diff --git a/test/form/samples/tree-shake-default-parameters/class-methods/main.js b/test/form/samples/tree-shake-default-parameters/class-methods/main.js deleted file mode 100644 index 64101ab49df..00000000000 --- a/test/form/samples/tree-shake-default-parameters/class-methods/main.js +++ /dev/null @@ -1,29 +0,0 @@ -var isUndefined; - -class Test { - constructor(a = 'retained', b = 'retained', c = 'removed', d = 'removed') { - console.log(a, b, c); - } - - method(a = 'retained') { - console.log(a); - } - - prop = (a = 'retained') => console.log(a); - - static staticMethod(a = 'retained', b = 'retained', c = 'removed', d = 'removed') { - console.log(a, b, c); - } - - static staticProp = (a = 'retained', b = 'retained', c = 'removed', d = 'removed') => - console.log(a, b, c); -} - -new Test(isUndefined, 'b', 'c'); -new Test('a', globalThis.unknown, 'c').method(); - -Test.staticMethod(isUndefined, 'b', 'c'); -Test.staticMethod('a', globalThis.unknown, 'c'); - -Test.staticProp(isUndefined, 'b', 'c'); -Test.staticProp('a', globalThis.unknown, 'c'); diff --git a/test/form/samples/tree-shake-default-parameters/default-parameter-side-effects/_config.js b/test/form/samples/tree-shake-default-parameters/default-parameter-side-effects/_config.js deleted file mode 100644 index 7a232d2ace8..00000000000 --- a/test/form/samples/tree-shake-default-parameters/default-parameter-side-effects/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - description: 'handles side effects of default parameters' -}; diff --git a/test/form/samples/tree-shake-default-parameters/default-parameter-side-effects/_expected.js b/test/form/samples/tree-shake-default-parameters/default-parameter-side-effects/_expected.js deleted file mode 100644 index 6eccd926254..00000000000 --- a/test/form/samples/tree-shake-default-parameters/default-parameter-side-effects/_expected.js +++ /dev/null @@ -1,17 +0,0 @@ -const a = (p = 'retained') => console.log(p); -a(); - -const b = (p) => console.log(p); -b('value'); - -const c = (p = console.log('retained because of side effect')) => {}; -c(); - -const d = (p) => console.log('effect'); -d(); - -const e = (p) => {}; -e(); - -const f = ({ x = console.log('retained') }) => {}; -f('value'); diff --git a/test/form/samples/tree-shake-default-parameters/default-parameter-side-effects/main.js b/test/form/samples/tree-shake-default-parameters/default-parameter-side-effects/main.js deleted file mode 100644 index 25501dd5f0a..00000000000 --- a/test/form/samples/tree-shake-default-parameters/default-parameter-side-effects/main.js +++ /dev/null @@ -1,17 +0,0 @@ -const a = (p = 'retained') => console.log(p); -a(); - -const b = (p = console.log('removed')) => console.log(p); -b('value'); - -const c = (p = console.log('retained because of side effect')) => {}; -c(); - -const d = (p = 'removed because no side effect') => console.log('effect'); -d(); - -const e = (p = console.log('removed')) => {}; -e('value'); - -const f = ({ x = console.log('retained') } = {}) => {}; -f('value'); diff --git a/test/form/samples/tree-shake-default-parameters/functions/_config.js b/test/form/samples/tree-shake-default-parameters/functions/_config.js deleted file mode 100644 index 0faee52a9f7..00000000000 --- a/test/form/samples/tree-shake-default-parameters/functions/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - description: 'supports tree-shaking for unused default parameter values for functions' -}; diff --git a/test/form/samples/tree-shake-default-parameters/functions/_expected.js b/test/form/samples/tree-shake-default-parameters/functions/_expected.js deleted file mode 100644 index 945aec4a8bc..00000000000 --- a/test/form/samples/tree-shake-default-parameters/functions/_expected.js +++ /dev/null @@ -1,21 +0,0 @@ -var isUndefined; - -function funDecl(a = 'retained', b = 'retained', c, d) { - console.log(a, b, c); -} - -funDecl(isUndefined, 'b', 'c'); -funDecl('a', globalThis.unknown, 'c'); - -const funExp = function (a = 'retained', b = 'retained', c, d) { - console.log(a, b, c); -}; - -funExp(isUndefined, 'b', 'c'); -funExp('a', globalThis.unknown, 'c'); - -const arrow = (a = 'retained', b = 'retained', c, d) => - console.log(a, b, c); - -arrow(isUndefined, 'b', 'c'); -arrow('a', globalThis.unknown, 'c'); diff --git a/test/form/samples/tree-shake-default-parameters/functions/main.js b/test/form/samples/tree-shake-default-parameters/functions/main.js deleted file mode 100644 index 048cf4dbae2..00000000000 --- a/test/form/samples/tree-shake-default-parameters/functions/main.js +++ /dev/null @@ -1,21 +0,0 @@ -var isUndefined; - -function funDecl(a = 'retained', b = 'retained', c = 'removed', d = 'removed') { - console.log(a, b, c); -} - -funDecl(isUndefined, 'b', 'c'); -funDecl('a', globalThis.unknown, 'c'); - -const funExp = function (a = 'retained', b = 'retained', c = 'removed', d = 'removed') { - console.log(a, b, c); -}; - -funExp(isUndefined, 'b', 'c'); -funExp('a', globalThis.unknown, 'c'); - -const arrow = (a = 'retained', b = 'retained', c = 'removed', d = 'removed') => - console.log(a, b, c); - -arrow(isUndefined, 'b', 'c'); -arrow('a', globalThis.unknown, 'c'); diff --git a/test/form/samples/tree-shake-default-parameters/non-literal-arguments/_config.js b/test/form/samples/tree-shake-default-parameters/non-literal-arguments/_config.js deleted file mode 100644 index 341af06cb2d..00000000000 --- a/test/form/samples/tree-shake-default-parameters/non-literal-arguments/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - description: 'recognizes non-literal arguments as not undefined' -}; diff --git a/test/form/samples/tree-shake-default-parameters/non-literal-arguments/_expected.js b/test/form/samples/tree-shake-default-parameters/non-literal-arguments/_expected.js deleted file mode 100644 index 3ac927db486..00000000000 --- a/test/form/samples/tree-shake-default-parameters/non-literal-arguments/_expected.js +++ /dev/null @@ -1,11 +0,0 @@ -function test(a) { - console.log(a); -} - -test({}); -test([]); -test(() => {}); -test(function () {}); -function a(){} -test(a); -test(Symbol); diff --git a/test/form/samples/tree-shake-default-parameters/non-literal-arguments/main.js b/test/form/samples/tree-shake-default-parameters/non-literal-arguments/main.js deleted file mode 100644 index 6f3421429ae..00000000000 --- a/test/form/samples/tree-shake-default-parameters/non-literal-arguments/main.js +++ /dev/null @@ -1,11 +0,0 @@ -function test(a = 'removed') { - console.log(a); -} - -test({}); -test([]); -test(() => {}); -test(function () {}); -function a(){} -test(a); -test(Symbol); diff --git a/test/form/samples/tree-shake-default-parameters/object-methods/_config.js b/test/form/samples/tree-shake-default-parameters/object-methods/_config.js deleted file mode 100644 index 879fa5d672b..00000000000 --- a/test/form/samples/tree-shake-default-parameters/object-methods/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - description: 'supports tree-shaking for unused default parameter values on objects' -}; diff --git a/test/form/samples/tree-shake-default-parameters/object-methods/_expected.js b/test/form/samples/tree-shake-default-parameters/object-methods/_expected.js deleted file mode 100644 index e3164086966..00000000000 --- a/test/form/samples/tree-shake-default-parameters/object-methods/_expected.js +++ /dev/null @@ -1,10 +0,0 @@ -var isUndefined; - -const test = { - method(a = 'retained', b = 'retained', c, d) { - console.log(a, b, c); - } -}; - -test.method(isUndefined, 'b', 'c'); -test.method('a', globalThis.unknown, 'c'); diff --git a/test/form/samples/tree-shake-default-parameters/object-methods/main.js b/test/form/samples/tree-shake-default-parameters/object-methods/main.js deleted file mode 100644 index 6a7d812c145..00000000000 --- a/test/form/samples/tree-shake-default-parameters/object-methods/main.js +++ /dev/null @@ -1,10 +0,0 @@ -var isUndefined; - -const test = { - method(a = 'retained', b = 'retained', c = 'removed', d = 'removed') { - console.log(a, b, c); - } -}; - -test.method(isUndefined, 'b', 'c'); -test.method('a', globalThis.unknown, 'c'); diff --git a/test/form/samples/tree-shake-default-parameters/super-class-methods/_config.js b/test/form/samples/tree-shake-default-parameters/super-class-methods/_config.js deleted file mode 100644 index 6dbe1e96dd5..00000000000 --- a/test/form/samples/tree-shake-default-parameters/super-class-methods/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - description: 'supports tree-shaking for unused default parameter values on super classes' -}; diff --git a/test/form/samples/tree-shake-default-parameters/super-class-methods/_expected.js b/test/form/samples/tree-shake-default-parameters/super-class-methods/_expected.js deleted file mode 100644 index 3cbf6cdad4e..00000000000 --- a/test/form/samples/tree-shake-default-parameters/super-class-methods/_expected.js +++ /dev/null @@ -1,25 +0,0 @@ -var isUndefined; - -class TestSuper { - constructor(a = 'retained', b = 'retained', c, d) { - console.log(a, b, c); - } - - static staticMethod(a = 'retained', b = 'retained', c, d) { - console.log(a, b, c); - } - - static staticProp = (a = 'retained', b = 'retained', c, d) => - console.log(a, b, c); -} - -class Test extends TestSuper {} - -new Test(isUndefined, 'b', 'c'); -new Test('a', globalThis.unknown, 'c').method(); - -Test.staticMethod(isUndefined, 'b', 'c'); -Test.staticMethod('a', globalThis.unknown, 'c'); - -Test.staticProp(isUndefined, 'b', 'c'); -Test.staticProp('a', globalThis.unknown, 'c'); diff --git a/test/form/samples/tree-shake-default-parameters/super-class-methods/main.js b/test/form/samples/tree-shake-default-parameters/super-class-methods/main.js deleted file mode 100644 index 7cb2dacc9a5..00000000000 --- a/test/form/samples/tree-shake-default-parameters/super-class-methods/main.js +++ /dev/null @@ -1,25 +0,0 @@ -var isUndefined; - -class TestSuper { - constructor(a = 'retained', b = 'retained', c = 'removed', d = 'removed') { - console.log(a, b, c); - } - - static staticMethod(a = 'retained', b = 'retained', c = 'removed', d = 'removed') { - console.log(a, b, c); - } - - static staticProp = (a = 'retained', b = 'retained', c = 'removed', d = 'removed') => - console.log(a, b, c); -} - -class Test extends TestSuper {} - -new Test(isUndefined, 'b', 'c'); -new Test('a', globalThis.unknown, 'c').method(); - -Test.staticMethod(isUndefined, 'b', 'c'); -Test.staticMethod('a', globalThis.unknown, 'c'); - -Test.staticProp(isUndefined, 'b', 'c'); -Test.staticProp('a', globalThis.unknown, 'c'); diff --git a/test/form/samples/tree-shake-default-parameters/termplate-tags/_config.js b/test/form/samples/tree-shake-default-parameters/termplate-tags/_config.js deleted file mode 100644 index 6c444966620..00000000000 --- a/test/form/samples/tree-shake-default-parameters/termplate-tags/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - description: 'supports tree-shaking for unused default parameter values on template tags' -}; diff --git a/test/form/samples/tree-shake-default-parameters/termplate-tags/_expected.js b/test/form/samples/tree-shake-default-parameters/termplate-tags/_expected.js deleted file mode 100644 index 39bc336e378..00000000000 --- a/test/form/samples/tree-shake-default-parameters/termplate-tags/_expected.js +++ /dev/null @@ -1,6 +0,0 @@ -const templateTag = (_, a = 'retained', b = 'retained', c, d) => { - console.log(a, b, c); -}; - -templateTag`${isUndefined}${'b'}${'c'}`; -templateTag`${'a'}${globalThis.unknown}${'c'}`; diff --git a/test/form/samples/tree-shake-default-parameters/termplate-tags/main.js b/test/form/samples/tree-shake-default-parameters/termplate-tags/main.js deleted file mode 100644 index bed212d6f45..00000000000 --- a/test/form/samples/tree-shake-default-parameters/termplate-tags/main.js +++ /dev/null @@ -1,6 +0,0 @@ -const templateTag = (_, a = 'retained', b = 'retained', c = 'removed', d = 'removed') => { - console.log(a, b, c); -}; - -templateTag`${isUndefined}${'b'}${'c'}`; -templateTag`${'a'}${globalThis.unknown}${'c'}`; diff --git a/test/form/samples/treeshake-excess-arguments/unused-parameters/_expected.js b/test/form/samples/treeshake-excess-arguments/unused-parameters/_expected.js index c13a9d031cf..256b223b12a 100644 --- a/test/form/samples/treeshake-excess-arguments/unused-parameters/_expected.js +++ b/test/form/samples/treeshake-excess-arguments/unused-parameters/_expected.js @@ -1,11 +1,11 @@ -function test(a, b, c) {} -test(); +function test(a, b = globalThis.unknown(), c) {} +test(1, 2); function noEffect() {} test(1, 2, noEffect(), globalThis.unknown()); -const testArr = (a, b, c) => {}; -testArr(); +const testArr = (a, b = globalThis.unknown(), c) => {}; +testArr(1, 2); function noEffectArr() {} testArr(1, 2, noEffectArr(), globalThis.unknown()); diff --git a/test/function/samples/class-method-mutation/_config.js b/test/function/samples/class-method-mutation/_config.js deleted file mode 100644 index fcf070b8b11..00000000000 --- a/test/function/samples/class-method-mutation/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - description: 'tracks mutations of class methods' -}; diff --git a/test/function/samples/class-method-mutation/main.js b/test/function/samples/class-method-mutation/main.js deleted file mode 100644 index 0d089acdd3e..00000000000 --- a/test/function/samples/class-method-mutation/main.js +++ /dev/null @@ -1,14 +0,0 @@ -let effect = false; - -class Foo { - method() {} -} - -const foo = new Foo(); -Object.defineProperty(foo.method, 'effect', { - get() { - effect = true; - } -}); - -Foo.prototype.method.effect; diff --git a/test/function/samples/default-parameter-tagged-templates/_config.js b/test/function/samples/default-parameter-tagged-templates/_config.js deleted file mode 100644 index 6e2f4a704b6..00000000000 --- a/test/function/samples/default-parameter-tagged-templates/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - description: 'includes necessary default parameters for tagged template literals' -}; diff --git a/test/function/samples/default-parameter-tagged-templates/main.js b/test/function/samples/default-parameter-tagged-templates/main.js deleted file mode 100644 index 3c0a5f8bd8c..00000000000 --- a/test/function/samples/default-parameter-tagged-templates/main.js +++ /dev/null @@ -1,6 +0,0 @@ -const templateTag = ([, a = 'quasiFallback'], b = 'expressionFallback') => { - assert.strictEqual(a, 'quasiFallback'); - assert.strictEqual(b, 'expressionFallback'); -}; - -templateTag``; diff --git a/test/function/samples/default-parameters-exported/_config.js b/test/function/samples/default-parameters-exported/_config.js deleted file mode 100644 index e2b24f315cd..00000000000 --- a/test/function/samples/default-parameters-exported/_config.js +++ /dev/null @@ -1,10 +0,0 @@ -const assert = require('assert'); - -module.exports = { - description: 'includes default parameters for exported functions', - exports({ funDecl, funExp, arrow }) { - assert.strictEqual(funDecl(), 'defaultValue', 'function declaration'); - assert.strictEqual(funExp(), 'defaultValue', 'function expression'); - assert.strictEqual(arrow(), 'defaultValue', 'arrow function'); - } -}; diff --git a/test/function/samples/default-parameters-exported/main.js b/test/function/samples/default-parameters-exported/main.js deleted file mode 100644 index c40f173c19b..00000000000 --- a/test/function/samples/default-parameters-exported/main.js +++ /dev/null @@ -1,9 +0,0 @@ -export function funDecl(a = 'defaultValue') { - return a; -} - -export const funExp = function (a = 'defaultValue') { - return a; -}; - -export const arrow = (a = 'defaultValue') => a; diff --git a/test/function/samples/parameter-defaults-module-side-effects/_config.js b/test/function/samples/parameter-defaults-module-side-effects/_config.js deleted file mode 100644 index 6c32573208e..00000000000 --- a/test/function/samples/parameter-defaults-module-side-effects/_config.js +++ /dev/null @@ -1,9 +0,0 @@ -const path = require('path'); - -module.exports = { - description: - 'does not tree-shake necessary parameter defaults when modulesSideEffects are disabled', - options: { - treeshake: { moduleSideEffects: false } - } -}; diff --git a/test/function/samples/parameter-defaults-module-side-effects/main.js b/test/function/samples/parameter-defaults-module-side-effects/main.js deleted file mode 100644 index 61838adaa51..00000000000 --- a/test/function/samples/parameter-defaults-module-side-effects/main.js +++ /dev/null @@ -1,3 +0,0 @@ -import { foo } from './other'; - -assert.strictEqual(foo(), 'fallback'); diff --git a/test/function/samples/parameter-defaults-module-side-effects/other.js b/test/function/samples/parameter-defaults-module-side-effects/other.js deleted file mode 100644 index 2bded0cd154..00000000000 --- a/test/function/samples/parameter-defaults-module-side-effects/other.js +++ /dev/null @@ -1 +0,0 @@ -export const foo = (a = 'fallback') => a; diff --git a/test/function/samples/tagged-template-deoptimize/_config.js b/test/function/samples/tagged-template-deoptimize/_config.js deleted file mode 100644 index 2f550cae185..00000000000 --- a/test/function/samples/tagged-template-deoptimize/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - description: 'correctly deoptimizes tagged template expressions' -}; diff --git a/test/function/samples/tagged-template-deoptimize/_expected.js b/test/function/samples/tagged-template-deoptimize/_expected.js deleted file mode 100644 index c387edcca09..00000000000 --- a/test/function/samples/tagged-template-deoptimize/_expected.js +++ /dev/null @@ -1,6 +0,0 @@ -var a = () => { - console.log('props'); -}; - -a(); -a(); diff --git a/test/function/samples/tagged-template-deoptimize/main.js b/test/function/samples/tagged-template-deoptimize/main.js deleted file mode 100644 index b564677dd8f..00000000000 --- a/test/function/samples/tagged-template-deoptimize/main.js +++ /dev/null @@ -1,16 +0,0 @@ -const tagReturn = 'return'; - -const param = { modified: false }; - -const obj = { - modified: false, - tag(_, param) { - this.modified = true; - param.modified = true; - return tagReturn; - } -}; - -assert.strictEqual(obj.tag`${param}`, 'return'); -assert.ok(obj.modified ? true : false, 'obj'); -assert.ok(param.modified ? true : false, 'param');