From 0e04a22cb36a34d0e2a76ca790c5a6138e2262e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Santos?= Date: Tue, 2 Apr 2024 15:01:00 +0100 Subject: [PATCH] fix precedence of `#private in ...` operator --- lib/output.js | 55 ++++++++++++-- lib/parse.js | 55 +++++++------- test/compress/class-properties.js | 122 ++++++++++++++++++++++++++++++ 3 files changed, 198 insertions(+), 34 deletions(-) diff --git a/lib/output.js b/lib/output.js index 545f97f25..75edb83f4 100644 --- a/lib/output.js +++ b/lib/output.js @@ -1072,25 +1072,66 @@ function OutputStream(options) { return true; // this deals with precedence: 3 * (2 + 1) if (p instanceof AST_Binary) { - const po = p.operator; - const so = this.operator; + const parent_op = p.operator; + const op = this.operator; - if (so === "??" && (po === "||" || po === "&&")) { + // It is forbidden for ?? to be used with || or && without parens. + if (op === "??" && (parent_op === "||" || parent_op === "&&")) { + return true; + } + if (parent_op === "??" && (op === "||" || op === "&&")) { return true; } - if (po === "??" && (so === "||" || so === "&&")) { + const pp = PRECEDENCE[parent_op]; + const sp = PRECEDENCE[op]; + if (pp > sp + || (pp == sp + && (this === p.right || parent_op == "**"))) { return true; } + } + if (p instanceof AST_PrivateIn) { + const op = this.operator; + + const pp = PRECEDENCE["in"]; + const sp = PRECEDENCE[op]; + if (pp > sp || (pp == sp && this === p.value)) { + return true; + } + } + }); + + PARENS(AST_PrivateIn, function(output) { + var p = output.parent(); + // (#x in this)() + if (p instanceof AST_Call && p.expression === this) { + return true; + } + // typeof (#x in this) + if (p instanceof AST_Unary) { + return true; + } + // (#x in this)["prop"], (#x in this).prop + if (p instanceof AST_PropAccess && p.expression === this) { + return true; + } + // same precedence as regular in operator + if (p instanceof AST_Binary) { + const parent_op = p.operator; - const pp = PRECEDENCE[po]; - const sp = PRECEDENCE[so]; + const pp = PRECEDENCE[parent_op]; + const sp = PRECEDENCE["in"]; if (pp > sp || (pp == sp - && (this === p.right || po == "**"))) { + && (this === p.right || parent_op == "**"))) { return true; } } + // rules are the same as binary in, but the class differs + if (p instanceof AST_PrivateIn && this === p.value) { + return true; + } }); PARENS(AST_Yield, function(output) { diff --git a/lib/parse.js b/lib/parse.js index a79800525..1a5c21711 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -1027,9 +1027,8 @@ var LOGICAL_ASSIGNMENT = makePredicate([ "??=", "&&=", "||=" ]); var PRECEDENCE = (function(a, ret) { for (var i = 0; i < a.length; ++i) { - var b = a[i]; - for (var j = 0; j < b.length; ++j) { - ret[b[j]] = i + 1; + for (const op of a[i]) { + ret[op] = i + 1; } } return ret; @@ -2381,29 +2380,6 @@ function parse($TEXT, options) { if (is("template_head")) { return subscripts(template_string(), allow_calls); } - if (is("privatename")) { - if(!S.in_class) { - croak("Private field must be used in an enclosing class"); - } - - const start = S.token; - const key = new AST_SymbolPrivateProperty({ - start, - name: start.value, - end: start - }); - next(); - expect_token("operator", "in"); - - const private_in = new AST_PrivateIn({ - start, - key, - value: subscripts(as_atom_node(), allow_calls), - end: prev() - }); - - return subscripts(private_in, allow_calls); - } if (ATOMIC_START_TOKEN.has(S.token.type)) { return subscripts(as_atom_node(), allow_calls); } @@ -3316,7 +3292,32 @@ function parse($TEXT, options) { }; function expr_ops(no_in) { - return expr_op(maybe_unary(true, true), 0, no_in); + // maybe_unary won't return us a AST_SymbolPrivateProperty + if (!no_in && is("privatename")) { + if(!S.in_class) { + croak("Private field must be used in an enclosing class"); + } + + const start = S.token; + const key = new AST_SymbolPrivateProperty({ + start, + name: start.value, + end: start + }); + next(); + expect_token("operator", "in"); + + const private_in = new AST_PrivateIn({ + start, + key, + value: expr_op(maybe_unary(true), PRECEDENCE["in"], no_in), + end: prev() + }); + + return expr_op(private_in, 0, no_in); + } else { + return expr_op(maybe_unary(true, true), 0, no_in); + } } var maybe_conditional = function(no_in) { diff --git a/test/compress/class-properties.js b/test/compress/class-properties.js index 6bfeeb90c..63830ba01 100644 --- a/test/compress/class-properties.js +++ b/test/compress/class-properties.js @@ -452,3 +452,125 @@ allow_subscript_private_field: { "PASS" ] } + +parens_in: { + input: { + class X { + static { + console.log(!(#x in this)); + } + } + } + expect_exact: "class X{static{console.log(!(#x in this))}}" +} + +parens_in_2: { + input: { + class X { + static { + console.log((#x in this) + 1); + } + } + } + expect_exact: "class X{static{console.log((#x in this)+1)}}" +} + +parens_in_3: { + input: { + class X { + static { + console.log(#x in (this + 1)); + } + } + } + expect_exact: "class X{static{console.log(#x in this+1)}}" +} + +parens_in_4: { + input: { + class X { + static { + console.log(#x in this + 1); + } + } + } + expect_exact: "class X{static{console.log(#x in this+1)}}" +} + +parens_in_5: { + input: { + class X { + static { + console.log((#x in this) | 1); + } + } + } + expect_exact: "class X{static{console.log(#x in this|1)}}" +} + +parens_in_6: { + input: { + class X { + static { + console.log(#x in (this | 1)); + } + } + } + expect_exact: "class X{static{console.log(#x in(this|1))}}" +} + +parens_in_7: { + input: { + class X { + static { + console.log(#x in this | 1); + } + } + } + expect_exact: "class X{static{console.log(#x in this|1)}}" +} + +parens_in_8: { + input: { + class X { + static { + console.log((#x in this) in this); + } + } + } + expect_exact: "class X{static{console.log(#x in this in this)}}" +} + +parens_in_9: { + input: { + class X { + static { + console.log(#x in (this in this)); + } + } + } + expect_exact: "class X{static{console.log(#x in(this in this))}}" +} + +parens_in_10: { + input: { + class X { + static { + console.log(#x in this in this); + } + } + } + expect_exact: "class X{static{console.log(#x in this in this)}}" +} + +parens_in_11: { + input: { + class X { + static { + console.log(this in (#x in this)); + } + } + } + expect_exact: "class X{static{console.log(this in(#x in this))}}" +} +