├── .editorconfig ├── .eslintrc ├── .gitignore ├── .travis.yml ├── Makefile ├── README.md ├── index.js ├── package.json ├── test-helpers.js └── test ├── libraries.js ├── meta.js ├── strict.js ├── types.js └── unit.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "node": true 4 | }, 5 | 6 | "rules": { 7 | "curly": [0, "multi"], 8 | "eqeqeq": [2, "smart"], 9 | "max-depth": [1, 4], 10 | "max-params": [1, 4], 11 | "new-cap": 2, 12 | "new-parens": 0, 13 | "no-constant-condition": 1, 14 | "no-div-regex": 1, 15 | "no-else-return": 1, 16 | "no-extra-parens": 1, 17 | "no-floating-decimal": 2, 18 | "no-inner-declarations": 2, 19 | "no-lonely-if": 2, 20 | "no-nested-ternary": 2, 21 | "no-underscore-dangle": 2, 22 | "no-use-before-define": 2, 23 | "quotes": [2, "double"], 24 | "radix": 2, 25 | "space-after-keywords": [2, "always"], 26 | "space-in-brackets": [2, "never"], 27 | "space-unary-word-ops": 2, 28 | "strict": 2, 29 | "wrap-iife": 2 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /bower_components/ 2 | /node_modules/ 3 | /lib/ 4 | /coverage/ 5 | /output/ 6 | /tmp/ 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.8" 4 | - "0.10" 5 | - "0.11" 6 | before_install: 7 | - "npm install npm -g" 8 | - "git submodule update --init" 9 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | default: build 2 | all: build lint test 3 | 4 | ESLINT = node_modules/.bin/eslint 5 | ISTANBUL = node_modules/.bin/istanbul 6 | MOCHA = node_modules/.bin/_mocha 7 | MOCHA_OPTS = --inline-diffs --check-leaks -u tdd -R dot 8 | XYZ = node_modules/.bin/xyz --repo git@github.com:michaelficarra/esvalid.git 9 | 10 | build: 11 | # nothing to build yet 12 | 13 | .PHONY: default all build release-major release-minor release-patch test lint clean 14 | release-major: lint test 15 | $(XYZ) --increment major 16 | release-minor: lint test 17 | $(XYZ) --increment minor 18 | release-patch: lint test 19 | $(XYZ) --increment patch 20 | 21 | test: build 22 | $(ISTANBUL) cover $(MOCHA) -- $(MOCHA_OPTS) -- test/*.js 23 | 24 | lint: build 25 | $(ESLINT) -- index.js test-helpers.js test/*.js 26 | 27 | clean: 28 | rm -rf lib 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # esvalid 2 | 3 | ## Install 4 | 5 | npm install esvalid 6 | 7 | ## Usage 8 | 9 | #### `esvalid.isValid(node)` :: Spidermonkey AST Node → Boolean 10 | 11 | Returns true if and only if the given AST node represents a valid ECMAScript 12 | program. 13 | 14 | #### `esvalid.isValidExpression(node)` :: Spidermonkey AST Node → Boolean 15 | 16 | Returns true if and only if the given AST node represents a valid ECMAScript 17 | expression. 18 | 19 | #### `esvalid.errors(node)` :: Spidermonkey AST Node → [InvalidAstError] 20 | 21 | Returns an array of `InvalidAstError` objects representing the errors in the 22 | given AST. An effort is made to continue collecting errors in the face of 23 | malformed ASTs. If an empty array is returned, it is implied that the given AST 24 | node is error free. 25 | 26 | #### `new esvalid.InvalidAstError(node, message)` :: Node -> String -> InvalidAstError 27 | 28 | Constructs a new `InvalidAstError` instance. `node` must be non-null. 29 | 30 | ##### Example 31 | 32 | ``` 33 | var esvalid = require("esvalid"); 34 | var esprima = require("esprima"); 35 | 36 | var program = esprima.parse(fs.readFileSync(require.resolve("esprima"))); 37 | esvalid.isValid(program); // true 38 | 39 | esvalid.isValid({type: "Program", body: []}); // true 40 | esvalid.isValid({type: "Program", body: null}); // false 41 | 42 | esvalid.isValidExpression({type: "Program", body: []}); // false 43 | esvalid.isValidExpression({type: "Literal", value: 0}); // true 44 | 45 | esvalid.errors({type: "Program", body: []}); // [] 46 | var error = esvalid.errors({type: "Program", body: null})[0]; 47 | error instanceof esvalid.InvalidAstError; // true 48 | error.node; // {type: "Program", body: null} 49 | error.message; // "Program `body` member must be non-null" 50 | ``` 51 | 52 | ## Validity Tests 53 | 54 | This is a list of all esvalid validity tests other than `null` tests and type checks. 55 | 56 | * BreakStatement must have an IterationStatement or SwitchStatement as an ancestor 57 | * labelled BreakStatement must have a matching LabeledStatement ancestor 58 | * CatchClause `param` member must not be `eval` or `arguments` in strict mode 59 | * ContinueStatement must have an IterationStatement as an ancestor 60 | * labelled ContinueStatement must have a matching LabeledStatement ancestor 61 | * FunctionDeclaration `id` member must not be `eval` or `arguments` in strict mode 62 | * FunctionExpression `id` member must not be `eval` or `arguments` in strict mode 63 | * FunctionDeclaration parameter names must be unique in strict mode 64 | * FunctionExpression parameter names must be unique in strict mode 65 | * Identifier `name` member must be a valid IdentifierName 66 | * Identifier `name` member must not be a ReservedWord 67 | * IfStatement with null `alternate` must not be the `consequent` of an IfStatement with a non-null `alternate` 68 | * LabeledStatement must not be nested within a LabeledStatement with the same label 69 | * numeric Literal nodes must not be NaN 70 | * numeric Literal nodes must be non-negative 71 | * numeric Literal nodes must be finite 72 | * static MemberExpression `property` member must have a valid IdentifierName `name` member 73 | * ObjectExpression getter property `value` member must have zero parameters 74 | * ObjectExpression setter property `value` member must have exactly one parameter 75 | * ObjectExpression must not have more than one data property with the same name in strict mode 76 | * ObjectExpression must not have data and getter properties with the same name 77 | * ObjectExpression must not have data and setter properties with the same name 78 | * ObjectExpression must not have data and getter properties with the same name 79 | * ObjectExpression must not have multiple getters with the same name 80 | * ObjectExpression must not have data and setter properties with the same name 81 | * ObjectExpression must not have multiple setters with the same name 82 | * ReturnStatement must be nested within a FunctionExpression or FunctionDeclaration node 83 | * SequenceExpression `expressions` member length must be >= 2 84 | * SwitchStatement `cases` member must contain no more than one SwitchCase with a null `test` member 85 | * TryStatement must have a non-null `handler` member or a non-null `finalizer` member 86 | * `delete` with unqualified identifier not allowed in strict mode 87 | * VariableDeclaration `declarations` member must be non-empty 88 | * WithStatement not allowed in strict mode 89 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var esutils = require("esutils"); 3 | var merge = Object.assign || require("object-assign"); 4 | 5 | // getClass :: Object -> String 6 | function getClass(obj) { 7 | return {}.toString.call(obj).slice(8, -1); 8 | } 9 | 10 | // any :: forall a. [a] -> (a -> Boolean) -> Boolean 11 | function any(predicate, xs) { 12 | for (var i = 0, l = xs.length; i < l; ++i) { 13 | if (predicate(xs[i])) return true; 14 | } 15 | return false; 16 | } 17 | 18 | // concatMap :: forall a b. -> (a -> [b]) -> [a] -> [b] 19 | function concatMap(fn, xs) { 20 | var result = []; 21 | for (var i = 0, l = xs.length; i < l; ++i) { 22 | [].push.apply(result, fn(xs[i])); 23 | } 24 | return result; 25 | } 26 | 27 | // filter :: forall a. (a -> Boolean) -> [a] -> [a] 28 | function filter(xs, predicate) { 29 | var filtered = []; 30 | for (var i = 0, l = xs.length; i < l; ++i) { 31 | if (predicate(xs[i])) filtered.push(xs[i]); 32 | } 33 | return filtered; 34 | } 35 | 36 | // isExpression :: Node -> Boolean 37 | var isExpression = esutils.ast.isExpression; 38 | // isStatement :: Node -> Boolean 39 | var isStatement = esutils.ast.isStatement; 40 | // isSourceElement :: Node -> Boolean 41 | var isSourceElement = esutils.ast.isSourceElement; 42 | // directives :: [Maybe Node] -> [Node] 43 | function directives(stmts) { 44 | if (stmts && stmts.length > 0) { 45 | var s = stmts[0]; 46 | if (s && s.type === "ExpressionStatement" && s.expression && s.expression.type === "Literal" && typeof s.expression.value === "string") 47 | return [s.expression.value].concat(directives([].slice.call(stmts, 1))); 48 | } 49 | return []; 50 | } 51 | 52 | var OBJECT_PROPERTY_KINDS = ["init", "get", "set"]; 53 | var VARIABLE_DECLARATION_KINDS = ["var", "let", "const"]; 54 | 55 | var ASSIGNMENT_OPERATORS = ["=", "+=", "-=", "*=", "/=", "%=", "<<=", ">>=", ">>>=", "|=", "^=", "&="]; 56 | var BINARY_OPERATORS = ["==", "!=", "===", "!==", "<", "<=", ">", ">=", "<<", ">>", ">>>", "+", "-", "*", "/", "%", "|", "^", "&", "in", "instanceof"]; 57 | var LOGICAL_OPERATORS = ["||", "&&"]; 58 | var UNARY_OPERATORS = ["-", "+", "!", "~", "typeof", "void", "delete"]; 59 | var UPDATE_OPERATORS = ["++", "--"]; 60 | 61 | // isAssignmentOperator :: String -> Boolean 62 | function isAssignmentOperator(op) { return ASSIGNMENT_OPERATORS.indexOf(op) >= 0; } 63 | // isBinaryOperator :: String -> Boolean 64 | function isBinaryOperator(op) { return BINARY_OPERATORS.indexOf(op) >= 0; } 65 | // isLogicalOperator :: String -> Boolean 66 | function isLogicalOperator(op) { return LOGICAL_OPERATORS.indexOf(op) >= 0; } 67 | // isUnaryOperator :: String -> Boolean 68 | function isUnaryOperator(op) { return UNARY_OPERATORS.indexOf(op) >= 0; } 69 | // isUpdateOperator :: String -> Boolean 70 | function isUpdateOperator(op) { return UPDATE_OPERATORS.indexOf(op) >= 0; } 71 | 72 | 73 | var E, InvalidAstError = E = (function() { 74 | function C(){} 75 | C.prototype = Error.prototype; 76 | function InvalidAstError(node, message) { 77 | Error.call(this); 78 | this.node = node; 79 | this.message = message; 80 | } 81 | InvalidAstError.prototype = new C; 82 | InvalidAstError.prototype.constructor = InvalidAstError; 83 | InvalidAstError.prototype.name = "InvalidAstError"; 84 | return InvalidAstError; 85 | }()); 86 | 87 | 88 | // errorsP :: {labels :: [Label], inFunc :: Boolean, inIter :: Boolean, inSwitch :: Boolean} -> Node -> [InvalidAstError] 89 | function errorsP(state) { 90 | return function recurse(node) { 91 | var errors = [], line, column, strict, recursionFn; 92 | var paramSet, initKeySet, getKeySet, setKeySet; 93 | 94 | if (node.loc != null) { 95 | if (node.loc.source != null && typeof node.loc.source !== "string") 96 | errors.push(new E(node, "`loc.source` must be a string or null")); 97 | if (node.loc.start == null) { 98 | errors.push(new E(node, "`loc.start` must be non-null if `loc` is non-null")); 99 | } else { 100 | line = node.loc.start.line; 101 | column = node.loc.start.column; 102 | if (typeof line !== "number" || line % 1 !== 0 || line < 1) 103 | errors.push(new E(node, "`loc.start.line` must be a positive integer")); 104 | if (typeof column !== "number" || column % 1 !== 0 || column < 0) 105 | errors.push(new E(node, "`loc.start.column` must be a non-negative integer")); 106 | } 107 | if (node.loc.end == null) { 108 | errors.push(new E(node, "`loc.end` must be non-null if `loc` is non-null")); 109 | } else { 110 | line = node.loc.end.line; 111 | column = node.loc.end.column; 112 | if (typeof line !== "number" || line % 1 !== 0 || line < 1) 113 | errors.push(new E(node, "`loc.end.line` must be a positive integer")); 114 | if (typeof column !== "number" || column % 1 !== 0 || column < 0) 115 | errors.push(new E(node, "`loc.end.column` must be a non-negative integer")); 116 | } 117 | } 118 | 119 | switch (node.type) { 120 | 121 | case "ArrayExpression": 122 | if (node.elements == null) 123 | errors.push(new E(node, "ArrayExpression `elements` member must be non-null")); 124 | else 125 | [].push.apply(errors, concatMap(function(element) { 126 | if (element == null) 127 | return []; 128 | else if (!isExpression(element)) 129 | return [new E(element, "non-null ArrayExpression elements must be expression nodes")]; 130 | return recurse(element); 131 | }, node.elements)); 132 | break; 133 | 134 | case "AssignmentExpression": 135 | if (!isAssignmentOperator(node.operator)) 136 | errors.push(new E(node, "AssignmentExpression `operator` member must be one of " + JSON.stringify(ASSIGNMENT_OPERATORS))); 137 | if (!isExpression(node.left)) 138 | errors.push(new E(node, "AssignmentExpression `left` member must be an expression node")); 139 | if (!isExpression(node.right)) 140 | errors.push(new E(node, "AssignmentExpression `right` member must be an expression node")); 141 | if (node.left != null) 142 | [].push.apply(errors, recurse(node.left)); 143 | if (node.right != null) 144 | [].push.apply(errors, recurse(node.right)); 145 | break; 146 | 147 | case "BinaryExpression": 148 | if (!isBinaryOperator(node.operator)) 149 | errors.push(new E(node, "BinaryExpression `operator` member must be one of " + JSON.stringify(BINARY_OPERATORS))); 150 | if (!isExpression(node.left)) 151 | errors.push(new E(node, "BinaryExpression `left` member must be an expression node")); 152 | if (!isExpression(node.right)) 153 | errors.push(new E(node, "BinaryExpression `right` member must be an expression node")); 154 | if (node.left != null) 155 | [].push.apply(errors, recurse(node.left)); 156 | if (node.right != null) 157 | [].push.apply(errors, recurse(node.right)); 158 | break; 159 | 160 | case "BlockStatement": 161 | if (node.body == null) 162 | errors.push(new E(node, "BlockStatement `body` member must be non-null")); 163 | else 164 | [].push.apply(errors, concatMap(function(stmt) { 165 | var es = []; 166 | if (!isStatement(stmt)) 167 | es.push(new E(stmt != null ? stmt : node, "BlockStatement `body` member must only contain statement nodes")); 168 | if (stmt != null) 169 | [].push.apply(es, recurse(stmt)); 170 | return es; 171 | }, node.body)); 172 | break; 173 | 174 | case "BreakStatement": 175 | if (!state.inIter && !state.inSwitch) 176 | errors.push(new E(node, "BreakStatement must have an IterationStatement or SwitchStatement as an ancestor")); 177 | if (node.label != null) { 178 | if (node.label.type !== "Identifier") 179 | errors.push(new E(node.label, "BreakStatement `label` member must be an Identifier node")); 180 | else if (state.labels.indexOf(node.label.name) < 0) 181 | errors.push(new E(node.label, "labelled BreakStatement must have a matching LabeledStatement ancestor")); 182 | [].push.apply(errors, recurse(node.label)); 183 | } 184 | break; 185 | 186 | case "CallExpression": 187 | if (!isExpression(node.callee)) 188 | errors.push(new E(node, "CallExpression `callee` member must be an expression node")); 189 | if (node.arguments == null) 190 | errors.push(new E(node, "CallExpression `arguments` member must be non-null")); 191 | else 192 | [].push.apply(errors, concatMap(function(arg) { 193 | var es = []; 194 | if (!isExpression(arg)) 195 | es.push(new E(arg != null ? arg : node, "CallExpression `arguments` member must only contain expression nodes")); 196 | if (arg != null) 197 | [].push.apply(es, recurse(arg)); 198 | return es; 199 | }, node.arguments)); 200 | if (node.callee != null) 201 | [].push.apply(errors, recurse(node.callee)); 202 | break; 203 | 204 | case "CatchClause": 205 | if (!isExpression(node.param)) 206 | errors.push(new E(node, "CatchClause `param` member must be an expression node")); 207 | else if (state.strict && node.param.type === "Identifier" && esutils.keyword.isRestrictedWord(node.param.name)) 208 | errors.push(new E(node, "CatchClause `param` member must not be `eval` or `arguments` in strict mode")); 209 | if (node.body == null || node.body.type !== "BlockStatement") 210 | errors.push(new E(node, "CatchClause `body` member must be a BlockStatement node")); 211 | if (node.param != null) 212 | [].push.apply(errors, recurse(node.param)); 213 | if (node.body != null) 214 | [].push.apply(errors, recurse(node.body)); 215 | break; 216 | 217 | case "ConditionalExpression": 218 | if (!isExpression(node.test)) 219 | errors.push(new E(node, "ConditionalExpression `test` member must be an expression node")); 220 | if (!isExpression(node.alternate)) 221 | errors.push(new E(node, "ConditionalExpression `alternate` member must be an expression node")); 222 | if (!isExpression(node.consequent)) 223 | errors.push(new E(node, "ConditionalExpression `consequent` member must be an expression node")); 224 | if (node.test != null) 225 | [].push.apply(errors, recurse(node.test)); 226 | if (node.alternate != null) 227 | [].push.apply(errors, recurse(node.alternate)); 228 | if (node.consequent != null) 229 | [].push.apply(errors, recurse(node.consequent)); 230 | break; 231 | 232 | case "ContinueStatement": 233 | if (!state.inIter) 234 | errors.push(new E(node, "ContinueStatement must have an IterationStatement as an ancestor")); 235 | if (node.label != null) { 236 | if (node.label.type !== "Identifier") 237 | errors.push(new E(node.label, "ContinueStatement `label` member must be an Identifier node")); 238 | else if (state.labels.indexOf(node.label.name) < 0) 239 | errors.push(new E(node.label, "labelled ContinueStatement must have a matching LabeledStatement ancestor")); 240 | [].push.apply(errors, recurse(node.label)); 241 | } 242 | break; 243 | 244 | case "DebuggerStatement": 245 | break; 246 | 247 | case "DoWhileStatement": 248 | if (!isStatement(node.body)) 249 | errors.push(new E(node, "DoWhileStatement `body` member must be a statement node")); 250 | if (!isExpression(node.test)) 251 | errors.push(new E(node, "DoWhileStatement `test` member must be an expression node")); 252 | if (node.body != null) 253 | [].push.apply(errors, errorsP(merge({}, state, {inIter: true}))(node.body)); 254 | if (node.test != null) 255 | [].push.apply(errors, recurse(node.test)); 256 | break; 257 | 258 | case "EmptyStatement": 259 | break; 260 | 261 | case "ExpressionStatement": 262 | if (!isExpression(node.expression)) 263 | errors.push(new E(node, "ExpressionStatement `expression` member must be an expression node")); 264 | if (node.expression != null) 265 | [].push.apply(errors, recurse(node.expression)); 266 | break; 267 | 268 | case "ForInStatement": 269 | if (node.left == null || !isExpression(node.left) && node.left.type !== "VariableDeclaration") 270 | errors.push(new E(node, "ForInStatement `left` member must be an expression or VariableDeclaration node")); 271 | if (!isExpression(node.right)) 272 | errors.push(new E(node, "ForInStatement `right` member must be an expression node")); 273 | if (!isStatement(node.body)) 274 | errors.push(new E(node, "ForInStatement `body` member must be a statement node")); 275 | if (node.left != null) 276 | [].push.apply(errors, recurse(node.left)); 277 | if (node.right != null) 278 | [].push.apply(errors, recurse(node.right)); 279 | if (node.body != null) 280 | [].push.apply(errors, errorsP(merge({}, state, {inIter: true}))(node.body)); 281 | break; 282 | 283 | case "ForStatement": 284 | if (node.init != null && !isExpression(node.init) && node.init.type !== "VariableDeclaration") 285 | errors.push(new E(node, "ForStatement `init` member must be an expression or VariableDeclaration node or null")); 286 | if (node.test != null && !isExpression(node.test)) 287 | errors.push(new E(node.test, "ForStatement `test` member must be an expression node or null")); 288 | if (node.update != null && !isExpression(node.update)) 289 | errors.push(new E(node, "ForStatement `update` member must be an expression node or null")); 290 | if (!isStatement(node.body)) 291 | errors.push(new E(node, "ForStatement `body` member must be a statement node")); 292 | if (node.init != null) 293 | [].push.apply(errors, recurse(node.init)); 294 | if (node.test != null) 295 | [].push.apply(errors, recurse(node.test)); 296 | if (node.update != null) 297 | [].push.apply(errors, recurse(node.update)); 298 | if (node.body != null) 299 | [].push.apply(errors, errorsP(merge({}, state, {inIter: true}))(node.body)); 300 | break; 301 | 302 | case "FunctionDeclaration": 303 | paramSet = {}; 304 | if (node.id == null || node.id.type !== "Identifier") 305 | errors.push(new E(node, "FunctionDeclaration `id` member must be an Identifier node")); 306 | else if (state.strict && esutils.keyword.isRestrictedWord(node.id.name)) 307 | errors.push(new E(node, "FunctionDeclaration `id` member must not be `eval` or `arguments` in strict mode")); 308 | if (node.params == null) 309 | errors.push(new E(node, "FunctionDeclaration `params` member must be non-null")); 310 | else 311 | [].push.apply(errors, concatMap(function(param) { 312 | var es = []; 313 | if (param == null) 314 | return [new E(node, "FunctionDeclaration `params` member must not contain null values")]; 315 | if (!isExpression(param)) { 316 | es.push(new E(param, "FunctionDeclaration params must be expression nodes")); 317 | } else if (state.strict && param.type === "Identifier") { 318 | if (paramSet.hasOwnProperty("$" + param.name)) 319 | es.push(new E(param, "FunctionDeclaration parameter names must be unique in strict mode")); 320 | else 321 | paramSet["$" + param.name] = true; 322 | } 323 | [].push.apply(es, recurse(param)); 324 | return es; 325 | }, node.params)); 326 | if (node.body == null || node.body.type !== "BlockStatement") 327 | errors.push(new E(node, "FunctionDeclaration `body` member must be an BlockStatement node")); 328 | if (node.id != null) 329 | [].push.apply(errors, recurse(node.id)); 330 | if (node.body != null) { 331 | recursionFn = errorsP(merge({}, state, {inFunc: true})); 332 | if (node.body.type === "BlockStatement") { 333 | strict = state.strict || any(function(d) { return d === "use strict"; }, directives(node.body.body)); 334 | if (strict && !state.strict) 335 | recursionFn = errorsP(merge({}, state, {strict: true, inFunc: true})); 336 | [].push.apply(errors, recursionFn({type: "Program", body: node.body.body})); 337 | } else { 338 | [].push.apply(errors, recursionFn(node.body)); 339 | } 340 | } 341 | break; 342 | 343 | case "FunctionExpression": 344 | paramSet = {}; 345 | if (node.id != null) { 346 | if (node.id.type !== "Identifier") 347 | errors.push(new E(node, "FunctionExpression `id` member must be an Identifier node or null")); 348 | else if (state.strict && esutils.keyword.isRestrictedWord(node.id.name)) 349 | errors.push(new E(node, "FunctionExpression `id` member must not be `eval` or `arguments` in strict mode")); 350 | } 351 | if (node.params == null) 352 | errors.push(new E(node, "FunctionExpression `params` member must be non-null")); 353 | else 354 | [].push.apply(errors, concatMap(function(param) { 355 | var es = []; 356 | if (param == null) 357 | return [new E(node, "FunctionExpression `params` member must not contain null values")]; 358 | if (!isExpression(param)) { 359 | es.push(new E(param, "FunctionExpression params must be expression nodes")); 360 | } else if (state.strict && param.type === "Identifier") { 361 | if (paramSet.hasOwnProperty("$" + param.name)) 362 | es.push(new E(param, "FunctionExpression parameter names must be unique in strict mode")); 363 | else 364 | paramSet["$" + param.name] = true; 365 | } 366 | [].push.apply(es, recurse(param)); 367 | return es; 368 | }, node.params)); 369 | if (node.body == null || node.body.type !== "BlockStatement") 370 | errors.push(new E(node, "FunctionExpression `body` member must be an BlockStatement node")); 371 | if (node.id != null) 372 | [].push.apply(errors, recurse(node.id)); 373 | if (node.body != null) { 374 | recursionFn = errorsP(merge({}, state, {inFunc: true})); 375 | if (node.body.type === "BlockStatement") { 376 | strict = state.strict || any(function(d) { return d === "use strict"; }, directives(node.body.body)); 377 | if (strict && !state.strict) 378 | recursionFn = errorsP(merge({}, state, {strict: true, inFunc: true})); 379 | [].push.apply(errors, recursionFn({type: "Program", body: node.body.body})); 380 | } else { 381 | [].push.apply(errors, recursionFn(node.body)); 382 | } 383 | } 384 | break; 385 | 386 | case "Identifier": 387 | if (node.name == null) 388 | errors.push(new E(node, "Identifier `name` member must be non-null")); 389 | else if (!esutils.keyword.isIdentifierName(node.name)) 390 | errors.push(new E(node, "Identifier `name` member must be a valid IdentifierName")); 391 | else if (esutils.keyword.isReservedWordES5(node.name, state.strict)) 392 | errors.push(new E(node, "Identifier `name` member must not be a ReservedWord")); 393 | break; 394 | 395 | case "IfStatement": 396 | if (!isExpression(node.test)) 397 | errors.push(new E(node, "IfStatement `test` member must be an expression node")); 398 | if (!isStatement(node.consequent)) 399 | errors.push(new E(node, "IfStatement `consequent` member must be a statement node")); 400 | if (node.alternate != null && !isStatement(node.alternate)) 401 | errors.push(new E(node, "IfStatement `alternate` member must be a statement node or null")); 402 | if (node.alternate != null && node.consequent != null && esutils.ast.isProblematicIfStatement(node)) 403 | errors.push(new E(node, "IfStatement with null `alternate` must not be the `consequent` of an IfStatement with a non-null `alternate`")); 404 | if (node.test != null) 405 | [].push.apply(errors, recurse(node.test)); 406 | if (node.consequent != null) 407 | [].push.apply(errors, recurse(node.consequent)); 408 | if (node.alternate != null) 409 | [].push.apply(errors, recurse(node.alternate)); 410 | break; 411 | 412 | case "LabeledStatement": 413 | if (node.label == null) { 414 | errors.push(new E(node, "LabeledStatement `label` member must be an Identifier node")); 415 | } else { 416 | if (node.label.type !== "Identifier") 417 | errors.push(new E(node, "LabeledStatement `label` member must be an Identifier node")); 418 | else if (state.labels.indexOf(node.label.name) >= 0) 419 | errors.push(new E(node, "LabeledStatement must not be nested within a LabeledStatement with the same label")); 420 | [].push.apply(errors, recurse(node.label)); 421 | } 422 | if (!isStatement(node.body)) 423 | errors.push(new E(node, "LabeledStatement `body` member must be a statement node")); 424 | if (node.body != null) { 425 | if (node.label != null) 426 | [].push.apply(errors, errorsP(merge({}, state, {labels: state.labels.concat(node.label.name)}))(node.body)); 427 | else 428 | [].push.apply(errors, recurse(node.body)); 429 | } 430 | break; 431 | 432 | case "Literal": 433 | switch (getClass(node.value)) { 434 | case "Boolean": 435 | case "Null": 436 | case "RegExp": 437 | case "String": 438 | break; 439 | case "Number": 440 | if (node.value !== node.value) 441 | errors.push(new E(node, "numeric Literal nodes must not be NaN")); 442 | else if (node.value < 0 || node.value === 0 && 1 / node.value < 0) 443 | errors.push(new E(node, "numeric Literal nodes must be non-negative")); 444 | break; 445 | default: 446 | errors.push(new E(node, "Literal nodes must have a boolean, null, regexp, string, or number as the `value` member")); 447 | } 448 | break; 449 | 450 | case "LogicalExpression": 451 | if (!isLogicalOperator(node.operator)) 452 | errors.push(new E(node, "LogicalExpression `operator` member must be one of " + JSON.stringify(LOGICAL_OPERATORS))); 453 | if (!isExpression(node.left)) 454 | errors.push(new E(node, "LogicalExpression `left` member must be an expression node")); 455 | if (!isExpression(node.right)) 456 | errors.push(new E(node, "LogicalExpression `right` member must be an expression node")); 457 | if (node.left != null) 458 | [].push.apply(errors, recurse(node.left)); 459 | if (node.right != null) 460 | [].push.apply(errors, recurse(node.right)); 461 | break; 462 | 463 | case "MemberExpression": 464 | if (!isExpression(node.object)) 465 | errors.push(new E(node, "MemberExpression `object` member must be an expression node")); 466 | if (node.computed) { 467 | if (!isExpression(node.property)) 468 | errors.push(new E(node, "computed MemberExpression `property` member must be an expression node")); 469 | if (node.property != null) 470 | [].push.apply(errors, recurse(node.property)); 471 | } else if (node.property == null || node.property.type !== "Identifier") { 472 | errors.push(new E(node, "static MemberExpression `property` member must be an Identifier node")); 473 | } else if (node.property.name == null || !esutils.keyword.isIdentifierName(node.property.name)) { 474 | errors.push(new E(node, "static MemberExpression `property` member must have a valid IdentifierName `name` member")); 475 | } 476 | if (node.object != null) 477 | [].push.apply(errors, recurse(node.object)); 478 | break; 479 | 480 | case "NewExpression": 481 | if (!isExpression(node.callee)) 482 | errors.push(new E(node, "NewExpression `callee` member must be an expression node")); 483 | if (node.arguments == null) 484 | errors.push(new E(node, "NewExpression `arguments` member must be non-null")); 485 | else 486 | [].push.apply(errors, concatMap(function(arg) { 487 | var es = []; 488 | if (!isExpression(arg)) 489 | es.push(new E(arg != null ? arg : node, "NewExpression `arguments` member must only contain expression nodes")); 490 | if (arg != null) 491 | [].push.apply(es, recurse(arg)); 492 | return es; 493 | }, node.arguments)); 494 | if (node.callee != null) 495 | [].push.apply(errors, recurse(node.callee)); 496 | break; 497 | 498 | case "ObjectExpression": 499 | if (node.properties == null) { 500 | errors.push(new E(node, "ObjectExpression `properties` member must be non-null")); 501 | } else { 502 | initKeySet = {}; 503 | getKeySet = {}; 504 | setKeySet = {}; 505 | [].push.apply(errors, concatMap(function(property) { 506 | var es = [], key; 507 | if (property == null) 508 | return [new E(node, "ObjectExpression `properties` must not contain null values")]; 509 | if (!isExpression(property.value)) 510 | es.push(new E(property, "ObjectExpression property `value` member must be an expression node")); 511 | if (property.value != null) 512 | [].push.apply(es, recurse(property.value)); 513 | switch (property.kind) { 514 | case "init": break; 515 | case "get": 516 | if (property.value != null) { 517 | if (property.value.type !== "FunctionExpression") 518 | es.push(new E(property.value, "ObjectExpression getter property `value` member must be a FunctionExpression node")); 519 | else if (property.value.params == null || property.value.params.length !== 0) 520 | es.push(new E(property.value, "ObjectExpression getter property `value` member must have zero parameters")); 521 | } 522 | break; 523 | case "set": 524 | if (property.value != null) { 525 | if (property.value.type !== "FunctionExpression") 526 | es.push(new E(property.value, "ObjectExpression setter property `value` member must be a FunctionExpression node")); 527 | else if (property.value.params == null || property.value.params.length !== 1) 528 | es.push(new E(property.value, "ObjectExpression setter property `value` member must have exactly one parameter")); 529 | } 530 | break; 531 | default: 532 | es.push(new E(property, "ObjectExpression property `kind` member must be one of " + JSON.stringify(OBJECT_PROPERTY_KINDS))); 533 | } 534 | if (property.key == null) { 535 | es.push(new E(property, "ObjectExpression property `key` member must be an Identifier or Literal node")); 536 | } else { 537 | switch (property.key.type) { 538 | case "Identifier": 539 | if (property.key.name == null || !esutils.keyword.isIdentifierName(property.key.name)) 540 | es.push(new E(property, "ObjectExpression property `key` members of type Identifier must be an IdentifierName")); 541 | else 542 | key = "$" + property.key.name; 543 | break; 544 | case "Literal": 545 | if (["Number", "String"].indexOf(getClass(property.key.value)) < 0) { 546 | es.push(new E(property, "ObjectExpression property `key` members of type Literal must have either a number or string `value` member")); 547 | } else { 548 | [].push.apply(es, recurse(property.key)); 549 | key = "$" + property.key.value; 550 | } 551 | break; 552 | default: 553 | es.push(new E(property, "ObjectExpression property `key` member must be an Identifier or Literal node")); 554 | } 555 | if (key != null) 556 | switch (property.kind) { 557 | case "init": 558 | if (initKeySet.hasOwnProperty(key) && state.strict) 559 | es.push(new E(property, "ObjectExpression must not have more than one data property with the same name in strict mode")); 560 | if (getKeySet.hasOwnProperty(key)) 561 | es.push(new E(property, "ObjectExpression must not have data and getter properties with the same name")); 562 | if (setKeySet.hasOwnProperty(key)) 563 | es.push(new E(property, "ObjectExpression must not have data and setter properties with the same name")); 564 | initKeySet[key] = true; 565 | break; 566 | case "get": 567 | if (initKeySet.hasOwnProperty(key)) 568 | es.push(new E(property, "ObjectExpression must not have data and getter properties with the same name")); 569 | if (getKeySet.hasOwnProperty(key)) 570 | es.push(new E(property, "ObjectExpression must not have multiple getters with the same name")); 571 | getKeySet[key] = true; 572 | break; 573 | case "set": 574 | if (initKeySet.hasOwnProperty(key)) 575 | es.push(new E(property, "ObjectExpression must not have data and setter properties with the same name")); 576 | if (setKeySet.hasOwnProperty(key)) 577 | es.push(new E(property, "ObjectExpression must not have multiple setters with the same name")); 578 | setKeySet[key] = true; 579 | break; 580 | } 581 | } 582 | return es; 583 | }, node.properties)); 584 | } 585 | break; 586 | 587 | case "Program": 588 | if (node.body == null) { 589 | errors.push(new E(node, "Program `body` member must be non-null")); 590 | } else { 591 | strict = state.strict || any(function(d) { return d === "use strict"; }, directives(node.body)); 592 | recursionFn = strict && !state.strict ? errorsP(merge({}, state, {strict: true})) : recurse; 593 | [].push.apply(errors, concatMap(function(sourceElement) { 594 | var es = []; 595 | if (!isSourceElement(sourceElement)) 596 | es.push(new E(sourceElement != null ? sourceElement : node, "Program `body` member must only contain statement or function declaration nodes")); 597 | if (sourceElement != null) 598 | [].push.apply(es, recursionFn(sourceElement)); 599 | return es; 600 | }, node.body)); 601 | } 602 | break; 603 | 604 | case "ReturnStatement": 605 | if (!state.inFunc) 606 | errors.push(new E(node, "ReturnStatement must be nested within a FunctionExpression or FunctionDeclaration node")); 607 | if (node.argument != null) { 608 | if (!isExpression(node.argument)) 609 | errors.push(new E(node, "ReturnStatement `argument` member must be an expression node or null")); 610 | [].push.apply(errors, recurse(node.argument)); 611 | } 612 | break; 613 | 614 | case "SequenceExpression": 615 | if (node.expressions == null) { 616 | errors.push(new E(node, "SequenceExpression `expressions` member must be non-null")); 617 | } else { 618 | if (node.expressions.length < 2) 619 | errors.push(new E(node, "SequenceExpression `expressions` member length must be >= 2")); 620 | [].push.apply(errors, concatMap(function(expr) { 621 | var es = []; 622 | if (!isExpression(expr)) 623 | es.push(new E(expr != null ? expr : node, "SequenceExpression `expressions` member must only contain expression nodes")); 624 | if (expr != null) 625 | [].push.apply(es, recurse(expr)); 626 | return es; 627 | }, node.expressions)); 628 | } 629 | break; 630 | 631 | case "SwitchCase": 632 | if (node.test != null) { 633 | if (!isExpression(node.test)) 634 | errors.push(new E(node, "SwitchCase `test` member must be an expression node or null")); 635 | [].push.apply(errors, recurse(node.test)); 636 | } 637 | if (node.consequent == null) { 638 | errors.push(new E(node, "SwitchCase `consequent` member must be non-null")); 639 | } else { 640 | recursionFn = errorsP(merge({}, state, {inSwitch: true})); 641 | [].push.apply(errors, concatMap(function(stmt) { 642 | var es = []; 643 | if (!isStatement(stmt)) 644 | es.push(new E(stmt != null ? stmt : node, "SwitchCase `consequent` member must only contain statement nodes")); 645 | if (stmt != null) 646 | [].push.apply(es, recursionFn(stmt)); 647 | return es; 648 | }, node.consequent)); 649 | } 650 | break; 651 | 652 | case "SwitchStatement": 653 | if (!isExpression(node.discriminant)) 654 | errors.push(new E(node, "SwitchStatement `discriminant` member must be an expression node")); 655 | if (node.cases == null) { 656 | errors.push(new E(node, "SwitchStatement `cases` member must be non-null")); 657 | } else { 658 | [].push.apply(errors, concatMap(function(switchCase) { 659 | var es = []; 660 | if (switchCase == null || switchCase.type !== "SwitchCase") 661 | es.push(new E(switchCase != null ? switchCase : node, "SwitchStatement `cases` member must only contain SwitchCase nodes")); 662 | if (switchCase != null) 663 | [].push.apply(es, recurse(switchCase)); 664 | return es; 665 | }, node.cases)); 666 | if (filter(node.cases, function(c) { return c != null && c.test == null; }).length > 1) 667 | errors.push(new E(node, "SwitchStatement `cases` member must contain no more than one SwitchCase with a null `test` member")); 668 | } 669 | if (node.discriminant != null) 670 | [].push.apply(errors, recurse(node.discriminant)); 671 | break; 672 | 673 | case "ThisExpression": 674 | break; 675 | 676 | case "ThrowStatement": 677 | if (!isExpression(node.argument)) 678 | errors.push(new E(node, "ThrowStatement `argument` member must be an expression node")); 679 | if (node.argument != null) 680 | [].push.apply(errors, recurse(node.argument)); 681 | break; 682 | 683 | case "TryStatement": 684 | // NOTE: TryStatement interface changed from {handlers: [CatchClause]} to {handler: CatchClause}; we support both 685 | var handlers = node.handlers || (node.handler ? [node.handler] : []); 686 | if (node.block == null || node.block.type !== "BlockStatement") 687 | errors.push(new E(node.block != null ? node.block : node, "TryStatement `block` member must be a BlockStatement node")); 688 | if (node.finalizer != null && node.finalizer.type !== "BlockStatement") 689 | errors.push(new E(node.finalizer, "TryStatement `finalizer` member must be a BlockStatement node")); 690 | [].push.apply(errors, concatMap(function(handler) { 691 | var es = []; 692 | if (handler == null || handler.type !== "CatchClause") 693 | es.push(new E(handler != null ? handler : node, "TryStatement `handler` member must be a CatchClause node")); 694 | if (handler != null) 695 | [].push.apply(es, recurse(handler)); 696 | return es; 697 | }, handlers)); 698 | if (node.block != null) 699 | [].push.apply(errors, recurse(node.block)); 700 | if (node.finalizer != null) 701 | [].push.apply(errors, recurse(node.finalizer)); 702 | if (handlers.length < 1 && node.finalizer == null) 703 | errors.push(new E(node, "TryStatement must have a non-null `handler` member or a non-null `finalizer` member")); 704 | break; 705 | 706 | case "UnaryExpression": 707 | if (!isUnaryOperator(node.operator)) 708 | errors.push(new E(node, "UnaryExpression `operator` member must be one of " + JSON.stringify(UNARY_OPERATORS))); 709 | if (!isExpression(node.argument)) 710 | errors.push(new E(node, "UnaryExpression `argument` member must be an expression node")); 711 | if (node.argument != null) { 712 | [].push.apply(errors, recurse(node.argument)); 713 | if (state.strict && node.operator === "delete" && node.argument.type === "Identifier") 714 | errors.push(new E(node, "`delete` with unqualified identifier not allowed in strict mode")); 715 | } 716 | break; 717 | 718 | case "UpdateExpression": 719 | if (!isUpdateOperator(node.operator)) 720 | errors.push(new E(node, "UpdateExpression `operator` member must be one of " + JSON.stringify(UNARY_OPERATORS))); 721 | if (!isExpression(node.argument)) 722 | errors.push(new E(node, "UpdateExpression `argument` member must be an expression node")); 723 | if (node.argument != null) 724 | [].push.apply(errors, recurse(node.argument)); 725 | break; 726 | 727 | case "VariableDeclaration": 728 | if (node.declarations == null) { 729 | errors.push(new E(node, "VariableDeclaration `declarations` member must be non-null")); 730 | } else { 731 | if (node.declarations.length < 1) 732 | errors.push(new E(node, "VariableDeclaration `declarations` member must be non-empty")); 733 | if (VARIABLE_DECLARATION_KINDS.indexOf(node.kind) < 0) 734 | errors.push(new E(node, "VariableDeclaration `kind` member must be one of " + JSON.stringify(VARIABLE_DECLARATION_KINDS))); 735 | [].push.apply(errors, concatMap(function(decl) { 736 | var es = []; 737 | if (decl == null || decl.type !== "VariableDeclarator") 738 | es.push(new E(decl != null ? decl : node, "VariableDeclaration `declarations` member must contain only VariableDeclarator nodes")); 739 | if (decl != null) 740 | [].push.apply(es, recurse(decl)); 741 | return es; 742 | }, node.declarations)); 743 | } 744 | break; 745 | 746 | case "VariableDeclarator": 747 | if (!isExpression(node.id)) 748 | errors.push(new E(node, "VariableDeclarator `id` member must be an expression node")); 749 | if (node.init != null) { 750 | if (!isExpression(node.init)) 751 | errors.push(new E(node, "VariableDeclarator `init` member must be an expression node or null")); 752 | [].push.apply(errors, recurse(node.init)); 753 | } 754 | if (node.id != null) 755 | [].push.apply(errors, recurse(node.id)); 756 | break; 757 | 758 | case "WhileStatement": 759 | if (!isExpression(node.test)) 760 | errors.push(new E(node, "WhileStatement `test` member must be an expression node")); 761 | if (!isStatement(node.body)) 762 | errors.push(new E(node, "WhileStatement `body` member must be a statement node")); 763 | if (node.test != null) 764 | [].push.apply(errors, recurse(node.test)); 765 | if (node.body != null) 766 | [].push.apply(errors, errorsP(merge({}, state, {inIter: true}))(node.body)); 767 | break; 768 | 769 | case "WithStatement": 770 | if (state.strict) 771 | errors.push(new E(node, "WithStatement not allowed in strict mode")); 772 | if (!isExpression(node.object)) 773 | errors.push(new E(node, "WithStatement `object` member must be an expression node")); 774 | if (!isStatement(node.body)) 775 | errors.push(new E(node, "WithStatement `body` member must be a statement node")); 776 | if (node.object != null) 777 | [].push.apply(errors, recurse(node.object)); 778 | if (node.body != null) 779 | [].push.apply(errors, errorsP(merge({}, state, {inIter: true}))(node.body)); 780 | break; 781 | 782 | default: 783 | switch (getClass(node.type)) { 784 | case "String": 785 | errors.push(new E(node, "unrecognised node type: " + JSON.stringify(node.type))); 786 | break; 787 | case "Null": 788 | case "Undefined": 789 | errors.push(new E(node, "all AST nodes must have a `type` member")); 790 | break; 791 | default: 792 | errors.push(new E(node, "AST node `type` must be a string")); 793 | } 794 | } 795 | 796 | return errors; 797 | }; 798 | } 799 | 800 | var START_STATE = {labels: [], inFunc: false, inIter: false, inSwitch: false, strict: false}; 801 | 802 | module.exports = { 803 | 804 | // isValid :: Maybe Node -> Boolean 805 | isValid: function isValid(node) { 806 | return node != null && node.type === "Program" && 807 | errorsP(START_STATE)(node).length < 1; 808 | }, 809 | 810 | // isValidExpression :: Maybe Node -> Boolean 811 | isValidExpression: function isValidExpression(node) { 812 | return isExpression(node) && errorsP(START_STATE)(node).length < 1; 813 | }, 814 | 815 | // InvalidAstError :: Node -> String -> InvalidAstError 816 | InvalidAstError: InvalidAstError, 817 | 818 | // errors :: Maybe Node -> [InvalidAstError] 819 | errors: function errors(node) { 820 | var errors = []; 821 | if (node == null) { 822 | errors.push(new E(node, "given AST node should be non-null")); 823 | } else { 824 | if (node.type !== "Program") 825 | errors.push(new E(node, "given AST node should be of type Program")); 826 | [].push.apply(errors, errorsP(START_STATE)(node)); 827 | } 828 | return errors; 829 | } 830 | 831 | }; 832 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "esvalid", 3 | "version": "1.1.0", 4 | "description": "confirm that a SpiderMonkey format AST represents an ECMAScript program", 5 | "main": "index", 6 | "scripts": { 7 | "test": "make lint test" 8 | }, 9 | "files": [ 10 | "index.js" 11 | ], 12 | "repository": { 13 | "type": "git", 14 | "url": "git@github.com:michaelficarra/esvalid.git" 15 | }, 16 | "keywords": [ 17 | "esvalid", 18 | "ECMAScript", 19 | "ES262", 20 | "SpiderMonkey", 21 | "AST", 22 | "validate", 23 | "validator", 24 | "valid" 25 | ], 26 | "author": "Michael Ficarra", 27 | "license": "BSD-3-Clause", 28 | "bugs": { 29 | "url": "https://github.com/michaelficarra/esvalid/issues" 30 | }, 31 | "homepage": "https://github.com/michaelficarra/esvalid", 32 | "dependencies": { 33 | "esutils": "^1.1.4", 34 | "object-assign": "^0.3.1" 35 | }, 36 | "devDependencies": { 37 | "async": "0.9.0", 38 | "coffee-script": "1.7.1", 39 | "colors": "0.6.2", 40 | "commander": "2.2.0", 41 | "eslint": "^0.6.2", 42 | "esprima": "^1.2.2", 43 | "everything.js": "0.0.0", 44 | "express": "4.4.4", 45 | "istanbul": "^0.2.11", 46 | "lodash": "2.4.1", 47 | "mkdirp": "0.5.0", 48 | "mocha": "^1.20.1", 49 | "optimist": "0.6.1", 50 | "request": "2.36.0", 51 | "underscore": "1.6.0", 52 | "xyz": "^0.4.0" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /test-helpers.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var esvalid = require("./"); 4 | var assert = require("assert"); 5 | 6 | var STMT = {type: "EmptyStatement"}; 7 | var BLOCK = {type: "BlockStatement", body: []}; 8 | var EXPR = {type: "Literal", value: null}; 9 | var NUM = {type: "Literal", value: 0}; 10 | var STR = {type: "Literal", value: "a"}; 11 | var ID = {type: "Identifier", name: "a"}; 12 | var CATCH = {type: "CatchClause", param: ID, body: BLOCK}; 13 | 14 | // wrap a statement in a program 15 | function wrapProgram(n) { return {type: "Program", body: [n]}; } 16 | // wrap a statement in an iteration statement 17 | function wrapIter(n) { return {type: "WhileStatement", test: {type: "Literal", value: true}, body: n}; } 18 | // wrap zero or more statements in a function expression 19 | function FE() { return {type: "FunctionExpression", params: [], body: {type: "BlockStatement", body: arguments}}; } 20 | // wrap zero or more statements in a function declaration 21 | function FD() { return {type: "FunctionDeclaration", id: ID, params: [], body: {type: "BlockStatement", body: arguments}}; } 22 | // wrap a statement in a labeledstatement 23 | function label(l, n) { return {type: "LabeledStatement", label: {type: "Identifier", name: l}, body: n}; } 24 | // wrap an expression in an expressionstatement 25 | function exprStmt(e) { return {type: "ExpressionStatement", expression: e}; } 26 | 27 | 28 | function validStmt(x, msg) { assert.ok(esvalid.isValid(wrapProgram(x)), msg); } 29 | function invalidStmt(n, x, msg) { 30 | assert.ok(!esvalid.isValid(wrapProgram(x)), msg); 31 | var errors = esvalid.errors(wrapProgram(x)); 32 | errors.forEach(function(e) { assert.notEqual(e.node, null, msg); }); 33 | assert.equal(n, errors.length, msg); 34 | } 35 | 36 | function validExpr(x, msg) { assert.ok(esvalid.isValidExpression(x), msg); } 37 | function invalidExpr(n, x, msg) { 38 | assert.ok(!esvalid.isValidExpression(x), msg); 39 | var errors = esvalid.errors(wrapProgram(exprStmt(x))); 40 | errors.forEach(function(e) { assert.notEqual(e.node, null, msg); }); 41 | assert.equal(n, errors.length, msg); 42 | } 43 | 44 | exports.STMT = STMT; 45 | exports.BLOCK = BLOCK; 46 | exports.EXPR = EXPR; 47 | exports.NUM = NUM; 48 | exports.STR = STR; 49 | exports.ID = ID; 50 | exports.CATCH = CATCH; 51 | 52 | exports.wrapProgram = wrapProgram; 53 | exports.wrapIter = wrapIter; 54 | exports.FE = FE; 55 | exports.FD = FD; 56 | exports.label = label; 57 | exports.exprStmt = exprStmt; 58 | 59 | exports.validExpr = validExpr; 60 | exports.invalidExpr = invalidExpr; 61 | exports.validStmt = validStmt; 62 | exports.invalidStmt = invalidStmt; 63 | -------------------------------------------------------------------------------- /test/libraries.js: -------------------------------------------------------------------------------- 1 | /* global suite test */ 2 | "use strict"; 3 | var assert = require("assert"); 4 | var fs = require("fs"); 5 | 6 | var esprima = require("esprima"); 7 | var esvalid = require("../"); 8 | 9 | function validate(lib) { 10 | return function test(done) { 11 | fs.readFile(require.resolve(lib), function(err, programText) { 12 | if (err) throw err; 13 | var program = esprima.parse("" + programText); 14 | assert.ok(esvalid.isValid(program)); 15 | done(); 16 | }); 17 | }; 18 | } 19 | 20 | if (!process.env.REALCOVERAGE) { 21 | 22 | suite("everything.js", function() { 23 | test("is valid", validate("everything.js")); 24 | }); 25 | 26 | suite("top 10 most popular npm packages", function() { 27 | test("underscore", validate("underscore")); 28 | test("async", validate("async")); 29 | test("request", validate("request")); 30 | test("lodash", validate("lodash")); 31 | test("commander", validate("commander")); 32 | test("express", validate("express")); 33 | test("optimist", validate("optimist")); 34 | test("coffee-script", validate("coffee-script")); 35 | test("colors", validate("colors")); 36 | test("mkdirp", validate("mkdirp")); 37 | }); 38 | 39 | } 40 | -------------------------------------------------------------------------------- /test/meta.js: -------------------------------------------------------------------------------- 1 | /* global suite test */ 2 | "use strict"; 3 | var assert = require("assert"); 4 | var fs = require("fs"); 5 | 6 | var esprima = require("esprima"); 7 | var esvalid = require("../"); 8 | 9 | function validate(done) { 10 | return function(err, programText) { 11 | if (err) throw err; 12 | var program = esprima.parse(programText); 13 | assert.ok(esvalid.isValid(program)); 14 | done(); 15 | }; 16 | } 17 | 18 | if (!process.env.REALCOVERAGE) 19 | suite("meta", function(){ 20 | 21 | test("esvalid itself is valid", function(done) { 22 | fs.readFile(require.resolve(".."), validate(done)); 23 | }); 24 | 25 | test("esprima is valid", function(done) { 26 | fs.readFile(require.resolve("esprima"), validate(done)); 27 | }); 28 | 29 | test("esutils is valid", function(done) { 30 | fs.readFile(require.resolve("esutils"), validate(done)); 31 | }); 32 | 33 | test("eslint is valid", function(done) { 34 | fs.readFile(require.resolve("eslint/lib/eslint"), validate(done)); 35 | }); 36 | 37 | test("mocha is valid", function(done) { 38 | fs.readFile(require.resolve("mocha/lib/mocha"), validate(done)); 39 | }); 40 | 41 | }); 42 | -------------------------------------------------------------------------------- /test/strict.js: -------------------------------------------------------------------------------- 1 | /* global suite test */ 2 | "use strict"; 3 | 4 | var assert = require("assert"); 5 | 6 | var esvalid = require("../"); 7 | 8 | var helpers = require("../test-helpers"); 9 | var STMT = helpers.STMT, BLOCK = helpers.BLOCK, EXPR = helpers.EXPR, 10 | NUM = helpers.NUM, STR = helpers.STR, ID = helpers.ID, CATCH = helpers.CATCH; 11 | var wrapProgram = helpers.wrapProgram, wrapIter = helpers.wrapIter, 12 | FE = helpers.FE, FD = helpers.FD, label = helpers.label, exprStmt = helpers.exprStmt; 13 | var validExpr = helpers.validExpr, invalidExpr = helpers.invalidExpr, 14 | validStmt = helpers.validStmt, invalidStmt = helpers.invalidStmt; 15 | 16 | suite("strict mode", function() { 17 | 18 | // wrap zero or more statements in a strict-mode function expression 19 | function strictFE() { return FE.apply(null, [exprStmt({type: "Literal", value: "use strict"})].concat([].slice.call(arguments))); } 20 | 21 | test("basic directive support", function() { 22 | validStmt(FD(exprStmt({type: "Literal", value: "use strict"}))); 23 | validStmt(FD(exprStmt({type: "Literal", value: "directive"}), exprStmt({type: "Literal", value: "use strict"}))); 24 | validStmt(exprStmt(FE(exprStmt({type: "Literal", value: "use strict"})))); 25 | validStmt(exprStmt(FE(exprStmt({type: "Literal", value: "directive"}), exprStmt({type: "Literal", value: "use strict"})))); 26 | validStmt(exprStmt({type: "Literal", value: "use strict"})); 27 | validExpr(FE(exprStmt({type: "Literal", value: "use strict"}))); 28 | }); 29 | 30 | test("CatchClause param must not be restricted in strict mode", function() { 31 | validStmt({type: "TryStatement", block: BLOCK, handler: {type: "CatchClause", param: {type: "Identifier", name: "eval"}, body: BLOCK}}); 32 | validStmt({type: "TryStatement", block: BLOCK, handler: {type: "CatchClause", param: {type: "Identifier", name: "arguments"}, body: BLOCK}}); 33 | validExpr(strictFE({type: "TryStatement", block: BLOCK, handler: {type: "CatchClause", param: {type: "Identifier", name: "x"}, body: BLOCK}})); 34 | invalidExpr(1, strictFE({type: "TryStatement", block: BLOCK, handler: {type: "CatchClause", param: {type: "Identifier", name: "eval"}, body: BLOCK}})); 35 | invalidExpr(1, strictFE({type: "TryStatement", block: BLOCK, handler: {type: "CatchClause", param: {type: "Identifier", name: "arguments"}, body: BLOCK}})); 36 | }); 37 | 38 | test("Function names must not be restricted in strict mode", function() { 39 | validExpr(strictFE(exprStmt({type: "FunctionExpression", id: null, params: [], body: BLOCK}))); 40 | validExpr({type: "FunctionExpression", id: {type: "Identifier", name: "eval"}, params: [], body: BLOCK}); 41 | validExpr({type: "FunctionExpression", id: {type: "Identifier", name: "arguments"}, params: [], body: BLOCK}); 42 | validStmt({type: "FunctionDeclaration", id: {type: "Identifier", name: "eval"}, params: [], body: BLOCK}); 43 | validStmt({type: "FunctionDeclaration", id: {type: "Identifier", name: "arguments"}, params: [], body: BLOCK}); 44 | invalidExpr(1, strictFE(exprStmt({type: "FunctionExpression", id: {type: "Identifier", name: "eval"}, params: [], body: BLOCK}))); 45 | invalidExpr(1, strictFE(exprStmt({type: "FunctionExpression", id: {type: "Identifier", name: "arguments"}, params: [], body: BLOCK}))); 46 | invalidExpr(1, strictFE({type: "FunctionDeclaration", id: {type: "Identifier", name: "eval"}, params: [], body: BLOCK})); 47 | invalidExpr(1, strictFE({type: "FunctionDeclaration", id: {type: "Identifier", name: "arguments"}, params: [], body: BLOCK})); 48 | }); 49 | 50 | test("FunctionDeclaration parameter names must be unique in strict mode", function() { 51 | validExpr(strictFE({type: "FunctionDeclaration", id: ID, params: [ID], body: BLOCK})); 52 | validExpr(strictFE({type: "FunctionDeclaration", id: ID, params: [{type: "Identifier", name: "a"}, {type: "Identifier", name: "A"}], body: BLOCK})); 53 | validStmt({type: "FunctionDeclaration", id: ID, params: [ID, ID], body: BLOCK}); 54 | invalidExpr(1, strictFE({type: "FunctionDeclaration", id: ID, params: [ID, ID], body: BLOCK})); 55 | }); 56 | 57 | test("FunctionExpression parameter names must be unique in strict mode", function() { 58 | validExpr(strictFE(exprStmt({type: "FunctionExpression", id: ID, params: [ID], body: BLOCK}))); 59 | validExpr(strictFE(exprStmt({type: "FunctionExpression", id: ID, params: [{type: "Identifier", name: "a"}, {type: "Identifier", name: "A"}], body: BLOCK}))); 60 | validExpr({type: "FunctionExpression", id: ID, params: [ID, ID], body: BLOCK}); 61 | invalidExpr(1, strictFE(exprStmt({type: "FunctionExpression", id: ID, params: [ID, ID], body: BLOCK}))); 62 | }); 63 | 64 | test("Identifier FutureReservedWords", function() { 65 | validExpr({type: "Identifier", name: "let"}); 66 | validExpr({type: "Identifier", name: "yield"}); // ES5 only 67 | invalidExpr(1, strictFE(exprStmt({type: "Identifier", name: "let"}))); 68 | invalidExpr(1, strictFE(exprStmt({type: "Identifier", name: "yield"}))); 69 | }); 70 | 71 | test("ObjectExpression duplicate keys", function() { 72 | validExpr({type: "ObjectExpression", properties: [{kind: "init", key: {type: "Literal", value: "a"}, value: EXPR}, {kind: "init", key: {type: "Literal", value: "a"}, value: EXPR}]}); 73 | validExpr({type: "ObjectExpression", properties: [{kind: "init", key: {type: "Literal", value: "__proto__"}, value: EXPR}, {kind: "init", key: {type: "Literal", value: "a"}, value: EXPR}]}); 74 | validExpr(strictFE(exprStmt({type: "ObjectExpression", properties: [{kind: "init", key: {type: "Literal", value: "hasOwnProperty"}, value: EXPR}, {kind: "init", key: {type: "Literal", value: "a"}, value: EXPR}]}))); 75 | invalidExpr(1, strictFE(exprStmt({type: "ObjectExpression", properties: [{kind: "init", key: {type: "Literal", value: "a"}, value: EXPR}, {kind: "init", key: {type: "Literal", value: "a"}, value: EXPR}]}))); 76 | invalidExpr(1, strictFE(exprStmt({type: "ObjectExpression", properties: [{kind: "init", key: {type: "Literal", value: "a"}, value: EXPR}, {kind: "init", key: {type: "Identifier", name: "a"}, value: EXPR}]}))); 77 | invalidExpr(1, strictFE(exprStmt({type: "ObjectExpression", properties: [{kind: "init", key: {type: "Literal", value: "0"}, value: EXPR}, {kind: "init", key: {type: "Literal", value: 0}, value: EXPR}]}))); 78 | }); 79 | 80 | test("UnaryExpression delete with unqualified identifier", function() { 81 | validExpr({type: "UnaryExpression", operator: "delete", argument: NUM}); 82 | validExpr({type: "UnaryExpression", operator: "delete", argument: ID}); 83 | validExpr(strictFE(exprStmt({type: "UnaryExpression", operator: "delete", argument: NUM}))); 84 | invalidExpr(1, strictFE(exprStmt({type: "UnaryExpression", operator: "delete", argument: ID}))); 85 | }); 86 | 87 | test("WithStatement not allowed", function() { 88 | validStmt({type: "WithStatement", object: EXPR, body: STMT}); 89 | invalidExpr(1, strictFE({type: "WithStatement", object: EXPR, body: STMT})); 90 | }); 91 | 92 | }); 93 | -------------------------------------------------------------------------------- /test/types.js: -------------------------------------------------------------------------------- 1 | /* global suite test */ 2 | "use strict"; 3 | 4 | var assert = require("assert"); 5 | 6 | var esvalid = require("../"); 7 | 8 | var helpers = require("../test-helpers"); 9 | var STMT = helpers.STMT, BLOCK = helpers.BLOCK, EXPR = helpers.EXPR, 10 | NUM = helpers.NUM, STR = helpers.STR, ID = helpers.ID, CATCH = helpers.CATCH; 11 | var wrapProgram = helpers.wrapProgram, wrapIter = helpers.wrapIter, 12 | FE = helpers.FE, FD = helpers.FD, label = helpers.label, exprStmt = helpers.exprStmt; 13 | var validExpr = helpers.validExpr, invalidExpr = helpers.invalidExpr, 14 | validStmt = helpers.validStmt, invalidStmt = helpers.invalidStmt; 15 | 16 | suite("type checks", function() { 17 | 18 | test("Node", function() { 19 | invalidStmt(2, {type: "EmptyStatement", loc: {}}); 20 | invalidStmt(3, {type: "EmptyStatement", loc: {start: null, end: 0}}); 21 | invalidStmt(4, {type: "EmptyStatement", loc: {start: {}, end: {}}}); 22 | invalidStmt(4, {type: "EmptyStatement", loc: {start: {line: "a", column: "b"}, end: {line: "a", column: "b"}}}); 23 | invalidStmt(1, {type: "EmptyStatement", loc: {start: {line: 0, column: 0}, end: {line: 1, column: 0}}}); 24 | invalidStmt(1, {type: "EmptyStatement", loc: {start: {line: 1, column: 0}, end: {line: 0, column: 0}}}); 25 | invalidStmt(1, {type: "EmptyStatement", loc: {start: {line: 1, column: -1}, end: {line: 1, column: 0}}}); 26 | invalidStmt(1, {type: "EmptyStatement", loc: {start: {line: 1, column: 0}, end: {line: 1, column: -1}}}); 27 | invalidStmt(1, {type: "EmptyStatement", loc: {source: 0, start: {line: 1, column: 0}, end: {line: 1, column: 0}}}); 28 | validStmt({type: "EmptyStatement", loc: {start: {line: 1, column: 0}, end: {line: 1, column: 0}}}); 29 | validStmt({type: "EmptyStatement", loc: {source: "", start: {line: 1, column: 0}, end: {line: 1, column: 0}}}); 30 | validStmt({type: "EmptyStatement", loc: null}); 31 | }); 32 | 33 | 34 | test("ArrayExpression", function() { 35 | validExpr({type: "ArrayExpression", elements: []}); 36 | validExpr({type: "ArrayExpression", elements: [null]}); 37 | validExpr({type: "ArrayExpression", elements: [EXPR]}); 38 | validExpr({type: "ArrayExpression", elements: [EXPR, EXPR]}); 39 | validExpr({type: "ArrayExpression", elements: [EXPR, null, EXPR]}); 40 | invalidExpr(1, {type: "ArrayExpression"}); 41 | invalidExpr(1, {type: "ArrayExpression", elements: [STMT]}); 42 | }); 43 | 44 | test("AssignmentExpression", function() { 45 | validExpr({type: "AssignmentExpression", operator: "=", left: EXPR, right: EXPR}); 46 | validExpr({type: "AssignmentExpression", operator: "+=", left: EXPR, right: EXPR}); 47 | invalidExpr(3, {type: "AssignmentExpression"}); 48 | invalidExpr(1, {type: "AssignmentExpression", left: EXPR, right: EXPR}); 49 | invalidExpr(1, {type: "AssignmentExpression", operator: "=", left: EXPR}); 50 | invalidExpr(1, {type: "AssignmentExpression", operator: "=", right: EXPR}); 51 | invalidExpr(1, {type: "AssignmentExpression", left: EXPR, right: EXPR}); 52 | invalidExpr(2, {type: "AssignmentExpression", operator: "="}); 53 | invalidExpr(2, {type: "AssignmentExpression", left: EXPR}); 54 | invalidExpr(2, {type: "AssignmentExpression", right: EXPR}); 55 | invalidExpr(1, {type: "AssignmentExpression", operator: "||=", left: EXPR, right: EXPR}); 56 | invalidExpr(1, {type: "AssignmentExpression", operator: "=", left: STMT, right: EXPR}); 57 | invalidExpr(1, {type: "AssignmentExpression", operator: "=", left: EXPR, right: STMT}); 58 | }); 59 | 60 | test("BinaryExpression", function() { 61 | validExpr({type: "BinaryExpression", operator: "+", left: EXPR, right: EXPR}); 62 | validExpr({type: "BinaryExpression", operator: "&", left: EXPR, right: EXPR}); 63 | invalidExpr(3, {type: "BinaryExpression"}); 64 | invalidExpr(1, {type: "BinaryExpression", left: EXPR, right: EXPR}); 65 | invalidExpr(1, {type: "BinaryExpression", operator: "+", left: EXPR}); 66 | invalidExpr(1, {type: "BinaryExpression", operator: "+", right: EXPR}); 67 | invalidExpr(1, {type: "BinaryExpression", left: EXPR, right: EXPR}); 68 | invalidExpr(2, {type: "BinaryExpression", operator: "+"}); 69 | invalidExpr(2, {type: "BinaryExpression", left: EXPR}); 70 | invalidExpr(2, {type: "BinaryExpression", right: EXPR}); 71 | invalidExpr(1, {type: "BinaryExpression", operator: "=", left: EXPR, right: EXPR}); 72 | invalidExpr(1, {type: "BinaryExpression", operator: "||", left: EXPR, right: EXPR}); 73 | invalidExpr(1, {type: "BinaryExpression", operator: "+", left: STMT, right: EXPR}); 74 | invalidExpr(1, {type: "BinaryExpression", operator: "+", left: EXPR, right: STMT}); 75 | }); 76 | 77 | test("Blocktatement", function() { 78 | validStmt({type: "BlockStatement", body: []}); 79 | validStmt({type: "BlockStatement", body: [STMT]}); 80 | validStmt({type: "BlockStatement", body: [STMT, STMT]}); 81 | invalidStmt(1, {type: "BlockStatement"}); 82 | invalidStmt(1, {type: "BlockStatement", body: null}); 83 | invalidStmt(1, {type: "BlockStatement", body: [null]}); 84 | invalidStmt(1, {type: "BlockStatement", body: [EXPR]}); 85 | invalidStmt(1, {type: "BlockStatement", body: [FD(STMT)]}); 86 | }); 87 | 88 | test("BreakStatement", function() { 89 | validStmt(wrapIter({type: "BreakStatement"})); 90 | validStmt(wrapIter({type: "BreakStatement", label: null})); 91 | validStmt(label(ID.name, wrapIter({type: "BreakStatement", label: ID}))); 92 | validStmt({type: "SwitchStatement", discriminant: EXPR, cases: [{type: "SwitchCase", test: null, consequent: [{type: "BreakStatement"}]}]}); 93 | invalidStmt(1, label(ID.name, wrapIter({type: "BreakStatement", label: STMT}))); 94 | invalidStmt(1, {type: "SwitchStatement", discriminant: EXPR, cases: [{type: "SwitchCase", test: null, consequent: [{type: "BreakStatement", label: STMT}]}]}); 95 | invalidStmt(1, {type: "BreakStatement"}); 96 | }); 97 | 98 | test("CallExpression", function() { 99 | validExpr({type: "CallExpression", callee: EXPR, arguments: []}); 100 | validExpr({type: "CallExpression", callee: EXPR, arguments: [EXPR]}); 101 | validExpr({type: "CallExpression", callee: EXPR, arguments: [EXPR, EXPR, EXPR]}); 102 | invalidExpr(2, {type: "CallExpression"}); 103 | invalidExpr(1, {type: "CallExpression", callee: EXPR}); 104 | invalidExpr(1, {type: "CallExpression", arguments: []}); 105 | invalidExpr(1, {type: "CallExpression", callee: EXPR, arguments: [null]}); 106 | invalidExpr(1, {type: "CallExpression", callee: EXPR, arguments: [STMT]}); 107 | invalidExpr(1, {type: "CallExpression", callee: EXPR, arguments: [EXPR, STMT, EXPR]}); 108 | }); 109 | 110 | test("CatchClause", function() { 111 | function wrapTry(x) { return {type: "TryStatement", block: BLOCK, handler: x}; } 112 | validStmt(wrapTry({type: "CatchClause", param: ID, body: BLOCK})); 113 | validStmt(wrapTry({type: "CatchClause", param: EXPR, body: BLOCK})); 114 | invalidStmt(2, wrapTry({type: "CatchClause"})); 115 | invalidStmt(1, wrapTry({type: "CatchClause", param: ID})); 116 | invalidStmt(1, wrapTry({type: "CatchClause", body: BLOCK})); 117 | invalidStmt(1, wrapTry({type: "CatchClause", param: ID, body: {type: "EmptyStatement"}})); 118 | invalidStmt(1, wrapTry({type: "CatchClause", param: STMT, body: BLOCK})); 119 | }); 120 | 121 | test("ConditionalExpression", function() { 122 | validExpr({type: "ConditionalExpression", test: EXPR, alternate: EXPR, consequent: EXPR}); 123 | invalidExpr(3, {type: "ConditionalExpression"}); 124 | invalidExpr(1, {type: "ConditionalExpression", alternate: EXPR, consequent: EXPR}); 125 | invalidExpr(1, {type: "ConditionalExpression", test: EXPR, consequent: EXPR}); 126 | invalidExpr(1, {type: "ConditionalExpression", test: EXPR, alternate: EXPR}); 127 | invalidExpr(1, {type: "ConditionalExpression", test: STMT, alternate: EXPR, consequent: EXPR}); 128 | invalidExpr(1, {type: "ConditionalExpression", test: EXPR, alternate: STMT, consequent: EXPR}); 129 | invalidExpr(1, {type: "ConditionalExpression", test: EXPR, alternate: EXPR, consequent: STMT}); 130 | invalidExpr(1, {type: "ConditionalExpression", test: null, alternate: EXPR, consequent: EXPR}); 131 | invalidExpr(1, {type: "ConditionalExpression", test: EXPR, alternate: null, consequent: EXPR}); 132 | invalidExpr(1, {type: "ConditionalExpression", test: EXPR, alternate: EXPR, consequent: null}); 133 | }); 134 | 135 | test("ContinueStatement", function() { 136 | validStmt(wrapIter({type: "ContinueStatement"})); 137 | validStmt(wrapIter({type: "ContinueStatement", label: null})); 138 | validStmt(label(ID.name, wrapIter({type: "ContinueStatement", label: ID}))); 139 | invalidStmt(1, label(ID.name, wrapIter({type: "ContinueStatement", label: STMT}))); 140 | invalidStmt(1, {type: "ContinueStatement"}); 141 | }); 142 | 143 | test("DebuggerStatement", function() { 144 | validStmt({type: "DebuggerStatement"}); 145 | }); 146 | 147 | test("DoWhileStatement", function() { 148 | validStmt({type: "DoWhileStatement", body: STMT, test: EXPR}); 149 | invalidStmt(2, {type: "DoWhileStatement"}); 150 | invalidStmt(1, {type: "DoWhileStatement", body: STMT}); 151 | invalidStmt(1, {type: "DoWhileStatement", test: EXPR}); 152 | invalidStmt(1, {type: "DoWhileStatement", body: EXPR, test: EXPR}); 153 | invalidStmt(1, {type: "DoWhileStatement", body: STMT, test: STMT}); 154 | }); 155 | 156 | test("EmptyStatement", function() { 157 | validStmt({type: "EmptyStatement"}); 158 | }); 159 | 160 | test("ExpressionStatement", function() { 161 | validStmt({type: "ExpressionStatement", expression: EXPR}); 162 | invalidStmt(1, {type: "ExpressionStatement"}); 163 | invalidStmt(1, {type: "ExpressionStatement", expression: STMT}); 164 | invalidStmt(1, {type: "ExpressionStatement", expression: FD(STMT)}); 165 | }); 166 | 167 | test("ForInStatement", function() { 168 | validStmt({type: "ForInStatement", left: EXPR, right: EXPR, body: STMT}); 169 | validStmt({type: "ForInStatement", left: {type: "VariableDeclaration", kind: "var", declarations: [{type: "VariableDeclarator", id: ID}]}, right: EXPR, body: STMT}); 170 | invalidStmt(3, {type: "ForInStatement"}); 171 | invalidStmt(2, {type: "ForInStatement", left: EXPR}); 172 | invalidStmt(2, {type: "ForInStatement", right: EXPR}); 173 | invalidStmt(1, {type: "ForInStatement", left: EXPR, right: EXPR}); 174 | invalidStmt(1, {type: "ForInStatement", left: EXPR, body: STMT}); 175 | invalidStmt(1, {type: "ForInStatement", right: EXPR, body: STMT}); 176 | invalidStmt(1, {type: "ForInStatement", left: EXPR, right: EXPR, body: EXPR}); 177 | invalidStmt(1, {type: "ForInStatement", left: STMT, right: EXPR, body: STMT}); 178 | invalidStmt(1, {type: "ForInStatement", left: EXPR, right: STMT, body: STMT}); 179 | }); 180 | 181 | test("ForStatement", function() { 182 | validStmt({type: "ForStatement", init: EXPR, test: EXPR, update: EXPR, body: STMT}); 183 | validStmt({type: "ForStatement", init: {type: "VariableDeclaration", kind: "var", declarations: [{type: "VariableDeclarator", id: ID}]}, test: EXPR, update: EXPR, body: STMT}); 184 | validStmt({type: "ForStatement", init: EXPR, body: STMT}); 185 | validStmt({type: "ForStatement", init: {type: "VariableDeclaration", kind: "var", declarations: [{type: "VariableDeclarator", id: ID}]}, body: STMT}); 186 | validStmt({type: "ForStatement", test: EXPR, body: STMT}); 187 | validStmt({type: "ForStatement", update: EXPR, body: STMT}); 188 | validStmt({type: "ForStatement", body: STMT}); 189 | invalidStmt(1, {type: "ForStatement"}); 190 | invalidStmt(1, {type: "ForStatement", init: EXPR}); 191 | invalidStmt(1, {type: "ForStatement", test: EXPR}); 192 | invalidStmt(1, {type: "ForStatement", update: EXPR}); 193 | invalidStmt(1, {type: "ForStatement", init: EXPR, test: EXPR}); 194 | invalidStmt(1, {type: "ForStatement", test: EXPR, update: EXPR}); 195 | invalidStmt(1, {type: "ForStatement", init: EXPR, update: EXPR}); 196 | invalidStmt(1, {type: "ForStatement", init: EXPR, test: EXPR, update: EXPR}); 197 | invalidStmt(1, {type: "ForStatement", body: EXPR}); 198 | invalidStmt(1, {type: "ForStatement", init: STMT, body: STMT}); 199 | invalidStmt(1, {type: "ForStatement", test: STMT, body: STMT}); 200 | invalidStmt(1, {type: "ForStatement", update: STMT, body: STMT}); 201 | }); 202 | 203 | test("FunctionDeclaration", function() { 204 | validStmt({type: "FunctionDeclaration", id: ID, params: [], body: BLOCK}); 205 | validStmt({type: "FunctionDeclaration", id: ID, params: [ID], body: BLOCK}); 206 | validStmt({type: "FunctionDeclaration", id: ID, params: [{type: "Identifier", name: "a"}, {type: "Identifier", name: "b"}], body: BLOCK}); 207 | validStmt({type: "FunctionDeclaration", id: ID, params: [], body: {type: "BlockStatement", body: [FD(STMT)]}}); 208 | invalidStmt(3, {type: "FunctionDeclaration"}); 209 | invalidStmt(1, {type: "FunctionDeclaration", params: [], body: BLOCK}); 210 | invalidStmt(1, {type: "FunctionDeclaration", id: ID, params: [], body: EXPR}); 211 | invalidStmt(1, {type: "FunctionDeclaration", id: null, params: [], body: BLOCK}); 212 | invalidStmt(1, {type: "FunctionDeclaration", id: ID, params: []}); 213 | invalidStmt(1, {type: "FunctionDeclaration", id: STMT, params: [], body: BLOCK}); 214 | invalidStmt(1, {type: "FunctionDeclaration", id: ID, body: BLOCK}); 215 | invalidStmt(1, {type: "FunctionDeclaration", id: ID, params: [null], body: BLOCK}); 216 | invalidStmt(1, {type: "FunctionDeclaration", id: ID, params: [STMT], body: BLOCK}); 217 | }); 218 | 219 | test("FunctionExpression", function() { 220 | validExpr({type: "FunctionExpression", params: [], body: BLOCK}); 221 | validExpr({type: "FunctionExpression", id: null, params: [], body: BLOCK}); 222 | validExpr({type: "FunctionExpression", id: ID, params: [], body: BLOCK}); 223 | validExpr({type: "FunctionExpression", id: ID, params: [ID], body: BLOCK}); 224 | validExpr({type: "FunctionExpression", id: ID, params: [{type: "Identifier", name: "a"}, {type: "Identifier", name: "b"}], body: BLOCK}); 225 | validExpr({type: "FunctionExpression", params: [], body: {type: "BlockStatement", body: [FD(STMT)]}}); 226 | invalidExpr(2, {type: "FunctionExpression"}); 227 | invalidExpr(1, {type: "FunctionExpression", params: []}); 228 | invalidExpr(1, {type: "FunctionExpression", body: BLOCK}); 229 | invalidExpr(1, {type: "FunctionExpression", params: [null], body: BLOCK}); 230 | invalidExpr(1, {type: "FunctionExpression", params: [], body: EXPR}); 231 | invalidExpr(1, {type: "FunctionExpression", id: STMT, params: [], body: BLOCK}); 232 | invalidExpr(1, {type: "FunctionExpression", id: ID, params: [STMT], body: BLOCK}); 233 | }); 234 | 235 | test("Identifier", function() { 236 | validExpr({type: "Identifier", name: "x"}); 237 | invalidExpr(1, {type: "Identifier"}); 238 | invalidExpr(1, {type: "Identifier", name: null}); 239 | }); 240 | 241 | test("IfStatement", function() { 242 | validStmt({type: "IfStatement", test: EXPR, consequent: STMT}); 243 | validStmt({type: "IfStatement", test: EXPR, consequent: BLOCK}); 244 | validStmt({type: "IfStatement", test: EXPR, consequent: STMT, alternate: STMT}); 245 | validStmt({type: "IfStatement", test: EXPR, consequent: BLOCK, alternate: BLOCK}); 246 | validStmt({type: "IfStatement", test: EXPR, consequent: STMT, alternate: BLOCK}); 247 | validStmt({type: "IfStatement", test: EXPR, consequent: BLOCK, alternate: STMT}); 248 | invalidStmt(2, {type: "IfStatement"}); 249 | invalidStmt(1, {type: "IfStatement", test: EXPR}); 250 | invalidStmt(1, {type: "IfStatement", test: STMT, consequent: STMT}); 251 | invalidStmt(1, {type: "IfStatement", test: EXPR, consequent: EXPR}); 252 | invalidStmt(1, {type: "IfStatement", test: EXPR, alternate: STMT}); 253 | invalidStmt(1, {type: "IfStatement", test: EXPR, consequent: STMT, alternate: EXPR}); 254 | }); 255 | 256 | test("LabeledStatement", function() { 257 | validStmt({type: "LabeledStatement", label: ID, body: STMT}); 258 | invalidStmt(2, {type: "LabeledStatement"}); 259 | invalidStmt(1, {type: "LabeledStatement", label: null, body: STMT}); 260 | invalidStmt(2, {type: "LabeledStatement", label: 0, body: STMT}); 261 | invalidStmt(1, {type: "LabeledStatement", label: ID, body: null}); 262 | invalidStmt(2, {type: "LabeledStatement", label: ID, body: false}); 263 | }); 264 | 265 | test("Literal", function() { 266 | validExpr({type: "Literal", value: null}); 267 | validExpr({type: "Literal", value: "string"}); 268 | validExpr({type: "Literal", value: ""}); 269 | validExpr({type: "Literal", value: 0}); 270 | validExpr({type: "Literal", value: 1}); 271 | validExpr({type: "Literal", value: 1e308}); 272 | validExpr({type: "Literal", value: 1e-308}); 273 | validExpr({type: "Literal", value: /./i}); 274 | validExpr({type: "Literal", value: new RegExp}); 275 | validExpr({type: "Literal", value: true}); 276 | validExpr({type: "Literal", value: false}); 277 | invalidExpr(1, {type: "Literal"}); 278 | invalidExpr(1, {type: "Literal", value: void 0}); 279 | invalidExpr(1, {type: "Literal", value: new Date}); 280 | invalidExpr(1, {type: "Literal", value: arguments}); 281 | invalidExpr(1, {type: "Literal", value: {}}); 282 | invalidExpr(1, {type: "Literal", value: []}); 283 | invalidExpr(1, {type: "Literal", value: [0]}); 284 | invalidExpr(1, {type: "Literal", value: ["x"]}); 285 | }); 286 | 287 | test("LogicalExpression", function() { 288 | validExpr({type: "LogicalExpression", operator: "||", left: EXPR, right: EXPR}); 289 | validExpr({type: "LogicalExpression", operator: "&&", left: EXPR, right: EXPR}); 290 | invalidExpr(3, {type: "LogicalExpression"}); 291 | invalidExpr(1, {type: "LogicalExpression", left: EXPR, right: EXPR}); 292 | invalidExpr(1, {type: "LogicalExpression", operator: "||", left: EXPR}); 293 | invalidExpr(1, {type: "LogicalExpression", operator: "||", right: EXPR}); 294 | invalidExpr(1, {type: "LogicalExpression", left: EXPR, right: EXPR}); 295 | invalidExpr(2, {type: "LogicalExpression", operator: "||"}); 296 | invalidExpr(2, {type: "LogicalExpression", left: EXPR}); 297 | invalidExpr(2, {type: "LogicalExpression", right: EXPR}); 298 | invalidExpr(1, {type: "LogicalExpression", operator: "=", left: EXPR, right: EXPR}); 299 | invalidExpr(1, {type: "LogicalExpression", operator: "+", left: EXPR, right: EXPR}); 300 | invalidExpr(1, {type: "LogicalExpression", operator: "||", left: STMT, right: EXPR}); 301 | invalidExpr(1, {type: "LogicalExpression", operator: "||", left: EXPR, right: STMT}); 302 | }); 303 | 304 | test("MemberExpression", function() { 305 | validExpr({type: "MemberExpression", computed: true, object: EXPR, property: EXPR}); 306 | validExpr({type: "MemberExpression", computed: true, object: EXPR, property: ID}); 307 | validExpr({type: "MemberExpression", computed: true, object: EXPR, property: NUM}); 308 | validExpr({type: "MemberExpression", computed: false, object: EXPR, property: ID}); 309 | validExpr({type: "MemberExpression", object: EXPR, property: ID}); 310 | invalidExpr(2, {type: "MemberExpression"}); 311 | invalidExpr(1, {type: "MemberExpression", computed: true, object: EXPR}); 312 | invalidExpr(1, {type: "MemberExpression", computed: true, property: EXPR}); 313 | invalidExpr(1, {type: "MemberExpression", computed: false, object: EXPR, property: NUM}); 314 | invalidExpr(1, {type: "MemberExpression", computed: true, object: STMT, property: EXPR}); 315 | invalidExpr(1, {type: "MemberExpression", computed: false, object: STMT, property: ID}); 316 | invalidExpr(1, {type: "MemberExpression", computed: true, object: EXPR, property: STMT}); 317 | invalidExpr(1, {type: "MemberExpression", computed: false, object: EXPR, property: EXPR}); 318 | }); 319 | 320 | test("NewExpression", function() { 321 | validExpr({type: "NewExpression", callee: EXPR, arguments: []}); 322 | validExpr({type: "NewExpression", callee: EXPR, arguments: [EXPR]}); 323 | validExpr({type: "NewExpression", callee: EXPR, arguments: [EXPR, EXPR, EXPR]}); 324 | invalidExpr(2, {type: "NewExpression"}); 325 | invalidExpr(1, {type: "NewExpression", callee: EXPR}); 326 | invalidExpr(1, {type: "NewExpression", arguments: []}); 327 | invalidExpr(1, {type: "NewExpression", callee: EXPR, arguments: [null]}); 328 | invalidExpr(1, {type: "NewExpression", callee: EXPR, arguments: [STMT]}); 329 | invalidExpr(1, {type: "NewExpression", callee: EXPR, arguments: [EXPR, STMT, EXPR]}); 330 | }); 331 | 332 | test("ObjectExpression", function() { 333 | validExpr({type: "ObjectExpression", properties: []}); 334 | validExpr({type: "ObjectExpression", properties: [{kind: "init", key: ID, value: EXPR}]}); 335 | validExpr({type: "ObjectExpression", properties: [{kind: "init", key: NUM, value: EXPR}]}); 336 | validExpr({type: "ObjectExpression", properties: [{kind: "init", key: STR, value: EXPR}]}); 337 | validExpr({type: "ObjectExpression", properties: [{kind: "init", key: {type: "Identifier", name: "var"}, value: EXPR}]}); 338 | invalidExpr(1, {type: "ObjectExpression"}); 339 | invalidExpr(1, {type: "ObjectExpression", properties: [null]}); 340 | invalidExpr(3, {type: "ObjectExpression", properties: [{}]}); 341 | invalidExpr(1, {type: "ObjectExpression", properties: [{key: ID, value: EXPR}]}); 342 | invalidExpr(1, {type: "ObjectExpression", properties: [{kind: "init", key: ID}]}); 343 | invalidExpr(1, {type: "ObjectExpression", properties: [{kind: "-", key: ID, value: EXPR}]}); 344 | invalidExpr(1, {type: "ObjectExpression", properties: [{kind: "init", value: EXPR}]}); 345 | invalidExpr(1, {type: "ObjectExpression", properties: [{kind: "init", key: STMT, value: EXPR}]}); 346 | invalidExpr(1, {type: "ObjectExpression", properties: [{kind: "init", key: ID, value: STMT}]}); 347 | invalidExpr(1, {type: "ObjectExpression", properties: [{kind: "init", key: ID, value: BLOCK}]}); 348 | invalidExpr(1, {type: "ObjectExpression", properties: [{kind: "init", key: EXPR, value: EXPR}]}); 349 | invalidExpr(1, {type: "ObjectExpression", properties: [{kind: "init", key: {type: "Identifier", name: null}, value: EXPR}]}); 350 | invalidExpr(1, {type: "ObjectExpression", properties: [{kind: "init", key: {type: "Identifier", name: ""}, value: EXPR}]}); 351 | invalidExpr(1, {type: "ObjectExpression", properties: [{kind: "init", key: {type: "Identifier", name: "a-b"}, value: EXPR}]}); 352 | invalidExpr(1, {type: "ObjectExpression", properties: [{kind: "init", key: {type: "Literal", value: null}, value: EXPR}]}); 353 | invalidExpr(1, {type: "ObjectExpression", properties: [{kind: "init", key: {type: "Literal", value: /./}, value: EXPR}]}); 354 | invalidExpr(1, {type: "ObjectExpression", properties: [{kind: "get", key: ID, value: null}]}); 355 | invalidExpr(1, {type: "ObjectExpression", properties: [{kind: "set", key: ID, value: null}]}); 356 | }); 357 | 358 | test("Program", function() { 359 | function valid(x, msg) { assert.ok(esvalid.isValid(x), msg); } 360 | function invalid(x, msg) { assert.ok(!esvalid.isValid(x), msg); } 361 | invalid({type: "Program"}); 362 | invalid({type: "Program", body: null}); 363 | valid({type: "Program", body: []}); 364 | valid({type: "Program", body: [STMT]}); 365 | valid({type: "Program", body: [FD(STMT)]}); 366 | valid({type: "Program", body: [STMT, STMT]}); 367 | valid({type: "Program", body: [STMT, FD(STMT), STMT]}); 368 | invalid({type: "Program", body: [STMT, EXPR, STMT]}); 369 | invalid({type: "Program", body: [{type: "Node"}]}); 370 | }); 371 | 372 | test("ReturnStatement", function() { 373 | validExpr(FE({type: "ReturnStatement"})); 374 | validExpr(FE({type: "ReturnStatement", argument: null})); 375 | validExpr(FE({type: "ReturnStatement", argument: ID})); 376 | validExpr(FE({type: "ReturnStatement", argument: EXPR})); 377 | invalidExpr(1, FE({type: "ReturnStatement", argument: STMT})); 378 | }); 379 | 380 | test("SequenceExpression", function() { 381 | invalidExpr(1, {type: "SequenceExpression"}); 382 | invalidExpr(1, {type: "SequenceExpression", expressions: null}); 383 | invalidExpr(1, {type: "SequenceExpression", expressions: [EXPR, STMT]}); 384 | invalidExpr(1, {type: "SequenceExpression", expressions: [EXPR, null]}); 385 | invalidExpr(2, {type: "SequenceExpression", expressions: [null, null]}); 386 | validExpr({type: "SequenceExpression", expressions: [EXPR, EXPR]}); 387 | validExpr({type: "SequenceExpression", expressions: [EXPR, EXPR, EXPR]}); 388 | validExpr({type: "SequenceExpression", expressions: [EXPR, EXPR, EXPR, EXPR]}); 389 | }); 390 | 391 | test("SwitchCase", function() { 392 | function wrapSwitch(x) { return {type: "SwitchStatement", discriminant: EXPR, cases: [x]}; } 393 | validStmt(wrapSwitch({type: "SwitchCase", test: EXPR, consequent: []})); 394 | validStmt(wrapSwitch({type: "SwitchCase", consequent: []})); 395 | validStmt(wrapSwitch({type: "SwitchCase", test: EXPR, consequent: [STMT]})); 396 | validStmt(wrapSwitch({type: "SwitchCase", test: EXPR, consequent: [STMT, STMT, STMT]})); 397 | invalidStmt(1, wrapSwitch({type: "SwitchCase"})); 398 | invalidStmt(1, wrapSwitch({type: "SwitchCase", test: EXPR})); 399 | invalidStmt(1, wrapSwitch({type: "SwitchCase", test: STMT, consequent: []})); 400 | invalidStmt(1, wrapSwitch({type: "SwitchCase", test: EXPR, consequent: [null]})); 401 | invalidStmt(1, wrapSwitch({type: "SwitchCase", test: EXPR, consequent: [EXPR]})); 402 | invalidStmt(1, wrapSwitch({type: "SwitchCase", test: EXPR, consequent: [STMT, EXPR, STMT]})); 403 | }); 404 | 405 | test("SwitchStatement", function() { 406 | validStmt({type: "SwitchStatement", discriminant: EXPR, cases: []}); 407 | validStmt({type: "SwitchStatement", discriminant: EXPR, cases: [{type: "SwitchCase", test: null, consequent: []}]}); 408 | validStmt({type: "SwitchStatement", discriminant: EXPR, cases: [{type: "SwitchCase", test: EXPR, consequent: []}]}); 409 | validStmt({type: "SwitchStatement", discriminant: EXPR, cases: [{type: "SwitchCase", test: null, consequent: [STMT]}]}); 410 | validStmt({type: "SwitchStatement", discriminant: EXPR, cases: [{type: "SwitchCase", test: EXPR, consequent: [STMT]}]}); 411 | validStmt({type: "SwitchStatement", discriminant: EXPR, cases: [{type: "SwitchCase", test: EXPR, consequent: [STMT]}, {type: "SwitchCase", test: null, consequent: [STMT]}]}); 412 | invalidStmt(2, {type: "SwitchStatement"}); 413 | invalidStmt(1, {type: "SwitchStatement", discriminant: EXPR}); 414 | invalidStmt(1, {type: "SwitchStatement", discriminant: EXPR, cases: null}); 415 | invalidStmt(1, {type: "SwitchStatement", discriminant: EXPR, cases: [null]}); 416 | invalidStmt(1, {type: "SwitchStatement", discriminant: EXPR, cases: [ID]}); 417 | invalidStmt(1, {type: "SwitchStatement", discriminant: EXPR, cases: [{type: "SwitchCase", test: STMT, consequent: []}]}); 418 | invalidStmt(1, {type: "SwitchStatement", discriminant: EXPR, cases: [{type: "SwitchCase", test: EXPR, consequent: [EXPR]}]}); 419 | invalidStmt(1, {type: "SwitchStatement", discriminant: STMT, cases: [{type: "SwitchCase", test: EXPR, consequent: [STMT]}]}); 420 | }); 421 | 422 | test("ThisExpression", function() { 423 | validExpr({type: "ThisExpression"}); 424 | }); 425 | 426 | test("ThrowStatement", function() { 427 | validStmt({type: "ThrowStatement", argument: ID}); 428 | validStmt({type: "ThrowStatement", argument: EXPR}); 429 | invalidStmt(1, {type: "ThrowStatement"}); 430 | invalidStmt(1, {type: "ThrowStatement", argument: null}); 431 | invalidStmt(1, {type: "ThrowStatement", argument: STMT}); 432 | }); 433 | 434 | test("TryStatement", function() { 435 | validStmt({type: "TryStatement", block: BLOCK, handler: CATCH}); 436 | validStmt({type: "TryStatement", block: BLOCK, finalizer: BLOCK}); 437 | validStmt({type: "TryStatement", block: BLOCK, handler: CATCH, finalizer: BLOCK}); 438 | validStmt({type: "TryStatement", block: BLOCK, handlers: [CATCH]}); 439 | validStmt({type: "TryStatement", block: BLOCK, handlers: [CATCH, CATCH]}); 440 | validStmt({type: "TryStatement", block: BLOCK, finalizer: BLOCK}); 441 | validStmt({type: "TryStatement", block: BLOCK, handler: CATCH, finalizer: BLOCK}); 442 | invalidStmt(2, {type: "TryStatement"}); 443 | invalidStmt(1, {type: "TryStatement", block: BLOCK}); 444 | invalidStmt(1, {type: "TryStatement", handler: CATCH}); 445 | invalidStmt(1, {type: "TryStatement", handlers: [CATCH]}); 446 | invalidStmt(1, {type: "TryStatement", block: EXPR, handler: CATCH}); 447 | invalidStmt(1, {type: "TryStatement", block: BLOCK, finalizer: EXPR}); 448 | invalidStmt(1, {type: "TryStatement", block: BLOCK, handler: BLOCK}); 449 | invalidStmt(1, {type: "TryStatement", block: BLOCK, handlers: []}); 450 | invalidStmt(1, {type: "TryStatement", block: BLOCK, handlers: [CATCH, null, CATCH]}); 451 | invalidStmt(1, {type: "TryStatement", block: BLOCK, handlers: [CATCH, BLOCK, CATCH]}); 452 | }); 453 | 454 | test("UnaryExpression", function() { 455 | validExpr({type: "UnaryExpression", operator: "+", argument: EXPR}); 456 | validExpr({type: "UnaryExpression", operator: "!", argument: EXPR}); 457 | invalidExpr(1, {type: "UnaryExpression", operator: "/", argument: EXPR}); 458 | invalidExpr(1, {type: "UnaryExpression", operator: "+", argument: STMT}); 459 | invalidExpr(1, {type: "UnaryExpression", operator: "+"}); 460 | invalidExpr(1, {type: "UnaryExpression", argument: EXPR}); 461 | invalidExpr(2, {type: "UnaryExpression"}); 462 | }); 463 | 464 | test("UpdateExpression", function() { 465 | validExpr({type: "UpdateExpression", operator: "++", argument: EXPR, prefix: true}); 466 | validExpr({type: "UpdateExpression", operator: "++", argument: EXPR}); 467 | validExpr({type: "UpdateExpression", operator: "--", argument: EXPR, prefix: false}); 468 | invalidExpr(1, {type: "UpdateExpression", operator: "+", argument: EXPR, prefix: true}); 469 | invalidExpr(1, {type: "UpdateExpression", operator: "++", argument: STMT}); 470 | invalidExpr(1, {type: "UpdateExpression", operator: "++"}); 471 | invalidExpr(1, {type: "UpdateExpression", argument: EXPR}); 472 | invalidExpr(2, {type: "UpdateExpression"}); 473 | }); 474 | 475 | test("VariableDeclaration", function() { 476 | validStmt({type: "VariableDeclaration", kind: "var", declarations: [{type: "VariableDeclarator", id: ID}]}); 477 | validStmt({type: "VariableDeclaration", kind: "var", declarations: [{type: "VariableDeclarator", id: ID, init: EXPR}]}); 478 | validStmt({type: "VariableDeclaration", kind: "var", declarations: [{type: "VariableDeclarator", id: ID}, {type: "VariableDeclarator", id: ID}]}); 479 | validStmt({type: "VariableDeclaration", kind: "let", declarations: [{type: "VariableDeclarator", id: ID}]}); 480 | validStmt({type: "VariableDeclaration", kind: "const", declarations: [{type: "VariableDeclarator", id: ID}]}); 481 | invalidStmt(1, {type: "VariableDeclaration"}); 482 | invalidStmt(1, {type: "VariableDeclaration", declarations: null}); 483 | invalidStmt(1, {type: "VariableDeclaration", kind: "var", declarations: null}); 484 | invalidStmt(1, {type: "VariableDeclaration", kind: "var", declarations: [null]}); 485 | invalidStmt(1, {type: "VariableDeclaration", kind: "var", declarations: [ID]}); 486 | invalidStmt(1, {type: "VariableDeclaration", declarations: [{type: "VariableDeclarator", id: ID}]}); 487 | invalidStmt(1, {type: "VariableDeclaration", kind: "ng", declarations: [{type: "VariableDeclarator", id: ID}]}); 488 | }); 489 | 490 | test("VariableDeclarator", function() { 491 | function wrapVar(x) { return {type: "VariableDeclaration", kind: "var", declarations: [x]}; } 492 | validStmt(wrapVar({type: "VariableDeclarator", id: ID, init: EXPR})); 493 | validStmt(wrapVar({type: "VariableDeclarator", id: EXPR, init: EXPR})); 494 | validStmt(wrapVar({type: "VariableDeclarator", id: ID})); 495 | invalidStmt(1, wrapVar({type: "VariableDeclarator"})); 496 | invalidStmt(1, wrapVar({type: "VariableDeclarator", init: EXPR})); 497 | invalidStmt(1, wrapVar({type: "VariableDeclarator", id: STMT, init: EXPR})); 498 | invalidStmt(1, wrapVar({type: "VariableDeclarator", id: ID, init: STMT})); 499 | }); 500 | 501 | test("WhileStatement", function() { 502 | validStmt({type: "WhileStatement", test: EXPR, body: STMT}); 503 | invalidStmt(2, {type: "WhileStatement"}); 504 | invalidStmt(1, {type: "WhileStatement", test: EXPR}); 505 | invalidStmt(1, {type: "WhileStatement", body: STMT}); 506 | invalidStmt(1, {type: "WhileStatement", test: EXPR, body: EXPR}); 507 | invalidStmt(1, {type: "WhileStatement", test: STMT, body: STMT}); 508 | }); 509 | 510 | test("WithStatement", function() { 511 | validStmt({type: "WithStatement", object: EXPR, body: STMT}); 512 | invalidStmt(2, {type: "WithStatement"}); 513 | invalidStmt(1, {type: "WithStatement", object: EXPR}); 514 | invalidStmt(1, {type: "WithStatement", body: STMT}); 515 | invalidStmt(1, {type: "WithStatement", object: STMT, body: STMT}); 516 | invalidStmt(1, {type: "WithStatement", object: EXPR, body: EXPR}); 517 | }); 518 | 519 | }); 520 | -------------------------------------------------------------------------------- /test/unit.js: -------------------------------------------------------------------------------- 1 | /* global suite test */ 2 | "use strict"; 3 | 4 | var assert = require("assert"); 5 | 6 | var esvalid = require("../"); 7 | 8 | var helpers = require("../test-helpers"); 9 | var STMT = helpers.STMT, BLOCK = helpers.BLOCK, EXPR = helpers.EXPR, 10 | NUM = helpers.NUM, STR = helpers.STR, ID = helpers.ID, CATCH = helpers.CATCH; 11 | var wrapProgram = helpers.wrapProgram, wrapIter = helpers.wrapIter, 12 | FE = helpers.FE, FD = helpers.FD, label = helpers.label, exprStmt = helpers.exprStmt; 13 | var validExpr = helpers.validExpr, invalidExpr = helpers.invalidExpr, 14 | validStmt = helpers.validStmt, invalidStmt = helpers.invalidStmt; 15 | 16 | suite("unit", function() { 17 | 18 | test("non-nodes", function() { 19 | function invalid(x, msg) { 20 | assert.ok(!esvalid.isValid(x), msg); 21 | var errors = esvalid.errors(x); 22 | assert.notEqual(errors.length, 0, msg); 23 | invalidStmt(errors.length, x, msg); 24 | invalidExpr(errors.length, x, msg); 25 | } 26 | invalid(null); 27 | invalid(0); 28 | invalid({}); 29 | invalid("Program"); 30 | invalid({type: null}); 31 | invalid({type: false}); 32 | invalid({type: ""}); 33 | invalid({type: "Node"}); 34 | }); 35 | 36 | test("BreakStatement", function() { 37 | validStmt(label(ID.name, wrapIter({type: "BreakStatement", label: ID}))); 38 | validStmt({type: "SwitchStatement", discriminant: EXPR, cases: [{type: "SwitchCase", test: null, consequent: [{type: "BreakStatement"}]}]}); 39 | invalidStmt(1, wrapIter({type: "BreakStatement", label:ID})); 40 | invalidStmt(1, label(ID.name + ID.name, wrapIter({type: "BreakStatement", label: ID}))); 41 | invalidStmt(1, {type: "SwitchStatement", discriminant: EXPR, cases: [{type: "SwitchCase", test: null, consequent: [{type: "BreakStatement", label: ID}]}]}); 42 | }); 43 | 44 | test("ContinueStatement", function() { 45 | validStmt(label(ID.name, wrapIter({type: "ContinueStatement", label: ID}))); 46 | invalidStmt(1, wrapIter({type: "ContinueStatement", label:ID})); 47 | invalidStmt(1, label(ID.name + ID.name, wrapIter({type: "ContinueStatement", label: ID}))); 48 | }); 49 | 50 | test("Identifier `name` member must be a valid IdentifierName", function() { 51 | validExpr({type: "Identifier", name: "x"}); 52 | validExpr({type: "Identifier", name: "$"}); 53 | validExpr({type: "Identifier", name: "_"}); 54 | validExpr({type: "Identifier", name: "_$0x"}); 55 | invalidExpr(1, {type: "Identifier", name: ""}); 56 | invalidExpr(1, {type: "Identifier", name: "a-b"}); 57 | invalidExpr(1, {type: "Identifier", name: "0x0"}); 58 | }); 59 | 60 | test("Identifier `name` member must not be a ReservedWord", function() { 61 | validExpr({type: "Identifier", name: "varx"}); 62 | validExpr({type: "Identifier", name: "xvar"}); 63 | validExpr({type: "Identifier", name: "varif"}); 64 | validExpr({type: "Identifier", name: "if_var"}); 65 | validExpr({type: "Identifier", name: "function0"}); 66 | invalidExpr(1, {type: "Identifier", name: "if"}); 67 | invalidExpr(1, {type: "Identifier", name: "var"}); 68 | invalidExpr(1, {type: "Identifier", name: "function"}); 69 | }); 70 | 71 | test("IfStatement with null `alternate` must not be the `consequent` of an IfStatement with a non-null `alternate`", function() { 72 | validStmt({type: "IfStatement", test: EXPR, consequent: {type: "DoWhileStatement", test: EXPR, body: {type: "IfStatement", test: EXPR, consequent: STMT}}, alternate: STMT}); 73 | invalidStmt(1, {type: "IfStatement", test: EXPR, consequent: {type: "IfStatement", test: EXPR, consequent: STMT}, alternate: STMT}); 74 | invalidStmt(1, {type: "IfStatement", test: EXPR, consequent: {type: "IfStatement", test: EXPR, consequent: STMT, alternate: {type: "IfStatement", test: EXPR, consequent: STMT}}, alternate: STMT}); 75 | invalidStmt(1, {type: "IfStatement", test: EXPR, consequent: {type: "IfStatement", test: EXPR, consequent: {type: "IfStatement", test: EXPR, consequent: STMT}}, alternate: STMT}); 76 | invalidStmt(1, {type: "IfStatement", test: EXPR, consequent: {type: "LabeledStatement", label: ID, body: {type: "IfStatement", test: EXPR, consequent: STMT}}, alternate: STMT}); 77 | invalidStmt(1, {type: "IfStatement", test: EXPR, consequent: {type: "WhileStatement", test: EXPR, body: {type: "IfStatement", test: EXPR, consequent: STMT}}, alternate: STMT}); 78 | invalidStmt(1, {type: "IfStatement", test: EXPR, consequent: {type: "WithStatement", object: EXPR, body: {type: "IfStatement", test: EXPR, consequent: STMT}}, alternate: STMT}); 79 | invalidStmt(1, {type: "IfStatement", test: EXPR, consequent: {type: "ForStatement", init: EXPR, test: EXPR, update: EXPR, body: {type: "IfStatement", test: EXPR, consequent: STMT}}, alternate: STMT}); 80 | invalidStmt(1, {type: "IfStatement", test: EXPR, consequent: {type: "ForInStatement", left: EXPR, right: EXPR, body: {type: "IfStatement", test: EXPR, consequent: STMT}}, alternate: STMT}); 81 | }); 82 | 83 | test("LabeledStatement must not be nested within a LabeledStatement with the same label", function() { 84 | validStmt(label("a", label("b", STMT))); 85 | validStmt(label("y", exprStmt(FE(FD(label("a", STMT)))))); 86 | invalidStmt(1, label("a", label("a", STMT))); 87 | invalidStmt(1, label("a", exprStmt(FE(label("a", STMT))))); 88 | }); 89 | 90 | test("numeric Literal nodes must not be NaN", function() { 91 | invalidExpr(1, {type: "Literal", value: 0 / 0}); 92 | }); 93 | 94 | test("numeric Literal nodes must be non-negative", function() { 95 | validExpr({type: "Literal", value: 1 / 0}); 96 | validExpr({type: "Literal", value: 1e308}); 97 | validExpr({type: "Literal", value: 1}); 98 | validExpr({type: "Literal", value: 1e-308}); 99 | validExpr({type: "Literal", value: 0}); 100 | invalidExpr(1, {type: "Literal", value: -0}); 101 | invalidExpr(1, {type: "Literal", value: -1e-308}); 102 | invalidExpr(1, {type: "Literal", value: -1}); 103 | invalidExpr(1, {type: "Literal", value: -1e308}); 104 | invalidExpr(1, {type: "Literal", value: -1 / 0}); 105 | }); 106 | 107 | test("static MemberExpression `property` member must have a valid IdentifierName `name` member", function() { 108 | validExpr({type: "MemberExpression", computed: false, object: EXPR, property: {type: "Identifier", name: "var"}}); 109 | invalidExpr(1, {type: "MemberExpression", computed: false, object: EXPR, property: {type: "Identifier"}}); 110 | invalidExpr(1, {type: "MemberExpression", computed: false, object: EXPR, property: {type: "Identifier", name: null}}); 111 | invalidExpr(1, {type: "MemberExpression", computed: false, object: EXPR, property: {type: "Identifier", name: ""}}); 112 | invalidExpr(1, {type: "MemberExpression", computed: false, object: EXPR, property: {type: "Identifier", name: "0"}}); 113 | invalidExpr(1, {type: "MemberExpression", computed: false, object: EXPR, property: {type: "Identifier", name: "a-b"}}); 114 | }); 115 | 116 | test("ObjectExpression conflicting init/get/set properties", function() { 117 | var init = {kind: "init", key: ID, value: ID}; 118 | var getter = {kind: "get", key: ID, value: {type: "FunctionExpression", params: [], body: BLOCK}}; 119 | var setter = {kind: "set", key: ID, value: {type: "FunctionExpression", params: [ID], body: BLOCK}}; 120 | validExpr({type: "ObjectExpression", properties: [init, init]}); 121 | invalidExpr(1, {type: "ObjectExpression", properties: [init, getter]}); 122 | invalidExpr(1, {type: "ObjectExpression", properties: [init, setter]}); 123 | validExpr({type: "ObjectExpression", properties: [getter, setter]}); 124 | invalidExpr(1, {type: "ObjectExpression", properties: [getter, init]}); 125 | invalidExpr(1, {type: "ObjectExpression", properties: [getter, getter]}); 126 | validExpr({type: "ObjectExpression", properties: [setter, getter]}); 127 | invalidExpr(1, {type: "ObjectExpression", properties: [setter, init]}); 128 | invalidExpr(1, {type: "ObjectExpression", properties: [setter, setter]}); 129 | }); 130 | 131 | test("ObjectExpression getter property `value` member must have zero parameters", function() { 132 | validExpr({type: "ObjectExpression", properties: [{kind: "get", key: ID, value: {type: "FunctionExpression", params: [], body: BLOCK}}]}); 133 | invalidExpr(1, {type: "ObjectExpression", properties: [{kind: "get", key: ID, value: ID}]}); 134 | invalidExpr(2, {type: "ObjectExpression", properties: [{kind: "get", key: ID, value: {type: "FunctionDeclaration", id: ID, params: [], body: BLOCK}}]}); 135 | invalidExpr(1, {type: "ObjectExpression", properties: [{kind: "get", key: ID, value: {type: "FunctionExpression", params: [ID], body: BLOCK}}]}); 136 | }); 137 | 138 | test("ObjectExpression setter property `value` member must have exactly one parameter", function() { 139 | validExpr({type: "ObjectExpression", properties: [{kind: "set", key: ID, value: {type: "FunctionExpression", params: [ID], body: BLOCK}}]}); 140 | invalidExpr(1, {type: "ObjectExpression", properties: [{kind: "set", key: ID, value: ID}]}); 141 | invalidExpr(1, {type: "ObjectExpression", properties: [{kind: "set", key: ID, value: {type: "FunctionExpression", params: [], body: BLOCK}}]}); 142 | invalidExpr(2, {type: "ObjectExpression", properties: [{kind: "set", key: ID, value: {type: "FunctionDeclaration", id: ID, params: [ID], body: BLOCK}}]}); 143 | invalidExpr(1, {type: "ObjectExpression", properties: [{kind: "set", key: ID, value: {type: "FunctionExpression", params: [ID, ID], body: BLOCK}}]}); 144 | }); 145 | 146 | test("ReturnStatement must be nested within a FunctionExpression or FunctionDeclaration node", function() { 147 | validExpr(FE({type: "ReturnStatement"})); 148 | validStmt(FD({type: "ReturnStatement"})); 149 | invalidStmt(1, {type: "ReturnStatement"}); 150 | }); 151 | 152 | test("SequenceExpression `expressions` member length must be >= 2", function() { 153 | invalidExpr(1, {type: "SequenceExpression", expressions: []}); 154 | invalidExpr(1, {type: "SequenceExpression", expressions: [EXPR]}); 155 | validExpr({type: "SequenceExpression", expressions: [EXPR, EXPR]}); 156 | validExpr({type: "SequenceExpression", expressions: [EXPR, EXPR, EXPR]}); 157 | validExpr({type: "SequenceExpression", expressions: [EXPR, EXPR, EXPR, EXPR]}); 158 | }); 159 | 160 | test("SwitchStatement `cases` member must contain no more than one SwitchCase with a null `test` member", function() { 161 | validStmt({type: "SwitchStatement", discriminant: EXPR, cases: [{type: "SwitchCase", test: null, consequent: [STMT]}]}); 162 | invalidStmt(1, {type: "SwitchStatement", discriminant: EXPR, cases: [{type: "SwitchCase", test: null, consequent: [STMT]}, {type: "SwitchCase", test: null, consequent: [STMT]}]}); 163 | }); 164 | 165 | test("TryStatement must have a non-null `handler` member or a non-null `finalizer` member", function() { 166 | validStmt({type: "TryStatement", block: BLOCK, handler: CATCH}); 167 | validStmt({type: "TryStatement", block: BLOCK, finalizer: BLOCK}); 168 | invalidStmt(1, {type: "TryStatement", block: BLOCK}); 169 | }); 170 | 171 | test("VariableDeclaration `declarations` member must be non-empty", function() { 172 | validStmt({type: "VariableDeclaration", kind: "var", declarations: [{type: "VariableDeclarator", id: ID}]}); 173 | invalidStmt(1, {type: "VariableDeclaration", kind: "var", declarations: []}); 174 | }); 175 | 176 | }); 177 | --------------------------------------------------------------------------------