diff --git a/.github/workflows/ruby.yml b/.github/workflows/ruby.yml index 99628ec..9ce8908 100644 --- a/.github/workflows/ruby.yml +++ b/.github/workflows/ruby.yml @@ -16,16 +16,15 @@ jobs: fail-fast: true matrix: include: - - { ruby: '3.0', c-o-e: false } - - { ruby: 3.1, c-o-e: false } - { ruby: 3.2, c-o-e: false } - { ruby: 3.3, c-o-e: false } - - { ruby: jruby-9.4.9.0, c-o-e: false } + - { ruby: 3.4, c-o-e: false } + - { ruby: jruby-10.0.0.1, c-o-e: false } - { ruby: ruby-head, c-o-e: true } - { ruby: jruby-head, c-o-e: true } - { ruby: truffleruby, c-o-e: true } - { ruby: truffleruby-head, c-o-e: true } - - { ruby: 3.3, gemfile: ./gemfiles/miniracer, c-o-e: false } + - { ruby: 3.4, gemfile: ./gemfiles/miniracer, c-o-e: false } steps: - uses: actions/checkout@v3 - name: Set up Ruby diff --git a/.rubocop.yml b/.rubocop.yml index e7f31b1..c344ac7 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -174,6 +174,8 @@ Lint/EmptyClass: Enabled: true Style/NilLambda: Enabled: true +Style/UnpackFirst: + Enabled: false Layout/LineEndStringConcatenationIndentation: # (new in 1.18) Enabled: true Layout/SpaceBeforeBrackets: # (new in 1.7) diff --git a/CHANGELOG.md b/CHANGELOG.md index dc5c03c..b9566db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ Behavioural changes in TerserJS are listed [here](https://github.com/terser/terser/blob/master/CHANGELOG.md). ## Unreleased +## 1.2.6 (19 June 2025) +- update TerserJS to [5.43.1] +- remove base64 for compatibility with Ruby 3.4 + ## 1.2.5 (20 January 2025) - update TerserJS to [5.37.0] diff --git a/lib/terser.js b/lib/terser.js index 11d45d7..b47a3fa 100644 --- a/lib/terser.js +++ b/lib/terser.js @@ -329,7 +329,6 @@ ALL_RESERVED_WORDS = makePredicate(ALL_RESERVED_WORDS); var OPERATOR_CHARS = makePredicate(characters("+-*&%=<>!?|~^")); -var RE_NUM_LITERAL = /[0-9a-f]/i; var RE_HEX_NUMBER = /^0x[0-9a-f]+$/i; var RE_OCT_NUMBER = /^0[0-7]+$/; var RE_ES6_OCT_NUMBER = /^0o[0-7]+$/i; @@ -694,15 +693,16 @@ function tokenizer($TEXT, filename, html5_comments, shebang) { return after_e; case (after_e = false, 46): // . return (!has_dot && !has_x && !has_e) ? (has_dot = true) : false; - } - - if (ch === "n") { + case 110: // n is_big_int = true; - return true; } - return RE_NUM_LITERAL.test(ch); + return ( + code >= 48 && code <= 57 // 0-9 + || code >= 97 && code <= 102 // a-f + || code >= 65 && code <= 70 // A-F + ); }); if (prefix) num = prefix + num; @@ -719,7 +719,7 @@ function tokenizer($TEXT, filename, html5_comments, shebang) { } num = num.replace(/_/g, ""); } - if (num.endsWith("n")) { + if (is_big_int) { const without_n = num.slice(0, -1); const allow_e = RE_HEX_NUMBER.test(without_n); const valid = parse_js_number(without_n, allow_e); @@ -843,7 +843,7 @@ function tokenizer($TEXT, filename, html5_comments, shebang) { } else if (ch == "$" && peek() == "{") { next(true, true); S.brace_counter++; - tok = token(begin ? "template_head" : "template_substitution", content); + tok = token(begin ? "template_head" : "template_cont", content); TEMPLATE_RAWS.set(tok, raw); tok.template_end = false; return tok; @@ -860,7 +860,7 @@ function tokenizer($TEXT, filename, html5_comments, shebang) { content += ch; } S.template_braces.pop(); - tok = token(begin ? "template_head" : "template_substitution", content); + tok = token(begin ? "template_head" : "template_cont", content); TEMPLATE_RAWS.set(tok, raw); tok.template_end = true; return tok; @@ -894,7 +894,24 @@ function tokenizer($TEXT, filename, html5_comments, shebang) { return next_token; }); - var read_name = with_eof_error("Unterminated identifier name", function() { + var read_name = function () { + let start = S.pos, end = start - 1, ch = "c"; + + while ( + (ch = S.text.charAt(++end)) + && (ch >= "a" && ch <= "z" || ch >= "A" && ch <= "Z") + ); + + if (end > start + 1 && ch && ch !== "\\" && !is_identifier_char(ch)) { + S.pos += end - start; + S.col += end - start; + return S.text.slice(start, S.pos); + } + + return read_name_hard(); + }; + + var read_name_hard = with_eof_error("Unterminated identifier name", function() { var name = [], ch, escaped = false; var read_escaped_identifier_char = function() { escaped = true; @@ -1685,24 +1702,19 @@ function parse($TEXT, options) { var body = _function_body(is("punc", "{"), false, is_async); - var end = - body instanceof Array && body.length ? body[body.length - 1].end : - body instanceof Array ? start : - body.end; - return new AST_Arrow({ start : start, - end : end, + end : body.end, async : is_async, argnames : argnames, body : body }); }; - var function_ = function(ctor, is_generator_property, is_async, is_export_default) { + var function_ = function(ctor, is_generator, is_async, is_export_default) { var in_statement = ctor === AST_Defun; - var is_generator = is("operator", "*"); - if (is_generator) { + if (is("operator", "*")) { + is_generator = true; next(); } @@ -1719,7 +1731,7 @@ function parse($TEXT, options) { unexpected(prev()); var args = []; - var body = _function_body(true, is_generator || is_generator_property, is_async, name, args); + var body = _function_body(true, is_generator, is_async, name, args); return new ctor({ start : args.start, end : body.end, @@ -2111,11 +2123,6 @@ function parse($TEXT, options) { } function _yield_expression() { - // Previous token must be keyword yield and not be interpret as an identifier - if (!is_in_generator()) { - croak("Unexpected yield expression outside generator function", - S.prev.line, S.prev.col, S.prev.pos); - } var start = S.token; var star = false; var has_expression = true; @@ -2130,10 +2137,12 @@ function parse($TEXT, options) { // Note 1: It isn't allowed for yield* to close without an expression // Note 2: If there is a nlb between yield and star, it is interpret as // yield * - if (can_insert_semicolon() || - (is("punc") && PUNC_AFTER_EXPRESSION.has(S.token.value))) { + if ( + can_insert_semicolon() + || is("punc") && PUNC_AFTER_EXPRESSION.has(S.token.value) + || is("template_cont") + ) { has_expression = false; - } else if (is("operator", "*")) { star = true; next(); @@ -2354,7 +2363,12 @@ function parse($TEXT, options) { }); break; case "big_int": - ret = new AST_BigInt({ start: tok, end: tok, value: tok.value }); + ret = new AST_BigInt({ + start: tok, + end: tok, + value: tok.value, + raw: LATEST_RAW, + }); break; case "string": ret = new AST_String({ @@ -2618,7 +2632,7 @@ function parse($TEXT, options) { // Check property and fetch value if (!is("punc", ":")) { - var concise = concise_method_or_getset(name, start); + var concise = object_or_class_property(name, start); if (concise) { a.push(concise); continue; @@ -2653,7 +2667,7 @@ function parse($TEXT, options) { const kv = new AST_ObjectKeyVal({ start: start, quote: start.quote, - key: name instanceof AST_Node ? name : "" + name, + key: name, value: value, end: prev() }); @@ -2664,7 +2678,7 @@ function parse($TEXT, options) { }); function class_(KindOfClass, is_export_default) { - var start, method, class_name, extends_, a = []; + var start, method, class_name, extends_, properties = []; S.input.push_directives_stack(); // Push directive stack, but not scope stack S.input.add_directive("use strict"); @@ -2693,9 +2707,9 @@ function parse($TEXT, options) { while (is("punc", ";")) { next(); } // Leading semicolons are okay in class bodies. while (!is("punc", "}")) { start = S.token; - method = concise_method_or_getset(as_property_name(), start, true); + method = object_or_class_property(as_property_name(), start, true); if (!method) { unexpected(); } - a.push(method); + properties.push(method); while (is("punc", ";")) { next(); } } // mark in class feild, @@ -2709,19 +2723,15 @@ function parse($TEXT, options) { start: start, name: class_name, extends: extends_, - properties: a, + properties: properties, end: prev(), }); } - function concise_method_or_getset(name, start, is_class) { - const get_symbol_ast = (name, SymbolClass = AST_SymbolMethod) => { - if (typeof name === "string" || typeof name === "number") { - return new SymbolClass({ - start, - name: "" + name, - end: prev() - }); + function object_or_class_property(name, start, is_class) { + const get_symbol_ast = (name, SymbolClass) => { + if (typeof name === "string") { + return new SymbolClass({ start, name, end: prev() }); } else if (name === null) { unexpected(); } @@ -2769,7 +2779,7 @@ function parse($TEXT, options) { ? AST_ObjectGetter : AST_ObjectSetter; - name = get_symbol_ast(name); + name = get_symbol_ast(name, AST_SymbolMethod); return annotate(new AccessorClass({ start, static: is_static, @@ -2786,7 +2796,7 @@ function parse($TEXT, options) { return annotate(new AccessorClass({ start, static: is_static, - key: get_symbol_ast(name), + key: get_symbol_ast(name, AST_SymbolMethod), value: create_accessor(), end: prev(), })); @@ -2794,15 +2804,13 @@ function parse($TEXT, options) { } if (is("punc", "(")) { - name = get_symbol_ast(name); + name = get_symbol_ast(name, AST_SymbolMethod); const AST_MethodVariant = is_private ? AST_PrivateMethod : AST_ConciseMethod; var node = new AST_MethodVariant({ start : start, static : is_static, - is_generator: is_generator, - async : is_async, key : name, quote : name instanceof AST_SymbolMethod ? property_token.quote : undefined, @@ -2813,13 +2821,17 @@ function parse($TEXT, options) { } if (is_class) { - const key = get_symbol_ast(name, AST_SymbolClassProperty); - const quote = key instanceof AST_SymbolClassProperty - ? property_token.quote - : undefined; + const AST_SymbolVariant = is_private + ? AST_SymbolPrivateProperty + : AST_SymbolClassProperty; const AST_ClassPropertyVariant = is_private ? AST_ClassPrivateProperty : AST_ClassProperty; + + const key = get_symbol_ast(name, AST_SymbolVariant); + const quote = key instanceof AST_SymbolClassProperty + ? property_token.quote + : undefined; if (is("operator", "=")) { next(); return annotate( @@ -2839,6 +2851,9 @@ function parse($TEXT, options) { || is("operator", "*") || is("punc", ";") || is("punc", "}") + || is("string") + || is("num") + || is("big_int") ) { return annotate( new AST_ClassPropertyVariant({ @@ -2963,10 +2978,12 @@ function parse($TEXT, options) { } else { foreign_name = make_symbol(foreign_type, S.token.quote); } - } else if (is_import) { - name = new type(foreign_name); } else { - foreign_name = new foreign_type(name); + if (is_import) { + name = new type(foreign_name); + } else { + foreign_name = new foreign_type(name); + } } return new AST_NameMapping({ @@ -3137,12 +3154,14 @@ function parse($TEXT, options) { case "name": case "privatename": case "string": - case "num": - case "big_int": case "keyword": case "atom": next(); return tmp.value; + case "num": + case "big_int": + next(); + return "" + tmp.value; default: unexpected(tmp); } @@ -3286,6 +3305,7 @@ function parse($TEXT, options) { return subscripts(call, true, is_chain); } + // Optional chain if (is("punc", "?.")) { next(); @@ -5629,6 +5649,11 @@ var AST_Object = DEFNODE("Object", "properties", function AST_Object(props) { }, }); +/* -----[ OBJECT/CLASS PROPERTIES ]----- */ + +/** + * Everything inside the curly braces of an object/class is a subclass of AST_ObjectProperty, except for AST_ClassStaticBlock. + **/ var AST_ObjectProperty = DEFNODE("ObjectProperty", "key value", function AST_ObjectProperty(props) { if (props) { this.key = props.key; @@ -5643,7 +5668,7 @@ var AST_ObjectProperty = DEFNODE("ObjectProperty", "key value", function AST_Obj $documentation: "Base class for literal object properties", $propdoc: { key: "[string|AST_Node] property name. For ObjectKeyVal this is a string. For getters, setters and computed property this is an AST_Node.", - value: "[AST_Node] property value. For getters and setters this is an AST_Accessor." + value: "[AST_Node] property value. For getters, setters and methods this is an AST_Accessor." }, _walk: function(visitor) { return visitor._visit(this, function() { @@ -5655,7 +5680,7 @@ var AST_ObjectProperty = DEFNODE("ObjectProperty", "key value", function AST_Obj _children_backwards(push) { push(this.value); if (this.key instanceof AST_Node) push(this.key); - } + }, }); var AST_ObjectKeyVal = DEFNODE("ObjectKeyVal", "quote", function AST_ObjectKeyVal(props) { @@ -5765,45 +5790,32 @@ var AST_ObjectGetter = DEFNODE("ObjectGetter", "quote static", function AST_Obje } }, AST_ObjectProperty); -var AST_ConciseMethod = DEFNODE( - "ConciseMethod", - "quote static is_generator async", - function AST_ConciseMethod(props) { - if (props) { - this.quote = props.quote; - this.static = props.static; - this.is_generator = props.is_generator; - this.async = props.async; - this.key = props.key; - this.value = props.value; - this.start = props.start; - this.end = props.end; - this._annotations = props._annotations; - } +var AST_ConciseMethod = DEFNODE("ConciseMethod", "quote static", function AST_ConciseMethod(props) { + if (props) { + this.quote = props.quote; + this.static = props.static; + this.key = props.key; + this.value = props.value; + this.start = props.start; + this.end = props.end; + this._annotations = props._annotations; + } - this.flags = 0; - }, - { - $propdoc: { - quote: "[string|undefined] the original quote character, if any", - static: "[boolean] is this method static (classes only)", - is_generator: "[boolean] is this a generator method", - async: "[boolean] is this method async", - }, - $documentation: "An ES6 concise method inside an object or class", - computed_key() { - return !(this.key instanceof AST_SymbolMethod); - } + this.flags = 0; +}, { + $propdoc: { + quote: "[string|undefined] the original quote character, if any", + static: "[boolean] is this method static (classes only)", }, - AST_ObjectProperty -); + $documentation: "An ES6 concise method inside an object or class", + computed_key() { + return !(this.key instanceof AST_SymbolMethod); + } +}, AST_ObjectProperty); -var AST_PrivateMethod = DEFNODE("PrivateMethod", "", function AST_PrivateMethod(props) { +var AST_PrivateMethod = DEFNODE("PrivateMethod", "static", function AST_PrivateMethod(props) { if (props) { - this.quote = props.quote; this.static = props.static; - this.is_generator = props.is_generator; - this.async = props.async; this.key = props.key; this.value = props.value; this.start = props.start; @@ -5813,7 +5825,13 @@ var AST_PrivateMethod = DEFNODE("PrivateMethod", "", function AST_PrivateMethod( this.flags = 0; }, { $documentation: "A private class method inside a class", -}, AST_ConciseMethod); + $propdoc: { + static: "[boolean] is this a static private method", + }, + computed_key() { + return false; + }, +}, AST_ObjectProperty); var AST_Class = DEFNODE("Class", "name extends properties", function AST_Class(props) { if (props) { @@ -5837,7 +5855,7 @@ var AST_Class = DEFNODE("Class", "name extends properties", function AST_Class(p $propdoc: { name: "[AST_SymbolClass|AST_SymbolDefClass?] optional class name.", extends: "[AST_Node]? optional parent class", - properties: "[AST_ObjectProperty*] array of properties" + properties: "[AST_ObjectProperty|AST_ClassStaticBlock]* array of properties or static blocks" }, $documentation: "An ES6 class", _walk: function(visitor) { @@ -5872,7 +5890,10 @@ var AST_Class = DEFNODE("Class", "name extends properties", function AST_Class(p prop.key._walk(visitor); visitor.pop(); } - if ((prop instanceof AST_ClassPrivateProperty || prop instanceof AST_ClassProperty) && prop.static && prop.value) { + if ( + prop instanceof AST_ClassPrivateProperty && prop.static && prop.value + || prop instanceof AST_ClassProperty && prop.static && prop.value + ) { visitor.push(prop); prop.value._walk(visitor); visitor.pop(); @@ -5882,9 +5903,15 @@ var AST_Class = DEFNODE("Class", "name extends properties", function AST_Class(p /** go through the bits that are executed later, when the class is `new`'d or a static method is called */ visit_deferred_class_parts(visitor) { this.properties.forEach((prop) => { - if (prop instanceof AST_ConciseMethod) { + if ( + prop instanceof AST_ConciseMethod + || prop instanceof AST_PrivateMethod + ) { prop.walk(visitor); - } else if (prop instanceof AST_ClassProperty && !prop.static && prop.value) { + } else if ( + prop instanceof AST_ClassProperty && !prop.static && prop.value + || prop instanceof AST_ClassPrivateProperty && !prop.static && prop.value + ) { visitor.push(prop); prop.value._walk(visitor); visitor.pop(); @@ -5949,7 +5976,6 @@ var AST_ClassProperty = DEFNODE("ClassProperty", "static quote", function AST_Cl var AST_ClassPrivateProperty = DEFNODE("ClassPrivateProperty", "", function AST_ClassPrivateProperty(props) { if (props) { this.static = props.static; - this.quote = props.quote; this.key = props.key; this.value = props.value; this.start = props.start; @@ -5959,7 +5985,19 @@ var AST_ClassPrivateProperty = DEFNODE("ClassPrivateProperty", "", function AST_ this.flags = 0; }, { $documentation: "A class property for a private property", -}, AST_ClassProperty); + _walk: function(visitor) { + return visitor._visit(this, function() { + if (this.value instanceof AST_Node) + this.value._walk(visitor); + }); + }, + _children_backwards(push) { + if (this.value instanceof AST_Node) push(this.value); + }, + computed_key() { + return false; + }, +}, AST_ObjectProperty); var AST_PrivateIn = DEFNODE("PrivateIn", "key value", function AST_PrivateIn(props) { if (props) { @@ -6026,7 +6064,9 @@ var AST_ClassStaticBlock = DEFNODE("ClassStaticBlock", "body block_scope", funct while (i--) push(this.body[i]); }, clone: clone_block_scope, - computed_key: () => false + computed_key() { + return false; + }, }, AST_Scope); var AST_ClassExpression = DEFNODE("ClassExpression", null, function AST_ClassExpression(props) { @@ -6295,12 +6335,12 @@ var AST_SymbolImport = DEFNODE("SymbolImport", null, function AST_SymbolImport(p $documentation: "Symbol referring to an imported name", }, AST_SymbolBlockDeclaration); -var AST_SymbolImportForeign = DEFNODE("SymbolImportForeign", null, function AST_SymbolImportForeign(props) { +var AST_SymbolImportForeign = DEFNODE("SymbolImportForeign", "quote", function AST_SymbolImportForeign(props) { if (props) { + this.quote = props.quote; this.scope = props.scope; this.name = props.name; this.thedef = props.thedef; - this.quote = props.quote; this.start = props.start; this.end = props.end; } @@ -6347,12 +6387,12 @@ var AST_SymbolRef = DEFNODE("SymbolRef", null, function AST_SymbolRef(props) { $documentation: "Reference to some symbol (not definition/declaration)", }, AST_Symbol); -var AST_SymbolExport = DEFNODE("SymbolExport", null, function AST_SymbolExport(props) { +var AST_SymbolExport = DEFNODE("SymbolExport", "quote", function AST_SymbolExport(props) { if (props) { + this.quote = props.quote; this.scope = props.scope; this.name = props.name; this.thedef = props.thedef; - this.quote = props.quote; this.start = props.start; this.end = props.end; } @@ -6362,12 +6402,12 @@ var AST_SymbolExport = DEFNODE("SymbolExport", null, function AST_SymbolExport(p $documentation: "Symbol referring to a name to export", }, AST_SymbolRef); -var AST_SymbolExportForeign = DEFNODE("SymbolExportForeign", null, function AST_SymbolExportForeign(props) { +var AST_SymbolExportForeign = DEFNODE("SymbolExportForeign", "quote", function AST_SymbolExportForeign(props) { if (props) { + this.quote = props.quote; this.scope = props.scope; this.name = props.name; this.thedef = props.thedef; - this.quote = props.quote; this.start = props.start; this.end = props.end; } @@ -6482,9 +6522,10 @@ var AST_Number = DEFNODE("Number", "value raw", function AST_Number(props) { } }, AST_Constant); -var AST_BigInt = DEFNODE("BigInt", "value", function AST_BigInt(props) { +var AST_BigInt = DEFNODE("BigInt", "value raw", function AST_BigInt(props) { if (props) { this.value = props.value; + this.raw = props.raw; this.start = props.start; this.end = props.end; } @@ -6493,7 +6534,8 @@ var AST_BigInt = DEFNODE("BigInt", "value", function AST_BigInt(props) { }, { $documentation: "A big int literal", $propdoc: { - value: "[string] big int value" + value: "[string] big int value, represented as a string", + raw: "[string] the original format preserved" } }, AST_Constant); @@ -6776,6 +6818,28 @@ class TreeWalker { } } + is_within_loop() { + let i = this.stack.length - 1; + let child = this.stack[i]; + while (i--) { + const node = this.stack[i]; + + if (node instanceof AST_Lambda) return false; + if ( + node instanceof AST_IterationStatement + // exclude for-loop bits that only run once + && !((node instanceof AST_For) && child === node.init) + && !((node instanceof AST_ForIn || node instanceof AST_ForOf) && child === node.object) + ) { + return true; + } + + child = node; + } + + return false; + } + find_scope() { var stack = this.stack; for (var i = stack.length; --i >= 0;) { @@ -7151,6 +7215,7 @@ def_transform(AST_PrefixedTemplateString, function(self, tw) { body[i] = new AST_Directive({ start: body[i].start, end: body[i].end, + quote: '"', value: body[i].body.value }); } else { @@ -7274,8 +7339,8 @@ def_transform(AST_PrefixedTemplateString, function(self, tw) { return new AST_Defun({ start: my_start_token(M), end: my_end_token(M), - name: from_moz(M.id), - argnames: M.params.map(from_moz), + name: M.id && from_moz_symbol(AST_SymbolDefun, M.id), + argnames: M.params.map(M => from_moz_pattern(M, AST_SymbolFunarg)), is_generator: M.generator, async: M.async, body: normalize_directives(from_moz(M.body).body) @@ -7283,15 +7348,7 @@ def_transform(AST_PrefixedTemplateString, function(self, tw) { }, FunctionExpression: function(M) { - return new AST_Function({ - start: my_start_token(M), - end: my_end_token(M), - name: from_moz(M.id), - argnames: M.params.map(from_moz), - is_generator: M.generator, - async: M.async, - body: normalize_directives(from_moz(M.body).body) - }); + return from_moz_lambda(M, /*is_method=*/false); }, ArrowFunctionExpression: function(M) { @@ -7301,7 +7358,7 @@ def_transform(AST_PrefixedTemplateString, function(self, tw) { return new AST_Arrow({ start: my_start_token(M), end: my_end_token(M), - argnames: M.params.map(from_moz), + argnames: M.params.map(p => from_moz_pattern(p, AST_SymbolFunarg)), body, async: M.async, }); @@ -7330,57 +7387,48 @@ def_transform(AST_PrefixedTemplateString, function(self, tw) { }, Property: function(M) { - var key = M.key; - var args = { - start : my_start_token(key || M.value), - end : my_end_token(M.value), - key : key.type == "Identifier" ? key.name : key.value, - value : from_moz(M.value) - }; - if (M.computed) { - args.key = from_moz(M.key); - } - if (M.method) { - args.is_generator = M.value.generator; - args.async = M.value.async; - if (!M.computed) { - args.key = new AST_SymbolMethod({ name: args.key }); - } else { - args.key = from_moz(M.key); - } - return new AST_ConciseMethod(args); - } - if (M.kind == "init") { - if (key.type != "Identifier" && key.type != "Literal") { - args.key = from_moz(key); - } + if (M.kind == "init" && !M.method) { + var args = { + start : my_start_token(M.key || M.value), + end : my_end_token(M.value), + key : M.computed + ? from_moz(M.key) + : M.key.name || String(M.key.value), + quote : from_moz_quote(M.key, M.computed), + static : false, // always an object + value : from_moz(M.value) + }; + return new AST_ObjectKeyVal(args); - } - if (typeof args.key === "string" || typeof args.key === "number") { - args.key = new AST_SymbolMethod({ - name: args.key - }); - } - args.value = new AST_Accessor(args.value); - if (M.kind == "get") return new AST_ObjectGetter(args); - if (M.kind == "set") return new AST_ObjectSetter(args); - if (M.kind == "method") { - args.async = M.value.async; - args.is_generator = M.value.generator; - args.quote = M.computed ? "\"" : null; - return new AST_ConciseMethod(args); + } else { + var value = from_moz_lambda(M.value, /*is_method=*/true); + var args = { + start : my_start_token(M.key || M.value), + end : my_end_token(M.value), + key : M.computed + ? from_moz(M.key) + : from_moz_symbol(AST_SymbolMethod, M.key), + quote : from_moz_quote(M.key, M.computed), + static : false, // always an object + value, + }; + + if (M.kind == "get") return new AST_ObjectGetter(args); + if (M.kind == "set") return new AST_ObjectSetter(args); + if (M.method) return new AST_ConciseMethod(args); } }, MethodDefinition: function(M) { const is_private = M.key.type === "PrivateIdentifier"; - const key = M.computed ? from_moz(M.key) : new AST_SymbolMethod({ name: M.key.name || M.key.value }); + const key = M.computed ? from_moz(M.key) : new AST_SymbolMethod({ name: M.key.name || String(M.key.value) }); var args = { start : my_start_token(M), end : my_end_token(M), key, - value : from_moz(M.value), + quote : from_moz_quote(M.key, M.computed), + value : from_moz_lambda(M.value, /*is_method=*/true), static : M.static, }; if (M.kind == "get") { @@ -7389,8 +7437,6 @@ def_transform(AST_PrefixedTemplateString, function(self, tw) { if (M.kind == "set") { return new (is_private ? AST_PrivateSetter : AST_ObjectSetter)(args); } - args.is_generator = M.value.generator; - args.async = M.value.async; return new (is_private ? AST_PrivateMethod : AST_ConciseMethod)(args); }, @@ -7405,6 +7451,7 @@ def_transform(AST_PrefixedTemplateString, function(self, tw) { return new AST_ClassProperty({ start : my_start_token(M), end : my_end_token(M), + quote : from_moz_quote(M.key, M.computed), key, value : from_moz(M.value), static : M.static, @@ -7424,15 +7471,13 @@ def_transform(AST_PrefixedTemplateString, function(self, tw) { static : M.static, }); } else { - if (M.key.type !== "Identifier") { - throw new Error("Non-Identifier key in PropertyDefinition"); - } - key = from_moz(M.key); + key = from_moz_symbol(AST_SymbolClassProperty, M.key); } return new AST_ClassProperty({ start : my_start_token(M), end : my_end_token(M), + quote : from_moz_quote(M.key, M.computed), key, value : from_moz(M.value), static : M.static, @@ -7524,11 +7569,30 @@ def_transform(AST_PrefixedTemplateString, function(self, tw) { }, VariableDeclaration: function(M) { - return new (M.kind === "const" ? AST_Const : - M.kind === "let" ? AST_Let : AST_Var)({ + let decl_type; + let sym_type; + if (M.kind === "const") { + decl_type = AST_Const; + sym_type = AST_SymbolConst; + } else if (M.kind === "let") { + decl_type = AST_Let; + sym_type = AST_SymbolLet; + } else { + decl_type = AST_Var; + sym_type = AST_SymbolVar; + } + const definitions = M.declarations.map(M => { + return new AST_VarDef({ + start: my_start_token(M), + end: my_end_token(M), + name: from_moz_pattern(M.id, sym_type), + value: from_moz(M.init), + }); + }); + return new decl_type({ start : my_start_token(M), end : my_end_token(M), - definitions : M.declarations.map(from_moz) + definitions : definitions, }); }, @@ -7557,13 +7621,13 @@ def_transform(AST_PrefixedTemplateString, function(self, tw) { return new AST_NameMapping({ start: my_start_token(M), end: my_end_token(M), - foreign_name: from_moz(M.imported), - name: from_moz(M.local) + foreign_name: from_moz_symbol(AST_SymbolImportForeign, M.imported, M.imported.type === "Literal"), + name: from_moz_symbol(AST_SymbolImport, M.local) }); }, ImportDefaultSpecifier: function(M) { - return from_moz(M.local); + return from_moz_symbol(AST_SymbolImport, M.local); }, ImportNamespaceSpecifier: function(M) { @@ -7571,7 +7635,7 @@ def_transform(AST_PrefixedTemplateString, function(self, tw) { start: my_start_token(M), end: my_end_token(M), foreign_name: new AST_SymbolImportForeign({ name: "*" }), - name: from_moz(M.local) + name: from_moz_symbol(AST_SymbolImport, M.local) }); }, @@ -7593,15 +7657,17 @@ def_transform(AST_PrefixedTemplateString, function(self, tw) { }, ExportAllDeclaration: function(M) { - var foreign_name = M.exported == null ? + var foreign_name = M.exported == null ? new AST_SymbolExportForeign({ name: "*" }) : - from_moz(M.exported); + from_moz_symbol(AST_SymbolExportForeign, M.exported, M.exported.type === "Literal"); return new AST_Export({ start: my_start_token(M), end: my_end_token(M), exported_names: [ new AST_NameMapping({ - name: new AST_SymbolExportForeign({ name: "*" }), + start: my_start_token(M), + end: my_end_token(M), + name: new AST_SymbolExport({ name: "*" }), foreign_name: foreign_name }) ], @@ -7611,14 +7677,26 @@ def_transform(AST_PrefixedTemplateString, function(self, tw) { }, ExportNamedDeclaration: function(M) { - return new AST_Export({ - start: my_start_token(M), - end: my_end_token(M), - exported_definition: from_moz(M.declaration), - exported_names: M.specifiers && M.specifiers.length ? M.specifiers.map(from_moz) : null, - module_name: from_moz(M.source), - attributes: import_attributes_from_moz(M.attributes || M.assertions) - }); + if (M.declaration) { + // export const, export function, ... + return new AST_Export({ + start: my_start_token(M), + end: my_end_token(M), + exported_definition: from_moz(M.declaration), + exported_names: null, + module_name: null, + attributes: null, + }); + } else { + return new AST_Export({ + start: my_start_token(M), + end: my_end_token(M), + exported_definition: null, + exported_names: M.specifiers && M.specifiers.length ? M.specifiers.map(from_moz) : [], + module_name: from_moz(M.source), + attributes: import_attributes_from_moz(M.attributes || M.assertions), + }); + } }, ExportDefaultDeclaration: function(M) { @@ -7632,8 +7710,10 @@ def_transform(AST_PrefixedTemplateString, function(self, tw) { ExportSpecifier: function(M) { return new AST_NameMapping({ - foreign_name: from_moz(M.exported), - name: from_moz(M.local) + start: my_start_token(M), + end: my_end_token(M), + foreign_name: from_moz_symbol(AST_SymbolExportForeign, M.exported, M.exported.type === "Literal"), + name: from_moz_symbol(AST_SymbolExport, M.local, M.local.type === "Literal"), }); }, @@ -7662,27 +7742,13 @@ def_transform(AST_PrefixedTemplateString, function(self, tw) { const bi = typeof M.value === "bigint" ? M.value.toString() : M.bigint; if (typeof bi === "string") { args.value = bi; + args.raw = M.raw; return new AST_BigInt(args); } if (val === null) return new AST_Null(args); switch (typeof val) { case "string": args.quote = "\""; - var p = FROM_MOZ_STACK[FROM_MOZ_STACK.length - 2]; - if (p.type == "ImportSpecifier") { - args.name = val; - return new AST_SymbolImportForeign(args); - } else if (p.type == "ExportSpecifier") { - args.name = val; - if (M == p.exported) { - return new AST_SymbolExportForeign(args); - } else { - return new AST_SymbolExport(args); - } - } else if (p.type == "ExportAllDeclaration" && M == p.exported) { - args.name = val; - return new AST_SymbolExportForeign(args); - } args.value = val; return new AST_String(args); case "number": @@ -7709,26 +7775,11 @@ def_transform(AST_PrefixedTemplateString, function(self, tw) { }, Identifier: function(M) { - var p = FROM_MOZ_STACK[FROM_MOZ_STACK.length - 2]; - return new ( p.type == "LabeledStatement" ? AST_Label - : p.type == "VariableDeclarator" && p.id === M ? (p.kind == "const" ? AST_SymbolConst : p.kind == "let" ? AST_SymbolLet : AST_SymbolVar) - : /Import.*Specifier/.test(p.type) ? (p.local === M ? AST_SymbolImport : AST_SymbolImportForeign) - : p.type == "ExportSpecifier" ? (p.local === M ? AST_SymbolExport : AST_SymbolExportForeign) - : p.type == "FunctionExpression" ? (p.id === M ? AST_SymbolLambda : AST_SymbolFunarg) - : p.type == "FunctionDeclaration" ? (p.id === M ? AST_SymbolDefun : AST_SymbolFunarg) - : p.type == "ArrowFunctionExpression" ? (p.params.includes(M)) ? AST_SymbolFunarg : AST_SymbolRef - : p.type == "ClassExpression" ? (p.id === M ? AST_SymbolClass : AST_SymbolRef) - : p.type == "Property" ? (p.key === M && p.computed || p.value === M ? AST_SymbolRef : AST_SymbolMethod) - : p.type == "PropertyDefinition" || p.type === "FieldDefinition" ? (p.key === M && p.computed || p.value === M ? AST_SymbolRef : AST_SymbolClassProperty) - : p.type == "ClassDeclaration" ? (p.id === M ? AST_SymbolDefClass : AST_SymbolRef) - : p.type == "MethodDefinition" ? (p.computed ? AST_SymbolRef : AST_SymbolMethod) - : p.type == "CatchClause" ? AST_SymbolCatch - : p.type == "BreakStatement" || p.type == "ContinueStatement" ? AST_LabelRef - : AST_SymbolRef)({ - start : my_start_token(M), - end : my_end_token(M), - name : M.name - }); + return new AST_SymbolRef({ + start : my_start_token(M), + end : my_end_token(M), + name : M.name + }); }, EmptyStatement: function(M) { @@ -7757,19 +7808,28 @@ def_transform(AST_PrefixedTemplateString, function(self, tw) { }, LabeledStatement: function(M) { - return new AST_LabeledStatement({ - start: my_start_token(M), - end: my_end_token(M), - label: from_moz(M.label), - body: from_moz(M.body) - }); + try { + const label = from_moz_symbol(AST_Label, M.label); + FROM_MOZ_LABELS.push(label); + + const stat = new AST_LabeledStatement({ + start: my_start_token(M), + end: my_end_token(M), + label, + body: from_moz(M.body) + }); + + return stat; + } finally { + FROM_MOZ_LABELS.pop(); + } }, BreakStatement: function(M) { return new AST_Break({ start: my_start_token(M), end: my_end_token(M), - label: from_moz(M.label) + label: from_moz_label_ref(M.label), }); }, @@ -7777,7 +7837,7 @@ def_transform(AST_PrefixedTemplateString, function(self, tw) { return new AST_Continue({ start: my_start_token(M), end: my_end_token(M), - label: from_moz(M.label) + label: from_moz_label_ref(M.label), }); }, @@ -7889,20 +7949,11 @@ def_transform(AST_PrefixedTemplateString, function(self, tw) { }); }, - VariableDeclarator: function(M) { - return new AST_VarDef({ - start: my_start_token(M), - end: my_end_token(M), - name: from_moz(M.id), - value: from_moz(M.init) - }); - }, - CatchClause: function(M) { return new AST_Catch({ start: my_start_token(M), end: my_end_token(M), - argname: from_moz(M.param), + argname: M.param ? from_moz_pattern(M.param, AST_SymbolCatch) : null, body: from_moz(M.body).body }); }, @@ -7910,6 +7961,7 @@ def_transform(AST_PrefixedTemplateString, function(self, tw) { ThisExpression: function(M) { return new AST_This({ start: my_start_token(M), + name: "this", end: my_end_token(M) }); }, @@ -7917,7 +7969,8 @@ def_transform(AST_PrefixedTemplateString, function(self, tw) { Super: function(M) { return new AST_Super({ start: my_start_token(M), - end: my_end_token(M) + end: my_end_token(M), + name: "super", }); }, @@ -7958,6 +8011,7 @@ def_transform(AST_PrefixedTemplateString, function(self, tw) { start: my_start_token(M), end: my_end_token(M), operator: M.operator, + logical: M.operator === "??=" || M.operator === "&&=" || M.operator === "||=", left: from_moz(M.left), right: from_moz(M.right) }); @@ -8010,7 +8064,7 @@ def_transform(AST_PrefixedTemplateString, function(self, tw) { return new (M.type === "ClassDeclaration" ? AST_DefClass : AST_ClassExpression)({ start : my_start_token(M), end : my_end_token(M), - name : from_moz(M.id), + name : M.id && from_moz_symbol(M.type === "ClassDeclaration" ? AST_SymbolDefClass : AST_SymbolClass, M.id), extends : from_moz(M.superClass), properties: M.body.body.map(from_moz) }); @@ -8145,13 +8199,6 @@ def_transform(AST_PrefixedTemplateString, function(self, tw) { init: to_moz(M.value) }; }); - def_to_moz(AST_Catch, function To_Moz_CatchClause(M) { - return { - type: "CatchClause", - param: to_moz(M.argname), - body: to_moz_block(M) - }; - }); def_to_moz(AST_This, function To_Moz_ThisExpression() { return { @@ -8163,30 +8210,6 @@ def_transform(AST_PrefixedTemplateString, function(self, tw) { type: "Super" }; }); - def_to_moz(AST_Binary, function To_Moz_BinaryExpression(M) { - return { - type: "BinaryExpression", - operator: M.operator, - left: to_moz(M.left), - right: to_moz(M.right) - }; - }); - def_to_moz(AST_Binary, function To_Moz_LogicalExpression(M) { - return { - type: "LogicalExpression", - operator: M.operator, - left: to_moz(M.left), - right: to_moz(M.right) - }; - }); - def_to_moz(AST_Assign, function To_Moz_AssignmentExpression(M) { - return { - type: "AssignmentExpression", - operator: M.operator, - left: to_moz(M.left), - right: to_moz(M.right) - }; - }); def_to_moz(AST_Conditional, function To_Moz_ConditionalExpression(M) { return { type: "ConditionalExpression", @@ -8208,7 +8231,7 @@ def_transform(AST_PrefixedTemplateString, function(self, tw) { return { type: "ImportExpression", source, - options + options: options || null }; } @@ -8267,36 +8290,36 @@ def_transform(AST_PrefixedTemplateString, function(self, tw) { return { type: "FunctionDeclaration", id: to_moz(M.name), - params: M.argnames.map(to_moz), + params: M.argnames.map(to_moz_pattern), generator: M.is_generator, async: M.async, body: to_moz_scope("BlockStatement", M) }; }); - def_to_moz(AST_Function, function To_Moz_FunctionExpression(M, parent) { - var is_generator = parent.is_generator !== undefined ? - parent.is_generator : M.is_generator; + def_to_moz(AST_Function, function To_Moz_FunctionExpression(M) { return { type: "FunctionExpression", id: to_moz(M.name), - params: M.argnames.map(to_moz), - generator: is_generator, - async: M.async, + params: M.argnames.map(to_moz_pattern), + generator: M.is_generator || false, + async: M.async || false, body: to_moz_scope("BlockStatement", M) }; }); def_to_moz(AST_Arrow, function To_Moz_ArrowFunctionExpression(M) { - var body = { - type: "BlockStatement", - body: M.body.map(to_moz) - }; + var body = M.body.length === 1 && M.body[0] instanceof AST_Return && M.body[0].value + ? to_moz(M.body[0].value) + : { + type: "BlockStatement", + body: M.body.map(to_moz) + }; return { type: "ArrowFunctionExpression", - params: M.argnames.map(to_moz), + params: M.argnames.map(to_moz_pattern), async: M.async, - body: body + body: body, }; }); @@ -8304,12 +8327,39 @@ def_transform(AST_PrefixedTemplateString, function(self, tw) { if (M.is_array) { return { type: "ArrayPattern", - elements: M.names.map(to_moz) + elements: M.names.map( + M => M instanceof AST_Hole ? null : to_moz_pattern(M) + ), }; } return { type: "ObjectPattern", - properties: M.names.map(to_moz) + properties: M.names.map(M => { + if (M instanceof AST_ObjectKeyVal) { + var computed = M.computed_key(); + const [shorthand, key] = to_moz_property_key(M.key, computed, M.quote, M.value); + + return { + type: "Property", + computed, + kind: "init", + key: key, + method: false, + shorthand, + value: to_moz_pattern(M.value) + }; + } else { + return to_moz_pattern(M); + } + }), + }; + }); + + def_to_moz(AST_DefaultAssign, function To_Moz_AssignmentExpression(M) { + return { + type: "AssignmentPattern", + left: to_moz_pattern(M.left), + right: to_moz(M.right), }; }); @@ -8353,8 +8403,7 @@ def_transform(AST_PrefixedTemplateString, function(self, tw) { def_to_moz(AST_Catch, function To_Moz_CatchClause(M) { return { type: "CatchClause", - param: to_moz(M.argname), - guard: null, + param: M.argname != null ? to_moz_pattern(M.argname) : null, body: to_moz_block(M) }; }); @@ -8389,8 +8438,7 @@ def_transform(AST_PrefixedTemplateString, function(self, tw) { def_to_moz(AST_Export, function To_Moz_ExportDeclaration(M) { if (M.exported_names) { var first_exported = M.exported_names[0]; - var first_exported_name = first_exported.name; - if (first_exported_name.name === "*" && !first_exported_name.quote) { + if (first_exported && first_exported.name.name === "*" && !first_exported.name.quote) { var foreign_name = first_exported.foreign_name; var exported = foreign_name.name === "*" && !foreign_name.quote ? null @@ -8416,10 +8464,20 @@ def_transform(AST_PrefixedTemplateString, function(self, tw) { attributes: import_attributes_to_moz(M.attributes) }; } - return { - type: M.is_default ? "ExportDefaultDeclaration" : "ExportNamedDeclaration", - declaration: to_moz(M.exported_value || M.exported_definition) - }; + + if (M.is_default) { + return { + type: "ExportDefaultDeclaration", + declaration: to_moz(M.exported_value || M.exported_definition), + }; + } else { + return { + type: "ExportNamedDeclaration", + declaration: to_moz(M.exported_value || M.exported_definition), + specifiers: [], + source: null, + }; + } }); def_to_moz(AST_Import, function To_Moz_ImportDeclaration(M) { @@ -8537,6 +8595,15 @@ def_transform(AST_PrefixedTemplateString, function(self, tw) { }; }); + def_to_moz(AST_Assign, function To_Moz_AssignmentExpression(M) { + return { + type: "AssignmentExpression", + operator: M.operator, + left: to_moz(M.left), + right: to_moz(M.right) + }; + }); + def_to_moz(AST_PrivateIn, function To_Moz_BinaryExpression_PrivateIn(M) { return { type: "BinaryExpression", @@ -8561,29 +8628,10 @@ def_transform(AST_PrefixedTemplateString, function(self, tw) { }); def_to_moz(AST_ObjectProperty, function To_Moz_Property(M, parent) { - var key = M.key instanceof AST_Node ? to_moz(M.key) : { - type: "Identifier", - value: M.key - }; - if (typeof M.key === "number") { - key = { - type: "Literal", - value: Number(M.key) - }; - } - if (typeof M.key === "string") { - key = { - type: "Identifier", - name: M.key - }; - } + var computed = M.computed_key(); + const [shorthand, key] = to_moz_property_key(M.key, computed, M.quote, M.value); + var kind; - var string_or_num = typeof M.key === "string" || typeof M.key === "number"; - var computed = string_or_num ? false : !(M.key instanceof AST_Symbol) || M.key instanceof AST_SymbolRef; - if (M instanceof AST_ObjectKeyVal) { - kind = "init"; - computed = !string_or_num; - } else if (M instanceof AST_ObjectGetter) { kind = "get"; } else @@ -8638,38 +8686,62 @@ def_transform(AST_PrefixedTemplateString, function(self, tw) { return { type: "Property", computed: computed, - kind: kind, + method: false, + shorthand, + kind: kind, + key: key, + value: to_moz(M.value) + }; + }); + + def_to_moz(AST_ObjectKeyVal, function To_Moz_Property(M) { + var computed = M.computed_key(); + const [shorthand, key] = to_moz_property_key(M.key, computed, M.quote, M.value); + + return { + type: "Property", + computed: computed, + shorthand: shorthand, + method: false, + kind: "init", key: key, value: to_moz(M.value) }; }); def_to_moz(AST_ConciseMethod, function To_Moz_MethodDefinition(M, parent) { + const computed = M.computed_key(); + const [_always_false, key] = to_moz_property_key(M.key, computed, M.quote, M.value); + if (parent instanceof AST_Object) { return { type: "Property", - computed: !(M.key instanceof AST_Symbol) || M.key instanceof AST_SymbolRef, kind: "init", + computed, method: true, shorthand: false, - key: to_moz(M.key), - value: to_moz(M.value) + key, + value: to_moz(M.value), }; } - const key = M instanceof AST_PrivateMethod - ? { - type: "PrivateIdentifier", - name: M.key.name - } - : to_moz(M.key); - return { type: "MethodDefinition", - kind: M.key === "constructor" ? "constructor" : "method", + kind: !computed && M.key.name === "constructor" ? "constructor" : "method", + computed, key, value: to_moz(M.value), - computed: !(M.key instanceof AST_Symbol) || M.key instanceof AST_SymbolRef, + static: M.static, + }; + }); + + def_to_moz(AST_PrivateMethod, function To_Moz_MethodDefinition(M) { + return { + type: "MethodDefinition", + kind: "method", + key: { type: "PrivateIdentifier", name: M.key.name }, + value: to_moz(M.value), + computed: false, static: M.static, }; }); @@ -8764,6 +8836,7 @@ def_transform(AST_PrefixedTemplateString, function(self, tw) { // `M.value` is a string that may be a hex number representation. // but "bigint" property should have only decimal digits bigint: typeof BigInt === "function" ? BigInt(M.value).toString() : M.value, + raw: M.raw, })); AST_Boolean.DEFMETHOD("to_mozilla_ast", AST_Constant.prototype.to_mozilla_ast); @@ -8807,20 +8880,133 @@ def_transform(AST_PrefixedTemplateString, function(self, tw) { ); } - var FROM_MOZ_STACK = null; + var FROM_MOZ_LABELS = null; function from_moz(node) { - FROM_MOZ_STACK.push(node); - var ret = node != null ? MOZ_TO_ME[node.type](node) : null; - FROM_MOZ_STACK.pop(); - return ret; + if (node == null) return null; + return MOZ_TO_ME[node.type](node); + } + + function from_moz_quote(moz_key, computed) { + if (!computed && moz_key.type === "Literal" && typeof moz_key.value === "string") { + return '"'; + } else { + return ""; + } + } + + function from_moz_symbol(symbol_type, M, has_quote) { + return new symbol_type({ + start: my_start_token(M), + quote: has_quote ? '"' : undefined, + name: M.type === "Identifier" ? M.name : String(M.value), + end: my_end_token(M), + }); + } + + function from_moz_lambda(M, is_method) { + return new (is_method ? AST_Accessor : AST_Function)({ + start: my_start_token(M), + end: my_end_token(M), + name: M.id && from_moz_symbol(is_method ? AST_SymbolMethod : AST_SymbolLambda, M.id), + argnames: M.params.map(M => from_moz_pattern(M, AST_SymbolFunarg)), + is_generator: M.generator, + async: M.async, + body: normalize_directives(from_moz(M.body).body) + }); + } + + function from_moz_pattern(M, sym_type) { + switch (M.type) { + case "ObjectPattern": + return new AST_Destructuring({ + start: my_start_token(M), + end: my_end_token(M), + names: M.properties.map(p => from_moz_pattern(p, sym_type)), + is_array: false + }); + + case "Property": + var key = M.key; + var args = { + start : my_start_token(key || M.value), + end : my_end_token(M.value), + key : key.type == "Identifier" ? key.name : String(key.value), + quote : !M.computed && key.type === "Literal" && typeof key.value === "string" + ? '"' + : "", + value : from_moz_pattern(M.value, sym_type) + }; + if (M.computed) { + args.key = from_moz(M.key); + } + return new AST_ObjectKeyVal(args); + + case "ArrayPattern": + return new AST_Destructuring({ + start: my_start_token(M), + end: my_end_token(M), + names: M.elements.map(function(elm) { + if (elm === null) { + return new AST_Hole(); + } + return from_moz_pattern(elm, sym_type); + }), + is_array: true + }); + + case "SpreadElement": + case "RestElement": + return new AST_Expansion({ + start: my_start_token(M), + end: my_end_token(M), + expression: from_moz_pattern(M.argument, sym_type), + }); + + case "AssignmentPattern": + return new AST_DefaultAssign({ + start : my_start_token(M), + end : my_end_token(M), + left : from_moz_pattern(M.left, sym_type), + operator: "=", + right : from_moz(M.right), + }); + + case "Identifier": + return new sym_type({ + start : my_start_token(M), + end : my_end_token(M), + name : M.name, + }); + + default: + throw new Error("Invalid node type for destructuring: " + M.type); + } + } + + function from_moz_label_ref(m_label) { + if (!m_label) return null; + + const label = from_moz_symbol(AST_LabelRef, m_label); + + let i = FROM_MOZ_LABELS.length; + while (i--) { + const label_origin = FROM_MOZ_LABELS[i]; + + if (label.name === label_origin.name) { + label.thedef = label_origin; + break; + } + } + + return label; } AST_Node.from_mozilla_ast = function(node) { - var save_stack = FROM_MOZ_STACK; - FROM_MOZ_STACK = []; + var save_labels = FROM_MOZ_LABELS; + FROM_MOZ_LABELS = []; var ast = from_moz(node); - FROM_MOZ_STACK = save_stack; + FROM_MOZ_LABELS = save_labels; return ast; }; @@ -8862,6 +9048,52 @@ def_transform(AST_PrefixedTemplateString, function(self, tw) { return ast; } + /** Object property keys can be number literals, string literals, or raw names. Additionally they can be shorthand. We decide that here. */ + function to_moz_property_key(key, computed = false, quote = false, value = null) { + if (computed) { + return [false, to_moz(key)]; + } + + const key_name = typeof key === "string" ? key : key.name; + let moz_key; + if (quote) { + moz_key = { type: "Literal", value: key_name, raw: JSON.stringify(key_name) }; + } else if ("" + +key_name === key_name && +key_name >= 0) { + // representable as a number + moz_key = { type: "Literal", value: +key_name, raw: JSON.stringify(+key_name) }; + } else { + moz_key = { type: "Identifier", name: key_name }; + } + + const shorthand = + moz_key.type === "Identifier" + && moz_key.name === key_name + && (value instanceof AST_Symbol && value.name === key_name + || value instanceof AST_DefaultAssign && value.left.name === key_name); + return [shorthand, moz_key]; + } + + function to_moz_pattern(node) { + if (node instanceof AST_Expansion) { + return { + type: "RestElement", + argument: to_moz_pattern(node.expression), + }; + } + + if (( + node instanceof AST_Symbol + || node instanceof AST_Destructuring + || node instanceof AST_DefaultAssign + || node instanceof AST_PropAccess + )) { + // Plain translation + return to_moz(node); + } + + throw new Error(node.TYPE); + } + function to_moz_in_destructuring() { var i = TO_MOZ_STACK.length; while (i--) { @@ -9094,7 +9326,7 @@ function OutputStream(options) { webkit : false, width : 80, wrap_iife : false, - wrap_func_args : true, + wrap_func_args : false, _destroy_ast : false }, true); @@ -10618,11 +10850,11 @@ function OutputStream(options) { foreign_name.name; if (!names_are_different && foreign_name.name === "*" && - foreign_name.quote != self.name.quote) { + !!foreign_name.quote != !!self.name.quote) { // export * as "*" names_are_different = true; } - var foreign_name_is_name = foreign_name.quote == null; + var foreign_name_is_name = !foreign_name.quote; if (names_are_different) { if (is_import) { if (foreign_name_is_name) { @@ -10631,7 +10863,7 @@ function OutputStream(options) { output.print_string(foreign_name.name, foreign_name.quote); } } else { - if (self.name.quote == null) { + if (!self.name.quote) { self.name.print(output); } else { output.print_string(self.name.name, self.name.quote); @@ -10651,7 +10883,7 @@ function OutputStream(options) { } } } else { - if (self.name.quote == null) { + if (!self.name.quote) { self.name.print(output); } else { output.print_string(self.name.name, self.name.quote); @@ -11040,7 +11272,7 @@ function OutputStream(options) { output.print("#"); - print_property_name(self.key.name, self.quote, output); + print_property_name(self.key.name, undefined, output); if (self.value) { output.print("="); @@ -11103,13 +11335,24 @@ function OutputStream(options) { DEFPRINT(AST_PrivateGetter, function(self, output) { self._print_getter_setter("get", true, output); }); + DEFPRINT(AST_ConciseMethod, function(self, output) { + var type; + if (self.value.is_generator && self.value.async) { + type = "async*"; + } else if (self.value.is_generator) { + type = "*"; + } else if (self.value.async) { + type = "async"; + } + self._print_getter_setter(type, false, output); + }); DEFPRINT(AST_PrivateMethod, function(self, output) { var type; - if (self.is_generator && self.async) { + if (self.value.is_generator && self.value.async) { type = "async*"; - } else if (self.is_generator) { + } else if (self.value.is_generator) { type = "*"; - } else if (self.async) { + } else if (self.value.async) { type = "async"; } self._print_getter_setter(type, true, output); @@ -11124,17 +11367,6 @@ function OutputStream(options) { DEFPRINT(AST_SymbolPrivateProperty, function(self, output) { output.print("#" + self.name); }); - DEFPRINT(AST_ConciseMethod, function(self, output) { - var type; - if (self.is_generator && self.async) { - type = "async*"; - } else if (self.is_generator) { - type = "*"; - } else if (self.async) { - type = "async"; - } - self._print_getter_setter(type, false, output); - }); DEFPRINT(AST_ClassStaticBlock, function (self, output) { output.print("static"); output.space(); @@ -11168,7 +11400,11 @@ function OutputStream(options) { } }); DEFPRINT(AST_BigInt, function(self, output) { - output.print(self.getValue() + "n"); + if (output.option("keep_numbers") && self.raw) { + output.print(self.raw); + } else { + output.print(self.getValue() + "n"); + } }); const r_slash_script = /(<\s*\/\s*script)/i; @@ -11456,13 +11692,13 @@ AST_VarDef.prototype.shallow_cmp = function(other) { AST_NameMapping.prototype.shallow_cmp = pass_through; AST_Import.prototype.shallow_cmp = function(other) { - return (this.imported_name == null ? other.imported_name == null : this.imported_name === other.imported_name) && (this.imported_names == null ? other.imported_names == null : this.imported_names === other.imported_names); + return (this.imported_name == null ? other.imported_name == null : this.imported_name === other.imported_name) && (this.imported_names == null ? other.imported_names == null : this.imported_names === other.imported_names) && (this.attributes == null ? other.attributes == null : this.attributes === other.attributes); }; AST_ImportMeta.prototype.shallow_cmp = pass_through; AST_Export.prototype.shallow_cmp = function(other) { - return (this.exported_definition == null ? other.exported_definition == null : this.exported_definition === other.exported_definition) && (this.exported_value == null ? other.exported_value == null : this.exported_value === other.exported_value) && (this.exported_names == null ? other.exported_names == null : this.exported_names === other.exported_names) && this.module_name === other.module_name && this.is_default === other.is_default; + return (this.exported_definition == null ? other.exported_definition == null : this.exported_definition === other.exported_definition) && (this.exported_value == null ? other.exported_value == null : this.exported_value === other.exported_value) && (this.exported_names == null ? other.exported_names == null : this.exported_names === other.exported_names) && (this.attributes == null ? other.attributes == null : this.attributes === other.attributes) && this.module_name === other.module_name && this.is_default === other.is_default; }; AST_Call.prototype.shallow_cmp = pass_through; @@ -11489,6 +11725,8 @@ AST_Binary.prototype.shallow_cmp = function(other) { return this.operator === other.operator; }; +AST_PrivateIn.prototype.shallow_cmp = pass_through; + AST_Conditional.prototype.shallow_cmp = pass_through; AST_Array.prototype.shallow_cmp = pass_through; @@ -11498,7 +11736,7 @@ AST_Object.prototype.shallow_cmp = pass_through; AST_ObjectProperty.prototype.shallow_cmp = pass_through; AST_ObjectKeyVal.prototype.shallow_cmp = function(other) { - return this.key === other.key; + return this.key === other.key && this.quote === other.quote; }; AST_ObjectSetter.prototype.shallow_cmp = function(other) { @@ -11510,7 +11748,11 @@ AST_ObjectGetter.prototype.shallow_cmp = function(other) { }; AST_ConciseMethod.prototype.shallow_cmp = function(other) { - return this.static === other.static && this.is_generator === other.is_generator && this.async === other.async; + return this.static === other.static; +}; + +AST_PrivateMethod.prototype.shallow_cmp = function(other) { + return this.static === other.static; }; AST_Class.prototype.shallow_cmp = function(other) { @@ -11518,6 +11760,13 @@ AST_Class.prototype.shallow_cmp = function(other) { }; AST_ClassProperty.prototype.shallow_cmp = function(other) { + return this.static === other.static + && (typeof this.key === "string" + ? this.key === other.key + : true /* AST_Node handled elsewhere */); +}; + +AST_ClassPrivateProperty.prototype.shallow_cmp = function(other) { return this.static === other.static; }; @@ -12839,14 +13088,18 @@ AST_ObjectSetter.prototype._size = function () { }; AST_ConciseMethod.prototype._size = function () { - return static_size(this.static) + key_size(this.key) + lambda_modifiers(this); + return static_size(this.static) + key_size(this.key); }; AST_PrivateMethod.prototype._size = function () { return AST_ConciseMethod.prototype._size.call(this) + 1; }; -AST_PrivateGetter.prototype._size = AST_PrivateSetter.prototype._size = function () { +AST_PrivateGetter.prototype._size = function () { + return AST_ConciseMethod.prototype._size.call(this) + 4; +}; + +AST_PrivateSetter.prototype._size = function () { return AST_ConciseMethod.prototype._size.call(this) + 4; }; @@ -13000,7 +13253,6 @@ const TRUTHY = 0b00000010; const FALSY = 0b00000100; const UNDEFINED = 0b00001000; const INLINED = 0b00010000; - // Nodes to which values are ever written. Used when keep_assign is part of the unused option string. const WRITE_ONLY = 0b00100000; @@ -13632,18 +13884,24 @@ const unary_side_effects = makePredicate("delete ++ --"); def_is_number(AST_Node, return_false); def_is_number(AST_Number, return_true); const unary = makePredicate("+ - ~ ++ --"); - def_is_number(AST_Unary, function() { - return unary.has(this.operator) && !(this.expression instanceof AST_BigInt); + def_is_number(AST_Unary, function(compressor) { + return unary.has(this.operator) && this.expression.is_number(compressor); }); const numeric_ops = makePredicate("- * / % & | ^ << >> >>>"); def_is_number(AST_Binary, function(compressor) { - return numeric_ops.has(this.operator) || this.operator == "+" - && this.left.is_number(compressor) - && this.right.is_number(compressor); + if (this.operator === "+") { + // Both sides need to be `number`. Or one is a `number` and the other is number-ish. + return this.left.is_number(compressor) && this.right.is_number_or_bigint(compressor) + || this.right.is_number(compressor) && this.left.is_number_or_bigint(compressor); + } else if (numeric_ops.has(this.operator)) { + return this.left.is_number(compressor) || this.right.is_number(compressor); + } else { + return false; + } }); def_is_number(AST_Assign, function(compressor) { - return numeric_ops.has(this.operator.slice(0, -1)) - || this.operator == "=" && this.right.is_number(compressor); + return (this.operator === "=" || numeric_ops.has(this.operator.slice(0, -1))) + && this.right.is_number(compressor); }); def_is_number(AST_Sequence, function(compressor) { return this.tail_node().is_number(compressor); @@ -13655,19 +13913,83 @@ const unary_side_effects = makePredicate("delete ++ --"); node.DEFMETHOD("is_number", func); }); +// methods to determine if an expression returns a BigInt +(function(def_is_bigint) { + def_is_bigint(AST_Node, return_false); + def_is_bigint(AST_BigInt, return_true); + const unary = makePredicate("+ - ~ ++ --"); + def_is_bigint(AST_Unary, function(compressor) { + return unary.has(this.operator) && this.expression.is_bigint(compressor); + }); + const numeric_ops = makePredicate("- * / % & | ^ << >>"); + def_is_bigint(AST_Binary, function(compressor) { + if (this.operator === "+") { + return this.left.is_bigint(compressor) && this.right.is_number_or_bigint(compressor) + || this.right.is_bigint(compressor) && this.left.is_number_or_bigint(compressor); + } else if (numeric_ops.has(this.operator)) { + return this.left.is_bigint(compressor) || this.right.is_bigint(compressor); + } else { + return false; + } + }); + def_is_bigint(AST_Assign, function(compressor) { + return (numeric_ops.has(this.operator.slice(0, -1)) || this.operator == "=") + && this.right.is_bigint(compressor); + }); + def_is_bigint(AST_Sequence, function(compressor) { + return this.tail_node().is_bigint(compressor); + }); + def_is_bigint(AST_Conditional, function(compressor) { + return this.consequent.is_bigint(compressor) && this.alternative.is_bigint(compressor); + }); +})(function(node, func) { + node.DEFMETHOD("is_bigint", func); +}); + +// methods to determine if an expression is a number or a bigint +(function(def_is_number_or_bigint) { + def_is_number_or_bigint(AST_Node, return_false); + def_is_number_or_bigint(AST_Number, return_true); + def_is_number_or_bigint(AST_BigInt, return_true); + const numeric_unary_ops = makePredicate("+ - ~ ++ --"); + def_is_number_or_bigint(AST_Unary, function(_compressor) { + return numeric_unary_ops.has(this.operator); + }); + const numeric_ops = makePredicate("- * / % & | ^ << >>"); + def_is_number_or_bigint(AST_Binary, function(compressor) { + return this.operator === "+" + ? this.left.is_number_or_bigint(compressor) && this.right.is_number_or_bigint(compressor) + : numeric_ops.has(this.operator); + }); + def_is_number_or_bigint(AST_Assign, function(compressor) { + return numeric_ops.has(this.operator.slice(0, -1)) + || this.operator == "=" && this.right.is_number_or_bigint(compressor); + }); + def_is_number_or_bigint(AST_Sequence, function(compressor) { + return this.tail_node().is_number_or_bigint(compressor); + }); + def_is_number_or_bigint(AST_Conditional, function(compressor) { + return this.consequent.is_number_or_bigint(compressor) && this.alternative.is_number_or_bigint(compressor); + }); +}(function (node, func) { + node.DEFMETHOD("is_number_or_bigint", func); +})); + + // methods to determine if an expression is a 32 bit integer (IE results from bitwise ops, or is an integer constant fitting in that size (function(def_is_32_bit_integer) { def_is_32_bit_integer(AST_Node, return_false); - def_is_32_bit_integer(AST_Number, function() { + def_is_32_bit_integer(AST_Number, function(_compressor) { return this.value === (this.value | 0); }); - def_is_32_bit_integer(AST_UnaryPrefix, function() { - return this.operator == "~" ? this.expression.is_number() - : this.operator === "+" ? this.expression.is_32_bit_integer() + def_is_32_bit_integer(AST_UnaryPrefix, function(compressor) { + return this.operator == "~" ? this.expression.is_number(compressor) + : this.operator === "+" ? this.expression.is_32_bit_integer(compressor) : false; }); - def_is_32_bit_integer(AST_Binary, function() { - return bitwise_binop.has(this.operator); + def_is_32_bit_integer(AST_Binary, function(compressor) { + return bitwise_binop.has(this.operator) + && (this.left.is_number(compressor) || this.right.is_number(compressor)); }); }(function (node, func) { node.DEFMETHOD("is_32_bit_integer", func); @@ -13828,25 +14150,29 @@ function is_nullish(node, compressor) { def_has_side_effects(AST_Object, function(compressor) { return any(this.properties, compressor); }); - def_has_side_effects(AST_ObjectProperty, function(compressor) { + def_has_side_effects(AST_ObjectKeyVal, function(compressor) { return ( this.computed_key() && this.key.has_side_effects(compressor) || this.value && this.value.has_side_effects(compressor) ); }); - def_has_side_effects(AST_ClassProperty, function(compressor) { + def_has_side_effects([ + AST_ClassProperty, + AST_ClassPrivateProperty, + ], function(compressor) { return ( this.computed_key() && this.key.has_side_effects(compressor) || this.static && this.value && this.value.has_side_effects(compressor) ); }); - def_has_side_effects(AST_ConciseMethod, function(compressor) { - return this.computed_key() && this.key.has_side_effects(compressor); - }); - def_has_side_effects(AST_ObjectGetter, function(compressor) { - return this.computed_key() && this.key.has_side_effects(compressor); - }); - def_has_side_effects(AST_ObjectSetter, function(compressor) { + def_has_side_effects([ + AST_PrivateMethod, + AST_PrivateGetter, + AST_PrivateSetter, + AST_ConciseMethod, + AST_ObjectGetter, + AST_ObjectSetter, + ], function(compressor) { return this.computed_key() && this.key.has_side_effects(compressor); }); def_has_side_effects(AST_Array, function(compressor) { @@ -13891,8 +14217,10 @@ function is_nullish(node, compressor) { def_has_side_effects(AST_TemplateString, function(compressor) { return any(this.segments, compressor); }); -})(function(node, func) { - node.DEFMETHOD("has_side_effects", func); +})(function(node_or_nodes, func) { + for (const node of [].concat(node_or_nodes)) { + node.DEFMETHOD("has_side_effects", func); + } }); // determine if expression may throw @@ -13971,25 +14299,33 @@ function is_nullish(node, compressor) { def_may_throw(AST_Object, function(compressor) { return any(this.properties, compressor); }); - def_may_throw(AST_ObjectProperty, function(compressor) { - // TODO key may throw too - return this.value ? this.value.may_throw(compressor) : false; + def_may_throw(AST_ObjectKeyVal, function(compressor) { + return ( + this.computed_key() && this.key.may_throw(compressor) + || this.value ? this.value.may_throw(compressor) : false + ); }); - def_may_throw(AST_ClassProperty, function(compressor) { + def_may_throw([ + AST_ClassProperty, + AST_ClassPrivateProperty, + ], function(compressor) { return ( this.computed_key() && this.key.may_throw(compressor) || this.static && this.value && this.value.may_throw(compressor) ); }); - def_may_throw(AST_ConciseMethod, function(compressor) { - return this.computed_key() && this.key.may_throw(compressor); - }); - def_may_throw(AST_ObjectGetter, function(compressor) { - return this.computed_key() && this.key.may_throw(compressor); - }); - def_may_throw(AST_ObjectSetter, function(compressor) { + def_may_throw([ + AST_ConciseMethod, + AST_ObjectGetter, + AST_ObjectSetter, + ], function(compressor) { return this.computed_key() && this.key.may_throw(compressor); }); + def_may_throw([ + AST_PrivateMethod, + AST_PrivateGetter, + AST_PrivateSetter, + ], return_false); def_may_throw(AST_Return, function(compressor) { return this.value && this.value.may_throw(compressor); }); @@ -14034,8 +14370,10 @@ function is_nullish(node, compressor) { if (!this.value) return false; return this.value.may_throw(compressor); }); -})(function(node, func) { - node.DEFMETHOD("may_throw", func); +})(function(node_or_nodes, func) { + for (const node of [].concat(node_or_nodes)) { + node.DEFMETHOD("may_throw", func); + } }); // determine if expression is constant @@ -14288,30 +14626,36 @@ function is_lhs(node, parent) { }); (function (def_bitwise_negate) { - function basic_negation(exp) { + function basic_bitwise_negation(exp) { return make_node(AST_UnaryPrefix, exp, { operator: "~", expression: exp }); } - def_bitwise_negate(AST_Node, function() { - return basic_negation(this); + def_bitwise_negate(AST_Node, function(_compressor) { + return basic_bitwise_negation(this); }); - def_bitwise_negate(AST_Number, function() { + def_bitwise_negate(AST_Number, function(_compressor) { const neg = ~this.value; if (neg.toString().length > this.value.toString().length) { - return basic_negation(this); + return basic_bitwise_negation(this); } return make_node(AST_Number, this, { value: neg }); }); - def_bitwise_negate(AST_UnaryPrefix, function(in_32_bit_context) { - if (this.operator == "~" && (in_32_bit_context || this.expression.is_32_bit_integer())) { + def_bitwise_negate(AST_UnaryPrefix, function(compressor, in_32_bit_context) { + if ( + this.operator == "~" + && ( + this.expression.is_32_bit_integer(compressor) || + (in_32_bit_context != null ? in_32_bit_context : compressor.in_32_bit_context()) + ) + ) { return this.expression; } else { - return basic_negation(this); + return basic_bitwise_negation(this); } }); })(function (node, func) { @@ -14575,8 +14919,13 @@ AST_Node.DEFMETHOD("is_constant", function () { return !(this instanceof AST_RegExp); } else { return this instanceof AST_UnaryPrefix - && this.expression instanceof AST_Constant - && unaryPrefix.has(this.operator); + && unaryPrefix.has(this.operator) + && ( + // `this.expression` may be an `AST_RegExp`, + // so not only `.is_constant()`. + this.expression instanceof AST_Constant + || this.expression.is_constant() + ); } }); @@ -15025,8 +15374,10 @@ def_eval(AST_New, return_this); // 10 -> (nothing) // knownPureFunc(foo++) -> foo++ -function def_drop_side_effect_free(node, func) { - node.DEFMETHOD("drop_side_effect_free", func); +function def_drop_side_effect_free(node_or_nodes, func) { + for (const node of [].concat(node_or_nodes)) { + node.DEFMETHOD("drop_side_effect_free", func); + } } // Drop side-effect-free elements from an array of expressions. @@ -15117,7 +15468,10 @@ def_drop_side_effect_free(AST_Class, function (compressor) { } }); -def_drop_side_effect_free(AST_ClassProperty, function (compressor) { +def_drop_side_effect_free([ + AST_ClassProperty, + AST_ClassPrivateProperty, +], function (compressor) { const key = this.computed_key() && this.key.drop_side_effect_free(compressor); const value = this.static && this.value @@ -15221,26 +15575,30 @@ def_drop_side_effect_free(AST_Object, function (compressor, first_in_statement) return values && make_sequence(this, values); }); -def_drop_side_effect_free(AST_ObjectProperty, function (compressor, first_in_statement) { - const computed_key = this instanceof AST_ObjectKeyVal && this.key instanceof AST_Node; +def_drop_side_effect_free(AST_ObjectKeyVal, function (compressor, first_in_statement) { + const computed_key = this.key instanceof AST_Node; const key = computed_key && this.key.drop_side_effect_free(compressor, first_in_statement); - const value = this.value && this.value.drop_side_effect_free(compressor, first_in_statement); + const value = this.value.drop_side_effect_free(compressor, first_in_statement); if (key && value) { return make_sequence(this, [key, value]); } return key || value; }); -def_drop_side_effect_free(AST_ConciseMethod, function () { - return this.computed_key() ? this.key : null; -}); - -def_drop_side_effect_free(AST_ObjectGetter, function () { +def_drop_side_effect_free([ + AST_ConciseMethod, + AST_ObjectGetter, + AST_ObjectSetter, +], function () { return this.computed_key() ? this.key : null; }); -def_drop_side_effect_free(AST_ObjectSetter, function () { - return this.computed_key() ? this.key : null; +def_drop_side_effect_free([ + AST_PrivateMethod, + AST_PrivateGetter, + AST_PrivateSetter, +], function () { + return null; }); def_drop_side_effect_free(AST_Array, function (compressor, first_in_statement) { @@ -17472,7 +17830,7 @@ function tighten_body(statements, compressor) { stat = stat.clone(); stat.condition = stat.condition.negate(compressor); stat.body = make_node(AST_BlockStatement, stat, { - body: as_statement_array(stat.alternative).concat(extract_functions()) + body: as_statement_array(stat.alternative).concat(extract_defuns()) }); stat.alternative = make_node(AST_BlockStatement, stat, { body: new_else @@ -17492,7 +17850,7 @@ function tighten_body(statements, compressor) { CHANGED = true; stat = stat.clone(); stat.body = make_node(AST_BlockStatement, stat.body, { - body: as_statement_array(stat.body).concat(extract_functions()) + body: as_statement_array(stat.body).concat(extract_defuns()) }); stat.alternative = make_node(AST_BlockStatement, stat.alternative, { body: new_else @@ -17597,7 +17955,7 @@ function tighten_body(statements, compressor) { || ab instanceof AST_Break && lct instanceof AST_BlockStatement && self === lct; } - function extract_functions() { + function extract_defuns() { var tail = statements.slice(i + 1); statements.length = i + 1; return tail.filter(function (stat) { @@ -17615,6 +17973,9 @@ function tighten_body(statements, compressor) { return undefined; } body = body.slice(0, -1); + if (!body.every(stat => can_be_evicted_from_block(stat))) { + return undefined; + } if (ab.value) { body.push(make_node(AST_SimpleStatement, ab.value, { body: ab.value.expression @@ -18073,6 +18434,8 @@ function inline_into_symbolref(self, compressor) { return self; } + if (dont_inline_lambda_in_loop(compressor, fixed)) return self; + let single_use = def.single_use && !(parent instanceof AST_Call && (parent.is_callee_pure(compressor)) @@ -18226,6 +18589,11 @@ function inline_into_call(self, compressor) { fn = fixed; } + if ( + dont_inline_lambda_in_loop(compressor, fn) + && !has_annotation(self, _INLINE) + ) return self; + var is_func = fn instanceof AST_Lambda; var stat = is_func && fn.body[0]; @@ -18554,6 +18922,14 @@ function inline_into_call(self, compressor) { } } +/** prevent inlining functions into loops, for performance reasons */ +function dont_inline_lambda_in_loop(compressor, maybe_lambda) { + return ( + (maybe_lambda instanceof AST_Lambda || maybe_lambda instanceof AST_Class) + && !!compressor.is_within_loop() + ); +} + (function(def_find_defs) { function to_node(value, orig) { if (value instanceof AST_Node) { @@ -18834,12 +19210,16 @@ class Compressor extends TreeWalker { } } - in_32_bit_context() { + in_32_bit_context(other_operand_must_be_number) { if (!this.option("evaluate")) return false; var self = this.self(); for (var i = 0, p; p = this.parent(i); i++) { if (p instanceof AST_Binary && bitwise_binop.has(p.operator)) { - return true; + if (other_operand_must_be_number) { + return (self === p.left ? p.right : p.left).is_number(this); + } else { + return true; + } } if (p instanceof AST_UnaryPrefix) { return p.operator === "~"; @@ -18954,6 +19334,7 @@ class Compressor extends TreeWalker { } } + function def_optimize(node, optimizer) { node.DEFMETHOD("optimize", function(compressor) { var self = this; @@ -18982,17 +19363,19 @@ AST_Toplevel.DEFMETHOD("drop_console", function(options) { return; } - if (isArray && !options.includes(exp.property)) { - return; - } - var name = exp.expression; + var property = exp.property; var depth = 2; while (name.expression) { + property = name.property; name = name.expression; depth++; } + if (isArray && !options.includes(property)) { + return; + } + if (is_undeclared_ref(name) && name.name == "console") { if ( depth === 3 @@ -20176,6 +20559,23 @@ def_optimize(AST_Call, function(self, compressor) { self.args.length = last; } + if ( + exp instanceof AST_Dot + && exp.expression instanceof AST_SymbolRef + && exp.expression.name === "console" + && exp.expression.definition().undeclared + && exp.property === "assert" + ) { + const condition = self.args[0]; + if (condition) { + const value = condition.evaluate(compressor); + + if (value === 1 || value === true) { + return make_node(AST_Undefined, self); + } + } + } + if (compressor.option("unsafe") && !exp.contains_optional()) { if (exp instanceof AST_Dot && exp.start.value === "Array" && exp.property === "from" && self.args.length === 1) { const [argument] = self.args; @@ -20590,7 +20990,7 @@ def_optimize(AST_UnaryPrefix, function(self, compressor) { self.operator === "~" && self.expression instanceof AST_UnaryPrefix && self.expression.operator === "~" - && (compressor.in_32_bit_context() || self.expression.expression.is_32_bit_integer()) + && (compressor.in_32_bit_context(false) || self.expression.expression.is_32_bit_integer(compressor)) ) { return self.expression.expression; } @@ -20603,9 +21003,9 @@ def_optimize(AST_UnaryPrefix, function(self, compressor) { ) { if (e.left instanceof AST_UnaryPrefix && e.left.operator === "~") { // ~(~x ^ y) => x ^ y - e.left = e.left.bitwise_negate(true); + e.left = e.left.bitwise_negate(compressor, true); } else { - e.right = e.right.bitwise_negate(true); + e.right = e.right.bitwise_negate(compressor, true); } return e; } @@ -20700,10 +21100,13 @@ def_optimize(AST_Binary, function(self, compressor) { case "===": case "!==": var is_strict_comparison = true; - if ((self.left.is_string(compressor) && self.right.is_string(compressor)) || + if ( + (self.left.is_string(compressor) && self.right.is_string(compressor)) || (self.left.is_number(compressor) && self.right.is_number(compressor)) || + (self.left.is_bigint(compressor) && self.right.is_bigint(compressor)) || (self.left.is_boolean() && self.right.is_boolean()) || - self.left.equivalent_to(self.right)) { + self.left.equivalent_to(self.right) + ) { self.operator = self.operator.substr(0, 2); } @@ -20748,7 +21151,7 @@ def_optimize(AST_Binary, function(self, compressor) { && self.left.definition() === self.right.definition() && is_object(self.left.fixed_value())) { return make_node(self.operator[0] == "=" ? AST_True : AST_False, self); - } else if (self.left.is_32_bit_integer() && self.right.is_32_bit_integer()) { + } else if (self.left.is_32_bit_integer(compressor) && self.right.is_32_bit_integer(compressor)) { const not = node => make_node(AST_UnaryPrefix, node, { operator: "!", expression: node @@ -20781,7 +21184,7 @@ def_optimize(AST_Binary, function(self, compressor) { && (mask = and_op === self.left ? self.right : self.left) && and_op.operator === "&" && mask instanceof AST_Number - && mask.is_32_bit_integer() + && mask.is_32_bit_integer(compressor) && (x = and_op.left.equivalent_to(mask) ? and_op.right : and_op.right.equivalent_to(mask) ? and_op.left : null) @@ -21024,7 +21427,7 @@ def_optimize(AST_Binary, function(self, compressor) { // a + -b => a - b if (self.right instanceof AST_UnaryPrefix && self.right.operator == "-" - && self.left.is_number(compressor)) { + && self.left.is_number_or_bigint(compressor)) { self = make_node(AST_Binary, self, { operator: "-", left: self.left, @@ -21036,7 +21439,7 @@ def_optimize(AST_Binary, function(self, compressor) { if (self.left instanceof AST_UnaryPrefix && self.left.operator == "-" && reversible() - && self.right.is_number(compressor)) { + && self.right.is_number_or_bigint(compressor)) { self = make_node(AST_Binary, self, { operator: "-", left: self.right, @@ -21080,8 +21483,9 @@ def_optimize(AST_Binary, function(self, compressor) { case "|": case "^": // a + +b => +b + a - if (self.left.is_number(compressor) - && self.right.is_number(compressor) + if ( + self.left.is_number_or_bigint(compressor) + && self.right.is_number_or_bigint(compressor) && reversible() && !(self.left instanceof AST_Binary && self.left.operator != self.operator @@ -21098,7 +21502,7 @@ def_optimize(AST_Binary, function(self, compressor) { self = best_of(compressor, self, reversed); } } - if (associative && self.is_number(compressor)) { + if (associative && self.is_number_or_bigint(compressor)) { // a + (b + c) => (a + b) + c if (self.right instanceof AST_Binary && self.right.operator == self.operator) { @@ -21217,18 +21621,31 @@ def_optimize(AST_Binary, function(self, compressor) { } } - // x ^ x => 0 // x | x => 0 | x // x & x => 0 | x - const same_operands = self.left.equivalent_to(self.right) && !self.left.has_side_effects(compressor); - if (same_operands) { - if (self.operator === "^") { - return make_node(AST_Number, self, { value: 0 }); - } - if (self.operator === "|" || self.operator === "&") { - self.left = make_node(AST_Number, self, { value: 0 }); - self.operator = "|"; - } + if ( + (self.operator === "|" || self.operator === "&") + && self.left.equivalent_to(self.right) + && !self.left.has_side_effects(compressor) + && compressor.in_32_bit_context(true) + ) { + self.left = make_node(AST_Number, self, { value: 0 }); + self.operator = "|"; + } + + // ~x ^ ~y => x ^ y + if ( + self.operator === "^" + && self.left instanceof AST_UnaryPrefix + && self.left.operator === "~" + && self.right instanceof AST_UnaryPrefix + && self.right.operator === "~" + ) { + self = make_node(AST_Binary, self, { + operator: "^", + left: self.left.expression, + right: self.right.expression + }); } @@ -21252,7 +21669,7 @@ def_optimize(AST_Binary, function(self, compressor) { if ( zero_side && (self.operator === "|" || self.operator === "^") - && (non_zero_side.is_32_bit_integer() || compressor.in_32_bit_context()) + && (non_zero_side.is_32_bit_integer(compressor) || compressor.in_32_bit_context(true)) ) { return non_zero_side; } @@ -21262,65 +21679,48 @@ def_optimize(AST_Binary, function(self, compressor) { zero_side && self.operator === "&" && !non_zero_side.has_side_effects(compressor) + && non_zero_side.is_32_bit_integer(compressor) ) { return zero_side; } + // ~0 is all ones, as well as -1. + // We can ellide some operations with it. const is_full_mask = (node) => node instanceof AST_Number && node.value === -1 || - node instanceof AST_UnaryPrefix && ( - node.operator === "-" - && node.expression instanceof AST_Number - && node.expression.value === 1 - || node.operator === "~" - && node.expression instanceof AST_Number - && node.expression.value === 0); + node instanceof AST_UnaryPrefix + && node.operator === "-" + && node.expression instanceof AST_Number + && node.expression.value === 1; const full_mask = is_full_mask(self.right) ? self.right : is_full_mask(self.left) ? self.left : null; - const non_full_mask_side = full_mask && (full_mask === self.right ? self.left : self.right); + const other_side = (full_mask === self.right ? self.left : self.right); - switch (self.operator) { - case "|": - // {anything} | -1 => -1 - if (full_mask && !non_full_mask_side.has_side_effects(compressor)) { - return full_mask; - } - - break; - case "&": - // {32 bit integer} & -1 => {32 bit integer} - if ( - full_mask - && (non_full_mask_side.is_32_bit_integer() || compressor.in_32_bit_context()) - ) { - return non_full_mask_side; - } - - break; - case "^": - // {anything} ^ -1 => ~{anything} - if (full_mask) { - return non_full_mask_side.bitwise_negate(compressor.in_32_bit_context()); - } - - // ~x ^ ~y => x ^ y - if ( - self.left instanceof AST_UnaryPrefix - && self.left.operator === "~" - && self.right instanceof AST_UnaryPrefix - && self.right.operator === "~" - ) { - self = make_node(AST_Binary, self, { - operator: "^", - left: self.left.expression, - right: self.right.expression - }); - } + // {32 bit integer} & -1 => {32 bit integer} + if ( + full_mask + && self.operator === "&" + && ( + other_side.is_32_bit_integer(compressor) + || compressor.in_32_bit_context(true) + ) + ) { + return other_side; + } - break; + // {anything} ^ -1 => ~{anything} + if ( + full_mask + && self.operator === "^" + && ( + other_side.is_32_bit_integer(compressor) + || compressor.in_32_bit_context(true) + ) + ) { + return other_side.bitwise_negate(compressor); } } } @@ -21943,6 +22343,7 @@ function safe_to_flatten(value, compressor) { AST_PropAccess.DEFMETHOD("flatten_object", function(key, compressor) { if (!compressor.option("properties")) return; if (key === "__proto__") return; + if (this instanceof AST_DotHash) return; var arrows = compressor.option("unsafe_arrows") && compressor.option("ecma") >= 2015; var expr = this.expression; @@ -21955,7 +22356,7 @@ AST_PropAccess.DEFMETHOD("flatten_object", function(key, compressor) { if ("" + (prop instanceof AST_ConciseMethod ? prop.key.name : prop.key) == key) { const all_props_flattenable = props.every((p) => (p instanceof AST_ObjectKeyVal - || arrows && p instanceof AST_ConciseMethod && !p.is_generator + || arrows && p instanceof AST_ConciseMethod && !p.value.is_generator ) && !p.computed_key() ); @@ -22130,7 +22531,15 @@ def_optimize(AST_Chain, function (self, compressor) { } return make_node(AST_Undefined, self); } - return self; + if ( + self.expression instanceof AST_PropAccess + || self.expression instanceof AST_Call + ) { + return self; + } else { + // Keep the AST valid, in case the child swapped itself + return self.expression; + } }); def_optimize(AST_Dot, function(self, compressor) { @@ -22362,7 +22771,7 @@ def_optimize(AST_TemplateString, function(self, compressor) { && segments[1] instanceof AST_Node && ( segments[1].is_string(compressor) - || segments[1].is_number(compressor) + || segments[1].is_number_or_bigint(compressor) || is_nullish(segments[1], compressor) || compressor.option("unsafe") ) @@ -22433,7 +22842,7 @@ def_optimize(AST_ConciseMethod, function(self, compressor) { // p(){return x;} ---> p:()=>x if (compressor.option("arrows") && compressor.parent() instanceof AST_Object - && !self.is_generator + && !self.value.is_generator && !self.value.uses_arguments && !self.value.pinned() && self.value.body.length == 1 @@ -22441,8 +22850,8 @@ def_optimize(AST_ConciseMethod, function(self, compressor) { && self.value.body[0].value && !self.value.contains_this()) { var arrow = make_node(AST_Arrow, self.value, self.value); - arrow.async = self.async; - arrow.is_generator = self.is_generator; + arrow.async = self.value.async; + arrow.is_generator = self.value.is_generator; return make_node(AST_ObjectKeyVal, self, { key: self.key instanceof AST_SymbolMethod ? self.key.name : self.key, value: arrow, @@ -22470,8 +22879,6 @@ def_optimize(AST_ObjectKeyVal, function(self, compressor) { && !value.contains_this(); if ((is_arrow_with_block || value instanceof AST_Function) && !value.name) { return make_node(AST_ConciseMethod, self, { - async: value.async, - is_generator: value.is_generator, key: key instanceof AST_Node ? key : make_node(AST_SymbolMethod, self, { name: key, }), @@ -32654,7 +33061,7 @@ async function run_cli({ program, packageJson, fs, path }) { if (program.parse.acorn) { files = convert_ast(function(toplevel, name) { return require("acorn").parse(files[name], { - ecmaVersion: 2018, + ecmaVersion: 2024, locations: true, program: toplevel, sourceFile: name, diff --git a/lib/terser.rb b/lib/terser.rb index 7e3dc30..ec9608d 100644 --- a/lib/terser.rb +++ b/lib/terser.rb @@ -2,7 +2,6 @@ # frozen_string_literal: true require "json" -require "base64" require "execjs" require "terser/railtie" if defined?(Rails::Railtie) require "terser/version" @@ -163,7 +162,7 @@ def initialize(options = {}) def compile(source, source_map_options = @options) if source_map_options[:source_map] compiled, source_map = run_terserjs(source, true, source_map_options) - source_map_uri = Base64.strict_encode64(source_map) + source_map_uri = [source_map].pack("m0") source_map_mime = "application/json;charset=utf-8;base64" compiled + "\n//# sourceMappingURL=data:#{source_map_mime},#{source_map_uri}" else @@ -506,9 +505,19 @@ def input_source_map(source, generate_map, options) source_map_options = options[:source_map].is_a?(Hash) ? options[:source_map] : {} sanitize_map_root(source_map_options.fetch(:input_source_map) do url = extract_source_mapping_url(source) - Base64.strict_decode64(url.split(",", 2)[-1]) if url && url.start_with?("data:") + base64_strict_decode64(url.split(",", 2)[-1]) if url && url.start_with?("data:") end) rescue ArgumentError, JSON::ParserError nil end + + if "".respond_to?(:unpack1) + def base64_strict_decode64(str) + str.unpack1("m0") + end + else + def base64_strict_decode64(str) + str.unpack("m0")[0] + end + end end diff --git a/lib/terser/version.rb b/lib/terser/version.rb index 47a8949..bcbc278 100644 --- a/lib/terser/version.rb +++ b/lib/terser/version.rb @@ -2,5 +2,5 @@ class Terser # Current version of Terser. - VERSION = "1.2.5" + VERSION = "1.2.6" end diff --git a/spec/terser_spec.rb b/spec/terser_spec.rb index 9b3aa7d..fd1aac1 100644 --- a/spec/terser_spec.rb +++ b/spec/terser_spec.rb @@ -233,7 +233,7 @@ JS end - it 'inlines function declaration' do + it 'not inlining function declarations in a loop' do minified = Terser.compile( code, :mangle => false, @@ -244,7 +244,8 @@ :unused => true } ) - expect(minified).not_to include("indirect(") + + expect(minified).to include("indirect(") expect(minified).not_to include("foo(") end diff --git a/vendor/terser b/vendor/terser index 58ba5c1..da1e6fb 160000 --- a/vendor/terser +++ b/vendor/terser @@ -1 +1 @@ -Subproject commit 58ba5c163fa1684f2a63c7bc19b7ebcf85b74f73 +Subproject commit da1e6fb2acd90e62bac69967718b89d6f00aab79