Revert "Tree shake parameter defaults (#4498)"

This reverts commit 4624917137.
pull/4511/head
Lukas Taegert-Atkinson 2 years ago
parent fc99e96e09
commit 17437940f2

@ -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(),

@ -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 });

@ -16,7 +16,9 @@ export default class ArrayPattern extends NodeBase implements PatternNode {
exportNamesByVariable: ReadonlyMap<Variable, readonly string[]>
): 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();
}
}
}
}

@ -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;

@ -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);
}

@ -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 {

@ -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();
}
}

@ -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 === '+' &&

@ -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<ExpressionEntity>();
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) {

@ -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);
}

@ -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;
}
}

@ -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 {

@ -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;

@ -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;
}

@ -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;
}

@ -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);
}
}

@ -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<Variable, readonly string[]>
): 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!;
}
}

@ -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;
}

@ -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 =

@ -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;
}

@ -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,

@ -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 {

@ -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;

@ -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);
}
}

@ -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;
}

@ -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)

@ -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;

@ -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) {

@ -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);

@ -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;
}
}

@ -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);
}
}
}

@ -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);
}

@ -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);
}
}

@ -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);

@ -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;
}
}

@ -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();
}
}
}

@ -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<ExpressionEntity>();
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;
}

@ -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 {

@ -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 =

@ -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) {

@ -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 {

@ -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') {

@ -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);

@ -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;

@ -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,

@ -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);
}

@ -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) {

@ -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;

@ -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,
{

@ -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);
}
}

@ -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);
}
}

@ -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;
}));

@ -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;

@ -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 };

@ -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;
})();

@ -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);
})
};
}));

@ -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;
}));

@ -10,3 +10,5 @@ function foo () {
}
foo();
export default 42;

@ -4,4 +4,5 @@ class SuperAccess {
}
class Access extends SuperAccess {}
Access.prototype.doesNoExist.throws;
Access.prototype.method.doesNoExist.throws;
Access.prototype.prop.throws;

@ -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;

@ -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 {

@ -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();

@ -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();

@ -1,4 +1,5 @@
console.log('retained');
console.log('retained');
const prop = { isTrue: true };
class SuperDeopt {

@ -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 {

@ -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();

@ -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();

@ -1,3 +0,0 @@
module.exports = {
description: 'supports tree-shaking for unused default parameter values on arrays'
};

@ -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]();

@ -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]();

@ -1,3 +0,0 @@
module.exports = {
description: 'supports tree-shaking for unused default parameter values on classes'
};

@ -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');

@ -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');

@ -1,3 +0,0 @@
module.exports = {
description: 'handles side effects of default parameters'
};

@ -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');

@ -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');

@ -1,3 +0,0 @@
module.exports = {
description: 'supports tree-shaking for unused default parameter values for functions'
};

@ -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');

@ -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');

@ -1,3 +0,0 @@
module.exports = {
description: 'recognizes non-literal arguments as not undefined'
};

@ -1,11 +0,0 @@
function test(a) {
console.log(a);
}
test({});
test([]);
test(() => {});
test(function () {});
function a(){}
test(a);
test(Symbol);

@ -1,11 +0,0 @@
function test(a = 'removed') {
console.log(a);
}
test({});
test([]);
test(() => {});
test(function () {});
function a(){}
test(a);
test(Symbol);

@ -1,3 +0,0 @@
module.exports = {
description: 'supports tree-shaking for unused default parameter values on objects'
};

@ -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');

@ -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');

@ -1,3 +0,0 @@
module.exports = {
description: 'supports tree-shaking for unused default parameter values on super classes'
};

@ -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');

@ -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');

@ -1,3 +0,0 @@
module.exports = {
description: 'supports tree-shaking for unused default parameter values on template tags'
};

@ -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'}`;

@ -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'}`;

@ -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());

@ -1,3 +0,0 @@
module.exports = {
description: 'tracks mutations of class methods'
};

@ -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;

@ -1,3 +0,0 @@
module.exports = {
description: 'includes necessary default parameters for tagged template literals'
};

@ -1,6 +0,0 @@
const templateTag = ([, a = 'quasiFallback'], b = 'expressionFallback') => {
assert.strictEqual(a, 'quasiFallback');
assert.strictEqual(b, 'expressionFallback');
};
templateTag``;

@ -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');
}
};

@ -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;

@ -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 }
}
};

@ -1,3 +0,0 @@
import { foo } from './other';
assert.strictEqual(foo(), 'fallback');

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save