Improve @babel/types with env.BABEL_TYPES_8_BREAKING (#10917)

Co-authored-by: Justin Ridgewell <justin@ridgewell.name>
This commit is contained in:
Nicolò Ribaudo 2020-01-11 10:34:30 +01:00 committed by GitHub
parent 405c1aaad8
commit e7b80a2cb9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 514 additions and 194 deletions

View File

@ -101,8 +101,12 @@ Object.keys(types.BUILDER_KEYS)
} }
if (defaultValue !== null || types.NODE_FIELDS[key][field].optional) { if (defaultValue !== null || types.NODE_FIELDS[key][field].optional) {
fieldDescription.push( fieldDescription.push(
" (default: `" + util.inspect(defaultValue) + "`)" " (default: `" + util.inspect(defaultValue) + "`"
); );
if (types.BUILDER_KEYS[key].indexOf(field) < 0) {
fieldDescription.push(", excluded from builder function");
}
fieldDescription.push(")");
} else { } else {
fieldDescription.push(" (required)"); fieldDescription.push(" (required)");
} }

View File

@ -224,12 +224,12 @@ export function assertArrowFunctionExpression(
export function assertClassBody(node: Object, opts?: Object = {}): void { export function assertClassBody(node: Object, opts?: Object = {}): void {
assert("ClassBody", node, opts); assert("ClassBody", node, opts);
} }
export function assertClassDeclaration(node: Object, opts?: Object = {}): void {
assert("ClassDeclaration", node, opts);
}
export function assertClassExpression(node: Object, opts?: Object = {}): void { export function assertClassExpression(node: Object, opts?: Object = {}): void {
assert("ClassExpression", node, opts); assert("ClassExpression", node, opts);
} }
export function assertClassDeclaration(node: Object, opts?: Object = {}): void {
assert("ClassDeclaration", node, opts);
}
export function assertExportAllDeclaration( export function assertExportAllDeclaration(
node: Object, node: Object,
opts?: Object = {}, opts?: Object = {},

View File

@ -225,14 +225,14 @@ export function ClassBody(...args: Array<any>): Object {
return builder("ClassBody", ...args); return builder("ClassBody", ...args);
} }
export { ClassBody as classBody }; export { ClassBody as classBody };
export function ClassDeclaration(...args: Array<any>): Object {
return builder("ClassDeclaration", ...args);
}
export { ClassDeclaration as classDeclaration };
export function ClassExpression(...args: Array<any>): Object { export function ClassExpression(...args: Array<any>): Object {
return builder("ClassExpression", ...args); return builder("ClassExpression", ...args);
} }
export { ClassExpression as classExpression }; export { ClassExpression as classExpression };
export function ClassDeclaration(...args: Array<any>): Object {
return builder("ClassDeclaration", ...args);
}
export { ClassDeclaration as classDeclaration };
export function ExportAllDeclaration(...args: Array<any>): Object { export function ExportAllDeclaration(...args: Array<any>): Object {
return builder("ExportAllDeclaration", ...args); return builder("ExportAllDeclaration", ...args);
} }

View File

@ -41,6 +41,12 @@ export const BINARY_OPERATORS = [
...BOOLEAN_BINARY_OPERATORS, ...BOOLEAN_BINARY_OPERATORS,
]; ];
export const ASSIGNMENT_OPERATORS = [
"=",
"+=",
...NUMBER_BINARY_OPERATORS.map(op => op + "="),
];
export const BOOLEAN_UNARY_OPERATORS = ["delete", "!"]; export const BOOLEAN_UNARY_OPERATORS = ["delete", "!"];
export const NUMBER_UNARY_OPERATORS = ["+", "-", "~"]; export const NUMBER_UNARY_OPERATORS = ["+", "-", "~"];
export const STRING_UNARY_OPERATORS = ["typeof"]; export const STRING_UNARY_OPERATORS = ["typeof"];

View File

@ -1,9 +1,13 @@
// @flow // @flow
import isValidIdentifier from "../validators/isValidIdentifier";
import esutils from "esutils";
import is from "../validators/is";
import { import {
BINARY_OPERATORS, BINARY_OPERATORS,
LOGICAL_OPERATORS, LOGICAL_OPERATORS,
ASSIGNMENT_OPERATORS,
UNARY_OPERATORS, UNARY_OPERATORS,
UPDATE_OPERATORS, UPDATE_OPERATORS,
} from "../constants"; } from "../constants";
@ -26,7 +30,7 @@ defineType("ArrayExpression", {
assertNodeOrValueType("null", "Expression", "SpreadElement"), assertNodeOrValueType("null", "Expression", "SpreadElement"),
), ),
), ),
default: [], default: !process.env.BABEL_TYPES_8_BREAKING ? [] : undefined,
}, },
}, },
visitor: ["elements"], visitor: ["elements"],
@ -36,10 +40,29 @@ defineType("ArrayExpression", {
defineType("AssignmentExpression", { defineType("AssignmentExpression", {
fields: { fields: {
operator: { operator: {
validate: assertValueType("string"), validate: (function() {
if (!process.env.BABEL_TYPES_8_BREAKING) {
return assertValueType("string");
}
const identifier = assertOneOf(...ASSIGNMENT_OPERATORS);
const pattern = assertOneOf("=");
return function(node, key, val) {
const validator = is("Pattern", node.left) ? pattern : identifier;
validator(node, key, val);
};
})(),
}, },
left: { left: {
validate: assertNodeType("LVal"), validate: !process.env.BABEL_TYPES_8_BREAKING
? assertNodeType("LVal")
: assertNodeType(
"Identifier",
"MemberExpression",
"ArrayPattern",
"ObjectPattern",
),
}, },
right: { right: {
validate: assertNodeType("Expression"), validate: assertNodeType("Expression"),
@ -147,10 +170,14 @@ defineType("CallExpression", {
), ),
), ),
}, },
...(!process.env.BABEL_TYPES_8_BREAKING
? {
optional: { optional: {
validate: assertOneOf(true, false), validate: assertOneOf(true, false),
optional: true, optional: true,
}, },
}
: {}),
typeArguments: { typeArguments: {
validate: assertNodeType("TypeParameterInstantiation"), validate: assertNodeType("TypeParameterInstantiation"),
optional: true, optional: true,
@ -166,7 +193,7 @@ defineType("CatchClause", {
visitor: ["param", "body"], visitor: ["param", "body"],
fields: { fields: {
param: { param: {
validate: assertNodeType("Identifier"), validate: assertNodeType("Identifier", "ArrayPattern", "ObjectPattern"),
optional: true, optional: true,
}, },
body: { body: {
@ -256,7 +283,15 @@ defineType("ForInStatement", {
], ],
fields: { fields: {
left: { left: {
validate: assertNodeType("VariableDeclaration", "LVal"), validate: !process.env.BABEL_TYPES_8_BREAKING
? assertNodeType("VariableDeclaration", "LVal")
: assertNodeType(
"VariableDeclaration",
"Identifier",
"MemberExpression",
"ArrayPattern",
"ObjectPattern",
),
}, },
right: { right: {
validate: assertNodeType("Expression"), validate: assertNodeType("Expression"),
@ -305,10 +340,8 @@ export const functionCommon = {
}, },
generator: { generator: {
default: false, default: false,
validate: assertValueType("boolean"),
}, },
async: { async: {
validate: assertValueType("boolean"),
default: false, default: false,
}, },
}; };
@ -359,6 +392,17 @@ defineType("FunctionDeclaration", {
"Pureish", "Pureish",
"Declaration", "Declaration",
], ],
validate: (function() {
if (!process.env.BABEL_TYPES_8_BREAKING) return () => {};
const identifier = assertNodeType("Identifier");
return function(parent, key, node) {
if (!is("ExportDefaultDeclaration", parent)) {
identifier(node, "id", node.id);
}
};
})(),
}); });
defineType("FunctionExpression", { defineType("FunctionExpression", {
@ -405,17 +449,55 @@ defineType("Identifier", {
fields: { fields: {
...patternLikeCommon, ...patternLikeCommon,
name: { name: {
validate: chain(function(node, key, val) { validate: chain(assertValueType("string"), function(node, key, val) {
if (!isValidIdentifier(val)) { if (!process.env.BABEL_TYPES_8_BREAKING) return;
// throw new TypeError(`"${val}" is not a valid identifier name`);
if (!esutils.keyword.isIdentifierNameES6(val)) {
throw new TypeError(`"${val}" is not a valid identifier name`);
} }
}, assertValueType("string")), }),
}, },
optional: { optional: {
validate: assertValueType("boolean"), validate: assertValueType("boolean"),
optional: true, optional: true,
}, },
}, },
validate(parent, key, node) {
if (!process.env.BABEL_TYPES_8_BREAKING) return;
const match = /\.(\w+)$/.exec(key);
if (!match) return;
const [, parentKey] = match;
const nonComp = { computed: false };
// We can't check if `parent.property === node`, because nodes are validated
// before replacing them in the AST.
if (parentKey === "property") {
if (is("MemberExpression", parent, nonComp)) return;
if (is("OptionalMemberExpression", parent, nonComp)) return;
} else if (parentKey === "key") {
if (is("Property", parent, nonComp)) return;
if (is("Method", parent, nonComp)) return;
} else if (parentKey === "exported") {
if (is("ExportSpecifier", parent)) return;
} else if (parentKey === "imported") {
if (is("ImportSpecifier", parent, { imported: node })) return;
} else if (parentKey === "meta") {
if (is("MetaProperty", parent, { meta: node })) return;
}
if (
// Ideally this should be strict if this node is a descendant of a block
// in strict mode. Also, we should disallow "await" in modules.
esutils.keyword.isReservedWordES6(node.name, /* strict */ false) &&
// Even if "this" is a keyword, we are using the Identifier
// node to represent it.
node.name !== "this"
) {
throw new TypeError(`"${node.name}" is not a valid identifer`);
}
},
}); });
defineType("IfStatement", { defineType("IfStatement", {
@ -492,7 +574,14 @@ defineType("RegExpLiteral", {
validate: assertValueType("string"), validate: assertValueType("string"),
}, },
flags: { flags: {
validate: assertValueType("string"), 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`);
}
}),
default: "", default: "",
}, },
}, },
@ -537,10 +626,14 @@ defineType("MemberExpression", {
computed: { computed: {
default: false, default: false,
}, },
...(!process.env.BABEL_TYPES_8_BREAKING
? {
optional: { optional: {
validate: assertOneOf(true, false), validate: assertOneOf(true, false),
optional: true, optional: true,
}, },
}
: {}),
}, },
}); });
@ -597,19 +690,15 @@ defineType("ObjectExpression", {
}); });
defineType("ObjectMethod", { defineType("ObjectMethod", {
builder: ["kind", "key", "params", "body", "computed"], builder: ["kind", "key", "params", "body", "computed", "generator", "async"],
fields: { fields: {
...functionCommon, ...functionCommon,
...functionTypeAnnotationCommon, ...functionTypeAnnotationCommon,
kind: { kind: {
validate: chain( validate: assertOneOf("method", "get", "set"),
assertValueType("string"), ...(!process.env.BABEL_TYPES_8_BREAKING ? { default: "method" } : {}),
assertOneOf("method", "get", "set"),
),
default: "method",
}, },
computed: { computed: {
validate: assertValueType("boolean"),
default: false, default: false,
}, },
key: { key: {
@ -632,6 +721,7 @@ defineType("ObjectMethod", {
assertValueType("array"), assertValueType("array"),
assertEach(assertNodeType("Decorator")), assertEach(assertNodeType("Decorator")),
), ),
optional: true,
}, },
body: { body: {
validate: assertNodeType("BlockStatement"), validate: assertNodeType("BlockStatement"),
@ -657,10 +747,15 @@ defineType("ObjectMethod", {
}); });
defineType("ObjectProperty", { defineType("ObjectProperty", {
builder: ["key", "value", "computed", "shorthand", "decorators"], builder: [
"key",
"value",
"computed",
"shorthand",
...(!process.env.BABEL_TYPES_8_BREAKING ? ["decorators"] : []),
],
fields: { fields: {
computed: { computed: {
validate: assertValueType("boolean"),
default: false, default: false,
}, },
key: { key: {
@ -684,7 +779,27 @@ defineType("ObjectProperty", {
validate: assertNodeType("Expression", "PatternLike"), validate: assertNodeType("Expression", "PatternLike"),
}, },
shorthand: { shorthand: {
validate: assertValueType("boolean"), 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",
);
}
},
function(node, key, val) {
if (!process.env.BABEL_TYPES_8_BREAKING) return;
if (val && !is("Identifier", node.key)) {
throw new TypeError(
"Property shorthand of ObjectProperty cannot be true if key is not an Identifier",
);
}
},
),
default: false, default: false,
}, },
decorators: { decorators: {
@ -697,6 +812,17 @@ defineType("ObjectProperty", {
}, },
visitor: ["key", "value", "decorators"], visitor: ["key", "value", "decorators"],
aliases: ["UserWhitespacable", "Property", "ObjectMember"], aliases: ["UserWhitespacable", "Property", "ObjectMember"],
validate: (function() {
const pattern = assertNodeType("Identifier", "Pattern");
const expression = assertNodeType("Expression");
return function(parent, key, node) {
if (!process.env.BABEL_TYPES_8_BREAKING) return;
const validator = is("ObjectPattern", parent) ? pattern : expression;
validator(node, "value", node.value);
};
})(),
}); });
defineType("RestElement", { defineType("RestElement", {
@ -707,9 +833,22 @@ defineType("RestElement", {
fields: { fields: {
...patternLikeCommon, ...patternLikeCommon,
argument: { argument: {
validate: assertNodeType("LVal"), validate: !process.env.BABEL_TYPES_8_BREAKING
? assertNodeType("LVal")
: assertNodeType("Identifier", "Pattern", "MemberExpression"),
}, },
}, },
validate(parent, key) {
if (!process.env.BABEL_TYPES_8_BREAKING) return;
const match = /(\w+)\[(\d+)\]/.exec(key);
if (!match) throw new Error("Internal Babel error: malformed key.");
const [, listKey, index] = match;
if (parent[listKey].length > index + 1) {
throw new TypeError(`RestElement must be last element of ${listKey}`);
}
},
}); });
defineType("ReturnStatement", { defineType("ReturnStatement", {
@ -792,13 +931,23 @@ defineType("ThrowStatement", {
}, },
}); });
// todo: at least handler or finalizer should be set to be valid
defineType("TryStatement", { defineType("TryStatement", {
visitor: ["block", "handler", "finalizer"], visitor: ["block", "handler", "finalizer"],
aliases: ["Statement"], aliases: ["Statement"],
fields: { fields: {
block: { block: {
validate: assertNodeType("BlockStatement"), 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",
);
}
}),
}, },
handler: { handler: {
optional: true, optional: true,
@ -835,7 +984,9 @@ defineType("UpdateExpression", {
default: false, default: false,
}, },
argument: { argument: {
validate: assertNodeType("Expression"), validate: !process.env.BABEL_TYPES_8_BREAKING
? assertNodeType("Expression")
: assertNodeType("Identifier", "MemberExpression"),
}, },
operator: { operator: {
validate: assertOneOf(...UPDATE_OPERATORS), validate: assertOneOf(...UPDATE_OPERATORS),
@ -855,10 +1006,7 @@ defineType("VariableDeclaration", {
optional: true, optional: true,
}, },
kind: { kind: {
validate: chain( validate: assertOneOf("var", "let", "const"),
assertValueType("string"),
assertOneOf("var", "let", "const"),
),
}, },
declarations: { declarations: {
validate: chain( validate: chain(
@ -867,13 +1015,39 @@ defineType("VariableDeclaration", {
), ),
}, },
}, },
validate(parent, key, node) {
if (!process.env.BABEL_TYPES_8_BREAKING) return;
if (!is("ForXStatement", parent, { left: node })) return;
if (node.declarations.length !== 1) {
throw new TypeError(
`Exactly one VariableDeclarator is required in the VariableDeclaration of a ${parent.type}`,
);
}
},
}); });
defineType("VariableDeclarator", { defineType("VariableDeclarator", {
visitor: ["id", "init"], visitor: ["id", "init"],
fields: { fields: {
id: { id: {
validate: assertNodeType("LVal"), validate: (function() {
if (!process.env.BABEL_TYPES_8_BREAKING) {
return assertNodeType("LVal");
}
const normal = assertNodeType(
"Identifier",
"ArrayPattern",
"ObjectPattern",
);
const without = assertNodeType("Identifier");
return function(node, key, val) {
const validator = node.init ? normal : without;
validator(node, key, val);
};
})(),
}, },
definite: { definite: {
optional: true, optional: true,
@ -894,7 +1068,7 @@ defineType("WhileStatement", {
validate: assertNodeType("Expression"), validate: assertNodeType("Expression"),
}, },
body: { body: {
validate: assertNodeType("BlockStatement", "Statement"), validate: assertNodeType("Statement"),
}, },
}, },
}); });
@ -907,7 +1081,7 @@ defineType("WithStatement", {
validate: assertNodeType("Expression"), validate: assertNodeType("Expression"),
}, },
body: { body: {
validate: assertNodeType("BlockStatement", "Statement"), validate: assertNodeType("Statement"),
}, },
}, },
}); });

View File

@ -3,6 +3,7 @@ import defineType, {
assertShape, assertShape,
assertNodeType, assertNodeType,
assertValueType, assertValueType,
assertNodeOrValueType,
chain, chain,
assertEach, assertEach,
assertOneOf, assertOneOf,
@ -13,6 +14,7 @@ import {
functionTypeAnnotationCommon, functionTypeAnnotationCommon,
patternLikeCommon, patternLikeCommon,
} from "./core"; } from "./core";
import is from "../validators/is";
defineType("AssignmentPattern", { defineType("AssignmentPattern", {
visitor: ["left", "right", "decorators" /* for legacy param decorators */], visitor: ["left", "right", "decorators" /* for legacy param decorators */],
@ -31,11 +33,13 @@ defineType("AssignmentPattern", {
right: { right: {
validate: assertNodeType("Expression"), validate: assertNodeType("Expression"),
}, },
// For TypeScript
decorators: { decorators: {
validate: chain( validate: chain(
assertValueType("array"), assertValueType("array"),
assertEach(assertNodeType("Decorator")), assertEach(assertNodeType("Decorator")),
), ),
optional: true,
}, },
}, },
}); });
@ -49,14 +53,16 @@ defineType("ArrayPattern", {
elements: { elements: {
validate: chain( validate: chain(
assertValueType("array"), assertValueType("array"),
assertEach(assertNodeType("PatternLike")), assertEach(assertNodeOrValueType("null", "PatternLike")),
), ),
}, },
// For TypeScript
decorators: { decorators: {
validate: chain( validate: chain(
assertValueType("array"), assertValueType("array"),
assertEach(assertNodeType("Decorator")), assertEach(assertNodeType("Decorator")),
), ),
optional: true,
}, },
}, },
}); });
@ -106,7 +112,26 @@ defineType("ClassBody", {
}, },
}); });
const classCommon = { defineType("ClassExpression", {
builder: ["id", "superClass", "body", "decorators"],
visitor: [
"id",
"body",
"superClass",
"mixins",
"typeParameters",
"superTypeParameters",
"implements",
"decorators",
],
aliases: ["Scopable", "Class", "Expression", "Pureish"],
fields: {
id: {
validate: assertNodeType("Identifier"),
// In declarations, this is missing if this is the
// child of an ExportDefaultDeclaration.
optional: true,
},
typeParameters: { typeParameters: {
validate: assertNodeType( validate: assertNodeType(
"TypeParameterDeclaration", "TypeParameterDeclaration",
@ -138,35 +163,6 @@ const classCommon = {
), ),
optional: true, optional: true,
}, },
};
defineType("ClassDeclaration", {
builder: ["id", "superClass", "body", "decorators"],
visitor: [
"id",
"body",
"superClass",
"mixins",
"typeParameters",
"superTypeParameters",
"implements",
"decorators",
],
aliases: ["Scopable", "Class", "Statement", "Declaration", "Pureish"],
fields: {
...classCommon,
declare: {
validate: assertValueType("boolean"),
optional: true,
},
abstract: {
validate: assertValueType("boolean"),
optional: true,
},
id: {
validate: assertNodeType("Identifier"),
optional: true, // Missing if this is the child of an ExportDefaultDeclaration.
},
decorators: { decorators: {
validate: chain( validate: chain(
assertValueType("array"), assertValueType("array"),
@ -177,30 +173,30 @@ defineType("ClassDeclaration", {
}, },
}); });
defineType("ClassExpression", { defineType("ClassDeclaration", {
inherits: "ClassDeclaration", inherits: "ClassExpression",
aliases: ["Scopable", "Class", "Expression", "Pureish"], aliases: ["Scopable", "Class", "Statement", "Declaration", "Pureish"],
fields: { fields: {
...classCommon, declare: {
id: { validate: assertValueType("boolean"),
optional: true, optional: true,
validate: assertNodeType("Identifier"),
}, },
body: { abstract: {
validate: assertNodeType("ClassBody"), validate: assertValueType("boolean"),
},
superClass: {
optional: true,
validate: assertNodeType("Expression"),
},
decorators: {
validate: chain(
assertValueType("array"),
assertEach(assertNodeType("Decorator")),
),
optional: true, optional: true,
}, },
}, },
validate: (function() {
const identifier = assertNodeType("Identifier");
return function(parent, key, node) {
if (!process.env.BABEL_TYPES_8_BREAKING) return;
if (!is("ExportDefaultDeclaration", parent)) {
identifier(node, "id", node.id);
}
};
})(),
}); });
defineType("ExportAllDeclaration", { defineType("ExportAllDeclaration", {
@ -248,18 +244,53 @@ defineType("ExportNamedDeclaration", {
], ],
fields: { fields: {
declaration: { declaration: {
validate: assertNodeType("Declaration"),
optional: true, 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",
);
}
},
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.source) {
throw new TypeError("Cannot export a declaration from a source");
}
},
),
}, },
specifiers: { specifiers: {
default: [],
validate: chain( validate: chain(
assertValueType("array"), assertValueType("array"),
assertEach( assertEach(
assertNodeType( (function() {
const sourced = assertNodeType(
"ExportSpecifier", "ExportSpecifier",
"ExportDefaultSpecifier", "ExportDefaultSpecifier",
"ExportNamespaceSpecifier", "ExportNamespaceSpecifier",
), );
const sourceless = assertNodeType("ExportSpecifier");
if (!process.env.BABEL_TYPES_8_BREAKING) return sourced;
return function(node, key, val) {
const validator = node.source ? sourced : sourceless;
validator(node, key, val);
};
})(),
), ),
), ),
}, },
@ -286,6 +317,7 @@ defineType("ExportSpecifier", {
defineType("ForOfStatement", { defineType("ForOfStatement", {
visitor: ["left", "right", "body"], visitor: ["left", "right", "body"],
builder: ["left", "right", "body", "await"],
aliases: [ aliases: [
"Scopable", "Scopable",
"Statement", "Statement",
@ -296,7 +328,27 @@ defineType("ForOfStatement", {
], ],
fields: { fields: {
left: { left: {
validate: assertNodeType("VariableDeclaration", "LVal"), validate: (function() {
if (!process.env.BABEL_TYPES_8_BREAKING) {
return assertNodeType("VariableDeclaration", "LVal");
}
const declaration = assertNodeType("VariableDeclaration");
const lval = assertNodeType(
"Identifier",
"MemberExpression",
"ArrayPattern",
"ObjectPattern",
);
return function(node, key, val) {
if (is("VariableDeclaration", val)) {
declaration(node, key, val);
} else {
lval(node, key, val);
}
};
})(),
}, },
right: { right: {
validate: assertNodeType("Expression"), validate: assertNodeType("Expression"),
@ -306,7 +358,6 @@ defineType("ForOfStatement", {
}, },
await: { await: {
default: false, default: false,
validate: assertValueType("boolean"),
}, },
}, },
}); });
@ -380,9 +431,26 @@ defineType("MetaProperty", {
visitor: ["meta", "property"], visitor: ["meta", "property"],
aliases: ["Expression"], aliases: ["Expression"],
fields: { fields: {
// todo: limit to new.target
meta: { meta: {
validate: assertNodeType("Identifier"), 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");
}
}),
}, },
property: { property: {
validate: assertNodeType("Identifier"), validate: assertNodeType("Identifier"),
@ -396,19 +464,14 @@ export const classMethodOrPropertyCommon = {
optional: true, optional: true,
}, },
accessibility: { accessibility: {
validate: chain( validate: assertOneOf("public", "private", "protected"),
assertValueType("string"),
assertOneOf("public", "private", "protected"),
),
optional: true, optional: true,
}, },
static: { static: {
default: false, default: false,
validate: assertValueType("boolean"),
}, },
computed: { computed: {
default: false, default: false,
validate: assertValueType("boolean"),
}, },
optional: { optional: {
validate: assertValueType("boolean"), validate: assertValueType("boolean"),
@ -443,10 +506,7 @@ export const classMethodOrDeclareMethodCommon = {
...functionCommon, ...functionCommon,
...classMethodOrPropertyCommon, ...classMethodOrPropertyCommon,
kind: { kind: {
validate: chain( validate: assertOneOf("get", "set", "method", "constructor"),
assertValueType("string"),
assertOneOf("get", "set", "method", "constructor"),
),
default: "method", default: "method",
}, },
access: { access: {
@ -467,7 +527,16 @@ export const classMethodOrDeclareMethodCommon = {
defineType("ClassMethod", { defineType("ClassMethod", {
aliases: ["Function", "Scopable", "BlockParent", "FunctionParent", "Method"], aliases: ["Function", "Scopable", "BlockParent", "FunctionParent", "Method"],
builder: ["kind", "key", "params", "body", "computed", "static"], builder: [
"kind",
"key",
"params",
"body",
"computed",
"static",
"generator",
"async",
],
visitor: [ visitor: [
"key", "key",
"params", "params",
@ -554,7 +623,6 @@ defineType("TemplateElement", {
}), }),
}, },
tail: { tail: {
validate: assertValueType("boolean"),
default: false, default: false,
}, },
}, },
@ -595,7 +663,15 @@ defineType("YieldExpression", {
aliases: ["Expression", "Terminatorless"], aliases: ["Expression", "Terminatorless"],
fields: { fields: {
delegate: { delegate: {
validate: assertValueType("boolean"), 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",
);
}
}),
default: false, default: false,
}, },
argument: { argument: {

View File

@ -26,8 +26,15 @@ defineType("AwaitExpression", {
defineType("BindExpression", { defineType("BindExpression", {
visitor: ["object", "callee"], visitor: ["object", "callee"],
aliases: ["Expression"], aliases: ["Expression"],
fields: { fields: !process.env.BABEL_TYPES_8_BREAKING
// todo ? {}
: {
object: {
validate: assertNodeType("Expression"),
},
callee: {
validate: assertNodeType("Expression"),
},
}, },
}); });

View File

@ -468,7 +468,7 @@ defineType("VoidTypeAnnotation", {
// Enums // Enums
defineType("EnumDeclaration", { defineType("EnumDeclaration", {
alises: ["Declaration"], aliases: ["Declaration"],
visitor: ["id", "body"], visitor: ["id", "body"],
fields: { fields: {
id: validateType("Identifier"), id: validateType("Identifier"),

View File

@ -14,6 +14,7 @@ import {
NODE_FIELDS, NODE_FIELDS,
BUILDER_KEYS, BUILDER_KEYS,
DEPRECATED_KEYS, DEPRECATED_KEYS,
NODE_PARENT_VALIDATIONS,
} from "./utils"; } from "./utils";
import { import {
PLACEHOLDERS, PLACEHOLDERS,
@ -43,6 +44,7 @@ export {
NODE_FIELDS, NODE_FIELDS,
BUILDER_KEYS, BUILDER_KEYS,
DEPRECATED_KEYS, DEPRECATED_KEYS,
NODE_PARENT_VALIDATIONS,
PLACEHOLDERS, PLACEHOLDERS,
PLACEHOLDERS_ALIAS, PLACEHOLDERS_ALIAS,
PLACEHOLDERS_FLIPPED_ALIAS, PLACEHOLDERS_FLIPPED_ALIAS,

View File

@ -142,7 +142,6 @@ defineType("JSXOpeningElement", {
}, },
selfClosing: { selfClosing: {
default: false, default: false,
validate: assertValueType("boolean"),
}, },
attributes: { attributes: {
validate: chain( validate: chain(

View File

@ -1,6 +1,6 @@
// @flow // @flow
import is from "../validators/is"; import is from "../validators/is";
import { validateField } from "../validators/validate"; import { validateField, validateChild } from "../validators/validate";
export const VISITOR_KEYS: { [string]: Array<string> } = {}; export const VISITOR_KEYS: { [string]: Array<string> } = {};
export const ALIAS_KEYS: { [string]: Array<string> } = {}; export const ALIAS_KEYS: { [string]: Array<string> } = {};
@ -8,14 +8,13 @@ export const FLIPPED_ALIAS_KEYS: { [string]: Array<string> } = {};
export const NODE_FIELDS: { [string]: {} } = {}; export const NODE_FIELDS: { [string]: {} } = {};
export const BUILDER_KEYS: { [string]: Array<string> } = {}; export const BUILDER_KEYS: { [string]: Array<string> } = {};
export const DEPRECATED_KEYS: { [string]: string } = {}; export const DEPRECATED_KEYS: { [string]: string } = {};
export const NODE_PARENT_VALIDATIONS = {};
function getType(val) { function getType(val) {
if (Array.isArray(val)) { if (Array.isArray(val)) {
return "array"; return "array";
} else if (val === null) { } else if (val === null) {
return "null"; return "null";
} else if (val === undefined) {
return "undefined";
} else { } else {
return typeof val; return typeof val;
} }
@ -71,7 +70,10 @@ export function assertEach(callback: Validator): Validator {
if (!Array.isArray(val)) return; if (!Array.isArray(val)) return;
for (let i = 0; i < val.length; i++) { for (let i = 0; i < val.length; i++) {
callback(node, `${key}[${i}]`, val[i]); const subkey = `${key}[${i}]`;
const v = val[i];
callback(node, subkey, v);
if (process.env.BABEL_TYPES_8_BREAKING) validateChild(node, subkey, v);
} }
} }
validator.each = callback; validator.each = callback;
@ -96,24 +98,21 @@ export function assertOneOf(...values: Array<any>): Validator {
export function assertNodeType(...types: Array<string>): Validator { export function assertNodeType(...types: Array<string>): Validator {
function validate(node, key, val) { function validate(node, key, val) {
let valid = false;
for (const type of types) { for (const type of types) {
if (is(type, val)) { if (is(type, val)) {
valid = true; validateChild(node, key, val);
break; return;
} }
} }
if (!valid) {
throw new TypeError( throw new TypeError(
`Property ${key} of ${ `Property ${key} of ${
node.type node.type
} expected node to be of a type ${JSON.stringify(types)} ` + } expected node to be of a type ${JSON.stringify(
`but instead got ${JSON.stringify(val && val.type)}`, types,
)} but instead got ${JSON.stringify(val && val.type)}`,
); );
} }
}
validate.oneOfNodeTypes = types; validate.oneOfNodeTypes = types;
@ -122,24 +121,21 @@ export function assertNodeType(...types: Array<string>): Validator {
export function assertNodeOrValueType(...types: Array<string>): Validator { export function assertNodeOrValueType(...types: Array<string>): Validator {
function validate(node, key, val) { function validate(node, key, val) {
let valid = false;
for (const type of types) { for (const type of types) {
if (getType(val) === type || is(type, val)) { if (getType(val) === type || is(type, val)) {
valid = true; validateChild(node, key, val);
break; return;
} }
} }
if (!valid) {
throw new TypeError( throw new TypeError(
`Property ${key} of ${ `Property ${key} of ${
node.type node.type
} expected node to be of a type ${JSON.stringify(types)} ` + } expected node to be of a type ${JSON.stringify(
`but instead got ${JSON.stringify(val && val.type)}`, types,
)} but instead got ${JSON.stringify(val && val.type)}`,
); );
} }
}
validate.oneOfNodeOrValueTypes = types; validate.oneOfNodeOrValueTypes = types;
@ -200,6 +196,17 @@ export function chain(...fns: Array<Validator>): Validator {
return validate; return validate;
} }
const validTypeOpts = [
"aliases",
"builder",
"deprecatedAlias",
"fields",
"inherits",
"visitor",
"validate",
];
const validFieldKeys = ["default", "optional", "validate"];
export default function defineType( export default function defineType(
type: string, type: string,
opts: { opts: {
@ -211,16 +218,38 @@ export default function defineType(
builder?: Array<string>, builder?: Array<string>,
inherits?: string, inherits?: string,
deprecatedAlias?: string, deprecatedAlias?: string,
validate?: Validator,
} = {}, } = {},
) { ) {
const inherits = (opts.inherits && store[opts.inherits]) || {}; const inherits = (opts.inherits && store[opts.inherits]) || {};
const fields: Object = opts.fields || inherits.fields || {}; let fields = opts.fields;
if (!fields) {
fields = {};
if (inherits.fields) {
const keys = Object.getOwnPropertyNames(inherits.fields);
for (const key of (keys: Array<string>)) {
const field = inherits.fields[key];
fields[key] = {
default: field.default,
optional: field.optional,
validate: field.validate,
};
}
}
}
const visitor: Array<string> = opts.visitor || inherits.visitor || []; const visitor: Array<string> = opts.visitor || inherits.visitor || [];
const aliases: Array<string> = opts.aliases || inherits.aliases || []; const aliases: Array<string> = opts.aliases || inherits.aliases || [];
const builder: Array<string> = const builder: Array<string> =
opts.builder || inherits.builder || opts.visitor || []; opts.builder || inherits.builder || opts.visitor || [];
for (const k of (Object.keys(opts): Array<string>)) {
if (validTypeOpts.indexOf(k) === -1) {
throw new Error(`Unknown type option "${k}" on ${type}`);
}
}
if (opts.deprecatedAlias) { if (opts.deprecatedAlias) {
DEPRECATED_KEYS[opts.deprecatedAlias] = type; DEPRECATED_KEYS[opts.deprecatedAlias] = type;
} }
@ -233,14 +262,20 @@ export default function defineType(
for (const key of Object.keys(fields)) { for (const key of Object.keys(fields)) {
const field = fields[key]; const field = fields[key];
if (builder.indexOf(key) === -1) { if (field.default !== undefined && builder.indexOf(key) === -1) {
field.optional = true; field.optional = true;
} }
if (field.default === undefined) { if (field.default === undefined) {
field.default = null; field.default = null;
} else if (!field.validate) { } else if (!field.validate && field.default != null) {
field.validate = assertValueType(getType(field.default)); field.validate = assertValueType(getType(field.default));
} }
for (const k of (Object.keys(field): Array<string>)) {
if (validFieldKeys.indexOf(k) === -1) {
throw new Error(`Unknown field key "${k}" on ${type}.${key}`);
}
}
} }
VISITOR_KEYS[type] = opts.visitor = visitor; VISITOR_KEYS[type] = opts.visitor = visitor;
@ -252,6 +287,10 @@ export default function defineType(
FLIPPED_ALIAS_KEYS[alias].push(type); FLIPPED_ALIAS_KEYS[alias].push(type);
}); });
if (opts.validate) {
NODE_PARENT_VALIDATIONS[type] = opts.validate;
}
store[type] = opts; store[type] = opts;
} }

View File

@ -781,11 +781,11 @@ export function isClassBody(node: ?Object, opts?: Object): boolean {
return false; return false;
} }
export function isClassDeclaration(node: ?Object, opts?: Object): boolean { export function isClassExpression(node: ?Object, opts?: Object): boolean {
if (!node) return false; if (!node) return false;
const nodeType = node.type; const nodeType = node.type;
if (nodeType === "ClassDeclaration") { if (nodeType === "ClassExpression") {
if (typeof opts === "undefined") { if (typeof opts === "undefined") {
return true; return true;
} else { } else {
@ -795,11 +795,11 @@ export function isClassDeclaration(node: ?Object, opts?: Object): boolean {
return false; return false;
} }
export function isClassExpression(node: ?Object, opts?: Object): boolean { export function isClassDeclaration(node: ?Object, opts?: Object): boolean {
if (!node) return false; if (!node) return false;
const nodeType = node.type; const nodeType = node.type;
if (nodeType === "ClassExpression") { if (nodeType === "ClassDeclaration") {
if (typeof opts === "undefined") { if (typeof opts === "undefined") {
return true; return true;
} else { } else {
@ -3499,8 +3499,8 @@ export function isScopable(node: ?Object, opts?: Object): boolean {
"SwitchStatement" === nodeType || "SwitchStatement" === nodeType ||
"WhileStatement" === nodeType || "WhileStatement" === nodeType ||
"ArrowFunctionExpression" === nodeType || "ArrowFunctionExpression" === nodeType ||
"ClassDeclaration" === nodeType ||
"ClassExpression" === nodeType || "ClassExpression" === nodeType ||
"ClassDeclaration" === nodeType ||
"ForOfStatement" === nodeType || "ForOfStatement" === nodeType ||
"ClassMethod" === nodeType || "ClassMethod" === nodeType ||
"ClassPrivateMethod" === nodeType || "ClassPrivateMethod" === nodeType ||
@ -3847,8 +3847,8 @@ export function isPureish(node: ?Object, opts?: Object): boolean {
"NullLiteral" === nodeType || "NullLiteral" === nodeType ||
"BooleanLiteral" === nodeType || "BooleanLiteral" === nodeType ||
"ArrowFunctionExpression" === nodeType || "ArrowFunctionExpression" === nodeType ||
"ClassDeclaration" === nodeType ||
"ClassExpression" === nodeType || "ClassExpression" === nodeType ||
"ClassDeclaration" === nodeType ||
"BigIntLiteral" === nodeType || "BigIntLiteral" === nodeType ||
(nodeType === "Placeholder" && "StringLiteral" === node.expectedNode) (nodeType === "Placeholder" && "StringLiteral" === node.expectedNode)
) { ) {
@ -3887,6 +3887,7 @@ export function isDeclaration(node: ?Object, opts?: Object): boolean {
"InterfaceDeclaration" === nodeType || "InterfaceDeclaration" === nodeType ||
"OpaqueType" === nodeType || "OpaqueType" === nodeType ||
"TypeAlias" === nodeType || "TypeAlias" === nodeType ||
"EnumDeclaration" === nodeType ||
"TSDeclareFunction" === nodeType || "TSDeclareFunction" === nodeType ||
"TSInterfaceDeclaration" === nodeType || "TSInterfaceDeclaration" === nodeType ||
"TSTypeAliasDeclaration" === nodeType || "TSTypeAliasDeclaration" === nodeType ||
@ -4149,8 +4150,8 @@ export function isClass(node: ?Object, opts?: Object): boolean {
const nodeType = node.type; const nodeType = node.type;
if ( if (
nodeType === "Class" || nodeType === "Class" ||
"ClassDeclaration" === nodeType || "ClassExpression" === nodeType ||
"ClassExpression" === nodeType "ClassDeclaration" === nodeType
) { ) {
if (typeof opts === "undefined") { if (typeof opts === "undefined") {
return true; return true;

View File

@ -5,16 +5,20 @@ import esutils from "esutils";
* Check if the input `name` is a valid identifier name * Check if the input `name` is a valid identifier name
* and isn't a reserved word. * and isn't a reserved word.
*/ */
export default function isValidIdentifier(name: string): boolean { export default function isValidIdentifier(
if ( name: string,
typeof name !== "string" || reserved: boolean = true,
esutils.keyword.isReservedWordES6(name, true) ): boolean {
) { if (typeof name !== "string") return false;
if (reserved) {
if (esutils.keyword.isReservedWordES6(name, true)) {
return false; return false;
} else if (name === "await") { } else if (name === "await") {
// invalid in module, valid in script; better be safe (see #4952) // invalid in module, valid in script; better be safe (see #4952)
return false; return false;
} else { }
}
return esutils.keyword.isIdentifierNameES6(name); return esutils.keyword.isIdentifierNameES6(name);
} }
}

View File

@ -1,5 +1,5 @@
// @flow // @flow
import { NODE_FIELDS } from "../definitions"; import { NODE_FIELDS, NODE_PARENT_VALIDATIONS } from "../definitions";
export default function validate(node?: Object, key: string, val: any): void { export default function validate(node?: Object, key: string, val: any): void {
if (!node) return; if (!node) return;
@ -9,6 +9,7 @@ export default function validate(node?: Object, key: string, val: any): void {
const field = fields[key]; const field = fields[key];
validateField(node, key, val, field); validateField(node, key, val, field);
validateChild(node, key, val);
} }
export function validateField( export function validateField(
@ -22,3 +23,10 @@ export function validateField(
field.validate(node, key, val); field.validate(node, key, val);
} }
export function validateChild(node?: Object, key: string, val?: Object) {
if (val == null) return;
const validate = NODE_PARENT_VALIDATIONS[val.type];
if (!validate) return;
validate(node, key, val);
}