feat: refactoring project

This commit is contained in:
Carlos
2024-11-23 14:56:07 -05:00
parent f0c2a50c18
commit 1c6db5818d
2351 changed files with 39323 additions and 60326 deletions

View File

@@ -107,6 +107,23 @@ export function make_sequence(orig, expressions) {
});
}
export function make_empty_function(self) {
return make_node(AST_Function, self, {
uses_arguments: false,
argnames: [],
body: [],
is_generator: false,
async: false,
variables: new Map(),
uses_with: false,
uses_eval: false,
parent_scope: null,
enclosed: [],
cname: 0,
block_scope: undefined,
});
}
export function make_node_from_constant(val, orig) {
switch (typeof val) {
case "string":
@@ -333,9 +350,9 @@ export function is_reachable(scope_node, defs) {
}
/** Check if a ref refers to the name of a function/class it's defined within */
export function is_recursive_ref(compressor, def) {
export function is_recursive_ref(tw, def) {
var node;
for (var i = 0; node = compressor.parent(i); i++) {
for (var i = 0; node = tw.parent(i); i++) {
if (node instanceof AST_Lambda || node instanceof AST_Class) {
var name = node.name;
if (name && name.definition() === def) {

View File

@@ -103,8 +103,8 @@ import {
is_empty,
is_ref_of,
can_be_evicted_from_block,
requires_sequence_to_maintain_binding,
} from "./common.js";
import { is_used_in_expression } from "./inference.js";
const r_keep_assign = /keep_assign/;
@@ -503,42 +503,3 @@ AST_Scope.DEFMETHOD("drop_unused", function(compressor) {
}
}
});
/**
* Check if a node may be used by the expression it's in
* void (0, 1, {node}, 2) -> false
* console.log(0, {node}) -> true
*/
function is_used_in_expression(tw) {
for (let p = -1, node, parent; node = tw.parent(p), parent = tw.parent(p + 1); p++) {
if (parent instanceof AST_Sequence) {
const nth_expression = parent.expressions.indexOf(node);
if (nth_expression !== parent.expressions.length - 1) {
// Detect (0, x.noThis)() constructs
const grandparent = tw.parent(p + 2);
if (
parent.expressions.length > 2
|| parent.expressions.length === 1
|| !requires_sequence_to_maintain_binding(grandparent, parent, parent.expressions[1])
) {
return false;
}
return true;
} else {
continue;
}
}
if (parent instanceof AST_Unary) {
const op = parent.operator;
if (op === "void") {
return false;
}
if (op === "typeof" || op === "+" || op === "-" || op === "!" || op === "~") {
continue;
}
}
return true;
}
return true;
}

View File

@@ -218,14 +218,25 @@ def_eval(AST_Object, function (compressor, depth) {
var non_converting_unary = makePredicate("! typeof void");
def_eval(AST_UnaryPrefix, function (compressor, depth) {
var e = this.expression;
// Function would be evaluated to an array and so typeof would
// incorrectly return 'object'. Hence making is a special case.
if (compressor.option("typeofs")
&& this.operator == "typeof"
&& (e instanceof AST_Lambda
&& this.operator == "typeof") {
// Function would be evaluated to an array and so typeof would
// incorrectly return 'object'. Hence making is a special case.
if (e instanceof AST_Lambda
|| e instanceof AST_SymbolRef
&& e.fixed_value() instanceof AST_Lambda)) {
return typeof function () { };
&& e.fixed_value() instanceof AST_Lambda) {
return typeof function () { };
}
if (
(e instanceof AST_Object
|| e instanceof AST_Array
|| (e instanceof AST_SymbolRef
&& (e.fixed_value() instanceof AST_Object
|| e.fixed_value() instanceof AST_Array)))
&& !e.has_side_effects(compressor)
) {
return typeof {};
}
}
if (!non_converting_unary.has(this.operator))
depth++;

View File

@@ -176,6 +176,7 @@ import {
is_undefined,
is_lhs,
aborts,
is_used_in_expression,
} from "./inference.js";
import {
SQUEEZED,
@@ -195,6 +196,7 @@ import {
make_sequence,
best_of,
best_of_expression,
make_empty_function,
make_node_from_constant,
merge_sequence,
get_simple_key,
@@ -510,9 +512,8 @@ def_optimize(AST_Node, function(self) {
});
AST_Toplevel.DEFMETHOD("drop_console", function(options) {
var isArray = Array.isArray(options);
return this.transform(new TreeTransformer(function(self) {
const isArray = Array.isArray(options);
const tt = new TreeTransformer(function(self) {
if (self.TYPE !== "Call") {
return;
}
@@ -523,18 +524,35 @@ AST_Toplevel.DEFMETHOD("drop_console", function(options) {
return;
}
if (isArray && options.indexOf(exp.property) === -1) {
if (isArray && !options.includes(exp.property)) {
return;
}
var name = exp.expression;
var depth = 2;
while (name.expression) {
name = name.expression;
depth++;
}
if (is_undeclared_ref(name) && name.name == "console") {
return make_node(AST_Undefined, self);
if (
depth === 3
&& !["call", "apply"].includes(exp.property)
&& is_used_in_expression(tt)
) {
// a (used) call to Function.prototype methods (eg: console.log.bind(console))
// but not .call and .apply which would also return undefined.
exp.expression = make_empty_function(self);
set_flag(exp.expression, SQUEEZED);
self.args = [];
} else {
return make_node(AST_Undefined, self);
}
}
}));
});
return this.transform(tt);
});
AST_Node.DEFMETHOD("equivalent_to", function(node) {
@@ -1233,7 +1251,9 @@ def_optimize(AST_Switch, function(self, compressor) {
eliminate_branch(branch, body[body.length - 1]);
continue;
}
if (exp instanceof AST_Node) exp = branch.expression.tail_node().evaluate(compressor);
if (exp instanceof AST_Node && !exp.has_side_effects(compressor)) {
exp = branch.expression.tail_node().evaluate(compressor);
}
if (exp === value) {
exact_match = branch;
if (default_branch) {
@@ -1415,12 +1435,9 @@ def_optimize(AST_Switch, function(self, compressor) {
break DEFAULT;
}
let sideEffect = body.find(branch => {
return (
branch !== default_or_exact
&& branch.expression.has_side_effects(compressor)
);
});
let sideEffect = body.find(
branch => branch !== default_or_exact && branch.expression.has_side_effects(compressor)
);
// If no cases cause a side-effect, we can eliminate the switch entirely.
if (!sideEffect) {
return make_node(AST_BlockStatement, self, {
@@ -1528,9 +1545,10 @@ def_optimize(AST_Switch, function(self, compressor) {
right: branch.expression,
}),
body: consequent,
alternative: null
})
].concat(always)
alternative: null,
}),
always,
],
}).optimize(compressor);
}
return self;
@@ -1553,13 +1571,12 @@ def_optimize(AST_Switch, function(self, compressor) {
let pblock = make_node(AST_BlockStatement, prev, { body: pbody });
return bblock.equivalent_to(pblock);
}
function statement(expression) {
return make_node(AST_SimpleStatement, expression, {
body: expression
});
function statement(body) {
return make_node(AST_SimpleStatement, body, { body });
}
function has_nested_break(root) {
let has_break = false;
let tw = new TreeWalker(node => {
if (has_break) return true;
if (node instanceof AST_Lambda) return true;
@@ -1912,10 +1929,7 @@ def_optimize(AST_Call, function(self, compressor) {
&& is_undeclared_ref(exp)
&& exp.name == "Function") {
// new Function() => function(){}
if (self.args.length == 0) return make_node(AST_Function, self, {
argnames: [],
body: []
}).optimize(compressor);
if (self.args.length == 0) return make_empty_function(self).optimize(compressor);
if (self.args.every((x) => x instanceof AST_String)) {
// quite a corner-case, but we can handle it:
// https://github.com/mishoo/UglifyJS2/issues/203
@@ -3675,10 +3689,7 @@ def_optimize(AST_Dot, function(self, compressor) {
});
break;
case "Function":
self.expression = make_node(AST_Function, self.expression, {
argnames: [],
body: []
});
self.expression = make_empty_function(self.expression);
break;
case "Number":
self.expression = make_node(AST_Number, self.expression, {

View File

@@ -121,7 +121,7 @@ import {
member,
has_annotation,
} from "../utils/index.js";
import { make_sequence, best_of_expression, read_property } from "./common.js";
import { make_sequence, best_of_expression, read_property, requires_sequence_to_maintain_binding } from "./common.js";
import { INLINED, UNDEFINED, has_flag } from "./compressor-flags.js";
import { pure_prop_access_globals, is_pure_native_fn, is_pure_native_method } from "./native-objects.js";
@@ -424,7 +424,7 @@ export function is_nullish(node, compressor) {
return any(this.definitions, compressor);
});
def_has_side_effects(AST_VarDef, function() {
return this.value;
return this.value != null;
});
def_has_side_effects(AST_TemplateSegment, return_false);
def_has_side_effects(AST_TemplateString, function(compressor) {
@@ -982,3 +982,51 @@ export function is_modified(compressor, tw, node, value, level, immutable) {
return !immutable && is_modified(compressor, tw, parent, prop, level + 1);
}
}
/**
* Check if a node may be used by the expression it's in
* void (0, 1, {node}, 2) -> false
* console.log(0, {node}) -> true
*/
export function is_used_in_expression(tw) {
for (let p = -1, node, parent; node = tw.parent(p), parent = tw.parent(p + 1); p++) {
if (parent instanceof AST_Sequence) {
const nth_expression = parent.expressions.indexOf(node);
if (nth_expression !== parent.expressions.length - 1) {
// Detect (0, x.noThis)() constructs
const grandparent = tw.parent(p + 2);
if (
parent.expressions.length > 2
|| parent.expressions.length === 1
|| !requires_sequence_to_maintain_binding(grandparent, parent, parent.expressions[1])
) {
return false;
}
return true;
} else {
continue;
}
}
if (parent instanceof AST_Unary) {
const op = parent.operator;
if (op === "void") {
return false;
}
if (op === "typeof" || op === "+" || op === "-" || op === "!" || op === "~") {
continue;
}
}
if (
parent instanceof AST_SimpleStatement
|| parent instanceof AST_LabeledStatement
) {
return false;
}
if (parent instanceof AST_Scope) {
return false;
}
return true;
}
return true;
}

View File

@@ -94,8 +94,7 @@ import {
walk,
walk_body,
TreeWalker,
walk_parent,
} from "../ast.js";
import { HOP, make_node, noop } from "../utils/index.js";
@@ -496,28 +495,50 @@ function mark_lambda(tw, descend, compressor) {
* // use defined_after
* }
*
* This function is called on the parent to handle this issue.
* Or even indirectly:
*
* B();
* var defined_after = true;
* function A() {
* // use defined_after
* }
* function B() {
* A();
* }
*
* Access a variable before declaration will either throw a ReferenceError
* (if the variable is declared with `let` or `const`),
* or get an `undefined` (if the variable is declared with `var`).
*
* If the variable is inlined into the function, the behavior will change.
*
* This function is called on the parent to disallow inlining of such variables,
*/
function handle_defined_after_hoist(parent) {
const defuns = [];
walk(parent, node => {
if (node === parent) return;
if (node instanceof AST_Defun) defuns.push(node);
if (node instanceof AST_Defun) {
defuns.push(node);
return true;
}
if (
node instanceof AST_Scope
|| node instanceof AST_SimpleStatement
) return true;
});
// `defun` id to array of `defun` it uses
const defun_dependencies_map = new Map();
// `defun` id to array of enclosing `def` that are used by the function
const dependencies_map = new Map();
// all symbol ids that will be tracked for read/write
const symbols_of_interest = new Set();
const defuns_of_interest = new Set();
const potential_conflicts = [];
for (const defun of defuns) {
const fname_def = defun.name.definition();
const found_self_ref_in_other_defuns = defuns.some(
d => d !== defun && d.enclosed.indexOf(fname_def) !== -1
);
const enclosing_defs = [];
for (const def of defun.enclosed) {
if (
@@ -528,93 +549,107 @@ function handle_defined_after_hoist(parent) {
continue;
}
// defun is hoisted, so always safe
symbols_of_interest.add(def.id);
// found a reference to another function
if (
def.assignments === 0
&& def.orig.length === 1
&& def.orig[0] instanceof AST_SymbolDefun
) {
defuns_of_interest.add(def.id);
symbols_of_interest.add(def.id);
defuns_of_interest.add(fname_def.id);
symbols_of_interest.add(fname_def.id);
if (!defun_dependencies_map.has(fname_def.id)) {
defun_dependencies_map.set(fname_def.id, []);
}
defun_dependencies_map.get(fname_def.id).push(def.id);
continue;
}
if (found_self_ref_in_other_defuns) {
def.fixed = false;
continue;
}
enclosing_defs.push(def);
}
// for the slower checks below this loop
potential_conflicts.push({ defun, def, fname_def });
symbols_of_interest.add(def.id);
if (enclosing_defs.length) {
dependencies_map.set(fname_def.id, enclosing_defs);
defuns_of_interest.add(fname_def.id);
symbols_of_interest.add(fname_def.id);
defuns_of_interest.add(defun);
}
}
// linearize all symbols, and locate defs that are read after the defun
if (potential_conflicts.length) {
// All "symbols of interest", that is, defuns or defs, that we found.
// These are placed in order so we can check which is after which.
const found_symbols = [];
// Indices of `found_symbols` which are writes
const found_symbol_writes = new Set();
// Defun ranges are recorded because we don't care if a function uses the def internally
const defun_ranges = new Map();
// No defuns use outside constants
if (!dependencies_map.size) {
return;
}
let tw;
parent.walk((tw = new TreeWalker((node, descend) => {
if (node instanceof AST_Defun && defuns_of_interest.has(node)) {
const start = found_symbols.length;
descend();
const end = found_symbols.length;
// Increment to count "symbols of interest" (defuns or defs) that we found.
// These are tracked in AST order so we can check which is after which.
let symbol_index = 1;
// Map a defun ID to its first read (a `symbol_index`)
const defun_first_read_map = new Map();
// Map a symbol ID to its last write (a `symbol_index`)
const symbol_last_write_map = new Map();
defun_ranges.set(node, { start, end });
return true;
}
// if we found a defun on the list, mark IN_DEFUN=id and descend
walk_parent(parent, (node, walk_info) => {
if (node instanceof AST_Symbol && node.thedef) {
const id = node.definition().id;
if (node instanceof AST_Symbol && node.thedef) {
const id = node.definition().id;
if (symbols_of_interest.has(id)) {
if (node instanceof AST_SymbolDeclaration || is_lhs(node, tw)) {
found_symbol_writes.add(found_symbols.length);
}
found_symbols.push(id);
symbol_index++;
// Track last-writes to symbols
if (symbols_of_interest.has(id)) {
if (node instanceof AST_SymbolDeclaration || is_lhs(node, walk_info.parent())) {
symbol_last_write_map.set(id, symbol_index);
}
}
})));
for (const { def, defun, fname_def } of potential_conflicts) {
const defun_range = defun_ranges.get(defun);
// find the index in `found_symbols`, with some special rules:
const find = (sym_id, starting_at = 0, must_be_write = false) => {
let index = starting_at;
for (;;) {
index = found_symbols.indexOf(sym_id, index);
if (index === -1) {
break;
} else if (index >= defun_range.start && index < defun_range.end) {
index = defun_range.end;
continue;
} else if (must_be_write && !found_symbol_writes.has(index)) {
index++;
continue;
} else {
break;
}
// Track first-reads of defuns (refined later)
if (defuns_of_interest.has(id)) {
if (!defun_first_read_map.has(id) && !is_recursive_ref(walk_info, id)) {
defun_first_read_map.set(id, symbol_index);
}
}
}
});
return index;
};
// Refine `defun_first_read_map` to be as high as possible
for (const [defun, defun_first_read] of defun_first_read_map) {
// Update all depdencies of `defun`
const queue = new Set(defun_dependencies_map.get(defun));
for (const enclosed_defun of queue) {
let enclosed_defun_first_read = defun_first_read_map.get(enclosed_defun);
if (enclosed_defun_first_read != null && enclosed_defun_first_read < defun_first_read) {
continue;
}
const read_defun_at = find(fname_def.id);
const wrote_def_at = find(def.id, read_defun_at + 1, true);
defun_first_read_map.set(enclosed_defun, defun_first_read);
const wrote_def_after_reading_defun = read_defun_at != -1 && wrote_def_at != -1 && wrote_def_at > read_defun_at;
for (const enclosed_enclosed_defun of defun_dependencies_map.get(enclosed_defun) || []) {
queue.add(enclosed_enclosed_defun);
}
}
}
if (wrote_def_after_reading_defun) {
// ensure write-then-read order, otherwise clear `fixed`
// This is safe because last-writes (found_symbol_writes) are assumed to be as late as possible, and first-reads (defun_first_read_map) are assumed to be as early as possible.
for (const [defun, defs] of dependencies_map) {
const defun_first_read = defun_first_read_map.get(defun);
if (defun_first_read === undefined) {
continue;
}
for (const def of defs) {
if (def.fixed === false) {
continue;
}
let def_last_write = symbol_last_write_map.get(def.id) || 0;
if (defun_first_read < def_last_write) {
def.fixed = false;
}
}