Skip to content

Commit 5cd837f

Browse files
authoredJul 13, 2024··
fix(es/fixer): Wrap in expr in for-in head (#9209)
**Related issue:** - Closes #9200
1 parent 78b2964 commit 5cd837f

File tree

10 files changed

+225
-32
lines changed

10 files changed

+225
-32
lines changed
 
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"jsc": {
3+
"parser": {
4+
"syntax": "typescript",
5+
"tsx": true,
6+
"decorators": true
7+
},
8+
"loose": false,
9+
"minify": {
10+
"compress": false,
11+
"mangle": false
12+
},
13+
"target": "es2022"
14+
},
15+
"module": {
16+
"type": "es6"
17+
},
18+
"minify": false,
19+
"isModule": false
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
let count = 0;
2+
for (var a = 1 || (2 in {}) in { x: 1 }) count++;
3+
console.log(count);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
for (var a = (b in c) in {});
2+
for (var a = 1 || (b in c) in {});
3+
for (var a = 1 + (2 || (b in c)) in {});
4+
for (var a = (() => b in c) in {});
5+
for (var a = 1 || (() => b in c) in {});
6+
for (var a = (() => { b in c; }) in {});
7+
for (var a = [b in c] in {});
8+
for (var a = {b: b in c} in {});
9+
for (var a = (x = b in c) => {} in {});
10+
for (var a = class extends (b in c) {} in {});
11+
for (var a = function (x = b in c) {} in {});
12+
13+
for (var a = (b in c);;);
14+
for (var a = 1 || (b in c);;);
15+
for (var a = 1 + (2 || (b in c));;);
16+
for (var a = (() => b in c);;);
17+
for (var a = 1 || (() => b in c);;);
18+
for (var a = (() => { b in c; });;);
19+
for (var a = [b in c];;);
20+
for (var a = {b: b in c};;);
21+
for (var a = (x = b in c) => {};;);
22+
for (var a = class extends (b in c) {};;);
23+
for (var a = function (x = b in c) {};;);
24+
25+
for (var a in (b in c));
26+
for (var a in 1 || (b in c));
27+
for (var a in 1 + (2 || (b in c)));
28+
for (var a in (() => b in c));
29+
for (var a in 1 || (() => b in c));
30+
for (var a in (() => { b in c; }));
31+
for (var a in [b in c]);
32+
for (var a in {b: b in c});
33+
for (var a in (x = b in c) => {});
34+
for (var a in class extends (b in c) {});
35+
for (var a in function (x = b in c) {});
36+
37+
for (;a = (b in c););
38+
for (;a = 1 || (b in c););
39+
for (;a = 1 + (2 || (b in c)););
40+
for (;a = (() => b in c););
41+
for (;a = 1 || (() => b in c););
42+
for (;a = (() => { b in c; }););
43+
for (;a = [b in c];);
44+
for (;a = {b: b in c};);
45+
for (;a = (x = b in c) => {};);
46+
for (;a = class extends (b in c) {};);
47+
for (;a = function (x = b in c) {};);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
let count = 0;
2+
for(var a = 1 || (2 in {}) in {
3+
x: 1
4+
})count++;
5+
console.log(count);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
for(var a = (b in c) in {});
2+
for(var a = 1 || (b in c) in {});
3+
for(var a = 1 + (2 || (b in c)) in {});
4+
for(var a = ()=>(b in c) in {});
5+
for(var a = 1 || (()=>(b in c)) in {});
6+
for(var a = ()=>{
7+
b in c;
8+
} in {});
9+
for(var a = [
10+
b in c
11+
] in {});
12+
for(var a = {
13+
b: b in c
14+
} in {});
15+
for(var a = (x = b in c)=>{} in {});
16+
for(var a = class extends (b in c) {
17+
} in {});
18+
for(var a = function(x = b in c) {} in {});
19+
for(var a = (b in c);;);
20+
for(var a = 1 || (b in c);;);
21+
for(var a = 1 + (2 || (b in c));;);
22+
for(var a = ()=>(b in c);;);
23+
for(var a = 1 || (()=>(b in c));;);
24+
for(var a = ()=>{
25+
b in c;
26+
};;);
27+
for(var a = [
28+
b in c
29+
];;);
30+
for(var a = {
31+
b: b in c
32+
};;);
33+
for(var a = (x = b in c)=>{};;);
34+
for(var a = class extends (b in c) {
35+
};;);
36+
for(var a = function(x = b in c) {};;);
37+
for(var a in b in c);
38+
for(var a in 1 || b in c);
39+
for(var a in 1 + (2 || b in c));
40+
for(var a in ()=>b in c);
41+
for(var a in 1 || (()=>b in c));
42+
for(var a in ()=>{
43+
b in c;
44+
});
45+
for(var a in [
46+
b in c
47+
]);
48+
for(var a in {
49+
b: b in c
50+
});
51+
for(var a in (x = b in c)=>{});
52+
for(var a in class extends (b in c) {
53+
});
54+
for(var a in function(x = b in c) {});
55+
for(; a = b in c;);
56+
for(; a = 1 || b in c;);
57+
for(; a = 1 + (2 || b in c););
58+
for(; a = ()=>b in c;);
59+
for(; a = 1 || (()=>b in c););
60+
for(; a = ()=>{
61+
b in c;
62+
};);
63+
for(; a = [
64+
b in c
65+
];);
66+
for(; a = {
67+
b: b in c
68+
};);
69+
for(; a = (x = b in c)=>{};);
70+
for(; a = class extends (b in c) {
71+
};);
72+
for(; a = function(x = b in c) {};);

‎crates/swc_ecma_minifier/tests/benches-full/echarts.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -6154,7 +6154,7 @@
61546154
needDrawBg && this._renderBackground(style, style, boxX, boxY, outerWidth_1, outerHeight);
61556155
}
61566156
textY += lineHeight / 2, textPadding && (textX = getTextXForPadding(baseX, textAlign, textPadding), 'top' === verticalAlign ? textY += textPadding[0] : 'bottom' === verticalAlign && (textY -= textPadding[2]));
6157-
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++){
6157+
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++){
61586158
var el = this._getOrCreateChild(TSpan), subElStyle = el.createStyle();
61596159
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));
61606160
}

