Add support for TS declare modifier on fields (#10545)

* [parser] Add support for TS declare modifier on fields (#10484)

* [parser] Add support for TS declare modifier on fields

* Use Object.create(null)

* Comment

* Add support for TS declare types to types and generator (#10544)

* Transform TypeScript "declare" fields (#10546)

* Transform TypeScript "declare" fields

* Remove multiple spaces

* declareFields -> allowDeclareFields

* Update after rebase
This commit is contained in:
Nicolò Ribaudo 2019-11-05 10:56:57 +01:00 committed by GitHub
parent 87feda7c2a
commit e9c1bce50f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 984 additions and 165 deletions

View File

@ -71,26 +71,8 @@ export function ClassBody(node: Object) {
export function ClassProperty(node: Object) { export function ClassProperty(node: Object) {
this.printJoin(node.decorators, node); this.printJoin(node.decorators, node);
this.tsPrintClassMemberModifiers(node, /* isField */ true);
if (node.accessibility) {
// TS
this.word(node.accessibility);
this.space();
}
if (node.static) {
this.word("static");
this.space();
}
if (node.abstract) {
// TS
this.word("abstract");
this.space();
}
if (node.readonly) {
// TS
this.word("readonly");
this.space();
}
if (node.computed) { if (node.computed) {
this.token("["); this.token("[");
this.print(node.key, node); this.print(node.key, node);
@ -148,23 +130,6 @@ export function ClassPrivateMethod(node: Object) {
export function _classMethodHead(node) { export function _classMethodHead(node) {
this.printJoin(node.decorators, node); this.printJoin(node.decorators, node);
this.tsPrintClassMemberModifiers(node, /* isField */ false);
if (node.accessibility) {
// TS
this.word(node.accessibility);
this.space();
}
if (node.abstract) {
// TS
this.word("abstract");
this.space();
}
if (node.static) {
this.word("static");
this.space();
}
this._methodHead(node); this._methodHead(node);
} }

View File

@ -556,3 +556,26 @@ export function tsPrintSignatureDeclarationBase(node) {
this.token(")"); this.token(")");
this.print(node.typeAnnotation, node); this.print(node.typeAnnotation, node);
} }
export function tsPrintClassMemberModifiers(node, isField) {
if (isField && node.declare) {
this.word("declare");
this.space();
}
if (node.accessibility) {
this.word(node.accessibility);
this.space();
}
if (node.static) {
this.word("static");
this.space();
}
if (node.abstract) {
this.word("abstract");
this.space();
}
if (isField && node.readonly) {
this.word("readonly");
this.space();
}
}

View File

@ -0,0 +1,5 @@
class A {
declare foo;
declare bar: string;
declare readonly bax: number;
}

View File

@ -0,0 +1,4 @@
{
"sourceType": "module",
"plugins": ["typescript", "classProperties"]
}

View File

@ -0,0 +1,5 @@
class A {
declare foo;
declare bar: string;
declare readonly bax: number;
}

View File

@ -5,6 +5,8 @@ import ReplaceSupers, {
import memberExpressionToFunctions from "@babel/helper-member-expression-to-functions"; import memberExpressionToFunctions from "@babel/helper-member-expression-to-functions";
import optimiseCall from "@babel/helper-optimise-call-expression"; import optimiseCall from "@babel/helper-optimise-call-expression";
import * as ts from "./typescript";
export function buildPrivateNamesMap(props) { export function buildPrivateNamesMap(props) {
const privateNamesMap = new Map(); const privateNamesMap = new Map();
for (const prop of props) { for (const prop of props) {
@ -556,6 +558,8 @@ export function buildFieldsInitNodes(
let needsClassRef = false; let needsClassRef = false;
for (const prop of props) { for (const prop of props) {
ts.assertFieldTransformed(prop);
const isStatic = prop.node.static; const isStatic = prop.node.static;
const isInstance = !isStatic; const isInstance = !isStatic;
const isPrivate = prop.isPrivate(); const isPrivate = prop.isPrivate();

View File

@ -0,0 +1,19 @@
// @flow
import type { NodePath } from "@babel/traverse";
export function assertFieldTransformed(path: NodePath) {
// TODO (Babel 8): Also check path.node.definite
if (path.node.declare) {
throw path.buildCodeFrameError(
`TypeScript 'declare' fields must first be transformed by ` +
`@babel/plugin-transform-typescript.\n` +
`If you have already enabled that plugin (or '@babel/preset-typescript'), make sure ` +
`that it runs before any plugin related to additional class features:\n` +
` - @babel/plugin-proposal-class-properties\n` +
` - @babel/plugin-proposal-private-methods\n` +
` - @babel/plugin-proposal-decorators`,
);
}
}

View File

@ -28,6 +28,7 @@ import * as charCodes from "charcodes";
type TsModifier = type TsModifier =
| "readonly" | "readonly"
| "abstract" | "abstract"
| "declare"
| "static" | "static"
| "public" | "public"
| "private" | "private"
@ -129,6 +130,31 @@ export default (superClass: Class<Parser>): Class<Parser> =>
return undefined; 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 { tsIsListTerminator(kind: ParsingContext): boolean {
switch (kind) { switch (kind) {
case "EnumMembers": case "EnumMembers":
@ -405,7 +431,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
return this.eat(tt.name) && this.match(tt.colon); return this.eat(tt.name) && this.match(tt.colon);
} }
tsTryParseIndexSignature(node: N.TsIndexSignature): ?N.TsIndexSignature { tsTryParseIndexSignature(node: N.Node): ?N.TsIndexSignature {
if ( if (
!( !(
this.match(tt.bracketL) && this.match(tt.bracketL) &&
@ -1844,50 +1870,49 @@ export default (superClass: Class<Parser>): Class<Parser> =>
parseClassMemberWithIsStatic( parseClassMemberWithIsStatic(
classBody: N.ClassBody, classBody: N.ClassBody,
member: any, member: N.ClassMember | N.TsIndexSignature,
state: { hadConstructor: boolean }, state: { hadConstructor: boolean },
isStatic: boolean, isStatic: boolean,
constructorAllowsSuper: boolean, constructorAllowsSuper: boolean,
): void { ): void {
const methodOrProp: N.ClassMethod | N.ClassProperty = member; const modifiers = this.tsParseModifiers([
const prop: N.ClassProperty = member; "abstract",
const propOrIdx: N.ClassProperty | N.TsIndexSignature = member; "readonly",
"declare",
]);
let abstract = false, Object.assign(member, modifiers);
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;
}
if (abstract) methodOrProp.abstract = true;
if (readonly) propOrIdx.readonly = true;
if (!abstract && !isStatic && !methodOrProp.accessibility) {
const idx = this.tsTryParseIndexSignature(member); const idx = this.tsTryParseIndexSignature(member);
if (idx) { if (idx) {
classBody.body.push(idx); classBody.body.push(idx);
return;
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; return;
} }
/*:: invariant(member.type !== "TSIndexSignature") */
super.parseClassMemberWithIsStatic( super.parseClassMemberWithIsStatic(
classBody, classBody,
member, member,
@ -1902,6 +1927,20 @@ export default (superClass: Class<Parser>): Class<Parser> =>
): void { ): void {
const optional = this.eat(tt.question); const optional = this.eat(tt.question);
if (optional) methodOrProp.optional = true; 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` // Note: The reason we do this in `parseExpressionStatement` and not `parseStatement`
@ -2048,6 +2087,14 @@ export default (superClass: Class<Parser>): Class<Parser> =>
parseClassProperty(node: N.ClassProperty): N.ClassProperty { parseClassProperty(node: N.ClassProperty): N.ClassProperty {
this.parseClassPropertyAnnotation(node); 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); return super.parseClassProperty(node);
} }

View File

@ -743,7 +743,8 @@ export type ClassPrivateMethod = NodeBase &
computed: false, computed: false,
}; };
export type ClassProperty = ClassMemberBase & { export type ClassProperty = ClassMemberBase &
DeclarationBase & {
type: "ClassProperty", type: "ClassProperty",
key: Expression, key: Expression,
value: ?Expression, // TODO: Not in spec that this is nullable. value: ?Expression, // TODO: Not in spec that this is nullable.
@ -754,7 +755,7 @@ export type ClassProperty = ClassMemberBase & {
// TypeScript only: (TODO: Not in spec) // TypeScript only: (TODO: Not in spec)
readonly?: true, readonly?: true,
definite?: true, definite?: true,
}; };
export type ClassPrivateProperty = NodeBase & { export type ClassPrivateProperty = NodeBase & {
type: "ClassPrivateProperty", type: "ClassPrivateProperty",

View File

@ -0,0 +1,3 @@
class A {
declare bar: string = "test";
}

View File

@ -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": []
}
}

View File

@ -0,0 +1,4 @@
class A {
declare foo;
declare bar: string;
}

View File

@ -0,0 +1,187 @@
{
"type": "File",
"start": 0,
"end": 49,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 4,
"column": 1
}
},
"program": {
"type": "Program",
"start": 0,
"end": 49,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 4,
"column": 1
}
},
"sourceType": "module",
"interpreter": null,
"body": [
{
"type": "ClassDeclaration",
"start": 0,
"end": 49,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 4,
"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": 49,
"loc": {
"start": {
"line": 1,
"column": 8
},
"end": {
"line": 4,
"column": 1
}
},
"body": [
{
"type": "ClassProperty",
"start": 12,
"end": 24,
"loc": {
"start": {
"line": 2,
"column": 2
},
"end": {
"line": 2,
"column": 14
}
},
"declare": true,
"static": false,
"key": {
"type": "Identifier",
"start": 20,
"end": 23,
"loc": {
"start": {
"line": 2,
"column": 10
},
"end": {
"line": 2,
"column": 13
},
"identifierName": "foo"
},
"name": "foo"
},
"computed": false,
"value": null
},
{
"type": "ClassProperty",
"start": 27,
"end": 47,
"loc": {
"start": {
"line": 3,
"column": 2
},
"end": {
"line": 3,
"column": 22
}
},
"declare": true,
"static": false,
"key": {
"type": "Identifier",
"start": 35,
"end": 38,
"loc": {
"start": {
"line": 3,
"column": 10
},
"end": {
"line": 3,
"column": 13
},
"identifierName": "bar"
},
"name": "bar"
},
"computed": false,
"typeAnnotation": {
"type": "TSTypeAnnotation",
"start": 38,
"end": 46,
"loc": {
"start": {
"line": 3,
"column": 13
},
"end": {
"line": 3,
"column": 21
}
},
"typeAnnotation": {
"type": "TSStringKeyword",
"start": 40,
"end": 46,
"loc": {
"start": {
"line": 3,
"column": 15
},
"end": {
"line": 3,
"column": 21
}
}
}
},
"value": null
}
]
}
}
],
"directives": []
}
}

View File

@ -0,0 +1,3 @@
class A {
declare foo() {}
}

View File

@ -0,0 +1,145 @@
{
"type": "File",
"start": 0,
"end": 30,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 3,
"column": 1
}
},
"errors": [
"SyntaxError: Class methods cannot have the 'declare' modifier (2:2)"
],
"program": {
"type": "Program",
"start": 0,
"end": 30,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 3,
"column": 1
}
},
"sourceType": "module",
"interpreter": null,
"body": [
{
"type": "ClassDeclaration",
"start": 0,
"end": 30,
"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": 30,
"loc": {
"start": {
"line": 1,
"column": 8
},
"end": {
"line": 3,
"column": 1
}
},
"body": [
{
"type": "ClassMethod",
"start": 12,
"end": 28,
"loc": {
"start": {
"line": 2,
"column": 2
},
"end": {
"line": 2,
"column": 18
}
},
"declare": true,
"static": false,
"key": {
"type": "Identifier",
"start": 20,
"end": 23,
"loc": {
"start": {
"line": 2,
"column": 10
},
"end": {
"line": 2,
"column": 13
},
"identifierName": "foo"
},
"name": "foo"
},
"computed": false,
"kind": "method",
"id": null,
"generator": false,
"async": false,
"params": [],
"body": {
"type": "BlockStatement",
"start": 26,
"end": 28,
"loc": {
"start": {
"line": 2,
"column": 16
},
"end": {
"line": 2,
"column": 18
}
},
"body": [],
"directives": []
}
}
]
}
}
],
"directives": []
}
}

View File

@ -1,3 +0,0 @@
{
"throws": "Unexpected token, expected \";\" (2:14)"
}

View File

@ -0,0 +1,145 @@
{
"type": "File",
"start": 0,
"end": 31,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 3,
"column": 1
}
},
"errors": [
"SyntaxError: Class methods cannot have the 'readonly' modifier (2:4)"
],
"program": {
"type": "Program",
"start": 0,
"end": 31,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 3,
"column": 1
}
},
"sourceType": "module",
"interpreter": null,
"body": [
{
"type": "ClassDeclaration",
"start": 0,
"end": 31,
"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": "C"
},
"name": "C"
},
"superClass": null,
"body": {
"type": "ClassBody",
"start": 8,
"end": 31,
"loc": {
"start": {
"line": 1,
"column": 8
},
"end": {
"line": 3,
"column": 1
}
},
"body": [
{
"type": "ClassMethod",
"start": 14,
"end": 29,
"loc": {
"start": {
"line": 2,
"column": 4
},
"end": {
"line": 2,
"column": 19
}
},
"readonly": true,
"static": false,
"key": {
"type": "Identifier",
"start": 23,
"end": 24,
"loc": {
"start": {
"line": 2,
"column": 13
},
"end": {
"line": 2,
"column": 14
},
"identifierName": "m"
},
"name": "m"
},
"computed": false,
"kind": "method",
"id": null,
"generator": false,
"async": false,
"params": [],
"body": {
"type": "BlockStatement",
"start": 27,
"end": 29,
"loc": {
"start": {
"line": 2,
"column": 17
},
"end": {
"line": 2,
"column": 19
}
},
"body": [],
"directives": []
}
}
]
}
}
],
"directives": []
}
}

View File

@ -77,7 +77,7 @@
}, },
"body": [ "body": [
{ {
"type": "ClassProperty", "type": "ClassPrivateProperty",
"start": 12, "start": 12,
"end": 24, "end": 24,
"loc": { "loc": {
@ -127,7 +127,7 @@
"value": null "value": null
}, },
{ {
"type": "ClassProperty", "type": "ClassPrivateProperty",
"start": 27, "start": 27,
"end": 47, "end": 47,
"loc": { "loc": {

View File

@ -44,11 +44,94 @@ function registerGlobalType(programScope, name) {
} }
export default declare( export default declare(
(api, { jsxPragma = "React", allowNamespaces = false }) => { (
api,
{
jsxPragma = "React",
allowNamespaces = false,
allowDeclareFields = false,
},
) => {
api.assertVersion(7); api.assertVersion(7);
const JSX_ANNOTATION_REGEX = /\*?\s*@jsx\s+([^\s]+)/; const JSX_ANNOTATION_REGEX = /\*?\s*@jsx\s+([^\s]+)/;
const classMemberVisitors = {
field(path) {
const { node } = path;
if (!allowDeclareFields && node.declare) {
throw path.buildCodeFrameError(
`The 'declare' modifier is only allowed when the 'allowDeclareFields' option of ` +
`@babel/plugin-transform-typescript or @babel/preset-typescript is enabled.`,
);
}
if (node.definite || node.declare) {
if (node.value) {
throw path.buildCodeFrameError(
`Definietly assigned fields and fields with the 'declare' modifier cannot` +
` be initialized here, but only in the constructor`,
);
}
path.remove();
} else if (!allowDeclareFields && !node.value && !node.decorators) {
path.remove();
}
if (node.accessibility) node.accessibility = null;
if (node.abstract) node.abstract = null;
if (node.readonly) node.readonly = null;
if (node.optional) node.optional = null;
if (node.typeAnnotation) node.typeAnnotation = null;
},
method({ node }) {
if (node.accessibility) node.accessibility = null;
if (node.abstract) node.abstract = null;
if (node.optional) node.optional = null;
// Rest handled by Function visitor
},
constructor(path, classPath) {
// Collects parameter properties so that we can add an assignment
// for each of them in the constructor body
//
// We use a WeakSet to ensure an assignment for a parameter
// property is only added once. This is necessary for cases like
// using `transform-classes`, which causes this visitor to run
// twice.
const parameterProperties = [];
for (const param of path.node.params) {
if (
param.type === "TSParameterProperty" &&
!PARSED_PARAMS.has(param.parameter)
) {
PARSED_PARAMS.add(param.parameter);
parameterProperties.push(param.parameter);
}
}
if (parameterProperties.length) {
const assigns = parameterProperties.map(p => {
let id;
if (t.isIdentifier(p)) {
id = p;
} else if (t.isAssignmentPattern(p) && t.isIdentifier(p.left)) {
id = p.left;
} else {
throw path.buildCodeFrameError(
"Parameter properties can not be destructuring patterns.",
);
}
return template.statement.ast`this.${id} = ${id}`;
});
injectInitialization(classPath, path, assigns);
}
},
};
return { return {
name: "transform-typescript", name: "transform-typescript",
inherits: syntaxTypeScript, inherits: syntaxTypeScript,
@ -192,27 +275,6 @@ export default declare(
if (node.definite) node.definite = null; if (node.definite) node.definite = null;
}, },
ClassMethod(path) {
const { node } = path;
if (node.accessibility) node.accessibility = null;
if (node.abstract) node.abstract = null;
if (node.optional) node.optional = null;
// Rest handled by Function visitor
},
ClassProperty(path) {
const { node } = path;
if (node.accessibility) node.accessibility = null;
if (node.abstract) node.abstract = null;
if (node.readonly) node.readonly = null;
if (node.optional) node.optional = null;
if (node.definite) node.definite = null;
if (node.typeAnnotation) node.typeAnnotation = null;
},
TSIndexSignature(path) { TSIndexSignature(path) {
path.remove(); path.remove();
}, },
@ -238,54 +300,14 @@ export default declare(
// class transform would transform the class, causing more specific // class transform would transform the class, causing more specific
// visitors to not run. // visitors to not run.
path.get("body.body").forEach(child => { path.get("body.body").forEach(child => {
const childNode = child.node; if (child.isClassMethod()) {
if (child.node.kind === "constructor") {
if (t.isClassMethod(childNode, { kind: "constructor" })) { classMemberVisitors.constructor(child, path);
// Collects parameter properties so that we can add an assignment
// for each of them in the constructor body
//
// We use a WeakSet to ensure an assignment for a parameter
// property is only added once. This is necessary for cases like
// using `transform-classes`, which causes this visitor to run
// twice.
const parameterProperties = [];
for (const param of childNode.params) {
if (
param.type === "TSParameterProperty" &&
!PARSED_PARAMS.has(param.parameter)
) {
PARSED_PARAMS.add(param.parameter);
parameterProperties.push(param.parameter);
}
}
if (parameterProperties.length) {
const assigns = parameterProperties.map(p => {
let id;
if (t.isIdentifier(p)) {
id = p;
} else if (
t.isAssignmentPattern(p) &&
t.isIdentifier(p.left)
) {
id = p.left;
} else { } else {
throw path.buildCodeFrameError( classMemberVisitors.method(child, path);
"Parameter properties can not be destructuring patterns.",
);
}
return template.statement.ast`this.${id} = ${id}`;
});
injectInitialization(path, child, assigns);
} }
} else if (child.isClassProperty()) { } else if (child.isClassProperty()) {
childNode.typeAnnotation = null; classMemberVisitors.field(child, path);
if (!childNode.value && !childNode.decorators) {
child.remove();
}
} }
}); });
}, },

View File

@ -0,0 +1,3 @@
class A {
declare x;
}

View File

@ -0,0 +1,4 @@
{
"plugins": ["transform-typescript"],
"throws": "The 'declare' modifier is only allowed when the 'allowDeclareFields' option of @babel/plugin-transform-typescript or @babel/preset-typescript is enabled."
}

View File

@ -0,0 +1,3 @@
class A {
x;
}

View File

@ -0,0 +1,3 @@
{
"plugins": [["transform-typescript", { "allowDeclareFields": true }]]
}

View File

@ -0,0 +1,3 @@
class A {
x;
}

View File

@ -0,0 +1,3 @@
class A {
declare x;
}

View File

@ -0,0 +1,3 @@
{
"plugins": [["transform-typescript", { "allowDeclareFields": true }]]
}

View File

@ -0,0 +1 @@
class A {}

View File

@ -1,7 +1,8 @@
class C { class C {
public a?: number; public a?: number;
private b: number = 0; private b: number = 0;
readonly c!: number = 1; readonly c: number = 1;
@foo d: number; @foo d: number;
@foo e: number = 3; @foo e: number = 3;
f!: number;
} }

View File

@ -0,0 +1,4 @@
class A {
declare x;
y;
}

View File

@ -0,0 +1,7 @@
{
"plugins": [
"proposal-class-properties",
["transform-typescript", { "allowDeclareFields": true }]
],
"throws": "TypeScript 'declare' fields must first be transformed by @babel/plugin-transform-typescript.\nIf you have already enabled that plugin (or '@babel/preset-typescript'), make sure that it runs before any plugin related to additional class features:\n - @babel/plugin-proposal-class-properties\n - @babel/plugin-proposal-private-methods\n - @babel/plugin-proposal-decorators"
}

View File

@ -0,0 +1,4 @@
class A {
declare x;
y;
}

View File

@ -0,0 +1,6 @@
{
"plugins": [
["transform-typescript", { "allowDeclareFields": true }],
"proposal-class-properties"
]
}

View File

@ -0,0 +1,8 @@
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
class A {
constructor() {
_defineProperty(this, "y", void 0);
}
}

View File

@ -0,0 +1,3 @@
{
"plugins": [["transform-typescript", { "allowDeclareFields": true }]]
}

View File

@ -4,7 +4,13 @@ import transformTypeScript from "@babel/plugin-transform-typescript";
export default declare( export default declare(
( (
api, api,
{ jsxPragma, allExtensions = false, isTSX = false, allowNamespaces }, {
jsxPragma,
allExtensions = false,
isTSX = false,
allowNamespaces,
allowDeclareFields,
},
) => { ) => {
api.assertVersion(7); api.assertVersion(7);
@ -19,13 +25,18 @@ export default declare(
throw new Error("isTSX:true requires allExtensions:true"); throw new Error("isTSX:true requires allExtensions:true");
} }
const pluginOptions = isTSX => ({
jsxPragma,
isTSX,
allowNamespaces,
allowDeclareFields,
});
return { return {
overrides: allExtensions overrides: allExtensions
? [ ? [
{ {
plugins: [ plugins: [[transformTypeScript, pluginOptions(isTSX)]],
[transformTypeScript, { jsxPragma, isTSX, allowNamespaces }],
],
}, },
] ]
: [ : [
@ -33,18 +44,13 @@ export default declare(
// Only set 'test' if explicitly requested, since it requires that // Only set 'test' if explicitly requested, since it requires that
// Babel is being called` // Babel is being called`
test: /\.ts$/, test: /\.ts$/,
plugins: [[transformTypeScript, { jsxPragma, allowNamespaces }]], plugins: [[transformTypeScript, pluginOptions(false)]],
}, },
{ {
// Only set 'test' if explicitly requested, since it requires that // Only set 'test' if explicitly requested, since it requires that
// Babel is being called` // Babel is being called`
test: /\.tsx$/, test: /\.tsx$/,
plugins: [ plugins: [[transformTypeScript, pluginOptions(true)]],
[
transformTypeScript,
{ jsxPragma, isTSX: true, allowNamespaces },
],
],
}, },
], ],
}; };

View File

@ -67,6 +67,10 @@ defineType("ClassProperty", {
validate: assertValueType("boolean"), validate: assertValueType("boolean"),
optional: true, optional: true,
}, },
declare: {
validate: assertValueType("boolean"),
optional: true,
},
}, },
}); });