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