diff --git a/src/ast/nodes/ArrowFunctionExpression.ts b/src/ast/nodes/ArrowFunctionExpression.ts index 8bf2df38572..c99578c872e 100644 --- a/src/ast/nodes/ArrowFunctionExpression.ts +++ b/src/ast/nodes/ArrowFunctionExpression.ts @@ -5,8 +5,9 @@ import type ChildScope from '../scopes/ChildScope'; import ReturnValueScope from '../scopes/ReturnValueScope'; import { type ObjectPath } from '../utils/PathTracker'; import type BlockStatement from './BlockStatement'; +import type CallExpression from './CallExpression'; import Identifier from './Identifier'; -import type * as NodeType from './NodeType'; +import * as NodeType from './NodeType'; import { Flag, isFlagSet, setFlag } from './shared/BitFlags'; import FunctionBase from './shared/FunctionBase'; import type { ExpressionNode, IncludeChildren } from './shared/Node'; @@ -67,6 +68,13 @@ export default class ArrowFunctionExpression extends FunctionBase { return false; } + protected onlyFunctionCallUsed(): boolean { + const isIIFE = + this.parent.type === NodeType.CallExpression && + (this.parent as CallExpression).callee === this; + return isIIFE || super.onlyFunctionCallUsed(); + } + include(context: InclusionContext, includeChildrenRecursively: IncludeChildren): void { super.include(context, includeChildrenRecursively); for (const parameter of this.params) { diff --git a/src/ast/nodes/CallExpression.ts b/src/ast/nodes/CallExpression.ts index 737f1a21eef..85ddcaeaffa 100644 --- a/src/ast/nodes/CallExpression.ts +++ b/src/ast/nodes/CallExpression.ts @@ -63,20 +63,17 @@ export default class CallExpression } hasEffects(context: HasEffectsContext): boolean { - try { - for (const argument of this.arguments) { - if (argument.hasEffects(context)) return true; - } - if (this.annotationPure) { - return false; - } - return ( - this.callee.hasEffects(context) || - this.callee.hasEffectsOnInteractionAtPath(EMPTY_PATH, this.interaction, 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.annotationPure) { + return false; + } + return ( + this.callee.hasEffects(context) || + this.callee.hasEffectsOnInteractionAtPath(EMPTY_PATH, this.interaction, context) + ); } include(context: InclusionContext, includeChildrenRecursively: IncludeChildren): void { diff --git a/src/ast/nodes/FunctionDeclaration.ts b/src/ast/nodes/FunctionDeclaration.ts index 4f75fce15f4..9cfa8b21bd5 100644 --- a/src/ast/nodes/FunctionDeclaration.ts +++ b/src/ast/nodes/FunctionDeclaration.ts @@ -14,6 +14,11 @@ export default class FunctionDeclaration extends FunctionNode { } } + protected onlyFunctionCallUsed(): boolean { + // call super.onlyFunctionCallUsed for export default anonymous function + return this.id?.variable.getOnlyFunctionCallUsed() ?? super.onlyFunctionCallUsed(); + } + parseNode(esTreeNode: GenericEsTreeNode): this { if (esTreeNode.id !== null) { this.id = new Identifier(this, this.scope.parent as ChildScope).parseNode( diff --git a/src/ast/nodes/FunctionExpression.ts b/src/ast/nodes/FunctionExpression.ts index be7c5e43144..85b2b5cb45f 100644 --- a/src/ast/nodes/FunctionExpression.ts +++ b/src/ast/nodes/FunctionExpression.ts @@ -2,6 +2,7 @@ import type MagicString from 'magic-string'; import { BLANK } from '../../utils/blank'; import type { NodeRenderOptions, RenderOptions } from '../../utils/renderHelpers'; import ChildScope from '../scopes/ChildScope'; +import type CallExpression from './CallExpression'; import type { IdentifierWithVariable } from './Identifier'; import Identifier from './Identifier'; import * as NodeType from './NodeType'; @@ -25,6 +26,14 @@ export default class FunctionExpression extends FunctionNode { return super.parseNode(esTreeNode); } + protected onlyFunctionCallUsed(): boolean { + const isIIFE = + this.parent.type === NodeType.CallExpression && + (this.parent as CallExpression).callee === this && + (this.id === null || this.id.variable.getOnlyFunctionCallUsed()); + return isIIFE || super.onlyFunctionCallUsed(); + } + render( code: MagicString, options: RenderOptions, diff --git a/src/ast/nodes/Identifier.ts b/src/ast/nodes/Identifier.ts index 11147f3fb09..8bc4ffcd8ea 100644 --- a/src/ast/nodes/Identifier.ts +++ b/src/ast/nodes/Identifier.ts @@ -60,10 +60,12 @@ export default class Identifier extends NodeBase implements PatternNode { } } + private isReferenceVariable = false; bind(): void { if (!this.variable && isReference(this, this.parent as NodeWithFieldDefinition)) { this.variable = this.scope.findVariable(this.name); this.variable.addReference(this); + this.isReferenceVariable = true; } } @@ -295,6 +297,10 @@ export default class Identifier extends NodeBase implements PatternNode { this.variable.consolidateInitializers(); this.scope.context.requestTreeshakingPass(); } + if (this.isReferenceVariable) { + this.variable!.addUsedPlace(this); + this.scope.context.requestTreeshakingPass(); + } } private getVariableRespectingTDZ(): ExpressionEntity | null { diff --git a/src/ast/nodes/MemberExpression.ts b/src/ast/nodes/MemberExpression.ts index 5c32a896b3d..7d6c6204731 100644 --- a/src/ast/nodes/MemberExpression.ts +++ b/src/ast/nodes/MemberExpression.ts @@ -396,6 +396,10 @@ export default class MemberExpression ); this.scope.context.requestTreeshakingPass(); } + if (this.variable) { + this.variable.addUsedPlace(this); + this.scope.context.requestTreeshakingPass(); + } } private applyAssignmentDeoptimization(): void { diff --git a/src/ast/nodes/UpdateExpression.ts b/src/ast/nodes/UpdateExpression.ts index 2d7b0613fe5..860bdd927a5 100644 --- a/src/ast/nodes/UpdateExpression.ts +++ b/src/ast/nodes/UpdateExpression.ts @@ -87,7 +87,7 @@ export default class UpdateExpression extends NodeBase { this.argument.deoptimizePath(EMPTY_PATH); if (this.argument instanceof Identifier) { const variable = this.scope.findVariable(this.argument.name); - variable.isReassigned = true; + variable.markReassigned(); } this.scope.context.requestTreeshakingPass(); } diff --git a/src/ast/nodes/shared/FunctionBase.ts b/src/ast/nodes/shared/FunctionBase.ts index 55b9f7f3da1..e6fc3fb83b7 100644 --- a/src/ast/nodes/shared/FunctionBase.ts +++ b/src/ast/nodes/shared/FunctionBase.ts @@ -10,11 +10,16 @@ import { import type ReturnValueScope from '../../scopes/ReturnValueScope'; import type { ObjectPath, PathTracker } from '../../utils/PathTracker'; import { UNKNOWN_PATH, UnknownKey } from '../../utils/PathTracker'; +import { UNDEFINED_EXPRESSION } from '../../values'; import type ParameterVariable from '../../variables/ParameterVariable'; +import type Variable from '../../variables/Variable'; import BlockStatement from '../BlockStatement'; +import type ExportDefaultDeclaration from '../ExportDefaultDeclaration'; import Identifier from '../Identifier'; +import * as NodeType from '../NodeType'; import RestElement from '../RestElement'; import type SpreadElement from '../SpreadElement'; +import type VariableDeclarator from '../VariableDeclarator'; import { Flag, isFlagSet, setFlag } from './BitFlags'; import type { ExpressionEntity, LiteralValueOrUnknown } from './Expression'; import { UNKNOWN_EXPRESSION, UNKNOWN_RETURN_EXPRESSION } from './Expression'; @@ -27,6 +32,8 @@ import { import type { ObjectEntity } from './ObjectEntity'; import type { PatternNode } from './Pattern'; +type InteractionCalledArguments = NodeInteractionCalled['args']; + export default abstract class FunctionBase extends NodeBase { declare body: BlockStatement | ExpressionNode; declare params: PatternNode[]; @@ -57,6 +64,27 @@ export default abstract class FunctionBase extends NodeBase { this.flags = setFlag(this.flags, Flag.generator, value); } + private updateParameterVariableValues(_arguments: InteractionCalledArguments): void { + for (let position = 0; position < this.params.length; position++) { + const parameter = this.params[position]; + if (!(parameter instanceof Identifier)) { + continue; + } + const parameterVariable = parameter.variable as ParameterVariable; + const argument = _arguments[position + 1] ?? UNDEFINED_EXPRESSION; + parameterVariable.updateKnownValue(argument); + } + } + + private deoptimizeParameterVariableValues() { + for (const parameter of this.params) { + if (parameter instanceof Identifier) { + const parameterVariable = parameter.variable as ParameterVariable; + parameterVariable.markReassigned(); + } + } + } + protected objectEntity: ObjectEntity | null = null; deoptimizeArgumentsOnInteractionAtPath( @@ -84,6 +112,7 @@ export default abstract class FunctionBase extends NodeBase { this.addArgumentToBeDeoptimized(argument); } } + this.updateParameterVariableValues(args); } else { this.getObjectEntity().deoptimizeArgumentsOnInteractionAtPath( interaction, @@ -102,6 +131,7 @@ export default abstract class FunctionBase extends NodeBase { for (const parameterList of this.scope.parameters) { for (const parameter of parameterList) { parameter.deoptimizePath(UNKNOWN_PATH); + parameter.markReassigned(); } } } @@ -180,7 +210,26 @@ export default abstract class FunctionBase extends NodeBase { return false; } + /** + * If the function (expression or declaration) is only used as function calls + */ + protected onlyFunctionCallUsed(): boolean { + let variable: Variable | null = null; + if (this.parent.type === NodeType.VariableDeclarator) { + variable = (this.parent as VariableDeclarator).id.variable ?? null; + } + if (this.parent.type === NodeType.ExportDefaultDeclaration) { + variable = (this.parent as ExportDefaultDeclaration).variable; + } + return variable?.getOnlyFunctionCallUsed() ?? false; + } + + private parameterVariableValuesDeoptimized = false; include(context: InclusionContext, includeChildrenRecursively: IncludeChildren): void { + if (!this.parameterVariableValuesDeoptimized && !this.onlyFunctionCallUsed()) { + this.parameterVariableValuesDeoptimized = true; + this.deoptimizeParameterVariableValues(); + } if (!this.deoptimized) this.applyDeoptimizations(); this.included = true; const { brokenFlow } = context; diff --git a/src/ast/variables/ExportDefaultVariable.ts b/src/ast/variables/ExportDefaultVariable.ts index 979405b5784..70810936e5a 100644 --- a/src/ast/variables/ExportDefaultVariable.ts +++ b/src/ast/variables/ExportDefaultVariable.ts @@ -3,6 +3,7 @@ import ClassDeclaration from '../nodes/ClassDeclaration'; import type ExportDefaultDeclaration from '../nodes/ExportDefaultDeclaration'; import FunctionDeclaration from '../nodes/FunctionDeclaration'; import Identifier, { type IdentifierWithVariable } from '../nodes/Identifier'; +import type { NodeBase } from '../nodes/shared/Node'; import LocalVariable from './LocalVariable'; import UndefinedVariable from './UndefinedVariable'; import type Variable from './Variable'; @@ -37,6 +38,15 @@ export default class ExportDefaultVariable extends LocalVariable { } } + addUsedPlace(usedPlace: NodeBase): void { + const original = this.getOriginalVariable(); + if (original === this) { + super.addUsedPlace(usedPlace); + } else { + original.addUsedPlace(usedPlace); + } + } + forbidName(name: string) { const original = this.getOriginalVariable(); if (original === this) { diff --git a/src/ast/variables/GlobalVariable.ts b/src/ast/variables/GlobalVariable.ts index d55ed63ae00..4f168ac8f86 100644 --- a/src/ast/variables/GlobalVariable.ts +++ b/src/ast/variables/GlobalVariable.ts @@ -13,9 +13,12 @@ import type { ObjectPath, PathTracker } from '../utils/PathTracker'; import Variable from './Variable'; export default class GlobalVariable extends Variable { - // Ensure we use live-bindings for globals as we do not know if they have - // been reassigned - isReassigned = true; + constructor(name: string) { + super(name); + // Ensure we use live-bindings for globals as we do not know if they have + // been reassigned + this.markReassigned(); + } deoptimizeArgumentsOnInteractionAtPath( interaction: NodeInteraction, diff --git a/src/ast/variables/LocalVariable.ts b/src/ast/variables/LocalVariable.ts index fe5bd488492..83b32a81ef7 100644 --- a/src/ast/variables/LocalVariable.ts +++ b/src/ast/variables/LocalVariable.ts @@ -91,7 +91,7 @@ export default class LocalVariable extends Variable { return; } if (path.length === 0) { - this.isReassigned = true; + this.markReassigned(); const expressionsToBeDeoptimized = this.expressionsToBeDeoptimized; this.expressionsToBeDeoptimized = EMPTY_ARRAY as unknown as DeoptimizableEntity[]; for (const expression of expressionsToBeDeoptimized) { @@ -222,7 +222,7 @@ export default class LocalVariable extends Variable { if (this.additionalInitializers === null) { this.additionalInitializers = [this.init]; this.init = UNKNOWN_EXPRESSION; - this.isReassigned = true; + this.markReassigned(); } return this.additionalInitializers; } diff --git a/src/ast/variables/ParameterVariable.ts b/src/ast/variables/ParameterVariable.ts index 94f3feb90d9..a17bf3b1152 100644 --- a/src/ast/variables/ParameterVariable.ts +++ b/src/ast/variables/ParameterVariable.ts @@ -1,17 +1,21 @@ import type { AstContext } from '../../Module'; import { EMPTY_ARRAY } from '../../utils/blank'; +import type { DeoptimizableEntity } from '../DeoptimizableEntity'; +import type { HasEffectsContext } from '../ExecutionContext'; import type { NodeInteraction } from '../NodeInteractions'; -import { INTERACTION_CALLED } from '../NodeInteractions'; +import { INTERACTION_ASSIGNED, INTERACTION_CALLED } from '../NodeInteractions'; import type ExportDefaultDeclaration from '../nodes/ExportDefaultDeclaration'; -import type Identifier from '../nodes/Identifier'; -import type { ExpressionEntity } from '../nodes/shared/Expression'; +import Identifier from '../nodes/Identifier'; +import type { ExpressionEntity, LiteralValueOrUnknown } from '../nodes/shared/Expression'; import { deoptimizeInteraction, UNKNOWN_EXPRESSION, - UNKNOWN_RETURN_EXPRESSION + UNKNOWN_RETURN_EXPRESSION, + UnknownValue } from '../nodes/shared/Expression'; import type { ObjectPath, ObjectPathKey } from '../utils/PathTracker'; import { + EMPTY_PATH, PathTracker, SHARED_RECURSION_TRACKER, UNKNOWN_PATH, @@ -35,6 +39,7 @@ export default class ParameterVariable extends LocalVariable { private deoptimizations = new PathTracker(); private deoptimizedFields = new Set(); private entitiesToBeDeoptimized = new Set(); + private expressionsUseTheKnownValue: DeoptimizableEntity[] = []; constructor( name: string, @@ -71,6 +76,110 @@ export default class ParameterVariable extends LocalVariable { } } + markReassigned(): void { + if (this.isReassigned) { + return; + } + super.markReassigned(); + for (const expression of this.expressionsUseTheKnownValue) { + expression.deoptimizeCache(); + } + this.expressionsUseTheKnownValue = EMPTY_ARRAY as unknown as DeoptimizableEntity[]; + } + + deoptimizeCache(): void { + this.markReassigned(); + } + + private knownValue: ExpressionEntity | null = null; + private knownValueLiteral: LiteralValueOrUnknown = UnknownValue; + /** + * Update the known value of the parameter variable. + * Must be called for every function call, so it can track all the arguments, + * and deoptimizeCache itself to mark reassigned if the argument is changed. + * @param argument The argument of the function call + */ + updateKnownValue(argument: ExpressionEntity) { + if (this.isReassigned) { + return; + } + + if (this.knownValue === null) { + this.knownValue = argument; + this.knownValueLiteral = argument.getLiteralValueAtPath( + EMPTY_PATH, + SHARED_RECURSION_TRACKER, + this + ); + return; + } + + // the same literal or identifier, do nothing + if ( + this.knownValue === argument || + (this.knownValue instanceof Identifier && + argument instanceof Identifier && + this.knownValue.variable === argument.variable) + ) { + return; + } + + const oldValue = this.knownValueLiteral; + if (typeof oldValue === 'symbol') { + this.markReassigned(); + return; + } + // add tracking for the new argument + const newValue = argument.getLiteralValueAtPath(EMPTY_PATH, SHARED_RECURSION_TRACKER, this); + if (newValue !== oldValue) { + this.markReassigned(); + } + } + + private frozenValue: ExpressionEntity | null = null; + /** + * This function freezes the known value of the parameter variable, + * so the optimization starts with a certain ExpressionEntity. + * The optimization can be undone by calling `markReassigned`. + * @returns the frozen value + */ + private getKnownValue(): ExpressionEntity { + if (this.frozenValue === null) { + this.frozenValue = this.knownValue || UNKNOWN_EXPRESSION; + } + return this.frozenValue; + } + + getLiteralValueAtPath( + path: ObjectPath, + recursionTracker: PathTracker, + origin: DeoptimizableEntity + ): LiteralValueOrUnknown { + if (this.isReassigned) { + return UnknownValue; + } + const knownValue = this.getKnownValue(); + this.expressionsUseTheKnownValue.push(origin); + return recursionTracker.withTrackedEntityAtPath( + path, + knownValue, + () => knownValue.getLiteralValueAtPath(path, recursionTracker, origin), + UnknownValue + ); + } + + hasEffectsOnInteractionAtPath( + path: ObjectPath, + interaction: NodeInteraction, + context: HasEffectsContext + ): boolean { + if (this.isReassigned || interaction.type === INTERACTION_ASSIGNED) { + return super.hasEffectsOnInteractionAtPath(path, interaction, context); + } + const knownValue = this.getKnownValue(); + return knownValue.hasEffectsOnInteractionAtPath(path, interaction, context); + } + deoptimizeArgumentsOnInteractionAtPath(interaction: NodeInteraction, path: ObjectPath): void { // For performance reasons, we fully deoptimize all deeper interactions if ( @@ -98,7 +207,11 @@ export default class ParameterVariable extends LocalVariable { } deoptimizePath(path: ObjectPath): void { - if (path.length === 0 || this.deoptimizedFields.has(UnknownKey)) { + if (path.length === 0) { + this.markReassigned(); + return; + } + if (this.deoptimizedFields.has(UnknownKey)) { return; } const key = path[0]; diff --git a/src/ast/variables/Variable.ts b/src/ast/variables/Variable.ts index 521ff1f9b71..0cd2e40dddb 100644 --- a/src/ast/variables/Variable.ts +++ b/src/ast/variables/Variable.ts @@ -4,8 +4,11 @@ import type { RenderOptions } from '../../utils/renderHelpers'; import type { HasEffectsContext } from '../ExecutionContext'; import type { NodeInteraction } from '../NodeInteractions'; import { INTERACTION_ACCESSED } from '../NodeInteractions'; +import type CallExpression from '../nodes/CallExpression'; import type Identifier from '../nodes/Identifier'; +import * as NodeType from '../nodes/NodeType'; import { ExpressionEntity } from '../nodes/shared/Expression'; +import type { NodeBase } from '../nodes/shared/Node'; import type { VariableKind } from '../nodes/shared/VariableKinds'; import type { ObjectPath } from '../utils/PathTracker'; @@ -16,7 +19,6 @@ export default class Variable extends ExpressionEntity { isId = false; // both NamespaceVariable and ExternalVariable can be namespaces declare isNamespace?: boolean; - isReassigned = false; kind: VariableKind | null = null; declare module?: Module | ExternalModule; renderBaseName: string | null = null; @@ -24,6 +26,11 @@ export default class Variable extends ExpressionEntity { private renderedLikeHoisted?: Variable; + readonly isReassigned = false; + markReassigned() { + (this as { isReassigned: boolean }).isReassigned = true; + } + constructor(public name: string) { super(); } @@ -34,6 +41,28 @@ export default class Variable extends ExpressionEntity { */ addReference(_identifier: Identifier): void {} + private onlyFunctionCallUsed = true; + /** + * Check if the identifier variable is only used as function call + * @returns true if the variable is only used as function call + */ + getOnlyFunctionCallUsed(): boolean { + return this.onlyFunctionCallUsed; + } + + /** + * Collect the places where the identifier variable is used + * @param usedPlace Where the variable is used + */ + addUsedPlace(usedPlace: NodeBase): void { + const isFunctionCall = + usedPlace.parent.type === NodeType.CallExpression && + (usedPlace.parent as CallExpression).callee === usedPlace; + if (!isFunctionCall && usedPlace.parent.type !== NodeType.ExportDefaultDeclaration) { + this.onlyFunctionCallUsed = false; + } + } + /** * Prevent this variable from being renamed to this name to avoid name * collisions diff --git a/test/chunking-form/samples/missing-export-reused-deconflicting/_expected/amd/dep1.js b/test/chunking-form/samples/missing-export-reused-deconflicting/_expected/amd/dep1.js index 1ebb9552c37..c0bb33a6273 100644 --- a/test/chunking-form/samples/missing-export-reused-deconflicting/_expected/amd/dep1.js +++ b/test/chunking-form/samples/missing-export-reused-deconflicting/_expected/amd/dep1.js @@ -5,12 +5,13 @@ define(['exports'], (function (exports) { 'use strict'; console.log('This is the output when a missing export is used internally but not reexported'); function almostUseUnused(useIt) { - if (useIt) { + { + console.log(useIt); console.log(_missingExportShim); } } - almostUseUnused(false); + almostUseUnused(true); exports.missing1 = _missingExportShim; diff --git a/test/chunking-form/samples/missing-export-reused-deconflicting/_expected/cjs/dep1.js b/test/chunking-form/samples/missing-export-reused-deconflicting/_expected/cjs/dep1.js index a024bcd6f7f..31b08b56a1f 100644 --- a/test/chunking-form/samples/missing-export-reused-deconflicting/_expected/cjs/dep1.js +++ b/test/chunking-form/samples/missing-export-reused-deconflicting/_expected/cjs/dep1.js @@ -5,11 +5,12 @@ var _missingExportShim = void 0; console.log('This is the output when a missing export is used internally but not reexported'); function almostUseUnused(useIt) { - if (useIt) { + { + console.log(useIt); console.log(_missingExportShim); } } -almostUseUnused(false); +almostUseUnused(true); exports.missing1 = _missingExportShim; diff --git a/test/chunking-form/samples/missing-export-reused-deconflicting/_expected/es/dep1.js b/test/chunking-form/samples/missing-export-reused-deconflicting/_expected/es/dep1.js index 99f4196bf72..d9c8b65f05c 100644 --- a/test/chunking-form/samples/missing-export-reused-deconflicting/_expected/es/dep1.js +++ b/test/chunking-form/samples/missing-export-reused-deconflicting/_expected/es/dep1.js @@ -3,11 +3,12 @@ var _missingExportShim = void 0; console.log('This is the output when a missing export is used internally but not reexported'); function almostUseUnused(useIt) { - if (useIt) { + { + console.log(useIt); console.log(_missingExportShim); } } -almostUseUnused(false); +almostUseUnused(true); export { _missingExportShim as missing1 }; diff --git a/test/chunking-form/samples/missing-export-reused-deconflicting/_expected/system/dep1.js b/test/chunking-form/samples/missing-export-reused-deconflicting/_expected/system/dep1.js index fb77b0059b6..24aa7ab54ea 100644 --- a/test/chunking-form/samples/missing-export-reused-deconflicting/_expected/system/dep1.js +++ b/test/chunking-form/samples/missing-export-reused-deconflicting/_expected/system/dep1.js @@ -8,12 +8,13 @@ System.register([], (function (exports) { console.log('This is the output when a missing export is used internally but not reexported'); function almostUseUnused(useIt) { - if (useIt) { + { + console.log(useIt); console.log(_missingExportShim); } } - almostUseUnused(false); + almostUseUnused(true); exports("missing1", _missingExportShim); diff --git a/test/chunking-form/samples/missing-export-reused-deconflicting/dep1.js b/test/chunking-form/samples/missing-export-reused-deconflicting/dep1.js index 84e22e0fe8f..f0751ef4cf6 100644 --- a/test/chunking-form/samples/missing-export-reused-deconflicting/dep1.js +++ b/test/chunking-form/samples/missing-export-reused-deconflicting/dep1.js @@ -2,8 +2,9 @@ console.log('This is the output when a missing export is used internally but not function almostUseUnused(useIt) { if (useIt) { + console.log(useIt); console.log(_missingExportShim); } } -almostUseUnused(false); +almostUseUnused(true); diff --git a/test/chunking-form/samples/preserve-modules-commonjs/_expected/amd/_virtual/_commonjsHelpers.js b/test/chunking-form/samples/preserve-modules-commonjs/_expected/amd/_virtual/_commonjsHelpers.js index f35ac4ef0d7..25ed6cf3721 100644 --- a/test/chunking-form/samples/preserve-modules-commonjs/_expected/amd/_virtual/_commonjsHelpers.js +++ b/test/chunking-form/samples/preserve-modules-commonjs/_expected/amd/_virtual/_commonjsHelpers.js @@ -1,7 +1,7 @@ define(['exports'], (function (exports) { 'use strict'; function getDefaultExportFromCjs (x) { - return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x; + return x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x; } exports.getDefaultExportFromCjs = getDefaultExportFromCjs; diff --git a/test/chunking-form/samples/preserve-modules-commonjs/_expected/cjs/_virtual/_commonjsHelpers.js b/test/chunking-form/samples/preserve-modules-commonjs/_expected/cjs/_virtual/_commonjsHelpers.js index ceeaa4d077f..fc314aed4db 100644 --- a/test/chunking-form/samples/preserve-modules-commonjs/_expected/cjs/_virtual/_commonjsHelpers.js +++ b/test/chunking-form/samples/preserve-modules-commonjs/_expected/cjs/_virtual/_commonjsHelpers.js @@ -1,7 +1,7 @@ 'use strict'; function getDefaultExportFromCjs (x) { - return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x; + return x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x; } exports.getDefaultExportFromCjs = getDefaultExportFromCjs; diff --git a/test/chunking-form/samples/preserve-modules-commonjs/_expected/es/_virtual/_commonjsHelpers.js b/test/chunking-form/samples/preserve-modules-commonjs/_expected/es/_virtual/_commonjsHelpers.js index 7b7c5f4f531..5fea8b90999 100644 --- a/test/chunking-form/samples/preserve-modules-commonjs/_expected/es/_virtual/_commonjsHelpers.js +++ b/test/chunking-form/samples/preserve-modules-commonjs/_expected/es/_virtual/_commonjsHelpers.js @@ -1,5 +1,5 @@ function getDefaultExportFromCjs (x) { - return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x; + return x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x; } export { getDefaultExportFromCjs }; diff --git a/test/chunking-form/samples/preserve-modules-commonjs/_expected/system/_virtual/_commonjsHelpers.js b/test/chunking-form/samples/preserve-modules-commonjs/_expected/system/_virtual/_commonjsHelpers.js index dd2c5886a6d..15f3676f92e 100644 --- a/test/chunking-form/samples/preserve-modules-commonjs/_expected/system/_virtual/_commonjsHelpers.js +++ b/test/chunking-form/samples/preserve-modules-commonjs/_expected/system/_virtual/_commonjsHelpers.js @@ -6,7 +6,7 @@ System.register([], (function (exports) { exports("getDefaultExportFromCjs", getDefaultExportFromCjs); function getDefaultExportFromCjs (x) { - return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x; + return x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x; } }) diff --git a/test/form/samples/deopt-string-concatenation/_expected.js b/test/form/samples/deopt-string-concatenation/_expected.js index 68a30dc4f66..42785de7340 100644 --- a/test/form/samples/deopt-string-concatenation/_expected.js +++ b/test/form/samples/deopt-string-concatenation/_expected.js @@ -9,3 +9,4 @@ function parseInt(str, radix) { } console.log(parseInt('1')); +console.log(parseInt(Symbol('1'))); diff --git a/test/form/samples/deopt-string-concatenation/main.js b/test/form/samples/deopt-string-concatenation/main.js index 68a30dc4f66..42785de7340 100644 --- a/test/form/samples/deopt-string-concatenation/main.js +++ b/test/form/samples/deopt-string-concatenation/main.js @@ -9,3 +9,4 @@ function parseInt(str, radix) { } console.log(parseInt('1')); +console.log(parseInt(Symbol('1'))); diff --git a/test/form/samples/request-tree-shaking-before-render/_config.js b/test/form/samples/request-tree-shaking-before-render/_config.js new file mode 100644 index 00000000000..00d3b4e069f --- /dev/null +++ b/test/form/samples/request-tree-shaking-before-render/_config.js @@ -0,0 +1,3 @@ +module.exports = defineTest({ + description: 'a new tree-shaking is required so render will not fail' +}); diff --git a/test/form/samples/request-tree-shaking-before-render/_expected.js b/test/form/samples/request-tree-shaking-before-render/_expected.js new file mode 100644 index 00000000000..f026827564f --- /dev/null +++ b/test/form/samples/request-tree-shaking-before-render/_expected.js @@ -0,0 +1,16 @@ +function s(t) { + t(x); +} + +function f(b) { + return x.concat((b ? 1 : 0)) +} + +function w(b) { + f(b); +} + +w(1); +s(() => { + return w(0) +}); diff --git a/test/form/samples/request-tree-shaking-before-render/main.js b/test/form/samples/request-tree-shaking-before-render/main.js new file mode 100644 index 00000000000..85b8d493594 --- /dev/null +++ b/test/form/samples/request-tree-shaking-before-render/main.js @@ -0,0 +1,16 @@ +function s(t) { + t(x) +} + +function f(b) { + return x.concat((b ? 1 : 0)) +} + +function w(b) { + f(b) +} + +w(1) +s(() => { + return w(0) +}) diff --git a/test/form/samples/side-effect-h/_expected/amd.js b/test/form/samples/side-effect-h/_expected/amd.js index d3a055b343a..e46b7cd7cb9 100644 --- a/test/form/samples/side-effect-h/_expected/amd.js +++ b/test/form/samples/side-effect-h/_expected/amd.js @@ -7,6 +7,7 @@ define((function () { 'use strict'; } foo(); + foo(true); var main = 42; diff --git a/test/form/samples/side-effect-h/_expected/cjs.js b/test/form/samples/side-effect-h/_expected/cjs.js index 1d437295c93..4862e7aa0e7 100644 --- a/test/form/samples/side-effect-h/_expected/cjs.js +++ b/test/form/samples/side-effect-h/_expected/cjs.js @@ -7,6 +7,7 @@ function foo ( ok ) { } foo(); +foo(true); var main = 42; diff --git a/test/form/samples/side-effect-h/_expected/es.js b/test/form/samples/side-effect-h/_expected/es.js index 1d5410d073d..39c215e330c 100644 --- a/test/form/samples/side-effect-h/_expected/es.js +++ b/test/form/samples/side-effect-h/_expected/es.js @@ -5,6 +5,7 @@ function foo ( ok ) { } foo(); +foo(true); var main = 42; diff --git a/test/form/samples/side-effect-h/_expected/iife.js b/test/form/samples/side-effect-h/_expected/iife.js index 083d37914dd..b6fc61fb49b 100644 --- a/test/form/samples/side-effect-h/_expected/iife.js +++ b/test/form/samples/side-effect-h/_expected/iife.js @@ -8,6 +8,7 @@ var myBundle = (function () { } foo(); + foo(true); var main = 42; diff --git a/test/form/samples/side-effect-h/_expected/system.js b/test/form/samples/side-effect-h/_expected/system.js index dcb761aa7ba..6ace3fb0f81 100644 --- a/test/form/samples/side-effect-h/_expected/system.js +++ b/test/form/samples/side-effect-h/_expected/system.js @@ -10,6 +10,7 @@ System.register('myBundle', [], (function (exports) { } foo(); + foo(true); var main = exports("default", 42); diff --git a/test/form/samples/side-effect-h/_expected/umd.js b/test/form/samples/side-effect-h/_expected/umd.js index 770859846a0..f72ccce31f6 100644 --- a/test/form/samples/side-effect-h/_expected/umd.js +++ b/test/form/samples/side-effect-h/_expected/umd.js @@ -11,6 +11,7 @@ } foo(); + foo(true); var main = 42; diff --git a/test/form/samples/side-effect-h/main.js b/test/form/samples/side-effect-h/main.js index 16d73469124..b736c13f944 100644 --- a/test/form/samples/side-effect-h/main.js +++ b/test/form/samples/side-effect-h/main.js @@ -5,5 +5,6 @@ function foo ( ok ) { } foo(); +foo(true); export default 42; diff --git a/test/form/samples/side-effects-return-statements/_expected/amd.js b/test/form/samples/side-effects-return-statements/_expected/amd.js index dcdb995d37a..1633b0ab0e9 100644 --- a/test/form/samples/side-effects-return-statements/_expected/amd.js +++ b/test/form/samples/side-effects-return-statements/_expected/amd.js @@ -8,5 +8,6 @@ define((function () { 'use strict'; } assert.equal( isUsed( true ), 2 ); + assert.equal( isUsed( false ), 1 ); })); diff --git a/test/form/samples/side-effects-return-statements/_expected/cjs.js b/test/form/samples/side-effects-return-statements/_expected/cjs.js index 7dd6e813034..93d4ccf36c7 100644 --- a/test/form/samples/side-effects-return-statements/_expected/cjs.js +++ b/test/form/samples/side-effects-return-statements/_expected/cjs.js @@ -8,3 +8,4 @@ function isUsed ( x ) { } assert.equal( isUsed( true ), 2 ); +assert.equal( isUsed( false ), 1 ); diff --git a/test/form/samples/side-effects-return-statements/_expected/es.js b/test/form/samples/side-effects-return-statements/_expected/es.js index 38f357c97fc..9898b0d0f8b 100644 --- a/test/form/samples/side-effects-return-statements/_expected/es.js +++ b/test/form/samples/side-effects-return-statements/_expected/es.js @@ -6,3 +6,4 @@ function isUsed ( x ) { } assert.equal( isUsed( true ), 2 ); +assert.equal( isUsed( false ), 1 ); diff --git a/test/form/samples/side-effects-return-statements/_expected/iife.js b/test/form/samples/side-effects-return-statements/_expected/iife.js index c9da6fbbcd3..81b64ec8112 100644 --- a/test/form/samples/side-effects-return-statements/_expected/iife.js +++ b/test/form/samples/side-effects-return-statements/_expected/iife.js @@ -9,5 +9,6 @@ } assert.equal( isUsed( true ), 2 ); + assert.equal( isUsed( false ), 1 ); })(); diff --git a/test/form/samples/side-effects-return-statements/_expected/system.js b/test/form/samples/side-effects-return-statements/_expected/system.js index 968b341638b..096bcc73f08 100644 --- a/test/form/samples/side-effects-return-statements/_expected/system.js +++ b/test/form/samples/side-effects-return-statements/_expected/system.js @@ -11,6 +11,7 @@ System.register('myBundle', [], (function () { } assert.equal( isUsed( true ), 2 ); + assert.equal( isUsed( false ), 1 ); }) }; diff --git a/test/form/samples/side-effects-return-statements/_expected/umd.js b/test/form/samples/side-effects-return-statements/_expected/umd.js index 97a5be31699..fc3ff304af8 100644 --- a/test/form/samples/side-effects-return-statements/_expected/umd.js +++ b/test/form/samples/side-effects-return-statements/_expected/umd.js @@ -11,5 +11,6 @@ } assert.equal( isUsed( true ), 2 ); + assert.equal( isUsed( false ), 1 ); })); diff --git a/test/form/samples/side-effects-return-statements/main.js b/test/form/samples/side-effects-return-statements/main.js index 8e6b3b54f4f..a1e68e5d63f 100644 --- a/test/form/samples/side-effects-return-statements/main.js +++ b/test/form/samples/side-effects-return-statements/main.js @@ -15,3 +15,4 @@ function isUsed ( x ) { } assert.equal( isUsed( true ), 2 ); +assert.equal( isUsed( false ), 1 ); diff --git a/test/form/samples/supports-es5-shim/_expected.js b/test/form/samples/supports-es5-shim/_expected.js index bc68debf94b..645169277b6 100644 --- a/test/form/samples/supports-es5-shim/_expected.js +++ b/test/form/samples/supports-es5-shim/_expected.js @@ -2202,7 +2202,6 @@ var es5Shim = {exports: {}}; // eslint-disable-next-line no-global-assign, no-implicit-globals parseInt = (function (origParseInt) { return function parseInt(str, radix) { - if (this instanceof parseInt) { new origParseInt(); } // eslint-disable-line new-cap, no-new, max-statements-per-line var string = trim(String(str)); var defaultedRadix = $Number(radix) || (hexRegex.test(string) ? 16 : 10); return origParseInt(string, defaultedRadix); @@ -2233,7 +2232,6 @@ var es5Shim = {exports: {}}; // eslint-disable-next-line no-global-assign, no-implicit-globals parseInt = (function (origParseInt) { return function parseInt(str, radix) { - if (this instanceof parseInt) { new origParseInt(); } // eslint-disable-line new-cap, no-new, max-statements-per-line var isSym = typeof str === 'symbol'; if (!isSym && str && typeof str === 'object') { try { diff --git a/test/form/samples/supports-es6-shim/_expected.js b/test/form/samples/supports-es6-shim/_expected.js index ae2951820eb..84fb3b7f09d 100644 --- a/test/form/samples/supports-es6-shim/_expected.js +++ b/test/form/samples/supports-es6-shim/_expected.js @@ -2057,7 +2057,7 @@ var es6Shim = {exports: {}}; }; var withinULPDistance = function withinULPDistance(result, expected, distance) { - return _abs(1 - (result / expected)) / Number.EPSILON < (distance || 8); + return _abs(1 - (result / expected)) / Number.EPSILON < (8); }; defineProperties(Math, MathShims); diff --git a/test/form/samples/tree-shake-literal-parameter/argument-assignment/_config.js b/test/form/samples/tree-shake-literal-parameter/argument-assignment/_config.js new file mode 100644 index 00000000000..1a210baea05 --- /dev/null +++ b/test/form/samples/tree-shake-literal-parameter/argument-assignment/_config.js @@ -0,0 +1,3 @@ +module.exports = defineTest({ + description: 'argument assignment side-effect' +}); diff --git a/test/form/samples/tree-shake-literal-parameter/argument-assignment/_expected.js b/test/form/samples/tree-shake-literal-parameter/argument-assignment/_expected.js new file mode 100644 index 00000000000..f7cb2c16d5a --- /dev/null +++ b/test/form/samples/tree-shake-literal-parameter/argument-assignment/_expected.js @@ -0,0 +1,15 @@ +function add(a, b) { + let sum = 0; + for (let i = a; i < b; i++) { + sum += i; + } + return sum; +} + +function foo(a) { + // don't optimize to '0;' + a = 0; + console.log(a); +} + +console.log(foo(add(0, 100))); diff --git a/test/form/samples/tree-shake-literal-parameter/argument-assignment/main.js b/test/form/samples/tree-shake-literal-parameter/argument-assignment/main.js new file mode 100644 index 00000000000..d9b3646538f --- /dev/null +++ b/test/form/samples/tree-shake-literal-parameter/argument-assignment/main.js @@ -0,0 +1,15 @@ +function add(a, b) { + let sum = 0; + for (let i = a; i < b; i++) { + sum += i; + } + return sum; +} + +function foo(a) { + // don't optimize to '0;' + a = 0; + console.log(a) +} + +console.log(foo(add(0, 100))) diff --git a/test/form/samples/tree-shake-literal-parameter/do-not-optimize/_config.js b/test/form/samples/tree-shake-literal-parameter/do-not-optimize/_config.js new file mode 100644 index 00000000000..d16519b0931 --- /dev/null +++ b/test/form/samples/tree-shake-literal-parameter/do-not-optimize/_config.js @@ -0,0 +1,3 @@ +module.exports = defineTest({ + description: 'do not tree-shake literal parameter cases' +}); diff --git a/test/form/samples/tree-shake-literal-parameter/do-not-optimize/_expected.js b/test/form/samples/tree-shake-literal-parameter/do-not-optimize/_expected.js new file mode 100644 index 00000000000..8e1230c7540 --- /dev/null +++ b/test/form/samples/tree-shake-literal-parameter/do-not-optimize/_expected.js @@ -0,0 +1,18 @@ +function foo(enable) { + console.log(enable ? 1 : 0); +} + +foo(1); + +function bar(f = foo) { + f(0); +} + +// global variable +g = function (enable) { + console.log(enable ? 1: 0); +}; + +g(1); + +export { bar }; diff --git a/test/form/samples/tree-shake-literal-parameter/do-not-optimize/main.js b/test/form/samples/tree-shake-literal-parameter/do-not-optimize/main.js new file mode 100644 index 00000000000..079d60a1be5 --- /dev/null +++ b/test/form/samples/tree-shake-literal-parameter/do-not-optimize/main.js @@ -0,0 +1,16 @@ +function foo(enable) { + console.log(enable ? 1 : 0); +} + +foo(1); + +export function bar(f = foo) { + f(0); +} + +// global variable +g = function (enable) { + console.log(enable ? 1: 0); +}; + +g(1); diff --git a/test/form/samples/tree-shake-literal-parameter/expression-cache/_config.js b/test/form/samples/tree-shake-literal-parameter/expression-cache/_config.js new file mode 100644 index 00000000000..697aea54b46 --- /dev/null +++ b/test/form/samples/tree-shake-literal-parameter/expression-cache/_config.js @@ -0,0 +1,3 @@ +module.exports = defineTest({ + description: 'should update cache of logical and conditional expressions' +}); diff --git a/test/form/samples/tree-shake-literal-parameter/expression-cache/_expected.js b/test/form/samples/tree-shake-literal-parameter/expression-cache/_expected.js new file mode 100644 index 00000000000..8cee561823a --- /dev/null +++ b/test/form/samples/tree-shake-literal-parameter/expression-cache/_expected.js @@ -0,0 +1,14 @@ +function add1(a, b, enable) { + { + return a + b; + } +} + +function add2(a, b, enable) { + { + return a + b; + } +} + +console.log(add1(1, 2)); +console.log(add2(1, 2)); diff --git a/test/form/samples/tree-shake-literal-parameter/expression-cache/main.js b/test/form/samples/tree-shake-literal-parameter/expression-cache/main.js new file mode 100644 index 00000000000..4c1f739a1d5 --- /dev/null +++ b/test/form/samples/tree-shake-literal-parameter/expression-cache/main.js @@ -0,0 +1,16 @@ +function add1(a, b, enable) { + if (enable? true: false) { + return a + b; + } + return a - b; +} + +function add2(a, b, enable) { + if (enable && 1) { + return a + b; + } + return a - b; +} + +console.log(add1(1, 2, true)); +console.log(add2(1, 2, true)); \ No newline at end of file diff --git a/test/form/samples/tree-shake-literal-parameter/iife/_config.js b/test/form/samples/tree-shake-literal-parameter/iife/_config.js new file mode 100644 index 00000000000..789458177ac --- /dev/null +++ b/test/form/samples/tree-shake-literal-parameter/iife/_config.js @@ -0,0 +1,3 @@ +module.exports = defineTest({ + description: 'tree-shake literal parameter for IIFE' +}); diff --git a/test/form/samples/tree-shake-literal-parameter/iife/_expected.js b/test/form/samples/tree-shake-literal-parameter/iife/_expected.js new file mode 100644 index 00000000000..3e6eaeced15 --- /dev/null +++ b/test/form/samples/tree-shake-literal-parameter/iife/_expected.js @@ -0,0 +1,32 @@ +const result1 = ((enable) => { + { + return 'enabled'; + } +})(); + +const result2 = (function (enable) { + { + return 'enabled'; + } +})(); + +const result3 = (function foo (enable) { + { + return 'enabled'; + } +})(); + +// lose track of iife +const result4 = (function foo (enable) { + if (enable) { + unknown_global_function(foo); + return 'enabled'; + } else { + return 'disabled'; + } +})(true); + +console.log(result1); +console.log(result2); +console.log(result3); +console.log(result4); diff --git a/test/form/samples/tree-shake-literal-parameter/iife/main.js b/test/form/samples/tree-shake-literal-parameter/iife/main.js new file mode 100644 index 00000000000..844a193c3ff --- /dev/null +++ b/test/form/samples/tree-shake-literal-parameter/iife/main.js @@ -0,0 +1,38 @@ +const result1 = ((enable) => { + if (enable) { + return 'enabled'; + } else { + return 'disabled'; + } +})(true); + +const result2 = (function (enable) { + if (enable) { + return 'enabled'; + } else { + return 'disabled'; + } +})(true); + +const result3 = (function foo (enable) { + if (enable) { + return 'enabled'; + } else { + return 'disabled'; + } +})(true); + +// lose track of iife +const result4 = (function foo (enable) { + if (enable) { + unknown_global_function(foo); + return 'enabled'; + } else { + return 'disabled'; + } +})(true); + +console.log(result1); +console.log(result2); +console.log(result3); +console.log(result4); diff --git a/test/form/samples/tree-shake-literal-parameter/import-function/_config.js b/test/form/samples/tree-shake-literal-parameter/import-function/_config.js new file mode 100644 index 00000000000..e4a54b5f90f --- /dev/null +++ b/test/form/samples/tree-shake-literal-parameter/import-function/_config.js @@ -0,0 +1,3 @@ +module.exports = defineTest({ + description: 'set parameters to literal if all calls are literal' +}); diff --git a/test/form/samples/tree-shake-literal-parameter/import-function/_expected.js b/test/form/samples/tree-shake-literal-parameter/import-function/_expected.js new file mode 100644 index 00000000000..f9d8a0fd937 --- /dev/null +++ b/test/form/samples/tree-shake-literal-parameter/import-function/_expected.js @@ -0,0 +1,70 @@ +// export default +function add (a, b, enable) { + { + return a + b; + } +} + +function add1(a, b, enable) { + return a - b; +} + +function add2(a, b, enable) { + return a - b; +} + +// keep it +function add3(a, b, enable) { + if (enable) { + return a + b; + } + return a - b; +} + +// conditional expression +function add4(a, b, enable) { + { + return a + b; + } +} + +// export default +var arrowAdd = (a, b, enable) => { + { + return a + b; + } +}; + +const arrowAdd1 = (a, b, enable) => { + return a - b; +}; + +// keep it +const arrowAdd2 = (a, b, enable) => { + if (enable) { + return a + b; + } + return a - b; +}; + +// conditional expression +const arrowAdd3 = (a, b, enable) => { + { + return a + b; + } +}; + +function foo(bar) { + console.log(bar()); +} + +console.log(add(1, 2)); +console.log(add1(1, 2)); +console.log(add2(1, 2)); // unused argument should be treated as undefined +console.log(foo(add3)); +console.log(add4(1, 2)); + +console.log(arrowAdd(1, 2)); +console.log(arrowAdd1(1, 2)); +console.log(foo(arrowAdd2)); +console.log(arrowAdd3(1, 2)); diff --git a/test/form/samples/tree-shake-literal-parameter/import-function/arrow_lib.js b/test/form/samples/tree-shake-literal-parameter/import-function/arrow_lib.js new file mode 100644 index 00000000000..8b85efc44a7 --- /dev/null +++ b/test/form/samples/tree-shake-literal-parameter/import-function/arrow_lib.js @@ -0,0 +1,30 @@ +// export default +export default (a, b, enable) => { + if (enable) { + return a + b; + } + return a - b; +} + +export const arrowAdd1 = (a, b, enable) => { + if (enable) { + return a + b; + } + return a - b; +} + +// keep it +export const arrowAdd2 = (a, b, enable) => { + if (enable) { + return a + b; + } + return a - b; +} + +// conditional expression +export const arrowAdd3 = (a, b, enable) => { + if (enable? true: false) { + return a + b; + } + return a - b; +} diff --git a/test/form/samples/tree-shake-literal-parameter/import-function/lib.js b/test/form/samples/tree-shake-literal-parameter/import-function/lib.js new file mode 100644 index 00000000000..be4b8a63c76 --- /dev/null +++ b/test/form/samples/tree-shake-literal-parameter/import-function/lib.js @@ -0,0 +1,37 @@ +// export default +export default function (a, b, enable) { + if (enable) { + return a + b; + } + return a - b; +} + +export function add1(a, b, enable) { + if (enable) { + return a + b; + } + return a - b; +} + +export function add2(a, b, enable) { + if (enable) { + return a + b; + } + return a - b; +} + +// keep it +export function add3(a, b, enable) { + if (enable) { + return a + b; + } + return a - b; +} + +// conditional expression +export function add4(a, b, enable) { + if (enable? true: false) { + return a + b; + } + return a - b; +} diff --git a/test/form/samples/tree-shake-literal-parameter/import-function/main.js b/test/form/samples/tree-shake-literal-parameter/import-function/main.js new file mode 100644 index 00000000000..fcd0b9dcbe1 --- /dev/null +++ b/test/form/samples/tree-shake-literal-parameter/import-function/main.js @@ -0,0 +1,17 @@ +import add, { add1, add2, add3, add4 } from './lib.js' +import arrowAdd, { arrowAdd1, arrowAdd2, arrowAdd3 } from './arrow_lib.js' + +function foo(bar) { + console.log(bar()); +} + +console.log(add(1, 2, true)); +console.log(add1(1, 2, false)); +console.log(add2(1, 2)); // unused argument should be treated as undefined +console.log(foo(add3)) +console.log(add4(1, 2, true)); + +console.log(arrowAdd(1, 2, true)); +console.log(arrowAdd1(1, 2, false)); +console.log(foo(arrowAdd2)); +console.log(arrowAdd3(1, 2, true)); \ No newline at end of file diff --git a/test/form/samples/tree-shake-literal-parameter/indirect-import-function/_config.js b/test/form/samples/tree-shake-literal-parameter/indirect-import-function/_config.js new file mode 100644 index 00000000000..43454f74be2 --- /dev/null +++ b/test/form/samples/tree-shake-literal-parameter/indirect-import-function/_config.js @@ -0,0 +1,3 @@ +module.exports = defineTest({ + description: 'parameters of indirect function import should also be tree-shaken' +}); diff --git a/test/form/samples/tree-shake-literal-parameter/indirect-import-function/_expected.js b/test/form/samples/tree-shake-literal-parameter/indirect-import-function/_expected.js new file mode 100644 index 00000000000..39872a4cff7 --- /dev/null +++ b/test/form/samples/tree-shake-literal-parameter/indirect-import-function/_expected.js @@ -0,0 +1,14 @@ +function importThenExportSub(enable) { + { + return 'importThenExportSub'; + } +} + +function defineAndExportDefault(enable) { + { + return 'defineAndExportDefault'; + } +} + +console.log(importThenExportSub()); +console.log(defineAndExportDefault()); diff --git a/test/form/samples/tree-shake-literal-parameter/indirect-import-function/define_and_export_default.js b/test/form/samples/tree-shake-literal-parameter/indirect-import-function/define_and_export_default.js new file mode 100644 index 00000000000..b52e1846d0f --- /dev/null +++ b/test/form/samples/tree-shake-literal-parameter/indirect-import-function/define_and_export_default.js @@ -0,0 +1,7 @@ +function defineAndExportDefault(enable) { + if (enable) { + return 'defineAndExportDefault'; + } +} + +export default defineAndExportDefault; diff --git a/test/form/samples/tree-shake-literal-parameter/indirect-import-function/import_then_export.js b/test/form/samples/tree-shake-literal-parameter/indirect-import-function/import_then_export.js new file mode 100644 index 00000000000..30e7e2b911d --- /dev/null +++ b/test/form/samples/tree-shake-literal-parameter/indirect-import-function/import_then_export.js @@ -0,0 +1,3 @@ +import { importThenExportSub } from './import_then_export_sub.js' + +export { importThenExportSub } diff --git a/test/form/samples/tree-shake-literal-parameter/indirect-import-function/import_then_export_sub.js b/test/form/samples/tree-shake-literal-parameter/indirect-import-function/import_then_export_sub.js new file mode 100644 index 00000000000..2afc494681e --- /dev/null +++ b/test/form/samples/tree-shake-literal-parameter/indirect-import-function/import_then_export_sub.js @@ -0,0 +1,5 @@ +export function importThenExportSub(enable) { + if (enable) { + return 'importThenExportSub'; + } +} diff --git a/test/form/samples/tree-shake-literal-parameter/indirect-import-function/main.js b/test/form/samples/tree-shake-literal-parameter/indirect-import-function/main.js new file mode 100644 index 00000000000..ad0e9fb2639 --- /dev/null +++ b/test/form/samples/tree-shake-literal-parameter/indirect-import-function/main.js @@ -0,0 +1,5 @@ +import { importThenExportSub } from './import_then_export.js' +import defineAndExportDefault from './define_and_export_default.js' + +console.log(importThenExportSub(true)) +console.log(defineAndExportDefault(true)) diff --git a/test/form/samples/tree-shake-literal-parameter/recursion-literal-object/_config.js b/test/form/samples/tree-shake-literal-parameter/recursion-literal-object/_config.js new file mode 100644 index 00000000000..251df7a6a12 --- /dev/null +++ b/test/form/samples/tree-shake-literal-parameter/recursion-literal-object/_config.js @@ -0,0 +1,3 @@ +module.exports = defineTest({ + description: 'function parameters tree-shaken should be recursive if literal' +}); diff --git a/test/form/samples/tree-shake-literal-parameter/recursion-literal-object/_expected.js b/test/form/samples/tree-shake-literal-parameter/recursion-literal-object/_expected.js new file mode 100644 index 00000000000..f909ce360ec --- /dev/null +++ b/test/form/samples/tree-shake-literal-parameter/recursion-literal-object/_expected.js @@ -0,0 +1,22 @@ +function fun1(options) { + { + return 'fun1'; + } +} + +function fun2(options) { + { + return fun1(); + } +} + +function fun4(options) { + { + console.log('func4'); + } +} + +console.log( + fun2(), + fun4() +); diff --git a/test/form/samples/tree-shake-literal-parameter/recursion-literal-object/main.js b/test/form/samples/tree-shake-literal-parameter/recursion-literal-object/main.js new file mode 100644 index 00000000000..4f845cc4e2c --- /dev/null +++ b/test/form/samples/tree-shake-literal-parameter/recursion-literal-object/main.js @@ -0,0 +1,40 @@ +function fun1(options) { + if (options.enable) { + return 'fun1'; + } else { + console.log('func1'); + } +} + +function fun2(options) { + if (options.enable) { + return fun1(options); + } else { + console.log('func2'); + } +} + +function fun3(options) { + if (options.enable) { + return 'fun3'; + } else { + console.log('func3'); + } +} + +function fun4(options) { + if (options.enable) { + return fun3(options); + } else { + console.log('func4'); + } +} + +console.log( + fun2({ + enable: true + }), + fun4({ + enable: false + }) +); diff --git a/test/form/samples/tree-shake-literal-parameter/recursion-literal/_config.js b/test/form/samples/tree-shake-literal-parameter/recursion-literal/_config.js new file mode 100644 index 00000000000..251df7a6a12 --- /dev/null +++ b/test/form/samples/tree-shake-literal-parameter/recursion-literal/_config.js @@ -0,0 +1,3 @@ +module.exports = defineTest({ + description: 'function parameters tree-shaken should be recursive if literal' +}); diff --git a/test/form/samples/tree-shake-literal-parameter/recursion-literal/_expected.js b/test/form/samples/tree-shake-literal-parameter/recursion-literal/_expected.js new file mode 100644 index 00000000000..2fcab0594bc --- /dev/null +++ b/test/form/samples/tree-shake-literal-parameter/recursion-literal/_expected.js @@ -0,0 +1,13 @@ +function fun1(enable) { + { + return 'fun1'; + } +} + +function fun2(enable) { + { + return fun1(); + } +} + +console.log(fun2()); \ No newline at end of file diff --git a/test/form/samples/tree-shake-literal-parameter/recursion-literal/main.js b/test/form/samples/tree-shake-literal-parameter/recursion-literal/main.js new file mode 100644 index 00000000000..1187cc47fb3 --- /dev/null +++ b/test/form/samples/tree-shake-literal-parameter/recursion-literal/main.js @@ -0,0 +1,13 @@ +function fun1(enable) { + if (enable) { + return 'fun1'; + } +} + +function fun2(enable) { + if (enable) { + return fun1(enable); + } +} + +console.log(fun2(true)); \ No newline at end of file diff --git a/test/form/samples/tree-shake-literal-parameter/reexport-function/_config.js b/test/form/samples/tree-shake-literal-parameter/reexport-function/_config.js new file mode 100644 index 00000000000..74efd8ee3ad --- /dev/null +++ b/test/form/samples/tree-shake-literal-parameter/reexport-function/_config.js @@ -0,0 +1,3 @@ +module.exports = defineTest({ + description: 'function parameters tree-shaken should support reexport' +}); diff --git a/test/form/samples/tree-shake-literal-parameter/reexport-function/_expected.js b/test/form/samples/tree-shake-literal-parameter/reexport-function/_expected.js new file mode 100644 index 00000000000..fd105290ab6 --- /dev/null +++ b/test/form/samples/tree-shake-literal-parameter/reexport-function/_expected.js @@ -0,0 +1,7 @@ +function module2 (enable) { + { + return 'module2' + } +} + +console.log(module2()); \ No newline at end of file diff --git a/test/form/samples/tree-shake-literal-parameter/reexport-function/main.js b/test/form/samples/tree-shake-literal-parameter/reexport-function/main.js new file mode 100644 index 00000000000..c83b279bb08 --- /dev/null +++ b/test/form/samples/tree-shake-literal-parameter/reexport-function/main.js @@ -0,0 +1,3 @@ +import { module2 } from './module1.js' + +console.log(module2(true)) \ No newline at end of file diff --git a/test/form/samples/tree-shake-literal-parameter/reexport-function/module1.js b/test/form/samples/tree-shake-literal-parameter/reexport-function/module1.js new file mode 100644 index 00000000000..01e152738e8 --- /dev/null +++ b/test/form/samples/tree-shake-literal-parameter/reexport-function/module1.js @@ -0,0 +1 @@ +export * from './module2.js'; \ No newline at end of file diff --git a/test/form/samples/tree-shake-literal-parameter/reexport-function/module2.js b/test/form/samples/tree-shake-literal-parameter/reexport-function/module2.js new file mode 100644 index 00000000000..c4f4cacafd1 --- /dev/null +++ b/test/form/samples/tree-shake-literal-parameter/reexport-function/module2.js @@ -0,0 +1,5 @@ +export function module2 (enable) { + if (enable) { + return 'module2' + } +} \ No newline at end of file diff --git a/test/form/samples/tree-shake-literal-parameter/side-effect/_config.js b/test/form/samples/tree-shake-literal-parameter/side-effect/_config.js new file mode 100644 index 00000000000..e06ff5cdc74 --- /dev/null +++ b/test/form/samples/tree-shake-literal-parameter/side-effect/_config.js @@ -0,0 +1,3 @@ +module.exports = defineTest({ + description: 'test tree-shake-literal-parameter with side-effect' +}); diff --git a/test/form/samples/tree-shake-literal-parameter/side-effect/_expected.js b/test/form/samples/tree-shake-literal-parameter/side-effect/_expected.js new file mode 100644 index 00000000000..77275663462 --- /dev/null +++ b/test/form/samples/tree-shake-literal-parameter/side-effect/_expected.js @@ -0,0 +1,63 @@ +function foo1() { + return 1; +} + +function bar1(foo) { + console.log(foo()); +} + +function bar2(foo) { +} + +// not pure, preserve +function foo3() { + console.log(1); +} + +function bar3(foo) { + foo(); +} + +console.log(bar1(foo1), bar2(), bar3(foo3)); + +const options = { + enable: 1 +}; + +const options2 = { + enable: 1 +}; + +function calledWithSameVariable(options) { + { + return 'enabled'; + } +} + +function calledWithDifferentVariable(options) { + if (options.enable) { + return 'enabled'; + } else { + return 'disabled'; + } +} + +// forward hasEffects to `options` +console.log(calledWithSameVariable(), calledWithSameVariable()); +// no optimization +console.log(calledWithDifferentVariable(options), calledWithDifferentVariable(options2)); + +const optionsBeModified = { + enable: 1 +}; + +function calledWithModifiedVariable(options) { + if (options.enable) { + return 'enabled'; + } else { + return 'disabled'; + } +} + +console.log(calledWithModifiedVariable(optionsBeModified)); +optionsBeModified.enable = 0; diff --git a/test/form/samples/tree-shake-literal-parameter/side-effect/main.js b/test/form/samples/tree-shake-literal-parameter/side-effect/main.js new file mode 100644 index 00000000000..d7f3ba00148 --- /dev/null +++ b/test/form/samples/tree-shake-literal-parameter/side-effect/main.js @@ -0,0 +1,71 @@ +function foo1() { + return 1; +} + +function bar1(foo) { + console.log(foo()) +} + +// pure, can be tree-shaken +function foo2() { + return 1; +} + +function bar2(foo) { + foo() +} + +// not pure, preserve +function foo3() { + console.log(1); +} + +function bar3(foo) { + foo() +} + +console.log(bar1(foo1), bar2(foo2), bar3(foo3)) + +const options = { + enable: 1 +} + +const options2 = { + enable: 1 +} + +function calledWithSameVariable(options) { + if (options.enable) { + return 'enabled'; + } else { + return 'disabled'; + } +} + +function calledWithDifferentVariable(options) { + if (options.enable) { + return 'enabled'; + } else { + return 'disabled'; + } +} + +// forward hasEffects to `options` +console.log(calledWithSameVariable(options), calledWithSameVariable(options)) +// no optimization +console.log(calledWithDifferentVariable(options), calledWithDifferentVariable(options2)) + +const optionsBeModified = { + enable: 1 +} + +function calledWithModifiedVariable(options) { + if (options.enable) { + return 'enabled'; + } else { + return 'disabled'; + } +} + +console.log(calledWithModifiedVariable(optionsBeModified)) +optionsBeModified.enable = 0; diff --git a/test/form/samples/tree-shake-literal-parameter/top-level/_config.js b/test/form/samples/tree-shake-literal-parameter/top-level/_config.js new file mode 100644 index 00000000000..027f90cf36d --- /dev/null +++ b/test/form/samples/tree-shake-literal-parameter/top-level/_config.js @@ -0,0 +1,3 @@ +module.exports = defineTest({ + description: 'Top level exports and global variables should not be optimized' +}); diff --git a/test/form/samples/tree-shake-literal-parameter/top-level/_expected.js b/test/form/samples/tree-shake-literal-parameter/top-level/_expected.js new file mode 100644 index 00000000000..70d050ab798 --- /dev/null +++ b/test/form/samples/tree-shake-literal-parameter/top-level/_expected.js @@ -0,0 +1,23 @@ +function foo(x) { + // The exported function might also be called with "false" + if (x) console.log('true'); + else console.log('false'); +} + +// global variable should not be optimized +foo2 = (x) => { + if (x) console.log('true'); + else console.log('false'); +}; + +// export default should not be optimized +var main = foo3 = (x) => { + if (x) console.log('true'); + else console.log('false'); +}; + +foo(true); +foo2(true); +foo3(true); + +export { main as default, foo }; diff --git a/test/form/samples/tree-shake-literal-parameter/top-level/main.js b/test/form/samples/tree-shake-literal-parameter/top-level/main.js new file mode 100644 index 00000000000..9f706606d6f --- /dev/null +++ b/test/form/samples/tree-shake-literal-parameter/top-level/main.js @@ -0,0 +1,23 @@ +function foo(x) { + // The exported function might also be called with "false" + if (x) console.log('true'); + else console.log('false'); +} + +// global variable should not be optimized +foo2 = (x) => { + if (x) console.log('true'); + else console.log('false'); +} + +// export default should not be optimized +export default foo3 = (x) => { + if (x) console.log('true'); + else console.log('false'); +} + +foo(true); +foo2(true); +foo3(true); + +export { foo }; diff --git a/test/form/samples/try-statement-deoptimization/deactivate-via-option/_expected.js b/test/form/samples/try-statement-deoptimization/deactivate-via-option/_expected.js index a8d430c8fbb..7a1891aaca5 100644 --- a/test/form/samples/try-statement-deoptimization/deactivate-via-option/_expected.js +++ b/test/form/samples/try-statement-deoptimization/deactivate-via-option/_expected.js @@ -6,13 +6,11 @@ const mutated = {}; function test(callback) { try { - callback(); mutate(mutated); } catch {} } -test(() => { -}); +test(); try {} finally { console.log('retained'); diff --git a/test/function/samples/acorn-walk/_config.js b/test/function/samples/acorn-walk/_config.js new file mode 100644 index 00000000000..02a5c20f5dd --- /dev/null +++ b/test/function/samples/acorn-walk/_config.js @@ -0,0 +1,3 @@ +module.exports = defineTest({ + description: 'in acorn walk, immediate function lose track so we do not optimize parameter' +}); diff --git a/test/function/samples/acorn-walk/main.js b/test/function/samples/acorn-walk/main.js new file mode 100644 index 00000000000..32da4905193 --- /dev/null +++ b/test/function/samples/acorn-walk/main.js @@ -0,0 +1,26 @@ +let found = false; + +const base = { + ExpressionStatement(node, c) { + c(node.value, "Expression"); + }, + Expression() { }, + Identifier() { } +}; + +function simple(node, visitors, baseVisitor) { + if (!baseVisitor) baseVisitor = base + ; (function c(node, override) { + let type = override || node.type + baseVisitor[type](node, c) + if (visitors[type]) visitors[type](node) + })(node) +} + +simple({ type: "ExpressionStatement", value: { type: "Identifier" } }, { + Expression(node) { + found = true; + } +}); + +assert.equal(found, true); diff --git a/test/function/samples/reassigned-parameter-side-effect/_config.js b/test/function/samples/reassigned-parameter-side-effect/_config.js new file mode 100644 index 00000000000..a1ffc02b258 --- /dev/null +++ b/test/function/samples/reassigned-parameter-side-effect/_config.js @@ -0,0 +1,3 @@ +module.exports = defineTest({ + description: 'when a parameter is reassigned, hasEffectsOnInteractionAtPath returns true' +}); diff --git a/test/function/samples/reassigned-parameter-side-effect/main.js b/test/function/samples/reassigned-parameter-side-effect/main.js new file mode 100644 index 00000000000..b8d99e0d21b --- /dev/null +++ b/test/function/samples/reassigned-parameter-side-effect/main.js @@ -0,0 +1,14 @@ +function foo(a) { + a.x; +} +let sideEffect = false +foo({ + x: 1 +}); +foo({ + get x() { + sideEffect = true + }, +}); + +assert.equal(sideEffect, true) diff --git a/test/function/samples/reassigned-parameter/_config.js b/test/function/samples/reassigned-parameter/_config.js new file mode 100644 index 00000000000..5923f0afe5d --- /dev/null +++ b/test/function/samples/reassigned-parameter/_config.js @@ -0,0 +1,3 @@ +module.exports = defineTest({ + description: 'parameters reassigned/updated should be detected' +}); diff --git a/test/function/samples/reassigned-parameter/main.js b/test/function/samples/reassigned-parameter/main.js new file mode 100644 index 00000000000..f2c6fe7d3f6 --- /dev/null +++ b/test/function/samples/reassigned-parameter/main.js @@ -0,0 +1,21 @@ +function f(a) { + assert.equal(a ? 'OK' : 'FAIL', 'OK'); + a = false; + assert.equal(a ? 'FAIL' : 'OK', 'OK'); +} + +f(true); + +function g(array) { + if (array === null) { + array = []; + } + + if (array) { + return 'OK'; + } + return array; +} + +assert.equal(g(null), 'OK'); +