diff --git a/crates/swc/tests/fixture/issues-9xxx/9200/input/.swcrc b/crates/swc/tests/fixture/issues-9xxx/9200/input/.swcrc
new file mode 100644
index 000000000000..a56e12b83de9
--- /dev/null
+++ b/crates/swc/tests/fixture/issues-9xxx/9200/input/.swcrc
@@ -0,0 +1,20 @@
+{
+ "jsc": {
+ "parser": {
+ "syntax": "typescript",
+ "tsx": true,
+ "decorators": true
+ },
+ "loose": false,
+ "minify": {
+ "compress": false,
+ "mangle": false
+ },
+ "target": "es2022"
+ },
+ "module": {
+ "type": "es6"
+ },
+ "minify": false,
+ "isModule": false
+}
\ No newline at end of file
diff --git a/crates/swc/tests/fixture/issues-9xxx/9200/input/1.js b/crates/swc/tests/fixture/issues-9xxx/9200/input/1.js
new file mode 100644
index 000000000000..407f397c1586
--- /dev/null
+++ b/crates/swc/tests/fixture/issues-9xxx/9200/input/1.js
@@ -0,0 +1,3 @@
+let count = 0;
+for (var a = 1 || (2 in {}) in { x: 1 }) count++;
+console.log(count);
\ No newline at end of file
diff --git a/crates/swc/tests/fixture/issues-9xxx/9200/input/2.js b/crates/swc/tests/fixture/issues-9xxx/9200/input/2.js
new file mode 100644
index 000000000000..eda3b1cacf29
--- /dev/null
+++ b/crates/swc/tests/fixture/issues-9xxx/9200/input/2.js
@@ -0,0 +1,47 @@
+for (var a = (b in c) in {});
+for (var a = 1 || (b in c) in {});
+for (var a = 1 + (2 || (b in c)) in {});
+for (var a = (() => b in c) in {});
+for (var a = 1 || (() => b in c) in {});
+for (var a = (() => { b in c; }) in {});
+for (var a = [b in c] in {});
+for (var a = {b: b in c} in {});
+for (var a = (x = b in c) => {} in {});
+for (var a = class extends (b in c) {} in {});
+for (var a = function (x = b in c) {} in {});
+
+for (var a = (b in c);;);
+for (var a = 1 || (b in c);;);
+for (var a = 1 + (2 || (b in c));;);
+for (var a = (() => b in c);;);
+for (var a = 1 || (() => b in c);;);
+for (var a = (() => { b in c; });;);
+for (var a = [b in c];;);
+for (var a = {b: b in c};;);
+for (var a = (x = b in c) => {};;);
+for (var a = class extends (b in c) {};;);
+for (var a = function (x = b in c) {};;);
+
+for (var a in (b in c));
+for (var a in 1 || (b in c));
+for (var a in 1 + (2 || (b in c)));
+for (var a in (() => b in c));
+for (var a in 1 || (() => b in c));
+for (var a in (() => { b in c; }));
+for (var a in [b in c]);
+for (var a in {b: b in c});
+for (var a in (x = b in c) => {});
+for (var a in class extends (b in c) {});
+for (var a in function (x = b in c) {});
+
+for (;a = (b in c););
+for (;a = 1 || (b in c););
+for (;a = 1 + (2 || (b in c)););
+for (;a = (() => b in c););
+for (;a = 1 || (() => b in c););
+for (;a = (() => { b in c; }););
+for (;a = [b in c];);
+for (;a = {b: b in c};);
+for (;a = (x = b in c) => {};);
+for (;a = class extends (b in c) {};);
+for (;a = function (x = b in c) {};);
diff --git a/crates/swc/tests/fixture/issues-9xxx/9200/output/1.js b/crates/swc/tests/fixture/issues-9xxx/9200/output/1.js
new file mode 100644
index 000000000000..994d293e8bac
--- /dev/null
+++ b/crates/swc/tests/fixture/issues-9xxx/9200/output/1.js
@@ -0,0 +1,5 @@
+let count = 0;
+for(var a = 1 || (2 in {}) in {
+ x: 1
+})count++;
+console.log(count);
diff --git a/crates/swc/tests/fixture/issues-9xxx/9200/output/2.js b/crates/swc/tests/fixture/issues-9xxx/9200/output/2.js
new file mode 100644
index 000000000000..066c4d076d9a
--- /dev/null
+++ b/crates/swc/tests/fixture/issues-9xxx/9200/output/2.js
@@ -0,0 +1,72 @@
+for(var a = (b in c) in {});
+for(var a = 1 || (b in c) in {});
+for(var a = 1 + (2 || (b in c)) in {});
+for(var a = ()=>(b in c) in {});
+for(var a = 1 || (()=>(b in c)) in {});
+for(var a = ()=>{
+ b in c;
+} in {});
+for(var a = [
+ b in c
+] in {});
+for(var a = {
+ b: b in c
+} in {});
+for(var a = (x = b in c)=>{} in {});
+for(var a = class extends (b in c) {
+} in {});
+for(var a = function(x = b in c) {} in {});
+for(var a = (b in c);;);
+for(var a = 1 || (b in c);;);
+for(var a = 1 + (2 || (b in c));;);
+for(var a = ()=>(b in c);;);
+for(var a = 1 || (()=>(b in c));;);
+for(var a = ()=>{
+ b in c;
+};;);
+for(var a = [
+ b in c
+];;);
+for(var a = {
+ b: b in c
+};;);
+for(var a = (x = b in c)=>{};;);
+for(var a = class extends (b in c) {
+};;);
+for(var a = function(x = b in c) {};;);
+for(var a in b in c);
+for(var a in 1 || b in c);
+for(var a in 1 + (2 || b in c));
+for(var a in ()=>b in c);
+for(var a in 1 || (()=>b in c));
+for(var a in ()=>{
+ b in c;
+});
+for(var a in [
+ b in c
+]);
+for(var a in {
+ b: b in c
+});
+for(var a in (x = b in c)=>{});
+for(var a in class extends (b in c) {
+});
+for(var a in function(x = b in c) {});
+for(; a = b in c;);
+for(; a = 1 || b in c;);
+for(; a = 1 + (2 || b in c););
+for(; a = ()=>b in c;);
+for(; a = 1 || (()=>b in c););
+for(; a = ()=>{
+ b in c;
+};);
+for(; a = [
+ b in c
+];);
+for(; a = {
+ b: b in c
+};);
+for(; a = (x = b in c)=>{};);
+for(; a = class extends (b in c) {
+};);
+for(; a = function(x = b in c) {};);
diff --git a/crates/swc_ecma_minifier/tests/benches-full/echarts.js b/crates/swc_ecma_minifier/tests/benches-full/echarts.js
index 3899d7174345..cf1578a6c8ea 100644
--- a/crates/swc_ecma_minifier/tests/benches-full/echarts.js
+++ b/crates/swc_ecma_minifier/tests/benches-full/echarts.js
@@ -6154,7 +6154,7 @@
needDrawBg && this._renderBackground(style, style, boxX, boxY, outerWidth_1, outerHeight);
}
textY += lineHeight / 2, textPadding && (textX = getTextXForPadding(baseX, textAlign, textPadding), 'top' === verticalAlign ? textY += textPadding[0] : 'bottom' === verticalAlign && (textY -= textPadding[2]));
- for(var defaultLineWidth = 0, useDefaultFill = !1, textFill = null == (fill = ('fill' in style) ? style.fill : (useDefaultFill = !0, defaultStyle.fill)) || 'none' === fill ? null : fill.image || fill.colorStops ? '#000' : fill, textStroke = getStroke(('stroke' in style) ? style.stroke : bgColorDrawn || defaultStyle.autoStroke && !useDefaultFill ? null : (defaultLineWidth = 2, defaultStyle.stroke)), hasShadow = style.textShadowBlur > 0, fixedBoundingRect = null != style.width && ('truncate' === style.overflow || 'break' === style.overflow || 'breakAll' === style.overflow), calculatedLineHeight = contentBlock.calculatedLineHeight, i = 0; i < textLines.length; i++){
+ for(var defaultLineWidth = 0, useDefaultFill = !1, textFill = null == (fill = ('fill' in style) ? style.fill : (useDefaultFill = !0, defaultStyle.fill)) || 'none' === fill ? null : fill.image || fill.colorStops ? '#000' : fill, textStroke = getStroke('stroke' in style ? style.stroke : bgColorDrawn || defaultStyle.autoStroke && !useDefaultFill ? null : (defaultLineWidth = 2, defaultStyle.stroke)), hasShadow = style.textShadowBlur > 0, fixedBoundingRect = null != style.width && ('truncate' === style.overflow || 'break' === style.overflow || 'breakAll' === style.overflow), calculatedLineHeight = contentBlock.calculatedLineHeight, i = 0; i < textLines.length; i++){
var el = this._getOrCreateChild(TSpan), subElStyle = el.createStyle();
el.useStyle(subElStyle), subElStyle.text = textLines[i], subElStyle.x = textX, subElStyle.y = textY, textAlign && (subElStyle.textAlign = textAlign), subElStyle.textBaseline = 'middle', subElStyle.opacity = style.opacity, subElStyle.strokeFirst = !0, hasShadow && (subElStyle.shadowBlur = style.textShadowBlur || 0, subElStyle.shadowColor = style.textShadowColor || 'transparent', subElStyle.shadowOffsetX = style.textShadowOffsetX || 0, subElStyle.shadowOffsetY = style.textShadowOffsetY || 0), textStroke && (subElStyle.stroke = textStroke, subElStyle.lineWidth = style.lineWidth || defaultLineWidth, subElStyle.lineDash = style.lineDash, subElStyle.lineDashOffset = style.lineDashOffset || 0), textFill && (subElStyle.fill = textFill), subElStyle.font = textFont, textY += lineHeight, fixedBoundingRect && el.setBoundingRect(new BoundingRect(adjustTextX(subElStyle.x, style.width, subElStyle.textAlign), adjustTextY(subElStyle.y, calculatedLineHeight, subElStyle.textBaseline), style.width, calculatedLineHeight));
}
diff --git a/crates/swc_ecma_minifier/tests/benches-full/jquery.js b/crates/swc_ecma_minifier/tests/benches-full/jquery.js
index 1fb3fa08fb33..0b5f113a35bc 100644
--- a/crates/swc_ecma_minifier/tests/benches-full/jquery.js
+++ b/crates/swc_ecma_minifier/tests/benches-full/jquery.js
@@ -3125,9 +3125,9 @@
for(!function(props, specialEasing) {
var index, name, easing, value, hooks;
// camelCase, specialEasing and expand cssHook pass
- for(index in props)if (easing = specialEasing[name = camelCase(index)], Array.isArray(value = props[index]) && (easing = value[1], value = props[index] = value[0]), index !== name && (props[name] = value, delete props[index]), (hooks = jQuery.cssHooks[name]) && ("expand" in hooks)) // Not quite $.extend, this won't overwrite existing keys.
+ for(index in props)if (easing = specialEasing[name = camelCase(index)], Array.isArray(value = props[index]) && (easing = value[1], value = props[index] = value[0]), index !== name && (props[name] = value, delete props[index]), (hooks = jQuery.cssHooks[name]) && "expand" in hooks) // Not quite $.extend, this won't overwrite existing keys.
// Reusing 'index' because we have the correct "name"
- for(index in value = hooks.expand(value), delete props[name], value)(index in props) || (props[index] = value[index], specialEasing[index] = easing);
+ for(index in value = hooks.expand(value), delete props[name], value)index in props || (props[index] = value[index], specialEasing[index] = easing);
else specialEasing[name] = easing;
}(props, animation.opts.specialEasing); index < length; index++)if (result = Animation.prefilters[index].call(animation, elem, props, animation.opts)) return isFunction(result.stop) && (jQuery._queueHooks(animation.elem, animation.opts.queue).stop = result.stop.bind(result)), result;
return jQuery.map(props, createTween, animation), isFunction(animation.opts.start) && animation.opts.start.call(elem, animation), // Attach callbacks from options
diff --git a/crates/swc_ecma_minifier/tests/fixture/issues/2257/full/output.js b/crates/swc_ecma_minifier/tests/fixture/issues/2257/full/output.js
index 9f1e158a48d6..0dec27404a6c 100644
--- a/crates/swc_ecma_minifier/tests/fixture/issues/2257/full/output.js
+++ b/crates/swc_ecma_minifier/tests/fixture/issues/2257/full/output.js
@@ -8201,14 +8201,14 @@
for(var RegExpWrapper = function(pattern, flags) {
var rawFlags, dotAll, sticky, handled, result, state, thisIsRegExp = this instanceof RegExpWrapper, patternIsRegExp = isRegExp(pattern), flagsAreUndefined = void 0 === flags, groups = [], rawPattern = pattern;
if (!thisIsRegExp && patternIsRegExp && flagsAreUndefined && pattern.constructor === RegExpWrapper) return pattern;
- if ((patternIsRegExp || pattern instanceof RegExpWrapper) && (pattern = pattern.source, flagsAreUndefined && (flags = ("flags" in rawPattern) ? rawPattern.flags : getFlags.call(rawPattern))), pattern = void 0 === pattern ? "" : toString1(pattern), flags = void 0 === flags ? "" : toString1(flags), rawPattern = pattern, UNSUPPORTED_DOT_ALL && ("dotAll" in re1) && (dotAll = !!flags && flags.indexOf("s") > -1) && (flags = flags.replace(/s/g, "")), rawFlags = flags, UNSUPPORTED_Y && ("sticky" in re1) && (sticky = !!flags && flags.indexOf("y") > -1) && (flags = flags.replace(/y/g, "")), UNSUPPORTED_NCG && (pattern = (handled = handleNCG(pattern))[0], groups = handled[1]), result = inheritIfRequired(NativeRegExp(pattern, flags), thisIsRegExp ? this : RegExpPrototype, RegExpWrapper), (dotAll || sticky || groups.length) && (state = enforceInternalState(result), dotAll && (state.dotAll = !0, state.raw = RegExpWrapper(handleDotAll(pattern), rawFlags)), sticky && (state.sticky = !0), groups.length && (state.groups = groups)), pattern !== rawPattern) try {
+ if ((patternIsRegExp || pattern instanceof RegExpWrapper) && (pattern = pattern.source, flagsAreUndefined && (flags = "flags" in rawPattern ? rawPattern.flags : getFlags.call(rawPattern))), pattern = void 0 === pattern ? "" : toString1(pattern), flags = void 0 === flags ? "" : toString1(flags), rawPattern = pattern, UNSUPPORTED_DOT_ALL && "dotAll" in re1 && (dotAll = !!flags && flags.indexOf("s") > -1) && (flags = flags.replace(/s/g, "")), rawFlags = flags, UNSUPPORTED_Y && "sticky" in re1 && (sticky = !!flags && flags.indexOf("y") > -1) && (flags = flags.replace(/y/g, "")), UNSUPPORTED_NCG && (pattern = (handled = handleNCG(pattern))[0], groups = handled[1]), result = inheritIfRequired(NativeRegExp(pattern, flags), thisIsRegExp ? this : RegExpPrototype, RegExpWrapper), (dotAll || sticky || groups.length) && (state = enforceInternalState(result), dotAll && (state.dotAll = !0, state.raw = RegExpWrapper(handleDotAll(pattern), rawFlags)), sticky && (state.sticky = !0), groups.length && (state.groups = groups)), pattern !== rawPattern) try {
// fails in old engines, but we have no alternatives for unsupported regex syntax
createNonEnumerableProperty(result, "source", "" === rawPattern ? "(?:)" : rawPattern);
} catch (error) {
/* empty */ }
return result;
}, proxy = function(key) {
- (key in RegExpWrapper) || defineProperty(RegExpWrapper, key, {
+ key in RegExpWrapper || defineProperty(RegExpWrapper, key, {
configurable: !0,
get: function() {
return NativeRegExp[key];
diff --git a/crates/swc_ecma_minifier/tests/projects/output/jquery-1.9.1.js b/crates/swc_ecma_minifier/tests/projects/output/jquery-1.9.1.js
index b3cf88a988f9..22b38bee1e3d 100644
--- a/crates/swc_ecma_minifier/tests/projects/output/jquery-1.9.1.js
+++ b/crates/swc_ecma_minifier/tests/projects/output/jquery-1.9.1.js
@@ -3855,9 +3855,9 @@
for(function(props, specialEasing) {
var value, name1, index, easing, hooks;
// camelCase, specialEasing and expand cssHook pass
- for(index in props)if (easing = specialEasing[name1 = jQuery.camelCase(index)], value = props[index], jQuery.isArray(value) && (easing = value[1], value = props[index] = value[0]), index !== name1 && (props[name1] = value, delete props[index]), (hooks = jQuery.cssHooks[name1]) && ("expand" in hooks)) // not quite $.extend, this wont overwrite keys already present.
+ for(index in props)if (easing = specialEasing[name1 = jQuery.camelCase(index)], value = props[index], jQuery.isArray(value) && (easing = value[1], value = props[index] = value[0]), index !== name1 && (props[name1] = value, delete props[index]), (hooks = jQuery.cssHooks[name1]) && "expand" in hooks) // not quite $.extend, this wont overwrite keys already present.
// also - reusing 'index' from above because we have the correct "name"
- for(index in value = hooks.expand(value), delete props[name1], value)(index in props) || (props[index] = value[index], specialEasing[index] = easing);
+ for(index in value = hooks.expand(value), delete props[name1], value)index in props || (props[index] = value[index], specialEasing[index] = easing);
else specialEasing[name1] = easing;
}(props, animation.opts.specialEasing); index < length; index++)if (result = animationPrefilters[index].call(animation, elem, props, animation.opts)) return result;
// attach callbacks from options
diff --git a/crates/swc_ecma_transforms_base/src/fixer.rs b/crates/swc_ecma_transforms_base/src/fixer.rs
index 265e64e6c8b2..1e8737a3468b 100644
--- a/crates/swc_ecma_transforms_base/src/fixer.rs
+++ b/crates/swc_ecma_transforms_base/src/fixer.rs
@@ -71,17 +71,6 @@ enum Context {
FreeExpr,
}
-macro_rules! array {
- ($name:ident, $T:tt) => {
- fn $name(&mut self, e: &mut $T) {
- let old = self.ctx;
- self.ctx = Context::ForcedExpr;
- e.elems.visit_mut_with(self);
- self.ctx = old;
- }
- };
-}
-
impl Fixer<'_> {
fn wrap_callee(&mut self, e: &mut Expr) {
match e {
@@ -102,7 +91,13 @@ impl Fixer<'_> {
impl VisitMut for Fixer<'_> {
noop_visit_mut_type!();
- array!(visit_mut_array_lit, ArrayLit);
+ fn visit_mut_array_lit(&mut self, e: &mut ArrayLit) {
+ let ctx = mem::replace(&mut self.ctx, Context::ForcedExpr);
+ let in_for_stmt_head = mem::replace(&mut self.in_for_stmt_head, false);
+ e.elems.visit_mut_with(self);
+ self.in_for_stmt_head = in_for_stmt_head;
+ self.ctx = ctx;
+ }
fn visit_mut_arrow_expr(&mut self, node: &mut ArrowExpr) {
let old = self.ctx;
@@ -173,7 +168,9 @@ impl VisitMut for Fixer<'_> {
}
fn visit_mut_assign_pat(&mut self, node: &mut AssignPat) {
+ let in_for_stmt_head = mem::replace(&mut self.in_for_stmt_head, false);
node.visit_mut_children_with(self);
+ self.in_for_stmt_head = in_for_stmt_head;
if let Expr::Seq(..) = &*node.right {
self.wrap(&mut node.right);
@@ -185,7 +182,9 @@ impl VisitMut for Fixer<'_> {
let old = self.ctx;
self.ctx = Context::ForcedExpr;
+ let in_for_stmt_head = mem::replace(&mut self.in_for_stmt_head, false);
node.value.visit_mut_with(self);
+ self.in_for_stmt_head = in_for_stmt_head;
self.ctx = old;
}
@@ -345,6 +344,12 @@ impl VisitMut for Fixer<'_> {
}
}
+ fn visit_mut_block_stmt(&mut self, n: &mut BlockStmt) {
+ let in_for_stmt_head = mem::replace(&mut self.in_for_stmt_head, false);
+ n.visit_mut_children_with(self);
+ self.in_for_stmt_head = in_for_stmt_head;
+ }
+
fn visit_mut_block_stmt_or_expr(&mut self, body: &mut BlockStmtOrExpr) {
body.visit_mut_children_with(self);
@@ -376,9 +381,14 @@ impl VisitMut for Fixer<'_> {
}
fn visit_mut_class(&mut self, node: &mut Class) {
- let old = self.ctx;
- self.ctx = Context::Default;
- node.visit_mut_children_with(self);
+ let ctx = mem::replace(&mut self.ctx, Context::Default);
+
+ node.super_class.visit_mut_with(self);
+
+ let in_for_stmt_head = mem::replace(&mut self.in_for_stmt_head, false);
+ node.body.visit_mut_with(self);
+ self.in_for_stmt_head = in_for_stmt_head;
+
match &mut node.super_class {
Some(e)
if e.is_seq()
@@ -393,7 +403,7 @@ impl VisitMut for Fixer<'_> {
}
_ => {}
};
- self.ctx = old;
+ self.ctx = ctx;
node.body.retain(|m| !matches!(m, ClassMember::Empty(..)));
}
@@ -472,6 +482,12 @@ impl VisitMut for Fixer<'_> {
self.handle_expr_stmt(&mut s.expr);
}
+ fn visit_mut_for_head(&mut self, n: &mut ForHead) {
+ let in_for_stmt_head = mem::replace(&mut self.in_for_stmt_head, true);
+ n.visit_mut_children_with(self);
+ self.in_for_stmt_head = in_for_stmt_head;
+ }
+
fn visit_mut_for_of_stmt(&mut self, s: &mut ForOfStmt) {
s.visit_mut_children_with(self);
@@ -507,15 +523,13 @@ impl VisitMut for Fixer<'_> {
}
fn visit_mut_for_stmt(&mut self, n: &mut ForStmt) {
- let old = self.in_for_stmt_head;
- self.in_for_stmt_head = true;
+ let in_for_stmt_head = mem::replace(&mut self.in_for_stmt_head, true);
n.init.visit_mut_with(self);
+ self.in_for_stmt_head = in_for_stmt_head;
+
n.test.visit_mut_with(self);
n.update.visit_mut_with(self);
-
- self.in_for_stmt_head = false;
n.body.visit_mut_with(self);
- self.in_for_stmt_head = old;
}
fn visit_mut_if_stmt(&mut self, node: &mut IfStmt) {
@@ -594,10 +608,9 @@ impl VisitMut for Fixer<'_> {
}
fn visit_mut_new_expr(&mut self, node: &mut NewExpr) {
- let old = self.ctx;
- self.ctx = Context::ForcedExpr;
+ let ctx = mem::replace(&mut self.ctx, Context::ForcedExpr);
+
node.args.visit_mut_with(self);
- self.ctx = old;
self.ctx = Context::Callee { is_new: true };
node.callee.visit_mut_with(self);
@@ -612,7 +625,7 @@ impl VisitMut for Fixer<'_> {
| Expr::Lit(..) => self.wrap(&mut node.callee),
_ => {}
}
- self.ctx = old;
+ self.ctx = ctx;
}
fn visit_mut_opt_call(&mut self, node: &mut OptCall) {
@@ -767,6 +780,31 @@ impl VisitMut for Fixer<'_> {
expr.arg.visit_mut_with(self);
self.ctx = old;
}
+
+ fn visit_mut_object_lit(&mut self, n: &mut ObjectLit) {
+ let in_for_stmt_head = mem::replace(&mut self.in_for_stmt_head, false);
+ n.visit_mut_children_with(self);
+ self.in_for_stmt_head = in_for_stmt_head;
+ }
+
+ fn visit_mut_params(&mut self, n: &mut Vec) {
+ let in_for_stmt_head = mem::replace(&mut self.in_for_stmt_head, false);
+ n.visit_mut_children_with(self);
+ self.in_for_stmt_head = in_for_stmt_head;
+ }
+
+ // only used in ArrowExpr
+ fn visit_mut_pats(&mut self, n: &mut Vec) {
+ let in_for_stmt_head = mem::replace(&mut self.in_for_stmt_head, false);
+ n.visit_mut_children_with(self);
+ self.in_for_stmt_head = in_for_stmt_head;
+ }
+
+ fn visit_mut_expr_or_spreads(&mut self, n: &mut Vec) {
+ let in_for_stmt_head = mem::replace(&mut self.in_for_stmt_head, false);
+ n.visit_mut_children_with(self);
+ self.in_for_stmt_head = in_for_stmt_head;
+ }
}
impl Fixer<'_> {
@@ -774,6 +812,14 @@ impl Fixer<'_> {
let mut has_padding_value = false;
match e {
Expr::Bin(BinExpr { op: op!("in"), .. }) if self.in_for_stmt_head => {
+ // TODO:
+ // if the in expression is in a parentheses, we should not wrap it with a
+ // parentheses again. But the parentheses is added later,
+ // so we don't have enough information to detect it at this moment.
+ // Example:
+ // for(var a = 1 + (2 || b in c) in {});
+ // |~~~~~~~~~~~|
+ // this parentheses is removed by unwrap_expr and added again later
self.wrap(e);
}