From 35a7656badff4ce76f1408a9c1f2e855f6f03e67 Mon Sep 17 00:00:00 2001 From: sosukesuzuki Date: Sun, 24 Jan 2021 18:45:54 +0900 Subject: [PATCH 1/6] Raise error for abstract method in non abstract class --- .../src/plugins/typescript/index.js | 22 ++++++++++++++----- packages/babel-parser/src/tokenizer/state.js | 1 + 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/packages/babel-parser/src/plugins/typescript/index.js b/packages/babel-parser/src/plugins/typescript/index.js index 8603b975c69f..0637d20787e3 100644 --- a/packages/babel-parser/src/plugins/typescript/index.js +++ b/packages/babel-parser/src/plugins/typescript/index.js @@ -85,6 +85,8 @@ const TSErrors = Object.freeze({ "Tuple members must be labeled with a simple identifier.", MixedLabeledAndUnlabeledElements: "Tuple members must all have names or all not have names.", + NonAbstractClassHasAbstractMethod: + "Abstract methods can only appear within an abstract class.", OptionalTypeBeforeRequired: "A required element cannot follow an optional element.", PatternIsOptional: @@ -1593,11 +1595,17 @@ export default (superClass: Class): Class => this.unexpected(null, tt._class); } } - return this.parseClass( - cls, - /* isStatement */ true, - /* optionalId */ false, - ); + const oldInAbstractClass = this.state.inAbstractClass; + this.state.inAbstractClass = true; + try { + return this.parseClass( + cls, + /* isStatement */ true, + /* optionalId */ false, + ); + } finally { + this.state.inAbstractClass = oldInAbstractClass; + } } break; @@ -2171,6 +2179,10 @@ export default (superClass: Class): Class => return; } + if (!this.state.inAbstractClass && (member: any).abstract) { + this.raise(member.start, TSErrors.NonAbstractClassHasAbstractMethod); + } + /*:: invariant(member.type !== "TSIndexSignature") */ super.parseClassMemberWithIsStatic(classBody, member, state, isStatic); diff --git a/packages/babel-parser/src/tokenizer/state.js b/packages/babel-parser/src/tokenizer/state.js index ef61fde4eaa8..8a4ca1aede31 100644 --- a/packages/babel-parser/src/tokenizer/state.js +++ b/packages/babel-parser/src/tokenizer/state.js @@ -65,6 +65,7 @@ export default class State { hasFlowComment: boolean = false; isIterator: boolean = false; isDeclareContext: boolean = false; + inAbstractClass: boolean = false; // For the smartPipelines plugin: topicContext: TopicContextState = { From 1f66e2413f1ca88b7395811c8508fcdaf13780c7 Mon Sep 17 00:00:00 2001 From: sosukesuzuki Date: Sun, 24 Jan 2021 18:53:34 +0900 Subject: [PATCH 2/6] Add and update tests --- .../input.ts | 3 ++ .../output.json | 49 +++++++++++++++++++ .../output.json | 3 ++ 3 files changed, 55 insertions(+) create mode 100644 packages/babel-parser/test/fixtures/typescript/class/abstract-method-in-non-abstract-class/input.ts create mode 100644 packages/babel-parser/test/fixtures/typescript/class/abstract-method-in-non-abstract-class/output.json diff --git a/packages/babel-parser/test/fixtures/typescript/class/abstract-method-in-non-abstract-class/input.ts b/packages/babel-parser/test/fixtures/typescript/class/abstract-method-in-non-abstract-class/input.ts new file mode 100644 index 000000000000..4dd125872b08 --- /dev/null +++ b/packages/babel-parser/test/fixtures/typescript/class/abstract-method-in-non-abstract-class/input.ts @@ -0,0 +1,3 @@ +class Foo { + abstract method(); +} diff --git a/packages/babel-parser/test/fixtures/typescript/class/abstract-method-in-non-abstract-class/output.json b/packages/babel-parser/test/fixtures/typescript/class/abstract-method-in-non-abstract-class/output.json new file mode 100644 index 000000000000..b1f22a4e62f1 --- /dev/null +++ b/packages/babel-parser/test/fixtures/typescript/class/abstract-method-in-non-abstract-class/output.json @@ -0,0 +1,49 @@ +{ + "type": "File", + "start":0,"end":34,"loc":{"start":{"line":1,"column":0},"end":{"line":3,"column":1}}, + "errors": [ + "SyntaxError: Abstract methods can only appear within an abstract class. (2:2)" + ], + "program": { + "type": "Program", + "start":0,"end":34,"loc":{"start":{"line":1,"column":0},"end":{"line":3,"column":1}}, + "sourceType": "module", + "interpreter": null, + "body": [ + { + "type": "ClassDeclaration", + "start":0,"end":34,"loc":{"start":{"line":1,"column":0},"end":{"line":3,"column":1}}, + "id": { + "type": "Identifier", + "start":6,"end":9,"loc":{"start":{"line":1,"column":6},"end":{"line":1,"column":9},"identifierName":"Foo"}, + "name": "Foo" + }, + "superClass": null, + "body": { + "type": "ClassBody", + "start":10,"end":34,"loc":{"start":{"line":1,"column":10},"end":{"line":3,"column":1}}, + "body": [ + { + "type": "TSDeclareMethod", + "start":14,"end":32,"loc":{"start":{"line":2,"column":2},"end":{"line":2,"column":20}}, + "abstract": true, + "static": false, + "key": { + "type": "Identifier", + "start":23,"end":29,"loc":{"start":{"line":2,"column":11},"end":{"line":2,"column":17},"identifierName":"method"}, + "name": "method" + }, + "computed": false, + "kind": "method", + "id": null, + "generator": false, + "async": false, + "params": [] + } + ] + } + } + ], + "directives": [] + } +} \ No newline at end of file diff --git a/packages/babel-parser/test/fixtures/typescript/class/generator-method-with-modifiers/output.json b/packages/babel-parser/test/fixtures/typescript/class/generator-method-with-modifiers/output.json index a02324333114..3fa9f58447d7 100644 --- a/packages/babel-parser/test/fixtures/typescript/class/generator-method-with-modifiers/output.json +++ b/packages/babel-parser/test/fixtures/typescript/class/generator-method-with-modifiers/output.json @@ -1,6 +1,9 @@ { "type": "File", "start":0,"end":139,"loc":{"start":{"line":1,"column":0},"end":{"line":9,"column":1}}, + "errors": [ + "SyntaxError: Abstract methods can only appear within an abstract class. (5:2)" + ], "program": { "type": "Program", "start":0,"end":139,"loc":{"start":{"line":1,"column":0},"end":{"line":9,"column":1}}, From c5c4d52e9810701c9e704aca2790019453d2158b Mon Sep 17 00:00:00 2001 From: sosukesuzuki Date: Sun, 24 Jan 2021 19:32:25 +0900 Subject: [PATCH 3/6] Update TS allowlist --- scripts/parser-tests/typescript/allowlist.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/parser-tests/typescript/allowlist.txt b/scripts/parser-tests/typescript/allowlist.txt index fd1355febf0d..82a74f141711 100644 --- a/scripts/parser-tests/typescript/allowlist.txt +++ b/scripts/parser-tests/typescript/allowlist.txt @@ -4,6 +4,7 @@ ParameterList13.ts ParameterList4.ts ParameterList5.ts ParameterList6.ts +abstractPropertyNegative.ts accessorParameterAccessibilityModifier.ts accessorWithoutBody1.ts accessorWithoutBody2.ts @@ -26,6 +27,7 @@ anonClassDeclarationEmitIsAnon.ts anyDeclare.ts argumentsBindsToFunctionScopeArgumentList.ts arrayOfExportedClass.ts +asiAbstract.ts asyncFunctionsAcrossFiles.ts augmentExportEquals1.ts augmentExportEquals1_1.ts From 1cdd3a54861726357065abb7ae206a5b55029087 Mon Sep 17 00:00:00 2001 From: sosukesuzuki Date: Tue, 26 Jan 2021 16:35:36 +0900 Subject: [PATCH 4/6] Address review --- packages/babel-parser/src/parser/statement.js | 2 + .../src/plugins/typescript/index.js | 32 ++++--- .../input.ts | 0 .../output.json | 0 .../input.ts | 7 ++ .../output.json | 89 +++++++++++++++++++ .../input.ts | 1 + .../output.json | 73 +++++++++++++++ 8 files changed, 193 insertions(+), 11 deletions(-) rename packages/babel-parser/test/fixtures/typescript/class/{abstract-method-in-non-abstract-class => abstract-method-in-non-abstract-class-1}/input.ts (100%) rename packages/babel-parser/test/fixtures/typescript/class/{abstract-method-in-non-abstract-class => abstract-method-in-non-abstract-class-1}/output.json (100%) create mode 100644 packages/babel-parser/test/fixtures/typescript/class/abstract-method-in-non-abstract-class-2/input.ts create mode 100644 packages/babel-parser/test/fixtures/typescript/class/abstract-method-in-non-abstract-class-2/output.json create mode 100644 packages/babel-parser/test/fixtures/typescript/class/abstract-method-in-non-abstract-class-3/input.ts create mode 100644 packages/babel-parser/test/fixtures/typescript/class/abstract-method-in-non-abstract-class-3/output.json diff --git a/packages/babel-parser/src/parser/statement.js b/packages/babel-parser/src/parser/statement.js index 5827e5154aa8..17912c1668fe 100644 --- a/packages/babel-parser/src/parser/statement.js +++ b/packages/babel-parser/src/parser/statement.js @@ -1152,6 +1152,8 @@ export default class StatementParser extends ExpressionParser { node: T, isStatement: /* T === ClassDeclaration */ boolean, optionalId?: boolean, + // eslint-disable-next-line no-unused-vars -- used in TypeScript plugin + abstract?: boolean, ): T { this.next(); this.takeDecorators(node); diff --git a/packages/babel-parser/src/plugins/typescript/index.js b/packages/babel-parser/src/plugins/typescript/index.js index 0637d20787e3..b672570c147f 100644 --- a/packages/babel-parser/src/plugins/typescript/index.js +++ b/packages/babel-parser/src/plugins/typescript/index.js @@ -1595,17 +1595,12 @@ export default (superClass: Class): Class => this.unexpected(null, tt._class); } } - const oldInAbstractClass = this.state.inAbstractClass; - this.state.inAbstractClass = true; - try { - return this.parseClass( - cls, - /* isStatement */ true, - /* optionalId */ false, - ); - } finally { - this.state.inAbstractClass = oldInAbstractClass; - } + return this.parseClass( + cls, + /* isStatement */ true, + /* optionalId */ false, + /* abstract */ true, + ); } break; @@ -2836,4 +2831,19 @@ export default (superClass: Class): Class => this.state.isDeclareContext = oldIsDeclareContext; } } + + parseClass( + node: T, + isStatement: /* T === ClassDeclaration */ boolean, + optionalId?: boolean, + abstract?: boolean, + ): T { + const oldInAbstractClass = this.state.inAbstractClass; + this.state.inAbstractClass = !!abstract; + try { + return super.parseClass(node, isStatement, optionalId, abstract); + } finally { + this.state.inAbstractClass = oldInAbstractClass; + } + } }; diff --git a/packages/babel-parser/test/fixtures/typescript/class/abstract-method-in-non-abstract-class/input.ts b/packages/babel-parser/test/fixtures/typescript/class/abstract-method-in-non-abstract-class-1/input.ts similarity index 100% rename from packages/babel-parser/test/fixtures/typescript/class/abstract-method-in-non-abstract-class/input.ts rename to packages/babel-parser/test/fixtures/typescript/class/abstract-method-in-non-abstract-class-1/input.ts diff --git a/packages/babel-parser/test/fixtures/typescript/class/abstract-method-in-non-abstract-class/output.json b/packages/babel-parser/test/fixtures/typescript/class/abstract-method-in-non-abstract-class-1/output.json similarity index 100% rename from packages/babel-parser/test/fixtures/typescript/class/abstract-method-in-non-abstract-class/output.json rename to packages/babel-parser/test/fixtures/typescript/class/abstract-method-in-non-abstract-class-1/output.json diff --git a/packages/babel-parser/test/fixtures/typescript/class/abstract-method-in-non-abstract-class-2/input.ts b/packages/babel-parser/test/fixtures/typescript/class/abstract-method-in-non-abstract-class-2/input.ts new file mode 100644 index 000000000000..c340456f44d0 --- /dev/null +++ b/packages/babel-parser/test/fixtures/typescript/class/abstract-method-in-non-abstract-class-2/input.ts @@ -0,0 +1,7 @@ +abstract class Foo { + method() { + return class { + abstract m(); + } + } +} diff --git a/packages/babel-parser/test/fixtures/typescript/class/abstract-method-in-non-abstract-class-2/output.json b/packages/babel-parser/test/fixtures/typescript/class/abstract-method-in-non-abstract-class-2/output.json new file mode 100644 index 000000000000..e304b1af3eef --- /dev/null +++ b/packages/babel-parser/test/fixtures/typescript/class/abstract-method-in-non-abstract-class-2/output.json @@ -0,0 +1,89 @@ +{ + "type": "File", + "start":0,"end":84,"loc":{"start":{"line":1,"column":0},"end":{"line":7,"column":1}}, + "errors": [ + "SyntaxError: Abstract methods can only appear within an abstract class. (4:6)" + ], + "program": { + "type": "Program", + "start":0,"end":84,"loc":{"start":{"line":1,"column":0},"end":{"line":7,"column":1}}, + "sourceType": "module", + "interpreter": null, + "body": [ + { + "type": "ClassDeclaration", + "start":0,"end":84,"loc":{"start":{"line":1,"column":0},"end":{"line":7,"column":1}}, + "abstract": true, + "id": { + "type": "Identifier", + "start":15,"end":18,"loc":{"start":{"line":1,"column":15},"end":{"line":1,"column":18},"identifierName":"Foo"}, + "name": "Foo" + }, + "superClass": null, + "body": { + "type": "ClassBody", + "start":19,"end":84,"loc":{"start":{"line":1,"column":19},"end":{"line":7,"column":1}}, + "body": [ + { + "type": "ClassMethod", + "start":23,"end":82,"loc":{"start":{"line":2,"column":2},"end":{"line":6,"column":3}}, + "static": false, + "key": { + "type": "Identifier", + "start":23,"end":29,"loc":{"start":{"line":2,"column":2},"end":{"line":2,"column":8},"identifierName":"method"}, + "name": "method" + }, + "computed": false, + "kind": "method", + "id": null, + "generator": false, + "async": false, + "params": [], + "body": { + "type": "BlockStatement", + "start":32,"end":82,"loc":{"start":{"line":2,"column":11},"end":{"line":6,"column":3}}, + "body": [ + { + "type": "ReturnStatement", + "start":38,"end":78,"loc":{"start":{"line":3,"column":4},"end":{"line":5,"column":5}}, + "argument": { + "type": "ClassExpression", + "start":45,"end":78,"loc":{"start":{"line":3,"column":11},"end":{"line":5,"column":5}}, + "id": null, + "superClass": null, + "body": { + "type": "ClassBody", + "start":51,"end":78,"loc":{"start":{"line":3,"column":17},"end":{"line":5,"column":5}}, + "body": [ + { + "type": "TSDeclareMethod", + "start":59,"end":72,"loc":{"start":{"line":4,"column":6},"end":{"line":4,"column":19}}, + "abstract": true, + "static": false, + "key": { + "type": "Identifier", + "start":68,"end":69,"loc":{"start":{"line":4,"column":15},"end":{"line":4,"column":16},"identifierName":"m"}, + "name": "m" + }, + "computed": false, + "kind": "method", + "id": null, + "generator": false, + "async": false, + "params": [] + } + ] + } + } + } + ], + "directives": [] + } + } + ] + } + } + ], + "directives": [] + } +} \ No newline at end of file diff --git a/packages/babel-parser/test/fixtures/typescript/class/abstract-method-in-non-abstract-class-3/input.ts b/packages/babel-parser/test/fixtures/typescript/class/abstract-method-in-non-abstract-class-3/input.ts new file mode 100644 index 000000000000..46339034a395 --- /dev/null +++ b/packages/babel-parser/test/fixtures/typescript/class/abstract-method-in-non-abstract-class-3/input.ts @@ -0,0 +1 @@ +abstract class C { p = class { abstract method() } } diff --git a/packages/babel-parser/test/fixtures/typescript/class/abstract-method-in-non-abstract-class-3/output.json b/packages/babel-parser/test/fixtures/typescript/class/abstract-method-in-non-abstract-class-3/output.json new file mode 100644 index 000000000000..ff36ce77b1c7 --- /dev/null +++ b/packages/babel-parser/test/fixtures/typescript/class/abstract-method-in-non-abstract-class-3/output.json @@ -0,0 +1,73 @@ +{ + "type": "File", + "start":0,"end":52,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":52}}, + "errors": [ + "SyntaxError: Abstract methods can only appear within an abstract class. (1:31)" + ], + "program": { + "type": "Program", + "start":0,"end":52,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":52}}, + "sourceType": "module", + "interpreter": null, + "body": [ + { + "type": "ClassDeclaration", + "start":0,"end":52,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":52}}, + "abstract": true, + "id": { + "type": "Identifier", + "start":15,"end":16,"loc":{"start":{"line":1,"column":15},"end":{"line":1,"column":16},"identifierName":"C"}, + "name": "C" + }, + "superClass": null, + "body": { + "type": "ClassBody", + "start":17,"end":52,"loc":{"start":{"line":1,"column":17},"end":{"line":1,"column":52}}, + "body": [ + { + "type": "ClassProperty", + "start":19,"end":50,"loc":{"start":{"line":1,"column":19},"end":{"line":1,"column":50}}, + "static": false, + "key": { + "type": "Identifier", + "start":19,"end":20,"loc":{"start":{"line":1,"column":19},"end":{"line":1,"column":20},"identifierName":"p"}, + "name": "p" + }, + "computed": false, + "value": { + "type": "ClassExpression", + "start":23,"end":50,"loc":{"start":{"line":1,"column":23},"end":{"line":1,"column":50}}, + "id": null, + "superClass": null, + "body": { + "type": "ClassBody", + "start":29,"end":50,"loc":{"start":{"line":1,"column":29},"end":{"line":1,"column":50}}, + "body": [ + { + "type": "TSDeclareMethod", + "start":31,"end":48,"loc":{"start":{"line":1,"column":31},"end":{"line":1,"column":48}}, + "abstract": true, + "static": false, + "key": { + "type": "Identifier", + "start":40,"end":46,"loc":{"start":{"line":1,"column":40},"end":{"line":1,"column":46},"identifierName":"method"}, + "name": "method" + }, + "computed": false, + "kind": "method", + "id": null, + "generator": false, + "async": false, + "params": [] + } + ] + } + } + } + ] + } + } + ], + "directives": [] + } +} \ No newline at end of file From d292e22b4b5159e01a5ed94456502510d6623c56 Mon Sep 17 00:00:00 2001 From: sosukesuzuki Date: Tue, 26 Jan 2021 16:40:56 +0900 Subject: [PATCH 5/6] Refactor --- packages/babel-parser/src/parser/statement.js | 2 -- packages/babel-parser/src/plugins/typescript/index.js | 11 +++-------- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/packages/babel-parser/src/parser/statement.js b/packages/babel-parser/src/parser/statement.js index 17912c1668fe..5827e5154aa8 100644 --- a/packages/babel-parser/src/parser/statement.js +++ b/packages/babel-parser/src/parser/statement.js @@ -1152,8 +1152,6 @@ export default class StatementParser extends ExpressionParser { node: T, isStatement: /* T === ClassDeclaration */ boolean, optionalId?: boolean, - // eslint-disable-next-line no-unused-vars -- used in TypeScript plugin - abstract?: boolean, ): T { this.next(); this.takeDecorators(node); diff --git a/packages/babel-parser/src/plugins/typescript/index.js b/packages/babel-parser/src/plugins/typescript/index.js index b672570c147f..f24e26782757 100644 --- a/packages/babel-parser/src/plugins/typescript/index.js +++ b/packages/babel-parser/src/plugins/typescript/index.js @@ -2832,16 +2832,11 @@ export default (superClass: Class): Class => } } - parseClass( - node: T, - isStatement: /* T === ClassDeclaration */ boolean, - optionalId?: boolean, - abstract?: boolean, - ): T { + parseClass(node: T, ...args: any[]): T { const oldInAbstractClass = this.state.inAbstractClass; - this.state.inAbstractClass = !!abstract; + this.state.inAbstractClass = !!(node: any).abstract; try { - return super.parseClass(node, isStatement, optionalId, abstract); + return super.parseClass(node, ...args); } finally { this.state.inAbstractClass = oldInAbstractClass; } From 7e0ce31f326071d39cff5eba1f62a758d68ab091 Mon Sep 17 00:00:00 2001 From: sosukesuzuki Date: Wed, 27 Jan 2021 16:42:01 +0900 Subject: [PATCH 6/6] Remove unnnecesary param --- packages/babel-parser/src/plugins/typescript/index.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/babel-parser/src/plugins/typescript/index.js b/packages/babel-parser/src/plugins/typescript/index.js index f24e26782757..c5ea23e9c7d7 100644 --- a/packages/babel-parser/src/plugins/typescript/index.js +++ b/packages/babel-parser/src/plugins/typescript/index.js @@ -1599,7 +1599,6 @@ export default (superClass: Class): Class => cls, /* isStatement */ true, /* optionalId */ false, - /* abstract */ true, ); } break;