babel-parser(ts): Raise recoverable error for abstract interface (#12771)

* Support parsing abstract interface

* Address review

Address reviews

Address reviews

* Fix types

* Add hasFollowingLineBreak
This commit is contained in:
Sosuke Suzuki 2021-02-09 17:56:18 +09:00 committed by GitHub
parent 4778e32570
commit d242ea04c8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 190 additions and 17 deletions

View File

@ -96,6 +96,12 @@ export default class UtilParser extends Tokenizer {
); );
} }
hasFollowingLineBreak(): boolean {
return lineBreak.test(
this.input.slice(this.state.end, this.nextTokenStart()),
);
}
// TODO // TODO
isLineTerminator(): boolean { isLineTerminator(): boolean {

View File

@ -89,6 +89,8 @@ const TSErrors = Object.freeze({
"Tuple members must all have names or all not have names.", "Tuple members must all have names or all not have names.",
NonAbstractClassHasAbstractMethod: NonAbstractClassHasAbstractMethod:
"Abstract methods can only appear within an abstract class.", "Abstract methods can only appear within an abstract class.",
NonClassMethodPropertyHasAbstractModifer:
"'abstract' modifier can only appear on a class, method, or property declaration.",
OptionalTypeBeforeRequired: OptionalTypeBeforeRequired:
"A required element cannot follow an optional element.", "A required element cannot follow an optional element.",
PatternIsOptional: PatternIsOptional:
@ -1585,20 +1587,13 @@ export default (superClass: Class<Parser>): Class<Parser> =>
): ?N.Declaration { ): ?N.Declaration {
switch (value) { switch (value) {
case "abstract": case "abstract":
if (this.tsCheckLineTerminatorAndMatch(tt._class, next)) { if (
const cls: N.ClassDeclaration = node; this.tsCheckLineTerminatorAndMatch(tt._class, next) ||
cls.abstract = true; // for interface
if (next) { this.tsCheckLineTerminatorAndMatch(tt.name, next)
this.next(); ) {
if (!this.match(tt._class)) { if (next) this.next();
this.unexpected(null, tt._class); return this.tsParseAbstractDeclaration(node);
}
}
return this.parseClass(
cls,
/* isStatement */ true,
/* optionalId */ false,
);
} }
break; break;
@ -2849,4 +2844,36 @@ export default (superClass: Class<Parser>): Class<Parser> =>
this.state.inAbstractClass = oldInAbstractClass; this.state.inAbstractClass = oldInAbstractClass;
} }
} }
tsParseAbstractDeclaration(
node: any,
): N.ClassDeclaration | N.TsInterfaceDeclaration | typeof undefined {
if (this.match(tt._class)) {
node.abstract = true;
return this.parseClass<N.ClassDeclaration>(
(node: N.ClassDeclaration),
/* isStatement */ true,
/* optionalId */ false,
);
} else if (this.isContextual("interface")) {
// for invalid abstract interface
// To avoid
// abstract interface
// Foo {}
if (!this.hasFollowingLineBreak()) {
node.abstract = true;
this.raise(
node.start,
TSErrors.NonClassMethodPropertyHasAbstractModifer,
);
this.next();
return this.tsParseInterfaceDeclaration(
(node: N.TsInterfaceDeclaration),
);
}
} else {
this.unexpected(null, tt._class);
}
}
}; };

View File

@ -0,0 +1,3 @@
abstract interface Foo {
foo: string;
}

View File

