diff --git a/packages/babel-traverse/src/path/modification.js b/packages/babel-traverse/src/path/modification.js index cd117f3b3b..4e0fb2ea0e 100644 --- a/packages/babel-traverse/src/path/modification.js +++ b/packages/babel-traverse/src/path/modification.js @@ -24,7 +24,7 @@ export function insertBefore(nodes) { return this._containerInsertBefore(nodes); } else if (this.isStatementOrBlock()) { if (this.node) nodes.push(this.node); - this.node = this.container[this.key] = t.blockStatement(nodes); + this._replaceWith(t.blockStatement(nodes)); } else { throw new Error("We don't know what to do with this node type. We were previously a Statement but we can't fit in here?"); } @@ -101,7 +101,7 @@ export function insertAfter(nodes) { return this._containerInsertAfter(nodes); } else if (this.isStatementOrBlock()) { if (this.node) nodes.unshift(this.node); - this.node = this.container[this.key] = t.blockStatement(nodes); + this._replaceWith(t.blockStatement(nodes)); } else { throw new Error("We don't know what to do with this node type. We were previously a Statement but we can't fit in here?"); } @@ -115,6 +115,8 @@ export function insertAfter(nodes) { */ export function updateSiblingKeys(fromIndex, incrementBy) { + if (!this.parent) return; + var paths = this.parent._paths; for (var i = 0; i < paths.length; i++) { let path = paths[i]; diff --git a/packages/babel-traverse/src/path/removal.js b/packages/babel-traverse/src/path/removal.js index f5a042fbe0..8b7ca1b5b3 100644 --- a/packages/babel-traverse/src/path/removal.js +++ b/packages/babel-traverse/src/path/removal.js @@ -40,13 +40,12 @@ export function _callRemovalHooks(position) { } } - export function _remove() { if (Array.isArray(this.container)) { this.container.splice(this.key, 1); this.updateSiblingKeys(this.key, -1); } else { - this.container[this.key] = null; + this._replaceWith(null); } } diff --git a/packages/babel-traverse/src/path/replacement.js b/packages/babel-traverse/src/path/replacement.js index e8cdf75b3c..1094e447a3 100644 --- a/packages/babel-traverse/src/path/replacement.js +++ b/packages/babel-traverse/src/path/replacement.js @@ -141,13 +141,27 @@ export function replaceWith(replacement, whateverAllowed) { if (oldNode) t.inheritsComments(replacement, oldNode); // replace the node - this.node = this.container[this.key] = replacement; + this._replaceWith(replacement); this.type = replacement.type; // potentially create new scope this.setScope(); } +/** + * Description + */ + +export function _replaceWith(node) { + if (this.inList) { + t.validate(this.key, this.parent, [node]); + } else { + t.validate(this.key, this.parent, node); + } + + this.node = this.container[this.key] = node; +} + /** * This method takes an array of statements nodes and then explodes it * into expressions. This method retains completion records which is diff --git a/packages/babel-types/src/definitions/core.js b/packages/babel-types/src/definitions/core.js index 512f791073..ce35a2941a 100644 --- a/packages/babel-types/src/definitions/core.js +++ b/packages/babel-types/src/definitions/core.js @@ -1,11 +1,21 @@ -import define from "./index"; +import define, { assertValueType, assertNodeType } from "./index"; define("ArrayExpression", { + fields: { + elements: { validate: assertValueType("array") } + }, visitor: ["elements"], aliases: ["Expression"] }); define("AssignmentExpression", { + fields: { + elements: { + operator: { validate: assertValueType("string") }, + left: { validate: assertNodeType("LVal") }, + right: { validate: assertNodeType("Expression") } + } + }, builder: ["operator", "left", "right"], visitor: ["left", "right"], aliases: ["Expression"] @@ -13,12 +23,20 @@ define("AssignmentExpression", { define("BinaryExpression", { builder: ["operator", "left", "right"], + fields: { + operator: { validate: assertValueType("string") }, + left: { validate: assertNodeType("Expression") }, + right: { validate: assertNodeType("Expression") } + }, visitor: ["left", "right"], aliases: ["Binary", "Expression"] }); define("BlockStatement", { visitor: ["body"], + fields: { + body: { validate: assertValueType("array") } + }, aliases: ["Scopable", "BlockParent", "Block", "Statement"] }); @@ -29,6 +47,10 @@ define("BreakStatement", { define("CallExpression", { visitor: ["callee", "arguments"], + fields: { + callee: { validate: assertNodeType("Expression") }, + arguments: { validate: assertValueType("array") } + }, aliases: ["Expression"] }); @@ -39,6 +61,11 @@ define("CatchClause", { define("ConditionalExpression", { visitor: ["test", "consequent", "alternate"], + fields: { + test: { validate: assertNodeType("Expression") }, + consequent: { validate: assertNodeType("Expression") }, + alternate: { validate: assertNodeType("Expression") } + }, aliases: ["Expression"] }); @@ -62,12 +89,18 @@ define("EmptyStatement", { define("ExpressionStatement", { visitor: ["expression"], + fields: { + expression: { validate: assertNodeType("Expression") } + }, aliases: ["Statement"] }); define("File", { builder: ["program", "comments", "tokens"], - visitor: ["program"] + visitor: ["program"], + fields: { + program: { validate: assertNodeType("Program") } + } }); define("ForInStatement", { @@ -81,24 +114,37 @@ define("ForStatement", { }); define("FunctionDeclaration", { - builder: { - id: null, - params: null, - body: null, - generator: false, - async: false - }, + builder: ["id", "params", "body", "generator", "async"], visitor: ["id", "params", "body", "returnType", "typeParameters"], + fields: { + id: { validate: assertNodeType("Identifier") }, + params: { validate: assertValueType("array") }, + body: { validate: assertNodeType("BlockStatement") }, + generator: { + default: false, + validate: assertValueType("boolean") + }, + async: { + default: false, + validate: assertValueType("boolean") + } + }, aliases: ["Scopable", "Function", "Func", "BlockParent", "FunctionParent", "Statement", "Pure", "Declaration"] }); define("FunctionExpression", { - builder: { - id: null, - params: null, - body: null, - generator: false, - async: false + builder: ["id", "params", "body", "generator", "async"], + fields: { + params: { validate: assertValueType("array") }, + body: { validate: assertNodeType("BlockStatement") }, + generator: { + default: false, + validate: assertValueType("boolean") + }, + async: { + default: false, + validate: assertValueType("boolean") + } }, visitor: ["id", "params", "body", "returnType", "typeParameters"], aliases: ["Scopable", "Function", "Func", "BlockParent", "FunctionParent", "Expression", "Pure"] @@ -107,7 +153,7 @@ define("FunctionExpression", { define("Identifier", { builder: ["name"], visitor: ["typeAnnotation"], - aliases: ["Expression"] + aliases: ["Expression", "LVal"] }); define("IfStatement", { @@ -120,9 +166,44 @@ define("LabeledStatement", { aliases: ["Statement"] }); -define("Literal", { +define("StringLiteral", { builder: ["value"], - aliases: ["Expression", "Pure"] + fields: { + value: { validate: assertValueType("string") } + }, + aliases: ["Expression", "Pure", "Literal", "Immutable"] +}); + +define("NumberLiteral", { + builder: ["value"], + fields: { + value: { validate: assertValueType("number") } + }, + aliases: ["Expression", "Pure", "Literal", "Immutable"] +}); + +define("NullLiteral", { + aliases: ["Expression", "Pure", "Literal", "Immutable"] +}); + +define("BooleanLiteral", { + builder: ["value"], + fields: { + value: { validate: assertValueType("boolean") } + }, + aliases: ["Expression", "Pure", "Literal", "Immutable"] +}); + +define("RegexLiteral", { + builder: ["pattern", "flags"], + fields: { + pattern: { validate: assertValueType("string") }, + flags: { + validate: assertValueType("string"), + default: "" + } + }, + aliases: ["Expression", "Literal"] }); define("LogicalExpression", { @@ -132,13 +213,12 @@ define("LogicalExpression", { }); define("MemberExpression", { - builder: { - object: null, - property: null, - computed: false + builder: ["object", "property", "computed"], + fields: { + computed: { default: false } }, visitor: ["object", "property"], - aliases: ["Expression"] + aliases: ["Expression", "LVal"] }); define("NewExpression", { @@ -153,15 +233,17 @@ define("ObjectExpression", { define("Program", { visitor: ["body"], + fields: { + body: { validate: assertValueType("array") } + }, aliases: ["Scopable", "BlockParent", "Block", "FunctionParent"] }); define("Property", { - builder: { - kind: "init", - key: null, - value: null, - computed: false + builder: ["kind", "key", "value", "computed"], + fields: { + kind: { default: "init" }, + computed: { default: false } }, visitor: ["key", "value", "decorators"], aliases: ["UserWhitespacable"] @@ -178,6 +260,9 @@ define("ReturnStatement", { define("SequenceExpression", { visitor: ["expressions"], + fields: { + expressions: { validate: assertValueType("array") } + }, aliases: ["Expression"] }); @@ -206,20 +291,18 @@ define("TryStatement", { }); define("UnaryExpression", { - builder: { - operator: null, - argument: null, - prefix: false + builder: ["operator", "argument", "prefix"], + fields: { + prefix: { default: false } }, visitor: ["argument"], aliases: ["UnaryLike", "Expression"] }); define("UpdateExpression", { - builder: { - operator: null, - argument: null, - prefix: false + builder: ["operator", "argument", "prefix"], + fields: { + prefix: { default: false } }, visitor: ["argument"], aliases: ["Expression"] diff --git a/packages/babel-types/src/definitions/es2015.js b/packages/babel-types/src/definitions/es2015.js index dc1f6c866f..7f1d6d238e 100644 --- a/packages/babel-types/src/definitions/es2015.js +++ b/packages/babel-types/src/definitions/es2015.js @@ -2,12 +2,12 @@ import define from "./index"; define("AssignmentPattern", { visitor: ["left", "right"], - aliases: ["Pattern"] + aliases: ["Pattern", "LVal"] }); define("ArrayPattern", { visitor: ["elements", "typeAnnotation"], - aliases: ["Pattern"] + aliases: ["Pattern", "LVal"] }); define("ArrowFunctionExpression", { @@ -91,19 +91,18 @@ define("MetaProperty", { }); define("MethodDefinition", { - builder: { - key: null, - value: null, - kind: "method", - computed: false, - static: false + builder: ["key", "value", "kind", "computed", "static"], + fields: { + kind: { default: "method" }, + computed: { default: false }, + static: { default: false } }, visitor: ["key", "value", "decorators"] }); define("ObjectPattern", { visitor: ["properties", "typeAnnotation"], - aliases: ["Pattern"] + aliases: ["Pattern", "LVal"] }); define("SpreadElement", { @@ -124,7 +123,7 @@ define("TemplateElement"); define("TemplateLiteral", { visitor: ["quasis", "expressions"], - aliases: ["Expression"] + aliases: ["Expression", "Literal"] }); define("YieldExpression", { diff --git a/packages/babel-types/src/definitions/index.js b/packages/babel-types/src/definitions/index.js index 4633ae2999..b384f7e1be 100644 --- a/packages/babel-types/src/definitions/index.js +++ b/packages/babel-types/src/definitions/index.js @@ -1,21 +1,67 @@ +import * as t from "../index"; + export var VISITOR_KEYS = {}; export var ALIAS_KEYS = {}; +export var NODE_FIELDS = {}; export var BUILDER_KEYS = {}; -function builderFromArray(arr) { - var builder = {}; - for (var key of (arr: Array)) builder[key] = null; - return builder; +export function assertContains(vals) { + return function (val, key) { + if (vals.indexOf(val) < 0) { + throw new TypeError(`Property ${key} with the value of ${val} expected to be one of ${JSON.stringify(vals)}`); + } + }; +} + +export function assertNodeType(...types) { + return function (node, key) { + var valid = false; + + for (var type of types) { + if (t.is(type, node)) { + valid = true; + break; + } + } + + if (!valid) { + throw new TypeError(`Property ${key} expected node to be of a type ${JSON.stringify(types)} but instead got ${node && node.type}`); + } + }; +} + +export function assertValueType(type) { + return function (val, key) { + var valid = typeof val === type; + if (type === "array" && Array.isArray(val)) valid = true; + + if (!valid) { + throw new TypeError(`Property ${key} expected type of ${type} but got ${typeof val}`); + } + }; } export default function define(type, opts = {}) { + opts.fields = opts.fields || {}; opts.visitor = opts.visitor || []; opts.aliases = opts.aliases || []; + opts.builder = opts.builder || opts.visitor || []; - if (!opts.builder) opts.builder = builderFromArray(opts.visitor); - if (Array.isArray(opts.builder)) opts.builder = builderFromArray(opts.builder); + // ensure all field keys are represented in `fields` + for (let key of (opts.visitor.concat(opts.builder): Array)) { + opts.fields[key] = opts.fields[key] || {}; + } + + for (let key in opts.fields) { + var field = opts.fields[key]; + + if (field.default === undefined) { + field.default = null; + } + } VISITOR_KEYS[type] = opts.visitor; - ALIAS_KEYS[type] = opts.aliases; BUILDER_KEYS[type] = opts.builder; + NODE_FIELDS[type] = opts.fields; + ALIAS_KEYS[type] = opts.aliases; } diff --git a/packages/babel-types/src/index.js b/packages/babel-types/src/index.js index b019cd3870..46597cb1f6 100644 --- a/packages/babel-types/src/index.js +++ b/packages/babel-types/src/index.js @@ -49,8 +49,8 @@ export const NUMBER_UNARY_OPERATORS = ["+", "-", "++", "--", "~"]; export const STRING_UNARY_OPERATORS = ["typeof"]; import "./definitions/init"; -import { VISITOR_KEYS, BUILDER_KEYS, ALIAS_KEYS } from "./definitions"; -export { VISITOR_KEYS, BUILDER_KEYS, ALIAS_KEYS }; +import { VISITOR_KEYS, ALIAS_KEYS, NODE_FIELDS, BUILDER_KEYS } from "./definitions"; +export { VISITOR_KEYS, ALIAS_KEYS, NODE_FIELDS, BUILDER_KEYS }; export * as react from "./react"; /** @@ -127,44 +127,44 @@ export function isType(nodeType: string, targetType: string): boolean { return false; } -/** - * [Please add a description.] - */ - -each(t.VISITOR_KEYS, function (keys, type) { - if (t.BUILDER_KEYS[type]) return; - - var defs = {}; - each(keys, function (key) { - defs[key] = null; - }); - t.BUILDER_KEYS[type] = defs; -}); - -/** - * [Please add a description.] - */ - each(t.BUILDER_KEYS, function (keys, type) { - var builder = function () { + function builder() { var node = {}; node.type = type; var i = 0; - for (var key in keys) { + for (var key of (keys: Array)) { + var field = t.NODE_FIELDS[type][key]; + var arg = arguments[i++]; - if (arg === undefined) arg = keys[key]; + if (arg === undefined) arg = field.default; + if (field.validate) field.validate(arg, key); + node[key] = arg; } return node; - }; + } t[type] = builder; t[type[0].toLowerCase() + type.slice(1)] = builder; }); +/** + * Description + */ + +export function validate(key, parent, node) { + var fields = t.NODE_FIELDS[parent.type]; + if (!fields) return; + + var field = fields[key]; + if (!field || !field.validate) return; + + field.validate(node, key); +} + /** * Test if an object is shallowly equal. */