diff --git a/LICENSE.md b/LICENSE.md index f4c3dad75aa..9f1eeef1d01 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -154,35 +154,6 @@ Repository: git@github.com:indutny/hash.js --------------------------------------- -## immutable -License: MIT -By: Lee Byron -Repository: git://github.com/facebook/immutable-js.git - -> MIT License -> -> Copyright (c) 2014-present, Facebook, Inc. -> -> Permission is hereby granted, free of charge, to any person obtaining a copy -> of this software and associated documentation files (the "Software"), to deal -> in the Software without restriction, including without limitation the rights -> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -> copies of the Software, and to permit persons to whom the Software is -> furnished to do so, subject to the following conditions: -> -> The above copyright notice and this permission notice shall be included in all -> copies or substantial portions of the Software. -> -> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -> SOFTWARE. - ---------------------------------------- - ## inherits License: ISC Repository: git://github.com/isaacs/inherits @@ -398,7 +369,7 @@ Repository: git://github.com/kamicane/require-relative.git ## rollup-pluginutils License: MIT By: Rich Harris -Repository: git+https://github.com/rollup/rollup-pluginutils.git +Repository: rollup/rollup-pluginutils --------------------------------------- diff --git a/package-lock.json b/package-lock.json index 285f5209edd..043724ed0de 100644 --- a/package-lock.json +++ b/package-lock.json @@ -143,28 +143,28 @@ } }, "@nodelib/fs.scandir": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.2.tgz", - "integrity": "sha512-wrIBsjA5pl13f0RN4Zx4FNWmU71lv03meGKnqRUoCyan17s4V3WL92f3w3AIuWbNnpcrQyFBU5qMavJoB8d27w==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz", + "integrity": "sha512-eGmwYQn3gxo4r7jdQnkrrN6bY478C3P+a/y72IJukF8LjB6ZHeB3c+Ehacj3sYeSmUXGlnA67/PmbM9CVwL7Dw==", "dev": true, "requires": { - "@nodelib/fs.stat": "2.0.2", + "@nodelib/fs.stat": "2.0.3", "run-parallel": "^1.1.9" } }, "@nodelib/fs.stat": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.2.tgz", - "integrity": "sha512-z8+wGWV2dgUhLqrtRYa03yDx4HWMvXKi1z8g3m2JyxAx8F7xk74asqPk5LAETjqDSGLFML/6CDl0+yFunSYicw==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz", + "integrity": "sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA==", "dev": true }, "@nodelib/fs.walk": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.3.tgz", - "integrity": "sha512-l6t8xEhfK9Sa4YO5mIRdau7XSOADfmh3jCr0evNHdY+HNkW6xuQhgMH7D73VV6WpZOagrW0UludvMTiifiwTfA==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz", + "integrity": "sha512-1V9XOY4rDW0rehzbrcqAmHnz8e7SKvX27gh8Gt2WgB0+pdzdiLV83p72kZPU+jvMbS1qU5mauP2iOvO8rhmurQ==", "dev": true, "requires": { - "@nodelib/fs.scandir": "2.1.2", + "@nodelib/fs.scandir": "2.1.3", "fastq": "^1.6.0" } }, @@ -245,9 +245,9 @@ "dev": true }, "@types/node": { - "version": "12.7.11", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.7.11.tgz", - "integrity": "sha512-Otxmr2rrZLKRYIybtdG/sgeO+tHY20GxeDjcGmUnmmlCWyEnv2a2x1ZXBo3BTec4OiTXMQCiazB8NMBf0iRlFw==" + "version": "12.7.12", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.7.12.tgz", + "integrity": "sha512-KPYGmfD0/b1eXurQ59fXD1GBzhSQfz6/lKBxkaHX9dKTzjXbK68Zt7yGUxUsCS1jeTy/8aL+d9JEr+S54mpkWQ==" }, "@types/normalize-package-data": { "version": "2.4.0", @@ -320,13 +320,13 @@ } }, "aggregate-error": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.0.0.tgz", - "integrity": "sha512-yKD9kEoJIR+2IFqhMwayIBgheLYbB3PS2OBhWae1L/ODTd/JF/30cW0bc9TqzRL3k4U41Dieu3BF4I29p8xesA==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.0.1.tgz", + "integrity": "sha512-quoaXsZ9/BLNae5yiNoUz+Nhkwz83GhWwtYFglcjEQB2NDHCIpApbqXxIFnm4Pq/Nvhrsq5sYJFyohrrxnTGAA==", "dev": true, "requires": { "clean-stack": "^2.0.0", - "indent-string": "^3.2.0" + "indent-string": "^4.0.0" } }, "ajv": { @@ -1699,12 +1699,12 @@ "dev": true }, "execa": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/execa/-/execa-2.0.5.tgz", - "integrity": "sha512-SwmwZZyJjflcqLSgllk4EQlMLst2p9muyzwNugKGFlpAz6rZ7M+s2nBR97GAq4Vzjwx2y9rcMcmqzojwN+xwNA==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-2.1.0.tgz", + "integrity": "sha512-Y/URAVapfbYy2Xp/gb6A0E7iR8xeqOCXsuuaoMn7A5PzrXUK84E1gyiEfq0wQd/GHA6GsoHWwhNq8anb0mleIw==", "dev": true, "requires": { - "cross-spawn": "^6.0.5", + "cross-spawn": "^7.0.0", "get-stream": "^5.0.0", "is-stream": "^2.0.0", "merge-stream": "^2.0.0", @@ -1715,6 +1715,17 @@ "strip-final-newline": "^2.0.0" }, "dependencies": { + "cross-spawn": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.1.tgz", + "integrity": "sha512-u7v4o84SwFpD32Z8IIcPZ6z1/ie24O6RU3RbtL5Y316l3KuHVPx9ItBgWQ6VlfAFnRnTtMUrsQ9MUUTuEZjogg==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, "mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", @@ -1729,6 +1740,36 @@ "requires": { "mimic-fn": "^2.1.0" } + }, + "path-key": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.0.tgz", + "integrity": "sha512-8cChqz0RP6SHJkMt48FW0A7+qUOn+OsnOsVtzI59tZ8m+5bCSk7hzwET0pulwOM2YMn9J1efb07KB9l9f30SGg==", + "dev": true + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "which": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.1.tgz", + "integrity": "sha512-N7GBZOTswtB9lkQBZA4+zAXrjEIWAUOB93AvzUiudRzRxhUdLURQ7D/gAIMY1gatT/LTbmbcv8SiYazy3eYB7w==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } } } }, @@ -1871,16 +1912,15 @@ "dev": true }, "fast-glob": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.0.4.tgz", - "integrity": "sha512-wkIbV6qg37xTJwqSsdnIphL1e+LaGz4AIQqr00mIubMaEhv1/HEmJ0uuCGZRNRUkZZmOB5mJKO0ZUTVq+SxMQg==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.1.0.tgz", + "integrity": "sha512-TrUz3THiq2Vy3bjfQUB2wNyPdGBeGmdjbzzBLhfHN4YFurYptCKwGq/TfiRavbGywFRzY6U2CdmQ1zmsY5yYaw==", "dev": true, "requires": { - "@nodelib/fs.stat": "^2.0.1", - "@nodelib/fs.walk": "^1.2.1", - "glob-parent": "^5.0.0", - "is-glob": "^4.0.1", - "merge2": "^1.2.3", + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.0", + "merge2": "^1.3.0", "micromatch": "^4.0.2" }, "dependencies": { @@ -2304,9 +2344,9 @@ "dev": true }, "get-own-enumerable-property-symbols": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.0.tgz", - "integrity": "sha512-CIJYJC4GGF06TakLg8z4GQKvDsx9EMspVxOYih7LerEL/WosUnFIww45CGfxfeKHqlg3twgUrYRT1O3WQqjGCg==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.1.tgz", + "integrity": "sha512-09/VS4iek66Dh2bctjRkowueRJbY1JDGR1L/zRxO1Qk8Uxs6PnqaNSqalpizPT+CDjre3hnEsuzvhgomz9qYrA==", "dev": true }, "get-stdin": { @@ -2750,12 +2790,6 @@ "minimatch": "^3.0.4" } }, - "immutable": { - "version": "4.0.0-rc.12", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.0.0-rc.12.tgz", - "integrity": "sha512-0M2XxkZLx/mi3t8NVwIm1g8nHoEmM9p9UBl/G9k4+hm0kBgOVdMV/B3CY5dQ8qG8qc80NN4gDV4HQv6FTJ5q7A==", - "dev": true - }, "import-fresh": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.1.0.tgz", @@ -2773,9 +2807,9 @@ "dev": true }, "indent-string": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", - "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", "dev": true }, "inflight": { @@ -3343,9 +3377,9 @@ } }, "lint-staged": { - "version": "9.4.1", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-9.4.1.tgz", - "integrity": "sha512-zFRbo1bAJEVf1m33paTTjDVfy2v3lICCqHfmQSgNoI+lWpi7HPG5y/R2Y7Whdce+FKxlZYs/U1sDSx8+nmQdDA==", + "version": "9.4.2", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-9.4.2.tgz", + "integrity": "sha512-OFyGokJSWTn2M6vngnlLXjaHhi8n83VIZZ5/1Z26SULRUWgR3ITWpAEQC9Pnm3MC/EpCxlwts/mQWDHNji2+zA==", "dev": true, "requires": { "chalk": "^2.4.2", @@ -3480,6 +3514,12 @@ "object-assign": "^4.1.0" } }, + "indent-string": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", + "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=", + "dev": true + }, "log-symbols": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-1.0.2.tgz", @@ -5163,13 +5203,13 @@ } }, "rollup": { - "version": "1.23.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-1.23.0.tgz", - "integrity": "sha512-/p72Z3NbHWV+Vi1p2X+BmPA3WqlZxpUqCy6E8U4crMohZnI+j9Ob8ZAfFyNfddT0LxgnJM0olO4mg+noH4SFbg==", + "version": "1.23.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-1.23.1.tgz", + "integrity": "sha512-95C1GZQpr/NIA0kMUQmSjuMDQ45oZfPgDBcN0yZwBG7Kee//m7H68vgIyg+SPuyrTZ5PrXfyLK80OzXeKG5dAA==", "dev": true, "requires": { "@types/estree": "*", - "@types/node": "^12.7.10", + "@types/node": "*", "acorn": "^7.1.0" } }, @@ -5853,9 +5893,9 @@ "dev": true }, "systemjs": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/systemjs/-/systemjs-6.1.2.tgz", - "integrity": "sha512-QN0E62xEGCjp4+Wwg5o50CgdlMkQdUkiw5rHKQNJ454iKJCsFmQjPBoCEs1xq8mM/J+eYtLkZS0JCcay/gaEFw==", + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/systemjs/-/systemjs-6.1.3.tgz", + "integrity": "sha512-gchy3HVD7U7frun2RT3ApxjLtekG39KQXpLhNxRR5HSWqA1+p3dxPXrmvoevEsKAhCEPi2MjZ75zPBDiftic8A==", "dev": true }, "table": { @@ -5935,9 +5975,9 @@ } }, "terser": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/terser/-/terser-4.3.6.tgz", - "integrity": "sha512-QQXGTgXT7zET9IbGSdRvExcL+rFZGiOxMDbPg1W0tc5gqbX6m7J6Eu0W3fQ2bK5Dks1WSvC2xAKOH+mzAuMLcg==", + "version": "4.3.8", + "resolved": "https://registry.npmjs.org/terser/-/terser-4.3.8.tgz", + "integrity": "sha512-otmIRlRVmLChAWsnSFNO0Bfk6YySuBp6G9qrHiJwlLDd4mxe2ta4sjI7TzIR+W1nBMjilzrMcPOz9pSusgx3hQ==", "dev": true, "requires": { "commander": "^2.20.0", @@ -6213,9 +6253,9 @@ "dev": true }, "typescript": { - "version": "3.6.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.6.3.tgz", - "integrity": "sha512-N7bceJL1CtRQ2RiG0AQME13ksR7DiuQh/QehubYcghzv20tnh+MQnQIuJddTmsbqYj+dztchykemz0zFzlvdQw==", + "version": "3.6.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.6.4.tgz", + "integrity": "sha512-unoCll1+l+YK4i4F8f22TaNVPRHcD9PA3yCuZ8g5e0qGqlVlJ/8FSateOLLSagn+Yg5+ZwuPkL8LFUc0Jcvksg==", "dev": true }, "uc.micro": { diff --git a/package.json b/package.json index e79af2328d5..21734de35f6 100644 --- a/package.json +++ b/package.json @@ -86,13 +86,12 @@ "es6-shim": "^0.35.5", "eslint": "^6.5.1", "eslint-plugin-import": "^2.18.2", - "execa": "^2.0.5", + "execa": "^2.1.0", "fixturify": "^1.2.0", "hash.js": "^1.1.7", "husky": "^3.0.8", - "immutable": "^4.0.0-rc.12", "is-reference": "^1.1.4", - "lint-staged": "^9.4.1", + "lint-staged": "^9.4.2", "locate-character": "^2.0.5", "magic-string": "^0.25.4", "markdownlint-cli": "^0.18.0", @@ -105,7 +104,7 @@ "pretty-ms": "^5.0.0", "require-relative": "^0.8.7", "requirejs": "^2.3.6", - "rollup": "^1.23.0", + "rollup": "^1.23.1", "rollup-plugin-alias": "^2.0.1", "rollup-plugin-buble": "^0.19.8", "rollup-plugin-commonjs": "^10.1.0", @@ -123,12 +122,12 @@ "source-map": "^0.6.1", "source-map-support": "^0.5.13", "sourcemap-codec": "^1.4.6", - "systemjs": "^6.1.2", - "terser": "^4.3.6", + "systemjs": "^6.1.3", + "terser": "^4.3.8", "tslib": "^1.10.0", "tslint": "^5.20.0", "turbocolor": "^2.6.1", - "typescript": "^3.6.3", + "typescript": "^3.6.4", "url-parse": "^1.4.7" }, "files": [ diff --git a/scripts/load-perf-config.js b/scripts/load-perf-config.js index a043e4649bb..8784f9c5071 100644 --- a/scripts/load-perf-config.js +++ b/scripts/load-perf-config.js @@ -8,7 +8,9 @@ const configFile = path.resolve(exports.targetDir, 'rollup.config.js'); try { fs.accessSync(configFile, fs.constants.R_OK); } catch (e) { - console.error(`No valid "rollup.config.js" in ${exports.targetDir}. Did you "npm run perf:init"?`); + console.error( + `No valid "rollup.config.js" in ${exports.targetDir}. Did you "npm run perf:init"?` + ); process.exit(1); } @@ -18,8 +20,9 @@ exports.loadPerfConfig = async () => { external: id => (id[0] !== '.' && !path.isAbsolute(id)) || id.slice(-5, id.length) === '.json', onwarn: warning => console.error(warning.message) }); - const configs = loadConfigFromCode((await bundle.generate({ format: 'cjs' })).code); - return Array.isArray(configs) ? configs[0] : configs; + let config = loadConfigFromCode((await bundle.generate({ format: 'cjs' })).output[0].code); + config = typeof config === 'function' ? config({}) : config; + return Array.isArray(config) ? config[0] : config; }; function loadConfigFromCode(code) { diff --git a/src/Chunk.ts b/src/Chunk.ts index 2a9b94aa76f..beb66769f48 100644 --- a/src/Chunk.ts +++ b/src/Chunk.ts @@ -1,6 +1,7 @@ import sha256 from 'hash.js/lib/hash/sha/256'; import MagicString, { Bundle as MagicStringBundle, SourceMap } from 'magic-string'; import { relative } from '../browser/path'; +import { createInclusionContext } from './ast/ExecutionContext'; import ExportDefaultDeclaration from './ast/nodes/ExportDefaultDeclaration'; import FunctionDeclaration from './ast/nodes/FunctionDeclaration'; import { UNDEFINED_EXPRESSION } from './ast/values'; @@ -1190,9 +1191,10 @@ export default class Chunk { } } } + const context = createInclusionContext(); for (const { node, resolution } of module.dynamicImports) { if (node.included && resolution instanceof Module && resolution.chunk === this) - resolution.getOrCreateNamespace().include(); + resolution.getOrCreateNamespace().include(context); } } } diff --git a/src/ExternalModule.ts b/src/ExternalModule.ts index d1f406a0e8c..5511a26d84f 100644 --- a/src/ExternalModule.ts +++ b/src/ExternalModule.ts @@ -37,7 +37,7 @@ export default class ExternalModule { this.exportedVariables = new Map(); } - getVariableForExportName(name: string, _isExportAllSearch?: boolean): ExternalVariable { + getVariableForExportName(name: string): ExternalVariable { if (name === '*') { this.exportsNamespace = true; } else if (name !== 'default') { diff --git a/src/Graph.ts b/src/Graph.ts index 7c928a052b1..6585379e529 100644 --- a/src/Graph.ts +++ b/src/Graph.ts @@ -2,7 +2,7 @@ import * as acorn from 'acorn'; import injectImportMeta from 'acorn-import-meta'; import * as ESTree from 'estree'; import GlobalScope from './ast/scopes/GlobalScope'; -import { EntityPathTracker } from './ast/utils/EntityPathTracker'; +import { PathTracker } from './ast/utils/PathTracker'; import Chunk, { isChunkRendered } from './Chunk'; import ExternalModule from './ExternalModule'; import Module, { defaultAcornOptions } from './Module'; @@ -64,7 +64,7 @@ export default class Graph { acornParser: typeof acorn.Parser; cachedModules: Map; contextParse: (code: string, acornOptions?: acorn.Options) => ESTree.Program; - deoptimizationTracker: EntityPathTracker; + deoptimizationTracker: PathTracker; getModuleContext: (id: string) => string; moduleById = new Map(); moduleLoader: ModuleLoader; @@ -87,7 +87,7 @@ export default class Graph { constructor(options: InputOptions, watcher?: RollupWatcher) { this.onwarn = (options.onwarn as WarningHandler) || makeOnwarn(); - this.deoptimizationTracker = new EntityPathTracker(); + this.deoptimizationTracker = new PathTracker(); this.cachedModules = new Map(); if (options.cache) { if (options.cache.modules) diff --git a/src/Module.ts b/src/Module.ts index 38f4e6643b3..bd675619b1a 100644 --- a/src/Module.ts +++ b/src/Module.ts @@ -3,6 +3,7 @@ import * as ESTree from 'estree'; import { locate } from 'locate-character'; import MagicString from 'magic-string'; import extractAssignedNames from 'rollup-pluginutils/src/extractAssignedNames'; +import { createInclusionContext, InclusionContext } from './ast/ExecutionContext'; import ClassDeclaration from './ast/nodes/ClassDeclaration'; import ExportAllDeclaration from './ast/nodes/ExportAllDeclaration'; import ExportDefaultDeclaration from './ast/nodes/ExportDefaultDeclaration'; @@ -21,8 +22,7 @@ import { Node, NodeBase } from './ast/nodes/shared/Node'; import TemplateLiteral from './ast/nodes/TemplateLiteral'; import VariableDeclaration from './ast/nodes/VariableDeclaration'; import ModuleScope from './ast/scopes/ModuleScope'; -import { EntityPathTracker } from './ast/utils/EntityPathTracker'; -import { UNKNOWN_PATH } from './ast/values'; +import { PathTracker, UNKNOWN_PATH } from './ast/utils/PathTracker'; import ExportShimVariable from './ast/variables/ExportShimVariable'; import ExternalVariable from './ast/variables/ExternalVariable'; import NamespaceVariable from './ast/variables/NamespaceVariable'; @@ -88,7 +88,7 @@ export interface AstContext { addImportMeta: (node: MetaProperty) => void; annotations: boolean; code: string; - deoptimizationTracker: EntityPathTracker; + deoptimizationTracker: PathTracker; error: (props: RollupError, pos: number) => void; fileName: string; getExports: () => string[]; @@ -98,7 +98,7 @@ export interface AstContext { getReexports: () => string[]; importDescriptions: { [name: string]: ImportDescription }; includeDynamicImport: (node: ImportExpression) => void; - includeVariable: (variable: Variable) => void; + includeVariable: (context: InclusionContext, variable: Variable) => void; isCrossChunkImport: (importDescription: ImportDescription) => boolean; magicString: MagicString; module: Module; // not to be used for tree-shaking @@ -450,7 +450,8 @@ export default class Module { } include(): void { - if (this.ast.shouldBeIncluded()) this.ast.include(false); + const context = createInclusionContext(); + if (this.ast.shouldBeIncluded(context)) this.ast.include(context, false); } includeAllExports() { @@ -459,11 +460,12 @@ export default class Module { markModuleAndImpureDependenciesAsExecuted(this); } + const context = createInclusionContext(); for (const exportName of this.getExports()) { const variable = this.getVariableForExportName(exportName); variable.deoptimizePath(UNKNOWN_PATH); if (!variable.included) { - variable.include(); + variable.include(context); this.graph.needsTreeshakingPass = true; } } @@ -472,7 +474,7 @@ export default class Module { const variable = this.getVariableForExportName(name); variable.deoptimizePath(UNKNOWN_PATH); if (!variable.included) { - variable.include(); + variable.include(context); this.graph.needsTreeshakingPass = true; } if (variable instanceof ExternalVariable) { @@ -482,7 +484,7 @@ export default class Module { } includeAllInBundle() { - this.ast.include(true); + this.ast.include(createInclusionContext(), true); } isIncluded() { @@ -835,10 +837,10 @@ export default class Module { } } - private includeVariable(variable: Variable) { + private includeVariable(context: InclusionContext, variable: Variable) { const variableModule = variable.module; if (!variable.included) { - variable.include(); + variable.include(context); this.graph.needsTreeshakingPass = true; } if (variableModule && variableModule !== this) { diff --git a/src/ast/CallOptions.ts b/src/ast/CallOptions.ts index 6a29388fca2..ad368d69951 100644 --- a/src/ast/CallOptions.ts +++ b/src/ast/CallOptions.ts @@ -1,40 +1,9 @@ -import CallExpression from './nodes/CallExpression'; -import NewExpression from './nodes/NewExpression'; -import Property from './nodes/Property'; import { ExpressionEntity } from './nodes/shared/Expression'; import SpreadElement from './nodes/SpreadElement'; -import TaggedTemplateExpression from './nodes/TaggedTemplateExpression'; -export type CallExpressionType = - | TaggedTemplateExpression - | CallExpression - | NewExpression - | Property; - -export interface CallCreateOptions { - args?: (ExpressionEntity | SpreadElement)[]; - callIdentifier: Object; - withNew: boolean; -} - -export default class CallOptions implements CallCreateOptions { - static create(callOptions: CallCreateOptions) { - return new this(callOptions); - } +export const NO_ARGS = []; +export interface CallOptions { args: (ExpressionEntity | SpreadElement)[]; - callIdentifier: Object; withNew: boolean; - - constructor( - { withNew = false, args = [], callIdentifier = undefined as any }: CallCreateOptions = {} as any - ) { - this.withNew = withNew; - this.args = args; - this.callIdentifier = callIdentifier; - } - - equals(callOptions: CallOptions) { - return callOptions && this.callIdentifier === callOptions.callIdentifier; - } } diff --git a/src/ast/Entity.ts b/src/ast/Entity.ts index e67b566d614..f67667c74c4 100644 --- a/src/ast/Entity.ts +++ b/src/ast/Entity.ts @@ -1,5 +1,5 @@ -import { ExecutionPathOptions } from './ExecutionPathOptions'; -import { ObjectPath } from './values'; +import { HasEffectsContext } from './ExecutionContext'; +import { ObjectPath } from './utils/PathTracker'; export interface Entity { toString: () => string; @@ -9,9 +9,9 @@ export interface WritableEntity extends Entity { /** * Reassign a given path of an object. * E.g., node.deoptimizePath(['x', 'y']) is called when something - * is assigned to node.x.y. If the path is [UNKNOWN_KEY], then the return + * is assigned to node.x.y. If the path is [UnknownKey], then the return * expression of this node is reassigned as well. */ deoptimizePath(path: ObjectPath): void; - hasEffectsWhenAssignedAtPath(path: ObjectPath, options: ExecutionPathOptions): boolean; + hasEffectsWhenAssignedAtPath(path: ObjectPath, execution: HasEffectsContext): boolean; } diff --git a/src/ast/ExecutionContext.ts b/src/ast/ExecutionContext.ts new file mode 100644 index 00000000000..dffd20fcc42 --- /dev/null +++ b/src/ast/ExecutionContext.ts @@ -0,0 +1,49 @@ +import { ExpressionEntity } from './nodes/shared/Expression'; +import { PathTracker } from './utils/PathTracker'; +import ThisVariable from './variables/ThisVariable'; + +interface ExecutionContextIgnore { + breakStatements: boolean; + labels: Set; + returnAwaitYield: boolean; +} + +export const BREAKFLOW_NONE: false = false; +export const BREAKFLOW_ERROR_RETURN: true = true; + +export type BreakFlow = typeof BREAKFLOW_NONE | typeof BREAKFLOW_ERROR_RETURN | Set; + +export interface InclusionContext { + breakFlow: BreakFlow; +} + +export interface HasEffectsContext extends InclusionContext { + accessed: PathTracker; + assigned: PathTracker; + called: PathTracker; + ignore: ExecutionContextIgnore; + instantiated: PathTracker; + replacedVariableInits: Map; +} + +export function createInclusionContext(): InclusionContext { + return { + breakFlow: BREAKFLOW_NONE + }; +} + +export function createHasEffectsContext(): HasEffectsContext { + return { + accessed: new PathTracker(), + assigned: new PathTracker(), + breakFlow: BREAKFLOW_NONE, + called: new PathTracker(), + ignore: { + breakStatements: false, + labels: new Set(), + returnAwaitYield: false + }, + instantiated: new PathTracker(), + replacedVariableInits: new Map() + }; +} diff --git a/src/ast/ExecutionPathOptions.ts b/src/ast/ExecutionPathOptions.ts deleted file mode 100644 index 5402bd6f882..00000000000 --- a/src/ast/ExecutionPathOptions.ts +++ /dev/null @@ -1,200 +0,0 @@ -import Immutable from 'immutable'; -import CallOptions from './CallOptions'; -import { Entity, WritableEntity } from './Entity'; -import CallExpression from './nodes/CallExpression'; -import Property from './nodes/Property'; -import { ExpressionEntity } from './nodes/shared/Expression'; -import { ObjectPath } from './values'; -import ThisVariable from './variables/ThisVariable'; - -export enum OptionTypes { - IGNORED_LABELS, - ACCESSED_NODES, - ASSIGNED_NODES, - IGNORE_BREAK_STATEMENTS, - IGNORE_RETURN_AWAIT_YIELD, - NODES_CALLED_AT_PATH_WITH_OPTIONS, - REPLACED_VARIABLE_INITS, - RETURN_EXPRESSIONS_ACCESSED_AT_PATH, - RETURN_EXPRESSIONS_ASSIGNED_AT_PATH, - RETURN_EXPRESSIONS_CALLED_AT_PATH -} - -interface RESULT_KEY {} -const RESULT_KEY: RESULT_KEY = {}; -type KeyTypes = OptionTypes | Entity | RESULT_KEY; - -export class ExecutionPathOptions { - static create() { - return new this(Immutable.Map()); - } - - private optionValues: Immutable.Map; - - private constructor( - optionValues: Immutable.Map - ) { - this.optionValues = optionValues; - } - - addAccessedNodeAtPath(path: ObjectPath, node: ExpressionEntity) { - return this.setIn([OptionTypes.ACCESSED_NODES, node, ...path, RESULT_KEY], true); - } - - addAccessedReturnExpressionAtPath(path: ObjectPath, callExpression: CallExpression | Property) { - return this.setIn( - [OptionTypes.RETURN_EXPRESSIONS_ACCESSED_AT_PATH, callExpression, ...path, RESULT_KEY], - true - ); - } - - addAssignedNodeAtPath(path: ObjectPath, node: WritableEntity) { - return this.setIn([OptionTypes.ASSIGNED_NODES, node, ...path, RESULT_KEY], true); - } - - addAssignedReturnExpressionAtPath(path: ObjectPath, callExpression: CallExpression | Property) { - return this.setIn( - [OptionTypes.RETURN_EXPRESSIONS_ASSIGNED_AT_PATH, callExpression, ...path, RESULT_KEY], - true - ); - } - - addCalledNodeAtPathWithOptions( - path: ObjectPath, - node: ExpressionEntity, - callOptions: CallOptions - ) { - return this.setIn( - [OptionTypes.NODES_CALLED_AT_PATH_WITH_OPTIONS, node, ...path, RESULT_KEY, callOptions], - true - ); - } - - addCalledReturnExpressionAtPath(path: ObjectPath, callExpression: CallExpression | Property) { - return this.setIn( - [OptionTypes.RETURN_EXPRESSIONS_CALLED_AT_PATH, callExpression, ...path, RESULT_KEY], - true - ); - } - - getHasEffectsWhenCalledOptions() { - return this.setIgnoreReturnAwaitYield() - .setIgnoreBreakStatements(false) - .setIgnoreNoLabels(); - } - - getReplacedVariableInit(variable: ThisVariable): ExpressionEntity { - return this.optionValues.getIn([OptionTypes.REPLACED_VARIABLE_INITS, variable]); - } - - hasNodeBeenAccessedAtPath(path: ObjectPath, node: ExpressionEntity): boolean { - return this.optionValues.getIn([OptionTypes.ACCESSED_NODES, node, ...path, RESULT_KEY]); - } - - hasNodeBeenAssignedAtPath(path: ObjectPath, node: WritableEntity): boolean { - return this.optionValues.getIn([OptionTypes.ASSIGNED_NODES, node, ...path, RESULT_KEY]); - } - - hasNodeBeenCalledAtPathWithOptions( - path: ObjectPath, - node: ExpressionEntity, - callOptions: CallOptions - ): boolean { - const previousCallOptions = this.optionValues.getIn([ - OptionTypes.NODES_CALLED_AT_PATH_WITH_OPTIONS, - node, - ...path, - RESULT_KEY - ]); - return ( - previousCallOptions && - previousCallOptions.find((_: any, otherCallOptions: CallOptions) => - otherCallOptions.equals(callOptions) - ) - ); - } - - hasReturnExpressionBeenAccessedAtPath( - path: ObjectPath, - callExpression: CallExpression | Property - ): boolean { - return this.optionValues.getIn([ - OptionTypes.RETURN_EXPRESSIONS_ACCESSED_AT_PATH, - callExpression, - ...path, - RESULT_KEY - ]); - } - - hasReturnExpressionBeenAssignedAtPath( - path: ObjectPath, - callExpression: CallExpression | Property - ): boolean { - return this.optionValues.getIn([ - OptionTypes.RETURN_EXPRESSIONS_ASSIGNED_AT_PATH, - callExpression, - ...path, - RESULT_KEY - ]); - } - - hasReturnExpressionBeenCalledAtPath( - path: ObjectPath, - callExpression: CallExpression | Property - ): boolean { - return this.optionValues.getIn([ - OptionTypes.RETURN_EXPRESSIONS_CALLED_AT_PATH, - callExpression, - ...path, - RESULT_KEY - ]); - } - - ignoreBreakStatements() { - return this.get(OptionTypes.IGNORE_BREAK_STATEMENTS); - } - - ignoreLabel(labelName: string) { - return this.optionValues.getIn([OptionTypes.IGNORED_LABELS, labelName]); - } - - ignoreReturnAwaitYield() { - return this.get(OptionTypes.IGNORE_RETURN_AWAIT_YIELD); - } - - replaceVariableInit(variable: ThisVariable, init: ExpressionEntity) { - return this.setIn([OptionTypes.REPLACED_VARIABLE_INITS, variable], init); - } - - setIgnoreBreakStatements(value = true) { - return this.set(OptionTypes.IGNORE_BREAK_STATEMENTS, value); - } - - setIgnoreLabel(labelName: string) { - return this.setIn([OptionTypes.IGNORED_LABELS, labelName], true); - } - - setIgnoreNoLabels() { - return this.remove(OptionTypes.IGNORED_LABELS); - } - - setIgnoreReturnAwaitYield(value = true) { - return this.set(OptionTypes.IGNORE_RETURN_AWAIT_YIELD, value); - } - - private get(option: OptionTypes) { - return this.optionValues.get(option); - } - - private remove(option: OptionTypes) { - return new ExecutionPathOptions(this.optionValues.remove(option)); - } - - private set(option: OptionTypes, value: boolean | ExpressionEntity[]) { - return new ExecutionPathOptions(this.optionValues.set(option, value)); - } - - private setIn(optionPath: (string | Entity | RESULT_KEY)[], value: boolean | Entity) { - return new ExecutionPathOptions(this.optionValues.setIn(optionPath, value)); - } -} diff --git a/src/ast/nodes/ArrayExpression.ts b/src/ast/nodes/ArrayExpression.ts index e2c432ac6e1..f37b81dbbc5 100644 --- a/src/ast/nodes/ArrayExpression.ts +++ b/src/ast/nodes/ArrayExpression.ts @@ -1,12 +1,11 @@ -import CallOptions from '../CallOptions'; -import { ExecutionPathOptions } from '../ExecutionPathOptions'; +import { CallOptions } from '../CallOptions'; +import { HasEffectsContext } from '../ExecutionContext'; +import { ObjectPath, UNKNOWN_PATH } from '../utils/PathTracker'; import { arrayMembers, getMemberReturnExpressionWhenCalled, hasMemberEffectWhenCalled, - ObjectPath, - UNKNOWN_EXPRESSION, - UNKNOWN_PATH + UNKNOWN_EXPRESSION } from '../values'; import * as NodeType from './NodeType'; import { ExpressionNode, NodeBase } from './shared/Node'; @@ -35,10 +34,10 @@ export default class ArrayExpression extends NodeBase { hasEffectsWhenCalledAtPath( path: ObjectPath, callOptions: CallOptions, - options: ExecutionPathOptions + context: HasEffectsContext ): boolean { if (path.length === 1) { - return hasMemberEffectWhenCalled(arrayMembers, path[0], this.included, callOptions, options); + return hasMemberEffectWhenCalled(arrayMembers, path[0], this.included, callOptions, context); } return true; } diff --git a/src/ast/nodes/ArrayPattern.ts b/src/ast/nodes/ArrayPattern.ts index dc59174acf0..3336d6bfa75 100644 --- a/src/ast/nodes/ArrayPattern.ts +++ b/src/ast/nodes/ArrayPattern.ts @@ -1,8 +1,8 @@ -import { ExecutionPathOptions } from '../ExecutionPathOptions'; -import { EMPTY_PATH, ObjectPath, UNKNOWN_EXPRESSION } from '../values'; +import { HasEffectsContext } from '../ExecutionContext'; +import { EMPTY_PATH, ObjectPath } from '../utils/PathTracker'; +import { UNKNOWN_EXPRESSION } from '../values'; import Variable from '../variables/Variable'; import * as NodeType from './NodeType'; -import { ExpressionEntity } from './shared/Expression'; import { NodeBase } from './shared/Node'; import { PatternNode } from './shared/Pattern'; @@ -18,7 +18,7 @@ export default class ArrayPattern extends NodeBase implements PatternNode { } } - declare(kind: string, _init: ExpressionEntity) { + declare(kind: string) { const variables = []; for (const element of this.elements) { if (element !== null) { @@ -38,10 +38,10 @@ export default class ArrayPattern extends NodeBase implements PatternNode { } } - hasEffectsWhenAssignedAtPath(path: ObjectPath, options: ExecutionPathOptions) { + hasEffectsWhenAssignedAtPath(path: ObjectPath, context: HasEffectsContext) { if (path.length > 0) return true; for (const element of this.elements) { - if (element !== null && element.hasEffectsWhenAssignedAtPath(EMPTY_PATH, options)) + if (element !== null && element.hasEffectsWhenAssignedAtPath(EMPTY_PATH, context)) return true; } return false; diff --git a/src/ast/nodes/ArrowFunctionExpression.ts b/src/ast/nodes/ArrowFunctionExpression.ts index 0aac311d6ec..4d344c934c1 100644 --- a/src/ast/nodes/ArrowFunctionExpression.ts +++ b/src/ast/nodes/ArrowFunctionExpression.ts @@ -1,13 +1,14 @@ -import CallOptions from '../CallOptions'; -import { ExecutionPathOptions } from '../ExecutionPathOptions'; +import { CallOptions } from '../CallOptions'; +import { BREAKFLOW_NONE, HasEffectsContext, InclusionContext } from '../ExecutionContext'; import ReturnValueScope from '../scopes/ReturnValueScope'; import Scope from '../scopes/Scope'; -import { ObjectPath, UNKNOWN_EXPRESSION, UNKNOWN_KEY, UNKNOWN_PATH } from '../values'; +import { ObjectPath, UNKNOWN_PATH, UnknownKey } from '../utils/PathTracker'; +import { UNKNOWN_EXPRESSION } from '../values'; import BlockStatement from './BlockStatement'; import Identifier from './Identifier'; import * as NodeType from './NodeType'; import RestElement from './RestElement'; -import { ExpressionNode, GenericEsTreeNode, NodeBase } from './shared/Node'; +import { ExpressionNode, GenericEsTreeNode, IncludeChildren, NodeBase } from './shared/Node'; import { PatternNode } from './shared/Pattern'; import SpreadElement from './SpreadElement'; @@ -25,7 +26,7 @@ export default class ArrowFunctionExpression extends NodeBase { deoptimizePath(path: ObjectPath) { // A reassignment of UNKNOWN_PATH is considered equivalent to having lost track // which means the return expression needs to be reassigned - if (path.length === 1 && path[0] === UNKNOWN_KEY) { + if (path.length === 1 && path[0] === UnknownKey) { this.scope.getReturnExpression().deoptimizePath(UNKNOWN_PATH); } } @@ -34,44 +35,54 @@ export default class ArrowFunctionExpression extends NodeBase { return path.length === 0 ? this.scope.getReturnExpression() : UNKNOWN_EXPRESSION; } - hasEffects(_options: ExecutionPathOptions) { + hasEffects() { return false; } - hasEffectsWhenAccessedAtPath(path: ObjectPath, _options: ExecutionPathOptions) { + hasEffectsWhenAccessedAtPath(path: ObjectPath) { return path.length > 1; } - hasEffectsWhenAssignedAtPath(path: ObjectPath, _options: ExecutionPathOptions) { + hasEffectsWhenAssignedAtPath(path: ObjectPath) { return path.length > 1; } hasEffectsWhenCalledAtPath( path: ObjectPath, _callOptions: CallOptions, - options: ExecutionPathOptions + context: HasEffectsContext ): boolean { - if (path.length > 0) { - return true; - } + if (path.length > 0) return true; for (const param of this.params) { - if (param.hasEffects(options)) return true; + if (param.hasEffects(context)) return true; } - return this.body.hasEffects(options); + const { ignore, breakFlow } = context; + context.ignore = { + breakStatements: false, + labels: new Set(), + returnAwaitYield: true + }; + if (this.body.hasEffects(context)) return true; + context.ignore = ignore; + context.breakFlow = breakFlow; + return false; } - include(includeChildrenRecursively: boolean | 'variables') { + include(context: InclusionContext, includeChildrenRecursively: IncludeChildren) { this.included = true; - this.body.include(includeChildrenRecursively); for (const param of this.params) { if (!(param instanceof Identifier)) { - param.include(includeChildrenRecursively); + param.include(context, includeChildrenRecursively); } } + const { breakFlow } = context; + context.breakFlow = BREAKFLOW_NONE; + this.body.include(context, includeChildrenRecursively); + context.breakFlow = breakFlow; } - includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void { - this.scope.includeCallArguments(args); + includeCallArguments(context: InclusionContext, args: (ExpressionNode | SpreadElement)[]): void { + this.scope.includeCallArguments(context, args); } initialise() { diff --git a/src/ast/nodes/AssignmentExpression.ts b/src/ast/nodes/AssignmentExpression.ts index 804e058c04c..98a539d3db5 100644 --- a/src/ast/nodes/AssignmentExpression.ts +++ b/src/ast/nodes/AssignmentExpression.ts @@ -1,8 +1,8 @@ import MagicString from 'magic-string'; import { RenderOptions } from '../../utils/renderHelpers'; import { getSystemExportStatement } from '../../utils/systemJsRendering'; -import { ExecutionPathOptions } from '../ExecutionPathOptions'; -import { EMPTY_PATH, ObjectPath, UNKNOWN_PATH } from '../values'; +import { HasEffectsContext } from '../ExecutionContext'; +import { EMPTY_PATH, ObjectPath, UNKNOWN_PATH } from '../utils/PathTracker'; import Variable from '../variables/Variable'; import * as NodeType from './NodeType'; import { ExpressionNode, NodeBase } from './shared/Node'; @@ -34,16 +34,16 @@ export default class AssignmentExpression extends NodeBase { this.right.deoptimizePath(UNKNOWN_PATH); } - hasEffects(options: ExecutionPathOptions): boolean { + hasEffects(context: HasEffectsContext): boolean { return ( - this.right.hasEffects(options) || - this.left.hasEffects(options) || - this.left.hasEffectsWhenAssignedAtPath(EMPTY_PATH, options) + this.right.hasEffects(context) || + this.left.hasEffects(context) || + this.left.hasEffectsWhenAssignedAtPath(EMPTY_PATH, context) ); } - hasEffectsWhenAccessedAtPath(path: ObjectPath, options: ExecutionPathOptions): boolean { - return path.length > 0 && this.right.hasEffectsWhenAccessedAtPath(path, options); + hasEffectsWhenAccessedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { + return path.length > 0 && this.right.hasEffectsWhenAccessedAtPath(path, context); } render(code: MagicString, options: RenderOptions) { diff --git a/src/ast/nodes/AssignmentPattern.ts b/src/ast/nodes/AssignmentPattern.ts index f172dd1bf5b..c9651fe5bb5 100644 --- a/src/ast/nodes/AssignmentPattern.ts +++ b/src/ast/nodes/AssignmentPattern.ts @@ -1,8 +1,8 @@ import MagicString from 'magic-string'; import { BLANK } from '../../utils/blank'; import { NodeRenderOptions, RenderOptions } from '../../utils/renderHelpers'; -import { ExecutionPathOptions } from '../ExecutionPathOptions'; -import { EMPTY_PATH, ObjectPath, UNKNOWN_PATH } from '../values'; +import { HasEffectsContext } from '../ExecutionContext'; +import { EMPTY_PATH, ObjectPath, UNKNOWN_PATH } from '../utils/PathTracker'; import Variable from '../variables/Variable'; import * as NodeType from './NodeType'; import { ExpressionEntity } from './shared/Expression'; @@ -32,8 +32,8 @@ export default class AssignmentPattern extends NodeBase implements PatternNode { path.length === 0 && this.left.deoptimizePath(path); } - hasEffectsWhenAssignedAtPath(path: ObjectPath, options: ExecutionPathOptions): boolean { - return path.length > 0 || this.left.hasEffectsWhenAssignedAtPath(EMPTY_PATH, options); + hasEffectsWhenAssignedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { + return path.length > 0 || this.left.hasEffectsWhenAssignedAtPath(EMPTY_PATH, context); } render( diff --git a/src/ast/nodes/AwaitExpression.ts b/src/ast/nodes/AwaitExpression.ts index cacaaeb42b8..2240c4f1321 100644 --- a/src/ast/nodes/AwaitExpression.ts +++ b/src/ast/nodes/AwaitExpression.ts @@ -1,6 +1,6 @@ import MagicString from 'magic-string'; import { RenderOptions } from '../../utils/renderHelpers'; -import { ExecutionPathOptions } from '../ExecutionPathOptions'; +import { HasEffectsContext, InclusionContext } from '../ExecutionContext'; import ArrowFunctionExpression from './ArrowFunctionExpression'; import * as NodeType from './NodeType'; import FunctionNode from './shared/FunctionNode'; @@ -10,11 +10,11 @@ export default class AwaitExpression extends NodeBase { argument!: ExpressionNode; type!: NodeType.tAwaitExpression; - hasEffects(options: ExecutionPathOptions) { - return super.hasEffects(options) || !options.ignoreReturnAwaitYield(); + hasEffects(context: HasEffectsContext) { + return super.hasEffects(context) || !context.ignore.returnAwaitYield; } - include(includeChildrenRecursively: IncludeChildren) { + include(context: InclusionContext, includeChildrenRecursively: IncludeChildren) { checkTopLevelAwait: if (!this.included && !this.context.usesTopLevelAwait) { let parent = this.parent; do { @@ -23,7 +23,7 @@ export default class AwaitExpression extends NodeBase { } while ((parent = (parent as Node).parent as Node)); this.context.usesTopLevelAwait = true; } - super.include(includeChildrenRecursively); + super.include(context, includeChildrenRecursively); } render(code: MagicString, options: RenderOptions) { diff --git a/src/ast/nodes/BinaryExpression.ts b/src/ast/nodes/BinaryExpression.ts index c0ff4321ae9..9cf3f5d76bf 100644 --- a/src/ast/nodes/BinaryExpression.ts +++ b/src/ast/nodes/BinaryExpression.ts @@ -1,10 +1,7 @@ import { DeoptimizableEntity } from '../DeoptimizableEntity'; -import { ExecutionPathOptions } from '../ExecutionPathOptions'; -import { - EMPTY_IMMUTABLE_TRACKER, - ImmutableEntityPathTracker -} from '../utils/ImmutableEntityPathTracker'; -import { EMPTY_PATH, LiteralValueOrUnknown, ObjectPath, UNKNOWN_VALUE } from '../values'; +import { HasEffectsContext } from '../ExecutionContext'; +import { EMPTY_IMMUTABLE_TRACKER, EMPTY_PATH, ObjectPath, PathTracker } from '../utils/PathTracker'; +import { LiteralValueOrUnknown, UnknownValue } from '../values'; import ExpressionStatement from './ExpressionStatement'; import { LiteralValue } from './Literal'; import * as NodeType from './NodeType'; @@ -35,8 +32,8 @@ const binaryOperators: { '>>': (left: any, right: any) => left >> right, '>>>': (left: any, right: any) => left >>> right, '^': (left: any, right: any) => left ^ right, - in: () => UNKNOWN_VALUE, - instanceof: () => UNKNOWN_VALUE, + in: () => UnknownValue, + instanceof: () => UnknownValue, '|': (left: any, right: any) => left | right }; @@ -50,35 +47,34 @@ export default class BinaryExpression extends NodeBase implements DeoptimizableE getLiteralValueAtPath( path: ObjectPath, - recursionTracker: ImmutableEntityPathTracker, + recursionTracker: PathTracker, origin: DeoptimizableEntity ): LiteralValueOrUnknown { - if (path.length > 0) return UNKNOWN_VALUE; + if (path.length > 0) return UnknownValue; const leftValue = this.left.getLiteralValueAtPath(EMPTY_PATH, recursionTracker, origin); - if (leftValue === UNKNOWN_VALUE) return UNKNOWN_VALUE; + if (leftValue === UnknownValue) return UnknownValue; const rightValue = this.right.getLiteralValueAtPath(EMPTY_PATH, recursionTracker, origin); - if (rightValue === UNKNOWN_VALUE) return UNKNOWN_VALUE; + if (rightValue === UnknownValue) return UnknownValue; const operatorFn = binaryOperators[this.operator]; - if (!operatorFn) return UNKNOWN_VALUE; + if (!operatorFn) return UnknownValue; - return operatorFn(leftValue as LiteralValue, rightValue as LiteralValue); + return operatorFn(leftValue, rightValue); } - hasEffects(options: ExecutionPathOptions): boolean { + hasEffects(context: HasEffectsContext): boolean { // support some implicit type coercion runtime errors if ( this.operator === '+' && this.parent instanceof ExpressionStatement && this.left.getLiteralValueAtPath(EMPTY_PATH, EMPTY_IMMUTABLE_TRACKER, this) === '' - ) { + ) return true; - } - return super.hasEffects(options); + return super.hasEffects(context); } - hasEffectsWhenAccessedAtPath(path: ObjectPath, _options: ExecutionPathOptions) { + hasEffectsWhenAccessedAtPath(path: ObjectPath) { return path.length > 1; } } diff --git a/src/ast/nodes/BlockStatement.ts b/src/ast/nodes/BlockStatement.ts index 83f3bd01cfb..1752d5907dd 100644 --- a/src/ast/nodes/BlockStatement.ts +++ b/src/ast/nodes/BlockStatement.ts @@ -1,6 +1,6 @@ import MagicString from 'magic-string'; import { RenderOptions, renderStatementList } from '../../utils/renderHelpers'; -import { ExecutionPathOptions } from '../ExecutionPathOptions'; +import { HasEffectsContext, InclusionContext } from '../ExecutionContext'; import BlockScope from '../scopes/BlockScope'; import ChildScope from '../scopes/ChildScope'; import Scope from '../scopes/Scope'; @@ -25,18 +25,19 @@ export default class BlockStatement extends StatementBase { : new BlockScope(parentScope); } - hasEffects(options: ExecutionPathOptions) { + hasEffects(context: HasEffectsContext) { for (const node of this.body) { - if (node.hasEffects(options)) return true; + if (node.hasEffects(context)) return true; + if (context.breakFlow) break; } return false; } - include(includeChildrenRecursively: IncludeChildren) { + include(context: InclusionContext, includeChildrenRecursively: IncludeChildren) { this.included = true; for (const node of this.body) { - if (includeChildrenRecursively || node.shouldBeIncluded()) - node.include(includeChildrenRecursively); + if (includeChildrenRecursively || node.shouldBeIncluded(context)) + node.include(context, includeChildrenRecursively); } } diff --git a/src/ast/nodes/BreakStatement.ts b/src/ast/nodes/BreakStatement.ts index 24b6e3c01e2..d1c24b2a799 100644 --- a/src/ast/nodes/BreakStatement.ts +++ b/src/ast/nodes/BreakStatement.ts @@ -1,4 +1,4 @@ -import { ExecutionPathOptions } from '../ExecutionPathOptions'; +import { HasEffectsContext, InclusionContext } from '../ExecutionContext'; import Identifier from './Identifier'; import * as NodeType from './NodeType'; import { StatementBase } from './shared/Node'; @@ -7,11 +7,16 @@ export default class BreakStatement extends StatementBase { label!: Identifier | null; type!: NodeType.tBreakStatement; - hasEffects(options: ExecutionPathOptions) { - return ( - super.hasEffects(options) || - !options.ignoreBreakStatements() || - (this.label !== null && !options.ignoreLabel(this.label.name)) - ); + hasEffects(context: HasEffectsContext) { + if (!(this.label ? context.ignore.labels.has(this.label.name) : context.ignore.breakStatements)) + return true; + context.breakFlow = new Set([this.label && this.label.name]); + return false; + } + + include(context: InclusionContext) { + this.included = true; + if (this.label) this.label.include(context); + context.breakFlow = new Set([this.label && this.label.name]); } } diff --git a/src/ast/nodes/CallExpression.ts b/src/ast/nodes/CallExpression.ts index 4e2a7ae416c..68daeb86768 100644 --- a/src/ast/nodes/CallExpression.ts +++ b/src/ast/nodes/CallExpression.ts @@ -5,21 +5,17 @@ import { NodeRenderOptions, RenderOptions } from '../../utils/renderHelpers'; -import CallOptions from '../CallOptions'; +import { CallOptions } from '../CallOptions'; import { DeoptimizableEntity } from '../DeoptimizableEntity'; -import { ExecutionPathOptions } from '../ExecutionPathOptions'; +import { HasEffectsContext, InclusionContext } from '../ExecutionContext'; import { EMPTY_IMMUTABLE_TRACKER, - ImmutableEntityPathTracker -} from '../utils/ImmutableEntityPathTracker'; -import { EMPTY_PATH, - LiteralValueOrUnknown, ObjectPath, - UNKNOWN_EXPRESSION, - UNKNOWN_PATH, - UNKNOWN_VALUE -} from '../values'; + PathTracker, + UNKNOWN_PATH +} from '../utils/PathTracker'; +import { LiteralValueOrUnknown, UNKNOWN_EXPRESSION, UnknownValue } from '../values'; import Identifier from './Identifier'; import * as NodeType from './NodeType'; import { ExpressionEntity } from './shared/Expression'; @@ -86,21 +82,23 @@ export default class CallExpression extends NodeBase implements DeoptimizableEnt } deoptimizePath(path: ObjectPath) { - if (path.length > 0 && !this.context.deoptimizationTracker.track(this, path)) { - if (this.returnExpression === null) { - this.returnExpression = this.callee.getReturnExpressionWhenCalledAtPath( - EMPTY_PATH, - EMPTY_IMMUTABLE_TRACKER, - this - ); - } - this.returnExpression.deoptimizePath(path); + if (path.length === 0) return; + const trackedEntities = this.context.deoptimizationTracker.getEntities(path); + if (trackedEntities.has(this)) return; + trackedEntities.add(this); + if (this.returnExpression === null) { + this.returnExpression = this.callee.getReturnExpressionWhenCalledAtPath( + EMPTY_PATH, + EMPTY_IMMUTABLE_TRACKER, + this + ); } + this.returnExpression.deoptimizePath(path); } getLiteralValueAtPath( path: ObjectPath, - recursionTracker: ImmutableEntityPathTracker, + recursionTracker: PathTracker, origin: DeoptimizableEntity ): LiteralValueOrUnknown { if (this.returnExpression === null) { @@ -110,23 +108,23 @@ export default class CallExpression extends NodeBase implements DeoptimizableEnt this ); } - if ( - this.returnExpression === UNKNOWN_EXPRESSION || - recursionTracker.isTracked(this.returnExpression, path) - ) { - return UNKNOWN_VALUE; + if (this.returnExpression === UNKNOWN_EXPRESSION) { + return UnknownValue; + } + const trackedEntities = recursionTracker.getEntities(path); + if (trackedEntities.has(this.returnExpression)) { + return UnknownValue; } this.expressionsToBeDeoptimized.push(origin); - return this.returnExpression.getLiteralValueAtPath( - path, - recursionTracker.track(this.returnExpression, path), - origin - ); + trackedEntities.add(this.returnExpression); + const value = this.returnExpression.getLiteralValueAtPath(path, recursionTracker, origin); + trackedEntities.delete(this.returnExpression); + return value; } getReturnExpressionWhenCalledAtPath( path: ObjectPath, - recursionTracker: ImmutableEntityPathTracker, + recursionTracker: PathTracker, origin: DeoptimizableEntity ) { if (this.returnExpression === null) { @@ -136,73 +134,72 @@ export default class CallExpression extends NodeBase implements DeoptimizableEnt this ); } - if ( - this.returnExpression === UNKNOWN_EXPRESSION || - recursionTracker.isTracked(this.returnExpression, path) - ) { + if (this.returnExpression === UNKNOWN_EXPRESSION) { + return UNKNOWN_EXPRESSION; + } + const trackedEntities = recursionTracker.getEntities(path); + if (trackedEntities.has(this.returnExpression)) { return UNKNOWN_EXPRESSION; } this.expressionsToBeDeoptimized.push(origin); - return this.returnExpression.getReturnExpressionWhenCalledAtPath( + trackedEntities.add(this.returnExpression); + const value = this.returnExpression.getReturnExpressionWhenCalledAtPath( path, - recursionTracker.track(this.returnExpression, path), + recursionTracker, origin ); + trackedEntities.delete(this.returnExpression); + return value; } - hasEffects(options: ExecutionPathOptions): boolean { + hasEffects(context: HasEffectsContext): boolean { for (const argument of this.arguments) { - if (argument.hasEffects(options)) return true; + if (argument.hasEffects(context)) return true; } if (this.context.annotations && this.annotatedPure) return false; return ( - this.callee.hasEffects(options) || - this.callee.hasEffectsWhenCalledAtPath( - EMPTY_PATH, - this.callOptions, - options.getHasEffectsWhenCalledOptions() - ) + this.callee.hasEffects(context) || + this.callee.hasEffectsWhenCalledAtPath(EMPTY_PATH, this.callOptions, context) ); } - hasEffectsWhenAccessedAtPath(path: ObjectPath, options: ExecutionPathOptions): boolean { - return ( - path.length > 0 && - !options.hasReturnExpressionBeenAccessedAtPath(path, this) && - (this.returnExpression as ExpressionEntity).hasEffectsWhenAccessedAtPath( - path, - options.addAccessedReturnExpressionAtPath(path, this) - ) - ); + hasEffectsWhenAccessedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { + if (path.length === 0) return false; + const trackedExpressions = context.accessed.getEntities(path); + if (trackedExpressions.has(this)) return false; + trackedExpressions.add(this); + return (this.returnExpression as ExpressionEntity).hasEffectsWhenAccessedAtPath(path, context); } - hasEffectsWhenAssignedAtPath(path: ObjectPath, options: ExecutionPathOptions): boolean { - return ( - path.length === 0 || - (!options.hasReturnExpressionBeenAssignedAtPath(path, this) && - (this.returnExpression as ExpressionEntity).hasEffectsWhenAssignedAtPath( - path, - options.addAssignedReturnExpressionAtPath(path, this) - )) - ); + hasEffectsWhenAssignedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { + if (path.length === 0) return true; + const trackedExpressions = context.assigned.getEntities(path); + if (trackedExpressions.has(this)) return false; + trackedExpressions.add(this); + return (this.returnExpression as ExpressionEntity).hasEffectsWhenAssignedAtPath(path, context); } hasEffectsWhenCalledAtPath( path: ObjectPath, callOptions: CallOptions, - options: ExecutionPathOptions + context: HasEffectsContext ): boolean { - if (options.hasReturnExpressionBeenCalledAtPath(path, this)) return false; + const trackedExpressions = (callOptions.withNew + ? context.instantiated + : context.called + ).getEntities(path); + if (trackedExpressions.has(this)) return false; + trackedExpressions.add(this); return (this.returnExpression as ExpressionEntity).hasEffectsWhenCalledAtPath( path, callOptions, - options.addCalledReturnExpressionAtPath(path, this) + context ); } - include(includeChildrenRecursively: IncludeChildren) { + include(context: InclusionContext, includeChildrenRecursively: IncludeChildren) { if (includeChildrenRecursively) { - super.include(includeChildrenRecursively); + super.include(context, includeChildrenRecursively); if ( includeChildrenRecursively === INCLUDE_PARAMETERS && this.callee instanceof Identifier && @@ -212,20 +209,19 @@ export default class CallExpression extends NodeBase implements DeoptimizableEnt } } else { this.included = true; - this.callee.include(false); + this.callee.include(context, false); } - this.callee.includeCallArguments(this.arguments); + this.callee.includeCallArguments(context, this.arguments); if (!(this.returnExpression as ExpressionEntity).included) { - (this.returnExpression as ExpressionEntity).include(false); + (this.returnExpression as ExpressionEntity).include(context, false); } } initialise() { - this.callOptions = CallOptions.create({ + this.callOptions = { args: this.arguments, - callIdentifier: this, withNew: false - }); + }; } render( diff --git a/src/ast/nodes/ClassBody.ts b/src/ast/nodes/ClassBody.ts index c6cf7d4e559..1c341665489 100644 --- a/src/ast/nodes/ClassBody.ts +++ b/src/ast/nodes/ClassBody.ts @@ -1,6 +1,6 @@ -import CallOptions from '../CallOptions'; -import { ExecutionPathOptions } from '../ExecutionPathOptions'; -import { EMPTY_PATH, ObjectPath } from '../values'; +import { CallOptions } from '../CallOptions'; +import { HasEffectsContext } from '../ExecutionContext'; +import { EMPTY_PATH, ObjectPath } from '../utils/PathTracker'; import MethodDefinition from './MethodDefinition'; import * as NodeType from './NodeType'; import { NodeBase } from './shared/Node'; @@ -14,14 +14,12 @@ export default class ClassBody extends NodeBase { hasEffectsWhenCalledAtPath( path: ObjectPath, callOptions: CallOptions, - options: ExecutionPathOptions + context: HasEffectsContext ) { - if (path.length > 0) { - return true; - } + if (path.length > 0) return true; return ( this.classConstructor !== null && - this.classConstructor.hasEffectsWhenCalledAtPath(EMPTY_PATH, callOptions, options) + this.classConstructor.hasEffectsWhenCalledAtPath(EMPTY_PATH, callOptions, context) ); } diff --git a/src/ast/nodes/ConditionalExpression.ts b/src/ast/nodes/ConditionalExpression.ts index abf71d7af86..630cc08754f 100644 --- a/src/ast/nodes/ConditionalExpression.ts +++ b/src/ast/nodes/ConditionalExpression.ts @@ -7,20 +7,17 @@ import { RenderOptions } from '../../utils/renderHelpers'; import { removeAnnotations } from '../../utils/treeshakeNode'; -import CallOptions from '../CallOptions'; +import { CallOptions } from '../CallOptions'; import { DeoptimizableEntity } from '../DeoptimizableEntity'; -import { ExecutionPathOptions } from '../ExecutionPathOptions'; +import { HasEffectsContext, InclusionContext } from '../ExecutionContext'; import { EMPTY_IMMUTABLE_TRACKER, - ImmutableEntityPathTracker -} from '../utils/ImmutableEntityPathTracker'; -import { EMPTY_PATH, - LiteralValueOrUnknown, ObjectPath, - UNKNOWN_PATH, - UNKNOWN_VALUE -} from '../values'; + PathTracker, + UNKNOWN_PATH +} from '../utils/PathTracker'; +import { LiteralValueOrUnknown, UnknownValue } from '../values'; import CallExpression from './CallExpression'; import * as NodeType from './NodeType'; import { ExpressionEntity } from './shared/Expression'; @@ -70,18 +67,18 @@ export default class ConditionalExpression extends NodeBase implements Deoptimiz getLiteralValueAtPath( path: ObjectPath, - recursionTracker: ImmutableEntityPathTracker, + recursionTracker: PathTracker, origin: DeoptimizableEntity ): LiteralValueOrUnknown { if (!this.isBranchResolutionAnalysed) this.analyseBranchResolution(); - if (this.usedBranch === null) return UNKNOWN_VALUE; + if (this.usedBranch === null) return UnknownValue; this.expressionsToBeDeoptimized.push(origin); return this.usedBranch.getLiteralValueAtPath(path, recursionTracker, origin); } getReturnExpressionWhenCalledAtPath( path: ObjectPath, - recursionTracker: ImmutableEntityPathTracker, + recursionTracker: PathTracker, origin: DeoptimizableEntity ): ExpressionEntity { if (!this.isBranchResolutionAnalysed) this.analyseBranchResolution(); @@ -94,58 +91,62 @@ export default class ConditionalExpression extends NodeBase implements Deoptimiz return this.usedBranch.getReturnExpressionWhenCalledAtPath(path, recursionTracker, origin); } - hasEffects(options: ExecutionPathOptions): boolean { - if (this.test.hasEffects(options)) return true; + hasEffects(context: HasEffectsContext): boolean { + if (this.test.hasEffects(context)) return true; if (this.usedBranch === null) { - return this.consequent.hasEffects(options) || this.alternate.hasEffects(options); + return this.consequent.hasEffects(context) || this.alternate.hasEffects(context); } - return this.usedBranch.hasEffects(options); + return this.usedBranch.hasEffects(context); } - hasEffectsWhenAccessedAtPath(path: ObjectPath, options: ExecutionPathOptions): boolean { + hasEffectsWhenAccessedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { if (path.length === 0) return false; if (this.usedBranch === null) { return ( - this.consequent.hasEffectsWhenAccessedAtPath(path, options) || - this.alternate.hasEffectsWhenAccessedAtPath(path, options) + this.consequent.hasEffectsWhenAccessedAtPath(path, context) || + this.alternate.hasEffectsWhenAccessedAtPath(path, context) ); } - return this.usedBranch.hasEffectsWhenAccessedAtPath(path, options); + return this.usedBranch.hasEffectsWhenAccessedAtPath(path, context); } - hasEffectsWhenAssignedAtPath(path: ObjectPath, options: ExecutionPathOptions): boolean { + hasEffectsWhenAssignedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { if (path.length === 0) return true; if (this.usedBranch === null) { return ( - this.consequent.hasEffectsWhenAssignedAtPath(path, options) || - this.alternate.hasEffectsWhenAssignedAtPath(path, options) + this.consequent.hasEffectsWhenAssignedAtPath(path, context) || + this.alternate.hasEffectsWhenAssignedAtPath(path, context) ); } - return this.usedBranch.hasEffectsWhenAssignedAtPath(path, options); + return this.usedBranch.hasEffectsWhenAssignedAtPath(path, context); } hasEffectsWhenCalledAtPath( path: ObjectPath, callOptions: CallOptions, - options: ExecutionPathOptions + context: HasEffectsContext ): boolean { if (this.usedBranch === null) { return ( - this.consequent.hasEffectsWhenCalledAtPath(path, callOptions, options) || - this.alternate.hasEffectsWhenCalledAtPath(path, callOptions, options) + this.consequent.hasEffectsWhenCalledAtPath(path, callOptions, context) || + this.alternate.hasEffectsWhenCalledAtPath(path, callOptions, context) ); } - return this.usedBranch.hasEffectsWhenCalledAtPath(path, callOptions, options); + return this.usedBranch.hasEffectsWhenCalledAtPath(path, callOptions, context); } - include(includeChildrenRecursively: IncludeChildren) { + include(context: InclusionContext, includeChildrenRecursively: IncludeChildren) { this.included = true; - if (includeChildrenRecursively || this.usedBranch === null || this.test.shouldBeIncluded()) { - this.test.include(includeChildrenRecursively); - this.consequent.include(includeChildrenRecursively); - this.alternate.include(includeChildrenRecursively); + if ( + includeChildrenRecursively || + this.usedBranch === null || + this.test.shouldBeIncluded(context) + ) { + this.test.include(context, includeChildrenRecursively); + this.consequent.include(context, includeChildrenRecursively); + this.alternate.include(context, includeChildrenRecursively); } else { - this.usedBranch.include(includeChildrenRecursively); + this.usedBranch.include(context, includeChildrenRecursively); } } @@ -182,7 +183,7 @@ export default class ConditionalExpression extends NodeBase implements Deoptimiz private analyseBranchResolution() { this.isBranchResolutionAnalysed = true; const testValue = this.test.getLiteralValueAtPath(EMPTY_PATH, EMPTY_IMMUTABLE_TRACKER, this); - if (testValue !== UNKNOWN_VALUE) { + if (testValue !== UnknownValue) { if (testValue) { this.usedBranch = this.consequent; this.unusedBranch = this.alternate; diff --git a/src/ast/nodes/ContinueStatement.ts b/src/ast/nodes/ContinueStatement.ts new file mode 100644 index 00000000000..93cfec4d6f8 --- /dev/null +++ b/src/ast/nodes/ContinueStatement.ts @@ -0,0 +1,22 @@ +import { HasEffectsContext, InclusionContext } from '../ExecutionContext'; +import Identifier from './Identifier'; +import * as NodeType from './NodeType'; +import { StatementBase } from './shared/Node'; + +export default class ContinueStatement extends StatementBase { + label!: Identifier | null; + type!: NodeType.tContinueStatement; + + hasEffects(context: HasEffectsContext) { + if (!(this.label ? context.ignore.labels.has(this.label.name) : context.ignore.breakStatements)) + return true; + context.breakFlow = new Set([this.label && this.label.name]); + return false; + } + + include(context: InclusionContext) { + this.included = true; + if (this.label) this.label.include(context); + context.breakFlow = new Set([this.label && this.label.name]); + } +} diff --git a/src/ast/nodes/DoWhileStatement.ts b/src/ast/nodes/DoWhileStatement.ts index 8d4eac9c8ba..6dbed258576 100644 --- a/src/ast/nodes/DoWhileStatement.ts +++ b/src/ast/nodes/DoWhileStatement.ts @@ -1,15 +1,34 @@ -import { ExecutionPathOptions } from '../ExecutionPathOptions'; +import { HasEffectsContext, InclusionContext } from '../ExecutionContext'; import * as NodeType from './NodeType'; -import { ExpressionNode, StatementBase, StatementNode } from './shared/Node'; +import { ExpressionNode, IncludeChildren, StatementBase, StatementNode } from './shared/Node'; export default class DoWhileStatement extends StatementBase { body!: StatementNode; test!: ExpressionNode; type!: NodeType.tDoWhileStatement; - hasEffects(options: ExecutionPathOptions): boolean { - return ( - this.test.hasEffects(options) || this.body.hasEffects(options.setIgnoreBreakStatements()) - ); + hasEffects(context: HasEffectsContext): boolean { + if (this.test.hasEffects(context)) return true; + const { + breakFlow, + ignore: { breakStatements } + } = context; + context.ignore.breakStatements = true; + if (this.body.hasEffects(context)) return true; + context.ignore.breakStatements = breakStatements; + if (context.breakFlow instanceof Set && context.breakFlow.has(null)) { + context.breakFlow = breakFlow; + } + return false; + } + + include(context: InclusionContext, includeChildrenRecursively: IncludeChildren) { + this.included = true; + this.test.include(context, includeChildrenRecursively); + const { breakFlow } = context; + this.body.include(context, includeChildrenRecursively); + if (context.breakFlow instanceof Set && context.breakFlow.has(null)) { + context.breakFlow = breakFlow; + } } } diff --git a/src/ast/nodes/ExportDefaultDeclaration.ts b/src/ast/nodes/ExportDefaultDeclaration.ts index 32dcac1a977..cc7a068f7ea 100644 --- a/src/ast/nodes/ExportDefaultDeclaration.ts +++ b/src/ast/nodes/ExportDefaultDeclaration.ts @@ -6,6 +6,7 @@ import { RenderOptions } from '../../utils/renderHelpers'; import { treeshakeNode } from '../../utils/treeshakeNode'; +import { InclusionContext } from '../ExecutionContext'; import ModuleScope from '../scopes/ModuleScope'; import ExportDefaultVariable from '../variables/ExportDefaultVariable'; import ClassDeclaration from './ClassDeclaration'; @@ -43,10 +44,10 @@ export default class ExportDefaultDeclaration extends NodeBase { private declarationName: string | undefined; - include(includeChildrenRecursively: IncludeChildren) { - super.include(includeChildrenRecursively); + include(context: InclusionContext, includeChildrenRecursively: IncludeChildren) { + super.include(context, includeChildrenRecursively); if (includeChildrenRecursively) { - this.context.includeVariable(this.variable); + this.context.includeVariable(context, this.variable); } } diff --git a/src/ast/nodes/ExportNamedDeclaration.ts b/src/ast/nodes/ExportNamedDeclaration.ts index fbb90c3692e..0cdb997ba27 100644 --- a/src/ast/nodes/ExportNamedDeclaration.ts +++ b/src/ast/nodes/ExportNamedDeclaration.ts @@ -1,7 +1,7 @@ import MagicString from 'magic-string'; import { BLANK } from '../../utils/blank'; import { NodeRenderOptions, RenderOptions } from '../../utils/renderHelpers'; -import { ExecutionPathOptions } from '../ExecutionPathOptions'; +import { HasEffectsContext } from '../ExecutionContext'; import ClassDeclaration from './ClassDeclaration'; import ExportSpecifier from './ExportSpecifier'; import FunctionDeclaration from './FunctionDeclaration'; @@ -22,8 +22,8 @@ export default class ExportNamedDeclaration extends NodeBase { if (this.declaration !== null) this.declaration.bind(); } - hasEffects(options: ExecutionPathOptions) { - return this.declaration !== null && this.declaration.hasEffects(options); + hasEffects(context: HasEffectsContext) { + return this.declaration !== null && this.declaration.hasEffects(context); } initialise() { diff --git a/src/ast/nodes/ExpressionStatement.ts b/src/ast/nodes/ExpressionStatement.ts index de4aa1d8a7d..163954231d8 100644 --- a/src/ast/nodes/ExpressionStatement.ts +++ b/src/ast/nodes/ExpressionStatement.ts @@ -1,5 +1,6 @@ import MagicString from 'magic-string'; import { RenderOptions } from '../../utils/renderHelpers'; +import { InclusionContext } from '../ExecutionContext'; import * as NodeType from './NodeType'; import { ExpressionNode, StatementBase } from './shared/Node'; @@ -17,9 +18,7 @@ export default class ExpressionStatement extends StatementBase { // This is necessary, because either way (deleting or not) can lead to errors. { code: 'MODULE_LEVEL_DIRECTIVE', - message: `Module level directives cause errors when bundled, '${ - this.directive - }' was ignored.` + message: `Module level directives cause errors when bundled, '${this.directive}' was ignored.` }, this.start ); @@ -31,10 +30,10 @@ export default class ExpressionStatement extends StatementBase { if (this.included) this.insertSemicolon(code); } - shouldBeIncluded() { + shouldBeIncluded(context: InclusionContext) { if (this.directive && this.directive !== 'use strict') return this.parent.type !== NodeType.Program; - return super.shouldBeIncluded(); + return super.shouldBeIncluded(context); } } diff --git a/src/ast/nodes/ForInStatement.ts b/src/ast/nodes/ForInStatement.ts index 515e9ad4413..e078f79f6a8 100644 --- a/src/ast/nodes/ForInStatement.ts +++ b/src/ast/nodes/ForInStatement.ts @@ -1,9 +1,9 @@ import MagicString from 'magic-string'; import { NO_SEMICOLON, RenderOptions } from '../../utils/renderHelpers'; -import { ExecutionPathOptions } from '../ExecutionPathOptions'; +import { HasEffectsContext, InclusionContext } from '../ExecutionContext'; import BlockScope from '../scopes/BlockScope'; import Scope from '../scopes/Scope'; -import { EMPTY_PATH } from '../values'; +import { EMPTY_PATH } from '../utils/PathTracker'; import * as NodeType from './NodeType'; import { ExpressionNode, IncludeChildren, StatementBase, StatementNode } from './shared/Node'; import { PatternNode } from './shared/Pattern'; @@ -26,22 +26,33 @@ export default class ForInStatement extends StatementBase { this.scope = new BlockScope(parentScope); } - hasEffects(options: ExecutionPathOptions): boolean { - return ( + hasEffects(context: HasEffectsContext): boolean { + if ( (this.left && - (this.left.hasEffects(options) || - this.left.hasEffectsWhenAssignedAtPath(EMPTY_PATH, options))) || - (this.right && this.right.hasEffects(options)) || - this.body.hasEffects(options.setIgnoreBreakStatements()) - ); + (this.left.hasEffects(context) || + this.left.hasEffectsWhenAssignedAtPath(EMPTY_PATH, context))) || + (this.right && this.right.hasEffects(context)) + ) + return true; + const { + breakFlow, + ignore: { breakStatements } + } = context; + context.ignore.breakStatements = true; + if (this.body.hasEffects(context)) return true; + context.ignore.breakStatements = breakStatements; + context.breakFlow = breakFlow; + return false; } - include(includeChildrenRecursively: IncludeChildren) { + include(context: InclusionContext, includeChildrenRecursively: IncludeChildren) { this.included = true; - this.left.includeWithAllDeclaredVariables(includeChildrenRecursively); + this.left.includeWithAllDeclaredVariables(includeChildrenRecursively, context); this.left.deoptimizePath(EMPTY_PATH); - this.right.include(includeChildrenRecursively); - this.body.include(includeChildrenRecursively); + this.right.include(context, includeChildrenRecursively); + const { breakFlow } = context; + this.body.include(context, includeChildrenRecursively); + context.breakFlow = breakFlow; } render(code: MagicString, options: RenderOptions) { diff --git a/src/ast/nodes/ForOfStatement.ts b/src/ast/nodes/ForOfStatement.ts index 23874656dc7..71cd0e0b1bc 100644 --- a/src/ast/nodes/ForOfStatement.ts +++ b/src/ast/nodes/ForOfStatement.ts @@ -1,8 +1,9 @@ import MagicString from 'magic-string'; import { NO_SEMICOLON, RenderOptions } from '../../utils/renderHelpers'; +import { InclusionContext } from '../ExecutionContext'; import BlockScope from '../scopes/BlockScope'; import Scope from '../scopes/Scope'; -import { EMPTY_PATH } from '../values'; +import { EMPTY_PATH } from '../utils/PathTracker'; import * as NodeType from './NodeType'; import { ExpressionNode, IncludeChildren, StatementBase, StatementNode } from './shared/Node'; import { PatternNode } from './shared/Pattern'; @@ -31,12 +32,14 @@ export default class ForOfStatement extends StatementBase { return true; } - include(includeChildrenRecursively: IncludeChildren) { + include(context: InclusionContext, includeChildrenRecursively: IncludeChildren) { this.included = true; - this.left.includeWithAllDeclaredVariables(includeChildrenRecursively); + this.left.includeWithAllDeclaredVariables(includeChildrenRecursively, context); this.left.deoptimizePath(EMPTY_PATH); - this.right.include(includeChildrenRecursively); - this.body.include(includeChildrenRecursively); + this.right.include(context, includeChildrenRecursively); + const { breakFlow } = context; + this.body.include(context, includeChildrenRecursively); + context.breakFlow = breakFlow; } render(code: MagicString, options: RenderOptions) { diff --git a/src/ast/nodes/ForStatement.ts b/src/ast/nodes/ForStatement.ts index a6d633491aa..f07001e3716 100644 --- a/src/ast/nodes/ForStatement.ts +++ b/src/ast/nodes/ForStatement.ts @@ -1,10 +1,10 @@ import MagicString from 'magic-string'; import { NO_SEMICOLON, RenderOptions } from '../../utils/renderHelpers'; -import { ExecutionPathOptions } from '../ExecutionPathOptions'; +import { HasEffectsContext, InclusionContext } from '../ExecutionContext'; import BlockScope from '../scopes/BlockScope'; import Scope from '../scopes/Scope'; import * as NodeType from './NodeType'; -import { ExpressionNode, StatementBase, StatementNode } from './shared/Node'; +import { ExpressionNode, IncludeChildren, StatementBase, StatementNode } from './shared/Node'; import VariableDeclaration from './VariableDeclaration'; export default class ForStatement extends StatementBase { @@ -18,13 +18,32 @@ export default class ForStatement extends StatementBase { this.scope = new BlockScope(parentScope); } - hasEffects(options: ExecutionPathOptions): boolean { - return ( - (this.init && this.init.hasEffects(options)) || - (this.test && this.test.hasEffects(options)) || - (this.update && this.update.hasEffects(options)) || - this.body.hasEffects(options.setIgnoreBreakStatements()) - ); + hasEffects(context: HasEffectsContext): boolean { + if ( + (this.init && this.init.hasEffects(context)) || + (this.test && this.test.hasEffects(context)) || + (this.update && this.update.hasEffects(context)) + ) + return true; + const { + breakFlow, + ignore: { breakStatements } + } = context; + context.ignore.breakStatements = true; + if (this.body.hasEffects(context)) return true; + context.ignore.breakStatements = breakStatements; + context.breakFlow = breakFlow; + return false; + } + + include(context: InclusionContext, includeChildrenRecursively: IncludeChildren) { + this.included = true; + if (this.init) this.init.include(context, includeChildrenRecursively); + if (this.test) this.test.include(context, includeChildrenRecursively); + const { breakFlow } = context; + if (this.update) this.update.include(context, includeChildrenRecursively); + this.body.include(context, includeChildrenRecursively); + context.breakFlow = breakFlow; } render(code: MagicString, options: RenderOptions) { diff --git a/src/ast/nodes/Identifier.ts b/src/ast/nodes/Identifier.ts index 056e8338d15..9a94c48f8e1 100644 --- a/src/ast/nodes/Identifier.ts +++ b/src/ast/nodes/Identifier.ts @@ -2,12 +2,12 @@ import isReference from 'is-reference'; import MagicString from 'magic-string'; import { BLANK } from '../../utils/blank'; import { NodeRenderOptions, RenderOptions } from '../../utils/renderHelpers'; -import CallOptions from '../CallOptions'; +import { CallOptions } from '../CallOptions'; import { DeoptimizableEntity } from '../DeoptimizableEntity'; -import { ExecutionPathOptions } from '../ExecutionPathOptions'; +import { HasEffectsContext, InclusionContext } from '../ExecutionContext'; import FunctionScope from '../scopes/FunctionScope'; -import { ImmutableEntityPathTracker } from '../utils/ImmutableEntityPathTracker'; -import { EMPTY_PATH, LiteralValueOrUnknown, ObjectPath } from '../values'; +import { EMPTY_PATH, ObjectPath, PathTracker } from '../utils/PathTracker'; +import { LiteralValueOrUnknown } from '../values'; import GlobalVariable from '../variables/GlobalVariable'; import LocalVariable from '../variables/LocalVariable'; import Variable from '../variables/Variable'; @@ -52,9 +52,11 @@ export default class Identifier extends NodeBase implements PatternNode { let variable: LocalVariable; switch (kind) { case 'var': - case 'function': variable = this.scope.addDeclaration(this, this.context, init, true); break; + case 'function': + variable = this.scope.addDeclaration(this, this.context, init, 'function'); + break; case 'let': case 'const': case 'class': @@ -81,7 +83,7 @@ export default class Identifier extends NodeBase implements PatternNode { getLiteralValueAtPath( path: ObjectPath, - recursionTracker: ImmutableEntityPathTracker, + recursionTracker: PathTracker, origin: DeoptimizableEntity ): LiteralValueOrUnknown { if (!this.bound) this.bind(); @@ -90,7 +92,7 @@ export default class Identifier extends NodeBase implements PatternNode { getReturnExpressionWhenCalledAtPath( path: ObjectPath, - recursionTracker: ImmutableEntityPathTracker, + recursionTracker: PathTracker, origin: DeoptimizableEntity ) { if (!this.bound) this.bind(); @@ -109,33 +111,33 @@ export default class Identifier extends NodeBase implements PatternNode { ); } - hasEffectsWhenAccessedAtPath(path: ObjectPath, options: ExecutionPathOptions): boolean { - return this.variable !== null && this.variable.hasEffectsWhenAccessedAtPath(path, options); + hasEffectsWhenAccessedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { + return this.variable !== null && this.variable.hasEffectsWhenAccessedAtPath(path, context); } - hasEffectsWhenAssignedAtPath(path: ObjectPath, options: ExecutionPathOptions): boolean { - return !this.variable || this.variable.hasEffectsWhenAssignedAtPath(path, options); + hasEffectsWhenAssignedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { + return !this.variable || this.variable.hasEffectsWhenAssignedAtPath(path, context); } hasEffectsWhenCalledAtPath( path: ObjectPath, callOptions: CallOptions, - options: ExecutionPathOptions + context: HasEffectsContext ) { - return !this.variable || this.variable.hasEffectsWhenCalledAtPath(path, callOptions, options); + return !this.variable || this.variable.hasEffectsWhenCalledAtPath(path, callOptions, context); } - include() { + include(context: InclusionContext) { if (!this.included) { this.included = true; if (this.variable !== null) { - this.context.includeVariable(this.variable); + this.context.includeVariable(context, this.variable); } } } - includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void { - (this.variable as Variable).includeCallArguments(args); + includeCallArguments(context: InclusionContext, args: (ExpressionNode | SpreadElement)[]): void { + (this.variable as Variable).includeCallArguments(context, args); } render( diff --git a/src/ast/nodes/IfStatement.ts b/src/ast/nodes/IfStatement.ts index 5d2a9847dbc..73b6bb944f6 100644 --- a/src/ast/nodes/IfStatement.ts +++ b/src/ast/nodes/IfStatement.ts @@ -2,9 +2,14 @@ import MagicString from 'magic-string'; import { RenderOptions } from '../../utils/renderHelpers'; import { removeAnnotations } from '../../utils/treeshakeNode'; import { DeoptimizableEntity } from '../DeoptimizableEntity'; -import { ExecutionPathOptions } from '../ExecutionPathOptions'; -import { EMPTY_IMMUTABLE_TRACKER } from '../utils/ImmutableEntityPathTracker'; -import { EMPTY_PATH, LiteralValueOrUnknown, UNKNOWN_VALUE } from '../values'; +import { + BreakFlow, + BREAKFLOW_NONE, + HasEffectsContext, + InclusionContext +} from '../ExecutionContext'; +import { EMPTY_IMMUTABLE_TRACKER, EMPTY_PATH } from '../utils/PathTracker'; +import { LiteralValueOrUnknown, UnknownValue } from '../values'; import * as NodeType from './NodeType'; import { ExpressionNode, IncludeChildren, StatementBase, StatementNode } from './shared/Node'; @@ -14,57 +19,42 @@ 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 = UNKNOWN_VALUE; - 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() { - this.testValue = UNKNOWN_VALUE; + this.testValue = UnknownValue; } - hasEffects(options: ExecutionPathOptions): boolean { - if (this.test.hasEffects(options)) return true; - if (this.testValue === UNKNOWN_VALUE) { - return ( - this.consequent.hasEffects(options) || - (this.alternate !== null && this.alternate.hasEffects(options)) - ); + hasEffects(context: HasEffectsContext): boolean { + if (this.test.hasEffects(context)) return true; + if (this.testValue === UnknownValue) { + const { breakFlow } = context; + if (this.consequent.hasEffects(context)) return true; + const consequentBreakFlow = context.breakFlow; + context.breakFlow = breakFlow; + if (this.alternate === null) return false; + if (this.alternate.hasEffects(context)) return true; + this.updateBreakFlowUnknownCondition(consequentBreakFlow, context); + return false; } return this.testValue - ? this.consequent.hasEffects(options) - : this.alternate !== null && this.alternate.hasEffects(options); + ? this.consequent.hasEffects(context) + : this.alternate !== null && this.alternate.hasEffects(context); } - include(includeChildrenRecursively: IncludeChildren) { + include(context: InclusionContext, includeChildrenRecursively: IncludeChildren) { this.included = true; if (includeChildrenRecursively) { - this.test.include(includeChildrenRecursively); - this.consequent.include(includeChildrenRecursively); - if (this.alternate !== null) { - this.alternate.include(includeChildrenRecursively); - } - return; - } - const hasUnknownTest = this.testValue === UNKNOWN_VALUE; - if (hasUnknownTest || this.test.shouldBeIncluded()) { - this.test.include(false); - } - if ((hasUnknownTest || this.testValue) && this.consequent.shouldBeIncluded()) { - this.consequent.include(false); - } - if ( - this.alternate !== null && - ((hasUnknownTest || !this.testValue) && this.alternate.shouldBeIncluded()) - ) { - this.alternate.include(false); + this.includeRecursively(includeChildrenRecursively, context); + } else if (this.testValue === UnknownValue) { + this.includeUnknownTest(context); + } else { + this.includeKnownTest(context); } } @@ -103,4 +93,59 @@ export default class IfStatement extends StatementBase implements DeoptimizableE } } } + + private includeKnownTest(context: InclusionContext) { + if (this.test.shouldBeIncluded(context)) { + this.test.include(context, false); + } + if (this.testValue && this.consequent.shouldBeIncluded(context)) { + this.consequent.include(context, false); + } + if (this.alternate !== null && !this.testValue && this.alternate.shouldBeIncluded(context)) { + this.alternate.include(context, false); + } + } + + private includeRecursively( + includeChildrenRecursively: true | 'variables', + context: InclusionContext + ) { + this.test.include(context, includeChildrenRecursively); + this.consequent.include(context, includeChildrenRecursively); + if (this.alternate !== null) { + this.alternate.include(context, includeChildrenRecursively); + } + } + + private includeUnknownTest(context: InclusionContext) { + this.test.include(context, false); + const { breakFlow } = context; + let consequentBreakFlow: BreakFlow | false = false; + if (this.consequent.shouldBeIncluded(context)) { + this.consequent.include(context, false); + consequentBreakFlow = context.breakFlow; + context.breakFlow = breakFlow; + } + if (this.alternate !== null && this.alternate.shouldBeIncluded(context)) { + this.alternate.include(context, false); + this.updateBreakFlowUnknownCondition(consequentBreakFlow, context); + } + } + + private updateBreakFlowUnknownCondition( + consequentBreakFlow: BreakFlow | false, + context: InclusionContext + ) { + if (!(consequentBreakFlow && context.breakFlow)) { + context.breakFlow = BREAKFLOW_NONE; + } else if (context.breakFlow instanceof Set) { + if (consequentBreakFlow instanceof Set) { + for (const label of consequentBreakFlow) { + context.breakFlow.add(label); + } + } + } else { + context.breakFlow = consequentBreakFlow; + } + } } diff --git a/src/ast/nodes/ImportExpression.ts b/src/ast/nodes/ImportExpression.ts index 55188c2fd89..32c9bbe91c2 100644 --- a/src/ast/nodes/ImportExpression.ts +++ b/src/ast/nodes/ImportExpression.ts @@ -1,6 +1,7 @@ import MagicString from 'magic-string'; import { findFirstOccurrenceOutsideComment, RenderOptions } from '../../utils/renderHelpers'; import { INTEROP_NAMESPACE_VARIABLE } from '../../utils/variableNames'; +import { InclusionContext } from '../ExecutionContext'; import NamespaceVariable from '../variables/NamespaceVariable'; import * as NodeType from './NodeType'; import { ExpressionNode, IncludeChildren, NodeBase } from './shared/Node'; @@ -21,12 +22,12 @@ export default class Import extends NodeBase { return true; } - include(includeChildrenRecursively: IncludeChildren) { + include(context: InclusionContext, includeChildrenRecursively: IncludeChildren) { if (!this.included) { this.included = true; this.context.includeDynamicImport(this); } - this.source.include(includeChildrenRecursively); + this.source.include(context, includeChildrenRecursively); } initialise() { diff --git a/src/ast/nodes/LabeledStatement.ts b/src/ast/nodes/LabeledStatement.ts index 9a2e7b8d5a3..1abb654f568 100644 --- a/src/ast/nodes/LabeledStatement.ts +++ b/src/ast/nodes/LabeledStatement.ts @@ -1,14 +1,29 @@ -import { ExecutionPathOptions } from '../ExecutionPathOptions'; +import { BREAKFLOW_NONE, HasEffectsContext, InclusionContext } from '../ExecutionContext'; import Identifier from './Identifier'; import * as NodeType from './NodeType'; -import { StatementBase, StatementNode } from './shared/Node'; +import { IncludeChildren, StatementBase, StatementNode } from './shared/Node'; export default class LabeledStatement extends StatementBase { body!: StatementNode; label!: Identifier; type!: NodeType.tLabeledStatement; - hasEffects(options: ExecutionPathOptions) { - return this.body.hasEffects(options.setIgnoreLabel(this.label.name).setIgnoreBreakStatements()); + hasEffects(context: HasEffectsContext) { + context.ignore.labels.add(this.label.name); + if (this.body.hasEffects(context)) return true; + context.ignore.labels.delete(this.label.name); + if (context.breakFlow instanceof Set && context.breakFlow.has(this.label.name)) { + context.breakFlow = BREAKFLOW_NONE; + } + return false; + } + + include(context: InclusionContext, includeChildrenRecursively: IncludeChildren) { + this.included = true; + this.label.include(context); + this.body.include(context, includeChildrenRecursively); + if (context.breakFlow instanceof Set && context.breakFlow.has(this.label.name)) { + context.breakFlow = BREAKFLOW_NONE; + } } } diff --git a/src/ast/nodes/Literal.ts b/src/ast/nodes/Literal.ts index 83f61853cc0..29f575f52bb 100644 --- a/src/ast/nodes/Literal.ts +++ b/src/ast/nodes/Literal.ts @@ -1,16 +1,15 @@ import MagicString from 'magic-string'; -import { RenderOptions } from '../../utils/renderHelpers'; -import CallOptions from '../CallOptions'; -import { ExecutionPathOptions } from '../ExecutionPathOptions'; +import { CallOptions } from '../CallOptions'; +import { HasEffectsContext } from '../ExecutionContext'; +import { ObjectPath } from '../utils/PathTracker'; import { getLiteralMembersForValue, getMemberReturnExpressionWhenCalled, hasMemberEffectWhenCalled, LiteralValueOrUnknown, MemberDescription, - ObjectPath, UNKNOWN_EXPRESSION, - UNKNOWN_VALUE + UnknownValue } from '../values'; import * as NodeType from './NodeType'; import { NodeBase } from './shared/Node'; @@ -32,7 +31,7 @@ export default class Literal extends NodeBase { // to support shims for regular expressions this.context.code.charCodeAt(this.start) === 47 ) { - return UNKNOWN_VALUE; + return UnknownValue; } return this.value as any; } @@ -56,10 +55,10 @@ export default class Literal extends NodeBase { hasEffectsWhenCalledAtPath( path: ObjectPath, callOptions: CallOptions, - options: ExecutionPathOptions + context: HasEffectsContext ): boolean { if (path.length === 1) { - return hasMemberEffectWhenCalled(this.members, path[0], this.included, callOptions, options); + return hasMemberEffectWhenCalled(this.members, path[0], this.included, callOptions, context); } return true; } @@ -68,7 +67,7 @@ export default class Literal extends NodeBase { this.members = getLiteralMembersForValue(this.value); } - render(code: MagicString, _options: RenderOptions) { + render(code: MagicString) { if (typeof this.value === 'string') { (code.indentExclusionRanges as [number, number][]).push([this.start + 1, this.end - 1]); } diff --git a/src/ast/nodes/LogicalExpression.ts b/src/ast/nodes/LogicalExpression.ts index 56fa2bb047a..b0c52d59a14 100644 --- a/src/ast/nodes/LogicalExpression.ts +++ b/src/ast/nodes/LogicalExpression.ts @@ -7,20 +7,17 @@ import { RenderOptions } from '../../utils/renderHelpers'; import { removeAnnotations } from '../../utils/treeshakeNode'; -import CallOptions from '../CallOptions'; +import { CallOptions } from '../CallOptions'; import { DeoptimizableEntity } from '../DeoptimizableEntity'; -import { ExecutionPathOptions } from '../ExecutionPathOptions'; +import { HasEffectsContext, InclusionContext } from '../ExecutionContext'; import { EMPTY_IMMUTABLE_TRACKER, - ImmutableEntityPathTracker -} from '../utils/ImmutableEntityPathTracker'; -import { EMPTY_PATH, - LiteralValueOrUnknown, ObjectPath, - UNKNOWN_PATH, - UNKNOWN_VALUE -} from '../values'; + PathTracker, + UNKNOWN_PATH +} from '../utils/PathTracker'; +import { LiteralValueOrUnknown, UnknownValue } from '../values'; import CallExpression from './CallExpression'; import * as NodeType from './NodeType'; import { ExpressionEntity } from './shared/Expression'; @@ -72,18 +69,18 @@ export default class LogicalExpression extends NodeBase implements Deoptimizable getLiteralValueAtPath( path: ObjectPath, - recursionTracker: ImmutableEntityPathTracker, + recursionTracker: PathTracker, origin: DeoptimizableEntity ): LiteralValueOrUnknown { if (!this.isBranchResolutionAnalysed) this.analyseBranchResolution(); - if (this.usedBranch === null) return UNKNOWN_VALUE; + if (this.usedBranch === null) return UnknownValue; this.expressionsToBeDeoptimized.push(origin); return this.usedBranch.getLiteralValueAtPath(path, recursionTracker, origin); } getReturnExpressionWhenCalledAtPath( path: ObjectPath, - recursionTracker: ImmutableEntityPathTracker, + recursionTracker: PathTracker, origin: DeoptimizableEntity ): ExpressionEntity { if (!this.isBranchResolutionAnalysed) this.analyseBranchResolution(); @@ -96,60 +93,60 @@ export default class LogicalExpression extends NodeBase implements Deoptimizable return this.usedBranch.getReturnExpressionWhenCalledAtPath(path, recursionTracker, origin); } - hasEffects(options: ExecutionPathOptions): boolean { + hasEffects(context: HasEffectsContext): boolean { if (this.usedBranch === null) { - return this.left.hasEffects(options) || this.right.hasEffects(options); + return this.left.hasEffects(context) || this.right.hasEffects(context); } - return this.usedBranch.hasEffects(options); + return this.usedBranch.hasEffects(context); } - hasEffectsWhenAccessedAtPath(path: ObjectPath, options: ExecutionPathOptions): boolean { + hasEffectsWhenAccessedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { if (path.length === 0) return false; if (this.usedBranch === null) { return ( - this.left.hasEffectsWhenAccessedAtPath(path, options) || - this.right.hasEffectsWhenAccessedAtPath(path, options) + this.left.hasEffectsWhenAccessedAtPath(path, context) || + this.right.hasEffectsWhenAccessedAtPath(path, context) ); } - return this.usedBranch.hasEffectsWhenAccessedAtPath(path, options); + return this.usedBranch.hasEffectsWhenAccessedAtPath(path, context); } - hasEffectsWhenAssignedAtPath(path: ObjectPath, options: ExecutionPathOptions): boolean { + hasEffectsWhenAssignedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { if (path.length === 0) return true; if (this.usedBranch === null) { return ( - this.left.hasEffectsWhenAssignedAtPath(path, options) || - this.right.hasEffectsWhenAssignedAtPath(path, options) + this.left.hasEffectsWhenAssignedAtPath(path, context) || + this.right.hasEffectsWhenAssignedAtPath(path, context) ); } - return this.usedBranch.hasEffectsWhenAssignedAtPath(path, options); + return this.usedBranch.hasEffectsWhenAssignedAtPath(path, context); } hasEffectsWhenCalledAtPath( path: ObjectPath, callOptions: CallOptions, - options: ExecutionPathOptions + context: HasEffectsContext ): boolean { if (this.usedBranch === null) { return ( - this.left.hasEffectsWhenCalledAtPath(path, callOptions, options) || - this.right.hasEffectsWhenCalledAtPath(path, callOptions, options) + this.left.hasEffectsWhenCalledAtPath(path, callOptions, context) || + this.right.hasEffectsWhenCalledAtPath(path, callOptions, context) ); } - return this.usedBranch.hasEffectsWhenCalledAtPath(path, callOptions, options); + return this.usedBranch.hasEffectsWhenCalledAtPath(path, callOptions, context); } - include(includeChildrenRecursively: IncludeChildren) { + include(context: InclusionContext, includeChildrenRecursively: IncludeChildren) { this.included = true; if ( includeChildrenRecursively || this.usedBranch === null || - (this.unusedBranch as ExpressionNode).shouldBeIncluded() + (this.unusedBranch as ExpressionNode).shouldBeIncluded(context) ) { - this.left.include(includeChildrenRecursively); - this.right.include(includeChildrenRecursively); + this.left.include(context, includeChildrenRecursively); + this.right.include(context, includeChildrenRecursively); } else { - this.usedBranch.include(includeChildrenRecursively); + this.usedBranch.include(context, includeChildrenRecursively); } } @@ -187,7 +184,7 @@ export default class LogicalExpression extends NodeBase implements Deoptimizable private analyseBranchResolution() { this.isBranchResolutionAnalysed = true; const leftValue = this.left.getLiteralValueAtPath(EMPTY_PATH, EMPTY_IMMUTABLE_TRACKER, this); - if (leftValue !== UNKNOWN_VALUE) { + if (leftValue !== UnknownValue) { if (this.operator === '||' ? leftValue : !leftValue) { this.usedBranch = this.left; this.unusedBranch = this.right; diff --git a/src/ast/nodes/MemberExpression.ts b/src/ast/nodes/MemberExpression.ts index 5b14ca02553..fc292214745 100644 --- a/src/ast/nodes/MemberExpression.ts +++ b/src/ast/nodes/MemberExpression.ts @@ -2,21 +2,18 @@ import MagicString from 'magic-string'; import { BLANK } from '../../utils/blank'; import relativeId from '../../utils/relativeId'; import { NodeRenderOptions, RenderOptions } from '../../utils/renderHelpers'; -import CallOptions from '../CallOptions'; +import { CallOptions } from '../CallOptions'; import { DeoptimizableEntity } from '../DeoptimizableEntity'; -import { ExecutionPathOptions } from '../ExecutionPathOptions'; +import { HasEffectsContext, InclusionContext } from '../ExecutionContext'; import { EMPTY_IMMUTABLE_TRACKER, - ImmutableEntityPathTracker -} from '../utils/ImmutableEntityPathTracker'; -import { EMPTY_PATH, - LiteralValueOrUnknown, ObjectPath, ObjectPathKey, - UNKNOWN_KEY, - UNKNOWN_VALUE -} from '../values'; + PathTracker, + UnknownKey +} from '../utils/PathTracker'; +import { LiteralValueOrUnknown, UnknownValue } from '../values'; import ExternalVariable from '../variables/ExternalVariable'; import NamespaceVariable from '../variables/NamespaceVariable'; import Variable from '../variables/Variable'; @@ -133,7 +130,7 @@ export default class MemberExpression extends NodeBase implements DeoptimizableE getLiteralValueAtPath( path: ObjectPath, - recursionTracker: ImmutableEntityPathTracker, + recursionTracker: PathTracker, origin: DeoptimizableEntity ): LiteralValueOrUnknown { if (!this.bound) this.bind(); @@ -151,7 +148,7 @@ export default class MemberExpression extends NodeBase implements DeoptimizableE getReturnExpressionWhenCalledAtPath( path: ObjectPath, - recursionTracker: ImmutableEntityPathTracker, + recursionTracker: PathTracker, origin: DeoptimizableEntity ) { if (!this.bound) this.bind(); @@ -167,69 +164,67 @@ export default class MemberExpression extends NodeBase implements DeoptimizableE ); } - hasEffects(options: ExecutionPathOptions): boolean { + hasEffects(context: HasEffectsContext): boolean { return ( - this.property.hasEffects(options) || - this.object.hasEffects(options) || + this.property.hasEffects(context) || + this.object.hasEffects(context) || (this.context.propertyReadSideEffects && - this.object.hasEffectsWhenAccessedAtPath([this.propertyKey as ObjectPathKey], options)) + this.object.hasEffectsWhenAccessedAtPath([this.propertyKey as ObjectPathKey], context)) ); } - hasEffectsWhenAccessedAtPath(path: ObjectPath, options: ExecutionPathOptions): boolean { - if (path.length === 0) { - return false; - } + hasEffectsWhenAccessedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { + if (path.length === 0) return false; if (this.variable !== null) { - return this.variable.hasEffectsWhenAccessedAtPath(path, options); + return this.variable.hasEffectsWhenAccessedAtPath(path, context); } return this.object.hasEffectsWhenAccessedAtPath( [this.propertyKey as ObjectPathKey, ...path], - options + context ); } - hasEffectsWhenAssignedAtPath(path: ObjectPath, options: ExecutionPathOptions): boolean { + hasEffectsWhenAssignedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { if (this.variable !== null) { - return this.variable.hasEffectsWhenAssignedAtPath(path, options); + return this.variable.hasEffectsWhenAssignedAtPath(path, context); } return this.object.hasEffectsWhenAssignedAtPath( [this.propertyKey as ObjectPathKey, ...path], - options + context ); } hasEffectsWhenCalledAtPath( path: ObjectPath, callOptions: CallOptions, - options: ExecutionPathOptions + context: HasEffectsContext ): boolean { if (this.variable !== null) { - return this.variable.hasEffectsWhenCalledAtPath(path, callOptions, options); + return this.variable.hasEffectsWhenCalledAtPath(path, callOptions, context); } return this.object.hasEffectsWhenCalledAtPath( [this.propertyKey as ObjectPathKey, ...path], callOptions, - options + context ); } - include(includeChildrenRecursively: IncludeChildren) { + include(context: InclusionContext, includeChildrenRecursively: IncludeChildren) { if (!this.included) { this.included = true; if (this.variable !== null) { - this.context.includeVariable(this.variable); + this.context.includeVariable(context, this.variable); } } - this.object.include(includeChildrenRecursively); - this.property.include(includeChildrenRecursively); + this.object.include(context, includeChildrenRecursively); + this.property.include(context, includeChildrenRecursively); } - includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void { + includeCallArguments(context: InclusionContext, args: (ExpressionNode | SpreadElement)[]): void { if (this.variable) { - this.variable.includeCallArguments(args); + this.variable.includeCallArguments(context, args); } else { - super.includeCallArguments(args); + super.includeCallArguments(context, args); } } @@ -260,9 +255,9 @@ export default class MemberExpression extends NodeBase implements DeoptimizableE } private analysePropertyKey() { - this.propertyKey = UNKNOWN_KEY; + this.propertyKey = UnknownKey; const value = this.property.getLiteralValueAtPath(EMPTY_PATH, EMPTY_IMMUTABLE_TRACKER, this); - this.propertyKey = value === UNKNOWN_VALUE ? UNKNOWN_KEY : String(value); + this.propertyKey = value === UnknownValue ? UnknownKey : String(value); } private disallowNamespaceReassignment() { diff --git a/src/ast/nodes/MetaProperty.ts b/src/ast/nodes/MetaProperty.ts index c30b1bc8566..cc3e2de28b2 100644 --- a/src/ast/nodes/MetaProperty.ts +++ b/src/ast/nodes/MetaProperty.ts @@ -2,7 +2,7 @@ import MagicString from 'magic-string'; import { accessedFileUrlGlobals, accessedMetaUrlGlobals } from '../../utils/defaultPlugin'; import { dirname, normalize, relative } from '../../utils/path'; import { PluginDriver } from '../../utils/pluginDriver'; -import { ObjectPathKey } from '../values'; +import { ObjectPathKey } from '../utils/PathTracker'; import Identifier from './Identifier'; import MemberExpression from './MemberExpression'; import * as NodeType from './NodeType'; diff --git a/src/ast/nodes/MethodDefinition.ts b/src/ast/nodes/MethodDefinition.ts index 5fca36e5fc1..ec8de25f92e 100644 --- a/src/ast/nodes/MethodDefinition.ts +++ b/src/ast/nodes/MethodDefinition.ts @@ -1,6 +1,6 @@ -import CallOptions from '../CallOptions'; -import { ExecutionPathOptions } from '../ExecutionPathOptions'; -import { EMPTY_PATH, ObjectPath } from '../values'; +import { CallOptions } from '../CallOptions'; +import { HasEffectsContext } from '../ExecutionContext'; +import { EMPTY_PATH, ObjectPath } from '../utils/PathTracker'; import FunctionExpression from './FunctionExpression'; import * as NodeType from './NodeType'; import { ExpressionNode, NodeBase } from './shared/Node'; @@ -13,17 +13,17 @@ export default class MethodDefinition extends NodeBase { type!: NodeType.tMethodDefinition; value!: FunctionExpression; - hasEffects(options: ExecutionPathOptions) { - return this.key.hasEffects(options); + hasEffects(context: HasEffectsContext) { + return this.key.hasEffects(context); } hasEffectsWhenCalledAtPath( path: ObjectPath, callOptions: CallOptions, - options: ExecutionPathOptions + context: HasEffectsContext ) { return ( - path.length > 0 || this.value.hasEffectsWhenCalledAtPath(EMPTY_PATH, callOptions, options) + path.length > 0 || this.value.hasEffectsWhenCalledAtPath(EMPTY_PATH, callOptions, context) ); } } diff --git a/src/ast/nodes/NewExpression.ts b/src/ast/nodes/NewExpression.ts index ea5c69813b8..d7acdfd276d 100644 --- a/src/ast/nodes/NewExpression.ts +++ b/src/ast/nodes/NewExpression.ts @@ -1,6 +1,6 @@ -import CallOptions from '../CallOptions'; -import { ExecutionPathOptions } from '../ExecutionPathOptions'; -import { EMPTY_PATH, ObjectPath, UNKNOWN_PATH } from '../values'; +import { CallOptions } from '../CallOptions'; +import { HasEffectsContext } from '../ExecutionContext'; +import { EMPTY_PATH, ObjectPath, UNKNOWN_PATH } from '../utils/PathTracker'; import * as NodeType from './NodeType'; import { ExpressionNode, NodeBase } from './shared/Node'; @@ -20,27 +20,25 @@ export default class NewExpression extends NodeBase { } } - hasEffects(options: ExecutionPathOptions): boolean { + hasEffects(context: HasEffectsContext): boolean { for (const argument of this.arguments) { - if (argument.hasEffects(options)) return true; + if (argument.hasEffects(context)) return true; } - if (this.annotatedPure) return false; - return this.callee.hasEffectsWhenCalledAtPath( - EMPTY_PATH, - this.callOptions, - options.getHasEffectsWhenCalledOptions() + if (this.context.annotations && this.annotatedPure) return false; + return ( + this.callee.hasEffects(context) || + this.callee.hasEffectsWhenCalledAtPath(EMPTY_PATH, this.callOptions, context) ); } - hasEffectsWhenAccessedAtPath(path: ObjectPath, _options: ExecutionPathOptions) { + hasEffectsWhenAccessedAtPath(path: ObjectPath) { return path.length > 1; } initialise() { - this.callOptions = CallOptions.create({ + this.callOptions = { args: this.arguments, - callIdentifier: this, withNew: true - }); + }; } } diff --git a/src/ast/nodes/NodeType.ts b/src/ast/nodes/NodeType.ts index 74dc1950b69..adb6a38d0c4 100644 --- a/src/ast/nodes/NodeType.ts +++ b/src/ast/nodes/NodeType.ts @@ -13,6 +13,7 @@ export type tClassBody = 'ClassBody'; export type tClassDeclaration = 'ClassDeclaration'; export type tClassExpression = 'ClassExpression'; export type tConditionalExpression = 'ConditionalExpression'; +export type tContinueStatement = 'ContinueStatement'; export type tDoWhileStatement = 'DoWhileStatement'; export type tEmptyStatement = 'EmptyStatement'; export type tExportAllDeclaration = 'ExportAllDeclaration'; @@ -78,6 +79,7 @@ export const ClassBody: tClassBody = 'ClassBody'; export const ClassDeclaration: tClassDeclaration = 'ClassDeclaration'; export const ClassExpression: tClassExpression = 'ClassExpression'; export const ConditionalExpression: tConditionalExpression = 'ConditionalExpression'; +export const ContinueStatement: tContinueStatement = 'ContinueStatement'; export const DoWhileStatement: tDoWhileStatement = 'DoWhileStatement'; export const EmptyStatement: tEmptyStatement = 'EmptyStatement'; export const ExportAllDeclaration: tExportAllDeclaration = 'ExportAllDeclaration'; diff --git a/src/ast/nodes/ObjectExpression.ts b/src/ast/nodes/ObjectExpression.ts index 666330c4021..0d8fb2b61de 100644 --- a/src/ast/nodes/ObjectExpression.ts +++ b/src/ast/nodes/ObjectExpression.ts @@ -1,23 +1,23 @@ import MagicString from 'magic-string'; import { BLANK } from '../../utils/blank'; import { NodeRenderOptions, RenderOptions } from '../../utils/renderHelpers'; -import CallOptions from '../CallOptions'; +import { CallOptions } from '../CallOptions'; import { DeoptimizableEntity } from '../DeoptimizableEntity'; -import { ExecutionPathOptions } from '../ExecutionPathOptions'; +import { HasEffectsContext } from '../ExecutionContext'; import { EMPTY_IMMUTABLE_TRACKER, - ImmutableEntityPathTracker -} from '../utils/ImmutableEntityPathTracker'; -import { EMPTY_PATH, + ObjectPath, + PathTracker, + UNKNOWN_PATH +} from '../utils/PathTracker'; +import { getMemberReturnExpressionWhenCalled, hasMemberEffectWhenCalled, LiteralValueOrUnknown, objectMembers, - ObjectPath, UNKNOWN_EXPRESSION, - UNKNOWN_PATH, - UNKNOWN_VALUE + UnknownValue } from '../values'; import Identifier from './Identifier'; import Literal from './Literal'; @@ -96,7 +96,7 @@ export default class ObjectExpression extends NodeBase { getLiteralValueAtPath( path: ObjectPath, - recursionTracker: ImmutableEntityPathTracker, + recursionTracker: PathTracker, origin: DeoptimizableEntity ): LiteralValueOrUnknown { if (this.propertyMap === null) this.buildPropertyMap(); @@ -108,13 +108,13 @@ export default class ObjectExpression extends NodeBase { typeof key !== 'string' || this.deoptimizedPaths.has(key) ) - return UNKNOWN_VALUE; + return UnknownValue; if ( path.length === 1 && !(this.propertyMap as PropertyMap)[key] && !objectMembers[key] && - (this.unmatchablePropertiesRead).length === 0 + this.unmatchablePropertiesRead.length === 0 ) { const expressionsToBeDeoptimized = this.expressionsToBeDeoptimized.get(key); if (expressionsToBeDeoptimized) { @@ -130,7 +130,7 @@ export default class ObjectExpression extends NodeBase { (this.propertyMap as PropertyMap)[key].exactMatchRead === null || (this.propertyMap as PropertyMap)[key].propertiesRead.length > 1 ) { - return UNKNOWN_VALUE; + return UnknownValue; } const expressionsToBeDeoptimized = this.expressionsToBeDeoptimized.get(key); @@ -145,7 +145,7 @@ export default class ObjectExpression extends NodeBase { getReturnExpressionWhenCalledAtPath( path: ObjectPath, - recursionTracker: ImmutableEntityPathTracker, + recursionTracker: PathTracker, origin: DeoptimizableEntity ): ExpressionEntity { if (this.propertyMap === null) this.buildPropertyMap(); @@ -189,7 +189,7 @@ export default class ObjectExpression extends NodeBase { ); } - hasEffectsWhenAccessedAtPath(path: ObjectPath, options: ExecutionPathOptions) { + hasEffectsWhenAccessedAtPath(path: ObjectPath, context: HasEffectsContext) { if (path.length === 0) return false; const key = path[0]; if ( @@ -208,12 +208,12 @@ export default class ObjectExpression extends NodeBase { : (this.propertyMap as PropertyMap)[key] ? (this.propertyMap as PropertyMap)[key].propertiesRead : []) { - if (property.hasEffectsWhenAccessedAtPath(subPath, options)) return true; + if (property.hasEffectsWhenAccessedAtPath(subPath, context)) return true; } return false; } - hasEffectsWhenAssignedAtPath(path: ObjectPath, options: ExecutionPathOptions) { + hasEffectsWhenAssignedAtPath(path: ObjectPath, context: HasEffectsContext) { if (path.length === 0) return false; const key = path[0]; if ( @@ -234,7 +234,7 @@ export default class ObjectExpression extends NodeBase { : (this.propertyMap as PropertyMap)[key] ? (this.propertyMap as PropertyMap)[key].propertiesSet : []) { - if (property.hasEffectsWhenAssignedAtPath(subPath, options)) return true; + if (property.hasEffectsWhenAssignedAtPath(subPath, context)) return true; } return false; } @@ -242,7 +242,7 @@ export default class ObjectExpression extends NodeBase { hasEffectsWhenCalledAtPath( path: ObjectPath, callOptions: CallOptions, - options: ExecutionPathOptions + context: HasEffectsContext ): boolean { const key = path[0]; if ( @@ -259,10 +259,10 @@ export default class ObjectExpression extends NodeBase { for (const property of (this.propertyMap as PropertyMap)[key] ? (this.propertyMap as PropertyMap)[key].propertiesRead : []) { - if (property.hasEffectsWhenCalledAtPath(subPath, callOptions, options)) return true; + if (property.hasEffectsWhenCalledAtPath(subPath, callOptions, context)) return true; } if (path.length === 1 && objectMembers[key]) - return hasMemberEffectWhenCalled(objectMembers, key, this.included, callOptions, options); + return hasMemberEffectWhenCalled(objectMembers, key, this.included, callOptions, context); return false; } @@ -295,7 +295,7 @@ export default class ObjectExpression extends NodeBase { EMPTY_IMMUTABLE_TRACKER, this ); - if (keyValue === UNKNOWN_VALUE) { + if (keyValue === UnknownValue) { if (isRead) { this.unmatchablePropertiesRead.push(property); } else { diff --git a/src/ast/nodes/ObjectPattern.ts b/src/ast/nodes/ObjectPattern.ts index c1ed96bfe57..e4d68fbe6d7 100644 --- a/src/ast/nodes/ObjectPattern.ts +++ b/src/ast/nodes/ObjectPattern.ts @@ -1,5 +1,5 @@ -import { ExecutionPathOptions } from '../ExecutionPathOptions'; -import { EMPTY_PATH, ObjectPath } from '../values'; +import { HasEffectsContext } from '../ExecutionContext'; +import { EMPTY_PATH, ObjectPath } from '../utils/PathTracker'; import Variable from '../variables/Variable'; import * as NodeType from './NodeType'; import Property from './Property'; @@ -38,10 +38,10 @@ export default class ObjectPattern extends NodeBase implements PatternNode { } } - hasEffectsWhenAssignedAtPath(path: ObjectPath, options: ExecutionPathOptions) { + hasEffectsWhenAssignedAtPath(path: ObjectPath, context: HasEffectsContext) { if (path.length > 0) return true; for (const property of this.properties) { - if (property.hasEffectsWhenAssignedAtPath(EMPTY_PATH, options)) return true; + if (property.hasEffectsWhenAssignedAtPath(EMPTY_PATH, context)) return true; } return false; } diff --git a/src/ast/nodes/Program.ts b/src/ast/nodes/Program.ts index d11ca72523d..8b5351c9b19 100644 --- a/src/ast/nodes/Program.ts +++ b/src/ast/nodes/Program.ts @@ -1,6 +1,6 @@ import MagicString from 'magic-string'; import { RenderOptions, renderStatementList } from '../../utils/renderHelpers'; -import { ExecutionPathOptions } from '../ExecutionPathOptions'; +import { HasEffectsContext, InclusionContext } from '../ExecutionContext'; import * as NodeType from './NodeType'; import { IncludeChildren, NodeBase, StatementNode } from './shared/Node'; @@ -9,18 +9,18 @@ export default class Program extends NodeBase { sourceType!: 'module'; type!: NodeType.tProgram; - hasEffects(options: ExecutionPathOptions) { + hasEffects(context: HasEffectsContext) { for (const node of this.body) { - if (node.hasEffects(options)) return true; + if (node.hasEffects(context)) return true; } return false; } - include(includeChildrenRecursively: IncludeChildren) { + include(context: InclusionContext, includeChildrenRecursively: IncludeChildren) { this.included = true; for (const node of this.body) { - if (includeChildrenRecursively || node.shouldBeIncluded()) { - node.include(includeChildrenRecursively); + if (includeChildrenRecursively || node.shouldBeIncluded(context)) { + node.include(context, includeChildrenRecursively); } } } diff --git a/src/ast/nodes/Property.ts b/src/ast/nodes/Property.ts index f06dbbcb616..290ae0da0de 100644 --- a/src/ast/nodes/Property.ts +++ b/src/ast/nodes/Property.ts @@ -1,20 +1,16 @@ import MagicString from 'magic-string'; import { RenderOptions } from '../../utils/renderHelpers'; -import CallOptions from '../CallOptions'; +import { CallOptions, NO_ARGS } from '../CallOptions'; import { DeoptimizableEntity } from '../DeoptimizableEntity'; -import { ExecutionPathOptions } from '../ExecutionPathOptions'; +import { HasEffectsContext } from '../ExecutionContext'; import { EMPTY_IMMUTABLE_TRACKER, - ImmutableEntityPathTracker -} from '../utils/ImmutableEntityPathTracker'; -import { EMPTY_PATH, - LiteralValueOrUnknown, ObjectPath, - UNKNOWN_EXPRESSION, - UNKNOWN_KEY, - UNKNOWN_VALUE -} from '../values'; + PathTracker, + UnknownKey +} from '../utils/PathTracker'; +import { LiteralValueOrUnknown, UNKNOWN_EXPRESSION } from '../values'; import * as NodeType from './NodeType'; import { ExpressionEntity } from './shared/Expression'; import { ExpressionNode, NodeBase } from './shared/Node'; @@ -36,7 +32,7 @@ export default class Property extends NodeBase implements DeoptimizableEntity { super.bind(); if (this.kind === 'get' && this.returnExpression === null) this.updateReturnExpression(); if (this.declarationInit !== null) { - this.declarationInit.deoptimizePath([UNKNOWN_KEY, UNKNOWN_KEY]); + this.declarationInit.deoptimizePath([UnknownKey, UnknownKey]); } } @@ -64,12 +60,9 @@ export default class Property extends NodeBase implements DeoptimizableEntity { getLiteralValueAtPath( path: ObjectPath, - recursionTracker: ImmutableEntityPathTracker, + recursionTracker: PathTracker, origin: DeoptimizableEntity ): LiteralValueOrUnknown { - if (this.kind === 'set') { - return UNKNOWN_VALUE; - } if (this.kind === 'get') { if (this.returnExpression === null) this.updateReturnExpression(); return (this.returnExpression as ExpressionEntity).getLiteralValueAtPath( @@ -83,12 +76,9 @@ export default class Property extends NodeBase implements DeoptimizableEntity { getReturnExpressionWhenCalledAtPath( path: ObjectPath, - recursionTracker: ImmutableEntityPathTracker, + recursionTracker: PathTracker, origin: DeoptimizableEntity ): ExpressionEntity { - if (this.kind === 'set') { - return UNKNOWN_EXPRESSION; - } if (this.kind === 'get') { if (this.returnExpression === null) this.updateReturnExpression(); return (this.returnExpression as ExpressionEntity).getReturnExpressionWhenCalledAtPath( @@ -100,65 +90,69 @@ export default class Property extends NodeBase implements DeoptimizableEntity { return this.value.getReturnExpressionWhenCalledAtPath(path, recursionTracker, origin); } - hasEffects(options: ExecutionPathOptions): boolean { - return this.key.hasEffects(options) || this.value.hasEffects(options); + hasEffects(context: HasEffectsContext): boolean { + return this.key.hasEffects(context) || this.value.hasEffects(context); } - hasEffectsWhenAccessedAtPath(path: ObjectPath, options: ExecutionPathOptions): boolean { + hasEffectsWhenAccessedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { if (this.kind === 'get') { + const trackedExpressions = context.accessed.getEntities(path); + if (trackedExpressions.has(this)) return false; + trackedExpressions.add(this); return ( - this.value.hasEffectsWhenCalledAtPath( - EMPTY_PATH, - this.accessorCallOptions, - options.getHasEffectsWhenCalledOptions() - ) || + this.value.hasEffectsWhenCalledAtPath(EMPTY_PATH, this.accessorCallOptions, context) || (path.length > 0 && - (this.returnExpression as ExpressionEntity).hasEffectsWhenAccessedAtPath(path, options)) + (this.returnExpression as ExpressionEntity).hasEffectsWhenAccessedAtPath(path, context)) ); } - return this.value.hasEffectsWhenAccessedAtPath(path, options); + return this.value.hasEffectsWhenAccessedAtPath(path, context); } - hasEffectsWhenAssignedAtPath(path: ObjectPath, options: ExecutionPathOptions): boolean { + hasEffectsWhenAssignedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { if (this.kind === 'get') { - return ( - path.length === 0 || - (this.returnExpression as ExpressionEntity).hasEffectsWhenAssignedAtPath(path, options) + const trackedExpressions = context.assigned.getEntities(path); + if (trackedExpressions.has(this)) return false; + trackedExpressions.add(this); + return (this.returnExpression as ExpressionEntity).hasEffectsWhenAssignedAtPath( + path, + context ); } if (this.kind === 'set') { - return ( - path.length > 0 || - this.value.hasEffectsWhenCalledAtPath( - EMPTY_PATH, - this.accessorCallOptions, - options.getHasEffectsWhenCalledOptions() - ) - ); + const trackedExpressions = context.assigned.getEntities(path); + if (trackedExpressions.has(this)) return false; + trackedExpressions.add(this); + return this.value.hasEffectsWhenCalledAtPath(EMPTY_PATH, this.accessorCallOptions, context); } - return this.value.hasEffectsWhenAssignedAtPath(path, options); + return this.value.hasEffectsWhenAssignedAtPath(path, context); } hasEffectsWhenCalledAtPath( path: ObjectPath, callOptions: CallOptions, - options: ExecutionPathOptions + context: HasEffectsContext ) { if (this.kind === 'get') { + const trackedExpressions = (callOptions.withNew + ? context.instantiated + : context.called + ).getEntities(path); + if (trackedExpressions.has(this)) return false; + trackedExpressions.add(this); return (this.returnExpression as ExpressionEntity).hasEffectsWhenCalledAtPath( path, callOptions, - options + context ); } - return this.value.hasEffectsWhenCalledAtPath(path, callOptions, options); + return this.value.hasEffectsWhenCalledAtPath(path, callOptions, context); } initialise() { - this.accessorCallOptions = CallOptions.create({ - callIdentifier: this, + this.accessorCallOptions = { + args: NO_ARGS, withNew: false - }); + }; } render(code: MagicString, options: RenderOptions) { diff --git a/src/ast/nodes/RestElement.ts b/src/ast/nodes/RestElement.ts index 7c5e3e84bbb..82d3b50a5af 100644 --- a/src/ast/nodes/RestElement.ts +++ b/src/ast/nodes/RestElement.ts @@ -1,5 +1,6 @@ -import { ExecutionPathOptions } from '../ExecutionPathOptions'; -import { EMPTY_PATH, ObjectPath, UNKNOWN_EXPRESSION, UNKNOWN_KEY } from '../values'; +import { HasEffectsContext } from '../ExecutionContext'; +import { EMPTY_PATH, ObjectPath, UnknownKey } from '../utils/PathTracker'; +import { UNKNOWN_EXPRESSION } from '../values'; import Variable from '../variables/Variable'; import * as NodeType from './NodeType'; import { ExpressionEntity } from './shared/Expression'; @@ -19,7 +20,7 @@ export default class RestElement extends NodeBase implements PatternNode { bind() { super.bind(); if (this.declarationInit !== null) { - this.declarationInit.deoptimizePath([UNKNOWN_KEY, UNKNOWN_KEY]); + this.declarationInit.deoptimizePath([UnknownKey, UnknownKey]); } } @@ -32,7 +33,7 @@ export default class RestElement extends NodeBase implements PatternNode { path.length === 0 && this.argument.deoptimizePath(EMPTY_PATH); } - hasEffectsWhenAssignedAtPath(path: ObjectPath, options: ExecutionPathOptions): boolean { - return path.length > 0 || this.argument.hasEffectsWhenAssignedAtPath(EMPTY_PATH, options); + hasEffectsWhenAssignedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { + return path.length > 0 || this.argument.hasEffectsWhenAssignedAtPath(EMPTY_PATH, context); } } diff --git a/src/ast/nodes/ReturnStatement.ts b/src/ast/nodes/ReturnStatement.ts index 7f2998b5ea5..9b434540943 100644 --- a/src/ast/nodes/ReturnStatement.ts +++ b/src/ast/nodes/ReturnStatement.ts @@ -1,19 +1,30 @@ import MagicString from 'magic-string'; import { RenderOptions } from '../../utils/renderHelpers'; -import { ExecutionPathOptions } from '../ExecutionPathOptions'; +import { BREAKFLOW_ERROR_RETURN, HasEffectsContext, InclusionContext } from '../ExecutionContext'; import { UNKNOWN_EXPRESSION } from '../values'; import * as NodeType from './NodeType'; -import { ExpressionNode, StatementBase } from './shared/Node'; +import { ExpressionNode, IncludeChildren, StatementBase } from './shared/Node'; export default class ReturnStatement extends StatementBase { argument!: ExpressionNode | null; type!: NodeType.tReturnStatement; - hasEffects(options: ExecutionPathOptions) { - return ( - !options.ignoreReturnAwaitYield() || - (this.argument !== null && this.argument.hasEffects(options)) - ); + hasEffects(context: HasEffectsContext) { + if ( + !context.ignore.returnAwaitYield || + (this.argument !== null && this.argument.hasEffects(context)) + ) + return true; + context.breakFlow = BREAKFLOW_ERROR_RETURN; + return false; + } + + include(context: InclusionContext, includeChildrenRecursively: IncludeChildren) { + this.included = true; + if (this.argument) { + this.argument.include(context, includeChildrenRecursively); + } + context.breakFlow = BREAKFLOW_ERROR_RETURN; } initialise() { diff --git a/src/ast/nodes/SequenceExpression.ts b/src/ast/nodes/SequenceExpression.ts index e4a21b486fb..13930c336f7 100644 --- a/src/ast/nodes/SequenceExpression.ts +++ b/src/ast/nodes/SequenceExpression.ts @@ -7,11 +7,11 @@ import { RenderOptions } from '../../utils/renderHelpers'; import { treeshakeNode } from '../../utils/treeshakeNode'; -import CallOptions from '../CallOptions'; +import { CallOptions } from '../CallOptions'; import { DeoptimizableEntity } from '../DeoptimizableEntity'; -import { ExecutionPathOptions } from '../ExecutionPathOptions'; -import { ImmutableEntityPathTracker } from '../utils/ImmutableEntityPathTracker'; -import { LiteralValueOrUnknown, ObjectPath } from '../values'; +import { HasEffectsContext, InclusionContext } from '../ExecutionContext'; +import { ObjectPath, PathTracker } from '../utils/PathTracker'; +import { LiteralValueOrUnknown } from '../values'; import CallExpression from './CallExpression'; import * as NodeType from './NodeType'; import { ExpressionNode, IncludeChildren, NodeBase } from './shared/Node'; @@ -26,7 +26,7 @@ export default class SequenceExpression extends NodeBase { getLiteralValueAtPath( path: ObjectPath, - recursionTracker: ImmutableEntityPathTracker, + recursionTracker: PathTracker, origin: DeoptimizableEntity ): LiteralValueOrUnknown { return this.expressions[this.expressions.length - 1].getLiteralValueAtPath( @@ -36,47 +36,47 @@ export default class SequenceExpression extends NodeBase { ); } - hasEffects(options: ExecutionPathOptions): boolean { + hasEffects(context: HasEffectsContext): boolean { for (const expression of this.expressions) { - if (expression.hasEffects(options)) return true; + if (expression.hasEffects(context)) return true; } return false; } - hasEffectsWhenAccessedAtPath(path: ObjectPath, options: ExecutionPathOptions): boolean { + hasEffectsWhenAccessedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { return ( path.length > 0 && - this.expressions[this.expressions.length - 1].hasEffectsWhenAccessedAtPath(path, options) + this.expressions[this.expressions.length - 1].hasEffectsWhenAccessedAtPath(path, context) ); } - hasEffectsWhenAssignedAtPath(path: ObjectPath, options: ExecutionPathOptions): boolean { + hasEffectsWhenAssignedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { return ( path.length === 0 || - this.expressions[this.expressions.length - 1].hasEffectsWhenAssignedAtPath(path, options) + this.expressions[this.expressions.length - 1].hasEffectsWhenAssignedAtPath(path, context) ); } hasEffectsWhenCalledAtPath( path: ObjectPath, callOptions: CallOptions, - options: ExecutionPathOptions + context: HasEffectsContext ): boolean { return this.expressions[this.expressions.length - 1].hasEffectsWhenCalledAtPath( path, callOptions, - options + context ); } - include(includeChildrenRecursively: IncludeChildren) { + include(context: InclusionContext, includeChildrenRecursively: IncludeChildren) { this.included = true; for (let i = 0; i < this.expressions.length - 1; i++) { const node = this.expressions[i]; - if (includeChildrenRecursively || node.shouldBeIncluded()) - node.include(includeChildrenRecursively); + if (includeChildrenRecursively || node.shouldBeIncluded(context)) + node.include(context, includeChildrenRecursively); } - this.expressions[this.expressions.length - 1].include(includeChildrenRecursively); + this.expressions[this.expressions.length - 1].include(context, includeChildrenRecursively); } render( diff --git a/src/ast/nodes/SpreadElement.ts b/src/ast/nodes/SpreadElement.ts index 2ddbcffbac0..cc0807079c4 100644 --- a/src/ast/nodes/SpreadElement.ts +++ b/src/ast/nodes/SpreadElement.ts @@ -1,4 +1,4 @@ -import { UNKNOWN_KEY } from '../values'; +import { UnknownKey } from '../utils/PathTracker'; import * as NodeType from './NodeType'; import { ExpressionNode, NodeBase } from './shared/Node'; @@ -10,6 +10,6 @@ export default class SpreadElement extends NodeBase { super.bind(); // Only properties of properties of the argument could become subject to reassignment // This will also reassign the return values of iterators - this.argument.deoptimizePath([UNKNOWN_KEY, UNKNOWN_KEY]); + this.argument.deoptimizePath([UnknownKey, UnknownKey]); } } diff --git a/src/ast/nodes/SwitchCase.ts b/src/ast/nodes/SwitchCase.ts index fae1a539978..4306b66eac8 100644 --- a/src/ast/nodes/SwitchCase.ts +++ b/src/ast/nodes/SwitchCase.ts @@ -4,6 +4,7 @@ import { RenderOptions, renderStatementList } from '../../utils/renderHelpers'; +import { HasEffectsContext, InclusionContext } from '../ExecutionContext'; import * as NodeType from './NodeType'; import { ExpressionNode, IncludeChildren, NodeBase, StatementNode } from './shared/Node'; @@ -12,12 +13,21 @@ export default class SwitchCase extends NodeBase { test!: ExpressionNode | null; type!: NodeType.tSwitchCase; - include(includeChildrenRecursively: IncludeChildren) { + hasEffects(context: HasEffectsContext): boolean { + if (this.test && this.test.hasEffects(context)) return true; + for (const node of this.consequent) { + if (context.breakFlow) break; + if (node.hasEffects(context)) return true; + } + return false; + } + + include(context: InclusionContext, includeChildrenRecursively: IncludeChildren) { this.included = true; - if (this.test) this.test.include(includeChildrenRecursively); + if (this.test) this.test.include(context, includeChildrenRecursively); for (const node of this.consequent) { - if (includeChildrenRecursively || node.shouldBeIncluded()) - node.include(includeChildrenRecursively); + if (includeChildrenRecursively || node.shouldBeIncluded(context)) + node.include(context, includeChildrenRecursively); } } diff --git a/src/ast/nodes/SwitchStatement.ts b/src/ast/nodes/SwitchStatement.ts index b826bdf2787..072630b8977 100644 --- a/src/ast/nodes/SwitchStatement.ts +++ b/src/ast/nodes/SwitchStatement.ts @@ -1,10 +1,34 @@ -import { ExecutionPathOptions } from '../ExecutionPathOptions'; +import { + BreakFlow, + BREAKFLOW_ERROR_RETURN, + BREAKFLOW_NONE, + HasEffectsContext, + InclusionContext +} from '../ExecutionContext'; import BlockScope from '../scopes/BlockScope'; import Scope from '../scopes/Scope'; import * as NodeType from './NodeType'; -import { ExpressionNode, StatementBase } from './shared/Node'; +import { ExpressionNode, IncludeChildren, StatementBase } from './shared/Node'; import SwitchCase from './SwitchCase'; +function getMinBreakflowAfterCase( + minBreakFlow: BreakFlow | false, + context: InclusionContext +): BreakFlow | false { + if (!(minBreakFlow && context.breakFlow)) { + return BREAKFLOW_NONE; + } + if (minBreakFlow instanceof Set) { + if (context.breakFlow instanceof Set) { + for (const label of context.breakFlow) { + minBreakFlow.add(label); + } + } + return minBreakFlow; + } + return context.breakFlow; +} + export default class SwitchStatement extends StatementBase { cases!: SwitchCase[]; discriminant!: ExpressionNode; @@ -14,7 +38,42 @@ export default class SwitchStatement extends StatementBase { this.scope = new BlockScope(parentScope); } - hasEffects(options: ExecutionPathOptions) { - return super.hasEffects(options.setIgnoreBreakStatements()); + hasEffects(context: HasEffectsContext) { + if (this.discriminant.hasEffects(context)) return true; + const { + breakFlow, + ignore: { breakStatements } + } = context; + let hasDefault = false; + let minBreakFlow: BreakFlow | false = BREAKFLOW_ERROR_RETURN; + context.ignore.breakStatements = true; + for (const switchCase of this.cases) { + if (switchCase.hasEffects(context)) return true; + if (switchCase.test === null) hasDefault = true; + minBreakFlow = getMinBreakflowAfterCase(minBreakFlow, context); + context.breakFlow = breakFlow; + } + if (hasDefault && !(minBreakFlow instanceof Set && minBreakFlow.has(null))) { + context.breakFlow = minBreakFlow; + } + context.ignore.breakStatements = breakStatements; + return false; + } + + include(context: InclusionContext, includeChildrenRecursively: IncludeChildren) { + this.included = true; + this.discriminant.include(context, includeChildrenRecursively); + const { breakFlow } = context; + let hasDefault = false; + let minBreakFlow: BreakFlow | false = BREAKFLOW_ERROR_RETURN; + for (const switchCase of this.cases) { + if (switchCase.test === null) hasDefault = true; + switchCase.include(context, includeChildrenRecursively); + minBreakFlow = getMinBreakflowAfterCase(minBreakFlow, context); + context.breakFlow = breakFlow; + } + if (hasDefault && !(minBreakFlow instanceof Set && minBreakFlow.has(null))) { + context.breakFlow = minBreakFlow; + } } } diff --git a/src/ast/nodes/TaggedTemplateExpression.ts b/src/ast/nodes/TaggedTemplateExpression.ts index eb2bec9795e..079b883e955 100644 --- a/src/ast/nodes/TaggedTemplateExpression.ts +++ b/src/ast/nodes/TaggedTemplateExpression.ts @@ -1,6 +1,6 @@ -import CallOptions from '../CallOptions'; -import { ExecutionPathOptions } from '../ExecutionPathOptions'; -import { EMPTY_PATH } from '../values'; +import { CallOptions, NO_ARGS } from '../CallOptions'; +import { HasEffectsContext } from '../ExecutionContext'; +import { EMPTY_PATH } from '../utils/PathTracker'; import Identifier from './Identifier'; import * as NodeType from './NodeType'; import { ExpressionNode, NodeBase } from './shared/Node'; @@ -41,21 +41,17 @@ export default class TaggedTemplateExpression extends NodeBase { } } - hasEffects(options: ExecutionPathOptions) { + hasEffects(context: HasEffectsContext) { return ( - super.hasEffects(options) || - this.tag.hasEffectsWhenCalledAtPath( - EMPTY_PATH, - this.callOptions, - options.getHasEffectsWhenCalledOptions() - ) + super.hasEffects(context) || + this.tag.hasEffectsWhenCalledAtPath(EMPTY_PATH, this.callOptions, context) ); } initialise() { - this.callOptions = CallOptions.create({ - callIdentifier: this, + this.callOptions = { + args: NO_ARGS, withNew: false - }); + }; } } diff --git a/src/ast/nodes/TemplateElement.ts b/src/ast/nodes/TemplateElement.ts index 25ec2aa11d9..800062a4e25 100644 --- a/src/ast/nodes/TemplateElement.ts +++ b/src/ast/nodes/TemplateElement.ts @@ -1,4 +1,3 @@ -import { ExecutionPathOptions } from '../ExecutionPathOptions'; import * as NodeType from './NodeType'; import { NodeBase } from './shared/Node'; @@ -10,7 +9,7 @@ export default class TemplateElement extends NodeBase { raw: string; }; - hasEffects(_options: ExecutionPathOptions) { + hasEffects() { return false; } } diff --git a/src/ast/nodes/TemplateLiteral.ts b/src/ast/nodes/TemplateLiteral.ts index f628bd25af2..fbc02c708ee 100644 --- a/src/ast/nodes/TemplateLiteral.ts +++ b/src/ast/nodes/TemplateLiteral.ts @@ -1,6 +1,7 @@ import MagicString from 'magic-string'; import { RenderOptions } from '../../utils/renderHelpers'; -import { LiteralValueOrUnknown, ObjectPath, UNKNOWN_VALUE } from '../values'; +import { ObjectPath } from '../utils/PathTracker'; +import { LiteralValueOrUnknown, UnknownValue } from '../values'; import * as NodeType from './NodeType'; import { ExpressionNode, NodeBase } from './shared/Node'; import TemplateElement from './TemplateElement'; @@ -12,7 +13,7 @@ export default class TemplateLiteral extends NodeBase { getLiteralValueAtPath(path: ObjectPath): LiteralValueOrUnknown { if (path.length > 0 || this.quasis.length !== 1) { - return UNKNOWN_VALUE; + return UnknownValue; } return this.quasis[0].value.cooked; } diff --git a/src/ast/nodes/ThisExpression.ts b/src/ast/nodes/ThisExpression.ts index d575c07041b..512ac7b933b 100644 --- a/src/ast/nodes/ThisExpression.ts +++ b/src/ast/nodes/ThisExpression.ts @@ -1,8 +1,7 @@ import MagicString from 'magic-string'; -import { RenderOptions } from '../../utils/renderHelpers'; -import { ExecutionPathOptions } from '../ExecutionPathOptions'; +import { HasEffectsContext } from '../ExecutionContext'; import ModuleScope from '../scopes/ModuleScope'; -import { ObjectPath } from '../values'; +import { ObjectPath } from '../utils/PathTracker'; import ThisVariable from '../variables/ThisVariable'; import * as NodeType from './NodeType'; import { NodeBase } from './shared/Node'; @@ -18,12 +17,12 @@ export default class ThisExpression extends NodeBase { this.variable = this.scope.findVariable('this') as ThisVariable; } - hasEffectsWhenAccessedAtPath(path: ObjectPath, options: ExecutionPathOptions): boolean { - return path.length > 0 && this.variable.hasEffectsWhenAccessedAtPath(path, options); + hasEffectsWhenAccessedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { + return path.length > 0 && this.variable.hasEffectsWhenAccessedAtPath(path, context); } - hasEffectsWhenAssignedAtPath(path: ObjectPath, options: ExecutionPathOptions): boolean { - return this.variable.hasEffectsWhenAssignedAtPath(path, options); + hasEffectsWhenAssignedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { + return this.variable.hasEffectsWhenAssignedAtPath(path, context); } initialise() { @@ -41,7 +40,7 @@ export default class ThisExpression extends NodeBase { } } - render(code: MagicString, _options: RenderOptions) { + render(code: MagicString) { if (this.alias !== null) { code.overwrite(this.start, this.end, this.alias, { contentOnly: false, diff --git a/src/ast/nodes/ThrowStatement.ts b/src/ast/nodes/ThrowStatement.ts index bc63154df9f..aab2d944b34 100644 --- a/src/ast/nodes/ThrowStatement.ts +++ b/src/ast/nodes/ThrowStatement.ts @@ -1,17 +1,23 @@ import MagicString from 'magic-string'; import { RenderOptions } from '../../utils/renderHelpers'; -import { ExecutionPathOptions } from '../ExecutionPathOptions'; +import { BREAKFLOW_ERROR_RETURN, InclusionContext } from '../ExecutionContext'; import * as NodeType from './NodeType'; -import { ExpressionNode, StatementBase } from './shared/Node'; +import { ExpressionNode, IncludeChildren, StatementBase } from './shared/Node'; export default class ThrowStatement extends StatementBase { argument!: ExpressionNode; type!: NodeType.tThrowStatement; - hasEffects(_options: ExecutionPathOptions) { + hasEffects() { return true; } + include(context: InclusionContext, includeChildrenRecursively: IncludeChildren) { + this.included = true; + this.argument.include(context, includeChildrenRecursively); + context.breakFlow = BREAKFLOW_ERROR_RETURN; + } + render(code: MagicString, options: RenderOptions) { this.argument.render(code, options, { preventASI: true }); } diff --git a/src/ast/nodes/TryStatement.ts b/src/ast/nodes/TryStatement.ts index c71da45c81b..67182e53790 100644 --- a/src/ast/nodes/TryStatement.ts +++ b/src/ast/nodes/TryStatement.ts @@ -1,4 +1,4 @@ -import { ExecutionPathOptions } from '../ExecutionPathOptions'; +import { HasEffectsContext, InclusionContext } from '../ExecutionContext'; import BlockStatement from './BlockStatement'; import CatchClause from './CatchClause'; import * as NodeType from './NodeType'; @@ -12,27 +12,31 @@ export default class TryStatement extends StatementBase { private directlyIncluded = false; - hasEffects(options: ExecutionPathOptions): boolean { + hasEffects(context: HasEffectsContext): boolean { return ( this.block.body.length > 0 || - (this.handler !== null && this.handler.hasEffects(options)) || - (this.finalizer !== null && this.finalizer.hasEffects(options)) + (this.handler !== null && this.handler.hasEffects(context)) || + (this.finalizer !== null && this.finalizer.hasEffects(context)) ); } - include(includeChildrenRecursively: IncludeChildren) { + include(context: InclusionContext, includeChildrenRecursively: IncludeChildren) { + const { breakFlow } = context; if (!this.directlyIncluded || !this.context.tryCatchDeoptimization) { this.included = true; this.directlyIncluded = true; this.block.include( + context, this.context.tryCatchDeoptimization ? INCLUDE_PARAMETERS : includeChildrenRecursively ); + context.breakFlow = breakFlow; } if (this.handler !== null) { - this.handler.include(includeChildrenRecursively); + this.handler.include(context, includeChildrenRecursively); + context.breakFlow = breakFlow; } if (this.finalizer !== null) { - this.finalizer.include(includeChildrenRecursively); + this.finalizer.include(context, includeChildrenRecursively); } } } diff --git a/src/ast/nodes/UnaryExpression.ts b/src/ast/nodes/UnaryExpression.ts index 37731ae1860..b80b70474ea 100644 --- a/src/ast/nodes/UnaryExpression.ts +++ b/src/ast/nodes/UnaryExpression.ts @@ -1,7 +1,7 @@ import { DeoptimizableEntity } from '../DeoptimizableEntity'; -import { ExecutionPathOptions } from '../ExecutionPathOptions'; -import { ImmutableEntityPathTracker } from '../utils/ImmutableEntityPathTracker'; -import { EMPTY_PATH, LiteralValueOrUnknown, ObjectPath, UNKNOWN_VALUE } from '../values'; +import { HasEffectsContext } from '../ExecutionContext'; +import { EMPTY_PATH, ObjectPath, PathTracker } from '../utils/PathTracker'; +import { LiteralValueOrUnknown, UnknownValue } from '../values'; import Identifier from './Identifier'; import { LiteralValue } from './Literal'; import * as NodeType from './NodeType'; @@ -13,7 +13,7 @@ const unaryOperators: { '!': value => !value, '+': value => +(value as NonNullable), '-': value => -(value as NonNullable), - delete: () => UNKNOWN_VALUE, + delete: () => UnknownValue, typeof: value => typeof value, void: () => undefined, '~': value => ~(value as NonNullable) @@ -34,26 +34,26 @@ export default class UnaryExpression extends NodeBase { getLiteralValueAtPath( path: ObjectPath, - recursionTracker: ImmutableEntityPathTracker, + recursionTracker: PathTracker, origin: DeoptimizableEntity ): LiteralValueOrUnknown { - if (path.length > 0) return UNKNOWN_VALUE; + if (path.length > 0) return UnknownValue; const argumentValue = this.argument.getLiteralValueAtPath(EMPTY_PATH, recursionTracker, origin); - if (argumentValue === UNKNOWN_VALUE) return UNKNOWN_VALUE; + if (argumentValue === UnknownValue) return UnknownValue; - return unaryOperators[this.operator](argumentValue as LiteralValue); + return unaryOperators[this.operator](argumentValue); } - hasEffects(options: ExecutionPathOptions): boolean { + hasEffects(context: HasEffectsContext): boolean { if (this.operator === 'typeof' && this.argument instanceof Identifier) return false; return ( - this.argument.hasEffects(options) || + this.argument.hasEffects(context) || (this.operator === 'delete' && - this.argument.hasEffectsWhenAssignedAtPath(EMPTY_PATH, options)) + this.argument.hasEffectsWhenAssignedAtPath(EMPTY_PATH, context)) ); } - hasEffectsWhenAccessedAtPath(path: ObjectPath, _options: ExecutionPathOptions) { + hasEffectsWhenAccessedAtPath(path: ObjectPath) { if (this.operator === 'void') { return path.length > 0; } diff --git a/src/ast/nodes/UnknownNode.ts b/src/ast/nodes/UnknownNode.ts index 7dca98101eb..cdbd09ef538 100644 --- a/src/ast/nodes/UnknownNode.ts +++ b/src/ast/nodes/UnknownNode.ts @@ -1,12 +1,12 @@ -import { ExecutionPathOptions } from '../ExecutionPathOptions'; +import { InclusionContext } from '../ExecutionContext'; import { NodeBase } from './shared/Node'; export default class UnknownNode extends NodeBase { - hasEffects(_options: ExecutionPathOptions) { + hasEffects() { return true; } - include() { - super.include(true); + include(context: InclusionContext) { + super.include(context, true); } } diff --git a/src/ast/nodes/UpdateExpression.ts b/src/ast/nodes/UpdateExpression.ts index 1083d5948cd..5ea6204f942 100644 --- a/src/ast/nodes/UpdateExpression.ts +++ b/src/ast/nodes/UpdateExpression.ts @@ -1,7 +1,7 @@ import MagicString from 'magic-string'; import { RenderOptions } from '../../utils/renderHelpers'; -import { ExecutionPathOptions } from '../ExecutionPathOptions'; -import { EMPTY_PATH, ObjectPath } from '../values'; +import { HasEffectsContext } from '../ExecutionContext'; +import { EMPTY_PATH, ObjectPath } from '../utils/PathTracker'; import Identifier from './Identifier'; import * as NodeType from './NodeType'; import { ExpressionNode, NodeBase } from './shared/Node'; @@ -21,14 +21,14 @@ export default class UpdateExpression extends NodeBase { } } - hasEffects(options: ExecutionPathOptions): boolean { + hasEffects(context: HasEffectsContext): boolean { return ( - this.argument.hasEffects(options) || - this.argument.hasEffectsWhenAssignedAtPath(EMPTY_PATH, options) + this.argument.hasEffects(context) || + this.argument.hasEffectsWhenAssignedAtPath(EMPTY_PATH, context) ); } - hasEffectsWhenAccessedAtPath(path: ObjectPath, _options: ExecutionPathOptions) { + hasEffectsWhenAccessedAtPath(path: ObjectPath) { return path.length > 1; } diff --git a/src/ast/nodes/VariableDeclaration.ts b/src/ast/nodes/VariableDeclaration.ts index 8a84f93ef93..88208238f17 100644 --- a/src/ast/nodes/VariableDeclaration.ts +++ b/src/ast/nodes/VariableDeclaration.ts @@ -6,8 +6,8 @@ import { RenderOptions } from '../../utils/renderHelpers'; import { getSystemExportStatement } from '../../utils/systemJsRendering'; -import { ExecutionPathOptions } from '../ExecutionPathOptions'; -import { EMPTY_PATH, ObjectPath } from '../values'; +import { InclusionContext } from '../ExecutionContext'; +import { EMPTY_PATH } from '../utils/PathTracker'; import Variable from '../variables/Variable'; import Identifier, { IdentifierWithVariable } from './Identifier'; import * as NodeType from './NodeType'; @@ -20,9 +20,7 @@ function isReassignedExportsMember(variable: Variable): boolean { function areAllDeclarationsIncludedAndNotExported(declarations: VariableDeclarator[]): boolean { for (const declarator of declarations) { - if (!declarator.included) { - return false; - } + if (!declarator.included) return false; if (declarator.id.type === NodeType.Identifier) { if ((declarator.id.variable as Variable).exportName) return false; } else { @@ -39,28 +37,31 @@ export default class VariableDeclaration extends NodeBase { kind!: 'var' | 'let' | 'const'; type!: NodeType.tVariableDeclaration; - deoptimizePath(_path: ObjectPath) { + deoptimizePath() { for (const declarator of this.declarations) { declarator.deoptimizePath(EMPTY_PATH); } } - hasEffectsWhenAssignedAtPath(_path: ObjectPath, _options: ExecutionPathOptions) { + hasEffectsWhenAssignedAtPath() { return false; } - include(includeChildrenRecursively: IncludeChildren) { + include(context: InclusionContext, includeChildrenRecursively: IncludeChildren) { this.included = true; for (const declarator of this.declarations) { - if (includeChildrenRecursively || declarator.shouldBeIncluded()) - declarator.include(includeChildrenRecursively); + if (includeChildrenRecursively || declarator.shouldBeIncluded(context)) + declarator.include(context, includeChildrenRecursively); } } - includeWithAllDeclaredVariables(includeChildrenRecursively: IncludeChildren) { + includeWithAllDeclaredVariables( + includeChildrenRecursively: IncludeChildren, + context: InclusionContext + ) { this.included = true; for (const declarator of this.declarations) { - declarator.include(includeChildrenRecursively); + declarator.include(context, includeChildrenRecursively); } } diff --git a/src/ast/nodes/VariableDeclarator.ts b/src/ast/nodes/VariableDeclarator.ts index 292401b4f69..4c50807afaf 100644 --- a/src/ast/nodes/VariableDeclarator.ts +++ b/src/ast/nodes/VariableDeclarator.ts @@ -1,6 +1,7 @@ import MagicString from 'magic-string'; import { RenderOptions } from '../../utils/renderHelpers'; -import { ObjectPath, UNDEFINED_EXPRESSION } from '../values'; +import { ObjectPath } from '../utils/PathTracker'; +import { UNDEFINED_EXPRESSION } from '../values'; import * as NodeType from './NodeType'; import { ExpressionNode, NodeBase } from './shared/Node'; import { PatternNode } from './shared/Pattern'; diff --git a/src/ast/nodes/WhileStatement.ts b/src/ast/nodes/WhileStatement.ts index 5338276d1e7..3779f10650e 100644 --- a/src/ast/nodes/WhileStatement.ts +++ b/src/ast/nodes/WhileStatement.ts @@ -1,15 +1,30 @@ -import { ExecutionPathOptions } from '../ExecutionPathOptions'; +import { HasEffectsContext, InclusionContext } from '../ExecutionContext'; import * as NodeType from './NodeType'; -import { ExpressionNode, StatementBase, StatementNode } from './shared/Node'; +import { ExpressionNode, IncludeChildren, StatementBase, StatementNode } from './shared/Node'; export default class WhileStatement extends StatementBase { body!: StatementNode; test!: ExpressionNode; type!: NodeType.tWhileStatement; - hasEffects(options: ExecutionPathOptions): boolean { - return ( - this.test.hasEffects(options) || this.body.hasEffects(options.setIgnoreBreakStatements()) - ); + hasEffects(context: HasEffectsContext): boolean { + if (this.test.hasEffects(context)) return true; + const { + breakFlow, + ignore: { breakStatements } + } = context; + context.ignore.breakStatements = true; + if (this.body.hasEffects(context)) return true; + context.ignore.breakStatements = breakStatements; + context.breakFlow = breakFlow; + return false; + } + + include(context: InclusionContext, includeChildrenRecursively: IncludeChildren) { + this.included = true; + this.test.include(context, includeChildrenRecursively); + const { breakFlow } = context; + this.body.include(context, includeChildrenRecursively); + context.breakFlow = breakFlow; } } diff --git a/src/ast/nodes/YieldExpression.ts b/src/ast/nodes/YieldExpression.ts index fef3d5ddd38..ff0a96c3a6d 100644 --- a/src/ast/nodes/YieldExpression.ts +++ b/src/ast/nodes/YieldExpression.ts @@ -1,7 +1,7 @@ import MagicString from 'magic-string'; import { RenderOptions } from '../../utils/renderHelpers'; -import { ExecutionPathOptions } from '../ExecutionPathOptions'; -import { UNKNOWN_PATH } from '../values'; +import { HasEffectsContext } from '../ExecutionContext'; +import { UNKNOWN_PATH } from '../utils/PathTracker'; import * as NodeType from './NodeType'; import { ExpressionNode, NodeBase } from './shared/Node'; @@ -17,10 +17,10 @@ export default class YieldExpression extends NodeBase { } } - hasEffects(options: ExecutionPathOptions) { + hasEffects(context: HasEffectsContext) { return ( - !options.ignoreReturnAwaitYield() || - (this.argument !== null && this.argument.hasEffects(options)) + !context.ignore.returnAwaitYield || + (this.argument !== null && this.argument.hasEffects(context)) ); } diff --git a/src/ast/nodes/index.ts b/src/ast/nodes/index.ts index 6d629d152bb..d84aeaba725 100644 --- a/src/ast/nodes/index.ts +++ b/src/ast/nodes/index.ts @@ -13,6 +13,7 @@ import ClassBody from './ClassBody'; import ClassDeclaration from './ClassDeclaration'; import ClassExpression from './ClassExpression'; import ConditionalExpression from './ConditionalExpression'; +import ContinueStatement from './ContinueStatement'; import DoWhileStatement from './DoWhileStatement'; import EmptyStatement from './EmptyStatement'; import ExportAllDeclaration from './ExportAllDeclaration'; @@ -78,6 +79,7 @@ export const nodeConstructors: { ClassDeclaration, ClassExpression, ConditionalExpression, + ContinueStatement, DoWhileStatement, EmptyStatement, ExportAllDeclaration, diff --git a/src/ast/nodes/shared/ClassNode.ts b/src/ast/nodes/shared/ClassNode.ts index fae1c97144d..5acec68cd4f 100644 --- a/src/ast/nodes/shared/ClassNode.ts +++ b/src/ast/nodes/shared/ClassNode.ts @@ -1,8 +1,8 @@ -import CallOptions from '../../CallOptions'; -import { ExecutionPathOptions } from '../../ExecutionPathOptions'; +import { CallOptions } from '../../CallOptions'; +import { HasEffectsContext } from '../../ExecutionContext'; import ChildScope from '../../scopes/ChildScope'; import Scope from '../../scopes/Scope'; -import { ObjectPath } from '../../values'; +import { ObjectPath } from '../../utils/PathTracker'; import ClassBody from '../ClassBody'; import Identifier from '../Identifier'; import { ExpressionNode, NodeBase } from './Node'; @@ -16,23 +16,24 @@ export default class ClassNode extends NodeBase { this.scope = new ChildScope(parentScope); } - hasEffectsWhenAccessedAtPath(path: ObjectPath, _options: ExecutionPathOptions) { + hasEffectsWhenAccessedAtPath(path: ObjectPath) { return path.length > 1; } - hasEffectsWhenAssignedAtPath(path: ObjectPath, _options: ExecutionPathOptions) { + hasEffectsWhenAssignedAtPath(path: ObjectPath) { return path.length > 1; } hasEffectsWhenCalledAtPath( path: ObjectPath, callOptions: CallOptions, - options: ExecutionPathOptions + context: HasEffectsContext ) { + if (!callOptions.withNew) return true; return ( - this.body.hasEffectsWhenCalledAtPath(path, callOptions, options) || + this.body.hasEffectsWhenCalledAtPath(path, callOptions, context) || (this.superClass !== null && - this.superClass.hasEffectsWhenCalledAtPath(path, callOptions, options)) + this.superClass.hasEffectsWhenCalledAtPath(path, callOptions, context)) ); } diff --git a/src/ast/nodes/shared/Expression.ts b/src/ast/nodes/shared/Expression.ts index 292aa1fba43..08b03ae8e1a 100644 --- a/src/ast/nodes/shared/Expression.ts +++ b/src/ast/nodes/shared/Expression.ts @@ -1,9 +1,9 @@ -import CallOptions from '../../CallOptions'; +import { CallOptions } from '../../CallOptions'; import { DeoptimizableEntity } from '../../DeoptimizableEntity'; import { WritableEntity } from '../../Entity'; -import { ExecutionPathOptions } from '../../ExecutionPathOptions'; -import { ImmutableEntityPathTracker } from '../../utils/ImmutableEntityPathTracker'; -import { LiteralValueOrUnknown, ObjectPath } from '../../values'; +import { HasEffectsContext, InclusionContext } from '../../ExecutionContext'; +import { ObjectPath, PathTracker } from '../../utils/PathTracker'; +import { LiteralValueOrUnknown } from '../../values'; import SpreadElement from '../SpreadElement'; import { ExpressionNode, IncludeChildren } from './Node'; @@ -13,24 +13,24 @@ export interface ExpressionEntity extends WritableEntity { /** * If possible it returns a stringifyable literal value for this node that can be used * for inlining or comparing values. - * Otherwise it should return UNKNOWN_VALUE. + * Otherwise it should return UnknownValue. */ getLiteralValueAtPath( path: ObjectPath, - recursionTracker: ImmutableEntityPathTracker, + recursionTracker: PathTracker, origin: DeoptimizableEntity ): LiteralValueOrUnknown; getReturnExpressionWhenCalledAtPath( path: ObjectPath, - recursionTracker: ImmutableEntityPathTracker, + recursionTracker: PathTracker, origin: DeoptimizableEntity ): ExpressionEntity; - hasEffectsWhenAccessedAtPath(path: ObjectPath, options: ExecutionPathOptions): boolean; + hasEffectsWhenAccessedAtPath(path: ObjectPath, context: HasEffectsContext): boolean; hasEffectsWhenCalledAtPath( path: ObjectPath, callOptions: CallOptions, - options: ExecutionPathOptions + context: HasEffectsContext ): boolean; - include(includeChildrenRecursively: IncludeChildren): void; - includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void; + include(context: InclusionContext, includeChildrenRecursively: IncludeChildren): void; + includeCallArguments(context: InclusionContext, args: (ExpressionNode | SpreadElement)[]): void; } diff --git a/src/ast/nodes/shared/FunctionNode.ts b/src/ast/nodes/shared/FunctionNode.ts index 4d8ae7002a9..e3d7ca49b8e 100644 --- a/src/ast/nodes/shared/FunctionNode.ts +++ b/src/ast/nodes/shared/FunctionNode.ts @@ -1,12 +1,13 @@ -import CallOptions from '../../CallOptions'; -import { ExecutionPathOptions } from '../../ExecutionPathOptions'; +import { CallOptions } from '../../CallOptions'; +import { BREAKFLOW_NONE, HasEffectsContext, InclusionContext } from '../../ExecutionContext'; import FunctionScope from '../../scopes/FunctionScope'; -import { ObjectPath, UNKNOWN_EXPRESSION, UNKNOWN_KEY, UNKNOWN_PATH } from '../../values'; +import { ObjectPath, UNKNOWN_PATH, UnknownKey } from '../../utils/PathTracker'; +import { UNKNOWN_EXPRESSION, UnknownObjectExpression } from '../../values'; import BlockStatement from '../BlockStatement'; import Identifier, { IdentifierWithVariable } from '../Identifier'; import RestElement from '../RestElement'; import SpreadElement from '../SpreadElement'; -import { ExpressionNode, GenericEsTreeNode, NodeBase } from './Node'; +import { ExpressionNode, GenericEsTreeNode, IncludeChildren, NodeBase } from './Node'; import { PatternNode } from './Pattern'; export default class FunctionNode extends NodeBase { @@ -27,7 +28,7 @@ export default class FunctionNode extends NodeBase { if (path.length === 1) { if (path[0] === 'prototype') { this.isPrototypeDeoptimized = true; - } else if (path[0] === UNKNOWN_KEY) { + } else if (path[0] === UnknownKey) { this.isPrototypeDeoptimized = true; // A reassignment of UNKNOWN_PATH is considered equivalent to having lost track @@ -46,9 +47,7 @@ export default class FunctionNode extends NodeBase { } hasEffectsWhenAccessedAtPath(path: ObjectPath) { - if (path.length <= 1) { - return false; - } + if (path.length <= 1) return false; return path.length > 2 || path[0] !== 'prototype' || this.isPrototypeDeoptimized; } @@ -62,34 +61,51 @@ export default class FunctionNode extends NodeBase { hasEffectsWhenCalledAtPath( path: ObjectPath, callOptions: CallOptions, - options: ExecutionPathOptions + context: HasEffectsContext ) { - if (path.length > 0) { - return true; - } - const innerOptions = this.scope.getOptionsWhenCalledWith(callOptions, options); + if (path.length > 0) return true; for (const param of this.params) { - if (param.hasEffects(innerOptions)) return true; + if (param.hasEffects(context)) return true; } - return this.body.hasEffects(innerOptions); + const thisInit = context.replacedVariableInits.get(this.scope.thisVariable); + context.replacedVariableInits.set( + this.scope.thisVariable, + callOptions.withNew ? new UnknownObjectExpression() : UNKNOWN_EXPRESSION + ); + const { breakFlow, ignore } = context; + context.ignore = { + breakStatements: false, + labels: new Set(), + returnAwaitYield: true + }; + if (this.body.hasEffects(context)) return true; + context.breakFlow = breakFlow; + if (thisInit) { + context.replacedVariableInits.set(this.scope.thisVariable, thisInit); + } else { + context.replacedVariableInits.delete(this.scope.thisVariable); + } + context.ignore = ignore; + return false; } - include(includeChildrenRecursively: boolean | 'variables') { + include(context: InclusionContext, includeChildrenRecursively: IncludeChildren) { this.included = true; - this.body.include(includeChildrenRecursively); - if (this.id) { - this.id.include(); - } + if (this.id) this.id.include(context); const hasArguments = this.scope.argumentsVariable.included; for (const param of this.params) { if (!(param instanceof Identifier) || hasArguments) { - param.include(includeChildrenRecursively); + param.include(context, includeChildrenRecursively); } } + const { breakFlow } = context; + context.breakFlow = BREAKFLOW_NONE; + this.body.include(context, includeChildrenRecursively); + context.breakFlow = breakFlow; } - includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void { - this.scope.includeCallArguments(args); + includeCallArguments(context: InclusionContext, args: (ExpressionNode | SpreadElement)[]): void { + this.scope.includeCallArguments(context, args); } initialise() { diff --git a/src/ast/nodes/shared/MultiExpression.ts b/src/ast/nodes/shared/MultiExpression.ts index 4dbbaf7b345..89abd4abb71 100644 --- a/src/ast/nodes/shared/MultiExpression.ts +++ b/src/ast/nodes/shared/MultiExpression.ts @@ -1,11 +1,9 @@ -import CallOptions from '../../CallOptions'; +import { CallOptions } from '../../CallOptions'; import { DeoptimizableEntity } from '../../DeoptimizableEntity'; -import { ExecutionPathOptions } from '../../ExecutionPathOptions'; -import { ImmutableEntityPathTracker } from '../../utils/ImmutableEntityPathTracker'; -import { LiteralValueOrUnknown, ObjectPath, UNKNOWN_VALUE } from '../../values'; -import SpreadElement from '../SpreadElement'; +import { HasEffectsContext } from '../../ExecutionContext'; +import { ObjectPath, PathTracker } from '../../utils/PathTracker'; +import { LiteralValueOrUnknown, UnknownValue } from '../../values'; import { ExpressionEntity } from './Expression'; -import { ExpressionNode } from './Node'; export class MultiExpression implements ExpressionEntity { included = false; @@ -23,12 +21,12 @@ export class MultiExpression implements ExpressionEntity { } getLiteralValueAtPath(): LiteralValueOrUnknown { - return UNKNOWN_VALUE; + return UnknownValue; } getReturnExpressionWhenCalledAtPath( path: ObjectPath, - recursionTracker: ImmutableEntityPathTracker, + recursionTracker: PathTracker, origin: DeoptimizableEntity ): ExpressionEntity { return new MultiExpression( @@ -38,16 +36,16 @@ export class MultiExpression implements ExpressionEntity { ); } - hasEffectsWhenAccessedAtPath(path: ObjectPath, options: ExecutionPathOptions): boolean { + hasEffectsWhenAccessedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { for (const expression of this.expressions) { - if (expression.hasEffectsWhenAccessedAtPath(path, options)) return true; + if (expression.hasEffectsWhenAccessedAtPath(path, context)) return true; } return false; } - hasEffectsWhenAssignedAtPath(path: ObjectPath, options: ExecutionPathOptions): boolean { + hasEffectsWhenAssignedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { for (const expression of this.expressions) { - if (expression.hasEffectsWhenAssignedAtPath(path, options)) return true; + if (expression.hasEffectsWhenAssignedAtPath(path, context)) return true; } return false; } @@ -55,19 +53,15 @@ export class MultiExpression implements ExpressionEntity { hasEffectsWhenCalledAtPath( path: ObjectPath, callOptions: CallOptions, - options: ExecutionPathOptions + context: HasEffectsContext ): boolean { for (const expression of this.expressions) { - if (expression.hasEffectsWhenCalledAtPath(path, callOptions, options)) return true; + if (expression.hasEffectsWhenCalledAtPath(path, callOptions, context)) return true; } return false; } include(): void {} - includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void { - for (const expression of this.expressions) { - expression.includeCallArguments(args); - } - } + includeCallArguments(): void {} } diff --git a/src/ast/nodes/shared/Node.ts b/src/ast/nodes/shared/Node.ts index 24409790dc2..6e5842aabd3 100644 --- a/src/ast/nodes/shared/Node.ts +++ b/src/ast/nodes/shared/Node.ts @@ -2,14 +2,18 @@ import { locate } from 'locate-character'; import MagicString from 'magic-string'; import { AstContext, CommentDescription } from '../../../Module'; import { NodeRenderOptions, RenderOptions } from '../../../utils/renderHelpers'; -import CallOptions from '../../CallOptions'; +import { CallOptions } from '../../CallOptions'; import { DeoptimizableEntity } from '../../DeoptimizableEntity'; import { Entity } from '../../Entity'; -import { ExecutionPathOptions } from '../../ExecutionPathOptions'; +import { + createHasEffectsContext, + HasEffectsContext, + InclusionContext +} from '../../ExecutionContext'; import { getAndCreateKeys, keys } from '../../keys'; import ChildScope from '../../scopes/ChildScope'; -import { ImmutableEntityPathTracker } from '../../utils/ImmutableEntityPathTracker'; -import { LiteralValueOrUnknown, ObjectPath, UNKNOWN_EXPRESSION, UNKNOWN_VALUE } from '../../values'; +import { ObjectPath, PathTracker } from '../../utils/PathTracker'; +import { LiteralValueOrUnknown, UNKNOWN_EXPRESSION, UnknownValue } from '../../values'; import LocalVariable from '../../variables/LocalVariable'; import Variable from '../../variables/Variable'; import SpreadElement from '../SpreadElement'; @@ -52,21 +56,24 @@ export interface Node extends Entity { * which only have an effect if their surrounding loop or switch statement is included. * The options pass on information like this about the current execution path. */ - hasEffects(options: ExecutionPathOptions): boolean; + hasEffects(context: HasEffectsContext): boolean; /** * Includes the node in the bundle. If the flag is not set, children are usually included * if they are necessary for this node (e.g. a function body) or if they have effects. * Necessary variables need to be included as well. */ - include(includeChildrenRecursively: IncludeChildren): void; + include(context: InclusionContext, includeChildrenRecursively: IncludeChildren): void; /** * Alternative version of include to override the default behaviour of * declarations to only include nodes for declarators that have an effect. Necessary * for for-loops that do not use a declared loop variable. */ - includeWithAllDeclaredVariables(includeChildrenRecursively: IncludeChildren): void; + includeWithAllDeclaredVariables( + includeChildrenRecursively: IncludeChildren, + context: InclusionContext + ): void; render(code: MagicString, options: RenderOptions, nodeRenderOptions?: NodeRenderOptions): void; /** @@ -75,15 +82,13 @@ export interface Node extends Entity { * visits as the inclusion of additional variables may require the inclusion of more child * nodes in e.g. block statements. */ - shouldBeIncluded(): boolean; + shouldBeIncluded(context: InclusionContext): boolean; } export interface StatementNode extends Node {} export interface ExpressionNode extends ExpressionEntity, Node {} -const NEW_EXECUTION_PATH = ExecutionPathOptions.create(); - export class NodeBase implements ExpressionNode { context: AstContext; end!: number; @@ -142,72 +147,75 @@ export class NodeBase implements ExpressionNode { getLiteralValueAtPath( _path: ObjectPath, - _recursionTracker: ImmutableEntityPathTracker, + _recursionTracker: PathTracker, _origin: DeoptimizableEntity ): LiteralValueOrUnknown { - return UNKNOWN_VALUE; + return UnknownValue; } getReturnExpressionWhenCalledAtPath( _path: ObjectPath, - _recursionTracker: ImmutableEntityPathTracker, + _recursionTracker: PathTracker, _origin: DeoptimizableEntity ): ExpressionEntity { return UNKNOWN_EXPRESSION; } - hasEffects(options: ExecutionPathOptions): boolean { + hasEffects(context: HasEffectsContext): boolean { for (const key of this.keys) { const value = (this as GenericEsTreeNode)[key]; if (value === null || key === 'annotations') continue; if (Array.isArray(value)) { for (const child of value) { - if (child !== null && child.hasEffects(options)) return true; + if (child !== null && child.hasEffects(context)) return true; } - } else if (value.hasEffects(options)) return true; + } else if (value.hasEffects(context)) return true; } return false; } - hasEffectsWhenAccessedAtPath(path: ObjectPath, _options: ExecutionPathOptions) { + hasEffectsWhenAccessedAtPath(path: ObjectPath, _context: HasEffectsContext) { return path.length > 0; } - hasEffectsWhenAssignedAtPath(_path: ObjectPath, _options: ExecutionPathOptions) { + hasEffectsWhenAssignedAtPath(_path: ObjectPath, _context: HasEffectsContext) { return true; } hasEffectsWhenCalledAtPath( _path: ObjectPath, _callOptions: CallOptions, - _options: ExecutionPathOptions + _context: HasEffectsContext ) { return true; } - include(includeChildrenRecursively: IncludeChildren) { + include(context: InclusionContext, includeChildrenRecursively: IncludeChildren) { this.included = true; for (const key of this.keys) { const value = (this as GenericEsTreeNode)[key]; if (value === null || key === 'annotations') continue; if (Array.isArray(value)) { for (const child of value) { - if (child !== null) child.include(includeChildrenRecursively); + if (child !== null) child.include(context, includeChildrenRecursively); } } else { - value.include(includeChildrenRecursively); + value.include(context, includeChildrenRecursively); } } } - includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void { + includeCallArguments(context: InclusionContext, args: (ExpressionNode | SpreadElement)[]): void { for (const arg of args) { - arg.include(false); + arg.include(context, false); } } - includeWithAllDeclaredVariables(includeChildrenRecursively: IncludeChildren) { - this.include(includeChildrenRecursively); + includeWithAllDeclaredVariables( + includeChildrenRecursively: IncludeChildren, + context: InclusionContext + ) { + this.include(context, includeChildrenRecursively); } /** @@ -268,8 +276,8 @@ export class NodeBase implements ExpressionNode { } } - shouldBeIncluded(): boolean { - return this.included || this.hasEffects(NEW_EXECUTION_PATH); + shouldBeIncluded(context: InclusionContext): boolean { + return this.included || (!context.breakFlow && this.hasEffects(createHasEffectsContext())); } toString() { diff --git a/src/ast/nodes/shared/knownGlobals.ts b/src/ast/nodes/shared/knownGlobals.ts index 7bb41b60b81..85fe83b4a91 100644 --- a/src/ast/nodes/shared/knownGlobals.ts +++ b/src/ast/nodes/shared/knownGlobals.ts @@ -1,4 +1,4 @@ -import { ObjectPath } from '../../values'; +import { ObjectPath } from '../../utils/PathTracker'; const ValueProperties = Symbol('Value Properties'); diff --git a/src/ast/scopes/BlockScope.ts b/src/ast/scopes/BlockScope.ts index 494d7979924..d08b6767751 100644 --- a/src/ast/scopes/BlockScope.ts +++ b/src/ast/scopes/BlockScope.ts @@ -10,10 +10,15 @@ export default class BlockScope extends ChildScope { identifier: Identifier, context: AstContext, init: ExpressionEntity | null = null, - isHoisted = false + isHoisted: boolean | 'function' ): LocalVariable { if (isHoisted) { - return this.parent.addDeclaration(identifier, context, UNKNOWN_EXPRESSION, true); + return this.parent.addDeclaration( + identifier, + context, + isHoisted === 'function' ? init : UNKNOWN_EXPRESSION, + isHoisted + ); } else { return super.addDeclaration(identifier, context, init, false); } diff --git a/src/ast/scopes/CatchScope.ts b/src/ast/scopes/CatchScope.ts index 109d3da8f25..08e977006d8 100644 --- a/src/ast/scopes/CatchScope.ts +++ b/src/ast/scopes/CatchScope.ts @@ -8,11 +8,11 @@ export default class CatchScope extends ParameterScope { addDeclaration( identifier: Identifier, context: AstContext, - init: ExpressionEntity | null = null, - isHoisted = false + init: ExpressionEntity | null, + isHoisted: boolean | 'function' ): LocalVariable { if (isHoisted) { - return this.parent.addDeclaration(identifier, context, init, true); + return this.parent.addDeclaration(identifier, context, init, isHoisted); } else { return super.addDeclaration(identifier, context, init, false); } diff --git a/src/ast/scopes/FunctionScope.ts b/src/ast/scopes/FunctionScope.ts index fa050708d34..7519f8036ff 100644 --- a/src/ast/scopes/FunctionScope.ts +++ b/src/ast/scopes/FunctionScope.ts @@ -1,9 +1,7 @@ import { AstContext } from '../../Module'; -import CallOptions from '../CallOptions'; -import { ExecutionPathOptions } from '../ExecutionPathOptions'; +import { InclusionContext } from '../ExecutionContext'; import { ExpressionNode } from '../nodes/shared/Node'; import SpreadElement from '../nodes/SpreadElement'; -import { UNKNOWN_EXPRESSION, UnknownObjectExpression } from '../values'; import ArgumentsVariable from '../variables/ArgumentsVariable'; import ThisVariable from '../variables/ThisVariable'; import ChildScope from './ChildScope'; @@ -23,22 +21,12 @@ export default class FunctionScope extends ReturnValueScope { return this; } - getOptionsWhenCalledWith( - { withNew }: CallOptions, - options: ExecutionPathOptions - ): ExecutionPathOptions { - return options.replaceVariableInit( - this.thisVariable, - withNew ? new UnknownObjectExpression() : UNKNOWN_EXPRESSION - ); - } - - includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void { - super.includeCallArguments(args); + includeCallArguments(context: InclusionContext, args: (ExpressionNode | SpreadElement)[]): void { + super.includeCallArguments(context, args); if (this.argumentsVariable.included) { for (const arg of args) { if (!arg.included) { - arg.include(false); + arg.include(context, false); } } } diff --git a/src/ast/scopes/ParameterScope.ts b/src/ast/scopes/ParameterScope.ts index 316129816a1..1faecfddef7 100644 --- a/src/ast/scopes/ParameterScope.ts +++ b/src/ast/scopes/ParameterScope.ts @@ -1,4 +1,5 @@ import { AstContext } from '../../Module'; +import { InclusionContext } from '../ExecutionContext'; import Identifier from '../nodes/Identifier'; import { ExpressionNode } from '../nodes/shared/Node'; import SpreadElement from '../nodes/SpreadElement'; @@ -46,7 +47,7 @@ export default class ParameterScope extends ChildScope { this.hasRest = hasRest; } - includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void { + includeCallArguments(context: InclusionContext, args: (ExpressionNode | SpreadElement)[]): void { let calledFromTryStatement = false; let argIncluded = false; const restParam = this.hasRest && this.parameters[this.parameters.length - 1]; @@ -64,11 +65,11 @@ export default class ParameterScope extends ChildScope { } } } - if (!argIncluded && arg.shouldBeIncluded()) { + if (!argIncluded && arg.shouldBeIncluded(context)) { argIncluded = true; } if (argIncluded) { - arg.include(calledFromTryStatement); + arg.include(context, calledFromTryStatement); } } } diff --git a/src/ast/scopes/ReturnValueScope.ts b/src/ast/scopes/ReturnValueScope.ts index 030ee03a417..51975004fec 100644 --- a/src/ast/scopes/ReturnValueScope.ts +++ b/src/ast/scopes/ReturnValueScope.ts @@ -1,5 +1,6 @@ import { ExpressionEntity } from '../nodes/shared/Expression'; -import { UNKNOWN_EXPRESSION, UNKNOWN_PATH } from '../values'; +import { UNKNOWN_PATH } from '../utils/PathTracker'; +import { UNKNOWN_EXPRESSION } from '../values'; import ParameterScope from './ParameterScope'; export default class ReturnValueScope extends ParameterScope { diff --git a/src/ast/scopes/Scope.ts b/src/ast/scopes/Scope.ts index e38a5b068bc..9b5b5bd9739 100644 --- a/src/ast/scopes/Scope.ts +++ b/src/ast/scopes/Scope.ts @@ -14,7 +14,7 @@ export default class Scope { identifier: Identifier, context: AstContext, init: ExpressionEntity | null = null, - _isHoisted: boolean + _isHoisted: boolean | 'function' ) { const name = identifier.name; let variable = this.variables.get(name) as LocalVariable; diff --git a/src/ast/utils/EntityPathTracker.ts b/src/ast/utils/EntityPathTracker.ts deleted file mode 100644 index a0929fe0753..00000000000 --- a/src/ast/utils/EntityPathTracker.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { Entity } from '../Entity'; -import { ObjectPath } from '../values'; - -interface TrackedPaths { - paths: { [key: string]: TrackedPaths }; - tracked: boolean; - unknownPath: TrackedPaths | null; -} - -const getNewTrackedPaths = (): TrackedPaths => ({ - paths: Object.create(null), - tracked: false, - unknownPath: null -}); - -export class EntityPathTracker { - entityPaths: Map = new Map(); - - track(entity: Entity, path: ObjectPath): boolean { - let trackedPaths = this.entityPaths.get(entity); - if (!trackedPaths) { - trackedPaths = getNewTrackedPaths(); - this.entityPaths.set(entity, trackedPaths); - } - - let pathIndex = 0, - trackedSubPaths; - while (pathIndex < path.length) { - const key = path[pathIndex]; - if (typeof key === 'string') { - trackedSubPaths = trackedPaths.paths[key]; - if (!trackedSubPaths) { - trackedSubPaths = getNewTrackedPaths(); - trackedPaths.paths[key] = trackedSubPaths; - } - } else { - trackedSubPaths = trackedPaths.unknownPath; - if (!trackedSubPaths) { - trackedSubPaths = getNewTrackedPaths(); - trackedPaths.unknownPath = trackedSubPaths; - } - } - trackedPaths = trackedSubPaths; - pathIndex++; - } - const found = trackedPaths.tracked; - trackedPaths.tracked = true; - return found; - } -} diff --git a/src/ast/utils/ImmutableEntityPathTracker.ts b/src/ast/utils/ImmutableEntityPathTracker.ts deleted file mode 100644 index 9126ac3adac..00000000000 --- a/src/ast/utils/ImmutableEntityPathTracker.ts +++ /dev/null @@ -1,27 +0,0 @@ -import Immutable from 'immutable'; -import { Entity } from '../Entity'; -import { ObjectPath } from '../values'; - -interface RESULT_KEY {} -const RESULT_KEY: RESULT_KEY = {}; -type MapType = Immutable.Map; - -export class ImmutableEntityPathTracker { - entityPaths: MapType; - - constructor(existingEntityPaths: MapType = Immutable.Map()) { - this.entityPaths = existingEntityPaths; - } - - isTracked(entity: Entity, path: ObjectPath): boolean { - return this.entityPaths.getIn([entity, ...path, RESULT_KEY]); - } - - track(entity: Entity, path: ObjectPath): ImmutableEntityPathTracker { - return new ImmutableEntityPathTracker( - this.entityPaths.setIn([entity, ...path, RESULT_KEY], true) - ); - } -} - -export const EMPTY_IMMUTABLE_TRACKER = new ImmutableEntityPathTracker(); diff --git a/src/ast/utils/PathTracker.ts b/src/ast/utils/PathTracker.ts new file mode 100644 index 00000000000..7deba3346d5 --- /dev/null +++ b/src/ast/utils/PathTracker.ts @@ -0,0 +1,31 @@ +import { Entity } from '../Entity'; + +export const UnknownKey = Symbol('Unknown Key'); +export type ObjectPathKey = string | typeof UnknownKey; + +export type ObjectPath = ObjectPathKey[]; +export const EMPTY_PATH: ObjectPath = []; +export const UNKNOWN_PATH: ObjectPath = [UnknownKey]; + +const EntitiesKey = Symbol('Entities'); +interface EntityPaths { + [EntitiesKey]: Set; + [UnknownKey]?: EntityPaths; + [pathSegment: string]: EntityPaths; +} + +export class PathTracker { + entityPaths: EntityPaths = Object.create(null, { [EntitiesKey]: { value: new Set() } }); + + getEntities(path: ObjectPath) { + let currentPaths = this.entityPaths; + for (const pathSegment of path) { + currentPaths = currentPaths[pathSegment] = + currentPaths[pathSegment] || + Object.create(null, { [EntitiesKey]: { value: new Set() } }); + } + return currentPaths[EntitiesKey]; + } +} + +export const EMPTY_IMMUTABLE_TRACKER = new PathTracker(); diff --git a/src/ast/values.ts b/src/ast/values.ts index a218a5ba4bb..3f563c5928d 100644 --- a/src/ast/values.ts +++ b/src/ast/values.ts @@ -1,19 +1,10 @@ -import CallOptions from './CallOptions'; -import { ExecutionPathOptions } from './ExecutionPathOptions'; +import { CallOptions, NO_ARGS } from './CallOptions'; +import { HasEffectsContext, InclusionContext } from './ExecutionContext'; import { LiteralValue } from './nodes/Literal'; import { ExpressionEntity } from './nodes/shared/Expression'; import { ExpressionNode } from './nodes/shared/Node'; import SpreadElement from './nodes/SpreadElement'; - -export interface UnknownKey { - UNKNOWN_KEY: true; -} -export const UNKNOWN_KEY: UnknownKey = { UNKNOWN_KEY: true }; - -export type ObjectPathKey = string | UnknownKey; -export type ObjectPath = ObjectPathKey[]; -export const EMPTY_PATH: ObjectPath = []; -export const UNKNOWN_PATH: ObjectPath = [UNKNOWN_KEY]; +import { EMPTY_PATH, ObjectPath, ObjectPathKey } from './utils/PathTracker'; export interface MemberDescription { callsArgs: number[] | null; @@ -37,28 +28,26 @@ function assembleMemberDescriptions( return Object.create(inheritedDescriptions, memberDescriptions); } -export interface UnknownValue { - UNKNOWN_VALUE: true; -} -export const UNKNOWN_VALUE: UnknownValue = { UNKNOWN_VALUE: true }; -export type LiteralValueOrUnknown = LiteralValue | UnknownValue; +export const UnknownValue = Symbol('Unknown Value'); +export type LiteralValueOrUnknown = LiteralValue | typeof UnknownValue; export const UNKNOWN_EXPRESSION: ExpressionEntity = { deoptimizePath: () => {}, - getLiteralValueAtPath: () => UNKNOWN_VALUE, + getLiteralValueAtPath: () => UnknownValue, getReturnExpressionWhenCalledAtPath: () => UNKNOWN_EXPRESSION, hasEffectsWhenAccessedAtPath: path => path.length > 0, hasEffectsWhenAssignedAtPath: path => path.length > 0, hasEffectsWhenCalledAtPath: () => true, include: () => {}, - includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void { + includeCallArguments(context: InclusionContext, args: (ExpressionNode | SpreadElement)[]): void { for (const arg of args) { - arg.include(false); + arg.include(context, false); } }, included: true, toString: () => '[[UNKNOWN]]' }; + export const UNDEFINED_EXPRESSION: ExpressionEntity = { deoptimizePath: () => {}, getLiteralValueAtPath: () => undefined, @@ -71,6 +60,7 @@ export const UNDEFINED_EXPRESSION: ExpressionEntity = { included: true, toString: () => 'undefined' }; + const returnsUnknown: RawMemberDescription = { value: { callsArgs: null, @@ -91,8 +81,8 @@ export class UnknownArrayExpression implements ExpressionEntity { deoptimizePath() {} - getLiteralValueAtPath() { - return UNKNOWN_VALUE; + getLiteralValueAtPath(): LiteralValueOrUnknown { + return UnknownValue; } getReturnExpressionWhenCalledAtPath(path: ObjectPath) { @@ -113,10 +103,10 @@ export class UnknownArrayExpression implements ExpressionEntity { hasEffectsWhenCalledAtPath( path: ObjectPath, callOptions: CallOptions, - options: ExecutionPathOptions + context: HasEffectsContext ) { if (path.length === 1) { - return hasMemberEffectWhenCalled(arrayMembers, path[0], this.included, callOptions, options); + return hasMemberEffectWhenCalled(arrayMembers, path[0], this.included, callOptions, context); } return true; } @@ -125,9 +115,9 @@ export class UnknownArrayExpression implements ExpressionEntity { this.included = true; } - includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void { + includeCallArguments(context: InclusionContext, args: (ExpressionNode | SpreadElement)[]): void { for (const arg of args) { - arg.include(false); + arg.include(context, false); } } @@ -171,7 +161,7 @@ const callsArgMutatesSelfReturnsArray: RawMemberDescription = { const UNKNOWN_LITERAL_BOOLEAN: ExpressionEntity = { deoptimizePath: () => {}, - getLiteralValueAtPath: () => UNKNOWN_VALUE, + getLiteralValueAtPath: () => UnknownValue, getReturnExpressionWhenCalledAtPath: path => { if (path.length === 1) { return getMemberReturnExpressionWhenCalled(literalBooleanMembers, path[0]); @@ -188,9 +178,9 @@ const UNKNOWN_LITERAL_BOOLEAN: ExpressionEntity = { return true; }, include: () => {}, - includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void { + includeCallArguments(context: InclusionContext, args: (ExpressionNode | SpreadElement)[]): void { for (const arg of args) { - arg.include(false); + arg.include(context, false); } }, included: true, @@ -216,7 +206,7 @@ const callsArgReturnsBoolean: RawMemberDescription = { const UNKNOWN_LITERAL_NUMBER: ExpressionEntity = { deoptimizePath: () => {}, - getLiteralValueAtPath: () => UNKNOWN_VALUE, + getLiteralValueAtPath: () => UnknownValue, getReturnExpressionWhenCalledAtPath: path => { if (path.length === 1) { return getMemberReturnExpressionWhenCalled(literalNumberMembers, path[0]); @@ -233,9 +223,9 @@ const UNKNOWN_LITERAL_NUMBER: ExpressionEntity = { return true; }, include: () => {}, - includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void { + includeCallArguments(context: InclusionContext, args: (ExpressionNode | SpreadElement)[]): void { for (const arg of args) { - arg.include(false); + arg.include(context, false); } }, included: true, @@ -269,7 +259,7 @@ const callsArgReturnsNumber: RawMemberDescription = { const UNKNOWN_LITERAL_STRING: ExpressionEntity = { deoptimizePath: () => {}, - getLiteralValueAtPath: () => UNKNOWN_VALUE, + getLiteralValueAtPath: () => UnknownValue, getReturnExpressionWhenCalledAtPath: path => { if (path.length === 1) { return getMemberReturnExpressionWhenCalled(literalStringMembers, path[0]); @@ -278,16 +268,16 @@ const UNKNOWN_LITERAL_STRING: ExpressionEntity = { }, hasEffectsWhenAccessedAtPath: path => path.length > 1, hasEffectsWhenAssignedAtPath: path => path.length > 0, - hasEffectsWhenCalledAtPath: (path, callOptions, options) => { + hasEffectsWhenCalledAtPath: (path, callOptions, context) => { if (path.length === 1) { - return hasMemberEffectWhenCalled(literalStringMembers, path[0], true, callOptions, options); + return hasMemberEffectWhenCalled(literalStringMembers, path[0], true, callOptions, context); } return true; }, include: () => {}, - includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void { + includeCallArguments(context: InclusionContext, args: (ExpressionNode | SpreadElement)[]): void { for (const arg of args) { - arg.include(false); + arg.include(context, false); } }, included: true, @@ -308,8 +298,8 @@ export class UnknownObjectExpression implements ExpressionEntity { deoptimizePath() {} - getLiteralValueAtPath() { - return UNKNOWN_VALUE; + getLiteralValueAtPath(): LiteralValueOrUnknown { + return UnknownValue; } getReturnExpressionWhenCalledAtPath(path: ObjectPath) { @@ -330,10 +320,10 @@ export class UnknownObjectExpression implements ExpressionEntity { hasEffectsWhenCalledAtPath( path: ObjectPath, callOptions: CallOptions, - options: ExecutionPathOptions + context: HasEffectsContext ) { if (path.length === 1) { - return hasMemberEffectWhenCalled(objectMembers, path[0], this.included, callOptions, options); + return hasMemberEffectWhenCalled(objectMembers, path[0], this.included, callOptions, context); } return true; } @@ -342,9 +332,9 @@ export class UnknownObjectExpression implements ExpressionEntity { this.included = true; } - includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void { + includeCallArguments(context: InclusionContext, args: (ExpressionNode | SpreadElement)[]): void { for (const arg of args) { - arg.include(false); + arg.include(context, false); } } @@ -468,22 +458,25 @@ export function hasMemberEffectWhenCalled( memberName: ObjectPathKey, parentIncluded: boolean, callOptions: CallOptions, - options: ExecutionPathOptions + context: HasEffectsContext ) { - if (typeof memberName !== 'string' || !members[memberName]) return true; - if (members[memberName].mutatesSelf && parentIncluded) return true; + if ( + typeof memberName !== 'string' || + !members[memberName] || + (members[memberName].mutatesSelf && parentIncluded) + ) + return true; if (!members[memberName].callsArgs) return false; for (const argIndex of members[memberName].callsArgs as number[]) { if ( callOptions.args[argIndex] && callOptions.args[argIndex].hasEffectsWhenCalledAtPath( EMPTY_PATH, - CallOptions.create({ - args: [], - callIdentifier: {}, // make sure the caller is unique to avoid this check being ignored, + { + args: NO_ARGS, withNew: false - }), - options.getHasEffectsWhenCalledOptions() + }, + context ) ) return true; diff --git a/src/ast/variables/ArgumentsVariable.ts b/src/ast/variables/ArgumentsVariable.ts index 05ac3f84a37..b67ff2f0de4 100644 --- a/src/ast/variables/ArgumentsVariable.ts +++ b/src/ast/variables/ArgumentsVariable.ts @@ -1,5 +1,6 @@ import { AstContext } from '../../Module'; -import { ObjectPath, UNKNOWN_EXPRESSION } from '../values'; +import { ObjectPath } from '../utils/PathTracker'; +import { UNKNOWN_EXPRESSION } from '../values'; import LocalVariable from './LocalVariable'; export default class ArgumentsVariable extends LocalVariable { diff --git a/src/ast/variables/GlobalVariable.ts b/src/ast/variables/GlobalVariable.ts index 6aa5d5b9561..a70665c4244 100644 --- a/src/ast/variables/GlobalVariable.ts +++ b/src/ast/variables/GlobalVariable.ts @@ -1,5 +1,5 @@ import { isGlobalMember, isPureGlobal } from '../nodes/shared/knownGlobals'; -import { ObjectPath } from '../values'; +import { ObjectPath } from '../utils/PathTracker'; import Variable from './Variable'; export default class GlobalVariable extends Variable { diff --git a/src/ast/variables/LocalVariable.ts b/src/ast/variables/LocalVariable.ts index 24a01bdbf07..cd4cbd681e6 100644 --- a/src/ast/variables/LocalVariable.ts +++ b/src/ast/variables/LocalVariable.ts @@ -1,23 +1,16 @@ import Module, { AstContext } from '../../Module'; import { markModuleAndImpureDependenciesAsExecuted } from '../../utils/traverseStaticDependencies'; -import CallOptions from '../CallOptions'; +import { CallOptions } from '../CallOptions'; import { DeoptimizableEntity } from '../DeoptimizableEntity'; -import { ExecutionPathOptions } from '../ExecutionPathOptions'; +import { HasEffectsContext, InclusionContext } from '../ExecutionContext'; import ExportDefaultDeclaration from '../nodes/ExportDefaultDeclaration'; import Identifier from '../nodes/Identifier'; import * as NodeType from '../nodes/NodeType'; import { ExpressionEntity } from '../nodes/shared/Expression'; import { ExpressionNode, Node } from '../nodes/shared/Node'; import SpreadElement from '../nodes/SpreadElement'; -import { EntityPathTracker } from '../utils/EntityPathTracker'; -import { ImmutableEntityPathTracker } from '../utils/ImmutableEntityPathTracker'; -import { - LiteralValueOrUnknown, - ObjectPath, - UNKNOWN_EXPRESSION, - UNKNOWN_PATH, - UNKNOWN_VALUE -} from '../values'; +import { ObjectPath, PathTracker, UNKNOWN_PATH } from '../utils/PathTracker'; +import { LiteralValueOrUnknown, UNKNOWN_EXPRESSION, UnknownValue } from '../values'; import Variable from './Variable'; // To avoid infinite recursions @@ -32,7 +25,7 @@ export default class LocalVariable extends Variable { // Caching and deoptimization: // We track deoptimization when we do not return something unknown - private deoptimizationTracker: EntityPathTracker; + private deoptimizationTracker: PathTracker; private expressionsToBeDeoptimized: DeoptimizableEntity[] = []; constructor( @@ -70,109 +63,99 @@ export default class LocalVariable extends Variable { } deoptimizePath(path: ObjectPath) { - if (path.length > MAX_PATH_DEPTH) return; - if (!(this.isReassigned || this.deoptimizationTracker.track(this, path))) { - if (path.length === 0) { - if (!this.isReassigned) { - this.isReassigned = true; - for (const expression of this.expressionsToBeDeoptimized) { - expression.deoptimizeCache(); - } - if (this.init) { - this.init.deoptimizePath(UNKNOWN_PATH); - } + if (path.length > MAX_PATH_DEPTH || this.isReassigned) return; + const trackedEntities = this.deoptimizationTracker.getEntities(path); + if (trackedEntities.has(this)) return; + trackedEntities.add(this); + if (path.length === 0) { + if (!this.isReassigned) { + this.isReassigned = true; + for (const expression of this.expressionsToBeDeoptimized) { + expression.deoptimizeCache(); + } + if (this.init) { + this.init.deoptimizePath(UNKNOWN_PATH); } - } else if (this.init) { - this.init.deoptimizePath(path); } + } else if (this.init) { + this.init.deoptimizePath(path); } } getLiteralValueAtPath( path: ObjectPath, - recursionTracker: ImmutableEntityPathTracker, + recursionTracker: PathTracker, origin: DeoptimizableEntity ): LiteralValueOrUnknown { - if ( - this.isReassigned || - !this.init || - path.length > MAX_PATH_DEPTH || - recursionTracker.isTracked(this.init, path) - ) { - return UNKNOWN_VALUE; + if (this.isReassigned || !this.init || path.length > MAX_PATH_DEPTH) { + return UnknownValue; + } + const trackedEntities = recursionTracker.getEntities(path); + if (trackedEntities.has(this.init)) { + return UnknownValue; } this.expressionsToBeDeoptimized.push(origin); - return this.init.getLiteralValueAtPath(path, recursionTracker.track(this.init, path), origin); + trackedEntities.add(this.init); + const value = this.init.getLiteralValueAtPath(path, recursionTracker, origin); + trackedEntities.delete(this.init); + return value; } getReturnExpressionWhenCalledAtPath( path: ObjectPath, - recursionTracker: ImmutableEntityPathTracker, + recursionTracker: PathTracker, origin: DeoptimizableEntity ): ExpressionEntity { - if ( - this.isReassigned || - !this.init || - path.length > MAX_PATH_DEPTH || - recursionTracker.isTracked(this.init, path) - ) { + if (this.isReassigned || !this.init || path.length > MAX_PATH_DEPTH) { + return UNKNOWN_EXPRESSION; + } + const trackedEntities = recursionTracker.getEntities(path); + if (trackedEntities.has(this.init)) { return UNKNOWN_EXPRESSION; } this.expressionsToBeDeoptimized.push(origin); - return this.init.getReturnExpressionWhenCalledAtPath( - path, - recursionTracker.track(this.init, path), - origin - ); + trackedEntities.add(this.init); + const value = this.init.getReturnExpressionWhenCalledAtPath(path, recursionTracker, origin); + trackedEntities.delete(this.init); + return value; } - hasEffectsWhenAccessedAtPath(path: ObjectPath, options: ExecutionPathOptions) { + hasEffectsWhenAccessedAtPath(path: ObjectPath, context: HasEffectsContext) { if (path.length === 0) return false; - return ( - this.isReassigned || - path.length > MAX_PATH_DEPTH || - ((this.init && - !options.hasNodeBeenAccessedAtPath(path, this.init) && - this.init.hasEffectsWhenAccessedAtPath( - path, - options.addAccessedNodeAtPath(path, this.init) - )) as boolean) - ); + if (this.isReassigned || path.length > MAX_PATH_DEPTH) return true; + const trackedExpressions = context.accessed.getEntities(path); + if (trackedExpressions.has(this)) return false; + trackedExpressions.add(this); + return (this.init && this.init.hasEffectsWhenAccessedAtPath(path, context)) as boolean; } - hasEffectsWhenAssignedAtPath(path: ObjectPath, options: ExecutionPathOptions) { + hasEffectsWhenAssignedAtPath(path: ObjectPath, context: HasEffectsContext) { if (this.included || path.length > MAX_PATH_DEPTH) return true; if (path.length === 0) return false; - return ( - this.isReassigned || - ((this.init && - !options.hasNodeBeenAssignedAtPath(path, this.init) && - this.init.hasEffectsWhenAssignedAtPath( - path, - options.addAssignedNodeAtPath(path, this.init) - )) as boolean) - ); + if (this.isReassigned) return true; + const trackedExpressions = context.assigned.getEntities(path); + if (trackedExpressions.has(this)) return false; + trackedExpressions.add(this); + return (this.init && this.init.hasEffectsWhenAssignedAtPath(path, context)) as boolean; } hasEffectsWhenCalledAtPath( path: ObjectPath, callOptions: CallOptions, - options: ExecutionPathOptions + context: HasEffectsContext ) { - if (path.length > MAX_PATH_DEPTH) return true; - return ( - this.isReassigned || - ((this.init && - !options.hasNodeBeenCalledAtPathWithOptions(path, this.init, callOptions) && - this.init.hasEffectsWhenCalledAtPath( - path, - callOptions, - options.addCalledNodeAtPathWithOptions(path, this.init, callOptions) - )) as boolean) - ); + if (path.length > MAX_PATH_DEPTH || this.isReassigned) return true; + const trackedExpressions = (callOptions.withNew + ? context.instantiated + : context.called + ).getEntities(path); + if (trackedExpressions.has(this)) return false; + trackedExpressions.add(this); + return (this.init && + this.init.hasEffectsWhenCalledAtPath(path, callOptions, context)) as boolean; } - include() { + include(context: InclusionContext) { if (!this.included) { this.included = true; if (!this.module.isExecuted) { @@ -180,7 +163,7 @@ export default class LocalVariable extends Variable { } for (const declaration of this.declarations) { // If node is a default export, it can save a tree-shaking run to include the full declaration now - if (!declaration.included) declaration.include(false); + if (!declaration.included) declaration.include(context, false); let node = declaration.parent as Node; while (!node.included) { // We do not want to properly include parents in case they are part of a dead branch @@ -193,13 +176,13 @@ export default class LocalVariable extends Variable { } } - includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void { + includeCallArguments(context: InclusionContext, args: (ExpressionNode | SpreadElement)[]): void { if (this.isReassigned) { for (const arg of args) { - arg.include(false); + arg.include(context, false); } } else if (this.init) { - this.init.includeCallArguments(args); + this.init.includeCallArguments(context, args); } } diff --git a/src/ast/variables/NamespaceVariable.ts b/src/ast/variables/NamespaceVariable.ts index 10b36bdc9fb..b72eee28c16 100644 --- a/src/ast/variables/NamespaceVariable.ts +++ b/src/ast/variables/NamespaceVariable.ts @@ -1,8 +1,9 @@ import Module, { AstContext } from '../../Module'; import { RenderOptions } from '../../utils/renderHelpers'; import { RESERVED_NAMES } from '../../utils/reservedNames'; +import { InclusionContext } from '../ExecutionContext'; import Identifier from '../nodes/Identifier'; -import { UNKNOWN_PATH } from '../values'; +import { UNKNOWN_PATH } from '../utils/PathTracker'; import Variable from './Variable'; export default class NamespaceVariable extends Variable { @@ -36,7 +37,7 @@ export default class NamespaceVariable extends Variable { } } - include() { + include(context: InclusionContext) { if (!this.included) { if (this.containsExternalNamespace) { this.context.error( @@ -57,10 +58,10 @@ export default class NamespaceVariable extends Variable { } if (this.context.preserveModules) { for (const memberName of Object.keys(this.memberVariables)) - this.memberVariables[memberName].include(); + this.memberVariables[memberName].include(context); } else { for (const memberName of Object.keys(this.memberVariables)) - this.context.includeVariable(this.memberVariables[memberName]); + this.context.includeVariable(context, this.memberVariables[memberName]); } } } @@ -92,7 +93,7 @@ export default class NamespaceVariable extends Variable { }); members.unshift(`${t}__proto__:${_}null`); - + if (options.namespaceToStringTag) { members.unshift(`${t}[Symbol.toStringTag]:${_}'Module'`); } diff --git a/src/ast/variables/ThisVariable.ts b/src/ast/variables/ThisVariable.ts index a65c9706d7f..f9ec5a0d4eb 100644 --- a/src/ast/variables/ThisVariable.ts +++ b/src/ast/variables/ThisVariable.ts @@ -1,8 +1,9 @@ import { AstContext } from '../../Module'; -import CallOptions from '../CallOptions'; -import { ExecutionPathOptions } from '../ExecutionPathOptions'; +import { CallOptions } from '../CallOptions'; +import { HasEffectsContext } from '../ExecutionContext'; import { ExpressionEntity } from '../nodes/shared/Expression'; -import { LiteralValueOrUnknown, ObjectPath, UNKNOWN_EXPRESSION, UNKNOWN_VALUE } from '../values'; +import { ObjectPath } from '../utils/PathTracker'; +import { LiteralValueOrUnknown, UNKNOWN_EXPRESSION, UnknownValue } from '../values'; import LocalVariable from './LocalVariable'; export default class ThisVariable extends LocalVariable { @@ -10,36 +11,36 @@ export default class ThisVariable extends LocalVariable { super('this', null, null, context); } - _getInit(options: ExecutionPathOptions): ExpressionEntity { - return options.getReplacedVariableInit(this) || UNKNOWN_EXPRESSION; - } - getLiteralValueAtPath(): LiteralValueOrUnknown { - return UNKNOWN_VALUE; + return UnknownValue; } - hasEffectsWhenAccessedAtPath(path: ObjectPath, options: ExecutionPathOptions) { + hasEffectsWhenAccessedAtPath(path: ObjectPath, context: HasEffectsContext) { return ( - this._getInit(options).hasEffectsWhenAccessedAtPath(path, options) || - super.hasEffectsWhenAccessedAtPath(path, options) + this.getInit(context).hasEffectsWhenAccessedAtPath(path, context) || + super.hasEffectsWhenAccessedAtPath(path, context) ); } - hasEffectsWhenAssignedAtPath(path: ObjectPath, options: ExecutionPathOptions) { + hasEffectsWhenAssignedAtPath(path: ObjectPath, context: HasEffectsContext) { return ( - this._getInit(options).hasEffectsWhenAssignedAtPath(path, options) || - super.hasEffectsWhenAssignedAtPath(path, options) + this.getInit(context).hasEffectsWhenAssignedAtPath(path, context) || + super.hasEffectsWhenAssignedAtPath(path, context) ); } hasEffectsWhenCalledAtPath( path: ObjectPath, callOptions: CallOptions, - options: ExecutionPathOptions + context: HasEffectsContext ) { return ( - this._getInit(options).hasEffectsWhenCalledAtPath(path, callOptions, options) || - super.hasEffectsWhenCalledAtPath(path, callOptions, options) + this.getInit(context).hasEffectsWhenCalledAtPath(path, callOptions, context) || + super.hasEffectsWhenCalledAtPath(path, callOptions, context) ); } + + private getInit(context: HasEffectsContext): ExpressionEntity { + return context.replacedVariableInits.get(this) || UNKNOWN_EXPRESSION; + } } diff --git a/src/ast/variables/Variable.ts b/src/ast/variables/Variable.ts index d8d61042286..323059f48f3 100644 --- a/src/ast/variables/Variable.ts +++ b/src/ast/variables/Variable.ts @@ -1,14 +1,14 @@ import ExternalModule from '../../ExternalModule'; import Module from '../../Module'; -import CallOptions from '../CallOptions'; +import { CallOptions } from '../CallOptions'; import { DeoptimizableEntity } from '../DeoptimizableEntity'; -import { ExecutionPathOptions } from '../ExecutionPathOptions'; +import { HasEffectsContext, InclusionContext } from '../ExecutionContext'; import Identifier from '../nodes/Identifier'; import { ExpressionEntity } from '../nodes/shared/Expression'; import { ExpressionNode } from '../nodes/shared/Node'; import SpreadElement from '../nodes/SpreadElement'; -import { ImmutableEntityPathTracker } from '../utils/ImmutableEntityPathTracker'; -import { LiteralValueOrUnknown, ObjectPath, UNKNOWN_EXPRESSION, UNKNOWN_VALUE } from '../values'; +import { ObjectPath, PathTracker } from '../utils/PathTracker'; +import { LiteralValueOrUnknown, UNKNOWN_EXPRESSION, UnknownValue } from '../values'; export default class Variable implements ExpressionEntity { alwaysRendered = false; @@ -41,10 +41,10 @@ export default class Variable implements ExpressionEntity { getLiteralValueAtPath( _path: ObjectPath, - _recursionTracker: ImmutableEntityPathTracker, + _recursionTracker: PathTracker, _origin: DeoptimizableEntity ): LiteralValueOrUnknown { - return UNKNOWN_VALUE; + return UnknownValue; } getName(): string { @@ -54,24 +54,24 @@ export default class Variable implements ExpressionEntity { getReturnExpressionWhenCalledAtPath( _path: ObjectPath, - _recursionTracker: ImmutableEntityPathTracker, + _recursionTracker: PathTracker, _origin: DeoptimizableEntity ): ExpressionEntity { return UNKNOWN_EXPRESSION; } - hasEffectsWhenAccessedAtPath(path: ObjectPath, _options: ExecutionPathOptions) { + hasEffectsWhenAccessedAtPath(path: ObjectPath, _context: HasEffectsContext) { return path.length > 0; } - hasEffectsWhenAssignedAtPath(_path: ObjectPath, _options: ExecutionPathOptions) { + hasEffectsWhenAssignedAtPath(_path: ObjectPath, _context: HasEffectsContext) { return true; } hasEffectsWhenCalledAtPath( _path: ObjectPath, _callOptions: CallOptions, - _options: ExecutionPathOptions + _context: HasEffectsContext ) { return true; } @@ -82,13 +82,13 @@ export default class Variable implements ExpressionEntity { * previously. * Once a variable is included, it should take care all its declarations are included. */ - include() { + include(_context: InclusionContext) { this.included = true; } - includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void { + includeCallArguments(context: InclusionContext, args: (ExpressionNode | SpreadElement)[]): void { for (const arg of args) { - arg.include(false); + arg.include(context, false); } } diff --git a/test/form/samples/absolute-path-resolver/_expected/es.js b/test/form/samples/absolute-path-resolver/_expected.js similarity index 100% rename from test/form/samples/absolute-path-resolver/_expected/es.js rename to test/form/samples/absolute-path-resolver/_expected.js diff --git a/test/form/samples/absolute-path-resolver/_expected/amd.js b/test/form/samples/absolute-path-resolver/_expected/amd.js deleted file mode 100644 index 308b0b8c7bc..00000000000 --- a/test/form/samples/absolute-path-resolver/_expected/amd.js +++ /dev/null @@ -1,10 +0,0 @@ -define(function () { 'use strict'; - - var a = () => { - console.log('props'); - }; - - a(); - a(); - -}); diff --git a/test/form/samples/absolute-path-resolver/_expected/cjs.js b/test/form/samples/absolute-path-resolver/_expected/cjs.js deleted file mode 100644 index 4b1f7113031..00000000000 --- a/test/form/samples/absolute-path-resolver/_expected/cjs.js +++ /dev/null @@ -1,8 +0,0 @@ -'use strict'; - -var a = () => { - console.log('props'); -}; - -a(); -a(); diff --git a/test/form/samples/absolute-path-resolver/_expected/iife.js b/test/form/samples/absolute-path-resolver/_expected/iife.js deleted file mode 100644 index 46c38fba33b..00000000000 --- a/test/form/samples/absolute-path-resolver/_expected/iife.js +++ /dev/null @@ -1,11 +0,0 @@ -(function () { - 'use strict'; - - var a = () => { - console.log('props'); - }; - - a(); - a(); - -}()); diff --git a/test/form/samples/absolute-path-resolver/_expected/system.js b/test/form/samples/absolute-path-resolver/_expected/system.js deleted file mode 100644 index 349af935327..00000000000 --- a/test/form/samples/absolute-path-resolver/_expected/system.js +++ /dev/null @@ -1,15 +0,0 @@ -System.register([], function () { - 'use strict'; - return { - execute: function () { - - var a = () => { - console.log('props'); - }; - - a(); - a(); - - } - }; -}); diff --git a/test/form/samples/absolute-path-resolver/_expected/umd.js b/test/form/samples/absolute-path-resolver/_expected/umd.js deleted file mode 100644 index 636759fa2a8..00000000000 --- a/test/form/samples/absolute-path-resolver/_expected/umd.js +++ /dev/null @@ -1,13 +0,0 @@ -(function (factory) { - typeof define === 'function' && define.amd ? define(factory) : - factory(); -}(function () { 'use strict'; - - var a = () => { - console.log('props'); - }; - - a(); - a(); - -})); diff --git a/test/form/samples/arrow-function-call-parameters/_expected/es.js b/test/form/samples/arrow-function-call-parameters/_expected.js similarity index 100% rename from test/form/samples/arrow-function-call-parameters/_expected/es.js rename to test/form/samples/arrow-function-call-parameters/_expected.js diff --git a/test/form/samples/arrow-function-call-parameters/_expected/amd.js b/test/form/samples/arrow-function-call-parameters/_expected/amd.js deleted file mode 100644 index 3e20fc5fe5f..00000000000 --- a/test/form/samples/arrow-function-call-parameters/_expected/amd.js +++ /dev/null @@ -1,33 +0,0 @@ -define(function () { 'use strict'; - - const callArg = arg => arg(); - callArg( () => console.log( 'effect' ) ); - - const assignArg = arg => arg.foo.bar = 1; - assignArg( {} ); - - const returnArg = arg => arg; - returnArg( () => console.log( 'effect' ) )(); - - const returnArg2 = arg => arg; - returnArg2( {} ).foo.bar = 1; - - const returnArg3 = arg => arg; - returnArg3( () => () => console.log( 'effect' ) )()(); - - const returnArgReturn = arg => arg(); - returnArgReturn( () => () => console.log( 'effect' ) )(); - - const returnArgReturn2 = arg => arg(); - returnArgReturn2( () => ({}) ).foo.bar = 1; - - const returnArgReturn3 = arg => arg(); - returnArgReturn3( () => () => () => console.log( 'effect' ) )()(); - - const multiArgument = ( func, obj ) => func( obj ); - multiArgument( obj => obj(), () => console.log( 'effect' ) ); - - const multiArgument2 = ( func, obj ) => func( obj ); - multiArgument2( obj => obj.foo.bar = 1, {} ); - -}); diff --git a/test/form/samples/arrow-function-call-parameters/_expected/cjs.js b/test/form/samples/arrow-function-call-parameters/_expected/cjs.js deleted file mode 100644 index afc702f801c..00000000000 --- a/test/form/samples/arrow-function-call-parameters/_expected/cjs.js +++ /dev/null @@ -1,31 +0,0 @@ -'use strict'; - -const callArg = arg => arg(); -callArg( () => console.log( 'effect' ) ); - -const assignArg = arg => arg.foo.bar = 1; -assignArg( {} ); - -const returnArg = arg => arg; -returnArg( () => console.log( 'effect' ) )(); - -const returnArg2 = arg => arg; -returnArg2( {} ).foo.bar = 1; - -const returnArg3 = arg => arg; -returnArg3( () => () => console.log( 'effect' ) )()(); - -const returnArgReturn = arg => arg(); -returnArgReturn( () => () => console.log( 'effect' ) )(); - -const returnArgReturn2 = arg => arg(); -returnArgReturn2( () => ({}) ).foo.bar = 1; - -const returnArgReturn3 = arg => arg(); -returnArgReturn3( () => () => () => console.log( 'effect' ) )()(); - -const multiArgument = ( func, obj ) => func( obj ); -multiArgument( obj => obj(), () => console.log( 'effect' ) ); - -const multiArgument2 = ( func, obj ) => func( obj ); -multiArgument2( obj => obj.foo.bar = 1, {} ); diff --git a/test/form/samples/arrow-function-call-parameters/_expected/iife.js b/test/form/samples/arrow-function-call-parameters/_expected/iife.js deleted file mode 100644 index bf1dab98c27..00000000000 --- a/test/form/samples/arrow-function-call-parameters/_expected/iife.js +++ /dev/null @@ -1,34 +0,0 @@ -(function () { - 'use strict'; - - const callArg = arg => arg(); - callArg( () => console.log( 'effect' ) ); - - const assignArg = arg => arg.foo.bar = 1; - assignArg( {} ); - - const returnArg = arg => arg; - returnArg( () => console.log( 'effect' ) )(); - - const returnArg2 = arg => arg; - returnArg2( {} ).foo.bar = 1; - - const returnArg3 = arg => arg; - returnArg3( () => () => console.log( 'effect' ) )()(); - - const returnArgReturn = arg => arg(); - returnArgReturn( () => () => console.log( 'effect' ) )(); - - const returnArgReturn2 = arg => arg(); - returnArgReturn2( () => ({}) ).foo.bar = 1; - - const returnArgReturn3 = arg => arg(); - returnArgReturn3( () => () => () => console.log( 'effect' ) )()(); - - const multiArgument = ( func, obj ) => func( obj ); - multiArgument( obj => obj(), () => console.log( 'effect' ) ); - - const multiArgument2 = ( func, obj ) => func( obj ); - multiArgument2( obj => obj.foo.bar = 1, {} ); - -}()); diff --git a/test/form/samples/arrow-function-call-parameters/_expected/system.js b/test/form/samples/arrow-function-call-parameters/_expected/system.js deleted file mode 100644 index d457375a432..00000000000 --- a/test/form/samples/arrow-function-call-parameters/_expected/system.js +++ /dev/null @@ -1,38 +0,0 @@ -System.register([], function () { - 'use strict'; - return { - execute: function () { - - const callArg = arg => arg(); - callArg( () => console.log( 'effect' ) ); - - const assignArg = arg => arg.foo.bar = 1; - assignArg( {} ); - - const returnArg = arg => arg; - returnArg( () => console.log( 'effect' ) )(); - - const returnArg2 = arg => arg; - returnArg2( {} ).foo.bar = 1; - - const returnArg3 = arg => arg; - returnArg3( () => () => console.log( 'effect' ) )()(); - - const returnArgReturn = arg => arg(); - returnArgReturn( () => () => console.log( 'effect' ) )(); - - const returnArgReturn2 = arg => arg(); - returnArgReturn2( () => ({}) ).foo.bar = 1; - - const returnArgReturn3 = arg => arg(); - returnArgReturn3( () => () => () => console.log( 'effect' ) )()(); - - const multiArgument = ( func, obj ) => func( obj ); - multiArgument( obj => obj(), () => console.log( 'effect' ) ); - - const multiArgument2 = ( func, obj ) => func( obj ); - multiArgument2( obj => obj.foo.bar = 1, {} ); - - } - }; -}); diff --git a/test/form/samples/arrow-function-call-parameters/_expected/umd.js b/test/form/samples/arrow-function-call-parameters/_expected/umd.js deleted file mode 100644 index 8cecffec8f2..00000000000 --- a/test/form/samples/arrow-function-call-parameters/_expected/umd.js +++ /dev/null @@ -1,36 +0,0 @@ -(function (factory) { - typeof define === 'function' && define.amd ? define(factory) : - factory(); -}(function () { 'use strict'; - - const callArg = arg => arg(); - callArg( () => console.log( 'effect' ) ); - - const assignArg = arg => arg.foo.bar = 1; - assignArg( {} ); - - const returnArg = arg => arg; - returnArg( () => console.log( 'effect' ) )(); - - const returnArg2 = arg => arg; - returnArg2( {} ).foo.bar = 1; - - const returnArg3 = arg => arg; - returnArg3( () => () => console.log( 'effect' ) )()(); - - const returnArgReturn = arg => arg(); - returnArgReturn( () => () => console.log( 'effect' ) )(); - - const returnArgReturn2 = arg => arg(); - returnArgReturn2( () => ({}) ).foo.bar = 1; - - const returnArgReturn3 = arg => arg(); - returnArgReturn3( () => () => () => console.log( 'effect' ) )()(); - - const multiArgument = ( func, obj ) => func( obj ); - multiArgument( obj => obj(), () => console.log( 'effect' ) ); - - const multiArgument2 = ( func, obj ) => func( obj ); - multiArgument2( obj => obj.foo.bar = 1, {} ); - -})); diff --git a/test/form/samples/arrow-function-return-values/_expected/es.js b/test/form/samples/arrow-function-return-values/_expected.js similarity index 100% rename from test/form/samples/arrow-function-return-values/_expected/es.js rename to test/form/samples/arrow-function-return-values/_expected.js diff --git a/test/form/samples/arrow-function-return-values/_expected/amd.js b/test/form/samples/arrow-function-return-values/_expected/amd.js deleted file mode 100644 index d4407aeed3d..00000000000 --- a/test/form/samples/arrow-function-return-values/_expected/amd.js +++ /dev/null @@ -1,14 +0,0 @@ -define(function () { 'use strict'; - - (() => () => console.log( 'effect' ))()(); - (() => () => () => console.log( 'effect' ))()()(); - const retained1 = () => () => console.log( 'effect' ); - retained1()(); - - (() => { - return () => console.log( 'effect' ); - })()(); - (() => ({ foo: () => console.log( 'effect' ) }))().foo(); - (() => ({ foo: () => ({ bar: () => console.log( 'effect' ) }) }))().foo().bar(); - -}); diff --git a/test/form/samples/arrow-function-return-values/_expected/cjs.js b/test/form/samples/arrow-function-return-values/_expected/cjs.js deleted file mode 100644 index 0a34a9d6ed1..00000000000 --- a/test/form/samples/arrow-function-return-values/_expected/cjs.js +++ /dev/null @@ -1,12 +0,0 @@ -'use strict'; - -(() => () => console.log( 'effect' ))()(); -(() => () => () => console.log( 'effect' ))()()(); -const retained1 = () => () => console.log( 'effect' ); -retained1()(); - -(() => { - return () => console.log( 'effect' ); -})()(); -(() => ({ foo: () => console.log( 'effect' ) }))().foo(); -(() => ({ foo: () => ({ bar: () => console.log( 'effect' ) }) }))().foo().bar(); diff --git a/test/form/samples/arrow-function-return-values/_expected/iife.js b/test/form/samples/arrow-function-return-values/_expected/iife.js deleted file mode 100644 index 60a07d7709f..00000000000 --- a/test/form/samples/arrow-function-return-values/_expected/iife.js +++ /dev/null @@ -1,15 +0,0 @@ -(function () { - 'use strict'; - - (() => () => console.log( 'effect' ))()(); - (() => () => () => console.log( 'effect' ))()()(); - const retained1 = () => () => console.log( 'effect' ); - retained1()(); - - (() => { - return () => console.log( 'effect' ); - })()(); - (() => ({ foo: () => console.log( 'effect' ) }))().foo(); - (() => ({ foo: () => ({ bar: () => console.log( 'effect' ) }) }))().foo().bar(); - -}()); diff --git a/test/form/samples/arrow-function-return-values/_expected/system.js b/test/form/samples/arrow-function-return-values/_expected/system.js deleted file mode 100644 index ade4957f6eb..00000000000 --- a/test/form/samples/arrow-function-return-values/_expected/system.js +++ /dev/null @@ -1,19 +0,0 @@ -System.register([], function () { - 'use strict'; - return { - execute: function () { - - (() => () => console.log( 'effect' ))()(); - (() => () => () => console.log( 'effect' ))()()(); - const retained1 = () => () => console.log( 'effect' ); - retained1()(); - - (() => { - return () => console.log( 'effect' ); - })()(); - (() => ({ foo: () => console.log( 'effect' ) }))().foo(); - (() => ({ foo: () => ({ bar: () => console.log( 'effect' ) }) }))().foo().bar(); - - } - }; -}); diff --git a/test/form/samples/arrow-function-return-values/_expected/umd.js b/test/form/samples/arrow-function-return-values/_expected/umd.js deleted file mode 100644 index ed01b030f26..00000000000 --- a/test/form/samples/arrow-function-return-values/_expected/umd.js +++ /dev/null @@ -1,17 +0,0 @@ -(function (factory) { - typeof define === 'function' && define.amd ? define(factory) : - factory(); -}(function () { 'use strict'; - - (() => () => console.log( 'effect' ))()(); - (() => () => () => console.log( 'effect' ))()()(); - const retained1 = () => () => console.log( 'effect' ); - retained1()(); - - (() => { - return () => console.log( 'effect' ); - })()(); - (() => ({ foo: () => console.log( 'effect' ) }))().foo(); - (() => ({ foo: () => ({ bar: () => console.log( 'effect' ) }) }))().foo().bar(); - -})); diff --git a/test/form/samples/async-function-unused/_expected/es.js b/test/form/samples/async-function-unused/_expected.js similarity index 100% rename from test/form/samples/async-function-unused/_expected/es.js rename to test/form/samples/async-function-unused/_expected.js diff --git a/test/form/samples/async-function-unused/_expected/amd.js b/test/form/samples/async-function-unused/_expected/amd.js deleted file mode 100644 index 2726053a69c..00000000000 --- a/test/form/samples/async-function-unused/_expected/amd.js +++ /dev/null @@ -1,9 +0,0 @@ -define(function () { 'use strict'; - - async function foo () { - return 'foo'; - } - - foo().then( value => console.log( value ) ); - -}); diff --git a/test/form/samples/async-function-unused/_expected/cjs.js b/test/form/samples/async-function-unused/_expected/cjs.js deleted file mode 100644 index 7da92473a21..00000000000 --- a/test/form/samples/async-function-unused/_expected/cjs.js +++ /dev/null @@ -1,7 +0,0 @@ -'use strict'; - -async function foo () { - return 'foo'; -} - -foo().then( value => console.log( value ) ); diff --git a/test/form/samples/async-function-unused/_expected/iife.js b/test/form/samples/async-function-unused/_expected/iife.js deleted file mode 100644 index c61e87ebd7f..00000000000 --- a/test/form/samples/async-function-unused/_expected/iife.js +++ /dev/null @@ -1,10 +0,0 @@ -(function () { - 'use strict'; - - async function foo () { - return 'foo'; - } - - foo().then( value => console.log( value ) ); - -}()); diff --git a/test/form/samples/async-function-unused/_expected/system.js b/test/form/samples/async-function-unused/_expected/system.js deleted file mode 100644 index 54fe9414325..00000000000 --- a/test/form/samples/async-function-unused/_expected/system.js +++ /dev/null @@ -1,14 +0,0 @@ -System.register([], function () { - 'use strict'; - return { - execute: function () { - - async function foo () { - return 'foo'; - } - - foo().then( value => console.log( value ) ); - - } - }; -}); diff --git a/test/form/samples/async-function-unused/_expected/umd.js b/test/form/samples/async-function-unused/_expected/umd.js deleted file mode 100644 index 908abff5886..00000000000 --- a/test/form/samples/async-function-unused/_expected/umd.js +++ /dev/null @@ -1,12 +0,0 @@ -(function (factory) { - typeof define === 'function' && define.amd ? define(factory) : - factory(); -}(function () { 'use strict'; - - async function foo () { - return 'foo'; - } - - foo().then( value => console.log( value ) ); - -})); 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/block-comments/_expected/es.js b/test/form/samples/block-comments/_expected.js similarity index 100% rename from test/form/samples/block-comments/_expected/es.js rename to test/form/samples/block-comments/_expected.js diff --git a/test/form/samples/block-comments/_expected/amd.js b/test/form/samples/block-comments/_expected/amd.js deleted file mode 100644 index 5bf416a7549..00000000000 --- a/test/form/samples/block-comments/_expected/amd.js +++ /dev/null @@ -1,19 +0,0 @@ -define(function () { 'use strict'; - - function foo () { - return embiggen( 6, 7 ); - } - - /** - * Embiggens a number - * @param {number} num - the number to embiggen - * @param {number} factor - the factor to embiggen it by - * @returns {number} - */ - function embiggen ( num, factor ) { - return num * factor; - } - - alert( foo() ); - -}); diff --git a/test/form/samples/block-comments/_expected/cjs.js b/test/form/samples/block-comments/_expected/cjs.js deleted file mode 100644 index df8f15272c5..00000000000 --- a/test/form/samples/block-comments/_expected/cjs.js +++ /dev/null @@ -1,17 +0,0 @@ -'use strict'; - -function foo () { - return embiggen( 6, 7 ); -} - -/** - * Embiggens a number - * @param {number} num - the number to embiggen - * @param {number} factor - the factor to embiggen it by - * @returns {number} - */ -function embiggen ( num, factor ) { - return num * factor; -} - -alert( foo() ); diff --git a/test/form/samples/block-comments/_expected/iife.js b/test/form/samples/block-comments/_expected/iife.js deleted file mode 100644 index f8fbf5226d8..00000000000 --- a/test/form/samples/block-comments/_expected/iife.js +++ /dev/null @@ -1,20 +0,0 @@ -(function () { - 'use strict'; - - function foo () { - return embiggen( 6, 7 ); - } - - /** - * Embiggens a number - * @param {number} num - the number to embiggen - * @param {number} factor - the factor to embiggen it by - * @returns {number} - */ - function embiggen ( num, factor ) { - return num * factor; - } - - alert( foo() ); - -}()); diff --git a/test/form/samples/block-comments/_expected/system.js b/test/form/samples/block-comments/_expected/system.js deleted file mode 100644 index 6a133495893..00000000000 --- a/test/form/samples/block-comments/_expected/system.js +++ /dev/null @@ -1,24 +0,0 @@ -System.register([], function () { - 'use strict'; - return { - execute: function () { - - function foo () { - return embiggen( 6, 7 ); - } - - /** - * Embiggens a number - * @param {number} num - the number to embiggen - * @param {number} factor - the factor to embiggen it by - * @returns {number} - */ - function embiggen ( num, factor ) { - return num * factor; - } - - alert( foo() ); - - } - }; -}); diff --git a/test/form/samples/block-comments/_expected/umd.js b/test/form/samples/block-comments/_expected/umd.js deleted file mode 100644 index 7cd35b73402..00000000000 --- a/test/form/samples/block-comments/_expected/umd.js +++ /dev/null @@ -1,22 +0,0 @@ -(function (factory) { - typeof define === 'function' && define.amd ? define(factory) : - factory(); -}(function () { 'use strict'; - - function foo () { - return embiggen( 6, 7 ); - } - - /** - * Embiggens a number - * @param {number} num - the number to embiggen - * @param {number} factor - the factor to embiggen it by - * @returns {number} - */ - function embiggen ( num, factor ) { - return num * factor; - } - - alert( foo() ); - -})); diff --git a/test/form/samples/break-control-flow/break-statement-labels-do-while/_config.js b/test/form/samples/break-control-flow/break-statement-labels-do-while/_config.js new file mode 100644 index 00000000000..8b990af0d2f --- /dev/null +++ b/test/form/samples/break-control-flow/break-statement-labels-do-while/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'supports labels when breaking control flow from a do-while loop' +}; diff --git a/test/form/samples/break-control-flow/break-statement-labels-do-while/_expected.js b/test/form/samples/break-control-flow/break-statement-labels-do-while/_expected.js new file mode 100644 index 00000000000..26d7470af53 --- /dev/null +++ b/test/form/samples/break-control-flow/break-statement-labels-do-while/_expected.js @@ -0,0 +1,15 @@ +label: do { + console.log('retained'); +} while (globalThis.unknown); + +do { + console.log('retained'); +} while (globalThis.unknown); + +label: do { + console.log('retained'); +} while (globalThis.unknown); + +do { + console.log('retained'); +} while (globalThis.unknown); diff --git a/test/form/samples/break-control-flow/break-statement-labels-do-while/main.js b/test/form/samples/break-control-flow/break-statement-labels-do-while/main.js new file mode 100644 index 00000000000..d2862b47745 --- /dev/null +++ b/test/form/samples/break-control-flow/break-statement-labels-do-while/main.js @@ -0,0 +1,47 @@ +label: do { + do { + break; + console.log('removed'); + } while (globalThis.unknown); + console.log('retained'); +} while (globalThis.unknown); + +do { + label: do { + break; + console.log('removed'); + } while (globalThis.unknown); + console.log('retained'); +} while (globalThis.unknown); + +label: do { + do { + break label; + console.log('removed'); + } while (globalThis.unknown); + console.log('removed'); +} while (globalThis.unknown); + +label: do { + do { + continue; + console.log('removed'); + } while (globalThis.unknown); + console.log('retained'); +} while (globalThis.unknown); + +do { + label: do { + continue; + console.log('removed'); + } while (globalThis.unknown); + console.log('retained'); +} while (globalThis.unknown); + +label: do { + do { + continue label; + console.log('removed'); + } while (globalThis.unknown); + console.log('removed'); +} while (globalThis.unknown); diff --git a/test/form/samples/break-control-flow/break-statement-labels-in-loops/_config.js b/test/form/samples/break-control-flow/break-statement-labels-in-loops/_config.js new file mode 100644 index 00000000000..08ea0622523 --- /dev/null +++ b/test/form/samples/break-control-flow/break-statement-labels-in-loops/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'handles breaking to loops inside labeled statements' +}; diff --git a/test/form/samples/break-control-flow/break-statement-labels-in-loops/_expected.js b/test/form/samples/break-control-flow/break-statement-labels-in-loops/_expected.js new file mode 100644 index 00000000000..61e440a1a43 --- /dev/null +++ b/test/form/samples/break-control-flow/break-statement-labels-in-loops/_expected.js @@ -0,0 +1,27 @@ +while (globalThis.unknown) { + console.log('retained'); + label: { + break; + } +} + +{ + console.log('retained'); +} + +while (globalThis.unknown) { + console.log('retained'); +} + +label: { + while (globalThis.unknown) { + console.log('retained'); + break label; + } + console.log('retained'); +} + +while (globalThis.unknown) { + console.log('retained'); + console.log('retained'); +} diff --git a/test/form/samples/break-control-flow/break-statement-labels-in-loops/main.js b/test/form/samples/break-control-flow/break-statement-labels-in-loops/main.js new file mode 100644 index 00000000000..091368bcb91 --- /dev/null +++ b/test/form/samples/break-control-flow/break-statement-labels-in-loops/main.js @@ -0,0 +1,49 @@ +while (globalThis.unknown) { + console.log('retained'); + label: { + break; + console.log('removed'); + } + console.log('removed'); +} + +{ + while (globalThis.unknown) { + label: { + break; + console.log('removed'); + } + console.log('removed'); + } + console.log('retained'); +} + +while (globalThis.unknown) { + label: { + break label; + console.log('removed'); + } + console.log('retained'); +} + +label: { + while (globalThis.unknown) { + console.log('retained'); + break label; + console.log('removed'); + } + console.log('retained'); +} + +while (globalThis.unknown) { + console.log('retained'); + outer: { + label: { + break outer; + console.log('removed'); + } + console.log('removed'); + } + console.log('retained'); +} + diff --git a/test/form/samples/break-control-flow/break-statement-labels-switch/_config.js b/test/form/samples/break-control-flow/break-statement-labels-switch/_config.js new file mode 100644 index 00000000000..ffce5a641d6 --- /dev/null +++ b/test/form/samples/break-control-flow/break-statement-labels-switch/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'supports labels when breaking control flow from a switch statement' +}; diff --git a/test/form/samples/break-control-flow/break-statement-labels-switch/_expected.js b/test/form/samples/break-control-flow/break-statement-labels-switch/_expected.js new file mode 100644 index 00000000000..40b1a67a1ec --- /dev/null +++ b/test/form/samples/break-control-flow/break-statement-labels-switch/_expected.js @@ -0,0 +1,87 @@ +function returnAll() { + switch (globalThis.unknown) { + case 1: + console.log('retained'); + return; + + case 2: + console.log('retained'); + return; + + default: + console.log('retained'); + return; + + } +} + +returnAll(); + +{ + console.log('retained'); +} + +function returnNoDefault() { + switch (globalThis.unknown) { + case 1: + return; + + case 2: + return; + + } + console.log('retained'); +} + +returnNoDefault(); + +function returnSomeBreak() { + switch (globalThis.unknown) { + case 1: + return; + + case 2: + break; + + default: + return; + + } + console.log('retained'); +} + +returnSomeBreak(); + +function allBreak() { + console.log('retained'); +} + +allBreak(); + +function returnBreakDifferentLabels() { + outer: { + inner: { + switch (globalThis.unknown) { + case 1: + break outer; + + case 2: + break inner; + + default: + break outer; + + } + } + console.log('retained'); + } + console.log('retained'); +} + +returnBreakDifferentLabels(); + +function empty() { + console.log('retained'); +} + +empty(); diff --git a/test/form/samples/break-control-flow/break-statement-labels-switch/main.js b/test/form/samples/break-control-flow/break-statement-labels-switch/main.js new file mode 100644 index 00000000000..480a547205f --- /dev/null +++ b/test/form/samples/break-control-flow/break-statement-labels-switch/main.js @@ -0,0 +1,118 @@ +function returnAll() { + switch (globalThis.unknown) { + case 1: + console.log('retained'); + return; + console.log('removed'); + case 2: + console.log('retained'); + return; + console.log('removed'); + default: + console.log('retained'); + return; + console.log('removed'); + } + console.log('removed'); +} + +returnAll(); + +{ + function returnAllRemoved() { + switch (globalThis.unknown) { + case 1: + return; + console.log('removed'); + case 2: + return; + console.log('removed'); + default: + return; + console.log('removed'); + } + console.log('removed'); + } + console.log('retained'); + + returnAllRemoved(); +} + +function returnNoDefault() { + switch (globalThis.unknown) { + case 1: + return; + console.log('removed'); + case 2: + return; + console.log('removed'); + } + console.log('retained'); +} + +returnNoDefault(); + +function returnSomeBreak() { + switch (globalThis.unknown) { + case 1: + return; + console.log('removed'); + case 2: + break; + console.log('removed'); + default: + return; + console.log('removed'); + } + console.log('retained'); +} + +returnSomeBreak(); + +function allBreak() { + label: switch (globalThis.unknown) { + case 1: + break label; + console.log('removed'); + case 2: + break; + console.log('removed'); + default: + break label; + console.log('removed'); + } + console.log('retained'); +} + +allBreak(); + +function returnBreakDifferentLabels() { + outer: { + inner: { + switch (globalThis.unknown) { + case 1: + break outer; + console.log('removed'); + case 2: + break inner; + console.log('removed'); + default: + break outer; + console.log('removed'); + } + console.log('removed'); + } + console.log('retained'); + } + console.log('retained'); +} + +returnBreakDifferentLabels(); + +function empty() { + switch (globalThis.unknown) { + } + console.log('retained'); +} + +empty(); diff --git a/test/form/samples/break-control-flow/break-statement-labels/_config.js b/test/form/samples/break-control-flow/break-statement-labels/_config.js new file mode 100644 index 00000000000..44fe205431f --- /dev/null +++ b/test/form/samples/break-control-flow/break-statement-labels/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'supports labels when breaking control flow' +}; 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 new file mode 100644 index 00000000000..d7080a80bb6 --- /dev/null +++ b/test/form/samples/break-control-flow/break-statement-labels/_expected.js @@ -0,0 +1,61 @@ +outer: { + inner: { + console.log('retained'); + break inner; + } + console.log('retained'); + break outer; +} + +outer: { + console.log('retained'); + break outer; +} + +outer: { + inner: { + console.log('retained'); + break outer; + } +} + +{ + console.log('retained'); +} + +outer: { + inner: { + if (globalThis.unknown) break inner; + else break outer; + } + console.log('retained'); +} + +function withConsequentReturn() { + outer: { + inner: { + if (globalThis.unknown) return; + else break inner; + } + console.log('retained'); + } + outer: { + inner: { + return; + } + } +} + +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 new file mode 100644 index 00000000000..b7ac267b7f1 --- /dev/null +++ b/test/form/samples/break-control-flow/break-statement-labels/main.js @@ -0,0 +1,83 @@ +outer: { + inner: { + console.log('retained'); + break inner; + console.log('removed'); + } + console.log('retained'); + break outer; + console.log('removed'); +} + +outer: { + inner: { + break inner; + console.log('removed'); + } + console.log('retained'); + break outer; + console.log('removed'); +} + +outer: { + inner: { + console.log('retained'); + break outer; + console.log('removed'); + } + console.log('removed'); +} + +{ + outer: { + inner: { + break outer; + console.log('removed'); + } + console.log('removed'); + } + console.log('retained'); +} + +outer: { + inner: { + if (globalThis.unknown) break inner; + else break outer; + console.log('removed'); + } + console.log('retained'); +} + +function withConsequentReturn() { + outer: { + inner: { + if (globalThis.unknown) return; + else break inner; + console.log('removed'); + } + console.log('retained'); + } + outer: { + inner: { + return; + break inner; + console.log('removed'); + } + console.log('removed'); + } +} + +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/break-control-flow/break-statement-loops/_config.js b/test/form/samples/break-control-flow/break-statement-loops/_config.js new file mode 100644 index 00000000000..dab03d4632d --- /dev/null +++ b/test/form/samples/break-control-flow/break-statement-loops/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'breaks control flow when a break statement is encountered inside a loop' +}; diff --git a/test/form/samples/break-control-flow/break-statement-loops/_expected.js b/test/form/samples/break-control-flow/break-statement-loops/_expected.js new file mode 100644 index 00000000000..6db3aa1b163 --- /dev/null +++ b/test/form/samples/break-control-flow/break-statement-loops/_expected.js @@ -0,0 +1,56 @@ +while (globalThis.unknown) { + console.log('retained'); + break; +} + +{ + console.log('retained'); +} + +while (globalThis.unknown) { + console.log('retained'); + continue; +} + +{ + console.log('retained'); +} + +do { + console.log('retained'); + break; +} while (globalThis.unknown); + +{ + console.log('retained'); +} + +for (let i = 0; i < globalThis.unknown; i++) { + console.log('retained'); + break; +} + +{ + console.log('retained'); +} + +for (const foo of globalThis.unknown) { + console.log('retained'); + break; +} + +{ + for (const foo of globalThis.unknown) { + break; + } + console.log('retained'); +} + +for (const foo in globalThis.unknown) { + console.log('retained'); + break; +} + +{ + console.log('retained'); +} diff --git a/test/form/samples/break-control-flow/break-statement-loops/main.js b/test/form/samples/break-control-flow/break-statement-loops/main.js new file mode 100644 index 00000000000..080ea651e48 --- /dev/null +++ b/test/form/samples/break-control-flow/break-statement-loops/main.js @@ -0,0 +1,87 @@ +while (globalThis.unknown) { + console.log('retained'); + break; + continue; + console.log('removed'); +} + +{ + while (globalThis.unknown) { + break; + continue; + console.log('removed'); + } + console.log('retained'); +} + +while (globalThis.unknown) { + console.log('retained'); + continue; + break; + console.log('removed'); +} + +{ + while (globalThis.unknown) { + continue; + break; + console.log('removed'); + } + console.log('retained'); +} + +do { + console.log('retained'); + break; + console.log('removed'); +} while (globalThis.unknown); + +{ + do { + break; + console.log('removed'); + } while (globalThis.unknown); + console.log('retained'); +} + +for (let i = 0; i < globalThis.unknown; i++) { + console.log('retained'); + break; + console.log('removed'); +} + +{ + for (let i = 0; i < globalThis.unknown; i++) { + break; + console.log('removed'); + } + console.log('retained'); +} + +for (const foo of globalThis.unknown) { + console.log('retained'); + break; + console.log('removed'); +} + +{ + for (const foo of globalThis.unknown) { + break; + console.log('removed'); + } + console.log('retained'); +} + +for (const foo in globalThis.unknown) { + console.log('retained'); + break; + console.log('removed'); +} + +{ + for (const foo in globalThis.unknown) { + break; + console.log('removed'); + } + console.log('retained'); +} diff --git a/test/form/samples/break-control-flow/caught-errors/_config.js b/test/form/samples/break-control-flow/caught-errors/_config.js new file mode 100644 index 00000000000..618e294de16 --- /dev/null +++ b/test/form/samples/break-control-flow/caught-errors/_config.js @@ -0,0 +1,6 @@ +module.exports = { + description: 'breaks control flow when an error is thrown inside a catch block', + options: { + treeshake: { tryCatchDeoptimization: false } + } +}; diff --git a/test/form/samples/break-control-flow/caught-errors/_expected.js b/test/form/samples/break-control-flow/caught-errors/_expected.js new file mode 100644 index 00000000000..db3d5a2dc25 --- /dev/null +++ b/test/form/samples/break-control-flow/caught-errors/_expected.js @@ -0,0 +1,61 @@ +function errorTry() { + try { + throw new Error('Break'); + } catch { + console.log('retained'); + } finally { + console.log('retained'); + } + + console.log('retained'); +} + +try { + errorTry(); +} catch {} + +function errorCatch() { + try { + console.log('retained'); + } catch { + throw new Error('Break'); + } finally { + console.log('retained'); + } + + console.log('retained'); +} + +try { + errorCatch(); +} catch {} + +function errorFinally() { + try { + console.log('retained'); + } catch { + console.log('retained'); + } finally { + throw new Error('Break'); + } +} + +try { + errorFinally(); +} catch {} + +function tryAfterError() { + console.log(hoisted1, hoisted2, hoisted3); + throw new Error(); + try { + var hoisted1; + } catch { + var hoisted2; + } finally { + var hoisted3; + } +} + +try { + tryAfterError(); +} catch {} diff --git a/test/form/samples/break-control-flow/caught-errors/main.js b/test/form/samples/break-control-flow/caught-errors/main.js new file mode 100644 index 00000000000..432952087f8 --- /dev/null +++ b/test/form/samples/break-control-flow/caught-errors/main.js @@ -0,0 +1,70 @@ +function errorTry() { + try { + throw new Error('Break'); + console.log('removed'); + } catch { + console.log('retained'); + } finally { + console.log('retained'); + } + + console.log('retained'); +} + +try { + errorTry(); +} catch {} + +function errorCatch() { + try { + console.log('retained'); + } catch { + throw new Error('Break'); + console.log('removed'); + } finally { + console.log('retained'); + } + + console.log('retained'); +} + +try { + errorCatch(); +} catch {} + +function errorFinally() { + try { + console.log('retained'); + } catch { + console.log('retained'); + } finally { + throw new Error('Break'); + console.log('removed'); + } + + console.log('removed'); +} + +try { + errorFinally(); +} catch {} + +function tryAfterError() { + console.log(hoisted1, hoisted2, hoisted3); + throw new Error(); + try { + console.log('removed'); + var hoisted1; + } catch { + console.log('removed'); + var hoisted2; + } finally { + console.log('removed'); + var hoisted3; + } + console.log('removed'); +} + +try { + tryAfterError(); +} catch {} diff --git a/test/form/samples/break-control-flow/hoisted-declarations/_config.js b/test/form/samples/break-control-flow/hoisted-declarations/_config.js new file mode 100644 index 00000000000..6fddb43932a --- /dev/null +++ b/test/form/samples/break-control-flow/hoisted-declarations/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'includes hoisted declarations when control flow is broken' +}; diff --git a/test/form/samples/break-control-flow/hoisted-declarations/_expected.js b/test/form/samples/break-control-flow/hoisted-declarations/_expected.js new file mode 100644 index 00000000000..e9a2fe30c30 --- /dev/null +++ b/test/form/samples/break-control-flow/hoisted-declarations/_expected.js @@ -0,0 +1,21 @@ +try { + nested(); +} catch {} + +function nested() { + hoisted(); + + throw new Error(); + + function hoisted() { + console.log('included'); + } +} + +hoisted(); + +throw new Error(); + +function hoisted() { + console.log('included'); +} diff --git a/test/form/samples/break-control-flow/hoisted-declarations/main.js b/test/form/samples/break-control-flow/hoisted-declarations/main.js new file mode 100644 index 00000000000..97b19312169 --- /dev/null +++ b/test/form/samples/break-control-flow/hoisted-declarations/main.js @@ -0,0 +1,29 @@ +try { + nested(); +} catch {} + +function nested() { + hoisted(); + + throw new Error(); + + console.log('removed'); + + function hoisted() { + console.log('included'); + } + + console.log('removed'); +} + +hoisted(); + +throw new Error(); + +console.log('removed'); + +function hoisted() { + console.log('included'); +} + +console.log('removed'); diff --git a/test/form/samples/break-control-flow/if-statement-errors/_config.js b/test/form/samples/break-control-flow/if-statement-errors/_config.js new file mode 100644 index 00000000000..5b83386fecd --- /dev/null +++ b/test/form/samples/break-control-flow/if-statement-errors/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'handles conditionally thrown errors' +}; diff --git a/test/form/samples/break-control-flow/if-statement-errors/_expected.js b/test/form/samples/break-control-flow/if-statement-errors/_expected.js new file mode 100644 index 00000000000..cc62b9924d5 --- /dev/null +++ b/test/form/samples/break-control-flow/if-statement-errors/_expected.js @@ -0,0 +1,104 @@ +function unknownValueConsequent() { + if (globalThis.unknownValue) { + throw new Error(); + } else { + console.log('retained'); + } + console.log('retained'); +} + +try { + unknownValueConsequent(); +} catch {} + +function unknownValueOnlyConsequent() { + if (globalThis.unknownValue) { + throw new Error(); + } + console.log('retained'); +} + +try { + unknownValueOnlyConsequent(); +} catch {} + +function unknownValueAlternate() { + if (globalThis.unknownValue) { + console.log('retained'); + } else { + throw new Error(); + } + console.log('retained'); +} + +try { + unknownValueAlternate(); +} catch {} + +function unknownValueBoth() { + if (globalThis.unknownValue) { + throw new Error(); + } else { + throw new Error(); + } +} + +try { + unknownValueBoth(); +} catch {} + +function unknownValueAfterError() { + console.log(hoisted1, hoisted2); + throw new Error(); + if (globalThis.unknownValue) { + var hoisted1; + } else { + var hoisted2; + } +} + +try { + unknownValueAfterError(); +} catch {} + +function truthyValueConsequent() { + { + throw new Error(); + } +} + +try { + truthyValueConsequent(); +} catch {} + +function truthyValueAlternate() { + { + console.log('retained'); + } + console.log('retained'); +} + +try { + truthyValueAlternate(); +} catch {} + +function falsyValueConsequent() { + { + console.log('retained'); + } + console.log('retained'); +} + +try { + falsyValueConsequent(); +} catch {} + +function falsyValueAlternate() { + { + throw new Error(); + } +} + +try { + falsyValueAlternate(); +} catch {} diff --git a/test/form/samples/break-control-flow/if-statement-errors/main.js b/test/form/samples/break-control-flow/if-statement-errors/main.js new file mode 100644 index 00000000000..804d058fa8e --- /dev/null +++ b/test/form/samples/break-control-flow/if-statement-errors/main.js @@ -0,0 +1,125 @@ +function unknownValueConsequent() { + if (globalThis.unknownValue) { + throw new Error(); + console.log('removed'); + } else { + console.log('retained'); + } + console.log('retained'); +} + +try { + unknownValueConsequent(); +} catch {} + +function unknownValueOnlyConsequent() { + if (globalThis.unknownValue) { + throw new Error(); + console.log('removed'); + } + console.log('retained'); +} + +try { + unknownValueOnlyConsequent(); +} catch {} + +function unknownValueAlternate() { + if (globalThis.unknownValue) { + console.log('retained'); + } else { + throw new Error(); + console.log('removed'); + } + console.log('retained'); +} + +try { + unknownValueAlternate(); +} catch {} + +function unknownValueBoth() { + if (globalThis.unknownValue) { + throw new Error(); + console.log('removed'); + } else { + throw new Error(); + console.log('removed'); + } + console.log('removed'); +} + +try { + unknownValueBoth(); +} catch {} + +function unknownValueAfterError() { + console.log(hoisted1, hoisted2); + throw new Error(); + if (globalThis.unknownValue) { + console.log('removed'); + var hoisted1; + } else { + console.log('removed'); + var hoisted2; + } + console.log('removed'); +} + +try { + unknownValueAfterError(); +} catch {} + +function truthyValueConsequent() { + if (true) { + throw new Error(); + console.log('removed'); + } else { + console.log('removed'); + } + console.log('removed'); +} + +try { + truthyValueConsequent(); +} catch {} + +function truthyValueAlternate() { + if (true) { + console.log('retained'); + } else { + throw new Error(); + } + console.log('retained'); +} + +try { + truthyValueAlternate(); +} catch {} + +function falsyValueConsequent() { + if (false) { + throw new Error(); + } else { + console.log('retained'); + } + console.log('retained'); +} + +try { + falsyValueConsequent(); +} catch {} + +function falsyValueAlternate() { + if (false) { + console.log('removed'); + } else { + throw new Error(); + console.log('removed'); + } + console.log('removed'); +} + +try { + falsyValueAlternate(); +} catch {} diff --git a/test/form/samples/break-control-flow/loop-errors/_config.js b/test/form/samples/break-control-flow/loop-errors/_config.js new file mode 100644 index 00000000000..bd5c24a0782 --- /dev/null +++ b/test/form/samples/break-control-flow/loop-errors/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'does not break flow from inside loops that may not have executed' +}; diff --git a/test/form/samples/break-control-flow/loop-errors/_expected.js b/test/form/samples/break-control-flow/loop-errors/_expected.js new file mode 100644 index 00000000000..34484b2f1c7 --- /dev/null +++ b/test/form/samples/break-control-flow/loop-errors/_expected.js @@ -0,0 +1,77 @@ +function whileLoop() { + console.log(hoisted); + while (globalThis.unknown) { + throw new Error(); + } + console.log('retained'); + throw new Error(); + while (globalThis.unknown) { + var hoisted; + } +} + +try { + whileLoop(); +} catch {} + +function doWhileLoop() { + console.log(hoisted); + do { + throw new Error(); + } while (globalThis.unknown); + do { + var hoisted; + } while (globalThis.unknown); +} + +try { + doWhileLoop(); +} catch {} + +function forLoop() { + console.log(hoisted); + for (let i = 0; i < globalThis.unknown; i++) { + throw new Error(); + } + console.log('retained'); + throw new Error(); + for (let i = 0; i < globalThis.unknown; i++) { + var hoisted; + } +} + +try { + forLoop(); +} catch {} + +function forOfLoop() { + console.log(hoisted); + for (const foo of globalThis.unknown) { + throw new Error(); + } + console.log('retained'); + throw new Error(); + for (const foo of globalThis.unknown) { + var hoisted; + } +} + +try { + forOfLoop(); +} catch {} + +function forInLoop() { + console.log(hoisted); + for (const foo in globalThis.unknown) { + throw new Error(); + } + console.log('retained'); + throw new Error(); + for (const foo in globalThis.unknown) { + var hoisted; + } +} + +try { + forInLoop(); +} catch {} diff --git a/test/form/samples/break-control-flow/loop-errors/main.js b/test/form/samples/break-control-flow/loop-errors/main.js new file mode 100644 index 00000000000..b60cfb9ec62 --- /dev/null +++ b/test/form/samples/break-control-flow/loop-errors/main.js @@ -0,0 +1,93 @@ +function whileLoop() { + console.log(hoisted); + while (globalThis.unknown) { + throw new Error(); + console.log('removed'); + } + console.log('retained'); + throw new Error(); + while (globalThis.unknown) { + var hoisted; + console.log('removed'); + } + console.log('removed'); +} + +try { + whileLoop(); +} catch {} + +function doWhileLoop() { + console.log(hoisted); + do { + throw new Error(); + console.log('removed'); + } while (globalThis.unknown); + console.log('removed'); + do { + var hoisted; + console.log('removed'); + } while (globalThis.unknown); + console.log('removed'); +} + +try { + doWhileLoop(); +} catch {} + +function forLoop() { + console.log(hoisted); + for (let i = 0; i < globalThis.unknown; i++) { + throw new Error(); + console.log('removed'); + } + console.log('retained'); + throw new Error(); + for (let i = 0; i < globalThis.unknown; i++) { + var hoisted; + console.log('removed'); + } + console.log('removed'); +} + +try { + forLoop(); +} catch {} + +function forOfLoop() { + console.log(hoisted); + for (const foo of globalThis.unknown) { + throw new Error(); + console.log('removed'); + } + console.log('retained'); + throw new Error(); + for (const foo of globalThis.unknown) { + var hoisted; + console.log('removed'); + } + console.log('removed'); +} + +try { + forOfLoop(); +} catch {} + +function forInLoop() { + console.log(hoisted); + for (const foo in globalThis.unknown) { + throw new Error(); + console.log('removed'); + } + console.log('retained'); + throw new Error(); + for (const foo in globalThis.unknown) { + var hoisted; + console.log('removed'); + } + console.log('removed'); +} + +try { + forInLoop(); +} catch {} diff --git a/test/form/samples/break-control-flow/return-statements/_config.js b/test/form/samples/break-control-flow/return-statements/_config.js new file mode 100644 index 00000000000..f5f2cb40612 --- /dev/null +++ b/test/form/samples/break-control-flow/return-statements/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'breaks control flow when a return statement is encountered' +}; diff --git a/test/form/samples/break-control-flow/return-statements/_expected.js b/test/form/samples/break-control-flow/return-statements/_expected.js new file mode 100644 index 00000000000..08f55dfbcb4 --- /dev/null +++ b/test/form/samples/break-control-flow/return-statements/_expected.js @@ -0,0 +1,23 @@ +function brokenFunction() { + console.log('retained'); + return; +} + +brokenFunction(); + +const brokenFunctionExpression = function() { + console.log('retained'); + return; +}; + +brokenFunctionExpression(); + +const brokenArrow = () => { + console.log('retained'); + return; +}; + +brokenArrow(); +console.log('retained'); +console.log('retained'); +console.log('retained'); diff --git a/test/form/samples/break-control-flow/return-statements/main.js b/test/form/samples/break-control-flow/return-statements/main.js new file mode 100644 index 00000000000..adab939b120 --- /dev/null +++ b/test/form/samples/break-control-flow/return-statements/main.js @@ -0,0 +1,47 @@ +function brokenFunction() { + console.log('retained'); + return; + console.log('removed'); +} + +brokenFunction(); + +const brokenFunctionExpression = function() { + console.log('retained'); + return; + console.log('removed'); +}; + +brokenFunctionExpression(); + +const brokenArrow = () => { + console.log('retained'); + return; + console.log('removed'); +}; + +brokenArrow(); + +function brokenFunctionRemoved() { + return; + console.log('removed'); +} + +brokenFunctionRemoved(); +console.log('retained'); + +const brokenFunctionExpressionRemoved = function() { + return; + console.log('removed'); +}; + +brokenFunctionExpressionRemoved(); +console.log('retained'); + +const brokenArrowRemoved = () => { + return; + console.log('removed'); +}; + +brokenArrowRemoved(); +console.log('retained'); diff --git a/test/form/samples/break-control-flow/switch-errors/_config.js b/test/form/samples/break-control-flow/switch-errors/_config.js new file mode 100644 index 00000000000..441dcd44f44 --- /dev/null +++ b/test/form/samples/break-control-flow/switch-errors/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'handles errors in switch statements' +}; diff --git a/test/form/samples/break-control-flow/switch-errors/_expected.js b/test/form/samples/break-control-flow/switch-errors/_expected.js new file mode 100644 index 00000000000..011035aae86 --- /dev/null +++ b/test/form/samples/break-control-flow/switch-errors/_expected.js @@ -0,0 +1,26 @@ +switch (globalThis.unknown) { + case 1: + throw new Error(); + + case 2: + throw new Error(); + + case 3: + console.log('retained'); + default: + console.log('retained'); +} +console.log('retained'); + +console.log(hoisted1, hoisted2, hoisted3); +throw new Error(); +switch (globalThis.unknown) { + case 1: + var hoisted1; + case 2: + var hoisted2; + case 3: + var hoisted3; + default: + +} diff --git a/test/form/samples/break-control-flow/switch-errors/main.js b/test/form/samples/break-control-flow/switch-errors/main.js new file mode 100644 index 00000000000..f381f6309cf --- /dev/null +++ b/test/form/samples/break-control-flow/switch-errors/main.js @@ -0,0 +1,30 @@ +switch (globalThis.unknown) { + case 1: + throw new Error(); + console.log('removed'); + case 2: + throw new Error(); + console.log('removed'); + case 3: + console.log('retained'); + default: + console.log('retained'); +} +console.log('retained'); + +console.log(hoisted1, hoisted2, hoisted3); +throw new Error(); +switch (globalThis.unknown) { + case 1: + console.log('removed'); + var hoisted1; + case 2: + console.log('removed'); + var hoisted2; + case 3: + console.log('removed'); + var hoisted3; + default: + console.log('removed'); +} +console.log('removed'); diff --git a/test/form/samples/break-control-flow/thrown-errors/_config.js b/test/form/samples/break-control-flow/thrown-errors/_config.js new file mode 100644 index 00000000000..6ce1e181f39 --- /dev/null +++ b/test/form/samples/break-control-flow/thrown-errors/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'breaks control flow when an error is thrown' +}; diff --git a/test/form/samples/break-control-flow/thrown-errors/_expected.js b/test/form/samples/break-control-flow/thrown-errors/_expected.js new file mode 100644 index 00000000000..cb39bb4141f --- /dev/null +++ b/test/form/samples/break-control-flow/thrown-errors/_expected.js @@ -0,0 +1,37 @@ +function brokenFunction() { + console.log('start'); + throw new Error(); +} + +try { + brokenFunction(); +} catch {} + +const brokenFunctionExpression = function() { + console.log('start'); + throw new Error(); +}; + +try { + brokenFunctionExpression(); +} catch {} + +const brokenArrow = () => { + console.log('start'); + throw new Error(); +}; + +try { + brokenArrow(); +} catch {} + +function brokenFunction2() { + console.log('start'); + throw new Error(); +} + +try { + brokenFunction2(); +} catch {} + +throw new Error(); diff --git a/test/form/samples/break-control-flow/thrown-errors/main.js b/test/form/samples/break-control-flow/thrown-errors/main.js new file mode 100644 index 00000000000..9b366207399 --- /dev/null +++ b/test/form/samples/break-control-flow/thrown-errors/main.js @@ -0,0 +1,47 @@ +function brokenFunction() { + console.log('start'); + throw new Error(); + console.log('removed'); + throw new Error('removed'); +} + +try { + brokenFunction(); +} catch {} + +const brokenFunctionExpression = function() { + console.log('start'); + throw new Error(); + console.log('removed'); + throw new Error('removed'); +}; + +try { + brokenFunctionExpression(); +} catch {} + +const brokenArrow = () => { + console.log('start'); + throw new Error(); + console.log('removed'); + throw new Error('removed'); +}; + +try { + brokenArrow(); +} catch {} + +function brokenFunction2() { + console.log('start'); + throw new Error(); + console.log('removed'); + throw new Error('removed'); +} + +try { + brokenFunction2(); +} catch {} + +throw new Error(); +console.log('removed'); +throw new Error('removed'); 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/class-without-new/_config.js b/test/form/samples/class-without-new/_config.js new file mode 100644 index 00000000000..db77abbdd9e --- /dev/null +++ b/test/form/samples/class-without-new/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'instantiating a class without "new" is a side-effect' +}; diff --git a/test/form/samples/class-without-new/_expected.js b/test/form/samples/class-without-new/_expected.js new file mode 100644 index 00000000000..d895adf5b4f --- /dev/null +++ b/test/form/samples/class-without-new/_expected.js @@ -0,0 +1,3 @@ +class foo {} + +foo(); diff --git a/test/form/samples/class-without-new/main.js b/test/form/samples/class-without-new/main.js new file mode 100644 index 00000000000..d895adf5b4f --- /dev/null +++ b/test/form/samples/class-without-new/main.js @@ -0,0 +1,3 @@ +class foo {} + +foo(); diff --git a/test/form/samples/labeled-continue-statements/_config.js b/test/form/samples/labeled-continue-statements/_config.js new file mode 100644 index 00000000000..3aa496920c8 --- /dev/null +++ b/test/form/samples/labeled-continue-statements/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'keep continue statements if their label is included' +}; diff --git a/test/form/samples/labeled-continue-statements/_expected.js b/test/form/samples/labeled-continue-statements/_expected.js new file mode 100644 index 00000000000..0be1828f5cf --- /dev/null +++ b/test/form/samples/labeled-continue-statements/_expected.js @@ -0,0 +1,17 @@ +const condition = globalThis.unknown; + +label1: while (condition) { + if (condition) { + continue label1; + } + console.log('effect'); +} + +label2: while (condition) { + while (condition) { + if (condition) { + continue label2; + } + } + console.log('effect'); +} diff --git a/test/form/samples/labeled-continue-statements/main.js b/test/form/samples/labeled-continue-statements/main.js new file mode 100644 index 00000000000..874fc9ec297 --- /dev/null +++ b/test/form/samples/labeled-continue-statements/main.js @@ -0,0 +1,23 @@ +const condition = globalThis.unknown; + +label1: while (condition) { + if (condition) { + continue label1; + } + console.log('effect'); +} + +label1NoEffect: while (condition) { + if (condition) { + continue label1NoEffect; + } +} + +label2: while (condition) { + while (condition) { + if (condition) { + continue label2; + } + } + console.log('effect'); +} diff --git a/test/form/samples/multi-expression-calls/_config.js b/test/form/samples/multi-expression-calls/_config.js new file mode 100644 index 00000000000..5ba44790f77 --- /dev/null +++ b/test/form/samples/multi-expression-calls/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'handles side-effect-free multi-expressions' +}; diff --git a/test/form/samples/multi-expression-calls/_expected.js b/test/form/samples/multi-expression-calls/_expected.js new file mode 100644 index 00000000000..c7253d38a1d --- /dev/null +++ b/test/form/samples/multi-expression-calls/_expected.js @@ -0,0 +1 @@ +console.log('retained'); diff --git a/test/form/samples/multi-expression-calls/main.js b/test/form/samples/multi-expression-calls/main.js new file mode 100644 index 00000000000..f2bdd82a57e --- /dev/null +++ b/test/form/samples/multi-expression-calls/main.js @@ -0,0 +1,10 @@ +function noEffect1(x, y) { + return () => x; +} + +function noEffect2() { + return () => {}; +} + +(globalThis.unknown ? noEffect1 : noEffect2)()(); +console.log('retained'); diff --git a/test/form/samples/property-setters-and-getters/access-when-called-effect/_config.js b/test/form/samples/property-setters-and-getters/access-when-called-effect/_config.js new file mode 100644 index 00000000000..ae85f1ce6c6 --- /dev/null +++ b/test/form/samples/property-setters-and-getters/access-when-called-effect/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'respects access side-effects when calling a getter' +}; diff --git a/test/form/samples/property-setters-and-getters/access-when-called-effect/_expected.js b/test/form/samples/property-setters-and-getters/access-when-called-effect/_expected.js new file mode 100644 index 00000000000..90418f180ce --- /dev/null +++ b/test/form/samples/property-setters-and-getters/access-when-called-effect/_expected.js @@ -0,0 +1,39 @@ +const called1 = { + get value() { + console.log('retained'); + return function() {}; + } +}; + +called1.value(); + +const instantiated1 = { + get value() { + console.log('retained'); + return class {}; + } +}; + +new instantiated1.value(); + +const called2 = { + get value() { + return function() { + console.log('retained'); + }; + } +}; + +called2.value(); + +const instantiated2 = { + get value() { + return class { + constructor() { + console.log('retained'); + } + }; + } +}; + +new instantiated2.value(); diff --git a/test/form/samples/property-setters-and-getters/access-when-called-effect/main.js b/test/form/samples/property-setters-and-getters/access-when-called-effect/main.js new file mode 100644 index 00000000000..90418f180ce --- /dev/null +++ b/test/form/samples/property-setters-and-getters/access-when-called-effect/main.js @@ -0,0 +1,39 @@ +const called1 = { + get value() { + console.log('retained'); + return function() {}; + } +}; + +called1.value(); + +const instantiated1 = { + get value() { + console.log('retained'); + return class {}; + } +}; + +new instantiated1.value(); + +const called2 = { + get value() { + return function() { + console.log('retained'); + }; + } +}; + +called2.value(); + +const instantiated2 = { + get value() { + return class { + constructor() { + console.log('retained'); + } + }; + } +}; + +new instantiated2.value(); diff --git a/test/form/samples/property-setters-and-getters/early-access-getter-return/_config.js b/test/form/samples/property-setters-and-getters/early-access-getter-return/_config.js new file mode 100644 index 00000000000..bbfa47e1932 --- /dev/null +++ b/test/form/samples/property-setters-and-getters/early-access-getter-return/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'handles accessing the return expression of a getter before it has been bound' +}; diff --git a/test/form/samples/property-setters-and-getters/early-access-getter-return/_expected.js b/test/form/samples/property-setters-and-getters/early-access-getter-return/_expected.js new file mode 100644 index 00000000000..b57b090e68c --- /dev/null +++ b/test/form/samples/property-setters-and-getters/early-access-getter-return/_expected.js @@ -0,0 +1,7 @@ +function getReturnExpressionBeforeInit() { + { + console.log('retained'); + } +} + +getReturnExpressionBeforeInit(); diff --git a/test/form/samples/property-setters-and-getters/early-access-getter-return/main.js b/test/form/samples/property-setters-and-getters/early-access-getter-return/main.js new file mode 100644 index 00000000000..745a1206e6e --- /dev/null +++ b/test/form/samples/property-setters-and-getters/early-access-getter-return/main.js @@ -0,0 +1,16 @@ +function getReturnExpressionBeforeInit() { + const bar = { + [foo.value()]: true + }; + if (bar.baz) { + console.log('retained'); + } +} + +const foo = { + get value() { + return () => 'baz'; + } +}; + +getReturnExpressionBeforeInit(); diff --git a/test/form/samples/property-setters-and-getters/early-access-getter-value/_config.js b/test/form/samples/property-setters-and-getters/early-access-getter-value/_config.js new file mode 100644 index 00000000000..e362145ba90 --- /dev/null +++ b/test/form/samples/property-setters-and-getters/early-access-getter-value/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'handles accessing the value of a getter before it has been bound' +}; diff --git a/test/form/samples/property-setters-and-getters/early-access-getter-value/_expected.js b/test/form/samples/property-setters-and-getters/early-access-getter-value/_expected.js new file mode 100644 index 00000000000..ab4f0e6ebc3 --- /dev/null +++ b/test/form/samples/property-setters-and-getters/early-access-getter-value/_expected.js @@ -0,0 +1,7 @@ +function getLiteralValueBeforeInit() { + { + console.log('retained'); + } +} + +getLiteralValueBeforeInit(); diff --git a/test/form/samples/property-setters-and-getters/early-access-getter-value/main.js b/test/form/samples/property-setters-and-getters/early-access-getter-value/main.js new file mode 100644 index 00000000000..9e1ba279dd0 --- /dev/null +++ b/test/form/samples/property-setters-and-getters/early-access-getter-value/main.js @@ -0,0 +1,16 @@ +function getLiteralValueBeforeInit() { + const bar = { + [foo.value]: true + }; + if (bar.baz) { + console.log('retained'); + } +} + +const foo = { + get value() { + return 'baz'; + } +}; + +getLiteralValueBeforeInit(); diff --git a/test/form/samples/pure-comments-disabled/_expected.js b/test/form/samples/pure-comments-disabled/_expected.js index 069d154290f..dae6dbd4fa4 100644 --- a/test/form/samples/pure-comments-disabled/_expected.js +++ b/test/form/samples/pure-comments-disabled/_expected.js @@ -1,5 +1,6 @@ // should be retained /*@__PURE__*/ a(); +/*@__PURE__*/ new a(); console.log('code'); console.log('should remain impure'); diff --git a/test/form/samples/pure-comments-disabled/main.js b/test/form/samples/pure-comments-disabled/main.js index b3c947ad6dd..bafd2b7cba8 100644 --- a/test/form/samples/pure-comments-disabled/main.js +++ b/test/form/samples/pure-comments-disabled/main.js @@ -1,5 +1,6 @@ // should be retained /*@__PURE__*/ a(); +/*@__PURE__*/ new a(); console.log('code')/*@__PURE__*/; /*@__PURE__*/(() => {})(); diff --git a/test/form/samples/recursive-multi-expressions/_config.js b/test/form/samples/recursive-multi-expressions/_config.js new file mode 100644 index 00000000000..44fd55b6d27 --- /dev/null +++ b/test/form/samples/recursive-multi-expressions/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'handles recursive multi-expressions' +}; diff --git a/test/form/samples/recursive-multi-expressions/_expected.js b/test/form/samples/recursive-multi-expressions/_expected.js new file mode 100644 index 00000000000..7e29ee70457 --- /dev/null +++ b/test/form/samples/recursive-multi-expressions/_expected.js @@ -0,0 +1,19 @@ +const unknown = globalThis.unknown; + +var logical1 = logical1 || (() => {}); +logical1()(); +logical1.x = 1; +logical1().x = 1; +logical1()().x = 1; + +var logical2 = logical2 || console.log; +logical2(); + +var conditional1 = unknown ? conditional1 : () => {}; +conditional1()(); +conditional1.x = 1; +conditional1().x = 1; +conditional1()().x = 1; + +var conditional2 = unknown ? conditional1 : console.log; +conditional2(); diff --git a/test/form/samples/recursive-multi-expressions/main.js b/test/form/samples/recursive-multi-expressions/main.js new file mode 100644 index 00000000000..3b959d2d3b7 --- /dev/null +++ b/test/form/samples/recursive-multi-expressions/main.js @@ -0,0 +1,21 @@ +const unknown = globalThis.unknown; + +var logical1 = logical1 || (() => {}); +logical1(); // removed +logical1()(); +logical1.x = 1; +logical1().x = 1; +logical1()().x = 1; + +var logical2 = logical2 || console.log; +logical2(); + +var conditional1 = unknown ? conditional1 : () => {}; +conditional1(); // removed +conditional1()(); +conditional1.x = 1; +conditional1().x = 1; +conditional1()().x = 1; + +var conditional2 = unknown ? conditional1 : console.log; +conditional2(); diff --git a/test/form/samples/side-effect-q/_expected/es.js b/test/form/samples/side-effect-q/_expected.js similarity index 100% rename from test/form/samples/side-effect-q/_expected/es.js rename to test/form/samples/side-effect-q/_expected.js diff --git a/test/form/samples/side-effect-q/_expected/amd.js b/test/form/samples/side-effect-q/_expected/amd.js deleted file mode 100644 index f9f8229aa40..00000000000 --- a/test/form/samples/side-effect-q/_expected/amd.js +++ /dev/null @@ -1,5 +0,0 @@ -define(function () { 'use strict'; - - - -}); diff --git a/test/form/samples/side-effect-q/_expected/cjs.js b/test/form/samples/side-effect-q/_expected/cjs.js deleted file mode 100644 index eb109abbed0..00000000000 --- a/test/form/samples/side-effect-q/_expected/cjs.js +++ /dev/null @@ -1,2 +0,0 @@ -'use strict'; - diff --git a/test/form/samples/side-effect-q/_expected/iife.js b/test/form/samples/side-effect-q/_expected/iife.js deleted file mode 100644 index 43ef5426880..00000000000 --- a/test/form/samples/side-effect-q/_expected/iife.js +++ /dev/null @@ -1,6 +0,0 @@ -(function () { - 'use strict'; - - - -}()); diff --git a/test/form/samples/side-effect-q/_expected/system.js b/test/form/samples/side-effect-q/_expected/system.js deleted file mode 100644 index a702f2b06ef..00000000000 --- a/test/form/samples/side-effect-q/_expected/system.js +++ /dev/null @@ -1,10 +0,0 @@ -System.register([], function () { - 'use strict'; - return { - execute: function () { - - - - } - }; -}); diff --git a/test/form/samples/side-effect-q/_expected/umd.js b/test/form/samples/side-effect-q/_expected/umd.js deleted file mode 100644 index a12a1990f01..00000000000 --- a/test/form/samples/side-effect-q/_expected/umd.js +++ /dev/null @@ -1,8 +0,0 @@ -(function (factory) { - typeof define === 'function' && define.amd ? define(factory) : - factory(); -}(function () { 'use strict'; - - - -})); diff --git a/test/form/samples/side-effect-q/main.js b/test/form/samples/side-effect-q/main.js index 2272edb8bfa..32e0019af04 100644 --- a/test/form/samples/side-effect-q/main.js +++ b/test/form/samples/side-effect-q/main.js @@ -1,5 +1,5 @@ -var x = true ? foo () : bar(); +var x = true ? foo() : bar(); -function foo () { +function foo() { return 'should be removed, because x is unused'; } diff --git a/test/form/samples/side-effect-r/_expected/es.js b/test/form/samples/side-effect-r/_expected.js similarity index 100% rename from test/form/samples/side-effect-r/_expected/es.js rename to test/form/samples/side-effect-r/_expected.js diff --git a/test/form/samples/side-effect-r/_expected/amd.js b/test/form/samples/side-effect-r/_expected/amd.js deleted file mode 100644 index f9f8229aa40..00000000000 --- a/test/form/samples/side-effect-r/_expected/amd.js +++ /dev/null @@ -1,5 +0,0 @@ -define(function () { 'use strict'; - - - -}); diff --git a/test/form/samples/side-effect-r/_expected/cjs.js b/test/form/samples/side-effect-r/_expected/cjs.js deleted file mode 100644 index eb109abbed0..00000000000 --- a/test/form/samples/side-effect-r/_expected/cjs.js +++ /dev/null @@ -1,2 +0,0 @@ -'use strict'; - diff --git a/test/form/samples/side-effect-r/_expected/iife.js b/test/form/samples/side-effect-r/_expected/iife.js deleted file mode 100644 index 43ef5426880..00000000000 --- a/test/form/samples/side-effect-r/_expected/iife.js +++ /dev/null @@ -1,6 +0,0 @@ -(function () { - 'use strict'; - - - -}()); diff --git a/test/form/samples/side-effect-r/_expected/system.js b/test/form/samples/side-effect-r/_expected/system.js deleted file mode 100644 index a702f2b06ef..00000000000 --- a/test/form/samples/side-effect-r/_expected/system.js +++ /dev/null @@ -1,10 +0,0 @@ -System.register([], function () { - 'use strict'; - return { - execute: function () { - - - - } - }; -}); diff --git a/test/form/samples/side-effect-r/_expected/umd.js b/test/form/samples/side-effect-r/_expected/umd.js deleted file mode 100644 index a12a1990f01..00000000000 --- a/test/form/samples/side-effect-r/_expected/umd.js +++ /dev/null @@ -1,8 +0,0 @@ -(function (factory) { - typeof define === 'function' && define.amd ? define(factory) : - factory(); -}(function () { 'use strict'; - - - -})); diff --git a/test/form/samples/side-effect-with-plusplus-expression/_expected/es.js b/test/form/samples/side-effect-with-plusplus-expression/_expected.js similarity index 100% rename from test/form/samples/side-effect-with-plusplus-expression/_expected/es.js rename to test/form/samples/side-effect-with-plusplus-expression/_expected.js diff --git a/test/form/samples/side-effect-with-plusplus-expression/_expected/amd.js b/test/form/samples/side-effect-with-plusplus-expression/_expected/amd.js deleted file mode 100644 index f9f8229aa40..00000000000 --- a/test/form/samples/side-effect-with-plusplus-expression/_expected/amd.js +++ /dev/null @@ -1,5 +0,0 @@ -define(function () { 'use strict'; - - - -}); diff --git a/test/form/samples/side-effect-with-plusplus-expression/_expected/cjs.js b/test/form/samples/side-effect-with-plusplus-expression/_expected/cjs.js deleted file mode 100644 index eb109abbed0..00000000000 --- a/test/form/samples/side-effect-with-plusplus-expression/_expected/cjs.js +++ /dev/null @@ -1,2 +0,0 @@ -'use strict'; - diff --git a/test/form/samples/side-effect-with-plusplus-expression/_expected/iife.js b/test/form/samples/side-effect-with-plusplus-expression/_expected/iife.js deleted file mode 100644 index 43ef5426880..00000000000 --- a/test/form/samples/side-effect-with-plusplus-expression/_expected/iife.js +++ /dev/null @@ -1,6 +0,0 @@ -(function () { - 'use strict'; - - - -}()); diff --git a/test/form/samples/side-effect-with-plusplus-expression/_expected/system.js b/test/form/samples/side-effect-with-plusplus-expression/_expected/system.js deleted file mode 100644 index a702f2b06ef..00000000000 --- a/test/form/samples/side-effect-with-plusplus-expression/_expected/system.js +++ /dev/null @@ -1,10 +0,0 @@ -System.register([], function () { - 'use strict'; - return { - execute: function () { - - - - } - }; -}); diff --git a/test/form/samples/side-effect-with-plusplus-expression/_expected/umd.js b/test/form/samples/side-effect-with-plusplus-expression/_expected/umd.js deleted file mode 100644 index a12a1990f01..00000000000 --- a/test/form/samples/side-effect-with-plusplus-expression/_expected/umd.js +++ /dev/null @@ -1,8 +0,0 @@ -(function (factory) { - typeof define === 'function' && define.amd ? define(factory) : - factory(); -}(function () { 'use strict'; - - - -})); 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/tree-shake-curried-functions/_expected/es.js b/test/form/samples/tree-shake-curried-functions/_expected.js similarity index 100% rename from test/form/samples/tree-shake-curried-functions/_expected/es.js rename to test/form/samples/tree-shake-curried-functions/_expected.js diff --git a/test/form/samples/tree-shake-curried-functions/_expected/amd.js b/test/form/samples/tree-shake-curried-functions/_expected/amd.js deleted file mode 100644 index f9f8229aa40..00000000000 --- a/test/form/samples/tree-shake-curried-functions/_expected/amd.js +++ /dev/null @@ -1,5 +0,0 @@ -define(function () { 'use strict'; - - - -}); diff --git a/test/form/samples/tree-shake-curried-functions/_expected/cjs.js b/test/form/samples/tree-shake-curried-functions/_expected/cjs.js deleted file mode 100644 index eb109abbed0..00000000000 --- a/test/form/samples/tree-shake-curried-functions/_expected/cjs.js +++ /dev/null @@ -1,2 +0,0 @@ -'use strict'; - diff --git a/test/form/samples/tree-shake-curried-functions/_expected/iife.js b/test/form/samples/tree-shake-curried-functions/_expected/iife.js deleted file mode 100644 index 43ef5426880..00000000000 --- a/test/form/samples/tree-shake-curried-functions/_expected/iife.js +++ /dev/null @@ -1,6 +0,0 @@ -(function () { - 'use strict'; - - - -}()); diff --git a/test/form/samples/tree-shake-curried-functions/_expected/system.js b/test/form/samples/tree-shake-curried-functions/_expected/system.js deleted file mode 100644 index a702f2b06ef..00000000000 --- a/test/form/samples/tree-shake-curried-functions/_expected/system.js +++ /dev/null @@ -1,10 +0,0 @@ -System.register([], function () { - 'use strict'; - return { - execute: function () { - - - - } - }; -}); diff --git a/test/form/samples/tree-shake-curried-functions/_expected/umd.js b/test/form/samples/tree-shake-curried-functions/_expected/umd.js deleted file mode 100644 index a12a1990f01..00000000000 --- a/test/form/samples/tree-shake-curried-functions/_expected/umd.js +++ /dev/null @@ -1,8 +0,0 @@ -(function (factory) { - typeof define === 'function' && define.amd ? define(factory) : - factory(); -}(function () { 'use strict'; - - - -})); 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; diff --git a/test/function/samples/catch-scope-variables/_config.js b/test/function/samples/catch-scope-variables/_config.js new file mode 100644 index 00000000000..b902e7168d5 --- /dev/null +++ b/test/function/samples/catch-scope-variables/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'handles variable declarations in catch scopes' +}; diff --git a/test/function/samples/catch-scope-variables/main.js b/test/function/samples/catch-scope-variables/main.js new file mode 100644 index 00000000000..ea3a0a6aaf5 --- /dev/null +++ b/test/function/samples/catch-scope-variables/main.js @@ -0,0 +1,14 @@ +var outsideVar = 'outside'; +let outsideLet = 'outside'; + +try { + throw new Error(); +} catch (e) { + var outsideVar = 'inside'; + let outsideLet = 'inside'; + var insideVar = 'inside'; +} + +assert.equal(outsideVar, 'inside'); +assert.equal(outsideLet, 'outside'); +assert.equal(insideVar, 'inside');