diff --git a/packages/babel-types/scripts/generators/docs.js b/packages/babel-types/scripts/generators/docs.js index 3bbb52362400..340dbbc11315 100644 --- a/packages/babel-types/scripts/generators/docs.js +++ b/packages/babel-types/scripts/generators/docs.js @@ -101,8 +101,12 @@ Object.keys(types.BUILDER_KEYS) } if (defaultValue !== null || types.NODE_FIELDS[key][field].optional) { 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 { fieldDescription.push(" (required)"); } diff --git a/packages/babel-types/src/asserts/generated/index.js b/packages/babel-types/src/asserts/generated/index.js index 4fb0fd754a15..9f2ebce70e4a 100644 --- a/packages/babel-types/src/asserts/generated/index.js +++ b/packages/babel-types/src/asserts/generated/index.js @@ -224,12 +224,12 @@ export function assertArrowFunctionExpression( export function assertClassBody(node: Object, opts?: Object = {}): void { assert("ClassBody", node, opts); } -export function assertClassDeclaration(node: Object, opts?: Object = {}): void { - assert("ClassDeclaration", node, opts); -} export function assertClassExpression(node: Object, opts?: Object = {}): void { assert("ClassExpression", node, opts); } +export function assertClassDeclaration(node: Object, opts?: Object = {}): void { + assert("ClassDeclaration", node, opts); +} export function assertExportAllDeclaration( node: Object, opts?: Object = {}, diff --git a/packages/babel-types/src/builders/generated/index.js b/packages/babel-types/src/builders/generated/index.js index fe7f320d028a..8aebb3a8d830 100644 --- a/packages/babel-types/src/builders/generated/index.js +++ b/packages/babel-types/src/builders/generated/index.js @@ -225,14 +225,14 @@ export function ClassBody(...args: Array): Object { return builder("ClassBody", ...args); } export { ClassBody as classBody }; -export function ClassDeclaration(...args: Array): Object { - return builder("ClassDeclaration", ...args); -} -export { ClassDeclaration as classDeclaration }; export function ClassExpression(...args: Array): Object { return builder("ClassExpression", ...args); } export { ClassExpression as classExpression }; +export function ClassDeclaration(...args: Array): Object { + return builder("ClassDeclaration", ...args); +} +export { ClassDeclaration as classDeclaration }; export function ExportAllDeclaration(...args: Array): Object { return builder("ExportAllDeclaration", ...args); } diff --git a/packages/babel-types/src/constants/index.js b/packages/babel-types/src/constants/index.js index b777caa0f11e..4dfe13d65adb 100644 --- a/packages/babel-types/src/constants/index.js +++ b/packages/babel-types/src/constants/index.js @@ -41,6 +41,12 @@ export const BINARY_OPERATORS = [ ...BOOLEAN_BINARY_OPERATORS, ]; +export const ASSIGNMENT_OPERATORS = [ + "=", + "+=", + ...NUMBER_BINARY_OPERATORS.map(op => op + "="), +]; + export const BOOLEAN_UNARY_OPERATORS = ["delete", "!"]; export const NUMBER_UNARY_OPERATORS = ["+", "-", "~"]; export const STRING_UNARY_OPERATORS = ["typeof"]; diff --git a/packages/babel-types/src/definitions/core.js b/packages/babel-types/src/definitions/core.js index 973c3bfa67c8..9461e36d3cc2 100644 --- a/packages/babel-types/src/definitions/core.js +++ b/packages/babel-types/src/definitions/core.js @@ -1,9 +1,13 @@ // @flow -import isValidIdentifier from "../validators/isValidIdentifier"; + +import esutils from "esutils"; + +import is from "../validators/is"; import { BINARY_OPERATORS, LOGICAL_OPERATORS, + ASSIGNMENT_OPERATORS, UNARY_OPERATORS, UPDATE_OPERATORS, } from "../constants"; @@ -26,7 +30,7 @@ defineType("ArrayExpression", { assertNodeOrValueType("null", "Expression", "SpreadElement"), ), ), - default: [], + default: !process.env.BABEL_TYPES_8_BREAKING ? [] : undefined, }, }, visitor: ["elements"], @@ -36,10 +40,29 @@ defineType("ArrayExpression", { defineType("AssignmentExpression", { fields: { 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: { - validate: assertNodeType("LVal"), + validate: !process.env.BABEL_TYPES_8_BREAKING + ? assertNodeType("LVal") + : assertNodeType( + "Identifier", + "MemberExpression", + "ArrayPattern", + "ObjectPattern", + ), }, right: { validate: assertNodeType("Expression"), @@ -147,10 +170,14 @@ defineType("CallExpression", { ), ), }, - optional: { - validate: assertOneOf(true, false), - optional: true, - }, + ...(!process.env.BABEL_TYPES_8_BREAKING + ? { + optional: { + validate: assertOneOf(true, false), + optional: true, + }, + } + : {}), typeArguments: { validate: assertNodeType("TypeParameterInstantiation"), optional: true, @@ -166,7 +193,7 @@ defineType("CatchClause", { visitor: ["param", "body"], fields: { param: { - validate: assertNodeType("Identifier"), + validate: assertNodeType("Identifier", "ArrayPattern", "ObjectPattern"), optional: true, }, body: { @@ -256,7 +283,15 @@ defineType("ForInStatement", { ], fields: { left: { - validate: assertNodeType("VariableDeclaration", "LVal"), + validate: !process.env.BABEL_TYPES_8_BREAKING + ? assertNodeType("VariableDeclaration", "LVal") + : assertNodeType( + "VariableDeclaration", + "Identifier", + "MemberExpression", + "ArrayPattern", + "ObjectPattern", + ), }, right: { validate: assertNodeType("Expression"), @@ -305,10 +340,8 @@ export const functionCommon = { }, generator: { default: false, - validate: assertValueType("boolean"), }, async: { - validate: assertValueType("boolean"), default: false, }, }; @@ -359,6 +392,17 @@ defineType("FunctionDeclaration", { "Pureish", "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", { @@ -405,17 +449,55 @@ defineType("Identifier", { fields: { ...patternLikeCommon, name: { - validate: chain(function(node, key, val) { - if (!isValidIdentifier(val)) { - // throw new TypeError(`"${val}" is not a valid identifier name`); + validate: chain(assertValueType("string"), function(node, key, val) { + if (!process.env.BABEL_TYPES_8_BREAKING) return; + + if (!esutils.keyword.isIdentifierNameES6(val)) { + throw new TypeError(`"${val}" is not a valid identifier name`); } - }, assertValueType("string")), + }), }, optional: { validate: assertValueType("boolean"), 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", { @@ -492,7 +574,14 @@ defineType("RegExpLiteral", { validate: assertValueType("string"), }, 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: "", }, }, @@ -537,10 +626,14 @@ defineType("MemberExpression", { computed: { default: false, }, - optional: { - validate: assertOneOf(true, false), - optional: true, - }, + ...(!process.env.BABEL_TYPES_8_BREAKING + ? { + optional: { + validate: assertOneOf(true, false), + optional: true, + }, + } + : {}), }, }); @@ -597,19 +690,15 @@ defineType("ObjectExpression", { }); defineType("ObjectMethod", { - builder: ["kind", "key", "params", "body", "computed"], + builder: ["kind", "key", "params", "body", "computed", "generator", "async"], fields: { ...functionCommon, ...functionTypeAnnotationCommon, kind: { - validate: chain( - assertValueType("string"), - assertOneOf("method", "get", "set"), - ), - default: "method", + validate: assertOneOf("method", "get", "set"), + ...(!process.env.BABEL_TYPES_8_BREAKING ? { default: "method" } : {}), }, computed: { - validate: assertValueType("boolean"), default: false, }, key: { @@ -632,6 +721,7 @@ defineType("ObjectMethod", { assertValueType("array"), assertEach(assertNodeType("Decorator")), ), + optional: true, }, body: { validate: assertNodeType("BlockStatement"), @@ -657,10 +747,15 @@ defineType("ObjectMethod", { }); defineType("ObjectProperty", { - builder: ["key", "value", "computed", "shorthand", "decorators"], + builder: [ + "key", + "value", + "computed", + "shorthand", + ...(!process.env.BABEL_TYPES_8_BREAKING ? ["decorators"] : []), + ], fields: { computed: { - validate: assertValueType("boolean"), default: false, }, key: { @@ -684,7 +779,27 @@ defineType("ObjectProperty", { validate: assertNodeType("Expression", "PatternLike"), }, 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, }, decorators: { @@ -697,6 +812,17 @@ defineType("ObjectProperty", { }, visitor: ["key", "value", "decorators"], 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", { @@ -707,9 +833,22 @@ defineType("RestElement", { fields: { ...patternLikeCommon, 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", { @@ -792,13 +931,23 @@ defineType("ThrowStatement", { }, }); -// todo: at least handler or finalizer should be set to be valid defineType("TryStatement", { visitor: ["block", "handler", "finalizer"], aliases: ["Statement"], fields: { 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: { optional: true, @@ -835,7 +984,9 @@ defineType("UpdateExpression", { default: false, }, argument: { - validate: assertNodeType("Expression"), + validate: !process.env.BABEL_TYPES_8_BREAKING + ? assertNodeType("Expression") + : assertNodeType("Identifier", "MemberExpression"), }, operator: { validate: assertOneOf(...UPDATE_OPERATORS), @@ -855,10 +1006,7 @@ defineType("VariableDeclaration", { optional: true, }, kind: { - validate: chain( - assertValueType("string"), - assertOneOf("var", "let", "const"), - ), + validate: assertOneOf("var", "let", "const"), }, declarations: { 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", { visitor: ["id", "init"], fields: { 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: { optional: true, @@ -894,7 +1068,7 @@ defineType("WhileStatement", { validate: assertNodeType("Expression"), }, body: { - validate: assertNodeType("BlockStatement", "Statement"), + validate: assertNodeType("Statement"), }, }, }); @@ -907,7 +1081,7 @@ defineType("WithStatement", { validate: assertNodeType("Expression"), }, body: { - validate: assertNodeType("BlockStatement", "Statement"), + validate: assertNodeType("Statement"), }, }, }); diff --git a/packages/babel-types/src/definitions/es2015.js b/packages/babel-types/src/definitions/es2015.js index 53f3c1f3df7d..853af1b12e65 100644 --- a/packages/babel-types/src/definitions/es2015.js +++ b/packages/babel-types/src/definitions/es2015.js @@ -3,6 +3,7 @@ import defineType, { assertShape, assertNodeType, assertValueType, + assertNodeOrValueType, chain, assertEach, assertOneOf, @@ -13,6 +14,7 @@ import { functionTypeAnnotationCommon, patternLikeCommon, } from "./core"; +import is from "../validators/is"; defineType("AssignmentPattern", { visitor: ["left", "right", "decorators" /* for legacy param decorators */], @@ -31,11 +33,13 @@ defineType("AssignmentPattern", { right: { validate: assertNodeType("Expression"), }, + // For TypeScript decorators: { validate: chain( assertValueType("array"), assertEach(assertNodeType("Decorator")), ), + optional: true, }, }, }); @@ -49,14 +53,16 @@ defineType("ArrayPattern", { elements: { validate: chain( assertValueType("array"), - assertEach(assertNodeType("PatternLike")), + assertEach(assertNodeOrValueType("null", "PatternLike")), ), }, + // For TypeScript decorators: { validate: chain( assertValueType("array"), assertEach(assertNodeType("Decorator")), ), + optional: true, }, }, }); @@ -106,41 +112,7 @@ defineType("ClassBody", { }, }); -const classCommon = { - 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, - }, -}; - -defineType("ClassDeclaration", { +defineType("ClassExpression", { builder: ["id", "superClass", "body", "decorators"], visitor: [ "id", @@ -152,40 +124,22 @@ defineType("ClassDeclaration", { "implements", "decorators", ], - aliases: ["Scopable", "Class", "Statement", "Declaration", "Pureish"], + aliases: ["Scopable", "Class", "Expression", "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. + // In declarations, this is missing if this is the + // child of an ExportDefaultDeclaration. + optional: true, }, - decorators: { - validate: chain( - assertValueType("array"), - assertEach(assertNodeType("Decorator")), + typeParameters: { + validate: assertNodeType( + "TypeParameterDeclaration", + "TSTypeParameterDeclaration", + "Noop", ), optional: true, }, - }, -}); - -defineType("ClassExpression", { - inherits: "ClassDeclaration", - aliases: ["Scopable", "Class", "Expression", "Pureish"], - fields: { - ...classCommon, - id: { - optional: true, - validate: assertNodeType("Identifier"), - }, body: { validate: assertNodeType("ClassBody"), }, @@ -193,6 +147,22 @@ defineType("ClassExpression", { 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"), @@ -203,6 +173,32 @@ defineType("ClassExpression", { }, }); +defineType("ClassDeclaration", { + inherits: "ClassExpression", + aliases: ["Scopable", "Class", "Statement", "Declaration", "Pureish"], + fields: { + declare: { + validate: assertValueType("boolean"), + optional: true, + }, + abstract: { + validate: assertValueType("boolean"), + 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", { visitor: ["source"], aliases: [ @@ -248,18 +244,53 @@ defineType("ExportNamedDeclaration", { ], fields: { declaration: { - validate: assertNodeType("Declaration"), 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: { + default: [], validate: chain( assertValueType("array"), assertEach( - assertNodeType( - "ExportSpecifier", - "ExportDefaultSpecifier", - "ExportNamespaceSpecifier", - ), + (function() { + const sourced = assertNodeType( + "ExportSpecifier", + "ExportDefaultSpecifier", + "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", { visitor: ["left", "right", "body"], + builder: ["left", "right", "body", "await"], aliases: [ "Scopable", "Statement", @@ -296,7 +328,27 @@ defineType("ForOfStatement", { ], fields: { 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: { validate: assertNodeType("Expression"), @@ -306,7 +358,6 @@ defineType("ForOfStatement", { }, await: { default: false, - validate: assertValueType("boolean"), }, }, }); @@ -380,9 +431,26 @@ defineType("MetaProperty", { visitor: ["meta", "property"], aliases: ["Expression"], fields: { - // todo: limit to new.target 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: { validate: assertNodeType("Identifier"), @@ -396,19 +464,14 @@ export const classMethodOrPropertyCommon = { optional: true, }, accessibility: { - validate: chain( - assertValueType("string"), - assertOneOf("public", "private", "protected"), - ), + validate: assertOneOf("public", "private", "protected"), optional: true, }, static: { default: false, - validate: assertValueType("boolean"), }, computed: { default: false, - validate: assertValueType("boolean"), }, optional: { validate: assertValueType("boolean"), @@ -443,10 +506,7 @@ export const classMethodOrDeclareMethodCommon = { ...functionCommon, ...classMethodOrPropertyCommon, kind: { - validate: chain( - assertValueType("string"), - assertOneOf("get", "set", "method", "constructor"), - ), + validate: assertOneOf("get", "set", "method", "constructor"), default: "method", }, access: { @@ -467,7 +527,16 @@ export const classMethodOrDeclareMethodCommon = { defineType("ClassMethod", { aliases: ["Function", "Scopable", "BlockParent", "FunctionParent", "Method"], - builder: ["kind", "key", "params", "body", "computed", "static"], + builder: [ + "kind", + "key", + "params", + "body", + "computed", + "static", + "generator", + "async", + ], visitor: [ "key", "params", @@ -554,7 +623,6 @@ defineType("TemplateElement", { }), }, tail: { - validate: assertValueType("boolean"), default: false, }, }, @@ -595,7 +663,15 @@ defineType("YieldExpression", { aliases: ["Expression", "Terminatorless"], fields: { 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, }, argument: { diff --git a/packages/babel-types/src/definitions/experimental.js b/packages/babel-types/src/definitions/experimental.js index f4e110e1002f..a77350105a5b 100644 --- a/packages/babel-types/src/definitions/experimental.js +++ b/packages/babel-types/src/definitions/experimental.js @@ -26,9 +26,16 @@ defineType("AwaitExpression", { defineType("BindExpression", { visitor: ["object", "callee"], aliases: ["Expression"], - fields: { - // todo - }, + fields: !process.env.BABEL_TYPES_8_BREAKING + ? {} + : { + object: { + validate: assertNodeType("Expression"), + }, + callee: { + validate: assertNodeType("Expression"), + }, + }, }); defineType("ClassProperty", { diff --git a/packages/babel-types/src/definitions/flow.js b/packages/babel-types/src/definitions/flow.js index 68560582605f..e04d16886d07 100644 --- a/packages/babel-types/src/definitions/flow.js +++ b/packages/babel-types/src/definitions/flow.js @@ -468,7 +468,7 @@ defineType("VoidTypeAnnotation", { // Enums defineType("EnumDeclaration", { - alises: ["Declaration"], + aliases: ["Declaration"], visitor: ["id", "body"], fields: { id: validateType("Identifier"), diff --git a/packages/babel-types/src/definitions/index.js b/packages/babel-types/src/definitions/index.js index d6e1763ebdf0..7341c67369a9 100644 --- a/packages/babel-types/src/definitions/index.js +++ b/packages/babel-types/src/definitions/index.js @@ -14,6 +14,7 @@ import { NODE_FIELDS, BUILDER_KEYS, DEPRECATED_KEYS, + NODE_PARENT_VALIDATIONS, } from "./utils"; import { PLACEHOLDERS, @@ -43,6 +44,7 @@ export { NODE_FIELDS, BUILDER_KEYS, DEPRECATED_KEYS, + NODE_PARENT_VALIDATIONS, PLACEHOLDERS, PLACEHOLDERS_ALIAS, PLACEHOLDERS_FLIPPED_ALIAS, diff --git a/packages/babel-types/src/definitions/jsx.js b/packages/babel-types/src/definitions/jsx.js index a088f03c1929..9b74dd52f488 100644 --- a/packages/babel-types/src/definitions/jsx.js +++ b/packages/babel-types/src/definitions/jsx.js @@ -142,7 +142,6 @@ defineType("JSXOpeningElement", { }, selfClosing: { default: false, - validate: assertValueType("boolean"), }, attributes: { validate: chain( diff --git a/packages/babel-types/src/definitions/utils.js b/packages/babel-types/src/definitions/utils.js index 3f7225bd50b2..cc3a347cca4a 100644 --- a/packages/babel-types/src/definitions/utils.js +++ b/packages/babel-types/src/definitions/utils.js @@ -1,6 +1,6 @@ // @flow import is from "../validators/is"; -import { validateField } from "../validators/validate"; +import { validateField, validateChild } from "../validators/validate"; export const VISITOR_KEYS: { [string]: Array } = {}; export const ALIAS_KEYS: { [string]: Array } = {}; @@ -8,14 +8,13 @@ export const FLIPPED_ALIAS_KEYS: { [string]: Array } = {}; export const NODE_FIELDS: { [string]: {} } = {}; export const BUILDER_KEYS: { [string]: Array } = {}; export const DEPRECATED_KEYS: { [string]: string } = {}; +export const NODE_PARENT_VALIDATIONS = {}; function getType(val) { if (Array.isArray(val)) { return "array"; } else if (val === null) { return "null"; - } else if (val === undefined) { - return "undefined"; } else { return typeof val; } @@ -71,7 +70,10 @@ export function assertEach(callback: Validator): Validator { if (!Array.isArray(val)) return; 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; @@ -96,23 +98,20 @@ export function assertOneOf(...values: Array): Validator { export function assertNodeType(...types: Array): Validator { function validate(node, key, val) { - let valid = false; - for (const type of types) { if (is(type, val)) { - valid = true; - break; + validateChild(node, key, val); + return; } } - if (!valid) { - throw new TypeError( - `Property ${key} of ${ - node.type - } expected node to be of a type ${JSON.stringify(types)} ` + - `but instead got ${JSON.stringify(val && val.type)}`, - ); - } + throw new TypeError( + `Property ${key} of ${ + node.type + } expected node to be of a type ${JSON.stringify( + types, + )} but instead got ${JSON.stringify(val && val.type)}`, + ); } validate.oneOfNodeTypes = types; @@ -122,23 +121,20 @@ export function assertNodeType(...types: Array): Validator { export function assertNodeOrValueType(...types: Array): Validator { function validate(node, key, val) { - let valid = false; - for (const type of types) { if (getType(val) === type || is(type, val)) { - valid = true; - break; + validateChild(node, key, val); + return; } } - if (!valid) { - throw new TypeError( - `Property ${key} of ${ - node.type - } expected node to be of a type ${JSON.stringify(types)} ` + - `but instead got ${JSON.stringify(val && val.type)}`, - ); - } + throw new TypeError( + `Property ${key} of ${ + node.type + } expected node to be of a type ${JSON.stringify( + types, + )} but instead got ${JSON.stringify(val && val.type)}`, + ); } validate.oneOfNodeOrValueTypes = types; @@ -200,6 +196,17 @@ export function chain(...fns: Array): Validator { return validate; } +const validTypeOpts = [ + "aliases", + "builder", + "deprecatedAlias", + "fields", + "inherits", + "visitor", + "validate", +]; +const validFieldKeys = ["default", "optional", "validate"]; + export default function defineType( type: string, opts: { @@ -211,16 +218,38 @@ export default function defineType( builder?: Array, inherits?: string, deprecatedAlias?: string, + validate?: Validator, } = {}, ) { 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)) { + const field = inherits.fields[key]; + fields[key] = { + default: field.default, + optional: field.optional, + validate: field.validate, + }; + } + } + } + const visitor: Array = opts.visitor || inherits.visitor || []; const aliases: Array = opts.aliases || inherits.aliases || []; const builder: Array = opts.builder || inherits.builder || opts.visitor || []; + for (const k of (Object.keys(opts): Array)) { + if (validTypeOpts.indexOf(k) === -1) { + throw new Error(`Unknown type option "${k}" on ${type}`); + } + } + if (opts.deprecatedAlias) { DEPRECATED_KEYS[opts.deprecatedAlias] = type; } @@ -233,14 +262,20 @@ export default function defineType( for (const key of Object.keys(fields)) { const field = fields[key]; - if (builder.indexOf(key) === -1) { + if (field.default !== undefined && builder.indexOf(key) === -1) { field.optional = true; } if (field.default === undefined) { field.default = null; - } else if (!field.validate) { + } else if (!field.validate && field.default != null) { field.validate = assertValueType(getType(field.default)); } + + for (const k of (Object.keys(field): Array)) { + if (validFieldKeys.indexOf(k) === -1) { + throw new Error(`Unknown field key "${k}" on ${type}.${key}`); + } + } } VISITOR_KEYS[type] = opts.visitor = visitor; @@ -252,6 +287,10 @@ export default function defineType( FLIPPED_ALIAS_KEYS[alias].push(type); }); + if (opts.validate) { + NODE_PARENT_VALIDATIONS[type] = opts.validate; + } + store[type] = opts; } diff --git a/packages/babel-types/src/validators/generated/index.js b/packages/babel-types/src/validators/generated/index.js index 13ac0be73ac4..7c78934dfe8c 100644 --- a/packages/babel-types/src/validators/generated/index.js +++ b/packages/babel-types/src/validators/generated/index.js @@ -781,11 +781,11 @@ export function isClassBody(node: ?Object, opts?: Object): boolean { return false; } -export function isClassDeclaration(node: ?Object, opts?: Object): boolean { +export function isClassExpression(node: ?Object, opts?: Object): boolean { if (!node) return false; const nodeType = node.type; - if (nodeType === "ClassDeclaration") { + if (nodeType === "ClassExpression") { if (typeof opts === "undefined") { return true; } else { @@ -795,11 +795,11 @@ export function isClassDeclaration(node: ?Object, opts?: Object): boolean { return false; } -export function isClassExpression(node: ?Object, opts?: Object): boolean { +export function isClassDeclaration(node: ?Object, opts?: Object): boolean { if (!node) return false; const nodeType = node.type; - if (nodeType === "ClassExpression") { + if (nodeType === "ClassDeclaration") { if (typeof opts === "undefined") { return true; } else { @@ -3499,8 +3499,8 @@ export function isScopable(node: ?Object, opts?: Object): boolean { "SwitchStatement" === nodeType || "WhileStatement" === nodeType || "ArrowFunctionExpression" === nodeType || - "ClassDeclaration" === nodeType || "ClassExpression" === nodeType || + "ClassDeclaration" === nodeType || "ForOfStatement" === nodeType || "ClassMethod" === nodeType || "ClassPrivateMethod" === nodeType || @@ -3847,8 +3847,8 @@ export function isPureish(node: ?Object, opts?: Object): boolean { "NullLiteral" === nodeType || "BooleanLiteral" === nodeType || "ArrowFunctionExpression" === nodeType || - "ClassDeclaration" === nodeType || "ClassExpression" === nodeType || + "ClassDeclaration" === nodeType || "BigIntLiteral" === nodeType || (nodeType === "Placeholder" && "StringLiteral" === node.expectedNode) ) { @@ -3887,6 +3887,7 @@ export function isDeclaration(node: ?Object, opts?: Object): boolean { "InterfaceDeclaration" === nodeType || "OpaqueType" === nodeType || "TypeAlias" === nodeType || + "EnumDeclaration" === nodeType || "TSDeclareFunction" === nodeType || "TSInterfaceDeclaration" === nodeType || "TSTypeAliasDeclaration" === nodeType || @@ -4149,8 +4150,8 @@ export function isClass(node: ?Object, opts?: Object): boolean { const nodeType = node.type; if ( nodeType === "Class" || - "ClassDeclaration" === nodeType || - "ClassExpression" === nodeType + "ClassExpression" === nodeType || + "ClassDeclaration" === nodeType ) { if (typeof opts === "undefined") { return true; diff --git a/packages/babel-types/src/validators/isValidIdentifier.js b/packages/babel-types/src/validators/isValidIdentifier.js index 6dbbb7c32cd0..d8aaf8c36372 100644 --- a/packages/babel-types/src/validators/isValidIdentifier.js +++ b/packages/babel-types/src/validators/isValidIdentifier.js @@ -5,16 +5,20 @@ import esutils from "esutils"; * Check if the input `name` is a valid identifier name * and isn't a reserved word. */ -export default function isValidIdentifier(name: string): boolean { - if ( - typeof name !== "string" || - esutils.keyword.isReservedWordES6(name, true) - ) { - return false; - } else if (name === "await") { - // invalid in module, valid in script; better be safe (see #4952) - return false; - } else { - return esutils.keyword.isIdentifierNameES6(name); +export default function isValidIdentifier( + name: string, + reserved: boolean = true, +): boolean { + if (typeof name !== "string") return false; + + if (reserved) { + if (esutils.keyword.isReservedWordES6(name, true)) { + return false; + } else if (name === "await") { + // invalid in module, valid in script; better be safe (see #4952) + return false; + } } + + return esutils.keyword.isIdentifierNameES6(name); } diff --git a/packages/babel-types/src/validators/validate.js b/packages/babel-types/src/validators/validate.js index 738683849927..0ec35b684a0f 100644 --- a/packages/babel-types/src/validators/validate.js +++ b/packages/babel-types/src/validators/validate.js @@ -1,5 +1,5 @@ // @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 { if (!node) return; @@ -9,6 +9,7 @@ export default function validate(node?: Object, key: string, val: any): void { const field = fields[key]; validateField(node, key, val, field); + validateChild(node, key, val); } export function validateField( @@ -22,3 +23,10 @@ export function validateField( 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); +}