diff --git a/packages/babel-parser/src/plugins/typescript/index.js b/packages/babel-parser/src/plugins/typescript/index.js index a890a7136ee8..3b4aea59c087 100644 --- a/packages/babel-parser/src/plugins/typescript/index.js +++ b/packages/babel-parser/src/plugins/typescript/index.js @@ -87,6 +87,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: @@ -2172,6 +2174,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); @@ -2835,4 +2841,14 @@ export default (superClass: Class): Class => this.state.isDeclareContext = oldIsDeclareContext; } } + + parseClass(node: T, ...args: any[]): T { + const oldInAbstractClass = this.state.inAbstractClass; + this.state.inAbstractClass = !!(node: any).abstract; + try { + return super.parseClass(node, ...args); + } finally { + this.state.inAbstractClass = oldInAbstractClass; + } + } }; diff --git a/packages/babel-parser/src/tokenizer/state.js b/packages/babel-parser/src/tokenizer/state.js index 4f42b15faed0..e10cbd5295c6 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 = { diff --git a/packages/babel-parser/test/fixtures/typescript/class/abstract-method-in-non-abstract-class-1/input.ts b/packages/babel-parser/test/fixtures/typescript/class/abstract-method-in-non-abstract-class-1/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-1/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-1/output.json b/packages/babel-parser/test/fixtures/typescript/class/abstract-method-in-non-abstract-class-1/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-1/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/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 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}}, 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