Skip to content

Commit

Permalink
fix precedence of #private in ... operator
Browse files Browse the repository at this point in the history
  • Loading branch information
fabiosantoscode committed Apr 2, 2024
1 parent 694846c commit 0e04a22
Show file tree
Hide file tree
Showing 3 changed files with 198 additions and 34 deletions.
55 changes: 48 additions & 7 deletions lib/output.js
Expand Up @@ -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) {
Expand Down
55 changes: 28 additions & 27 deletions lib/parse.js
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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) {
Expand Down
122 changes: 122 additions & 0 deletions test/compress/class-properties.js
Expand Up @@ -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))}}"
}

0 comments on commit 0e04a22

Please sign in to comment.