Skip to content

Commit

Permalink
improve node type definitions to avoid any's in generated types (#11687)
Browse files Browse the repository at this point in the history
  • Loading branch information
zxbodya committed Jun 8, 2020
1 parent 4108524 commit 36f9798
Show file tree
Hide file tree
Showing 4 changed files with 228 additions and 82 deletions.
134 changes: 94 additions & 40 deletions packages/babel-types/src/definitions/core.js
Expand Up @@ -84,10 +84,13 @@ defineType("BinaryExpression", {
const expression = assertNodeType("Expression");
const inOp = assertNodeType("Expression", "PrivateName");

return function (node, key, val) {
const validator = function (node, key, val) {
const validator = node.operator === "in" ? inOp : expression;
validator(node, key, val);
};
// todo(ts): can be discriminated union by `operator` property
validator.oneOfNodeTypes = ["Expression", "PrivateName"];
return validator;
})(),
},
right: {
Expand Down Expand Up @@ -276,6 +279,15 @@ defineType("File", {
program: {
validate: assertNodeType("Program"),
},
comments: {
validate: assertEach(assertNodeType("Comment")),
optional: true,
},
tokens: {
// todo(ts): add Token type
validate: assertEach(Object.assign(() => {}, { type: "any" })),
optional: true,
},
},
});

