From a97c83354991892e44f6b3b3192d02813791ebb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hu=C3=A1ng=20J=C3=B9nli=C3=A0ng?= Date: Tue, 7 Apr 2020 21:05:59 -0400 Subject: [PATCH 1/2] fix: do not push new token context when function is following dot/questionDot --- .../babel-parser/src/parser/expression.js | 13 +- .../babel-parser/src/tokenizer/context.js | 5 +- .../jsx/regression/issue-11387/input.js | 1 + .../jsx/regression/issue-11387/options.json | 3 + .../jsx/regression/issue-11387/output.json | 115 ++++++++++++++++++ 5 files changed, 127 insertions(+), 10 deletions(-) create mode 100644 packages/babel-parser/test/fixtures/jsx/regression/issue-11387/input.js create mode 100644 packages/babel-parser/test/fixtures/jsx/regression/issue-11387/options.json create mode 100644 packages/babel-parser/test/fixtures/jsx/regression/issue-11387/output.json diff --git a/packages/babel-parser/src/parser/expression.js b/packages/babel-parser/src/parser/expression.js index d97a429ee0b6..46e90e825507 100644 --- a/packages/babel-parser/src/parser/expression.js +++ b/packages/babel-parser/src/parser/expression.js @@ -2125,17 +2125,12 @@ export default class ExpressionParser extends LValParser { } else if (this.state.type.keyword) { name = this.state.type.keyword; - // `class` and `function` keywords push new context into this.context. + // `class` and `function` keywords push function-type token context into this.context. // But there is no chance to pop the context if the keyword is consumed // as an identifier such as a property name. - // If the previous token is a dot, this does not apply because the - // context-managing code already ignored the keyword - if ( - (name === "class" || name === "function") && - (this.state.lastTokEnd !== this.state.lastTokStart + 1 || - this.input.charCodeAt(this.state.lastTokStart) !== charCodes.dot) - ) { - this.state.context.pop(); + const context = this.state.context; + if (context[context.length - 1].token === "function") { + context.pop(); } } else { throw this.unexpected(); diff --git a/packages/babel-parser/src/tokenizer/context.js b/packages/babel-parser/src/tokenizer/context.js index 3f3907872c06..83e21443a955 100644 --- a/packages/babel-parser/src/tokenizer/context.js +++ b/packages/babel-parser/src/tokenizer/context.js @@ -101,7 +101,10 @@ tt.incDec.updateContext = function() { }; tt._function.updateContext = tt._class.updateContext = function(prevType) { - if ( + if (prevType === tt.dot || prevType === tt.questionDot) { + // when function/class follows dot/questionDot, it is part of + // (optional)MemberExpression, then we don't need to push new token context + } else if ( prevType.beforeExpr && prevType !== tt.semi && prevType !== tt._else && diff --git a/packages/babel-parser/test/fixtures/jsx/regression/issue-11387/input.js b/packages/babel-parser/test/fixtures/jsx/regression/issue-11387/input.js new file mode 100644 index 000000000000..34425fe24a91 --- /dev/null +++ b/packages/babel-parser/test/fixtures/jsx/regression/issue-11387/input.js @@ -0,0 +1 @@ +
{(this?.class, this.class, this?.function, this.function)}
diff --git a/packages/babel-parser/test/fixtures/jsx/regression/issue-11387/options.json b/packages/babel-parser/test/fixtures/jsx/regression/issue-11387/options.json new file mode 100644 index 000000000000..698e7668500f --- /dev/null +++ b/packages/babel-parser/test/fixtures/jsx/regression/issue-11387/options.json @@ -0,0 +1,3 @@ +{ + "plugins": ["jsx", "flow"] +} diff --git a/packages/babel-parser/test/fixtures/jsx/regression/issue-11387/output.json b/packages/babel-parser/test/fixtures/jsx/regression/issue-11387/output.json new file mode 100644 index 000000000000..6627c48a90fa --- /dev/null +++ b/packages/babel-parser/test/fixtures/jsx/regression/issue-11387/output.json @@ -0,0 +1,115 @@ +{ + "type": "File", + "start":0,"end":69,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":69}}, + "program": { + "type": "Program", + "start":0,"end":69,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":69}}, + "sourceType": "script", + "interpreter": null, + "body": [ + { + "type": "ExpressionStatement", + "start":0,"end":69,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":69}}, + "expression": { + "type": "JSXElement", + "start":0,"end":69,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":69}}, + "openingElement": { + "type": "JSXOpeningElement", + "start":0,"end":5,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":5}}, + "name": { + "type": "JSXIdentifier", + "start":1,"end":4,"loc":{"start":{"line":1,"column":1},"end":{"line":1,"column":4}}, + "name": "div" + }, + "attributes": [], + "selfClosing": false + }, + "closingElement": { + "type": "JSXClosingElement", + "start":63,"end":69,"loc":{"start":{"line":1,"column":63},"end":{"line":1,"column":69}}, + "name": { + "type": "JSXIdentifier", + "start":65,"end":68,"loc":{"start":{"line":1,"column":65},"end":{"line":1,"column":68}}, + "name": "div" + } + }, + "children": [ + { + "type": "JSXExpressionContainer", + "start":5,"end":63,"loc":{"start":{"line":1,"column":5},"end":{"line":1,"column":63}}, + "expression": { + "type": "SequenceExpression", + "start":7,"end":61,"loc":{"start":{"line":1,"column":7},"end":{"line":1,"column":61}}, + "expressions": [ + { + "type": "OptionalMemberExpression", + "start":7,"end":18,"loc":{"start":{"line":1,"column":7},"end":{"line":1,"column":18}}, + "object": { + "type": "ThisExpression", + "start":7,"end":11,"loc":{"start":{"line":1,"column":7},"end":{"line":1,"column":11}} + }, + "property": { + "type": "Identifier", + "start":13,"end":18,"loc":{"start":{"line":1,"column":13},"end":{"line":1,"column":18},"identifierName":"class"}, + "name": "class" + }, + "computed": false, + "optional": true + }, + { + "type": "MemberExpression", + "start":20,"end":30,"loc":{"start":{"line":1,"column":20},"end":{"line":1,"column":30}}, + "object": { + "type": "ThisExpression", + "start":20,"end":24,"loc":{"start":{"line":1,"column":20},"end":{"line":1,"column":24}} + }, + "property": { + "type": "Identifier", + "start":25,"end":30,"loc":{"start":{"line":1,"column":25},"end":{"line":1,"column":30},"identifierName":"class"}, + "name": "class" + }, + "computed": false + }, + { + "type": "OptionalMemberExpression", + "start":32,"end":46,"loc":{"start":{"line":1,"column":32},"end":{"line":1,"column":46}}, + "object": { + "type": "ThisExpression", + "start":32,"end":36,"loc":{"start":{"line":1,"column":32},"end":{"line":1,"column":36}} + }, + "property": { + "type": "Identifier", + "start":38,"end":46,"loc":{"start":{"line":1,"column":38},"end":{"line":1,"column":46},"identifierName":"function"}, + "name": "function" + }, + "computed": false, + "optional": true + }, + { + "type": "MemberExpression", + "start":48,"end":61,"loc":{"start":{"line":1,"column":48},"end":{"line":1,"column":61}}, + "object": { + "type": "ThisExpression", + "start":48,"end":52,"loc":{"start":{"line":1,"column":48},"end":{"line":1,"column":52}} + }, + "property": { + "type": "Identifier", + "start":53,"end":61,"loc":{"start":{"line":1,"column":53},"end":{"line":1,"column":61},"identifierName":"function"}, + "name": "function" + }, + "computed": false + } + ], + "extra": { + "parenthesized": true, + "parenStart": 6 + } + } + } + ] + } + } + ], + "directives": [] + } +} \ No newline at end of file From fe4090b711a873710a67b68ec8491d1b383804c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hu=C3=A1ng=20J=C3=B9nli=C3=A0ng?= Date: Tue, 7 Apr 2020 21:17:18 -0400 Subject: [PATCH 2/2] more cautiously poping context --- packages/babel-parser/src/parser/expression.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/babel-parser/src/parser/expression.js b/packages/babel-parser/src/parser/expression.js index 46e90e825507..06999c46f24b 100644 --- a/packages/babel-parser/src/parser/expression.js +++ b/packages/babel-parser/src/parser/expression.js @@ -2129,7 +2129,10 @@ export default class ExpressionParser extends LValParser { // But there is no chance to pop the context if the keyword is consumed // as an identifier such as a property name. const context = this.state.context; - if (context[context.length - 1].token === "function") { + if ( + (name === "class" || name === "function") && + context[context.length - 1].token === "function" + ) { context.pop(); } } else {