‎crates/swc_ecma_minifier/tests/benches-full/jquery.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -3125,9 +3125,9 @@
31253125
for(!function(props, specialEasing) {
31263126
var index, name, easing, value, hooks;
31273127
// camelCase, specialEasing and expand cssHook pass
3128-
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.
3128+
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.
31293129
// Reusing 'index' because we have the correct "name"
3130-
for(index in value = hooks.expand(value), delete props[name], value)(index in props) || (props[index] = value[index], specialEasing[index] = easing);
3130+
for(index in value = hooks.expand(value), delete props[name], value)index in props || (props[index] = value[index], specialEasing[index] = easing);
31313131
else specialEasing[name] = easing;
31323132
}(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;
31333133
return jQuery.map(props, createTween, animation), isFunction(animation.opts.start) && animation.opts.start.call(elem, animation), // Attach callbacks from options

‎crates/swc_ecma_minifier/tests/fixture/issues/2257/full/output.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -8201,14 +8201,14 @@
82018201
for(var RegExpWrapper = function(pattern, flags) {
82028202
var rawFlags, dotAll, sticky, handled, result, state, thisIsRegExp = this instanceof RegExpWrapper, patternIsRegExp = isRegExp(pattern), flagsAreUndefined = void 0 === flags, groups = [], rawPattern = pattern;
82038203
if (!thisIsRegExp && patternIsRegExp && flagsAreUndefined && pattern.constructor === RegExpWrapper) return pattern;
8204-
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 {
8204+
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 {
82058205
// fails in old engines, but we have no alternatives for unsupported regex syntax
82068206
createNonEnumerableProperty(result, "source", "" === rawPattern ? "(?:)" : rawPattern);
82078207
} catch (error) {
82088208
/* empty */ }
82098209
return result;
82108210
}, proxy = function(key) {
8211-
(key in RegExpWrapper) || defineProperty(RegExpWrapper, key, {
8211+
key in RegExpWrapper || defineProperty(RegExpWrapper, key, {
82128212
configurable: !0,
82138213
get: function() {
82148214
return NativeRegExp[key];

‎crates/swc_ecma_minifier/tests/projects/output/jquery-1.9.1.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -3855,9 +3855,9 @@
38553855
for(function(props, specialEasing) {
38563856
var value, name1, index, easing, hooks;
38573857
// camelCase, specialEasing and expand cssHook pass
3858-
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.
3858+
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.
38593859
// also - reusing 'index' from above because we have the correct "name"
3860-
for(index in value = hooks.expand(value), delete props[name1], value)(index in props) || (props[index] = value[index], specialEasing[index] = easing);
3860+
for(index in value = hooks.expand(value), delete props[name1], value)index in props || (props[index] = value[index], specialEasing[index] = easing);
38613861
else specialEasing[name1] = easing;
38623862
}(props, animation.opts.specialEasing); index < length; index++)if (result = animationPrefilters[index].call(animation, elem, props, animation.opts)) return result;
38633863
// attach callbacks from options

‎crates/swc_ecma_transforms_base/src/fixer.rs

+71-25
Original file line numberDiff line numberDiff line change
@@ -71,17 +71,6 @@ enum Context {
7171
FreeExpr,
7272
}
7373

74-
macro_rules! array {
75-
($name:ident, $T:tt) => {
76-
fn $name(&mut self, e: &mut $T) {
77-
let old = self.ctx;
78-
self.ctx = Context::ForcedExpr;
79-
e.elems.visit_mut_with(self);
80-
self.ctx = old;
81-
}
82-
};
83-
}
84-
8574
impl Fixer<'_> {
8675
fn wrap_callee(&mut self, e: &mut Expr) {
8776
match e {
@@ -102,7 +91,13 @@ impl Fixer<'_> {
10291
impl VisitMut for Fixer<'_> {
10392
noop_visit_mut_type!();
10493

105-
array!(visit_mut_array_lit, ArrayLit);
94+
fn visit_mut_array_lit(&mut self, e: &mut ArrayLit) {
95+
let ctx = mem::replace(&mut self.ctx, Context::ForcedExpr);
96+
let in_for_stmt_head = mem::replace(&mut self.in_for_stmt_head, false);
97+
e.elems.visit_mut_with(self);
98+
self.in_for_stmt_head = in_for_stmt_head;
99+
self.ctx = ctx;
100+
}
106101

107102
fn visit_mut_arrow_expr(&mut self, node: &mut ArrowExpr) {
108103
let old = self.ctx;
@@ -173,7 +168,9 @@ impl VisitMut for Fixer<'_> {
173168
}
174169

175170
fn visit_mut_assign_pat(&mut self, node: &mut AssignPat) {
171+
let in_for_stmt_head = mem::replace(&mut self.in_for_stmt_head, false);
176172
node.visit_mut_children_with(self);
173+
self.in_for_stmt_head = in_for_stmt_head;
177174

178175
if let Expr::Seq(..) = &*node.right {
179176
self.wrap(&mut node.right);
@@ -185,7 +182,9 @@ impl VisitMut for Fixer<'_> {
185182

186183
let old = self.ctx;
187184
self.ctx = Context::ForcedExpr;
185+
let in_for_stmt_head = mem::replace(&mut self.in_for_stmt_head, false);
188186
node.value.visit_mut_with(self);
187+
self.in_for_stmt_head = in_for_stmt_head;
189188
self.ctx = old;
190189
}
191190

@@ -345,6 +344,12 @@ impl VisitMut for Fixer<'_> {
345344
}
346345
}
347346

347+
fn visit_mut_block_stmt(&mut self, n: &mut BlockStmt) {
348+
let in_for_stmt_head = mem::replace(&mut self.in_for_stmt_head, false);
349+
n.visit_mut_children_with(self);
350+
self.in_for_stmt_head = in_for_stmt_head;
351+
}
352+
348353
fn visit_mut_block_stmt_or_expr(&mut self, body: &mut BlockStmtOrExpr) {
349354
body.visit_mut_children_with(self);
350355

@@ -376,9 +381,14 @@ impl VisitMut for Fixer<'_> {
376381
}
377382

378383
fn visit_mut_class(&mut self, node: &mut Class) {
379-
let old = self.ctx;
380-
self.ctx = Context::Default;
381-
node.visit_mut_children_with(self);
384+
let ctx = mem::replace(&mut self.ctx, Context::Default);
385+
386+
node.super_class.visit_mut_with(self);
387+
388+
let in_for_stmt_head = mem::replace(&mut self.in_for_stmt_head, false);
389+
node.body.visit_mut_with(self);
390+
self.in_for_stmt_head = in_for_stmt_head;
391+
382392
match &mut node.super_class {
383393
Some(e)
384394
if e.is_seq()
@@ -393,7 +403,7 @@ impl VisitMut for Fixer<'_> {
393403
}
394404
_ => {}
395405
};
396-
self.ctx = old;
406+
self.ctx = ctx;
397407

398408
node.body.retain(|m| !matches!(m, ClassMember::Empty(..)));
399409
}
@@ -472,6 +482,12 @@ impl VisitMut for Fixer<'_> {
472482
self.handle_expr_stmt(&mut s.expr);
473483
}
474484

485+
fn visit_mut_for_head(&mut self, n: &mut ForHead) {
486+
let in_for_stmt_head = mem::replace(&mut self.in_for_stmt_head, true);
487+
n.visit_mut_children_with(self);
488+
self.in_for_stmt_head = in_for_stmt_head;
489+
}
490+
475491
fn visit_mut_for_of_stmt(&mut self, s: &mut ForOfStmt) {
476492
s.visit_mut_children_with(self);
477493

@@ -507,15 +523,13 @@ impl VisitMut for Fixer<'_> {
507523
}
508524

509525
fn visit_mut_for_stmt(&mut self, n: &mut ForStmt) {
510-
let old = self.in_for_stmt_head;
511-
self.in_for_stmt_head = true;
526+
let in_for_stmt_head = mem::replace(&mut self.in_for_stmt_head, true);
512527
n.init.visit_mut_with(self);
528+
self.in_for_stmt_head = in_for_stmt_head;
529+
513530
n.test.visit_mut_with(self);
514531
n.update.visit_mut_with(self);
515-
516-
self.in_for_stmt_head = false;
517532
n.body.visit_mut_with(self);
518-
self.in_for_stmt_head = old;
519533
}
520534

521535
fn visit_mut_if_stmt(&mut self, node: &mut IfStmt) {
@@ -594,10 +608,9 @@ impl VisitMut for Fixer<'_> {
594608
}
595609

596610
fn visit_mut_new_expr(&mut self, node: &mut NewExpr) {
597-
let old = self.ctx;
598-
self.ctx = Context::ForcedExpr;
611+
let ctx = mem::replace(&mut self.ctx, Context::ForcedExpr);
612+
599613
node.args.visit_mut_with(self);
600-
self.ctx = old;
601614

602615
self.ctx = Context::Callee { is_new: true };
603616
node.callee.visit_mut_with(self);
@@ -612,7 +625,7 @@ impl VisitMut for Fixer<'_> {
612625
| Expr::Lit(..) => self.wrap(&mut node.callee),
613626
_ => {}
614627
}
615-
self.ctx = old;
628+
self.ctx = ctx;
616629
}
617630

618631
fn visit_mut_opt_call(&mut self, node: &mut OptCall) {
@@ -767,13 +780,46 @@ impl VisitMut for Fixer<'_> {
767780
expr.arg.visit_mut_with(self);
768781
self.ctx = old;
769782
}
783+
784+
fn visit_mut_object_lit(&mut self, n: &mut ObjectLit) {
785+
let in_for_stmt_head = mem::replace(&mut self.in_for_stmt_head, false);
786+
n.visit_mut_children_with(self);
787+
self.in_for_stmt_head = in_for_stmt_head;
788+
}
789+
790+
fn visit_mut_params(&mut self, n: &mut Vec<Param>) {
791+
let in_for_stmt_head = mem::replace(&mut self.in_for_stmt_head, false);
792+
n.visit_mut_children_with(self);
793+
self.in_for_stmt_head = in_for_stmt_head;
794+
}
795+
796+
// only used in ArrowExpr
797+
fn visit_mut_pats(&mut self, n: &mut Vec<Pat>) {
798+
let in_for_stmt_head = mem::replace(&mut self.in_for_stmt_head, false);
799+
n.visit_mut_children_with(self);
800+
self.in_for_stmt_head = in_for_stmt_head;
801+
}
802+
803+
fn visit_mut_expr_or_spreads(&mut self, n: &mut Vec<ExprOrSpread>) {
804+
let in_for_stmt_head = mem::replace(&mut self.in_for_stmt_head, false);
805+
n.visit_mut_children_with(self);
806+
self.in_for_stmt_head = in_for_stmt_head;
807+
}
770808
}
771809

772810
impl Fixer<'_> {
773811
fn wrap_with_paren_if_required(&mut self, e: &mut Expr) {
774812
let mut has_padding_value = false;
775813
match e {
776814
Expr::Bin(BinExpr { op: op!("in"), .. }) if self.in_for_stmt_head => {
815+
// TODO:
816+
// if the in expression is in a parentheses, we should not wrap it with a
817+
// parentheses again. But the parentheses is added later,
818+
// so we don't have enough information to detect it at this moment.
819+
// Example:
820+
// for(var a = 1 + (2 || b in c) in {});
821+
// |~~~~~~~~~~~|
822+
// this parentheses is removed by unwrap_expr and added again later
777823
self.wrap(e);
778824
}
779825

0 commit comments

Comments
 (0)
Please sign in to comment.