diff --git a/docs/999-big-list-of-options.md b/docs/999-big-list-of-options.md
index 9db70067512..33ce54fa8d4 100755
--- a/docs/999-big-list-of-options.md
+++ b/docs/999-big-list-of-options.md
@@ -1421,20 +1421,23 @@ Type: `boolean`
CLI: `--treeshake.correctVarValueBeforeDeclaration`/`--no-treeshake.correctVarValueBeforeDeclaration`
Default: `false`
-If a variable is assigned a value in its declaration and is never reassigned, Rollup assumes the value to be constant. This is not true if the variable is declared with `var`, however, as those variables can be accessed before their declaration where they will evaluate to `undefined`.
+If a variable is assigned a value in its declaration and is never reassigned, Rollup sometimes assumes the value to be constant. This is not true if the variable is declared with `var`, however, as those variables can be accessed before their declaration where they will evaluate to `undefined`.
Choosing `true` will make sure Rollup does not make (wrong) assumptions about the value of such variables. Note though that this can have a noticeable negative impact on tree-shaking results.
```js
// input
-if (x) console.log('not executed');
-var x = true;
+if (Math.random() < 0.5) var x = true;
+if (!x) {
+ console.log('effect');
+}
-// output with treeshake.correctVarValueBeforeDeclaration === false
-console.log('not executed');
+// no output with treeshake.correctVarValueBeforeDeclaration === false
// output with treeshake.correctVarValueBeforeDeclaration === true
-if (x) console.log('not executed');
-var x = true;
+if (Math.random() < 0.5) var x = true;
+if (!x) {
+ console.log('effect');
+}
```
**treeshake.moduleSideEffects**
diff --git a/src/Graph.ts b/src/Graph.ts
index a1643bb42e3..67b423bf724 100644
--- a/src/Graph.ts
+++ b/src/Graph.ts
@@ -183,11 +183,7 @@ export default class Graph {
private includeStatements() {
for (const module of [...this.entryModules, ...this.implicitEntryModules]) {
- if (module.preserveSignature !== false) {
- module.includeAllExports(false);
- } else {
- markModuleAndImpureDependenciesAsExecuted(module);
- }
+ markModuleAndImpureDependenciesAsExecuted(module);
}
if (this.options.treeshake) {
let treeshakingPass = 1;
@@ -203,6 +199,16 @@ export default class Graph {
}
}
}
+ if (treeshakingPass === 1) {
+ // We only include exports after the first pass to avoid issues with
+ // the TDZ detection logic
+ for (const module of [...this.entryModules, ...this.implicitEntryModules]) {
+ if (module.preserveSignature !== false) {
+ module.includeAllExports(false);
+ this.needsTreeshakingPass = true;
+ }
+ }
+ }
timeEnd(`treeshaking pass ${treeshakingPass++}`, 3);
} while (this.needsTreeshakingPass);
} else {
diff --git a/src/Module.ts b/src/Module.ts
index 531872eb121..3cd879cc055 100644
--- a/src/Module.ts
+++ b/src/Module.ts
@@ -587,8 +587,8 @@ export default class Module {
includeAllExports(includeNamespaceMembers: boolean): void {
if (!this.isExecuted) {
- this.graph.needsTreeshakingPass = true;
markModuleAndImpureDependenciesAsExecuted(this);
+ this.graph.needsTreeshakingPass = true;
}
for (const exportName of this.getExports()) {
diff --git a/src/ast/nodes/ArrayPattern.ts b/src/ast/nodes/ArrayPattern.ts
index 5d1e440c983..8b737f6480c 100644
--- a/src/ast/nodes/ArrayPattern.ts
+++ b/src/ast/nodes/ArrayPattern.ts
@@ -50,4 +50,12 @@ export default class ArrayPattern extends NodeBase implements PatternNode {
}
return false;
}
+
+ markDeclarationReached(): void {
+ for (const element of this.elements) {
+ if (element !== null) {
+ element.markDeclarationReached();
+ }
+ }
+ }
}
diff --git a/src/ast/nodes/AssignmentPattern.ts b/src/ast/nodes/AssignmentPattern.ts
index 9386e07f325..22b148f4880 100644
--- a/src/ast/nodes/AssignmentPattern.ts
+++ b/src/ast/nodes/AssignmentPattern.ts
@@ -35,6 +35,10 @@ export default class AssignmentPattern extends NodeBase implements PatternNode {
return path.length > 0 || this.left.hasEffectsWhenAssignedAtPath(EMPTY_PATH, context);
}
+ markDeclarationReached(): void {
+ this.left.markDeclarationReached();
+ }
+
render(
code: MagicString,
options: RenderOptions,
diff --git a/src/ast/nodes/CallExpression.ts b/src/ast/nodes/CallExpression.ts
index e7df7509e6b..25875e511b2 100644
--- a/src/ast/nodes/CallExpression.ts
+++ b/src/ast/nodes/CallExpression.ts
@@ -176,19 +176,22 @@ export default class CallExpression extends NodeBase implements DeoptimizableEnt
}
hasEffects(context: HasEffectsContext): boolean {
- if (!this.deoptimized) this.applyDeoptimizations();
- for (const argument of this.arguments) {
- if (argument.hasEffects(context)) return true;
+ 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.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, context: HasEffectsContext): boolean {
diff --git a/src/ast/nodes/Identifier.ts b/src/ast/nodes/Identifier.ts
index 16aefa22754..3b4084c3972 100644
--- a/src/ast/nodes/Identifier.ts
+++ b/src/ast/nodes/Identifier.ts
@@ -15,18 +15,26 @@ import LocalVariable from '../variables/LocalVariable';
import Variable from '../variables/Variable';
import * as NodeType from './NodeType';
import SpreadElement from './SpreadElement';
-import { ExpressionEntity, LiteralValueOrUnknown } from './shared/Expression';
+import { ExpressionEntity, LiteralValueOrUnknown, UNKNOWN_EXPRESSION } from './shared/Expression';
import { ExpressionNode, NodeBase } from './shared/Node';
import { PatternNode } from './shared/Pattern';
export type IdentifierWithVariable = Identifier & { variable: Variable };
+const tdzVariableKinds = {
+ __proto__: null,
+ class: true,
+ const: true,
+ let: true,
+ var: true
+};
+
export default class Identifier extends NodeBase implements PatternNode {
name!: string;
type!: NodeType.tIdentifier;
-
variable: Variable | null = null;
protected deoptimized = false;
+ private isTDZAccess: boolean | null = null;
addExportedVariables(
variables: Variable[],
@@ -72,6 +80,7 @@ export default class Identifier extends NodeBase implements PatternNode {
/* istanbul ignore next */
throw new Error(`Internal Error: Unexpected identifier kind ${kind}.`);
}
+ variable.kind = kind;
return [(this.variable = variable)];
}
@@ -96,7 +105,7 @@ export default class Identifier extends NodeBase implements PatternNode {
recursionTracker: PathTracker,
origin: DeoptimizableEntity
): LiteralValueOrUnknown {
- return this.variable!.getLiteralValueAtPath(path, recursionTracker, origin);
+ return this.getVariableRespectingTDZ().getLiteralValueAtPath(path, recursionTracker, origin);
}
getReturnExpressionWhenCalledAtPath(
@@ -105,7 +114,7 @@ export default class Identifier extends NodeBase implements PatternNode {
recursionTracker: PathTracker,
origin: DeoptimizableEntity
): ExpressionEntity {
- return this.variable!.getReturnExpressionWhenCalledAtPath(
+ return this.getVariableRespectingTDZ().getReturnExpressionWhenCalledAtPath(
path,
callOptions,
recursionTracker,
@@ -115,6 +124,9 @@ export default class Identifier extends NodeBase implements PatternNode {
hasEffects(): boolean {
if (!this.deoptimized) this.applyDeoptimizations();
+ if (this.isPossibleTDZ() && this.variable!.kind !== 'var') {
+ return true;
+ }
return (
(this.context.options.treeshake as NormalizedTreeshakingOptions).unknownGlobalSideEffects &&
this.variable instanceof GlobalVariable &&
@@ -123,11 +135,20 @@ export default class Identifier extends NodeBase implements PatternNode {
}
hasEffectsWhenAccessedAtPath(path: ObjectPath, context: HasEffectsContext): boolean {
- return this.variable !== null && this.variable.hasEffectsWhenAccessedAtPath(path, context);
+ return (
+ this.variable !== null &&
+ this.getVariableRespectingTDZ().hasEffectsWhenAccessedAtPath(path, context)
+ );
}
hasEffectsWhenAssignedAtPath(path: ObjectPath, context: HasEffectsContext): boolean {
- return !this.variable || this.variable.hasEffectsWhenAssignedAtPath(path, context);
+ return (
+ !this.variable ||
+ (path.length > 0
+ ? this.getVariableRespectingTDZ()
+ : this.variable
+ ).hasEffectsWhenAssignedAtPath(path, context)
+ );
}
hasEffectsWhenCalledAtPath(
@@ -135,7 +156,10 @@ export default class Identifier extends NodeBase implements PatternNode {
callOptions: CallOptions,
context: HasEffectsContext
): boolean {
- return !this.variable || this.variable.hasEffectsWhenCalledAtPath(path, callOptions, context);
+ return (
+ !this.variable ||
+ this.getVariableRespectingTDZ().hasEffectsWhenCalledAtPath(path, callOptions, context)
+ );
}
include(): void {
@@ -149,7 +173,11 @@ export default class Identifier extends NodeBase implements PatternNode {
}
includeCallArguments(context: InclusionContext, args: (ExpressionNode | SpreadElement)[]): void {
- this.variable!.includeCallArguments(context, args);
+ this.getVariableRespectingTDZ().includeCallArguments(context, args);
+ }
+
+ markDeclarationReached(): void {
+ this.variable!.initReached = true;
}
render(
@@ -197,4 +225,32 @@ export default class Identifier extends NodeBase implements PatternNode {
this.start
);
}
+
+ private getVariableRespectingTDZ(): ExpressionEntity {
+ if (this.isPossibleTDZ()) {
+ return UNKNOWN_EXPRESSION;
+ }
+ return this.variable!;
+ }
+
+ private isPossibleTDZ(): boolean {
+ // return cached value to avoid issues with the next tree-shaking pass
+ if (this.isTDZAccess !== null) return this.isTDZAccess;
+
+ if (
+ !(this.variable instanceof LocalVariable) ||
+ !this.variable.kind ||
+ !(this.variable.kind in tdzVariableKinds)
+ ) {
+ return (this.isTDZAccess = false);
+ }
+
+ if (!this.variable.initReached) {
+ // Either a const/let TDZ violation or
+ // var use before declaration was encountered.
+ return (this.isTDZAccess = true);
+ }
+
+ return (this.isTDZAccess = false);
+ }
}
diff --git a/src/ast/nodes/ObjectPattern.ts b/src/ast/nodes/ObjectPattern.ts
index 06226a73c95..57d1b1632ed 100644
--- a/src/ast/nodes/ObjectPattern.ts
+++ b/src/ast/nodes/ObjectPattern.ts
@@ -52,4 +52,10 @@ export default class ObjectPattern extends NodeBase implements PatternNode {
}
return false;
}
+
+ markDeclarationReached(): void {
+ for (const property of this.properties) {
+ property.markDeclarationReached();
+ }
+ }
}
diff --git a/src/ast/nodes/Property.ts b/src/ast/nodes/Property.ts
index 3f5649669d1..6e2021512ec 100644
--- a/src/ast/nodes/Property.ts
+++ b/src/ast/nodes/Property.ts
@@ -35,6 +35,10 @@ export default class Property extends MethodBase implements PatternNode {
);
}
+ markDeclarationReached(): void {
+ (this.value as PatternNode).markDeclarationReached();
+ }
+
render(code: MagicString, options: RenderOptions): void {
if (!this.shorthand) {
this.key.render(code, options);
diff --git a/src/ast/nodes/RestElement.ts b/src/ast/nodes/RestElement.ts
index de3fb0dc7b0..77653bde676 100644
--- a/src/ast/nodes/RestElement.ts
+++ b/src/ast/nodes/RestElement.ts
@@ -33,6 +33,10 @@ export default class RestElement extends NodeBase implements PatternNode {
return path.length > 0 || this.argument.hasEffectsWhenAssignedAtPath(EMPTY_PATH, context);
}
+ markDeclarationReached(): void {
+ this.argument.markDeclarationReached();
+ }
+
protected applyDeoptimizations(): void {
this.deoptimized = true;
if (this.declarationInit !== null) {
diff --git a/src/ast/nodes/VariableDeclarator.ts b/src/ast/nodes/VariableDeclarator.ts
index b479ea65dda..1f3f1338703 100644
--- a/src/ast/nodes/VariableDeclarator.ts
+++ b/src/ast/nodes/VariableDeclarator.ts
@@ -28,17 +28,20 @@ export default class VariableDeclarator extends NodeBase {
}
hasEffects(context: HasEffectsContext): boolean {
- return this.id.hasEffects(context) || (this.init !== null && this.init.hasEffects(context));
+ 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;
- if (includeChildrenRecursively || this.id.shouldBeIncluded(context)) {
- this.id.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);
+ }
}
render(code: MagicString, options: RenderOptions): void {
diff --git a/src/ast/nodes/shared/ClassNode.ts b/src/ast/nodes/shared/ClassNode.ts
index c0b05401d61..b7d0cc4e31c 100644
--- a/src/ast/nodes/shared/ClassNode.ts
+++ b/src/ast/nodes/shared/ClassNode.ts
@@ -1,6 +1,6 @@
import { CallOptions } from '../../CallOptions';
import { DeoptimizableEntity } from '../../DeoptimizableEntity';
-import { HasEffectsContext } from '../../ExecutionContext';
+import { HasEffectsContext, InclusionContext } from '../../ExecutionContext';
import { NodeEvent } from '../../NodeEvents';
import ChildScope from '../../scopes/ChildScope';
import Scope from '../../scopes/Scope';
@@ -16,7 +16,7 @@ import Identifier from '../Identifier';
import Literal from '../Literal';
import MethodDefinition from '../MethodDefinition';
import { ExpressionEntity, LiteralValueOrUnknown, UnknownValue } from './Expression';
-import { ExpressionNode, NodeBase } from './Node';
+import { ExpressionNode, IncludeChildren, NodeBase } from './Node';
import { ObjectEntity, ObjectProperty } from './ObjectEntity';
import { ObjectMember } from './ObjectMember';
import { OBJECT_PROTOTYPE } from './ObjectPrototype';
@@ -76,6 +76,12 @@ export default class ClassNode extends NodeBase implements DeoptimizableEntity {
);
}
+ 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 {
return this.getObjectEntity().hasEffectsWhenAccessedAtPath(path, context);
}
@@ -102,10 +108,18 @@ export default class ClassNode extends NodeBase implements DeoptimizableEntity {
}
}
- initialise(): void {
- if (this.id !== null) {
- this.id.declare('class', this);
+ include(context: InclusionContext, includeChildrenRecursively: IncludeChildren): void {
+ this.included = true;
+ this.superClass?.include(context, includeChildrenRecursively);
+ this.body.include(context, includeChildrenRecursively);
+ if (this.id) {
+ this.id.markDeclarationReached();
+ this.id.include();
}
+ }
+
+ initialise(): void {
+ this.id?.declare('class', this);
for (const method of this.body.body) {
if (method instanceof MethodDefinition && method.kind === 'constructor') {
this.classConstructor = method;
diff --git a/src/ast/nodes/shared/Pattern.ts b/src/ast/nodes/shared/Pattern.ts
index ccca00c9e01..a9e146a5dae 100644
--- a/src/ast/nodes/shared/Pattern.ts
+++ b/src/ast/nodes/shared/Pattern.ts
@@ -5,4 +5,5 @@ import { Node } from './Node';
export interface PatternNode extends WritableEntity, Node {
declare(kind: string, init: ExpressionEntity | null): LocalVariable[];
+ markDeclarationReached(): void;
}
diff --git a/src/ast/scopes/BlockScope.ts b/src/ast/scopes/BlockScope.ts
index ff790dd9566..ee694c790b5 100644
--- a/src/ast/scopes/BlockScope.ts
+++ b/src/ast/scopes/BlockScope.ts
@@ -14,7 +14,8 @@ export default class BlockScope extends ChildScope {
): LocalVariable {
if (isHoisted) {
this.parent.addDeclaration(identifier, context, init, isHoisted);
- // Necessary to make sure the init is deoptimized. We cannot call deoptimizePath here.
+ // Necessary to make sure the init is deoptimized for conditional declarations.
+ // We cannot call deoptimizePath here.
return this.parent.addDeclaration(identifier, context, UNDEFINED_EXPRESSION, isHoisted);
} else {
return super.addDeclaration(identifier, context, init, false);
diff --git a/src/ast/variables/Variable.ts b/src/ast/variables/Variable.ts
index 137d582595d..1ab08e8b5e6 100644
--- a/src/ast/variables/Variable.ts
+++ b/src/ast/variables/Variable.ts
@@ -8,10 +8,12 @@ import { ObjectPath } from '../utils/PathTracker';
export default class Variable extends ExpressionEntity {
alwaysRendered = false;
+ initReached = false;
isId = false;
// both NamespaceVariable and ExternalVariable can be namespaces
isNamespace?: boolean;
isReassigned = false;
+ kind: string | null = null;
module?: Module | ExternalModule;
renderBaseName: string | null = null;
renderName: string | null = null;
diff --git a/test/cli/samples/treeshake-preset-override/main.js b/test/cli/samples/treeshake-preset-override/main.js
index 46a542d6190..25388db20b9 100644
--- a/test/cli/samples/treeshake-preset-override/main.js
+++ b/test/cli/samples/treeshake-preset-override/main.js
@@ -14,5 +14,16 @@ try {
unknownGlobal;
-if (!foo) console.log('effect');
-var foo = true;
+let flag = true;
+
+function test() {
+ if (flag) var x = true;
+ if (x) {
+ return;
+ }
+ console.log('effect');
+}
+
+test();
+flag = false;
+test();
\ No newline at end of file
diff --git a/test/form/samples/recursive-multi-expressions/_expected.js b/test/form/samples/recursive-multi-expressions/_expected.js
index 7e29ee70457..0df267fb302 100644
--- a/test/form/samples/recursive-multi-expressions/_expected.js
+++ b/test/form/samples/recursive-multi-expressions/_expected.js
@@ -1,6 +1,7 @@
const unknown = globalThis.unknown;
var logical1 = logical1 || (() => {});
+logical1();
logical1()();
logical1.x = 1;
logical1().x = 1;
@@ -10,6 +11,7 @@ var logical2 = logical2 || console.log;
logical2();
var conditional1 = unknown ? conditional1 : () => {};
+conditional1();
conditional1()();
conditional1.x = 1;
conditional1().x = 1;
diff --git a/test/form/samples/recursive-multi-expressions/main.js b/test/form/samples/recursive-multi-expressions/main.js
index 3b959d2d3b7..0df267fb302 100644
--- a/test/form/samples/recursive-multi-expressions/main.js
+++ b/test/form/samples/recursive-multi-expressions/main.js
@@ -1,7 +1,7 @@
const unknown = globalThis.unknown;
var logical1 = logical1 || (() => {});
-logical1(); // removed
+logical1();
logical1()();
logical1.x = 1;
logical1().x = 1;
@@ -11,7 +11,7 @@ var logical2 = logical2 || console.log;
logical2();
var conditional1 = unknown ? conditional1 : () => {};
-conditional1(); // removed
+conditional1();
conditional1()();
conditional1.x = 1;
conditional1().x = 1;
diff --git a/test/form/samples/return-value-access-in-conditional/_config.js b/test/form/samples/return-value-access-in-conditional/_config.js
new file mode 100644
index 00000000000..e6a08be106d
--- /dev/null
+++ b/test/form/samples/return-value-access-in-conditional/_config.js
@@ -0,0 +1,3 @@
+module.exports = {
+ description: 'handles accessing funciton return values in deoptimized conditionals'
+};
diff --git a/test/form/samples/return-value-access-in-conditional/_expected.js b/test/form/samples/return-value-access-in-conditional/_expected.js
new file mode 100644
index 00000000000..87854dbd3de
--- /dev/null
+++ b/test/form/samples/return-value-access-in-conditional/_expected.js
@@ -0,0 +1,3 @@
+console.log(true);
+
+console.log('retained');
diff --git a/test/form/samples/return-value-access-in-conditional/main.js b/test/form/samples/return-value-access-in-conditional/main.js
new file mode 100644
index 00000000000..97b25c9ef67
--- /dev/null
+++ b/test/form/samples/return-value-access-in-conditional/main.js
@@ -0,0 +1,9 @@
+function foo() {
+ const result = false;
+ return result;
+}
+
+console.log(foo() || true);
+
+if (foo() || true) console.log('retained');
+else console.log('removed');
diff --git a/test/form/samples/simplify-return-expression/_expected.js b/test/form/samples/simplify-return-expression/_expected.js
index 67a7fcbae5a..e8a6b185d81 100644
--- a/test/form/samples/simplify-return-expression/_expected.js
+++ b/test/form/samples/simplify-return-expression/_expected.js
@@ -4,11 +4,45 @@ const test = () => {
};
const foo = () => {
- return A ;
+ return 'A' ;
};
const bar = () => {
- return A ;
+ return 'A' ;
};
-export { test };
+(function() {
+ const test = () => {
+ console.log(foo());
+ console.log(bar());
+ };
+
+ const foo = () => {
+ // optimized
+ return 'A' ;
+ };
+
+ const bar = () => {
+ // optimized
+ return 'A' ;
+ };
+
+ test();
+})();
+
+const test2 = () => {
+ console.log(foo2());
+ console.log(bar2());
+};
+
+const foo2 = () => {
+ // optimized
+ return 'A' ;
+};
+
+const bar2 = () => {
+ // optimized
+ return 'A' ;
+};
+
+export { test, test2 };
diff --git a/test/form/samples/simplify-return-expression/main.js b/test/form/samples/simplify-return-expression/main.js
index 854407b5114..796651eacad 100644
--- a/test/form/samples/simplify-return-expression/main.js
+++ b/test/form/samples/simplify-return-expression/main.js
@@ -4,13 +4,59 @@ export const test = () => {
};
const foo = () => {
- return BUILD ? A : B;
+ return BUILD ? 'A' : 'B';
};
const bar = () => {
- return getBuild() ? A : B;
+ return getBuild() ? 'A' : 'B';
};
const getBuild = () => BUILD;
const BUILD = true;
+
+(function() {
+ const test = () => {
+ console.log(foo());
+ console.log(bar());
+ };
+
+ const foo = () => {
+ // optimized
+ return BUILD ? 'A' : 'B';
+ };
+
+ const bar = () => {
+ // optimized
+ return getBuild() ? 'A' : 'B';
+ };
+
+ // optimized away
+ const getBuild = () => BUILD;
+
+ // optimized away
+ const BUILD = true;
+
+ test();
+})();
+
+// optimized away
+const BUILD2 = true;
+
+// optimized away
+const getBuild2 = () => BUILD2;
+
+export const test2 = () => {
+ console.log(foo2());
+ console.log(bar2());
+};
+
+const foo2 = () => {
+ // optimized
+ return BUILD2 ? 'A' : 'B';
+};
+
+const bar2 = () => {
+ // optimized
+ return getBuild2() ? 'A' : 'B';
+};
diff --git a/test/form/samples/tdz-access-in-declaration/_config.js b/test/form/samples/tdz-access-in-declaration/_config.js
new file mode 100644
index 00000000000..12ef3f7a53d
--- /dev/null
+++ b/test/form/samples/tdz-access-in-declaration/_config.js
@@ -0,0 +1,3 @@
+module.exports = {
+ description: 'detect accessing TDZ variables within the declaration'
+};
diff --git a/test/form/samples/tdz-access-in-declaration/_expected.js b/test/form/samples/tdz-access-in-declaration/_expected.js
new file mode 100644
index 00000000000..dfb1cc54fcb
--- /dev/null
+++ b/test/form/samples/tdz-access-in-declaration/_expected.js
@@ -0,0 +1,13 @@
+const a = a; // keep
+
+const b = getB(); // keep
+function getB() {
+ return b;
+}
+
+function getC() {
+ return c;
+}
+const c = getC(); // keep
+
+class d extends d {} // keep
diff --git a/test/form/samples/tdz-access-in-declaration/main.js b/test/form/samples/tdz-access-in-declaration/main.js
new file mode 100644
index 00000000000..dfb1cc54fcb
--- /dev/null
+++ b/test/form/samples/tdz-access-in-declaration/main.js
@@ -0,0 +1,13 @@
+const a = a; // keep
+
+const b = getB(); // keep
+function getB() {
+ return b;
+}
+
+function getC() {
+ return c;
+}
+const c = getC(); // keep
+
+class d extends d {} // keep
diff --git a/test/form/samples/tdz-common/_config.js b/test/form/samples/tdz-common/_config.js
new file mode 100644
index 00000000000..ba41751e0fd
--- /dev/null
+++ b/test/form/samples/tdz-common/_config.js
@@ -0,0 +1,3 @@
+module.exports = {
+ description: 'preserve common TDZ violations'
+};
diff --git a/test/form/samples/tdz-common/_expected.js b/test/form/samples/tdz-common/_expected.js
new file mode 100644
index 00000000000..0f3aecb22da
--- /dev/null
+++ b/test/form/samples/tdz-common/_expected.js
@@ -0,0 +1,43 @@
+console.log(function() {
+ if (x) return "HELLO"; // TDZ
+ const x = 1; // keep
+ return "WORLD"; // not reached
+}());
+
+const C = 1 + C + 2; // TDZ
+let L = L; // TDZ
+console.log("X+" ); // optimize
+
+console.log(Y ? "Y+" : "Y-"); // TDZ
+const Y = 2; // keep
+
+console.log(Z ? "Z+" : "Z-"); // TDZ
+const Z = 3; // keep
+console.log("Z+" ); // keep
+
+console.log(obj.x.y ? 1 : 2); // TDZ
+const obj = { // keep
+ x: {
+ y: true
+ }
+};
+console.log(3 ); // keep
+
+L2; // TDZ for L2
+L3 = 20; // TDZ for L3
+let L2, L3; // keep L2, L3
+L3 = 30; // keep
+
+cls; // TDZ
+class cls {}
+
+// Note that typical var/const/let use is still optimized
+(function() {
+ console.log(A ? "A" : "!A");
+ var A = 1;
+ console.log("A" );
+ console.log("B");
+ console.log("B" );
+ console.log("C" );
+ console.log("D" );
+})();
diff --git a/test/form/samples/tdz-common/main.js b/test/form/samples/tdz-common/main.js
new file mode 100644
index 00000000000..a5f29c17cf6
--- /dev/null
+++ b/test/form/samples/tdz-common/main.js
@@ -0,0 +1,57 @@
+console.log(function() {
+ if (x) return "HELLO"; // TDZ
+ const x = 1; // keep
+ return "WORLD"; // not reached
+}());
+
+const unused1 = 1; // drop
+let unused2 = 2; // drop
+unused3; // drop
+var unused3 = 3; // drop
+class unused4 {} // drop
+
+const C = 1 + C + 2; // TDZ
+let L = L; // TDZ
+
+const X = 1; // drop
+console.log(X ? "X+" : "X-"); // optimize
+
+console.log(Y ? "Y+" : "Y-"); // TDZ
+const Y = 2; // keep
+
+console.log(Z ? "Z+" : "Z-"); // TDZ
+const Z = 3; // keep
+console.log(Z ? "Z+" : "Z-"); // keep
+
+console.log(obj.x.y ? 1 : 2); // TDZ
+const obj = { // keep
+ x: {
+ y: true
+ }
+};
+console.log(obj.x.y ? 3 : 4); // keep
+
+V2, L2; // TDZ for L2
+var V2; // drop
+V3 = 10, L3 = 20; // TDZ for L3
+let L1, L2, L3, L4; // keep L2, L3
+var V3; // drop
+L3 = 30; // keep
+L4 = 40; // drop
+
+cls; // TDZ
+class cls {}
+
+// Note that typical var/const/let use is still optimized
+(function() {
+ console.log(A ? "A" : "!A");
+ var A = 1, B = 2;
+ const C = 3;
+ let D = 4;
+ console.log(A ? "A" : "!A");
+ if (B) console.log("B");
+ else console.log("!B");
+ console.log(B ? "B" : "!B");
+ console.log(C ? "C" : "!C");
+ console.log(D ? "D" : "!D");
+})();
diff --git a/test/form/samples/tdz-pattern-access/_config.js b/test/form/samples/tdz-pattern-access/_config.js
new file mode 100644
index 00000000000..a50522b9fd9
--- /dev/null
+++ b/test/form/samples/tdz-pattern-access/_config.js
@@ -0,0 +1,3 @@
+module.exports = {
+ description: 'handles accessing variables declared in patterns before their declaration'
+};
diff --git a/test/form/samples/tdz-pattern-access/_expected.js b/test/form/samples/tdz-pattern-access/_expected.js
new file mode 100644
index 00000000000..44e35fc1981
--- /dev/null
+++ b/test/form/samples/tdz-pattern-access/_expected.js
@@ -0,0 +1,8 @@
+x + y;
+const { x } = {};
+const [y] = [];
+
+if (z) console.log('unimportant');
+else console.log('retained');
+
+var { z } = { z: true };
diff --git a/test/form/samples/tdz-pattern-access/main.js b/test/form/samples/tdz-pattern-access/main.js
new file mode 100644
index 00000000000..847dbb3bc06
--- /dev/null
+++ b/test/form/samples/tdz-pattern-access/main.js
@@ -0,0 +1,8 @@
+const tdzAccess = x + y;
+const { x } = {};
+const [y] = [];
+
+if (z) console.log('unimportant');
+else console.log('retained');
+
+var { z } = { z: true };
diff --git a/test/form/samples/treeshake-presets/preset-with-override/main.js b/test/form/samples/treeshake-presets/preset-with-override/main.js
index 46a542d6190..d6c035738d1 100644
--- a/test/form/samples/treeshake-presets/preset-with-override/main.js
+++ b/test/form/samples/treeshake-presets/preset-with-override/main.js
@@ -14,5 +14,16 @@ try {
unknownGlobal;
-if (!foo) console.log('effect');
-var foo = true;
+let flag = true;
+
+function test() {
+ if (flag) var x = true;
+ if (x) {
+ return;
+ }
+ console.log('effect');
+}
+
+test();
+flag = false;
+test();
diff --git a/test/form/samples/treeshake-presets/recommended/main.js b/test/form/samples/treeshake-presets/recommended/main.js
index 46a542d6190..d6c035738d1 100644
--- a/test/form/samples/treeshake-presets/recommended/main.js
+++ b/test/form/samples/treeshake-presets/recommended/main.js
@@ -14,5 +14,16 @@ try {
unknownGlobal;
-if (!foo) console.log('effect');
-var foo = true;
+let flag = true;
+
+function test() {
+ if (flag) var x = true;
+ if (x) {
+ return;
+ }
+ console.log('effect');
+}
+
+test();
+flag = false;
+test();
diff --git a/test/form/samples/treeshake-presets/safest/_expected.js b/test/form/samples/treeshake-presets/safest/_expected.js
index a859fbd4964..889d307e6f9 100644
--- a/test/form/samples/treeshake-presets/safest/_expected.js
+++ b/test/form/samples/treeshake-presets/safest/_expected.js
@@ -14,5 +14,16 @@ try {
unknownGlobal;
-if (!foo) console.log('effect');
-var foo = true;
+let flag = true;
+
+function test() {
+ if (flag) var x = true;
+ if (x) {
+ return;
+ }
+ console.log('effect');
+}
+
+test();
+flag = false;
+test();
diff --git a/test/form/samples/treeshake-presets/safest/main.js b/test/form/samples/treeshake-presets/safest/main.js
index 46a542d6190..d6c035738d1 100644
--- a/test/form/samples/treeshake-presets/safest/main.js
+++ b/test/form/samples/treeshake-presets/safest/main.js
@@ -14,5 +14,16 @@ try {
unknownGlobal;
-if (!foo) console.log('effect');
-var foo = true;
+let flag = true;
+
+function test() {
+ if (flag) var x = true;
+ if (x) {
+ return;
+ }
+ console.log('effect');
+}
+
+test();
+flag = false;
+test();
diff --git a/test/form/samples/treeshake-presets/smallest/main.js b/test/form/samples/treeshake-presets/smallest/main.js
index 46a542d6190..d6c035738d1 100644
--- a/test/form/samples/treeshake-presets/smallest/main.js
+++ b/test/form/samples/treeshake-presets/smallest/main.js
@@ -14,5 +14,16 @@ try {
unknownGlobal;
-if (!foo) console.log('effect');
-var foo = true;
+let flag = true;
+
+function test() {
+ if (flag) var x = true;
+ if (x) {
+ return;
+ }
+ console.log('effect');
+}
+
+test();
+flag = false;
+test();
diff --git a/test/form/samples/treeshake-presets/true/main.js b/test/form/samples/treeshake-presets/true/main.js
index 46a542d6190..d6c035738d1 100644
--- a/test/form/samples/treeshake-presets/true/main.js
+++ b/test/form/samples/treeshake-presets/true/main.js
@@ -14,5 +14,16 @@ try {
unknownGlobal;
-if (!foo) console.log('effect');
-var foo = true;
+let flag = true;
+
+function test() {
+ if (flag) var x = true;
+ if (x) {
+ return;
+ }
+ console.log('effect');
+}
+
+test();
+flag = false;
+test();
diff --git a/test/function/samples/correct-var-before-declaration-deopt/_config.js b/test/function/samples/correct-var-before-declaration-deopt/_config.js
index 6c091ca84a8..c5e5b826788 100644
--- a/test/function/samples/correct-var-before-declaration-deopt/_config.js
+++ b/test/function/samples/correct-var-before-declaration-deopt/_config.js
@@ -1,6 +1,3 @@
module.exports = {
- description: 'adds necessary deoptimizations when using treeshake.correctVarBeforeDeclaration',
- options: {
- treeshake: { correctVarValueBeforeDeclaration: true }
- }
+ description: 'adds necessary deoptimizations when using var'
};
diff --git a/test/function/samples/use-var-before-decl/_config.js b/test/function/samples/use-var-before-decl/_config.js
new file mode 100644
index 00000000000..c39aa3c5730
--- /dev/null
+++ b/test/function/samples/use-var-before-decl/_config.js
@@ -0,0 +1,3 @@
+module.exports = {
+ description: 'exercise `var` variables before their declarations'
+};
diff --git a/test/function/samples/use-var-before-decl/main.js b/test/function/samples/use-var-before-decl/main.js
new file mode 100644
index 00000000000..48194b34d1f
--- /dev/null
+++ b/test/function/samples/use-var-before-decl/main.js
@@ -0,0 +1,44 @@
+var results = [], log = x => results.push(x);
+
+(function () {
+ var a = "PASS1";
+ for (var b = 2; --b >= 0; ) {
+ (function() {
+ var c = function() {
+ return 1;
+ }(c && (a = "FAIL1"));
+ })();
+ }
+ log(a);
+})();
+
+log(a ? "FAIL2" : "PASS2");
+var a = 1;
+
+var b = 2;
+log(b ? "PASS3" : "FAIL3");
+
+log(c ? "FAIL4" : "PASS4");
+var c = 3;
+log(c ? "PASS5" : "FAIL5");
+
+log(function() {
+ if (x) return "FAIL6";
+ var x = 1;
+ return "PASS6";
+}());
+
+(function () {
+ var first = state();
+ var on = true;
+ var obj = {
+ state: state
+ };
+ log(first)
+ log(obj.state());
+ function state() {
+ return on ? "ON" : "OFF";
+ }
+})();
+
+assert.strictEqual(results.join(" "), "PASS1 PASS2 PASS3 PASS4 PASS5 PASS6 OFF ON");