Skip to content

Commit

Permalink
Support TS 4.3 get/set type members (#13089)
Browse files Browse the repository at this point in the history
Co-authored-by: Nicol貌 Ribaudo <nicolo.ribaudo@gmail.com>
  • Loading branch information
2 people authored and JLHwung committed Apr 19, 2021
1 parent 159793a commit ca0f0f9
Show file tree
Hide file tree
Showing 41 changed files with 989 additions and 40 deletions.
5 changes: 5 additions & 0 deletions packages/babel-generator/src/generators/typescript.ts
Expand Up @@ -127,6 +127,11 @@ export function tsPrintPropertyOrMethodName(this: Printer, node) {
}

export function TSMethodSignature(this: Printer, node: t.TSMethodSignature) {
const { kind } = node;
if (kind === "set" || kind === "get") {
this.word(kind);
this.space();
}
this.tsPrintPropertyOrMethodName(node);
this.tsPrintSignatureDeclarationBase(node);
this.token(";");
Expand Down
@@ -0,0 +1,4 @@
interface Foo {
get foo();
set bar(v);
}
@@ -0,0 +1,4 @@
interface Foo {
get foo();
set bar(v);
}
8 changes: 8 additions & 0 deletions packages/babel-parser/src/parser/statement.js
Expand Up @@ -2384,4 +2384,12 @@ export default class StatementParser extends ExpressionParser {
this.checkLVal(specifier.local, "import specifier", BIND_LEXICAL);
node.specifiers.push(this.finishNode(specifier, "ImportSpecifier"));
}

// This is used in flow and typescript plugin
// Determine whether a parameter is a this param
isThisParam(
param: N.Pattern | N.Identifier | N.TSParameterProperty,
): boolean {
return param.type === "Identifier" && param.name === "this";
}
}
5 changes: 0 additions & 5 deletions packages/babel-parser/src/plugins/flow/index.js
Expand Up @@ -2392,11 +2392,6 @@ export default (superClass: Class<Parser>): Class<Parser> =>
return !this.match(tt.colon) && super.isNonstaticConstructor(method);
}

// determine whether a parameter is a this param
isThisParam(param) {
return param.type === "Identifier" && param.name === "this";
}

