UNPKG

d2-ui

Version:
1,948 lines (1,620 loc) 149 kB
/*! * JSHint, by JSHint Community. * * This file (and this file only) is licensed under the same slightly modified * MIT license that JSLint is. It stops evil-doers everywhere: * * Copyright (c) 2002 Douglas Crockford (www.JSLint.com) * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom * the Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * The Software shall be used for Good, not Evil. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ /*jshint quotmark:double */ /*global console:true */ /*exported console */ var _ = require("lodash"); var events = require("events"); var vars = require("./vars.js"); var messages = require("./messages.js"); var Lexer = require("./lex.js").Lexer; var reg = require("./reg.js"); var state = require("./state.js").state; var style = require("./style.js"); var options = require("./options.js"); var scopeManager = require("./scope-manager.js"); // We need this module here because environments such as IE and Rhino // don't necessarilly expose the 'console' API and browserify uses // it to log things. It's a sad state of affair, really. var console = require("console-browserify"); // We build the application inside a function so that we produce only a singleton // variable. That function will be invoked immediately, and its return value is // the JSHINT function itself. var JSHINT = (function() { "use strict"; var api, // Extension API // These are operators that should not be used with the ! operator. bang = { "<" : true, "<=" : true, "==" : true, "===": true, "!==": true, "!=" : true, ">" : true, ">=" : true, "+" : true, "-" : true, "*" : true, "/" : true, "%" : true }, declared, // Globals that were declared using /*global ... */ syntax. functionicity = [ "closure", "exception", "global", "label", "outer", "unused", "var" ], functions, // All of the functions inblock, indent, lookahead, lex, member, membersOnly, predefined, // Global variables defined by option stack, urls, extraModules = [], emitter = new events.EventEmitter(); function checkOption(name, t) { name = name.trim(); if (/^[+-]W\d{3}$/g.test(name)) { return true; } if (options.validNames.indexOf(name) === -1) { if (t.type !== "jslint" && !_.has(options.removed, name)) { error("E001", t, name); return false; } } return true; } function isString(obj) { return Object.prototype.toString.call(obj) === "[object String]"; } function isIdentifier(tkn, value) { if (!tkn) return false; if (!tkn.identifier || tkn.value !== value) return false; return true; } function isReserved(token) { if (!token.reserved) { return false; } var meta = token.meta; if (meta && meta.isFutureReservedWord) { if (meta.moduleOnly && !state.option.module) { return false; } if (state.inES5()) { // ES3 FutureReservedWord in an ES5 environment. if (!meta.es5) { return false; } // Some ES5 FutureReservedWord identifiers are active only // within a strict mode environment. if (meta.strictOnly) { if (!state.option.strict && !state.isStrict()) { return false; } } if (token.isProperty) { return false; } } } return true; } function supplant(str, data) { return str.replace(/\{([^{}]*)\}/g, function(a, b) { var r = data[b]; return typeof r === "string" || typeof r === "number" ? r : a; }); } function combine(dest, src) { Object.keys(src).forEach(function(name) { if (_.has(JSHINT.blacklist, name)) return; dest[name] = src[name]; }); } function processenforceall() { if (state.option.enforceall) { for (var enforceopt in options.bool.enforcing) { if (state.option[enforceopt] === undefined && !options.noenforceall[enforceopt]) { state.option[enforceopt] = true; } } for (var relaxopt in options.bool.relaxing) { if (state.option[relaxopt] === undefined) { state.option[relaxopt] = false; } } } } function assume() { var badESOpt = null; processenforceall(); /** * TODO: Remove in JSHint 3 */ badESOpt = state.inferEsVersion(); if (badESOpt) { quit("E059", state.tokens.next, "esversion", badESOpt); } if (state.inES5()) { combine(predefined, vars.ecmaIdentifiers[5]); } if (state.inES6()) { combine(predefined, vars.ecmaIdentifiers[6]); } /** * Use `in` to check for the presence of any explicitly-specified value for * `globalstrict` because both `true` and `false` should trigger an error. */ if (state.option.strict === "global" && "globalstrict" in state.option) { quit("E059", state.tokens.next, "strict", "globalstrict"); } if (state.option.module) { /** * TODO: Extend this restriction to *all* ES6-specific options. */ if (!state.inES6()) { warning("W134", state.tokens.next, "module", 6); } } if (state.option.couch) { combine(predefined, vars.couch); } if (state.option.qunit) { combine(predefined, vars.qunit); } if (state.option.rhino) { combine(predefined, vars.rhino); } if (state.option.shelljs) { combine(predefined, vars.shelljs); combine(predefined, vars.node); } if (state.option.typed) { combine(predefined, vars.typed); } if (state.option.phantom) { combine(predefined, vars.phantom); } if (state.option.prototypejs) { combine(predefined, vars.prototypejs); } if (state.option.node) { combine(predefined, vars.node); combine(predefined, vars.typed); } if (state.option.devel) { combine(predefined, vars.devel); } if (state.option.dojo) { combine(predefined, vars.dojo); } if (state.option.browser) { combine(predefined, vars.browser); combine(predefined, vars.typed); } if (state.option.browserify) { combine(predefined, vars.browser); combine(predefined, vars.typed); combine(predefined, vars.browserify); } if (state.option.nonstandard) { combine(predefined, vars.nonstandard); } if (state.option.jasmine) { combine(predefined, vars.jasmine); } if (state.option.jquery) { combine(predefined, vars.jquery); } if (state.option.mootools) { combine(predefined, vars.mootools); } if (state.option.worker) { combine(predefined, vars.worker); } if (state.option.wsh) { combine(predefined, vars.wsh); } if (state.option.yui) { combine(predefined, vars.yui); } if (state.option.mocha) { combine(predefined, vars.mocha); } } // Produce an error warning. function quit(code, token, a, b) { var percentage = Math.floor((token.line / state.lines.length) * 100); var message = messages.errors[code].desc; var exception = { name: "JSHintError", line: token.line, character: token.from, message: message + " (" + percentage + "% scanned).", raw: message, code: code, a: a, b: b }; exception.reason = supplant(message, exception) + " (" + percentage + "% scanned)."; throw exception; } function removeIgnoredMessages() { var ignored = state.ignoredLines; if (_.isEmpty(ignored)) return; JSHINT.errors = _.reject(JSHINT.errors, function(err) { return ignored[err.line] }); } function warning(code, t, a, b, c, d) { var ch, l, w, msg; if (/^W\d{3}$/.test(code)) { if (state.ignored[code]) return; msg = messages.warnings[code]; } else if (/E\d{3}/.test(code)) { msg = messages.errors[code]; } else if (/I\d{3}/.test(code)) { msg = messages.info[code]; } t = t || state.tokens.next || {}; if (t.id === "(end)") { // `~ t = state.tokens.curr; } l = t.line; ch = t.from; w = { id: "(error)", raw: msg.desc, code: msg.code, evidence: state.lines[l - 1] || "", line: l, character: ch, scope: JSHINT.scope, a: a, b: b, c: c, d: d }; w.reason = supplant(msg.desc, w); JSHINT.errors.push(w); removeIgnoredMessages(); if (JSHINT.errors.length >= state.option.maxerr) quit("E043", t); return w; } function warningAt(m, l, ch, a, b, c, d) { return warning(m, { line: l, from: ch }, a, b, c, d); } function error(m, t, a, b, c, d) { warning(m, t, a, b, c, d); } function errorAt(m, l, ch, a, b, c, d) { return error(m, { line: l, from: ch }, a, b, c, d); } // Tracking of "internal" scripts, like eval containing a static string function addInternalSrc(elem, src) { var i; i = { id: "(internal)", elem: elem, value: src }; JSHINT.internals.push(i); return i; } function doOption() { var nt = state.tokens.next; var body = nt.body.split(",").map(function(s) { return s.trim(); }); var predef = {}; if (nt.type === "globals") { body.forEach(function(g, idx) { g = g.split(":"); var key = (g[0] || "").trim(); var val = (g[1] || "").trim(); if (key === "-" || !key.length) { // Ignore trailing comma if (idx > 0 && idx === body.length - 1) { return; } error("E002", nt); return; } if (key.charAt(0) === "-") { key = key.slice(1); val = false; JSHINT.blacklist[key] = key; delete predefined[key]; } else { predef[key] = (val === "true"); } }); combine(predefined, predef); for (var key in predef) { if (_.has(predef, key)) { declared[key] = nt; } } } if (nt.type === "exported") { body.forEach(function(e, idx) { if (!e.length) { // Ignore trailing comma if (idx > 0 && idx === body.length - 1) { return; } error("E002", nt); return; } state.funct["(scope)"].addExported(e); }); } if (nt.type === "members") { membersOnly = membersOnly || {}; body.forEach(function(m) { var ch1 = m.charAt(0); var ch2 = m.charAt(m.length - 1); if (ch1 === ch2 && (ch1 === "\"" || ch1 === "'")) { m = m .substr(1, m.length - 2) .replace("\\\"", "\""); } membersOnly[m] = false; }); } var numvals = [ "maxstatements", "maxparams", "maxdepth", "maxcomplexity", "maxerr", "maxlen", "indent" ]; if (nt.type === "jshint" || nt.type === "jslint") { body.forEach(function(g) { g = g.split(":"); var key = (g[0] || "").trim(); var val = (g[1] || "").trim(); if (!checkOption(key, nt)) { return; } if (numvals.indexOf(key) >= 0) { // GH988 - numeric options can be disabled by setting them to `false` if (val !== "false") { val = +val; if (typeof val !== "number" || !isFinite(val) || val <= 0 || Math.floor(val) !== val) { error("E032", nt, g[1].trim()); return; } state.option[key] = val; } else { state.option[key] = key === "indent" ? 4 : false; } return; } if (key === "validthis") { // `validthis` is valid only within a function scope. if (state.funct["(global)"]) return void error("E009"); if (val !== "true" && val !== "false") return void error("E002", nt); state.option.validthis = (val === "true"); return; } if (key === "quotmark") { switch (val) { case "true": case "false": state.option.quotmark = (val === "true"); break; case "double": case "single": state.option.quotmark = val; break; default: error("E002", nt); } return; } if (key === "shadow") { switch (val) { case "true": state.option.shadow = true; break; case "outer": state.option.shadow = "outer"; break; case "false": case "inner": state.option.shadow = "inner"; break; default: error("E002", nt); } return; } if (key === "unused") { switch (val) { case "true": state.option.unused = true; break; case "false": state.option.unused = false; break; case "vars": case "strict": state.option.unused = val; break; default: error("E002", nt); } return; } if (key === "latedef") { switch (val) { case "true": state.option.latedef = true; break; case "false": state.option.latedef = false; break; case "nofunc": state.option.latedef = "nofunc"; break; default: error("E002", nt); } return; } if (key === "ignore") { switch (val) { case "line": state.ignoredLines[nt.line] = true; removeIgnoredMessages(); break; default: error("E002", nt); } return; } if (key === "strict") { switch (val) { case "true": state.option.strict = true; break; case "false": state.option.strict = false; break; case "global": case "implied": state.option.strict = val; break; default: error("E002", nt); } return; } if (key === "module") { /** * TODO: Extend this restriction to *all* "environmental" options. */ if (!hasParsedCode(state.funct)) { error("E055", state.tokens.next, "module"); } } if (key === "esversion") { switch (val) { case "3": case "5": case "6": state.option.moz = false; state.option.esversion = +val; break; case "2015": state.option.moz = false; state.option.esversion = 6; break; default: error("E002", nt); } if (!hasParsedCode(state.funct)) { error("E055", state.tokens.next, "esversion"); } return; } var match = /^([+-])(W\d{3})$/g.exec(key); if (match) { // ignore for -W..., unignore for +W... state.ignored[match[2]] = (match[1] === "-"); return; } var tn; if (val === "true" || val === "false") { if (nt.type === "jslint") { tn = options.renamed[key] || key; state.option[tn] = (val === "true"); if (options.inverted[tn] !== undefined) { state.option[tn] = !state.option[tn]; } } else { state.option[key] = (val === "true"); } return; } error("E002", nt); }); assume(); } } // We need a peek function. If it has an argument, it peeks that much farther // ahead. It is used to distinguish // for ( var i in ... // from // for ( var i = ... function peek(p) { var i = p || 0, j = lookahead.length, t; if (i < j) { return lookahead[i]; } while (j <= i) { t = lookahead[j]; if (!t) { t = lookahead[j] = lex.token(); } j += 1; } // Peeking past the end of the program should produce the "(end)" token. if (!t && state.tokens.next.id === "(end)") { return state.tokens.next; } return t; } function peekIgnoreEOL() { var i = 0; var t; do { t = peek(i++); } while (t.id === "(endline)"); return t; } // Produce the next token. It looks for programming errors. function advance(id, t) { switch (state.tokens.curr.id) { case "(number)": if (state.tokens.next.id === ".") { warning("W005", state.tokens.curr); } break; case "-": if (state.tokens.next.id === "-" || state.tokens.next.id === "--") { warning("W006"); } break; case "+": if (state.tokens.next.id === "+" || state.tokens.next.id === "++") { warning("W007"); } break; } if (id && state.tokens.next.id !== id) { if (t) { if (state.tokens.next.id === "(end)") { error("E019", t, t.id); } else { error("E020", state.tokens.next, id, t.id, t.line, state.tokens.next.value); } } else if (state.tokens.next.type !== "(identifier)" || state.tokens.next.value !== id) { warning("W116", state.tokens.next, id, state.tokens.next.value); } } state.tokens.prev = state.tokens.curr; state.tokens.curr = state.tokens.next; for (;;) { state.tokens.next = lookahead.shift() || lex.token(); if (!state.tokens.next) { // No more tokens left, give up quit("E041", state.tokens.curr); } if (state.tokens.next.id === "(end)" || state.tokens.next.id === "(error)") { return; } if (state.tokens.next.check) { state.tokens.next.check(); } if (state.tokens.next.isSpecial) { if (state.tokens.next.type === "falls through") { state.tokens.curr.caseFallsThrough = true; } else { doOption(); } } else { if (state.tokens.next.id !== "(endline)") { break; } } } } function isInfix(token) { return token.infix || (!token.identifier && !token.template && !!token.led); } function isEndOfExpr() { var curr = state.tokens.curr; var next = state.tokens.next; if (next.id === ";" || next.id === "}" || next.id === ":") { return true; } if (isInfix(next) === isInfix(curr) || (curr.id === "yield" && state.inMoz())) { return curr.line !== startLine(next); } return false; } function isBeginOfExpr(prev) { return !prev.left && prev.arity !== "unary"; } // This is the heart of JSHINT, the Pratt parser. In addition to parsing, it // is looking for ad hoc lint patterns. We add .fud to Pratt's model, which is // like .nud except that it is only used on the first token of a statement. // Having .fud makes it much easier to define statement-oriented languages like // JavaScript. I retained Pratt's nomenclature. // .nud Null denotation // .fud First null denotation // .led Left denotation // lbp Left binding power // rbp Right binding power // They are elements of the parsing method called Top Down Operator Precedence. function expression(rbp, initial) { var left, isArray = false, isObject = false, isLetExpr = false; state.nameStack.push(); // if current expression is a let expression if (!initial && state.tokens.next.value === "let" && peek(0).value === "(") { if (!state.inMoz()) { warning("W118", state.tokens.next, "let expressions"); } isLetExpr = true; // create a new block scope we use only for the current expression state.funct["(scope)"].stack(); advance("let"); advance("("); state.tokens.prev.fud(); advance(")"); } if (state.tokens.next.id === "(end)") error("E006", state.tokens.curr); var isDangerous = state.option.asi && state.tokens.prev.line !== startLine(state.tokens.curr) && _.contains(["]", ")"], state.tokens.prev.id) && _.contains(["[", "("], state.tokens.curr.id); if (isDangerous) warning("W014", state.tokens.curr, state.tokens.curr.id); advance(); if (initial) { state.funct["(verb)"] = state.tokens.curr.value; state.tokens.curr.beginsStmt = true; } if (initial === true && state.tokens.curr.fud) { left = state.tokens.curr.fud(); } else { if (state.tokens.curr.nud) { left = state.tokens.curr.nud(); } else { error("E030", state.tokens.curr, state.tokens.curr.id); } while (rbp < state.tokens.next.lbp && !isEndOfExpr()) { isArray = state.tokens.curr.value === "Array"; isObject = state.tokens.curr.value === "Object"; // #527, new Foo.Array(), Foo.Array(), new Foo.Object(), Foo.Object() // Line breaks in IfStatement heads exist to satisfy the checkJSHint // "Line too long." error. if (left && (left.value || (left.first && left.first.value))) { // If the left.value is not "new", or the left.first.value is a "." // then safely assume that this is not "new Array()" and possibly // not "new Object()"... if (left.value !== "new" || (left.first && left.first.value && left.first.value === ".")) { isArray = false; // ...In the case of Object, if the left.value and state.tokens.curr.value // are not equal, then safely assume that this not "new Object()" if (left.value !== state.tokens.curr.value) { isObject = false; } } } advance(); if (isArray && state.tokens.curr.id === "(" && state.tokens.next.id === ")") { warning("W009", state.tokens.curr); } if (isObject && state.tokens.curr.id === "(" && state.tokens.next.id === ")") { warning("W010", state.tokens.curr); } if (left && state.tokens.curr.led) { left = state.tokens.curr.led(left); } else { error("E033", state.tokens.curr, state.tokens.curr.id); } } } if (isLetExpr) { state.funct["(scope)"].unstack(); } state.nameStack.pop(); return left; } // Functions for conformance of style. function startLine(token) { return token.startLine || token.line; } function nobreaknonadjacent(left, right) { left = left || state.tokens.curr; right = right || state.tokens.next; if (!state.option.laxbreak && left.line !== startLine(right)) { warning("W014", right, right.value); } } function nolinebreak(t) { t = t || state.tokens.curr; if (t.line !== startLine(state.tokens.next)) { warning("E022", t, t.value); } } function nobreakcomma(left, right) { if (left.line !== startLine(right)) { if (!state.option.laxcomma) { if (parseComma.first) { warning("I001"); parseComma.first = false; } warning("W014", left, right.value); } } } function parseComma(opts) { opts = opts || {}; if (!opts.peek) { nobreakcomma(state.tokens.curr, state.tokens.next); advance(","); } else { nobreakcomma(state.tokens.prev, state.tokens.curr); } if (state.tokens.next.identifier && !(opts.property && state.inES5())) { // Keywords that cannot follow a comma operator. switch (state.tokens.next.value) { case "break": case "case": case "catch": case "continue": case "default": case "do": case "else": case "finally": case "for": case "if": case "in": case "instanceof": case "return": case "switch": case "throw": case "try": case "var": case "let": case "while": case "with": error("E024", state.tokens.next, state.tokens.next.value); return false; } } if (state.tokens.next.type === "(punctuator)") { switch (state.tokens.next.value) { case "}": case "]": case ",": if (opts.allowTrailing) { return true; } /* falls through */ case ")": error("E024", state.tokens.next, state.tokens.next.value); return false; } } return true; } // Functional constructors for making the symbols that will be inherited by // tokens. function symbol(s, p) { var x = state.syntax[s]; if (!x || typeof x !== "object") { state.syntax[s] = x = { id: s, lbp: p, value: s }; } return x; } function delim(s) { var x = symbol(s, 0); x.delim = true; return x; } function stmt(s, f) { var x = delim(s); x.identifier = x.reserved = true; x.fud = f; return x; } function blockstmt(s, f) { var x = stmt(s, f); x.block = true; return x; } function reserveName(x) { var c = x.id.charAt(0); if ((c >= "a" && c <= "z") || (c >= "A" && c <= "Z")) { x.identifier = x.reserved = true; } return x; } function prefix(s, f) { var x = symbol(s, 150); reserveName(x); x.nud = (typeof f === "function") ? f : function() { this.arity = "unary"; this.right = expression(150); if (this.id === "++" || this.id === "--") { if (state.option.plusplus) { warning("W016", this, this.id); } else if (this.right && (!this.right.identifier || isReserved(this.right)) && this.right.id !== "." && this.right.id !== "[") { warning("W017", this); } if (this.right && this.right.isMetaProperty) { error("E031", this); // detect increment/decrement of a const // in the case of a.b, right will be the "." punctuator } else if (this.right && this.right.identifier) { state.funct["(scope)"].block.modify(this.right.value, this); } } return this; }; return x; } function type(s, f) { var x = delim(s); x.type = s; x.nud = f; return x; } function reserve(name, func) { var x = type(name, func); x.identifier = true; x.reserved = true; return x; } function FutureReservedWord(name, meta) { var x = type(name, (meta && meta.nud) || function() { return this; }); meta = meta || {}; meta.isFutureReservedWord = true; x.value = name; x.identifier = true; x.reserved = true; x.meta = meta; return x; } function reservevar(s, v) { return reserve(s, function() { if (typeof v === "function") { v(this); } return this; }); } function infix(s, f, p, w) { var x = symbol(s, p); reserveName(x); x.infix = true; x.led = function(left) { if (!w) { nobreaknonadjacent(state.tokens.prev, state.tokens.curr); } if ((s === "in" || s === "instanceof") && left.id === "!") { warning("W018", left, "!"); } if (typeof f === "function") { return f(left, this); } else { this.left = left; this.right = expression(p); return this; } }; return x; } function application(s) { var x = symbol(s, 42); x.led = function(left) { nobreaknonadjacent(state.tokens.prev, state.tokens.curr); this.left = left; this.right = doFunction({ type: "arrow", loneArg: left }); return this; }; return x; } function relation(s, f) { var x = symbol(s, 100); x.led = function(left) { nobreaknonadjacent(state.tokens.prev, state.tokens.curr); this.left = left; var right = this.right = expression(100); if (isIdentifier(left, "NaN") || isIdentifier(right, "NaN")) { warning("W019", this); } else if (f) { f.apply(this, [left, right]); } if (!left || !right) { quit("E041", state.tokens.curr); } if (left.id === "!") { warning("W018", left, "!"); } if (right.id === "!") { warning("W018", right, "!"); } return this; }; return x; } function isPoorRelation(node) { return node && ((node.type === "(number)" && +node.value === 0) || (node.type === "(string)" && node.value === "") || (node.type === "null" && !state.option.eqnull) || node.type === "true" || node.type === "false" || node.type === "undefined"); } var typeofValues = {}; typeofValues.legacy = [ // E4X extended the `typeof` operator to return "xml" for the XML and // XMLList types it introduced. // Ref: 11.3.2 The typeof Operator // http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-357.pdf "xml", // IE<9 reports "unknown" when the `typeof` operator is applied to an // object existing across a COM+ bridge. In lieu of official documentation // (which does not exist), see: // http://robertnyman.com/2005/12/21/what-is-typeof-unknown/ "unknown" ]; typeofValues.es3 = [ "undefined", "boolean", "number", "string", "function", "object", ]; typeofValues.es3 = typeofValues.es3.concat(typeofValues.legacy); typeofValues.es6 = typeofValues.es3.concat("symbol"); // Checks whether the 'typeof' operator is used with the correct // value. For docs on 'typeof' see: // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/typeof function isTypoTypeof(left, right, state) { var values; if (state.option.notypeof) return false; if (!left || !right) return false; values = state.inES6() ? typeofValues.es6 : typeofValues.es3; if (right.type === "(identifier)" && right.value === "typeof" && left.type === "(string)") return !_.contains(values, left.value); return false; } function isGlobalEval(left, state) { var isGlobal = false; // permit methods to refer to an "eval" key in their own context if (left.type === "this" && state.funct["(context)"] === null) { isGlobal = true; } // permit use of "eval" members of objects else if (left.type === "(identifier)") { if (state.option.node && left.value === "global") { isGlobal = true; } else if (state.option.browser && (left.value === "window" || left.value === "document")) { isGlobal = true; } } return isGlobal; } function findNativePrototype(left) { var natives = [ "Array", "ArrayBuffer", "Boolean", "Collator", "DataView", "Date", "DateTimeFormat", "Error", "EvalError", "Float32Array", "Float64Array", "Function", "Infinity", "Intl", "Int16Array", "Int32Array", "Int8Array", "Iterator", "Number", "NumberFormat", "Object", "RangeError", "ReferenceError", "RegExp", "StopIteration", "String", "SyntaxError", "TypeError", "Uint16Array", "Uint32Array", "Uint8Array", "Uint8ClampedArray", "URIError" ]; function walkPrototype(obj) { if (typeof obj !== "object") return; return obj.right === "prototype" ? obj : walkPrototype(obj.left); } function walkNative(obj) { while (!obj.identifier && typeof obj.left === "object") obj = obj.left; if (obj.identifier && natives.indexOf(obj.value) >= 0 && state.funct["(scope)"].isPredefined(obj.value)) { return obj.value; } } var prototype = walkPrototype(left); if (prototype) return walkNative(prototype); } /** * Checks the left hand side of an assignment for issues, returns if ok * @param {token} left - the left hand side of the assignment * @param {token=} assignToken - the token for the assignment, used for reporting * @param {object=} options - optional object * @param {boolean} options.allowDestructuring - whether to allow destructuting binding * @returns {boolean} Whether the left hand side is OK */ function checkLeftSideAssign(left, assignToken, options) { var allowDestructuring = options && options.allowDestructuring; assignToken = assignToken || left; if (state.option.freeze) { var nativeObject = findNativePrototype(left); if (nativeObject) warning("W121", left, nativeObject); } if (checkPunctuator(left, "...")) { left = left.right; } if (left.identifier && !left.isMetaProperty) { // reassign also calls modify // but we are specific in order to catch function re-assignment // and globals re-assignment state.funct["(scope)"].block.reassign(left.value, left); } if (left.id === ".") { if (!left.left || left.left.value === "arguments" && !state.isStrict()) { warning("E031", assignToken); } state.nameStack.set(state.tokens.prev); return true; } else if (left.id === "{" || left.id === "[") { if (allowDestructuring && left.destructAssign) { left.destructAssign.forEach(function(t) { if (t.id) { state.funct["(scope)"].block.modify(t.id, t.token); } }); } else { if (left.id === "{" || !left.left) { warning("E031", assignToken); } else if (left.left.value === "arguments" && !state.isStrict()) { warning("E031", assignToken); } } if (left.id === "[") { state.nameStack.set(left.right); } return true; } else if (left.identifier && !isReserved(left) && !left.isMetaProperty) { if (state.funct["(scope)"].labeltype(left.value) === "exception") { warning("W022", left); } state.nameStack.set(left); return true; } if (left === state.syntax["function"]) { warning("W023", state.tokens.curr); } else { error("E031", assignToken); } return false; } function assignop(s, f, p) { var x = infix(s, typeof f === "function" ? f : function(left, that) { that.left = left; checkLeftSideAssign(left, that, { allowDestructuring: true }); that.right = expression(10); return that; }, p); x.exps = true; x.assign = true; return x; } function bitwise(s, f, p) { var x = symbol(s, p); reserveName(x); x.led = (typeof f === "function") ? f : function(left) { if (state.option.bitwise) { warning("W016", this, this.id); } this.left = left; this.right = expression(p); return this; }; return x; } function bitwiseassignop(s) { return assignop(s, function(left, that) { if (state.option.bitwise) { warning("W016", that, that.id); } checkLeftSideAssign(left, that); that.right = expression(10); return that; }, 20); } function suffix(s) { var x = symbol(s, 150); x.led = function(left) { // this = suffix e.g. "++" punctuator // left = symbol operated e.g. "a" identifier or "a.b" punctuator if (state.option.plusplus) { warning("W016", this, this.id); } else if ((!left.identifier || isReserved(left)) && left.id !== "." && left.id !== "[") { warning("W017", this); } if (left.isMetaProperty) { error("E031", this); // detect increment/decrement of a const // in the case of a.b, left will be the "." punctuator } else if (left && left.identifier) { state.funct["(scope)"].block.modify(left.value, left); } this.left = left; return this; }; return x; } // fnparam means that this identifier is being defined as a function // argument (see identifier()) // prop means that this identifier is that of an object property function optionalidentifier(fnparam, prop, preserve) { if (!state.tokens.next.identifier) { return; } if (!preserve) { advance(); } var curr = state.tokens.curr; var val = state.tokens.curr.value; if (!isReserved(curr)) { return val; } if (prop) { if (state.inES5()) { return val; } } if (fnparam && val === "undefined") { return val; } warning("W024", state.tokens.curr, state.tokens.curr.id); return val; } // fnparam means that this identifier is being defined as a function // argument // prop means that this identifier is that of an object property function identifier(fnparam, prop) { var i = optionalidentifier(fnparam, prop, false); if (i) { return i; } // parameter destructuring with rest operator if (state.tokens.next.value === "...") { if (!state.inES6(true)) { warning("W119", state.tokens.next, "spread/rest operator", "6"); } advance(); if (checkPunctuator(state.tokens.next, "...")) { warning("E024", state.tokens.next, "..."); while (checkPunctuator(state.tokens.next, "...")) { advance(); } } if (!state.tokens.next.identifier) { warning("E024", state.tokens.curr, "..."); return; } return identifier(fnparam, prop); } else { error("E030", state.tokens.next, state.tokens.next.value); // The token should be consumed after a warning is issued so the parser // can continue as though an identifier were found. The semicolon token // should not be consumed in this way so that the parser interprets it as // a statement delimeter; if (state.tokens.next.id !== ";") { advance(); } } } function reachable(controlToken) { var i = 0, t; if (state.tokens.next.id !== ";" || controlToken.inBracelessBlock) { return; } for (;;) { do { t = peek(i); i += 1; } while (t.id !== "(end)" && t.id === "(comment)"); if (t.reach) { return; } if (t.id !== "(endline)") { if (t.id === "function") { if (state.option.latedef === true) { warning("W026", t); } break; } warning("W027", t, t.value, controlToken.value); break; } } } function parseFinalSemicolon() { if (state.tokens.next.id !== ";") { // don't complain about unclosed templates / strings if (state.tokens.next.isUnclosed) return advance(); var sameLine = startLine(state.tokens.next) === state.tokens.curr.line && state.tokens.next.id !== "(end)"; var blockEnd = checkPunctuator(state.tokens.next, "}"); if (sameLine && !blockEnd) { errorAt("E058", state.tokens.curr.line, state.tokens.curr.character); } else if (!state.option.asi) { // If this is the last statement in a block that ends on // the same line *and* option lastsemic is on, ignore the warning. // Otherwise, complain about missing semicolon. if ((blockEnd && !state.option.lastsemic) || !sameLine) { warningAt("W033", state.tokens.curr.line, state.tokens.curr.character); } } } else { advance(";"); } } function statement() { var i = indent, r, t = state.tokens.next, hasOwnScope = false; if (t.id === ";") { advance(";"); return; } // Is this a labelled statement? var res = isReserved(t); // We're being more tolerant here: if someone uses // a FutureReservedWord as a label, we warn but proceed // anyway. if (res && t.meta && t.meta.isFutureReservedWord && peek().id === ":") { warning("W024", t, t.id); res = false; } if (t.identifier && !res && peek().id === ":") { advance(); advance(":"); hasOwnScope = true; state.funct["(scope)"].stack(); state.funct["(scope)"].block.addBreakLabel(t.value, { token: state.tokens.curr }); if (!state.tokens.next.labelled && state.tokens.next.value !== "{") { warning("W028", state.tokens.next, t.value, state.tokens.next.value); } state.tokens.next.label = t.value; t = state.tokens.next; } // Is it a lonely block? if (t.id === "{") { // Is it a switch case block? // // switch (foo) { // case bar: { <= here. // ... // } // } var iscase = (state.funct["(verb)"] === "case" && state.tokens.curr.value === ":"); block(true, true, false, false, iscase); return; } // Parse the statement. r = expression(0, true); if (r && !(r.identifier && r.value === "function") && !(r.type === "(punctuator)" && r.left && r.left.identifier && r.left.value === "function")) { if (!state.isStrict() && state.stmtMissingStrict()) { warning("E007"); } } // Look for the final semicolon. if (!t.block) { if (!state.option.expr && (!r || !r.exps)) { warning("W030", state.tokens.curr); } else if (state.option.nonew && r && r.left && r.id === "(" && r.left.id === "new") { warning("W031", t); } parseFinalSemicolon(); } // Restore the indentation. indent = i; if (hasOwnScope) { state.funct["(scope)"].unstack(); } return r; } function statements() { var a = [], p; while (!state.tokens.next.reach && state.tokens.next.id !== "(end)") { if (state.tokens.next.id === ";") { p = peek(); if (!p || (p.id !== "(" && p.id !== "[")) { warning("W032"); } advance(";"); } else { a.push(statement()); } } return a; } /* * read all directives * recognizes a simple form of asi, but always * warns, if it is used */ function directives() { var i, p, pn; while (state.tokens.next.id === "(string)") { p = peek(0); if (p.id === "(endline)") { i = 1; do { pn = peek(i++); } while (pn.id === "(endline)"); if (pn.id === ";") { p = pn; } else if (pn.value === "[" || pn.value === ".") { // string -> [ | . is a valid production break; } else if (!state.option.asi || pn.value === "(") { // string -> ( is not a valid production warning("W033", state.tokens.next); } } else if (p.id === "." || p.id === "[") { break; } else if (p.id !== ";") { warning("W033", p); } advance(); var directive = state.tokens.curr.value; if (state.directive[directive] || (directive === "use strict" && state.option.strict === "implied")) { warning("W034", state.tokens.curr, directive); } // there's no directive negation, so always set to true state.directive[directive] = true; if (p.id === ";") { advance(";"); } } if (state.isStrict()) { state.option.undef = true; } } /* * Parses a single block. A block is a sequence of statements wrapped in * braces. * * ordinary - true for everything but function bodies and try blocks. * stmt - true if block can be a single statement (e.g. in if/for/while). * isfunc - true if block is a function body * isfatarrow - true if its a body of a fat arrow function * iscase - true if block is a switch case block */ function block(ordinary, stmt, isfunc, isfatarrow, iscase) { var a, b = inblock, old_indent = indent, m, t, line, d; inblock = ordinary; t = state.tokens.next; var metrics = state.funct["(metrics)"]; metrics.nestedBlockDepth += 1; metrics.verifyMaxNestedBlockDepthPerFunction(); if (state.tokens.next.id === "{") { advance("{"); // create a new block scope state.funct["(scope)"].stack(); state.funct["(noblockscopedvar)"] = false; line = state.tokens.curr.line; if (state.tokens.next.id !== "}") { indent += state.option.indent; while (!ordinary && state.tokens.next.from > indent) { indent += state.option.indent; } if (isfunc) { m = {}; for (d in state.directive) { if (_.has(state.directive, d)) { m[d] = state.directive[d]; } } directives(); if (state.option.strict && state.funct["(context)"]["(global)"]) { if (!m["use strict"] && !state.isStrict()) { warning("E007"); } } } a = statements(); metrics.statementCount += a.length; indent -= state.option.indent; } advance("}", t); if (isfunc) { state.funct["(scope)"].validateParams(); if (m) { state.directive = m; } } state.funct["(scope)"].unstack(); indent = old_indent; } else if (!ordinary) { if (isfunc) { state.funct["(scope)"].stack(); m = {}; if (stmt && !isfatarrow && !state.inMoz()) { error("W118", state.tokens.curr, "function closure expressions"); } if (!stmt) { for (d in state.directive) { if (_.has(state.directive, d)) { m[d] = state.directive[d]; } } } expression(10); if (state.option.strict && state.funct["(context)"]["(global)"]) { if (!m["use strict"] && !state.isStrict()) { warning("E007"); } } state.funct["(scope)"].unstack(); } else { error("E021", state.tokens.next, "{", state.tokens.next.value); } } else { // check to avoid let declaration not within a block // though is fine inside for loop initializer section state.funct["(noblockscopedvar)"] = state.tokens.next.id !== "for"; state.funct["(scope)"].stack(); if (!stmt || state.option.curly) { warning("W116", state.tokens.next, "{", state.tokens.next.value); } state.tokens.next.inBracelessBlock = true; indent += state.option.indent; // test indentation only if statement is in new line a = [statement()]; indent -= state.option.indent; state.funct["(scope)"].unstack(); delete state.funct["(noblockscopedvar)"]; } // Don't clear and let it propagate out if it is "break", "return" or similar in switch case switch (state.funct["(verb)"]) { case "break": case "continue": case "return": case "throw": if (iscase) { break; } /* falls through */ default: state.funct["(verb)"] = null; } inblock = b; if (ordinary && state.option.noempty && (!a || a.length === 0)) { warning("W035", state.tokens.prev); } metrics.nestedBlockDepth -= 1; return a; } function countMember(m) { if (membersOnly && typeof membersOnly[m] !== "boolean") { warning("W036", state.tokens.curr, m); } if (typeof member[m] === "number") { member[m] += 1; } else { member[m] = 1; } } // Build the syntax table by declaring the syntactic elements of the language. type("(number)", function() { return this; }); type("(string)", function() { return this; }); state.syntax["(identifier)"] = { type: "(identifier)", lbp: 0, identifier: true, nud: function() { var v = this.value; // If this identifier is the lone parameter to a shorthand "fat arrow" // function definition, i.e. // // x => x; // // ...it should not be considered as a variable in the current scope. It // will be added to the scope of the new function when the next token is // parsed, so it can be safely ignored for now. if (state.tokens.next.id === "=>") { return this; } if (!state.funct["(comparray)"].check(v)) { state.funct["(scope)"].block.use(v, state.tokens.curr); } return this; }, led: function() { error("E033", state.tokens.next, state.tokens.next.value); } }; var baseTemplateSyntax = { identifier: false, template: true, }; state.syntax["(template)"] = _.extend({ lbp: 155, type: "(template)", nud: doTemplateLiteral, led: doTemplateLiteral, noSubst: false }, baseTemplateSyntax); state.s