From 11bfbd03ae0ca15dd6183468d56aebaaae27bc46 Mon Sep 17 00:00:00 2001 From: Bogdan Savluk Date: Sat, 6 Jun 2020 22:17:29 +0200 Subject: [PATCH 1/4] babel-types improve node type definitions to avoid any's in generated types --- packages/babel-types/src/definitions/core.js | 132 +++++++++++----- .../babel-types/src/definitions/es2015.js | 144 +++++++++++++----- .../src/definitions/experimental.js | 29 +++- packages/babel-types/src/definitions/jsx.js | 4 + 4 files changed, 227 insertions(+), 82 deletions(-) diff --git a/packages/babel-types/src/definitions/core.js b/packages/babel-types/src/definitions/core.js index 9dca26a5d4db..77a0af785fb3 100644 --- a/packages/babel-types/src/definitions/core.js +++ b/packages/babel-types/src/definitions/core.js @@ -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: { @@ -276,6 +279,13 @@ defineType("File", { program: { validate: assertNodeType("Program"), }, + comments: { + validate: assertEach(assertNodeType("Comment")), + }, + tokens: { + // todo(ts): add Token type + validate: assertEach(Object.assign(() => true, { type: "any" })), + }, }, }); @@ -457,13 +467,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"), @@ -583,14 +599,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: "", }, }, @@ -626,10 +648,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: { @@ -719,10 +744,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: { @@ -776,10 +809,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: { @@ -790,15 +831,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; @@ -945,18 +989,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, diff --git a/packages/babel-types/src/definitions/es2015.js b/packages/babel-types/src/definitions/es2015.js index 05100548c17a..cebf5966e915 100644 --- a/packages/babel-types/src/definitions/es2015.js +++ b/packages/babel-types/src/definitions/es2015.js @@ -170,6 +170,10 @@ defineType("ClassExpression", { ), optional: true, }, + mixins: { + validate: assertNodeType("InterfaceExtends"), + optional: true, + }, }, }); @@ -177,6 +181,51 @@ 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, @@ -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; @@ -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"), @@ -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: { diff --git a/packages/babel-types/src/definitions/experimental.js b/packages/babel-types/src/definitions/experimental.js index 3ccc2fcdd47f..c5ce25dae0a0 100644 --- a/packages/babel-types/src/definitions/experimental.js +++ b/packages/babel-types/src/definitions/experimental.js @@ -11,6 +11,7 @@ import { classMethodOrPropertyCommon, classMethodOrDeclareMethodCommon, } from "./es2015"; +import { functionTypeAnnotationCommon } from "./core"; defineType("ArgumentPlaceholder", {}); @@ -28,8 +29,20 @@ defineType("AwaitExpression", { defineType("BindExpression", { visitor: ["object", "callee"], aliases: ["Expression"], + // todo(ts): if this is breaking change - what node types should be here for old version? fields: !process.env.BABEL_TYPES_8_BREAKING - ? {} + ? { + object: { + validate: Object.assign(() => true, { + oneOfNodeTypes: ["Expression"], + }), + }, + callee: { + validate: Object.assign(() => true, { + oneOfNodeTypes: ["Expression"], + }), + }, + } : { object: { validate: assertNodeType("Expression"), @@ -96,10 +109,13 @@ defineType("OptionalMemberExpression", { const normal = assertNodeType("Identifier"); 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"]; + return validator; })(), }, computed: { @@ -211,6 +227,7 @@ defineType("ClassPrivateMethod", { ], fields: { ...classMethodOrDeclareMethodCommon, + ...functionTypeAnnotationCommon, key: { validate: assertNodeType("PrivateName"), }, @@ -226,6 +243,14 @@ defineType("Import", { defineType("ImportAttribute", { visitor: ["key", "value"], + fields: { + key: { + validate: assertNodeType("Identifier"), + }, + value: { + validate: assertNodeType("StringLiteral"), + }, + }, }); defineType("Decorator", { diff --git a/packages/babel-types/src/definitions/jsx.js b/packages/babel-types/src/definitions/jsx.js index 9b74dd52f488..837c4f25b277 100644 --- a/packages/babel-types/src/definitions/jsx.js +++ b/packages/babel-types/src/definitions/jsx.js @@ -65,6 +65,10 @@ defineType("JSXElement", { ), ), }, + selfClosing: { + validate: assertValueType("boolean"), + optional: true, + }, }, }); From f86790b7fb50372fad9d9cdb51b7933a0e44355d Mon Sep 17 00:00:00 2001 From: Bogdan Savluk Date: Sat, 6 Jun 2020 22:34:33 +0200 Subject: [PATCH 2/4] mark comments and tokens fields in File as optional --- packages/babel-types/src/definitions/core.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/babel-types/src/definitions/core.js b/packages/babel-types/src/definitions/core.js index 77a0af785fb3..0f9bf9536e8e 100644 --- a/packages/babel-types/src/definitions/core.js +++ b/packages/babel-types/src/definitions/core.js @@ -281,10 +281,12 @@ defineType("File", { }, comments: { validate: assertEach(assertNodeType("Comment")), + optional: true, }, tokens: { // todo(ts): add Token type validate: assertEach(Object.assign(() => true, { type: "any" })), + optional: true, }, }, }); From f61788ebbc52e8c805fa3b6a7e1c8d3d11b7ceeb Mon Sep 17 00:00:00 2001 From: Bogdan Savluk Date: Sat, 6 Jun 2020 22:35:29 +0200 Subject: [PATCH 3/4] remove already clarified todo --- packages/babel-types/src/definitions/experimental.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/babel-types/src/definitions/experimental.js b/packages/babel-types/src/definitions/experimental.js index c5ce25dae0a0..c4703ea903e9 100644 --- a/packages/babel-types/src/definitions/experimental.js +++ b/packages/babel-types/src/definitions/experimental.js @@ -29,7 +29,6 @@ defineType("AwaitExpression", { defineType("BindExpression", { visitor: ["object", "callee"], aliases: ["Expression"], - // todo(ts): if this is breaking change - what node types should be here for old version? fields: !process.env.BABEL_TYPES_8_BREAKING ? { object: { From 7470c765da8fe43d065de5fab2a37788f51767fb Mon Sep 17 00:00:00 2001 From: Bogdan Savluk Date: Sat, 6 Jun 2020 23:34:44 +0200 Subject: [PATCH 4/4] fix flow types for "empty" validators --- packages/babel-types/src/definitions/core.js | 2 +- packages/babel-types/src/definitions/experimental.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/babel-types/src/definitions/core.js b/packages/babel-types/src/definitions/core.js index 0f9bf9536e8e..38948a63dbc5 100644 --- a/packages/babel-types/src/definitions/core.js +++ b/packages/babel-types/src/definitions/core.js @@ -285,7 +285,7 @@ defineType("File", { }, tokens: { // todo(ts): add Token type - validate: assertEach(Object.assign(() => true, { type: "any" })), + validate: assertEach(Object.assign(() => {}, { type: "any" })), optional: true, }, }, diff --git a/packages/babel-types/src/definitions/experimental.js b/packages/babel-types/src/definitions/experimental.js index c4703ea903e9..50c4a6a0666a 100644 --- a/packages/babel-types/src/definitions/experimental.js +++ b/packages/babel-types/src/definitions/experimental.js @@ -32,12 +32,12 @@ defineType("BindExpression", { fields: !process.env.BABEL_TYPES_8_BREAKING ? { object: { - validate: Object.assign(() => true, { + validate: Object.assign(() => {}, { oneOfNodeTypes: ["Expression"], }), }, callee: { - validate: Object.assign(() => true, { + validate: Object.assign(() => {}, { oneOfNodeTypes: ["Expression"], }), },