// parse type parameters for class methods
pushClassMethod(
classBody: N.ClassBody,
Expand Down
87 changes: 79 additions & 8 deletions packages/babel-parser/src/plugins/typescript/index.js
Expand Up @@ -70,6 +70,9 @@ const TSErrors = makeErrorTemplates(
{
AbstractMethodHasImplementation:
"Method '%0' cannot have an implementation because it is marked abstract.",
AccesorCannotDeclareThisParameter:
"'get' and 'set' accessors cannot declare 'this' parameters.",
AccesorCannotHaveTypeParameters: "An accessor cannot have type parameters.",
ClassMethodHasDeclare: "Class methods cannot have the 'declare' modifier.",
ClassMethodHasReadonly:
"Class methods cannot have the 'readonly' modifier.",
Expand Down Expand Up @@ -122,6 +125,12 @@ const TSErrors = makeErrorTemplates(
"Private elements cannot have an accessibility modifier ('%0').",
ReadonlyForMethodSignature:
"'readonly' modifier can only appear on a property declaration or index signature.",
SetAccesorCannotHaveOptionalParameter:
"A 'set' accessor cannot have an optional parameter.",
SetAccesorCannotHaveRestParameter:
"A 'set' accessor cannot have rest parameter.",
SetAccesorCannotHaveReturnType:
"A 'set' accessor cannot have a return type annotation.",
TypeAnnotationAfterAssign:
"Type annotations must come before default assignments, e.g. instead of `age = 25: number` use `age: number = 25`.",
TypeImportCannotSpecifyDefaultAndNamed:
Expand Down Expand Up @@ -193,12 +202,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
return this.match(tt.name);
}

tsNextTokenCanFollowModifier() {
// Note: TypeScript's implementation is much more complicated because
// more things are considered modifiers there.
// This implementation only handles modifiers not handled by @babel/parser itself. And "static".
// TODO: Would be nice to avoid lookahead. Want a hasLineBreakUpNext() method...
this.next();
tsTokenCanFollowModifier() {
return (
(this.match(tt.bracketL) ||
this.match(tt.braceL) ||
Expand All @@ -210,6 +214,15 @@ export default (superClass: Class<Parser>): Class<Parser> =>
);
}

tsNextTokenCanFollowModifier() {
// Note: TypeScript's implementation is much more complicated because
// more things are considered modifiers there.
// This implementation only handles modifiers not handled by @babel/parser itself. And "static".
// TODO: Would be nice to avoid lookahead. Want a hasLineBreakUpNext() method...
this.next();
return this.tsTokenCanFollowModifier();
}

/** Parses a modifier matching one the given modifier names. */
tsParseModifier<T: TsModifier>(allowedModifiers: T[]): ?T {
if (!this.match(tt.name)) {
Expand Down Expand Up @@ -547,8 +560,8 @@ export default (superClass: Class<Parser>): Class<Parser> =>
}

tsParseTypeMemberSemicolon(): void {
if (!this.eat(tt.comma)) {
this.semicolon();
if (!this.eat(tt.comma) && !this.isLineTerminator()) {
this.expect(tt.semi);
}
}

Expand Down Expand Up @@ -602,8 +615,57 @@ export default (superClass: Class<Parser>): Class<Parser> =>
this.raise(node.start, TSErrors.ReadonlyForMethodSignature);
}
const method: N.TsMethodSignature = nodeAny;
if (method.kind && this.isRelational("<")) {
this.raise(this.state.pos, TSErrors.AccesorCannotHaveTypeParameters);
}
this.tsFillSignature(tt.colon, method);
this.tsParseTypeMemberSemicolon();
if (method.kind === "get") {
if (method.parameters.length > 0) {
this.raise(this.state.pos, Errors.BadGetterArity);
if (this.isThisParam(method.parameters[0])) {
this.raise(
this.state.pos,
TSErrors.AccesorCannotDeclareThisParameter,
);
}
}
} else if (method.kind === "set") {
if (method.parameters.length !== 1) {
this.raise(this.state.pos, Errors.BadSetterArity);
} else {
const firstParameter = method.parameters[0];
if (this.isThisParam(firstParameter)) {
this.raise(
this.state.pos,
TSErrors.AccesorCannotDeclareThisParameter,
);
}
if (
firstParameter.type === "Identifier" &&
firstParameter.optional
) {
this.raise(
this.state.pos,
TSErrors.SetAccesorCannotHaveOptionalParameter,
);
}
if (firstParameter.type === "RestElement") {
this.raise(
this.state.pos,
TSErrors.SetAccesorCannotHaveRestParameter,
);
}
}
if (method.typeAnnotation) {
this.raise(
method.typeAnnotation.start,
TSErrors.SetAccesorCannotHaveReturnType,
);
}
} else {
method.kind = "method";
}
return this.finishNode(method, "TSMethodSignature");
} else {
const property: N.TsPropertySignature = nodeAny;
Expand Down Expand Up @@ -657,6 +719,15 @@ export default (superClass: Class<Parser>): Class<Parser> =>
}

this.parsePropertyName(node, /* isPrivateNameAllowed */ false);
if (
!node.computed &&
node.key.type === "Identifier" &&
(node.key.name === "get" || node.key.name === "set") &&
this.tsTokenCanFollowModifier()
) {
node.kind = node.key.name;
this.parsePropertyName(node, /* isPrivateNameAllowed */ false);
}
return this.tsParsePropertyOrMethodSignature(node, !!node.readonly);
}

Expand Down
1 change: 1 addition & 0 deletions packages/babel-parser/src/types.js
Expand Up @@ -1208,6 +1208,7 @@ export type TsPropertySignature = TsNamedTypeElementBase & {
export type TsMethodSignature = TsSignatureDeclarationBase &
TsNamedTypeElementBase & {
type: "TSMethodSignature",
kind: "method" | "get" | "set",
};

// *Not* a ClassMemberBase: Can't have accessibility, can't be abstract, can't be optional.
Expand Down
Expand Up @@ -43,7 +43,8 @@
"type": "TSVoidKeyword",
"start":34,"end":38,"loc":{"start":{"line":2,"column":12},"end":{"line":2,"column":16}}
}
}
},
"kind": "method"
},
{
"type": "TSMethodSignature",
Expand Down Expand Up @@ -74,7 +75,8 @@
"type": "TSAnyKeyword",
"start":56,"end":59,"loc":{"start":{"line":3,"column":16},"end":{"line":3,"column":19}}
}
}
},
"kind": "method"
},
{
"type": "TSMethodSignature",
Expand Down Expand Up @@ -126,7 +128,8 @@
"type": "TSVoidKeyword",
"start":95,"end":99,"loc":{"start":{"line":4,"column":34},"end":{"line":4,"column":38}}
}
}
},
"kind": "method"
}
]
},
Expand Down
@@ -0,0 +1,6 @@
interface Foo {
get
foo(): string;
set
bar(v);
}
@@ -0,0 +1,86 @@
{
"type": "File",
"start":0,"end":56,"loc":{"start":{"line":1,"column":0},"end":{"line":6,"column":1}},
"program": {
"type": "Program",
"start":0,"end":56,"loc":{"start":{"line":1,"column":0},"end":{"line":6,"column":1}},
"sourceType": "module",
"interpreter": null,
"body": [
{
"type": "TSInterfaceDeclaration",
"start":0,"end":56,"loc":{"start":{"line":1,"column":0},"end":{"line":6,"column":1}},
"id": {
"type": "Identifier",
"start":10,"end":13,"loc":{"start":{"line":1,"column":10},"end":{"line":1,"column":13},"identifierName":"Foo"},
"name": "Foo"
},
"body": {
"type": "TSInterfaceBody",
"start":14,"end":56,"loc":{"start":{"line":1,"column":14},"end":{"line":6,"column":1}},
"body": [
{
"type": "TSPropertySignature",
"start":18,"end":21,"loc":{"start":{"line":2,"column":2},"end":{"line":2,"column":5}},
"key": {
"type": "Identifier",
"start":18,"end":21,"loc":{"start":{"line":2,"column":2},"end":{"line":2,"column":5},"identifierName":"get"},
"name": "get"
},
"computed": false
},
{
"type": "TSMethodSignature",
"start":24,"end":38,"loc":{"start":{"line":3,"column":2},"end":{"line":3,"column":16}},
"key": {
"type": "Identifier",
"start":24,"end":27,"loc":{"start":{"line":3,"column":2},"end":{"line":3,"column":5},"identifierName":"foo"},
"name": "foo"
},
"computed": false,
"parameters": [],
"typeAnnotation": {
"type": "TSTypeAnnotation",
"start":29,"end":37,"loc":{"start":{"line":3,"column":7},"end":{"line":3,"column":15}},
"typeAnnotation": {
"type": "TSStringKeyword",
"start":31,"end":37,"loc":{"start":{"line":3,"column":9},"end":{"line":3,"column":15}}
}
},
"kind": "method"
},
{
"type": "TSPropertySignature",
"start":41,"end":44,"loc":{"start":{"line":4,"column":2},"end":{"line":4,"column":5}},
"key": {
"type": "Identifier",
"start":41,"end":44,"loc":{"start":{"line":4,"column":2},"end":{"line":4,"column":5},"identifierName":"set"},
"name": "set"
},
"computed": false
},
{
"type": "TSMethodSignature",
"start":47,"end":54,"loc":{"start":{"line":5,"column":2},"end":{"line":5,"column":9}},
"key": {
"type": "Identifier",
"start":47,"end":50,"loc":{"start":{"line":5,"column":2},"end":{"line":5,"column":5},"identifierName":"bar"},
"name": "bar"
},
"computed": false,
"parameters": [
{
"type": "Identifier",
"start":51,"end":52,"loc":{"start":{"line":5,"column":6},"end":{"line":5,"column":7},"identifierName":"v"},
"name": "v"
}
],
"kind": "method"
}
]
}
}
],
"directives": []
}
}
@@ -0,0 +1,3 @@
interface Foo {
set bar(foo?: string);
}
@@ -0,0 +1,58 @@
{
"type": "File",
"start":0,"end":42,"loc":{"start":{"line":1,"column":0},"end":{"line":3,"column":1}},
"errors": [
"SyntaxError: A 'set' accessor cannot have an optional parameter. (3:1)"
],
"program": {
"type": "Program",
"start":0,"end":42,"loc":{"start":{"line":1,"column":0},"end":{"line":3,"column":1}},
"sourceType": "module",
"interpreter": null,
"body": [
{
"type": "TSInterfaceDeclaration",
"start":0,"end":42,"loc":{"start":{"line":1,"column":0},"end":{"line":3,"column":1}},
"id": {
"type": "Identifier",
"start":10,"end":13,"loc":{"start":{"line":1,"column":10},"end":{"line":1,"column":13},"identifierName":"Foo"},
"name": "Foo"
},
"body": {
"type": "TSInterfaceBody",
"start":14,"end":42,"loc":{"start":{"line":1,"column":14},"end":{"line":3,"column":1}},
"body": [
{
"type": "TSMethodSignature",
"start":18,"end":40,"loc":{"start":{"line":2,"column":2},"end":{"line":2,"column":24}},
"key": {
"type": "Identifier",
"start":22,"end":25,"loc":{"start":{"line":2,"column":6},"end":{"line":2,"column":9},"identifierName":"bar"},
"name": "bar"
},
"computed": false,
"kind": "set",
"parameters": [
{
"type": "Identifier",
"start":26,"end":38,"loc":{"start":{"line":2,"column":10},"end":{"line":2,"column":22},"identifierName":"foo"},
"name": "foo",
"optional": true,
"typeAnnotation": {
"type": "TSTypeAnnotation",
"start":30,"end":38,"loc":{"start":{"line":2,"column":14},"end":{"line":2,"column":22}},
"typeAnnotation": {
"type": "TSStringKeyword",
"start":32,"end":38,"loc":{"start":{"line":2,"column":16},"end":{"line":2,"column":22}}
}
}
}
]
}
]
}
}
],
"directives": []
}
}
@@ -0,0 +1,4 @@
interface Foo {
get foo(param): string;
set foo();
}

0 comments on commit ca0f0f9

Please sign in to comment.