| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538 |
- /***********************************************************************
- A JavaScript tokenizer / parser / beautifier / compressor.
- https://github.com/mishoo/UglifyJS2
- -------------------------------- (C) ---------------------------------
- Author: Mihai Bazon
- <mihai.bazon@gmail.com>
- http://mihai.bazon.net/blog
- Distributed under the BSD license:
- Copyright 2012 (c) Mihai Bazon <mihai.bazon@gmail.com>
- Redistribution and use in source and binary forms, with or without
- modification, are permitted provided that the following conditions
- are met:
- * Redistributions of source code must retain the above
- copyright notice, this list of conditions and the following
- disclaimer.
- * Redistributions in binary form must reproduce the above
- copyright notice, this list of conditions and the following
- disclaimer in the documentation and/or other materials
- provided with the distribution.
- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY
- EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
- PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
- LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
- OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
- PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
- PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
- TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
- THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- SUCH DAMAGE.
- ***********************************************************************/
- import { AST_Array, AST_Dot, AST_New, AST_Number, AST_SymbolRef } from "../ast.js";
- import { makePredicate } from "../utils/index.js";
- import { is_undeclared_ref } from "./inference.js";
- // Lists of native methods, useful for `unsafe` option which assumes they exist.
- // Note: Lots of methods and functions are missing here, in case they aren't pure
- // or not available in all JS environments.
- const make_nested_lookup = (feature_callback) => (compressor) => {
- const obj = feature_callback(feature_variables(compressor));
- const out = new Map();
- for (var key of Object.keys(obj)) {
- if (obj[key]) {
- out.set(key, makePredicate(remove_false(obj[key])));
- }
- }
- const does_have = (global_name, fname) => {
- const inner_map = out.get(global_name);
- return inner_map != null && inner_map.has(fname);
- };
- return does_have;
- };
- const make_lookup = (feature_callback) => (compressor) => {
- const obj = feature_callback(feature_variables(compressor));
- const predicate = makePredicate(remove_false(obj));
- const does_have = (global_name) => {
- return predicate.has(global_name);
- };
- return does_have;
- };
- function remove_false(arr) {
- for (let i = 0; i < arr.length; i++) {
- if (arr[i] === false) {
- arr.splice(i, 1);
- i--;
- }
- }
- return arr;
- }
- /** Generate the object with arguments seen below */
- function feature_variables(compressor) {
- return {
- sloppy: compressor.option("unsafe"),
- es: compressor.option("builtins_ecma"),
- };
- }
- // eslint-disable-next-line no-unused-vars
- export const pure_access_globals = make_lookup(({ sloppy, es }) => [
- "Array",
- "Boolean",
- "clearInterval",
- "clearTimeout",
- "console",
- "Date",
- "decodeURI",
- "decodeURIComponent",
- "encodeURI",
- "encodeURIComponent",
- "Error",
- "escape",
- "eval",
- "EvalError",
- "Function",
- es >= 2020 && "globalThis",
- "isFinite",
- "isNaN",
- "JSON",
- "Math",
- "Number",
- "parseFloat",
- "parseInt",
- "RangeError",
- "ReferenceError",
- "RegExp",
- "Object",
- "setInterval",
- "setTimeout",
- "String",
- "SyntaxError",
- "TypeError",
- "unescape",
- "URIError",
- ]);
- // Objects which are safe to access without throwing or causing a side effect.
- // Usually we'd check the `unsafe` option first but these are way too common for that
- export const pure_prop_access_globals = new Set([
- "Number",
- "String",
- "Array",
- "Object",
- "Function",
- "Promise",
- ]);
- // eslint-disable-next-line no-unused-vars
- export const is_pure_native_fn = make_lookup(({ sloppy, es }) => [
- sloppy && es >= 2021 && "AggregateError",
- "Array",
- "ArrayBuffer",
- es >= 2020 && "BigInt",
- es >= 2020 && "BigInt64Array",
- es >= 2020 && "BigUint64Array",
- "Boolean",
- "Date",
- sloppy && "decodeURI",
- sloppy && "decodeURIComponent",
- sloppy && "encodeURI",
- sloppy && "encodeURIComponent",
- "Error",
- "escape",
- "EvalError",
- es >= 2021 && "FinalizationRegistry",
- es >= 2026 && "Float16Array",
- "Float32Array",
- "Float64Array",
- "Int16Array",
- "Int32Array",
- "Int8Array",
- "isFinite",
- "isNaN",
- es >= 2026 && "Iterator",
- es >= 2015 && "Map",
- "Number",
- "parseFloat",
- "parseInt",
- es >= 2015 && "Promise",
- es >= 2015 && "Proxy",
- "RangeError",
- "ReferenceError",
- sloppy && "RegExp",
- es >= 2015 && "Set",
- "String",
- es >= 2015 && "Symbol",
- "SyntaxError",
- "TypeError",
- "Uint16Array",
- "Uint32Array",
- "Uint8Array",
- "Uint8ClampedArray",
- sloppy && "unescape",
- "URIError",
- sloppy && es >= 2015 && "WeakMap",
- sloppy && es >= 2021 && "WeakRef",
- sloppy && es >= 2015 && "WeakSet",
- ]);
- const arg1_is_iterable = new Set([
- "Map",
- "Set",
- "WeakMap",
- "WeakSet",
- ]);
- const arg1_is_range_or_iterable = new Set([
- "ArrayBuffer",
- "Float32Array",
- "Float64Array",
- "Int16Array",
- "Int32Array",
- "Int8Array",
- "Uint16Array",
- "Uint32Array",
- "Uint8Array",
- "Uint8ClampedArray",
- ]);
- const lone_arg_is_range = new Set(["Array"]);
- const object_methods = [
- "constructor",
- "toString",
- "valueOf",
- ];
- // eslint-disable-next-line no-unused-vars
- export const is_pure_native_method = make_nested_lookup(({ sloppy, es }) => ({
- Array: [
- es >= 2022 && "at",
- es >= 2019 && "flat",
- es >= 2016 && "includes",
- "indexOf",
- "join",
- "lastIndexOf",
- "slice",
- ...object_methods,
- ],
- Boolean: object_methods,
- Function: object_methods,
- Number: [
- "toExponential",
- "toFixed",
- "toPrecision",
- ...object_methods,
- ],
- Object: object_methods,
- RegExp: [
- "test",
- ...object_methods,
- ],
- String: [
- es >= 2022 && "at",
- "charAt",
- "charCodeAt",
- es >= 2015 && "codePointAt",
- "concat",
- es >= 2025 && "endsWith",
- es >= 2015 && "includes",
- "indexOf",
- "italics",
- "lastIndexOf",
- es >= 2020 && "localeCompare",
- "match",
- es >= 2020 && "matchAll",
- es >= 2015 && "normalize",
- es >= 2017 && "padStart",
- es >= 2017 && "padEnd",
- es >= 2015 && sloppy && "repeat",
- "replace",
- es >= 2021 && "replaceAll",
- "search",
- "slice",
- "split",
- es >= 2015 && "startsWith",
- "substr",
- "substring",
- es >= 2015 && "repeat",
- "toLocaleLowerCase",
- "toLocaleUpperCase",
- "toLowerCase",
- "toUpperCase",
- "trim",
- es >= 2019 && "trimEnd",
- es >= 2019 && "trimStart",
- es >= 2019 && "trimLeft",
- es >= 2019 && "trimRight",
- ...object_methods,
- ],
- }));
- // eslint-disable-next-line no-unused-vars
- export const is_pure_native_static_fn = make_nested_lookup(({ sloppy, es }) => ({
- Array: [
- "isArray",
- es >= 2015 && "of",
- ],
- ArrayBuffer: [
- "isView",
- ],
- BigInt: es >= 2020 && [
- sloppy && "asIntN",
- sloppy && "asUintN",
- ],
- BigInt64Array: sloppy && es >= 2020 && ["of"],
- BigUint64Array: sloppy && es >= 2020 && ["of"],
- Date: [
- "now",
- "parse",
- "UTC",
- ],
- Error: [
- es >= 2026 && "isError",
- ],
- Float16Array: sloppy && es >= 2026 && ["of"],
- Float32Array: sloppy && ["of"],
- Float64Array: sloppy && ["of"],
- Int16Array: sloppy && ["of"],
- Int32Array: sloppy && ["of"],
- Int8Array: sloppy && ["of"],
- Math: [
- "abs",
- "acos",
- es >= 2015 && "acosh",
- "asin",
- es >= 2015 && "asinh",
- "atan",
- "atan2",
- es >= 2015 && "atanh",
- es >= 2015 && "cbrt",
- "ceil",
- es >= 2015 && "clz32",
- "cos",
- es >= 2015 && "cosh",
- "exp",
- es >= 2015 && "expm1",
- "floor",
- es >= 2026 && "f16round",
- es >= 2015 && "fround",
- es >= 2015 && "hypot",
- es >= 2015 && "imul",
- "log",
- es >= 2015 && "log10",
- es >= 2015 && "log1p",
- es >= 2015 && "log2",
- "max",
- "min",
- "pow",
- "round",
- es >= 2015 && "sign",
- "sin",
- es >= 2015 && "sinh",
- "sqrt",
- "tan",
- es >= 2015 && "tanh",
- es >= 2015 && "trunc",
- ],
- Number: [
- es >= 2015 && "isFinite",
- es >= 2015 && "isInteger",
- es >= 2015 && "isSafeInteger",
- es >= 2015 && "isNaN",
- es >= 2015 && "parseFloat",
- es >= 2015 && "parseInt",
- ],
- Object: [
- sloppy && "create",
- sloppy && "getOwnPropertyDescriptor",
- es >= 2017 && sloppy && "getOwnPropertyDescriptors",
- sloppy && "getOwnPropertyNames",
- es >= 2015 && sloppy && "getOwnPropertySymbols",
- sloppy && "getPrototypeOf",
- es >= 2022 && sloppy && "hasOwn",
- es >= 2015 && "is",
- "isExtensible",
- "isFrozen",
- "isSealed",
- es >= 2015 && sloppy && "keys",
- ],
- Promise: es >= 2015 && [
- es >= 2024 && "withResolvers",
- ],
- Proxy: es >= 2015 && [
- sloppy && "revocable",
- ],
- Reflect: es >= 2015 && [
- sloppy && "has",
- sloppy && "isExtensible",
- sloppy && "ownKeys",
- ],
- RegExp: [
- es >= 2026 && sloppy && "escape",
- ],
- String: [
- "fromCharCode",
- sloppy && es >= 2025 && "fromCodePoint",
- ],
- Uint16Array: ["of"],
- Uint32Array: ["of"],
- Uint8Array: ["of"],
- Uint8ClampedArray: ["of"],
- }));
- // Known numeric values which come with JS environments
- // eslint-disable-next-line no-unused-vars
- export const is_pure_native_static_property = make_nested_lookup(({ sloppy, es }) => ({
- Math: [
- "E",
- "LN10",
- "LN2",
- "LOG2E",
- "LOG10E",
- "PI",
- "SQRT1_2",
- "SQRT2",
- ],
- Number: [
- es >= 2015 && "EPSILON",
- es >= 2015 && "MAX_SAFE_VALUE",
- "MAX_VALUE",
- es >= 2015 && "MIN_SAFE_VALUE",
- "MIN_VALUE",
- "NaN",
- "NEGATIVE_INFINITY",
- "POSITIVE_INFINITY",
- ],
- RegExp: [
- "$_",
- "$0",
- "$1",
- "$2",
- "$3",
- "$4",
- "$5",
- "$6",
- "$7",
- "$8",
- "$9",
- "input",
- "lastMatch",
- "lastParen",
- "leftContext",
- "rightContext",
- ],
- }));
- const re_uppercase_first_letter = /^[A-Z]/;
- export function is_pure_builtin_call(compressor, call) {
- let builtin = "";
- let method = "";
- let exp = call.expression;
- if (is_undeclared_ref(exp)) {
- builtin = exp.name;
- } else if (exp instanceof AST_Dot) {
- method = exp.property;
- exp = exp.expression;
- if (is_undeclared_ref(exp)) {
- if (
- // globalThis.pureFunc()
- exp.name === "globalThis"
- && compressor.option("builtins_ecma") >= 2020
- ) {
- builtin = method;
- method = "";
- } else {
- // SomeBuiltin.pureFunc()
- builtin = exp.name;
- }
- } else if (exp instanceof AST_Dot) {
- if (
- is_undeclared_ref(exp.expression)
- && exp.expression.name === "globalThis"
- && compressor.option("builtins_ecma") >= 2020
- ) {
- // globalThis.SomeBuiltin.pureFunc()
- builtin = exp.property;
- } else {
- return false;
- }
- } else {
- return false;
- }
- } else {
- return false;
- }
- if (!method) {
- if (compressor.is_pure_native_fn(builtin)) {
- // some require `new`, others throw if you use it
- const is_new = call instanceof AST_New;
- const should_be_new = re_uppercase_first_letter.test(builtin); // true of all `is_pure_native_fn`
- if (is_new !== should_be_new) return false;
- if (!is_builtin_pure_with_these_args(builtin, call.args)) {
- return false;
- }
- return true;
- }
- return false;
- } else {
- return compressor.is_pure_native_static_fn(builtin, method);
- }
- }
- /** Some builtins are listed above but their purity is subject to some conditions */
- function is_builtin_pure_with_these_args(builtin, args) {
- // all the builtins we deal with here are ok with getting 0 args
- if (args.length === 0) return true;
- let arg1 = args[0];
- if (arg1 instanceof AST_SymbolRef) {
- arg1 = arg1.fixed_value();
- }
- if (lone_arg_is_range.has(builtin)) { // new Array(number)
- const arg_valid = args.length > 1
- || arg1 instanceof AST_Number
- && arg1.value >= 0 && arg1.value <= 0xffffffff;
- // TODO: or, we are asked to ignore TypeError
- if (!arg_valid) return false;
- }
- if (arg1_is_range_or_iterable.has(builtin)) { // new Float32Array(number | Array)
- const arg_valid = args.length === 0
- || arg1 instanceof AST_Array
- || arg1 instanceof AST_Number
- && arg1.value >= 0 && arg1.value <= 0xffffffff;
- if (!arg_valid) return false;
- }
- if (arg1_is_iterable.has(builtin)) { // new Set(iterable)
- const arg_valid = args.length === 0 || arg1 instanceof AST_Array;
- if (!arg_valid) return false;
- }
- return true;
- }
|