esvalid
Version:
confirm that a SpiderMonkey format AST represents an ECMAScript program
807 lines (744 loc) • 38 kB
JavaScript
"use strict";
var esutils = require("esutils");
var merge = Object.assign || require("object-assign");
// getClass :: Object -> String
function getClass(obj) {
return {}.toString.call(obj).slice(8, -1);
}
// any :: forall a. [a] -> (a -> Boolean) -> Boolean
function any(predicate, xs) {
for (var i = 0, l = xs.length; i < l; ++i) {
if (predicate(xs[i])) return true;
}
return false;
}
// concatMap :: forall a b. -> (a -> [b]) -> [a] -> [b]
function concatMap(fn, xs) {
var result = [];
for (var i = 0, l = xs.length; i < l; ++i) {
[].push.apply(result, fn(xs[i]));
}
return result;
}
// filter :: forall a. (a -> Boolean) -> [a] -> [a]
function filter(xs, predicate) {
var filtered = [];
for (var i = 0, l = xs.length; i < l; ++i) {
if (predicate(xs[i])) filtered.push(xs[i]);
}
return filtered;
}
// isExpression :: Node -> Boolean
var isExpression = esutils.ast.isExpression;
// isStatement :: Node -> Boolean
var isStatement = esutils.ast.isStatement;
// isSourceElement :: Node -> Boolean
var isSourceElement = esutils.ast.isSourceElement;
// directives :: [Maybe Node] -> [Node]
function directives(stmts) {
if (stmts && stmts.length > 0) {
var s = stmts[0];
if (s && s.type === "ExpressionStatement" && s.expression && s.expression.type === "Literal" && typeof s.expression.value === "string")
return [s.expression.value].concat(directives([].slice.call(stmts, 1)));
}
return [];
}
var OBJECT_PROPERTY_KINDS = ["init", "get", "set"];
var VARIABLE_DECLARATION_KINDS = ["var", "let", "const"];
var ASSIGNMENT_OPERATORS = ["=", "+=", "-=", "*=", "/=", "%=", "<<=", ">>=", ">>>=", "|=", "^=", "&="];
var BINARY_OPERATORS = ["==", "!=", "===", "!==", "<", "<=", ">", ">=", "<<", ">>", ">>>", "+", "-", "*", "/", "%", "|", "^", "&", "in", "instanceof"];
var LOGICAL_OPERATORS = ["||", "&&"];
var UNARY_OPERATORS = ["-", "+", "!", "~", "typeof", "void", "delete"];
var UPDATE_OPERATORS = ["++", "--"];
// isAssignmentOperator :: String -> Boolean
function isAssignmentOperator(op) { return ASSIGNMENT_OPERATORS.indexOf(op) >= 0; }
// isBinaryOperator :: String -> Boolean
function isBinaryOperator(op) { return BINARY_OPERATORS.indexOf(op) >= 0; }
// isLogicalOperator :: String -> Boolean
function isLogicalOperator(op) { return LOGICAL_OPERATORS.indexOf(op) >= 0; }
// isUnaryOperator :: String -> Boolean
function isUnaryOperator(op) { return UNARY_OPERATORS.indexOf(op) >= 0; }
// isUpdateOperator :: String -> Boolean
function isUpdateOperator(op) { return UPDATE_OPERATORS.indexOf(op) >= 0; }
var E, InvalidAstError = E = (function() {
function C(){}
C.prototype = Error.prototype;
function InvalidAstError(node, message) {
Error.call(this);
this.node = node;
this.message = message;
}
InvalidAstError.prototype = new C;
InvalidAstError.prototype.constructor = InvalidAstError;
InvalidAstError.prototype.name = "InvalidAstError";
return InvalidAstError;
}());
// errorsP :: {labels :: [Label], inFunc :: Boolean, inIter :: Boolean, inSwitch :: Boolean} -> Node -> [InvalidAstError]
function errorsP(state) {
return function recurse(node) {
var errors = [], line, column, strict, recursionFn;
if (node.loc != null) {
if (node.loc.source != null && typeof node.loc.source !== "string")
errors.push(new E(node, "`loc.source` must be a string or null"));
if (node.loc.start == null) {
errors.push(new E(node, "`loc.start` must be non-null if `loc` is non-null"));
} else {
line = node.loc.start.line;
column = node.loc.start.column;
if (typeof line !== "number" || line % 1 !== 0 || line < 1)
errors.push(new E(node, "`loc.start.line` must be a positive integer"));
if (typeof column !== "number" || column % 1 !== 0 || column < 0)
errors.push(new E(node, "`loc.start.column` must be a non-negative integer"));
}
if (node.loc.end == null) {
errors.push(new E(node, "`loc.end` must be non-null if `loc` is non-null"));
} else {
line = node.loc.end.line;
column = node.loc.end.column;
if (typeof line !== "number" || line % 1 !== 0 || line < 1)
errors.push(new E(node, "`loc.end.line` must be a positive integer"));
if (typeof column !== "number" || column % 1 !== 0 || column < 0)
errors.push(new E(node, "`loc.end.column` must be a non-negative integer"));
}
}
switch (node.type) {
case "ArrayExpression":
if (node.elements == null)
errors.push(new E(node, "ArrayExpression `elements` member must be non-null"));
else
[].push.apply(errors, concatMap(function(element) {
if (element == null)
return [];
else if (!isExpression(element))
return [new E(element, "non-null ArrayExpression elements must be expression nodes")];
return recurse(element);
}, node.elements));
break;
case "AssignmentExpression":
if (!isAssignmentOperator(node.operator))
errors.push(new E(node, "AssignmentExpression `operator` member must be one of " + JSON.stringify(ASSIGNMENT_OPERATORS)));
if (!isExpression(node.left))
errors.push(new E(node, "AssignmentExpression `left` member must be an expression node"));
if (!isExpression(node.right))
errors.push(new E(node, "AssignmentExpression `right` member must be an expression node"));
if (node.left != null)
[].push.apply(errors, recurse(node.left));
if (node.right != null)
[].push.apply(errors, recurse(node.right));
break;
case "BinaryExpression":
if (!isBinaryOperator(node.operator))
errors.push(new E(node, "BinaryExpression `operator` member must be one of " + JSON.stringify(BINARY_OPERATORS)));
if (!isExpression(node.left))
errors.push(new E(node, "BinaryExpression `left` member must be an expression node"));
if (!isExpression(node.right))
errors.push(new E(node, "BinaryExpression `right` member must be an expression node"));
if (node.left != null)
[].push.apply(errors, recurse(node.left));
if (node.right != null)
[].push.apply(errors, recurse(node.right));
break;
case "BlockStatement":
if (node.body == null)
errors.push(new E(node, "BlockStatement `body` member must be non-null"));
else
[].push.apply(errors, concatMap(function(stmt) {
var es = [];
if (!isStatement(stmt))
es.push(new E(stmt != null ? stmt : node, "BlockStatement `body` member must only contain statement nodes"));
if (stmt != null)
[].push.apply(es, recurse(stmt));
return es;
}, node.body));
break;
case "BreakStatement":
if (!state.inIter && !state.inSwitch)
errors.push(new E(node, "BreakStatement must have an IterationStatement or SwitchStatement as an ancestor"));
if (node.label != null) {
if (node.label.type !== "Identifier")
errors.push(new E(node.label, "BreakStatement `label` member must be an Identifier node"));
else if (state.labels.indexOf(node.label.name) < 0)
errors.push(new E(node.label, "labelled BreakStatement must have a matching LabeledStatement ancestor"));
[].push.apply(errors, recurse(node.label));
}
break;
case "CallExpression":
if (!isExpression(node.callee))
errors.push(new E(node, "CallExpression `callee` member must be an expression node"));
if (node.arguments == null)
errors.push(new E(node, "CallExpression `arguments` member must be non-null"));
else
[].push.apply(errors, concatMap(function(arg) {
var es = [];
if (!isExpression(arg))
es.push(new E(arg != null ? arg : node, "CallExpression `arguments` member must only contain expression nodes"));
if (arg != null)
[].push.apply(es, recurse(arg));
return es;
}, node.arguments));
if (node.callee != null)
[].push.apply(errors, recurse(node.callee));
break;
case "CatchClause":
if (!isExpression(node.param))
errors.push(new E(node, "CatchClause `param` member must be an expression node"));
if (node.body == null || node.body.type !== "BlockStatement")
errors.push(new E(node, "CatchClause `body` member must be a BlockStatement node"));
if (node.param != null)
[].push.apply(errors, recurse(node.param));
if (node.body != null)
[].push.apply(errors, recurse(node.body));
break;
case "ConditionalExpression":
if (!isExpression(node.test))
errors.push(new E(node, "ConditionalExpression `test` member must be an expression node"));
if (!isExpression(node.alternate))
errors.push(new E(node, "ConditionalExpression `alternate` member must be an expression node"));
if (!isExpression(node.consequent))
errors.push(new E(node, "ConditionalExpression `consequent` member must be an expression node"));
if (node.test != null)
[].push.apply(errors, recurse(node.test));
if (node.alternate != null)
[].push.apply(errors, recurse(node.alternate));
if (node.consequent != null)
[].push.apply(errors, recurse(node.consequent));
break;
case "ContinueStatement":
if (!state.inIter)
errors.push(new E(node, "ContinueStatement must have an IterationStatement as an ancestor"));
if (node.label != null) {
if (node.label.type !== "Identifier")
errors.push(new E(node.label, "ContinueStatement `label` member must be an Identifier node"));
else if (state.labels.indexOf(node.label.name) < 0)
errors.push(new E(node.label, "labelled ContinueStatement must have a matching LabeledStatement ancestor"));
[].push.apply(errors, recurse(node.label));
}
break;
case "DebuggerStatement":
break;
case "DoWhileStatement":
if (!isStatement(node.body))
errors.push(new E(node, "DoWhileStatement `body` member must be a statement node"));
if (!isExpression(node.test))
errors.push(new E(node, "DoWhileStatement `test` member must be an expression node"));
if (node.body != null)
[].push.apply(errors, errorsP(merge({}, state, {inIter: true}))(node.body));
if (node.test != null)
[].push.apply(errors, recurse(node.test));
break;
case "EmptyStatement":
break;
case "ExpressionStatement":
if (!isExpression(node.expression))
errors.push(new E(node, "ExpressionStatement `expression` member must be an expression node"));
if (node.expression != null)
[].push.apply(errors, recurse(node.expression));
break;
case "ForInStatement":
if (node.left == null || !isExpression(node.left) && node.left.type !== "VariableDeclaration")
errors.push(new E(node, "ForInStatement `left` member must be an expression or VariableDeclaration node"));
if (!isExpression(node.right))
errors.push(new E(node, "ForInStatement `right` member must be an expression node"));
if (!isStatement(node.body))
errors.push(new E(node, "ForInStatement `body` member must be a statement node"));
if (node.left != null)
[].push.apply(errors, recurse(node.left));
if (node.right != null)
[].push.apply(errors, recurse(node.right));
if (node.body != null)
[].push.apply(errors, errorsP(merge({}, state, {inIter: true}))(node.body));
break;
case "ForStatement":
if (node.init != null && !isExpression(node.init) && node.init.type !== "VariableDeclaration")
errors.push(new E(node, "ForStatement `init` member must be an expression or VariableDeclaration node or null"));
if (node.test != null && !isExpression(node.test))
errors.push(new E(node.test, "ForStatement `test` member must be an expression node or null"));
if (node.update != null && !isExpression(node.update))
errors.push(new E(node, "ForStatement `update` member must be an expression node or null"));
if (!isStatement(node.body))
errors.push(new E(node, "ForStatement `body` member must be a statement node"));
if (node.init != null)
[].push.apply(errors, recurse(node.init));
if (node.test != null)
[].push.apply(errors, recurse(node.test));
if (node.update != null)
[].push.apply(errors, recurse(node.update));
if (node.body != null)
[].push.apply(errors, errorsP(merge({}, state, {inIter: true}))(node.body));
break;
case "FunctionDeclaration":
if (node.id == null || node.id.type !== "Identifier")
errors.push(new E(node, "FunctionDeclaration `id` member must be an Identifier node"));
if (node.params == null)
errors.push(new E(node, "FunctionDeclaration `params` member must be non-null"));
else
[].push.apply(errors, concatMap(function(param) {
if (param == null)
return [new E(node, "FunctionDeclaration `params` member must not contain null values")];
else if (!isExpression(param))
return [new E(param, "FunctionDeclaration params must be expression nodes")];
return recurse(param);
}, node.params));
if (node.body == null || node.body.type !== "BlockStatement")
errors.push(new E(node, "FunctionDeclaration `body` member must be an BlockStatement node"));
if (node.id != null)
[].push.apply(errors, recurse(node.id));
if (node.body != null) {
recursionFn = errorsP(merge({}, state, {inFunc: true}));
if (node.body.type === "BlockStatement") {
strict = state.strict || any(function(d) { return d === "use strict"; }, directives(node.body.body));
if (strict && !state.strict)
recursionFn = errorsP(merge({}, state, {strict: true, inFunc: true}));
[].push.apply(errors, recursionFn({type: "Program", body: node.body.body}));
} else {
[].push.apply(errors, recursionFn(node.body));
}
}
break;
case "FunctionExpression":
if (node.id != null && node.id.type !== "Identifier")
errors.push(new E(node, "FunctionExpression `id` member must be an Identifier node or null"));
if (node.params == null)
errors.push(new E(node, "FunctionExpression `params` member must be non-null"));
else
[].push.apply(errors, concatMap(function(param) {
if (param == null)
return [new E(node, "FunctionExpression `params` member must not contain null values")];
else if (!isExpression(param))
return [new E(param, "FunctionExpression params must be expression nodes")];
return recurse(param);
}, node.params));
if (node.body == null || node.body.type !== "BlockStatement")
errors.push(new E(node, "FunctionExpression `body` member must be an BlockStatement node"));
if (node.id != null)
[].push.apply(errors, recurse(node.id));
if (node.body != null) {
recursionFn = errorsP(merge({}, state, {inFunc: true}));
if (node.body.type === "BlockStatement") {
strict = state.strict || any(function(d) { return d === "use strict"; }, directives(node.body.body));
if (strict && !state.strict)
recursionFn = errorsP(merge({}, state, {strict: true, inFunc: true}));
[].push.apply(errors, recursionFn({type: "Program", body: node.body.body}));
} else {
[].push.apply(errors, recursionFn(node.body));
}
}
break;
case "Identifier":
if (node.name == null)
errors.push(new E(node, "Identifier `name` member must be non-null"));
else if (!esutils.keyword.isIdentifierName(node.name))
errors.push(new E(node, "Identifier `name` member must be a valid IdentifierName"));
else if (esutils.keyword.isReservedWordES5(node.name, state.strict))
errors.push(new E(node, "Identifier `name` member must not be a ReservedWord"));
break;
case "IfStatement":
if (!isExpression(node.test))
errors.push(new E(node, "IfStatement `test` member must be an expression node"));
if (!isStatement(node.consequent))
errors.push(new E(node, "IfStatement `consequent` member must be a statement node"));
if (node.alternate != null && !isStatement(node.alternate))
errors.push(new E(node, "IfStatement `alternate` member must be a statement node or null"));
if (node.alternate != null && node.consequent != null && esutils.ast.isProblematicIfStatement(node))
errors.push(new E(node, "IfStatement with null `alternate` must not be the `consequent` of an IfStatement with a non-null `alternate`"));
if (node.test != null)
[].push.apply(errors, recurse(node.test));
if (node.consequent != null)
[].push.apply(errors, recurse(node.consequent));
if (node.alternate != null)
[].push.apply(errors, recurse(node.alternate));
break;
case "LabeledStatement":
if (node.label == null) {
errors.push(new E(node, "LabeledStatement `label` member must be an Identifier node"));
} else {
if (node.label.type !== "Identifier")
errors.push(new E(node, "LabeledStatement `label` member must be an Identifier node"));
else if (state.labels.indexOf(node.label.name) >= 0)
errors.push(new E(node, "LabeledStatement must not be nested within a LabeledStatement with the same label"));
[].push.apply(errors, recurse(node.label));
}
if (!isStatement(node.body))
errors.push(new E(node, "LabeledStatement `body` member must be a statement node"));
if (node.body != null) {
if (node.label != null)
[].push.apply(errors, errorsP(merge({}, state, {labels: state.labels.concat(node.label.name)}))(node.body));
else
[].push.apply(errors, recurse(node.body));
}
break;
case "Literal":
switch (getClass(node.value)) {
case "Boolean":
case "Null":
case "RegExp":
case "String":
break;
case "Number":
if (node.value !== node.value) {
errors.push(new E(node, "numeric Literal nodes must not be NaN"));
} else {
if (node.value < 0 || node.value === 0 && 1 / node.value < 0)
errors.push(new E(node, "numeric Literal nodes must be non-negative"));
if (!isFinite(node.value))
errors.push(new E(node, "numeric Literal nodes must be finite"));
}
break;
default:
errors.push(new E(node, "Literal nodes must have a boolean, null, regexp, string, or number as the `value` member"));
}
break;
case "LogicalExpression":
if (!isLogicalOperator(node.operator))
errors.push(new E(node, "LogicalExpression `operator` member must be one of " + JSON.stringify(LOGICAL_OPERATORS)));
if (!isExpression(node.left))
errors.push(new E(node, "LogicalExpression `left` member must be an expression node"));
if (!isExpression(node.right))
errors.push(new E(node, "LogicalExpression `right` member must be an expression node"));
if (node.left != null)
[].push.apply(errors, recurse(node.left));
if (node.right != null)
[].push.apply(errors, recurse(node.right));
break;
case "MemberExpression":
if (!isExpression(node.object))
errors.push(new E(node, "MemberExpression `object` member must be an expression node"));
if (node.computed) {
if (!isExpression(node.property))
errors.push(new E(node, "computed MemberExpression `property` member must be an expression node"));
if (node.property != null)
[].push.apply(errors, recurse(node.property));
} else if (node.property == null || node.property.type !== "Identifier") {
errors.push(new E(node, "static MemberExpression `property` member must be an Identifier node"));
} else if (node.property.name == null || !esutils.keyword.isIdentifierName(node.property.name)) {
errors.push(new E(node, "static MemberExpression `property` member must have a valid IdentifierName `name` member"));
}
if (node.object != null)
[].push.apply(errors, recurse(node.object));
break;
case "NewExpression":
if (!isExpression(node.callee))
errors.push(new E(node, "NewExpression `callee` member must be an expression node"));
if (node.arguments == null)
errors.push(new E(node, "NewExpression `arguments` member must be non-null"));
else
[].push.apply(errors, concatMap(function(arg) {
var es = [];
if (!isExpression(arg))
es.push(new E(arg != null ? arg : node, "NewExpression `arguments` member must only contain expression nodes"));
if (arg != null)
[].push.apply(es, recurse(arg));
return es;
}, node.arguments));
if (node.callee != null)
[].push.apply(errors, recurse(node.callee));
break;
case "ObjectExpression":
if (node.properties == null) {
errors.push(new E(node, "ObjectExpression `properties` member must be non-null"));
} else {
var initKeySet = {}, getKeySet = {}, setKeySet = {};
[].push.apply(errors, concatMap(function(property) {
var es = [], key;
if (property == null)
return [new E(node, "ObjectExpression `properties` must not contain null values")];
if (!isExpression(property.value))
es.push(new E(property, "ObjectExpression property `value` member must be an expression node"));
if (property.value != null)
[].push.apply(es, recurse(property.value));
switch (property.kind) {
case "init": break;
case "get":
if (property.value != null) {
if (property.value.type !== "FunctionExpression")
es.push(new E(property.value, "ObjectExpression getter property `value` member must be a FunctionExpression node"));
else if (property.value.params == null || property.value.params.length !== 0)
es.push(new E(property.value, "ObjectExpression getter property `value` member must have zero parameters"));
}
break;
case "set":
if (property.value != null) {
if (property.value.type !== "FunctionExpression")
es.push(new E(property.value, "ObjectExpression setter property `value` member must be a FunctionExpression node"));
else if (property.value.params == null || property.value.params.length !== 1)
es.push(new E(property.value, "ObjectExpression setter property `value` member must have exactly one parameter"));
}
break;
default:
es.push(new E(property, "ObjectExpression property `kind` member must be one of " + JSON.stringify(OBJECT_PROPERTY_KINDS)));
}
if (property.key == null) {
es.push(new E(property, "ObjectExpression property `key` member must be an Identifier or Literal node"));
} else {
switch (property.key.type) {
case "Identifier":
if (property.key.name == null || !esutils.keyword.isIdentifierName(property.key.name))
es.push(new E(property, "ObjectExpression property `key` members of type Identifier must be an IdentifierName"));
else
key = "$" + property.key.name;
break;
case "Literal":
if (["Number", "String"].indexOf(getClass(property.key.value)) < 0) {
es.push(new E(property, "ObjectExpression property `key` members of type Literal must have either a number or string `value` member"));
} else {
[].push.apply(es, recurse(property.key));
key = "$" + property.key.value;
}
break;
default:
es.push(new E(property, "ObjectExpression property `key` member must be an Identifier or Literal node"));
}
if (key != null)
switch (property.kind) {
case "init":
if (initKeySet.hasOwnProperty(key) && state.strict)
es.push(new E(property, "ObjectExpression must not have more than one data property with the same name in strict mode"));
if (getKeySet.hasOwnProperty(key))
es.push(new E(property, "ObjectExpression must not have data and getter properties with the same name"));
if (setKeySet.hasOwnProperty(key))
es.push(new E(property, "ObjectExpression must not have data and setter properties with the same name"));
initKeySet[key] = true;
break;
case "get":
if (initKeySet.hasOwnProperty(key))
es.push(new E(property, "ObjectExpression must not have data and getter properties with the same name"));
if (getKeySet.hasOwnProperty(key))
es.push(new E(property, "ObjectExpression must not have multiple getters with the same name"));
getKeySet[key] = true;
break;
case "set":
if (initKeySet.hasOwnProperty(key))
es.push(new E(property, "ObjectExpression must not have data and setter properties with the same name"));
if (setKeySet.hasOwnProperty(key))
es.push(new E(property, "ObjectExpression must not have multiple setters with the same name"));
setKeySet[key] = true;
break;
}
}
return es;
}, node.properties));
}
break;
case "Program":
if (node.body == null) {
errors.push(new E(node, "Program `body` member must be non-null"));
} else {
strict = state.strict || any(function(d) { return d === "use strict"; }, directives(node.body));
recursionFn = strict && !state.strict ? errorsP(merge({}, state, {strict: true})) : recurse;
[].push.apply(errors, concatMap(function(sourceElement) {
var es = [];
if (!isSourceElement(sourceElement))
es.push(new E(sourceElement != null ? sourceElement : node, "Program `body` member must only contain statement or function declaration nodes"));
if (sourceElement != null)
[].push.apply(es, recursionFn(sourceElement));
return es;
}, node.body));
}
break;
case "ReturnStatement":
if (!state.inFunc)
errors.push(new E(node, "ReturnStatement must be nested within a FunctionExpression or FunctionDeclaration node"));
if (node.argument != null) {
if (!isExpression(node.argument))
errors.push(new E(node, "ReturnStatement `argument` member must be an expression node or null"));
[].push.apply(errors, recurse(node.argument));
}
break;
case "SequenceExpression":
if (node.expressions == null) {
errors.push(new E(node, "SequenceExpression `expressions` member must be non-null"));
} else {
if (node.expressions.length < 2)
errors.push(new E(node, "SequenceExpression `expressions` member length must be >= 2"));
[].push.apply(errors, concatMap(function(expr) {
var es = [];
if (!isExpression(expr))
es.push(new E(expr != null ? expr : node, "SequenceExpression `expressions` member must only contain expression nodes"));
if (expr != null)
[].push.apply(es, recurse(expr));
return es;
}, node.expressions));
}
break;
case "SwitchCase":
if (node.test != null) {
if (!isExpression(node.test))
errors.push(new E(node, "SwitchCase `test` member must be an expression node or null"));
[].push.apply(errors, recurse(node.test));
}
if (node.consequent == null) {
errors.push(new E(node, "SwitchCase `consequent` member must be non-null"));
} else {
recursionFn = errorsP(merge({}, state, {inSwitch: true}));
[].push.apply(errors, concatMap(function(stmt) {
var es = [];
if (!isStatement(stmt))
es.push(new E(stmt != null ? stmt : node, "SwitchCase `consequent` member must only contain statement nodes"));
if (stmt != null)
[].push.apply(es, recursionFn(stmt));
return es;
}, node.consequent));
}
break;
case "SwitchStatement":
if (!isExpression(node.discriminant))
errors.push(new E(node, "SwitchStatement `discriminant` member must be an expression node"));
if (node.cases == null) {
errors.push(new E(node, "SwitchStatement `cases` member must be non-null"));
} else {
[].push.apply(errors, concatMap(function(switchCase) {
var es = [];
if (switchCase == null || switchCase.type !== "SwitchCase")
es.push(new E(switchCase != null ? switchCase : node, "SwitchStatement `cases` member must only contain SwitchCase nodes"));
if (switchCase != null)
[].push.apply(es, recurse(switchCase));
return es;
}, node.cases));
if (filter(node.cases, function(c) { return c != null && c.test == null; }).length > 1)
errors.push(new E(node, "SwitchStatement `cases` member must contain no more than one SwitchCase with a null `test` member"));
}
if (node.discriminant != null)
[].push.apply(errors, recurse(node.discriminant));
break;
case "ThisExpression":
break;
case "ThrowStatement":
if (!isExpression(node.argument))
errors.push(new E(node, "ThrowStatement `argument` member must be an expression node"));
if (node.argument != null)
[].push.apply(errors, recurse(node.argument));
break;
case "TryStatement":
// NOTE: TryStatement interface changed from {handlers: [CatchClause]} to {handler: CatchClause}; we support both
var handlers = node.handlers || (node.handler ? [node.handler] : []);
if (node.block == null || node.block.type !== "BlockStatement")
errors.push(new E(node.block != null ? node.block : node, "TryStatement `block` member must be a BlockStatement node"));
if (node.finalizer != null && node.finalizer.type !== "BlockStatement")
errors.push(new E(node.finalizer, "TryStatement `finalizer` member must be a BlockStatement node"));
[].push.apply(errors, concatMap(function(handler) {
var es = [];
if (handler == null || handler.type !== "CatchClause")
es.push(new E(handler != null ? handler : node, "TryStatement `handler` member must be a CatchClause node"));
if (handler != null)
[].push.apply(es, recurse(handler));
return es;
}, handlers));
if (node.block != null)
[].push.apply(errors, recurse(node.block));
if (node.finalizer != null)
[].push.apply(errors, recurse(node.finalizer));
if (handlers.length < 1 && node.finalizer == null)
errors.push(new E(node, "TryStatement must have a non-null `handler` member or a non-null `finalizer` member"));
break;
case "UnaryExpression":
if (!isUnaryOperator(node.operator))
errors.push(new E(node, "UnaryExpression `operator` member must be one of " + JSON.stringify(UNARY_OPERATORS)));
if (!isExpression(node.argument))
errors.push(new E(node, "UnaryExpression `argument` member must be an expression node"));
if (node.argument != null) {
[].push.apply(errors, recurse(node.argument));
if (state.strict && node.operator === "delete" && node.argument.type === "Identifier")
errors.push(new E(node, "`delete` with unqualified identifier not allowed in strict mode"));
}
break;
case "UpdateExpression":
if (!isUpdateOperator(node.operator))
errors.push(new E(node, "UpdateExpression `operator` member must be one of " + JSON.stringify(UNARY_OPERATORS)));
if (!isExpression(node.argument))
errors.push(new E(node, "UpdateExpression `argument` member must be an expression node"));
if (node.argument != null)
[].push.apply(errors, recurse(node.argument));
break;
case "VariableDeclaration":
if (node.declarations == null) {
errors.push(new E(node, "VariableDeclaration `declarations` member must be non-null"));
} else {
if (node.declarations.length < 1)
errors.push(new E(node, "VariableDeclaration `declarations` member must be non-empty"));
if (VARIABLE_DECLARATION_KINDS.indexOf(node.kind) < 0)
errors.push(new E(node, "VariableDeclaration `kind` member must be one of " + JSON.stringify(VARIABLE_DECLARATION_KINDS)));
[].push.apply(errors, concatMap(function(decl) {
var es = [];
if (decl == null || decl.type !== "VariableDeclarator")
es.push(new E(decl != null ? decl : node, "VariableDeclaration `declarations` member must contain only VariableDeclarator nodes"));
if (decl != null)
[].push.apply(es, recurse(decl));
return es;
}, node.declarations));
}
break;
case "VariableDeclarator":
if (!isExpression(node.id))
errors.push(new E(node, "VariableDeclarator `id` member must be an expression node"));
if (node.init != null) {
if (!isExpression(node.init))
errors.push(new E(node, "VariableDeclarator `init` member must be an expression node or null"));
[].push.apply(errors, recurse(node.init));
}
if (node.id != null)
[].push.apply(errors, recurse(node.id));
break;
case "WhileStatement":
if (!isExpression(node.test))
errors.push(new E(node, "WhileStatement `test` member must be an expression node"));
if (!isStatement(node.body))
errors.push(new E(node, "WhileStatement `body` member must be a statement node"));
if (node.test != null)
[].push.apply(errors, recurse(node.test));
if (node.body != null)
[].push.apply(errors, errorsP(merge({}, state, {inIter: true}))(node.body));
break;
case "WithStatement":
if (state.strict)
errors.push(new E(node, "WithStatement not allowed in strict mode"));
if (!isExpression(node.object))
errors.push(new E(node, "WithStatement `object` member must be an expression node"));
if (!isStatement(node.body))
errors.push(new E(node, "WithStatement `body` member must be a statement node"));
if (node.object != null)
[].push.apply(errors, recurse(node.object));
if (node.body != null)
[].push.apply(errors, errorsP(merge({}, state, {inIter: true}))(node.body));
break;
default:
switch (getClass(node.type)) {
case "String":
errors.push(new E(node, "unrecognised node type: " + JSON.stringify(node.type)));
break;
case "Null":
case "Undefined":
errors.push(new E(node, "all AST nodes must have a `type` member"));
break;
default:
errors.push(new E(node, "AST node `type` must be a string"));
}
}
return errors;
};
}
var START_STATE = {labels: [], inFunc: false, inIter: false, inSwitch: false, strict: false};
module.exports = {
// isValid :: Maybe Node -> Boolean
isValid: function isValid(node) {
return node != null && node.type === "Program" &&
errorsP(START_STATE)(node).length < 1;
},
// isValidExpression :: Maybe Node -> Boolean
isValidExpression: function isValidExpression(node) {
return isExpression(node) && errorsP(START_STATE)(node).length < 1;
},
// InvalidAstError :: Node -> String -> InvalidAstError
InvalidAstError: InvalidAstError,
// errors :: Maybe Node -> [InvalidAstError]
errors: function errors(node) {
var errors = [];
if (node == null) {
errors.push(new E(node, "given AST node should be non-null"));
} else {
if (node.type !== "Program")
errors.push(new E(node, "given AST node should be of type Program"));
[].push.apply(errors, errorsP(START_STATE)(node));
}
return errors;
}
};