@@ -314,6 +314,55 @@ const mapToObject = (map) => {
314314const keepName = (setting, name) =>
315315 setting === true || (setting instanceof RegExp && setting.test(name));
316316
317+ /**
318+ * terser's `top_retain`, read into what answers whether it keeps a name: a
319+ * list of names, one string of them split by commas, a pattern, or a test.
320+ * @param {CompressOptions["top_retain"] | undefined} setting the option
321+ * @returns {((definition: NamedDefinition) => boolean) | null} the test, null where it keeps none
322+ */
323+ const topRetainOf = (setting) => {
324+ if (!setting) return null;
325+ if (typeof setting === "function") return setting;
326+ if (setting instanceof RegExp) {
327+ return (definition) => {
328+ setting.lastIndex = 0;
329+ return setting.test(definition.name);
330+ };
331+ }
332+ const names = new Set(
333+ typeof setting === "string"
334+ ? setting.split(/,/).map((name) => name.trim())
335+ : setting
336+ );
337+ return (definition) => names.has(definition.name);
338+ };
339+
340+ /**
341+ * terser's `global_defs`, the globals it replaces with a value, kept where the
342+ * value is a primitive one a literal prints; a node or code, terser's `@`
343+ * form, is not read.
344+ * @param {CompressOptions["global_defs"] | undefined} setting the option
345+ * @returns {Map<string, string | number | boolean | null> | null} the values by name, null where none
346+ */
347+ const globalDefsOf = (setting) => {
348+ if (!setting) return null;
349+ /** @type {Map<string, string | number | boolean | null>} */
350+ const values = new Map();
351+ for (const name of Object.keys(setting)) {
352+ const value = setting[name];
353+ if (
354+ name[0] !== "@" &&
355+ (value === null ||
356+ typeof value === "string" ||
357+ typeof value === "number" ||
358+ typeof value === "boolean")
359+ ) {
360+ values.set(name, value);
361+ }
362+ }
363+ return values.size === 0 ? null : values;
364+ };
365+
317366/**
318367 * Where mangled names come from: the name for each number, and, for a source
319368 * ordering its names by the characters printed, what counts them.
@@ -5601,6 +5650,21 @@ const createNodePrinter = (modules) => {
56015650 // Whether converting what may be an object runs code, terser's
56025651 // `pure_conversions` turned off.
56035652 let comparesConvert = false;
5653+ // Whether a `debugger` statement goes, and a global `Infinity` prints as written.
5654+ let dropsDebugger = false;
5655+ let keepsInfinity = false;
5656+ // The calls `pure_funcs` names, by how their callee is written.
5657+ /** @type {Set<string> | null} */
5658+ let pureFunctions = null;
5659+ // The `console` methods whose calls go, true for every one.
5660+ /** @type {Set<string> | true | null} */
5661+ let droppedConsole = null;
5662+ // The globals `global_defs` replaces, by name, each with a primitive value.
5663+ /** @type {Map<string, string | number | boolean | null> | null} */
5664+ let globalDefs = null;
5665+ // What answers whether `top_retain` keeps a top-level name.
5666+ /** @type {((definition: MinifierVariable) => boolean) | null} */
5667+ let retainsTop = null;
56045668 // Whether a computed key of a constant prints as the name it converts to,
56055669 // terser's `computed_props`.
56065670 let liftsKeys = false;
@@ -7933,7 +7997,8 @@ const createNodePrinter = (modules) => {
79337997 definition.identifiers.length <= 1 &&
79347998 definition.export === 0 &&
79357999 !definition.undeclared &&
7936- (!definition.global || dropsToplevel) &&
8000+ (!definition.global ||
8001+ (dropsToplevel && (retainsTop === null || !retainsTop(definition)))) &&
79378002 !(/** @type {MinifierScope} */ (definition.scope).isPinned())
79388003 );
79398004 };
@@ -8108,6 +8173,68 @@ const createNodePrinter = (modules) => {
81088173 return true;
81098174 };
81108175
8176+ /**
8177+ * @param {number} node a name
8178+ * @returns {string | number | boolean | null | undefined} the value `global_defs` gives it, where it reads a global nothing assigns
8179+ */
8180+ const globalDefOf = (node) => {
8181+ const name = tree.name(node);
8182+ const value = /** @type {Map<string, string | number | boolean | null>} */ (
8183+ globalDefs
8184+ ).get(name);
8185+ return value !== undefined && isGlobalUndefined(node, name)
8186+ ? value
8187+ : undefined;
8188+ };
8189+
8190+ /**
8191+ * A callee as `pure_funcs` names it: a name or a chain of names read off
8192+ * it with dots, as written.
8193+ * @param {number} node the callee, past parentheses
8194+ * @returns {string} its text, empty for any other callee
8195+ */
8196+ const writtenCallee = (node) => {
8197+ const type = typeOf(node);
8198+ if (type === NodeType.Identifier) return tree.name(node);
8199+ if (
8200+ type !== NodeType.MemberExpression ||
8201+ tree.flag(Flag.computed, node) ||
8202+ tree.flag(Flag.optional, node)
8203+ ) {
8204+ return "";
8205+ }
8206+ const object = writtenCallee(childOf(Field.object, node));
8207+ const property = childOf(Field.property, node);
8208+ return object === "" || typeOf(property) !== NodeType.Identifier
8209+ ? ""
8210+ : `${object}.${tree.name(property)}`;
8211+ };
8212+
8213+ /**
8214+ * @param {number} node an expression, past parentheses
8215+ * @returns {boolean} whether it is a call of a `console` method `drop_console` drops, on the global `console`
8216+ */
8217+ const callsDroppedConsole = (node) => {
8218+ if (droppedConsole === null || typeOf(node) !== NodeType.CallExpression) {
8219+ return false;
8220+ }
8221+ const callee = childOf(Field.callee, node);
8222+ if (
8223+ typeOf(callee) !== NodeType.MemberExpression ||
8224+ tree.flag(Flag.computed, callee)
8225+ ) {
8226+ return false;
8227+ }
8228+ const object = childOf(Field.object, callee);
8229+ const property = childOf(Field.property, callee);
8230+ return (
8231+ typeOf(object) === NodeType.Identifier &&
8232+ isGlobalUndefined(object, "console") &&
8233+ typeOf(property) === NodeType.Identifier &&
8234+ (droppedConsole === true || droppedConsole.has(tree.name(property)))
8235+ );
8236+ };
8237+
81118238 /**
81128239 * @param {number} node a call or `new`
81138240 * @returns {boolean} whether a `#__PURE__` comment before it marks it pure, where `side_effects` reads them
@@ -8122,6 +8249,12 @@ const createNodePrinter = (modules) => {
81228249 return true;
81238250 }
81248251 if (constructsRegExp(node)) return true;
8252+ if (
8253+ pureFunctions !== null &&
8254+ pureFunctions.has(writtenCallee(childOf(Field.callee, node)))
8255+ ) {
8256+ return true;
8257+ }
81258258 if (!readsPureAnnotations || commentTokens === null) return false;
81268259 const tables = output.tokenTables;
81278260 const table = /** @type {TokenTable} */ (tables.last);
@@ -8349,6 +8482,10 @@ const createNodePrinter = (modules) => {
83498482 case NodeType.Identifier: {
83508483 // Without the mangler's analysis nothing is known constant.
83518484 if (mangledNames === null) return undefined;
8485+ if (globalDefs !== null) {
8486+ const value = globalDefOf(node);
8487+ if (value !== undefined) return value;
8488+ }
83528489 const definition = mangledNames[node];
83538490 if (
83548491 !isMinifierVariable(definition) ||
@@ -9931,6 +10068,15 @@ const createNodePrinter = (modules) => {
993110068 }
993210069 } else if (statementType === NodeType.FunctionDeclaration) {
993310070 if (!hasEdgeComments(statement)) candidates.push(statement);
10071+ } else if (
10072+ (statementType === NodeType.DebuggerStatement && dropsDebugger) ||
10073+ (statementType === NodeType.ExpressionStatement &&
10074+ callsDroppedConsole(childOf(Field.expression, statement)))
10075+ ) {
10076+ // terser's `drop_debugger` and `drop_console`: the call goes whole,
10077+ // what its arguments read and run with it.
10078+ dropped[statement] = 1;
10079+ findNode(statement, discountRead);
993410080 } else if (statementType === NodeType.IfStatement) {
993510081 if (neverRuns(statement)) {
993610082 dropped[statement] = 1;
@@ -13775,7 +13921,11 @@ const createNodePrinter = (modules) => {
1377513921 const printsInfinity = (node) => {
1377613922 let negative = false;
1377713923 if (typeOf(node) === NodeType.Identifier) {
13778- if (keepsGlobalNames || !isGlobalUndefined(node, "Infinity")) {
13924+ if (
13925+ keepsGlobalNames ||
13926+ keepsInfinity ||
13927+ !isGlobalUndefined(node, "Infinity")
13928+ ) {
1377913929 return false;
1378013930 }
1378113931 } else {
@@ -14416,7 +14566,8 @@ const createNodePrinter = (modules) => {
1441614566 output.semicolon();
1441714567 };
1441814568 generators[NodeType.DebuggerStatement] = () => {
14419- output.print("debugger");
14569+ // One `drop_debugger` drops where no list holds it prints as `;`.
14570+ if (!dropsDebugger) output.print("debugger");
1442014571 output.semicolon();
1442114572 };
1442214573 generators[NodeType.LabeledStatement] = (node) => {
@@ -14868,6 +15019,13 @@ const createNodePrinter = (modules) => {
1486815019 const deleted =
1486915020 typeOf(parentAt(0)) === NodeType.UnaryExpression &&
1487015021 tree.textOf(Part.operator, parentAt(0)) === "delete";
15022+ if (compresses && !deleted && globalDefs !== null) {
15023+ const value = globalDefOf(node);
15024+ if (value !== undefined && readsAsValue(node, parentAt(0))) {
15025+ printValue(value, takesUnary(node));
15026+ return;
15027+ }
15028+ }
1487115029 if (compresses && !deleted && readsUndefined(node)) {
1487215030 // `void 0` is shorter, and no scope can rebind it.
1487315031 output.print("void 0");
@@ -16154,6 +16312,20 @@ const createNodePrinter = (modules) => {
1615416312 }
1615516313 dropsToplevel = Boolean(compress && (compress.toplevel || compress.module));
1615616314 keepsFunctionNames = Boolean(compress && compress.keep_fnames);
16315+ dropsDebugger = Boolean(compress && compress.drop_debugger !== false);
16316+ keepsInfinity = Boolean(compress && compress.keep_infinity);
16317+ pureFunctions =
16318+ compress &&
16319+ Array.isArray(compress.pure_funcs) &&
16320+ compress.pure_funcs.length > 0
16321+ ? new Set(compress.pure_funcs)
16322+ : null;
16323+ droppedConsole =
16324+ !compress || !compress.drop_console
16325+ ? null
16326+ : compress.drop_console === true || new Set(compress.drop_console);
16327+ retainsTop = compress ? topRetainOf(compress.top_retain) : null;
16328+ globalDefs = compress ? globalDefsOf(compress.global_defs) : null;
1615716329 keepsClassNames = Boolean(compress && compress.keep_classnames);
1615816330 privateNames = printOptions.names ? printOptions.names.privateNames : null;
1615916331 renamedProperties = printOptions.names
0 commit comments