Skip to content

Commit

Permalink
support class static initialization block (#5488)
Browse files Browse the repository at this point in the history
  • Loading branch information
alexlamsl committed Jun 6, 2022
1 parent d2bd0d1 commit 88b4283
Show file tree
Hide file tree
Showing 6 changed files with 304 additions and 31 deletions.
23 changes: 20 additions & 3 deletions lib/ast.js
Original file line number Diff line number Diff line change
Expand Up @@ -534,7 +534,7 @@ var AST_With = DEFNODE("With", "expression", {
/* -----[ scope and functions ]----- */

var AST_Scope = DEFNODE("Scope", "fn_defs may_call_this uses_eval uses_with", {
$documentation: "Base class for all statements introducing a lexical scope",
$documentation: "Base class for all statements introducing a lambda scope",
$propdoc: {
uses_eval: "[boolean/S] tells whether this scope contains a direct call to the global `eval`",
uses_with: "[boolean/S] tells whether this scope uses the `with` statement",
Expand Down Expand Up @@ -592,6 +592,10 @@ var AST_Toplevel = DEFNODE("Toplevel", "globals", {
}
}, AST_Scope);

var AST_ClassInitBlock = DEFNODE("ClassInitBlock", null, {
$documentation: "Value for `class` static initialization blocks",
}, AST_Scope);

var AST_Lambda = DEFNODE("Lambda", "argnames length_read rest safe_ids uses_arguments", {
$documentation: "Base class for functions",
$propdoc: {
Expand Down Expand Up @@ -874,7 +878,7 @@ var AST_ClassExpression = DEFNODE("ClassExpression", null, {
var AST_ClassProperty = DEFNODE("ClassProperty", "key private static value", {
$documentation: "Base class for `class` properties",
$propdoc: {
key: "[string|AST_Node] property name (AST_Node for computed property)",
key: "[string|AST_Node?] property name (AST_Node for computed property, null for initialization block)",
private: "[boolean] whether this is a private property",
static: "[boolean] whether this is a static property",
value: "[AST_Node?] property value (AST_Accessor for getters/setters, AST_LambdaExpression for methods, null if not specified for fields)",
Expand All @@ -888,7 +892,9 @@ var AST_ClassProperty = DEFNODE("ClassProperty", "key private static value", {
},
_validate: function() {
if (this.TYPE == "ClassProperty") throw new Error("should not instantiate AST_ClassProperty");
if (typeof this.key != "string") {
if (this instanceof AST_ClassInit) {
if (this.key != null) throw new Error("key must be null");
} else if (typeof this.key != "string") {
if (!(this.key instanceof AST_Node)) throw new Error("key must be string or AST_Node");
must_be_expression(this, "key");
}
Expand Down Expand Up @@ -928,6 +934,17 @@ var AST_ClassMethod = DEFNODE("ClassMethod", null, {
},
}, AST_ClassProperty);

var AST_ClassInit = DEFNODE("ClassInit", null, {
$documentation: "A `class` static initialization block",
_validate: function() {
if (!this.static) throw new Error("static must be true");
if (!(this.value instanceof AST_ClassInitBlock)) throw new Error("value must be AST_ClassInitBlock");
},
initialize: function() {
this.static = true;
},
}, AST_ClassProperty);

/* -----[ JUMPS ]----- */

var AST_Jump = DEFNODE("Jump", null, {
Expand Down
76 changes: 49 additions & 27 deletions lib/compress.js
Original file line number Diff line number Diff line change
Expand Up @@ -1146,7 +1146,7 @@ Compressor.prototype.compress = function(node) {
}
}
props.forEach(function(prop) {
if (!prop.static || prop instanceof AST_ClassField && prop.value.contains_this()) {
if (!prop.static || is_static_field_or_init(prop) && prop.value.contains_this()) {
push(tw);
prop.value.walk(tw);
pop(tw);
Expand All @@ -1156,6 +1156,14 @@ Compressor.prototype.compress = function(node) {
});
return true;
});
def(AST_ClassInitBlock, function(tw, descend, compressor) {
var node = this;
push(tw);
reset_variables(tw, compressor, node);
descend();
pop_scope(tw, node);
return true;
});
def(AST_Conditional, function(tw) {
this.condition.walk(tw);
push(tw);
Expand Down Expand Up @@ -1843,6 +1851,10 @@ Compressor.prototype.compress = function(node) {
|| compressor.option("unsafe") && global_names[this.name];
});

function is_static_field_or_init(prop) {
return prop.static && prop.value && (prop instanceof AST_ClassField || prop instanceof AST_ClassInit);
}

function declarations_only(node) {
return all(node.definitions, function(var_def) {
return !var_def.value;
Expand All @@ -1852,8 +1864,7 @@ Compressor.prototype.compress = function(node) {
function is_declaration(stat, lexical) {
if (stat instanceof AST_DefClass) return lexical && !stat.extends && all(stat.properties, function(prop) {
if (prop.key instanceof AST_Node) return false;
if (prop instanceof AST_ClassField && prop.static && prop.value) return false;
return true;
return !is_static_field_or_init(prop);
});
if (stat instanceof AST_Definitions) return (lexical || stat instanceof AST_Var) && declarations_only(stat);
if (stat instanceof AST_ExportDeclaration) return is_declaration(stat.body, lexical);
Expand Down Expand Up @@ -6651,7 +6662,7 @@ Compressor.prototype.compress = function(node) {
if (prop.key instanceof AST_Node) prop.key.walk(tw);
var value = prop.value;
if (!value) return;
if (prop instanceof AST_ClassField && prop.static) {
if (is_static_field_or_init(prop)) {
if (!used && value.contains_this()) used = true;
walk_class_prop(value);
} else {
Expand Down Expand Up @@ -8595,16 +8606,20 @@ Compressor.prototype.compress = function(node) {
});
def(AST_ClassExpression, function(compressor, first_in_statement) {
var self = this;
var exprs = [], values = [];
var exprs = [], values = [], init = 0;
var props = self.properties;
for (var i = 0; i < props.length; i++) {
var prop = props[i];
if (prop.key instanceof AST_Node) exprs.push(prop.key);
if (prop.static && prop.value
&& prop instanceof AST_ClassField
&& prop.value.has_side_effects(compressor)) {
if (prop.value.contains_this()) return self;
values.push(prop.value);
if (!is_static_field_or_init(prop)) continue;
var value = prop.value;
if (!value.has_side_effects(compressor)) continue;
if (value.contains_this()) return self;
if (prop instanceof AST_ClassInit) {
init++;
values.push(prop);
} else {
values.push(value);
}
}
var base = self.extends;
Expand All @@ -8623,33 +8638,40 @@ Compressor.prototype.compress = function(node) {
if (base || self.name || !compressor.has_directive("use strict")) {
var node = to_class_expr(self);
if (!base) node.extends = null;
node.properties = [];
if (values) {
node.properties.push(make_node(AST_ClassField, self, {
static: true,
key: exprs.length ? make_sequence(self, exprs) : "c",
value: make_sequence(self, values),
}));
} else if (exprs.length) {
node.properties.push(make_node(AST_ClassMethod, self, {
key: make_sequence(self, exprs),
value: make_node(AST_Function, self, {
argnames: [],
body: [],
}).init_vars(node),
}));
}
node.properties = values ? values.length == init ? values : [ make_node(AST_ClassField, self, {
static: true,
key: exprs.length ? make_sequence(self, exprs) : "c",
value: make_value(),
}) ] : exprs.length ? [ make_node(AST_ClassMethod, self, {
key: make_sequence(self, exprs),
value: make_node(AST_Function, self, {
argnames: [],
body: [],
}).init_vars(node),
}) ] : [];
return node;
}
if (values) exprs.push(make_node(AST_Call, self, {
expression: make_node(AST_Arrow, self, {
argnames: [],
body: [],
value: make_sequence(self, values),
value: make_value(),
}).init_vars(self.parent_scope),
args: [],
}));
return make_sequence(self, exprs);

function make_value() {
return make_sequence(self, values.map(function(node) {
if (!(node instanceof AST_ClassInit)) return node;
var fn = make_node(AST_Arrow, node, node.value);
fn.argnames = [];
return make_node(AST_Call, node, {
expression: fn,
args: [],
});
}));
}
});
def(AST_Conditional, function(compressor) {
var consequent = this.consequent.drop_side_effect_free(compressor);
Expand Down
5 changes: 5 additions & 0 deletions lib/output.js
Original file line number Diff line number Diff line change
Expand Up @@ -1257,6 +1257,11 @@ function OutputStream(options) {
}
print_method(self, output);
});
DEFPRINT(AST_ClassInit, function(output) {
output.print("static");
output.space();
print_braced(this.value, output);
});

/* -----[ jumps ]----- */
function print_jump(kind, prop) {
Expand Down
12 changes: 12 additions & 0 deletions lib/parse.js
Original file line number Diff line number Diff line change
Expand Up @@ -1119,6 +1119,18 @@ function parse($TEXT, options) {
}));
continue;
}
if (fixed && is("punc", "{")) {
props.push(new AST_ClassInit({
start: start,
value: new AST_ClassInitBlock({
start: start,
body: block_(),
end: prev(),
}),
end: prev(),
}));
continue;
}
var internal = is("name") && /^#/.test(S.token.value);
var key = as_property_key();
if (is("punc", "(")) {
Expand Down
Loading

0 comments on commit 88b4283

Please sign in to comment.