Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(WIP): introduce the scope of function parameters #1480

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
8 changes: 8 additions & 0 deletions CHANGELOG.md
@@ -1,5 +1,13 @@
# Changelog

## v5.27.0
- Created `minify_sync()` alternative to `minify()` since there's no async code left.

## v5.26.0
- Do not take the `/*#__PURE__*/` annotation into account when the `side_effects` compress option is off.
- The `preserve_annotations` option now automatically opts annotation comments in, instead of requiring the `comments` option to be configured for this.
- Refuse to parse empty parenthesized expressions (`()`)

## v5.25.0
- Regex properties added to reserved property mangler (#1471)
- `pure_new` option added to drop unused `new` expressions.
Expand Down
4 changes: 3 additions & 1 deletion README.md
Expand Up @@ -424,7 +424,7 @@ Browser loading is also supported. It exposes a global variable `Terser` contain
<script src="https://cdn.jsdelivr.net/npm/terser/dist/bundle.min.js"></script>
```

There is a single async high level function, **`async minify(code, options)`**,
There is an async high level function, **`async minify(code, options)`**,
which will perform all minification [phases](#minify-options) in a configurable
manner. By default `minify()` will enable [`compress`](#compress-options)
and [`mangle`](#mangle-options). Example:
Expand All @@ -435,6 +435,8 @@ console.log(result.code); // minified output: function add(n,d){return n+d}
console.log(result.map); // source map
```

There is also a `minify_sync()` alternative version of it, which returns instantly.

You can `minify` more than one JavaScript file at a time by using an object
for the first argument where the keys are file names and the values are source
code:
Expand Down
16 changes: 11 additions & 5 deletions lib/ast.js
Expand Up @@ -277,6 +277,9 @@ var AST_Block = DEFNODE("Block", "body block_scope", function AST_Block(props) {
body: "[AST_Statement*] an array of statements",
block_scope: "[AST_Scope] the block scope"
},
walk_body: function(visitor) {
walk_body(this, visitor);
},
_walk: function(visitor) {
return visitor._visit(this, function() {
walk_body(this, visitor);
Expand All @@ -286,7 +289,7 @@ var AST_Block = DEFNODE("Block", "body block_scope", function AST_Block(props) {
let i = this.body.length;
while (i--) push(this.body[i]);
},
clone: clone_block_scope
clone: clone_block_scope,
}, AST_Statement);

var AST_BlockStatement = DEFNODE("BlockStatement", null, function AST_BlockStatement(props) {
Expand Down Expand Up @@ -598,7 +601,7 @@ var AST_Scope = DEFNODE(
},
get_defun_scope: function() {
var self = this;
while (self.is_block_scope()) {
while (self._block_scope) {
self = self.parent_scope;
}
return self;
Expand Down Expand Up @@ -742,6 +745,11 @@ var AST_Lambda = DEFNODE(
}
return out;
},
walk_argnames: function(visitor) {
for (var i = 0; i < this.argnames.length; i++) {
this.argnames[i].walk(visitor);
}
},
_walk: function(visitor) {
return visitor._visit(this, function() {
if (this.name) this.name._walk(visitor);
Expand Down Expand Up @@ -2368,9 +2376,8 @@ var AST_DefClass = DEFNODE("DefClass", null, function AST_DefClass(props) {
$documentation: "A class definition",
}, AST_Class);

var AST_ClassStaticBlock = DEFNODE("ClassStaticBlock", "body block_scope", function AST_ClassStaticBlock (props) {
var AST_ClassStaticBlock = DEFNODE("ClassStaticBlock", "body", function AST_ClassStaticBlock (props) {
this.body = props.body;
this.block_scope = props.block_scope;
this.start = props.start;
this.end = props.end;
}, {
Expand All @@ -2387,7 +2394,6 @@ var AST_ClassStaticBlock = DEFNODE("ClassStaticBlock", "body block_scope", funct
let i = this.body.length;
while (i--) push(this.body[i]);
},
clone: clone_block_scope,
computed_key: () => false
}, AST_Scope);

Expand Down
4 changes: 2 additions & 2 deletions lib/compress/drop-unused.js
Expand Up @@ -459,8 +459,8 @@ AST_Scope.DEFMETHOD("drop_unused", function(compressor) {
if (!in_use_ids.has(node_def.id)) {
in_use_ids.set(node_def.id, node_def);
if (node_def.orig[0] instanceof AST_SymbolCatch) {
const redef = node_def.scope.is_block_scope()
&& node_def.scope.get_defun_scope().variables.get(node_def.name);
const redef = node_def.scope._block_scope
&& node_def.scope.get_defun_scope().defun_get_root_variable(node_def.name);
if (redef) in_use_ids.set(redef.id, redef);
}
}
Expand Down
5 changes: 4 additions & 1 deletion lib/compress/inference.js
Expand Up @@ -831,7 +831,10 @@ AST_Call.DEFMETHOD("is_callee_pure", function(compressor) {
if ((this instanceof AST_New) && compressor.option("pure_new")) {
return true;
}
return !!has_annotation(this, _PURE) || !compressor.pure_funcs(this);
if (compressor.option("side_effects") && has_annotation(this, _PURE)) {
return true;
}
return !compressor.pure_funcs(this);
});

// If I call this, is it a pure function?
Expand Down
41 changes: 21 additions & 20 deletions lib/compress/inline.js
Expand Up @@ -134,9 +134,9 @@ function within_array_or_object_literal(compressor) {
return false;
}

function scope_encloses_variables_in_this_scope(scope, pulled_scope) {
for (const enclosed of pulled_scope.enclosed) {
if (pulled_scope.variables.has(enclosed.name)) {
function fn_encloses_variables_in_this_scope(scope, pulled_fn) {
for (const enclosed of pulled_fn.enclosed) {
if (pulled_fn.defun_get_root_variable(enclosed.name)) {
continue;
}
const looked_up = scope.find_variable(enclosed.name);
Expand Down Expand Up @@ -172,12 +172,12 @@ export function inline_into_symbolref(self, compressor) {
const nearest_scope = compressor.find_scope();
let fixed = self.fixed_value();
if (
compressor.top_retain &&
def.global &&
compressor.top_retain(def) &&
compressor.top_retain
&& def.global
&& compressor.top_retain(def)
// when identifier is in top_retain option dose not mean we can always inline it.
// if identifier name is longer then init value, we can replace it.
is_const_symbol_short_than_init_value(def, fixed)
&& is_const_symbol_short_than_init_value(def, fixed)
) {
// keep it
def.fixed = false;
Expand Down Expand Up @@ -230,10 +230,10 @@ export function inline_into_symbolref(self, compressor) {
if (single_use && (fixed instanceof AST_Lambda || fixed instanceof AST_Class)) {
single_use =
def.scope === self.scope
&& !scope_encloses_variables_in_this_scope(nearest_scope, fixed)
&& !fn_encloses_variables_in_this_scope(nearest_scope, fixed)
|| parent instanceof AST_Call
&& parent.expression === self
&& !scope_encloses_variables_in_this_scope(nearest_scope, fixed)
&& !fn_encloses_variables_in_this_scope(nearest_scope, fixed)
&& !(fixed.name && fixed.name.definition().recursive_refs > 0);
}

Expand All @@ -248,7 +248,7 @@ export function inline_into_symbolref(self, compressor) {
}
if (def.recursive_refs > 0 && fixed.name instanceof AST_SymbolDefun) {
const defun_def = fixed.name.definition();
let lambda_def = fixed.variables.get(fixed.name.name);
let lambda_def = fixed.defun_get_root_variable(fixed.name.name);
let name = lambda_def && lambda_def.orig[0];
if (!(name instanceof AST_SymbolLambda)) {
name = make_node(AST_SymbolLambda, fixed.name, fixed.name);
Expand Down Expand Up @@ -407,7 +407,7 @@ export function inline_into_call(self, compressor) {
&& !fn.contains_this()
&& can_inject_symbols()
&& (nearest_scope = compressor.find_scope())
&& !scope_encloses_variables_in_this_scope(nearest_scope, fn)
&& !fn_encloses_variables_in_this_scope(nearest_scope, fn)
&& !(function in_default_assign() {
// Due to the fact function parameters have their own scope
// which can't use `var something` in the function body within,
Expand All @@ -420,7 +420,6 @@ export function inline_into_call(self, compressor) {
}
return false;
})()
&& !(scope instanceof AST_Class)
) {
set_flag(fn, SQUEEZED);
nearest_scope.add_child_scope(fn);
Expand All @@ -443,8 +442,8 @@ export function inline_into_call(self, compressor) {
}).optimize(compressor);
}

const can_drop_this_call = is_regular_func && compressor.option("side_effects") && fn.body.every(is_empty);
if (can_drop_this_call) {
const drop_empty_fn = is_regular_func && compressor.option("side_effects") && fn.body.every(is_empty);
if (drop_empty_fn) {
var args = self.args.concat(make_node(AST_Undefined, self));
return make_sequence(self, args).optimize(compressor);
}
Expand Down Expand Up @@ -517,7 +516,7 @@ export function inline_into_call(self, compressor) {
if (!safe_to_inject
|| block_scoped.has(arg.name)
|| identifier_atom.has(arg.name)
|| scope.conflicting_def(arg.name)) {
|| scope.defun_get_root_variable(arg.name)) {
return false;
}
if (in_loop) in_loop.push(arg.definition());
Expand All @@ -536,7 +535,7 @@ export function inline_into_call(self, compressor) {
if (name instanceof AST_Destructuring
|| block_scoped.has(name.name)
|| identifier_atom.has(name.name)
|| scope.conflicting_def(name.name)) {
|| scope.defun_get_root_variable(name.name)) {
return false;
}
if (in_loop) in_loop.push(name.definition());
Expand All @@ -549,7 +548,7 @@ export function inline_into_call(self, compressor) {
var block_scoped = new Set();
do {
scope = compressor.parent(++level);
if (scope.is_block_scope() && scope.block_scope) {
if (scope.block_scope && scope.block_scope._block_scope) {
// TODO this is sometimes undefined during compression.
// But it should always have a value!
scope.block_scope.variables.forEach(function (variable) {
Expand All @@ -568,6 +567,8 @@ export function inline_into_call(self, compressor) {
}
} while (!(scope instanceof AST_Scope));

if (scope instanceof AST_Class) return false;

var safe_to_inject = !(scope instanceof AST_Toplevel) || compressor.toplevel.vars;
var inline = compressor.option("inline");
if (!can_inject_vars(block_scoped, inline >= 3 && safe_to_inject)) return false;
Expand All @@ -579,9 +580,9 @@ export function inline_into_call(self, compressor) {
var def = name.definition();

// Name already exists, only when a function argument had the same name
const already_appended = scope.variables.has(name.name);
const already_appended = scope.defun_get_root_variable(name.name);
if (!already_appended) {
scope.variables.set(name.name, def);
scope.defun_set_block_variable(name.name, def);
scope.enclosed.push(def);
decls.push(make_node(AST_VarDef, name, {
name: name,
Expand Down Expand Up @@ -632,7 +633,7 @@ export function inline_into_call(self, compressor) {
if (in_loop && fn.argnames.every((argname) =>
argname.name != name.name
)) {
var def = fn.variables.get(name.name);
var def = fn.defun_get_root_variable(name.name);
var sym = make_node(AST_SymbolRef, name, name);
def.references.push(sym);
expressions.splice(pos++, 0, make_node(AST_Assign, var_def, {
Expand Down
40 changes: 28 additions & 12 deletions lib/compress/reduce-vars.js
Expand Up @@ -147,10 +147,21 @@ function reset_variables(tw, compressor, node) {
});
}

function reset_block_variables(compressor, node) {
if (node.block_scope) node.block_scope.variables.forEach((def) => {
reset_def(compressor, def);
});
function reset_block_variables(tw, compressor, node, is_defun = false) {
if (node.block_scope) {
node.block_scope.variables.forEach((def) => {
reset_def(compressor, def);
if (is_defun) {
if (def.fixed === null) {
tw.defs_to_safe_ids.set(def.id, tw.safe_ids);
mark(tw, def, true);
} else if (def.fixed) {
tw.loop_ids.set(def.id, tw.in_loop);
mark(tw, def, true);
}
}
});
}
}

function push(tw) {
Expand Down Expand Up @@ -362,7 +373,7 @@ def_reduce_vars(AST_Binary, function(tw) {
});

def_reduce_vars(AST_Block, function(tw, descend, compressor) {
reset_block_variables(compressor, this);
reset_block_variables(tw, compressor, this);
});

def_reduce_vars(AST_Case, function(tw) {
Expand All @@ -384,7 +395,7 @@ def_reduce_vars(AST_Class, function(tw, descend) {
});

def_reduce_vars(AST_ClassStaticBlock, function(tw, descend, compressor) {
reset_block_variables(compressor, this);
reset_block_variables(tw, compressor, this);
});

def_reduce_vars(AST_Conditional, function(tw) {
Expand Down Expand Up @@ -479,7 +490,12 @@ function mark_lambda(tw, descend, compressor) {
});
}

descend();
if (this.name) this.name.walk(tw);
this.walk_argnames(tw);

reset_block_variables(tw, compressor, this, true);
this.walk_body(tw);

pop(tw);

handle_defined_after_hoist(this);
Expand Down Expand Up @@ -624,7 +640,7 @@ function handle_defined_after_hoist(parent) {
def_reduce_vars(AST_Lambda, mark_lambda);

def_reduce_vars(AST_Do, function(tw, descend, compressor) {
reset_block_variables(compressor, this);
reset_block_variables(tw, compressor, this);
const saved_loop = tw.in_loop;
tw.in_loop = this;
push(tw);
Expand All @@ -640,7 +656,7 @@ def_reduce_vars(AST_Do, function(tw, descend, compressor) {
});

def_reduce_vars(AST_For, function(tw, descend, compressor) {
reset_block_variables(compressor, this);
reset_block_variables(tw, compressor, this);
if (this.init) this.init.walk(tw);
const saved_loop = tw.in_loop;
tw.in_loop = this;
Expand All @@ -660,7 +676,7 @@ def_reduce_vars(AST_For, function(tw, descend, compressor) {
});

def_reduce_vars(AST_ForIn, function(tw, descend, compressor) {
reset_block_variables(compressor, this);
reset_block_variables(tw, compressor, this);
suppress(this.init);
this.object.walk(tw);
const saved_loop = tw.in_loop;
Expand Down Expand Up @@ -747,7 +763,7 @@ def_reduce_vars(AST_Toplevel, function(tw, descend, compressor) {
});

def_reduce_vars(AST_Try, function(tw, descend, compressor) {
reset_block_variables(compressor, this);
reset_block_variables(tw, compressor, this);
push(tw);
this.body.walk(tw);
pop(tw);
Expand Down Expand Up @@ -813,7 +829,7 @@ def_reduce_vars(AST_VarDef, function(tw, descend) {
});

def_reduce_vars(AST_While, function(tw, descend, compressor) {
reset_block_variables(compressor, this);
reset_block_variables(tw, compressor, this);
const saved_loop = tw.in_loop;
tw.in_loop = this;
push(tw);
Expand Down
2 changes: 1 addition & 1 deletion lib/compress/tighten-body.js
Expand Up @@ -551,7 +551,7 @@ export function tighten_body(statements, compressor) {
arg.walk(new TreeWalker(function (node, descend) {
if (found)
return true;
if (node instanceof AST_SymbolRef && (fn.variables.has(node.name) || redefined_within_scope(node.definition(), fn))) {
if (node instanceof AST_SymbolRef && (fn.defun_get_root_variable(node.name) || redefined_within_scope(node.definition(), fn))) {
var s = node.definition().scope;
if (s !== defun_scope)
while (s = s.parent_scope) {
Expand Down