Expand Down Expand Up @@ -457,13 +469,19 @@ defineType("Identifier", {
fields: {
...patternLikeCommon,
name: {
validate: chain(assertValueType("string"), function (node, key, val) {
if (!process.env.BABEL_TYPES_8_BREAKING) return;

if (!isValidIdentifier(val, false)) {
throw new TypeError(`"${val}" is not a valid identifier name`);
}
}),
validate: chain(
assertValueType("string"),
Object.assign(
function (node, key, val) {
if (!process.env.BABEL_TYPES_8_BREAKING) return;

if (!isValidIdentifier(val, false)) {
throw new TypeError(`"${val}" is not a valid identifier name`);
}
},
{ type: "string" },
),
),
},
optional: {
validate: assertValueType("boolean"),
Expand Down Expand Up @@ -583,14 +601,20 @@ defineType("RegExpLiteral", {
validate: assertValueType("string"),
},
flags: {
validate: chain(assertValueType("string"), function (node, key, val) {
if (!process.env.BABEL_TYPES_8_BREAKING) return;

const invalid = /[^gimsuy]/.exec(val);
if (invalid) {
throw new TypeError(`"${invalid[0]}" is not a valid RegExp flag`);
}
}),
validate: chain(
assertValueType("string"),
Object.assign(
function (node, key, val) {
if (!process.env.BABEL_TYPES_8_BREAKING) return;

const invalid = /[^gimsuy]/.exec(val);
if (invalid) {
throw new TypeError(`"${invalid[0]}" is not a valid RegExp flag`);
}
},
{ type: "string" },
),
),
default: "",
},
},
Expand Down Expand Up @@ -626,10 +650,13 @@ defineType("MemberExpression", {
const normal = assertNodeType("Identifier", "PrivateName");
const computed = assertNodeType("Expression");

return function (node, key, val) {
const validator = function (node, key, val) {
const validator = node.computed ? computed : normal;
validator(node, key, val);
};
// todo(ts): can be discriminated union by `computed` property
validator.oneOfNodeTypes = ["Expression", "Identifier", "PrivateName"];
return validator;
})(),
},
computed: {
Expand Down Expand Up @@ -719,10 +746,18 @@ defineType("ObjectMethod", {
);
const computed = assertNodeType("Expression");

return function (node, key, val) {
const validator = function (node, key, val) {
const validator = node.computed ? computed : normal;
validator(node, key, val);
};
// todo(ts): can be discriminated union by `computed` property
validator.oneOfNodeTypes = [
"Expression",
"Identifier",
"StringLiteral",
"NumericLiteral",
];
return validator;
})(),
},
decorators: {
Expand Down Expand Up @@ -776,10 +811,18 @@ defineType("ObjectProperty", {
);
const computed = assertNodeType("Expression");

return function (node, key, val) {
const validator = function (node, key, val) {
const validator = node.computed ? computed : normal;
validator(node, key, val);
};
// todo(ts): can be discriminated union by `computed` property
validator.oneOfNodeTypes = [
"Expression",
"Identifier",
"StringLiteral",
"NumericLiteral",
];
return validator;
})(),
},
value: {
Expand All @@ -790,15 +833,18 @@ defineType("ObjectProperty", {
shorthand: {
validate: chain(
assertValueType("boolean"),
function (node, key, val) {
if (!process.env.BABEL_TYPES_8_BREAKING) return;

if (val && node.computed) {
throw new TypeError(
"Property shorthand of ObjectProperty cannot be true if computed is true",
);
}
},
Object.assign(
function (node, key, val) {
if (!process.env.BABEL_TYPES_8_BREAKING) return;

if (val && node.computed) {
throw new TypeError(
"Property shorthand of ObjectProperty cannot be true if computed is true",
);
}
},
{ type: "boolean" },
),
function (node, key, val) {
if (!process.env.BABEL_TYPES_8_BREAKING) return;

Expand Down Expand Up @@ -945,18 +991,26 @@ defineType("TryStatement", {
aliases: ["Statement"],
fields: {
block: {
validate: chain(assertNodeType("BlockStatement"), function (node) {
if (!process.env.BABEL_TYPES_8_BREAKING) return;

// This validator isn't put at the top level because we can run it
// even if this node doesn't have a parent.

if (!node.handler && !node.finalizer) {
throw new TypeError(
"TryStatement expects either a handler or finalizer, or both",
);
}
}),
validate: chain(
assertNodeType("BlockStatement"),
Object.assign(
function (node) {
if (!process.env.BABEL_TYPES_8_BREAKING) return;

// This validator isn't put at the top level because we can run it
// even if this node doesn't have a parent.

if (!node.handler && !node.finalizer) {
throw new TypeError(
"TryStatement expects either a handler or finalizer, or both",
);
}
},
{
oneOfNodeTypes: ["BlockStatement"],
},
),
),
},
handler: {
optional: true,
Expand Down
144 changes: 104 additions & 40 deletions packages/babel-types/src/definitions/es2015.js
Expand Up @@ -170,13 +170,62 @@ defineType("ClassExpression", {
),
optional: true,
},
mixins: {
validate: assertNodeType("InterfaceExtends"),
optional: true,
},
},
});

defineType("ClassDeclaration", {
inherits: "ClassExpression",
aliases: ["Scopable", "Class", "Statement", "Declaration"],
fields: {
id: {
validate: assertNodeType("Identifier"),
},
typeParameters: {
validate: assertNodeType(
"TypeParameterDeclaration",
"TSTypeParameterDeclaration",
"Noop",
),
optional: true,
},
body: {
validate: assertNodeType("ClassBody"),
},
superClass: {
optional: true,
validate: assertNodeType("Expression"),
},
superTypeParameters: {
validate: assertNodeType(
"TypeParameterInstantiation",
"TSTypeParameterInstantiation",
),
optional: true,
},
implements: {
validate: chain(
assertValueType("array"),
assertEach(
assertNodeType("TSExpressionWithTypeArguments", "ClassImplements"),
),
),
optional: true,
},
decorators: {
validate: chain(
assertValueType("array"),
assertEach(assertNodeType("Decorator")),
),
optional: true,
},
mixins: {
validate: assertNodeType("InterfaceExtends"),
optional: true,
},
declare: {
validate: assertValueType("boolean"),
optional: true,
Expand Down Expand Up @@ -247,18 +296,21 @@ defineType("ExportNamedDeclaration", {
optional: true,
validate: chain(
assertNodeType("Declaration"),
function (node, key, val) {
if (!process.env.BABEL_TYPES_8_BREAKING) return;

// This validator isn't put at the top level because we can run it
// even if this node doesn't have a parent.

if (val && node.specifiers.length) {
throw new TypeError(
"Only declaration or specifiers is allowed on ExportNamedDeclaration",
);
}
},
Object.assign(
function (node, key, val) {
if (!process.env.BABEL_TYPES_8_BREAKING) return;

// This validator isn't put at the top level because we can run it
// even if this node doesn't have a parent.

if (val && node.specifiers.length) {
throw new TypeError(
"Only declaration or specifiers is allowed on ExportNamedDeclaration",
);
}
},
{ oneOfNodeTypes: ["Declaration"] },
),
function (node, key, val) {
if (!process.env.BABEL_TYPES_8_BREAKING) return;

Expand Down Expand Up @@ -433,25 +485,31 @@ defineType("MetaProperty", {
aliases: ["Expression"],
fields: {
meta: {
validate: chain(assertNodeType("Identifier"), function (node, key, val) {
if (!process.env.BABEL_TYPES_8_BREAKING) return;

let property;
switch (val.name) {
case "function":
property = "sent";
break;
case "new":
property = "target";
break;
case "import":
property = "meta";
break;
}
if (!is("Identifier", node.property, { name: property })) {
throw new TypeError("Unrecognised MetaProperty");
}
}),
validate: chain(
assertNodeType("Identifier"),
Object.assign(
function (node, key, val) {
if (!process.env.BABEL_TYPES_8_BREAKING) return;

let property;
switch (val.name) {
case "function":
property = "sent";
break;
case "new":
property = "target";
break;
case "import":
property = "meta";
break;
}
if (!is("Identifier", node.property, { name: property })) {
throw new TypeError("Unrecognised MetaProperty");
}
},
{ oneOfNodeTypes: ["Identifier"] },
),
),
},
property: {
validate: assertNodeType("Identifier"),
Expand Down Expand Up @@ -665,15 +723,21 @@ defineType("YieldExpression", {
aliases: ["Expression", "Terminatorless"],
fields: {
delegate: {
validate: chain(assertValueType("boolean"), function (node, key, val) {
if (!process.env.BABEL_TYPES_8_BREAKING) return;

if (val && !node.argument) {
throw new TypeError(
"Property delegate of YieldExpression cannot be true if there is no argument",
);
}
}),
validate: chain(
assertValueType("boolean"),
Object.assign(
function (node, key, val) {
if (!process.env.BABEL_TYPES_8_BREAKING) return;

if (val && !node.argument) {
throw new TypeError(
"Property delegate of YieldExpression cannot be true if there is no argument",
);
}
},
{ type: "boolean" },
),
),
default: false,
},
argument: {
Expand Down

0 comments on commit 36f9798

Please sign in to comment.