diff --git a/src/ast/nodes/ForStatement.ts b/src/ast/nodes/ForStatement.ts index 6def8e46003..f07001e3716 100644 --- a/src/ast/nodes/ForStatement.ts +++ b/src/ast/nodes/ForStatement.ts @@ -42,7 +42,7 @@ export default class ForStatement extends StatementBase { if (this.test) this.test.include(context, includeChildrenRecursively); const { breakFlow } = context; if (this.update) this.update.include(context, includeChildrenRecursively); - if (this.body) this.body.include(context, includeChildrenRecursively); + this.body.include(context, includeChildrenRecursively); context.breakFlow = breakFlow; } diff --git a/src/ast/nodes/IfStatement.ts b/src/ast/nodes/IfStatement.ts index f9e9b0daa2f..73b6bb944f6 100644 --- a/src/ast/nodes/IfStatement.ts +++ b/src/ast/nodes/IfStatement.ts @@ -19,16 +19,11 @@ export default class IfStatement extends StatementBase implements DeoptimizableE test!: ExpressionNode; type!: NodeType.tIfStatement; - private isTestValueAnalysed = false; private testValue: LiteralValueOrUnknown; bind() { super.bind(); - if (!this.isTestValueAnalysed) { - this.testValue = UnknownValue; - this.isTestValueAnalysed = true; - this.testValue = this.test.getLiteralValueAtPath(EMPTY_PATH, EMPTY_IMMUTABLE_TRACKER, this); - } + this.testValue = this.test.getLiteralValueAtPath(EMPTY_PATH, EMPTY_IMMUTABLE_TRACKER, this); } deoptimizeCache() { diff --git a/src/ast/scopes/CatchScope.ts b/src/ast/scopes/CatchScope.ts index 6bbaa797632..08e977006d8 100644 --- a/src/ast/scopes/CatchScope.ts +++ b/src/ast/scopes/CatchScope.ts @@ -8,7 +8,7 @@ export default class CatchScope extends ParameterScope { addDeclaration( identifier: Identifier, context: AstContext, - init: ExpressionEntity | null = null, + init: ExpressionEntity | null, isHoisted: boolean | 'function' ): LocalVariable { if (isHoisted) { diff --git a/src/ast/values.ts b/src/ast/values.ts index df024efe881..3f563c5928d 100644 --- a/src/ast/values.ts +++ b/src/ast/values.ts @@ -47,6 +47,7 @@ export const UNKNOWN_EXPRESSION: ExpressionEntity = { included: true, toString: () => '[[UNKNOWN]]' }; + export const UNDEFINED_EXPRESSION: ExpressionEntity = { deoptimizePath: () => {}, getLiteralValueAtPath: () => undefined, @@ -59,6 +60,7 @@ export const UNDEFINED_EXPRESSION: ExpressionEntity = { included: true, toString: () => 'undefined' }; + const returnsUnknown: RawMemberDescription = { value: { callsArgs: null, diff --git a/test/form/samples/binary-expressions/_config.js b/test/form/samples/binary-expressions/_config.js new file mode 100644 index 00000000000..1c861eec011 --- /dev/null +++ b/test/form/samples/binary-expressions/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'handles binary expression side-effects' +}; diff --git a/test/form/samples/binary-expressions/_expected.js b/test/form/samples/binary-expressions/_expected.js new file mode 100644 index 00000000000..5ff02006ea4 --- /dev/null +++ b/test/form/samples/binary-expressions/_expected.js @@ -0,0 +1,25 @@ +if ((1 + 1).unknown) { + console.log('retained'); +} else { + console.log('retained'); +} +console.log('retained 1'); +console.log('retained 2'); +console.log('retained 3'); +console.log('retained 4'); +console.log('retained 5'); +console.log('retained 6'); +console.log('retained 7'); +console.log('retained 8'); +console.log('retained 9'); +console.log('retained 10'); +console.log('retained 11'); +console.log('retained 12'); +console.log('retained 13'); +console.log('retained 14'); +console.log('retained 15'); +console.log('retained 16'); +console.log('retained 17'); +if (1 in 2) console.log('retained 18'); +if (1 instanceof 2) console.log('retained 19'); +console.log('retained 20'); diff --git a/test/form/samples/binary-expressions/main.js b/test/form/samples/binary-expressions/main.js new file mode 100644 index 00000000000..0263ffe9303 --- /dev/null +++ b/test/form/samples/binary-expressions/main.js @@ -0,0 +1,28 @@ +if ((1 + 1).unknown) { + console.log('retained'); +} else { + console.log('retained'); +} + +if (1 != '1') console.log('removed'); +if (1 !== 1) console.log('removed'); +if (4 % 2 === 0) console.log('retained 1'); +if ((6 & 3) === 2) console.log('retained 2'); +if (2 * 3 === 6) console.log('retained 3'); +if (2 ** 3 === 8) console.log('retained 4'); +if (2 + 3 === 5) console.log('retained 5'); +if (3 - 2 === 1) console.log('retained 6'); +if (6 / 3 === 2) console.log('retained 7'); +if (1 < 2 ) console.log('retained 8'); +if (3 << 1 === 6) console.log('retained 9'); +if (3 <= 4) console.log('retained 10'); +if (1 == '1') console.log('retained 11'); +if (1 === 1) console.log('retained 12'); +if (3 > 2) console.log('retained 13'); +if (3 >= 2) console.log('retained 14'); +if (6 >> 1 === 3) console.log('retained 15'); +if (-1 >>> 28 === 15) console.log('retained 16'); +if (3 ^ 5 === 6) console.log('retained 17'); +if (1 in 2) console.log('retained 18'); +if (1 instanceof 2) console.log('retained 19'); +if (2 | 4 === 6) console.log('retained 20'); diff --git a/test/form/samples/break-control-flow/break-statement-labels/_expected.js b/test/form/samples/break-control-flow/break-statement-labels/_expected.js index 81784a01622..d7080a80bb6 100644 --- a/test/form/samples/break-control-flow/break-statement-labels/_expected.js +++ b/test/form/samples/break-control-flow/break-statement-labels/_expected.js @@ -31,11 +31,11 @@ outer: { console.log('retained'); } -function withReturn() { +function withConsequentReturn() { outer: { inner: { - if (globalThis.unknown) break inner; - else return; + if (globalThis.unknown) return; + else break inner; } console.log('retained'); } @@ -46,4 +46,16 @@ function withReturn() { } } -withReturn(); +withConsequentReturn(); + +function withAlternateReturn() { + outer: { + inner: { + if (globalThis.unknown) break inner; + else return; + } + console.log('retained'); + } +} + +withAlternateReturn(); diff --git a/test/form/samples/break-control-flow/break-statement-labels/main.js b/test/form/samples/break-control-flow/break-statement-labels/main.js index 336ae3797ee..b7ac267b7f1 100644 --- a/test/form/samples/break-control-flow/break-statement-labels/main.js +++ b/test/form/samples/break-control-flow/break-statement-labels/main.js @@ -48,11 +48,11 @@ outer: { console.log('retained'); } -function withReturn() { +function withConsequentReturn() { outer: { inner: { - if (globalThis.unknown) break inner; - else return; + if (globalThis.unknown) return; + else break inner; console.log('removed'); } console.log('retained'); @@ -67,4 +67,17 @@ function withReturn() { } } -withReturn(); +withConsequentReturn(); + +function withAlternateReturn() { + outer: { + inner: { + if (globalThis.unknown) break inner; + else return; + console.log('removed'); + } + console.log('retained'); + } +} + +withAlternateReturn(); diff --git a/test/form/samples/builtin-prototypes/array-expression/_config.js b/test/form/samples/builtin-prototypes/array-expression/_config.js index 90f3b19cd39..c18d1c7f176 100644 --- a/test/form/samples/builtin-prototypes/array-expression/_config.js +++ b/test/form/samples/builtin-prototypes/array-expression/_config.js @@ -1,4 +1,3 @@ module.exports = { - description: 'Tree-shake known array prototype functions', - options: { output: { name: 'bundle' } } + description: 'Tree-shake known array prototype functions' }; diff --git a/test/form/samples/builtin-prototypes/array-expression/_expected.js b/test/form/samples/builtin-prototypes/array-expression/_expected.js index effa27f383d..7318911baaa 100644 --- a/test/form/samples/builtin-prototypes/array-expression/_expected.js +++ b/test/form/samples/builtin-prototypes/array-expression/_expected.js @@ -3,6 +3,8 @@ const map4 = [ 1 ].map( x => x ).map( x => console.log( 1 ) ); const map5 = [ 1 ].map( x => console.log( 1 ) ).map( x => x ); const map7 = [ 1 ].map( x => x ).map( x => x ).map( x => console.log( 1 ) ); const map8 = [ 1 ].map( x => x ).map( x => console.log( 1 ) ).map( x => x ); + +[](); const _everyEffect = [ 1 ].every( () => console.log( 1 ) || true ); const _filterEffect = [ 1 ].filter( () => console.log( 1 ) || true ); const _findEffect = [ 1 ].find( () => console.log( 1 ) || true ); diff --git a/test/form/samples/builtin-prototypes/array-expression/main.js b/test/form/samples/builtin-prototypes/array-expression/main.js index 7fa15d0881d..a04bdfa57e3 100644 --- a/test/form/samples/builtin-prototypes/array-expression/main.js +++ b/test/form/samples/builtin-prototypes/array-expression/main.js @@ -12,6 +12,8 @@ const map6 = [ 1 ].map( x => x ).map( x => x ).map( x => x ); const map7 = [ 1 ].map( x => x ).map( x => x ).map( x => console.log( 1 ) ); const map8 = [ 1 ].map( x => x ).map( x => console.log( 1 ) ).map( x => x ); +[](); + // accessor methods const _includes = [].includes( 1 ).valueOf(); const _indexOf = [].indexOf( 1 ).toPrecision( 1 ); diff --git a/test/form/samples/class-constructor-side-effect/_expected.js b/test/form/samples/class-constructor-side-effect/_expected.js new file mode 100644 index 00000000000..469da25d52a --- /dev/null +++ b/test/form/samples/class-constructor-side-effect/_expected.js @@ -0,0 +1,11 @@ +class Effect { + constructor () { + console.log( 'Foo' ); + } +} + +new Effect(); + +class Empty {} + +new Empty.doesNotExist(); diff --git a/test/form/samples/class-constructor-side-effect/_expected/amd.js b/test/form/samples/class-constructor-side-effect/_expected/amd.js deleted file mode 100644 index 1b939aa8b03..00000000000 --- a/test/form/samples/class-constructor-side-effect/_expected/amd.js +++ /dev/null @@ -1,11 +0,0 @@ -define(function () { 'use strict'; - - class Foo { - constructor () { - console.log( 'Foo' ); - } - } - - new Foo; - -}); diff --git a/test/form/samples/class-constructor-side-effect/_expected/cjs.js b/test/form/samples/class-constructor-side-effect/_expected/cjs.js deleted file mode 100644 index ad3db871b73..00000000000 --- a/test/form/samples/class-constructor-side-effect/_expected/cjs.js +++ /dev/null @@ -1,9 +0,0 @@ -'use strict'; - -class Foo { - constructor () { - console.log( 'Foo' ); - } -} - -new Foo; diff --git a/test/form/samples/class-constructor-side-effect/_expected/es.js b/test/form/samples/class-constructor-side-effect/_expected/es.js deleted file mode 100644 index 705e3506882..00000000000 --- a/test/form/samples/class-constructor-side-effect/_expected/es.js +++ /dev/null @@ -1,7 +0,0 @@ -class Foo { - constructor () { - console.log( 'Foo' ); - } -} - -new Foo; diff --git a/test/form/samples/class-constructor-side-effect/_expected/iife.js b/test/form/samples/class-constructor-side-effect/_expected/iife.js deleted file mode 100644 index a047aa4e0d1..00000000000 --- a/test/form/samples/class-constructor-side-effect/_expected/iife.js +++ /dev/null @@ -1,12 +0,0 @@ -(function () { - 'use strict'; - - class Foo { - constructor () { - console.log( 'Foo' ); - } - } - - new Foo; - -}()); diff --git a/test/form/samples/class-constructor-side-effect/_expected/system.js b/test/form/samples/class-constructor-side-effect/_expected/system.js deleted file mode 100644 index c50f93205ef..00000000000 --- a/test/form/samples/class-constructor-side-effect/_expected/system.js +++ /dev/null @@ -1,16 +0,0 @@ -System.register([], function () { - 'use strict'; - return { - execute: function () { - - class Foo { - constructor () { - console.log( 'Foo' ); - } - } - - new Foo; - - } - }; -}); diff --git a/test/form/samples/class-constructor-side-effect/_expected/umd.js b/test/form/samples/class-constructor-side-effect/_expected/umd.js deleted file mode 100644 index 82f801be7d2..00000000000 --- a/test/form/samples/class-constructor-side-effect/_expected/umd.js +++ /dev/null @@ -1,14 +0,0 @@ -(function (factory) { - typeof define === 'function' && define.amd ? define(factory) : - factory(); -}(function () { 'use strict'; - - class Foo { - constructor () { - console.log( 'Foo' ); - } - } - - new Foo; - -})); diff --git a/test/form/samples/class-constructor-side-effect/main.js b/test/form/samples/class-constructor-side-effect/main.js index 705e3506882..2704996853a 100644 --- a/test/form/samples/class-constructor-side-effect/main.js +++ b/test/form/samples/class-constructor-side-effect/main.js @@ -1,7 +1,18 @@ -class Foo { +class Effect { constructor () { console.log( 'Foo' ); } } -new Foo; +new Effect(); + +class NoEffect { + constructor () { + } +} + +new NoEffect(); + +class Empty {} + +new Empty.doesNotExist(); diff --git a/test/form/samples/side-effects-switch-statements/_expected.js b/test/form/samples/side-effects-switch-statements/_expected.js index 1637398c2b5..6b541d9096e 100644 --- a/test/form/samples/side-effects-switch-statements/_expected.js +++ b/test/form/samples/side-effects-switch-statements/_expected.js @@ -30,3 +30,7 @@ switch ( globalThis.unknown ) { effect(); } }()); + +switch ( globalThis.unknown ) { + case effect(): +} diff --git a/test/form/samples/side-effects-switch-statements/main.js b/test/form/samples/side-effects-switch-statements/main.js index 306c82ecf5c..14763c4bd5b 100644 --- a/test/form/samples/side-effects-switch-statements/main.js +++ b/test/form/samples/side-effects-switch-statements/main.js @@ -44,3 +44,7 @@ switch ( globalThis.unknown ) { default: } }()); + +switch ( globalThis.unknown ) { + case effect(): +} diff --git a/test/form/samples/unary-expressions/_config.js b/test/form/samples/unary-expressions/_config.js new file mode 100644 index 00000000000..9e363ae5176 --- /dev/null +++ b/test/form/samples/unary-expressions/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'handles unary expression side-effects' +}; diff --git a/test/form/samples/unary-expressions/_expected.js b/test/form/samples/unary-expressions/_expected.js new file mode 100644 index 00000000000..d51c6bfe41b --- /dev/null +++ b/test/form/samples/unary-expressions/_expected.js @@ -0,0 +1,13 @@ +if ((!true).unknown) { + console.log('retained'); +} else { + console.log('retained'); +} + +console.log('retained 1'); +console.log('retained 2'); +console.log('retained 3'); +if (delete 1) console.log('retained 4'); +console.log('retained 5'); +console.log('retained 6'); +console.log('retained 7'); diff --git a/test/form/samples/unary-expressions/main.js b/test/form/samples/unary-expressions/main.js new file mode 100644 index 00000000000..c22c4ebd151 --- /dev/null +++ b/test/form/samples/unary-expressions/main.js @@ -0,0 +1,13 @@ +if ((!true).unknown) { + console.log('retained'); +} else { + console.log('retained'); +} + +if (!false) console.log('retained 1'); +if (+'1' === 1) console.log('retained 2'); +if (-1 + 2 === 1) console.log('retained 3'); +if (delete 1) console.log('retained 4'); +if (typeof 1 === 'number') console.log('retained 5'); +if (void 1 === undefined) console.log('retained 6'); +if (~1 === -2) console.log('retained 7'); diff --git a/test/form/samples/undefined-var/_expected.js b/test/form/samples/undefined-var/_expected.js index e10c01c8cef..710306c2a45 100644 --- a/test/form/samples/undefined-var/_expected.js +++ b/test/form/samples/undefined-var/_expected.js @@ -1,8 +1,10 @@ var z; -console.log('no'); -console.log('no'); -if (z) - console.log('yes'); -if (!z) - console.log('no'); + +console.log('retained'); + +console.log('retained'); + +if (z) console.log('retained'); +else console.log('retained'); + z = 1; diff --git a/test/form/samples/undefined-var/main.js b/test/form/samples/undefined-var/main.js index d6763907d01..7e219955ebe 100644 --- a/test/form/samples/undefined-var/main.js +++ b/test/form/samples/undefined-var/main.js @@ -1,16 +1,14 @@ var x; var y = undefined; var z; -if (x) - console.log('yes'); -if (!x) - console.log('no'); -if (y) - console.log('yes'); -if (!y) - console.log('no'); -if (z) - console.log('yes'); -if (!z) - console.log('no'); + +if (x) console.log('removed'); +else console.log('retained'); + +if (y) console.log('removed'); +else console.log('retained'); + +if (z) console.log('retained'); +else console.log('retained'); + z = 1;