Skip to content

Commit

Permalink
[parser] Add support for TS declare modifier on fields (#10484)
Browse files Browse the repository at this point in the history
* [parser] Add support for TS declare modifier on fields

* Use Object.create(null)

* Comment
  • Loading branch information
nicolo-ribaudo committed Nov 5, 2019
1 parent d25262e commit aa2363d
Show file tree
Hide file tree
Showing 10 changed files with 467 additions and 47 deletions.
117 changes: 83 additions & 34 deletions packages/babel-parser/src/plugins/typescript/index.js
@@ -1,5 +1,7 @@
// @flow

/*:: declare var invariant; */

import type { TokenType } from "../../tokenizer/types";
import { types as tt } from "../../tokenizer/types";
import { types as ct } from "../../tokenizer/context";
Expand All @@ -25,6 +27,7 @@ import * as charCodes from "charcodes";
type TsModifier =
| "readonly"
| "abstract"
| "declare"
| "static"
| "public"
| "private"
Expand Down Expand Up @@ -126,6 +129,31 @@ export default (superClass: Class<Parser>): Class<Parser> =>
return undefined;
}

/** Parses a list of modifiers, in any order.
* If you need a specific order, you must call this function multiple times:
* this.tsParseModifiers(["public"]);
* this.tsParseModifiers(["abstract", "readonly"]);
*/
tsParseModifiers<T: TsModifier>(
allowedModifiers: T[],
): { [key: TsModifier]: ?true, __proto__: null } {
const modifiers = Object.create(null);

while (true) {
const startPos = this.state.start;
const modifier: ?T = this.tsParseModifier(allowedModifiers);

if (!modifier) break;

if (Object.hasOwnProperty.call(modifiers, modifier)) {
this.raise(startPos, `Duplicate modifier: '${modifier}'`);
}
modifiers[modifier] = true;
}

return modifiers;
}