@ -0,0 +1,50 @@
{
"type": "File",
"start":0,"end":41,"loc":{"start":{"line":1,"column":0},"end":{"line":3,"column":1}},
"errors": [
"SyntaxError: 'abstract' modifier can only appear on a class, method, or property declaration. (1:0)"
],
"program": {
"type": "Program",
"start":0,"end":41,"loc":{"start":{"line":1,"column":0},"end":{"line":3,"column":1}},
"sourceType": "module",
"interpreter": null,
"body": [
{
"type": "TSInterfaceDeclaration",
"start":0,"end":41,"loc":{"start":{"line":1,"column":0},"end":{"line":3,"column":1}},
"abstract": true,
"id": {
"type": "Identifier",
"start":19,"end":22,"loc":{"start":{"line":1,"column":19},"end":{"line":1,"column":22},"identifierName":"Foo"},
"name": "Foo"
},
"body": {
"type": "TSInterfaceBody",
"start":23,"end":41,"loc":{"start":{"line":1,"column":23},"end":{"line":3,"column":1}},
"body": [
{
"type": "TSPropertySignature",
"start":27,"end":39,"loc":{"start":{"line":2,"column":2},"end":{"line":2,"column":14}},
"key": {
"type": "Identifier",
"start":27,"end":30,"loc":{"start":{"line":2,"column":2},"end":{"line":2,"column":5},"identifierName":"foo"},
"name": "foo"
},
"computed": false,
"typeAnnotation": {
"type": "TSTypeAnnotation",
"start":30,"end":38,"loc":{"start":{"line":2,"column":5},"end":{"line":2,"column":13}},
"typeAnnotation": {
"type": "TSStringKeyword",
"start":32,"end":38,"loc":{"start":{"line":2,"column":7},"end":{"line":2,"column":13}}
}
}
}
]
}
}
],
"directives": []
}
}

View File

@ -1,3 +0,0 @@
{
"throws": "Unexpected token, expected \"class\" (1:16)"
}

View File

@ -0,0 +1,38 @@
{
"type": "File",
"start":0,"end":32,"loc":{"start":{"line":1,"column":0},"end":{"line":3,"column":1}},
"errors": [
"SyntaxError: 'abstract' modifier can only appear on a class, method, or property declaration. (1:7)"
],
"program": {
"type": "Program",
"start":0,"end":32,"loc":{"start":{"line":1,"column":0},"end":{"line":3,"column":1}},
"sourceType": "module",
"interpreter": null,
"body": [
{
"type": "ExportNamedDeclaration",
"start":0,"end":32,"loc":{"start":{"line":1,"column":0},"end":{"line":3,"column":1}},
"exportKind": "type",
"specifiers": [],
"source": null,
"declaration": {
"type": "TSInterfaceDeclaration",
"start":7,"end":32,"loc":{"start":{"line":1,"column":7},"end":{"line":3,"column":1}},
"abstract": true,
"id": {
"type": "Identifier",
"start":26,"end":27,"loc":{"start":{"line":1,"column":26},"end":{"line":1,"column":27},"identifierName":"I"},
"name": "I"
},
"body": {
"type": "TSInterfaceBody",
"start":28,"end":32,"loc":{"start":{"line":1,"column":28},"end":{"line":3,"column":1}},
"body": []
}
}
}
],
"directives": []
}
}

View File

@ -0,0 +1,2 @@
abstract interface
Foo {}

View File

@ -0,0 +1,50 @@
{
"type": "File",
"start":0,"end":25,"loc":{"start":{"line":1,"column":0},"end":{"line":2,"column":6}},
"errors": [
"SyntaxError: Missing semicolon (1:8)",
"SyntaxError: Missing semicolon (2:3)"
],
"program": {
"type": "Program",
"start":0,"end":25,"loc":{"start":{"line":1,"column":0},"end":{"line":2,"column":6}},
"sourceType": "module",
"interpreter": null,
"body": [
{
"type": "ExpressionStatement",
"start":0,"end":8,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":8}},
"expression": {
"type": "Identifier",
"start":0,"end":8,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":8},"identifierName":"abstract"},
"name": "abstract"
}
},
{
"type": "ExpressionStatement",
"start":9,"end":18,"loc":{"start":{"line":1,"column":9},"end":{"line":1,"column":18}},
"expression": {
"type": "Identifier",
"start":9,"end":18,"loc":{"start":{"line":1,"column":9},"end":{"line":1,"column":18},"identifierName":"interface"},
"name": "interface"
}
},
{
"type": "ExpressionStatement",
"start":19,"end":22,"loc":{"start":{"line":2,"column":0},"end":{"line":2,"column":3}},
"expression": {
"type": "Identifier",
"start":19,"end":22,"loc":{"start":{"line":2,"column":0},"end":{"line":2,"column":3},"identifierName":"Foo"},
"name": "Foo"
}
},
{
"type": "BlockStatement",
"start":23,"end":25,"loc":{"start":{"line":2,"column":4},"end":{"line":2,"column":6}},
"body": [],
"directives": []
}
],
"directives": []
}
}