tsIsListTerminator(kind: ParsingContext): boolean {
switch (kind) {
case "EnumMembers":
Expand Down Expand Up @@ -402,7 +430,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
return this.eat(tt.name) && this.match(tt.colon);
}

tsTryParseIndexSignature(node: N.TsIndexSignature): ?N.TsIndexSignature {
tsTryParseIndexSignature(node: N.Node): ?N.TsIndexSignature {
if (
!(
this.match(tt.bracketL) &&
Expand Down Expand Up @@ -1844,50 +1872,49 @@ export default (superClass: Class<Parser>): Class<Parser> =>

parseClassMemberWithIsStatic(
classBody: N.ClassBody,
member: any,
member: N.ClassMember | N.TsIndexSignature,
state: { hadConstructor: boolean },
isStatic: boolean,
constructorAllowsSuper: boolean,
): void {
const methodOrProp: N.ClassMethod | N.ClassProperty = member;
const prop: N.ClassProperty = member;
const propOrIdx: N.ClassProperty | N.TsIndexSignature = member;

let abstract = false,
readonly = false;

const mod = this.tsParseModifier(["abstract", "readonly"]);
switch (mod) {
case "readonly":
readonly = true;
abstract = !!this.tsParseModifier(["abstract"]);
break;
case "abstract":
abstract = true;
readonly = !!this.tsParseModifier(["readonly"]);
break;
}
const modifiers = this.tsParseModifiers([
"abstract",
"readonly",
"declare",
]);

if (abstract) methodOrProp.abstract = true;
if (readonly) propOrIdx.readonly = true;
Object.assign(member, modifiers);

if (!abstract && !isStatic && !methodOrProp.accessibility) {
const idx = this.tsTryParseIndexSignature(member);
if (idx) {
classBody.body.push(idx);
return;
const idx = this.tsTryParseIndexSignature(member);
if (idx) {
classBody.body.push(idx);

if (modifiers.abstract) {
this.raise(
member.start,
"Index signatures cannot have the 'abstract' modifier",
);
}
if (isStatic) {
this.raise(
member.start,
"Index signatures cannot have the 'static' modifier",
);
}
if ((member: any).accessibility) {
this.raise(
member.start,
`Index signatures cannot have an accessibility modifier ('${
(member: any).accessibility
}')`,
);
}
}

if (readonly) {
// Must be a property (if not an index signature).
methodOrProp.static = isStatic;
this.parseClassPropertyName(prop);
this.parsePostMemberNameModifiers(methodOrProp);
this.pushClassProperty(classBody, prop);
return;
}

/*:: invariant(member.type !== "TSIndexSignature") */

super.parseClassMemberWithIsStatic(
classBody,
member,
Expand All @@ -1902,6 +1929,20 @@ export default (superClass: Class<Parser>): Class<Parser> =>
): void {
const optional = this.eat(tt.question);
if (optional) methodOrProp.optional = true;

if ((methodOrProp: any).readonly && this.match(tt.parenL)) {
this.raise(
methodOrProp.start,
"Class methods cannot have the 'readonly' modifier",
);
}

if ((methodOrProp: any).declare && this.match(tt.parenL)) {
this.raise(
methodOrProp.start,
"Class methods cannot have the 'declare' modifier",
);
}
}

// Note: The reason we do this in `parseExpressionStatement` and not `parseStatement`
Expand Down Expand Up @@ -2050,6 +2091,14 @@ export default (superClass: Class<Parser>): Class<Parser> =>

parseClassProperty(node: N.ClassProperty): N.ClassProperty {
this.parseClassPropertyAnnotation(node);

if (node.declare && this.match(tt.equal)) {
this.raise(
this.state.start,
"'declare' class fields cannot have an initializer",
);
}

return super.parseClassProperty(node);
}

Expand Down
21 changes: 11 additions & 10 deletions packages/babel-parser/src/types.js
Expand Up @@ -743,18 +743,19 @@ export type ClassPrivateMethod = NodeBase &
computed: false,
};

export type ClassProperty = ClassMemberBase & {
type: "ClassProperty",
key: Expression,
value: ?Expression, // TODO: Not in spec that this is nullable.
export type ClassProperty = ClassMemberBase &
DeclarationBase & {
type: "ClassProperty",
key: Expression,
value: ?Expression, // TODO: Not in spec that this is nullable.

typeAnnotation?: ?TypeAnnotationBase, // TODO: Not in spec
variance?: ?FlowVariance, // TODO: Not in spec
typeAnnotation?: ?TypeAnnotationBase, // TODO: Not in spec
variance?: ?FlowVariance, // TODO: Not in spec

// TypeScript only: (TODO: Not in spec)
readonly?: true,
definite?: true,
};
// TypeScript only: (TODO: Not in spec)
readonly?: true,
definite?: true,
};

export type ClassPrivateProperty = NodeBase & {
type: "ClassPrivateProperty",
Expand Down
@@ -0,0 +1,3 @@
class A {
declare bar: string = "test";
}
@@ -0,0 +1,170 @@
{
"type": "File",
"start": 0,
"end": 43,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 3,
"column": 1
}
},
"program": {
"type": "Program",
"start": 0,
"end": 43,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 3,
"column": 1
}
},
"sourceType": "module",
"interpreter": null,
"body": [
{
"type": "ClassDeclaration",
"start": 0,
"end": 43,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 3,
"column": 1
}
},
"id": {
"type": "Identifier",
"start": 6,
"end": 7,
"loc": {
"start": {
"line": 1,
"column": 6
},
"end": {
"line": 1,
"column": 7
},
"identifierName": "A"
},
"name": "A"
},
"superClass": null,
"body": {
"type": "ClassBody",
"start": 8,
"end": 43,
"loc": {
"start": {
"line": 1,
"column": 8
},
"end": {
"line": 3,
"column": 1
}
},
"body": [
{
"type": "ClassProperty",
"start": 12,
"end": 41,
"loc": {
"start": {
"line": 2,
"column": 2
},
"end": {
"line": 2,
"column": 31
}
},
"declare": true,
"static": false,
"key": {
"type": "Identifier",
"start": 20,
"end": 23,
"loc": {
"start": {
"line": 2,
"column": 10
},
"end": {
"line": 2,
"column": 13
},
"identifierName": "bar"
},
"name": "bar"
},
"computed": false,
"typeAnnotation": {
"type": "TSTypeAnnotation",
"start": 23,
"end": 31,
"loc": {
"start": {
"line": 2,
"column": 13
},
"end": {
"line": 2,
"column": 21
}
},
"typeAnnotation": {
"type": "TSStringKeyword",
"start": 25,
"end": 31,
"loc": {
"start": {
"line": 2,
"column": 15
},
"end": {
"line": 2,
"column": 21
}
}
}
},
"value": {
"type": "StringLiteral",
"start": 34,
"end": 40,
"loc": {
"start": {
"line": 2,
"column": 24
},
"end": {
"line": 2,
"column": 30
}
},
"extra": {
"rawValue": "test",
"raw": "\"test\""
},
"value": "test"
}
}
]
}
}
],
"directives": []
}
}
@@ -0,0 +1,4 @@
class A {
declare foo;
declare bar: string;
}

0 comments on commit aa2363d

Please sign in to comment.