From ced37c0c1974b57460164525aec759fe35e6f3fb Mon Sep 17 00:00:00 2001 From: nightwing Date: Tue, 6 Jan 2015 15:45:55 +0400 Subject: [PATCH] update jshint --- lib/ace/mode/javascript/jshint.js | 2827 +++++++++++++++++++++-------- 1 file changed, 2107 insertions(+), 720 deletions(-) diff --git a/lib/ace/mode/javascript/jshint.js b/lib/ace/mode/javascript/jshint.js index 7d8352a8..c390116e 100644 --- a/lib/ace/mode/javascript/jshint.js +++ b/lib/ace/mode/javascript/jshint.js @@ -1416,6 +1416,7 @@ var Lexer = _dereq_("./lex.js").Lexer; var reg = _dereq_("./reg.js"); var state = _dereq_("./state.js").state; var style = _dereq_("./style.js"); +var options = _dereq_("./options.js"); // 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 @@ -1424,8 +1425,7 @@ var style = _dereq_("./style.js"); var JSHINT = (function () { "use strict"; - var anonname, // The guessed name for anonymous functions. - api, // Extension API + var api, // Extension API // These are operators that should not be used with the ! operator. bang = { @@ -1444,153 +1444,6 @@ var JSHINT = (function () { "%" : true }, - // These are the JSHint boolean options. - boolOptions = { - asi : true, // if automatic semicolon insertion should be tolerated - bitwise : true, // if bitwise operators should not be allowed - boss : true, // if advanced usage of assignments should be allowed - browser : true, // if the standard browser globals should be predefined - camelcase : true, // if identifiers should be required in camel case - couch : true, // if CouchDB globals should be predefined - curly : true, // if curly braces around all blocks should be required - debug : true, // if debugger statements should be allowed - devel : true, // if logging globals should be predefined (console, alert, etc.) - dojo : true, // if Dojo Toolkit globals should be predefined - eqeqeq : true, // if === should be required - eqnull : true, // if == null comparisons should be tolerated - notypeof : true, // if should report typos in typeof comparisons - es3 : true, // if ES3 syntax should be allowed - es5 : true, // if ES5 syntax should be allowed (is now set per default) - esnext : true, // if es.next specific syntax should be allowed - moz : true, // if mozilla specific syntax should be allowed - evil : true, // if eval should be allowed - expr : true, // if ExpressionStatement should be allowed as Programs - forin : true, // if for in statements must filter - funcscope : true, // if only function scope should be used for scope tests - globalstrict: true, // if global "use strict"; should be allowed (also enables 'strict') - immed : true, // if immediate invocations must be wrapped in parens - iterator : true, // if the `__iterator__` property should be allowed - jasmine : true, // Jasmine functions should be predefined - jquery : true, // if jQuery globals should be predefined - lastsemic : true, // if semicolons may be ommitted for the trailing - // statements inside of a one-line blocks. - laxbreak : true, // if line breaks should not be checked - laxcomma : true, // if line breaks should not be checked around commas - loopfunc : true, // if functions should be allowed to be defined within - // loops - mootools : true, // if MooTools globals should be predefined - multistr : true, // allow multiline strings - freeze : true, // if modifying native object prototypes should be disallowed - newcap : true, // if constructor names must be capitalized - noarg : true, // if arguments.caller and arguments.callee should be - // disallowed - node : true, // if the Node.js environment globals should be - // predefined - noempty : true, // if empty blocks should be disallowed - nonbsp : true, // if non-breaking spaces should be disallowed - nonew : true, // if using `new` for side-effects should be disallowed - nonstandard : true, // if non-standard (but widely adopted) globals should - // be predefined - phantom : true, // if PhantomJS symbols should be allowed - plusplus : true, // if increment/decrement should not be allowed - proto : true, // if the `__proto__` property should be allowed - prototypejs : true, // if Prototype and Scriptaculous globals should be - // predefined - qunit : true, // if the QUnit environment globals should be predefined - rhino : true, // if the Rhino environment globals should be predefined - shelljs : true, // if ShellJS globals should be predefined - typed : true, // if typed array globals should be predefined - undef : true, // if variables should be declared before used - scripturl : true, // if script-targeted URLs should be tolerated - strict : true, // require the "use strict"; pragma - sub : true, // if all forms of subscript notation are tolerated - supernew : true, // if `new function () { ... };` and `new Object;` - // should be tolerated - validthis : true, // if 'this' inside a non-constructor function is valid. - // This is a function scoped option only. - withstmt : true, // if with statements should be allowed - worker : true, // if Web Worker script symbols should be allowed - wsh : true, // if the Windows Scripting Host environment globals - // should be predefined - yui : true, // YUI variables should be predefined - mocha : true, // Mocha functions should be predefined - noyield : true, // allow generators without a yield - - // Obsolete options - onecase : true, // if one case switch statements should be allowed - regexp : true, // if the . should not be allowed in regexp literals - regexdash : true // if unescaped first/last dash (-) inside brackets - // should be tolerated - }, - - // These are the JSHint options that can take any value - // (we use this object to detect invalid options) - valOptions = { - maxlen : false, - indent : false, - maxerr : false, - predef : false, // predef is deprecated and being replaced by globals - globals : false, - quotmark : false, // 'single'|'double'|true - scope : false, - maxstatements: false, // {int} max statements per function - maxdepth : false, // {int} max nested block depth per function - maxparams : false, // {int} max params per function - maxcomplexity: false, // {int} max cyclomatic complexity per function - shadow : false, // if variable shadowing should be tolerated - // "inner" - check for variables defined in the same scope only - // "outer" - check for variables defined in outer scopes as well - // false - same as inner - // true - allow variable shadowing - unused : true, // warn if variables are unused. Available options: - // false - don't check for unused variables - // true - "vars" + check last function param - // "vars" - skip checking unused function params - // "strict" - "vars" + check all function params - latedef : false, // warn if the variable is used before its definition - // false - don't emit any warnings - // true - warn if any variable is used before its definition - // "nofunc" - warn for any variable but function declarations - ignore : false // start/end ignoring lines of code, bypassing the lexer - // start - start ignoring lines, including the current line - // end - stop ignoring lines, starting on the next line - // line - ignore warnings / errors for just a single line - // (this option does not bypass the lexer) - }, - - // These are JSHint boolean options which are shared with JSLint - // where the definition in JSHint is opposite JSLint - invertedOptions = { - bitwise : true, - forin : true, - newcap : true, - plusplus: true, - regexp : true, - undef : true, - - // Inverted and renamed, use JSHint name here - eqeqeq : true, - strict : true - }, - - // These are JSHint boolean options which are shared with JSLint - // where the name has been changed but the effect is unchanged - renamedOptions = { - eqeq : "eqeqeq", - windows: "wsh", - sloppy : "strict" - }, - - removedOptions = { - nomen: true, - onevar: true, - passfail: true, - white: true, - gcl: true, - smarttabs: true, - trailing: true - }, - declared, // Globals that were declared using /*global ... */ syntax. exported, // Variables that are used outside of the current file. @@ -1610,7 +1463,6 @@ var JSHINT = (function () { lex, member, membersOnly, - noreach, predefined, // Global variables defined by option scope, // The current scope @@ -1628,8 +1480,8 @@ var JSHINT = (function () { return true; } - if (valOptions[name] === undefined && boolOptions[name] === undefined) { - if (t.type !== "jslint" && !removedOptions[name]) { + if (options.validNames.indexOf(name) === -1) { + if (t.type !== "jslint" && !_.has(options.removed, name)) { error("E001", t, name); return false; } @@ -1694,7 +1546,24 @@ var JSHINT = (function () { }); } + function processenforceall() { + if (state.option.enforceall) { + for (var enforceopt in options.bool.enforcing) { + if (state.option[enforceopt] === undefined) { + state.option[enforceopt] = true; + } + } + for (var relaxopt in options.bool.relaxing) { + if (state.option[relaxopt] === undefined) { + state.option[relaxopt] = false; + } + } + } + } + function assume() { + processenforceall(); + if (state.option.esnext) { combine(predefined, vars.newEcmaIdentifiers); } @@ -1745,6 +1614,12 @@ var JSHINT = (function () { 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); } @@ -1949,6 +1824,9 @@ var JSHINT = (function () { // if the identifier is from a let, adds it only to the current blockscope if (islet) { funct["(blockscope)"].current.add(name, type, state.tokens.curr); + if (funct["(blockscope)"].atTop() && exported[name]) { + state.tokens.curr.exported = true; + } } else { funct["(blockscope)"].shadow(name); funct[name] = type; @@ -2179,10 +2057,10 @@ var JSHINT = (function () { var tn; if (val === "true" || val === "false") { if (nt.type === "jslint") { - tn = renamedOptions[key] || key; + tn = options.renamed[key] || key; state.option[tn] = (val === "true"); - if (invertedOptions[tn] !== undefined) { + if (options.inverted[tn] !== undefined) { state.option[tn] = !state.option[tn]; } } else { @@ -2221,9 +2099,19 @@ var JSHINT = (function () { 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 === ".") { @@ -2242,10 +2130,6 @@ var JSHINT = (function () { break; } - if (state.tokens.curr.type === "(string)" || state.tokens.curr.identifier) { - anonname = state.tokens.curr.value; - } - if (id && state.tokens.next.id !== id) { if (t) { if (state.tokens.next.id === "(end)") { @@ -2254,7 +2138,14 @@ var JSHINT = (function () { 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); + // parameter destructuring with rest operator + if (state.tokens.next.value === "...") { + if (!state.option.esnext) { + warning("W119", state.tokens.next, "spread/rest operator"); + } + } else { + warning("W116", state.tokens.next, id, state.tokens.next.value); + } } } @@ -2316,7 +2207,10 @@ var JSHINT = (function () { // They are elements of the parsing method called Top Down Operator Precedence. function expression(rbp, initial) { - var left, isArray = false, isObject = false, isLetExpr = false; + var left, isArray = false, isObject = false, isLetExpr = false, + isFatArrowBody = state.tokens.curr.value === "=>"; + + state.nameStack.push(); // if current expression is a let expression if (!initial && state.tokens.next.value === "let" && peek(0).value === "(") { @@ -2335,6 +2229,10 @@ var JSHINT = (function () { if (state.tokens.next.id === "(end)") error("E006", state.tokens.curr); + if (state.tokens.next.type === "(template)") { + doTemplateLiteral(); + } + var isDangerous = state.option.asi && state.tokens.prev.line < state.tokens.curr.line && @@ -2347,7 +2245,6 @@ var JSHINT = (function () { advance(); if (initial) { - anonname = "anonymous"; funct["(verb)"] = state.tokens.curr.value; } @@ -2402,6 +2299,14 @@ var JSHINT = (function () { if (isLetExpr) { funct["(blockscope)"].unstack(); } + + if (state.option.singleGroups && left && left.paren && !left.exprs && + !isFatArrowBody && !left.triggerFnExpr) { + warning("W126"); + } + + state.nameStack.pop(); + return left; } @@ -2438,6 +2343,10 @@ var JSHINT = (function () { function comma(opts) { opts = opts || {}; + if (state.option.nocomma) { + warning("W127"); + } + if (!opts.peek) { nobreakcomma(state.tokens.curr, state.tokens.next); advance(","); @@ -2620,14 +2529,10 @@ var JSHINT = (function () { var x = symbol(s, 42); x.led = function (left) { - if (!state.option.inESNext()) { - warning("W104", state.tokens.curr, "arrow function syntax (=>)"); - } - nobreaknonadjacent(state.tokens.prev, state.tokens.curr); this.left = left; - this.right = doFunction(undefined, undefined, false, left); + this.right = doFunction(undefined, undefined, false, { loneArg: left }); return this; }; return x; @@ -2754,6 +2659,7 @@ var JSHINT = (function () { warning("E031", that); } + state.nameStack.set(state.tokens.prev); that.right = expression(10); return that; } else if (left.id === "[") { @@ -2768,12 +2674,16 @@ var JSHINT = (function () { } else if (left.left.value === "arguments" && !state.directive["use strict"]) { warning("E031", that); } + + state.nameStack.set(left.right); + that.right = expression(10); return that; } else if (left.identifier && !isReserved(left)) { if (funct[left.value] === "exception") { warning("W022", left); } + state.nameStack.set(left); that.right = expression(10); return that; } @@ -2848,17 +2758,24 @@ var JSHINT = (function () { // 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 + // exported means that the identifier is part of a valid ES6 `export` declaration - function optionalidentifier(fnparam, prop) { + function optionalidentifier(fnparam, prop, preserve, exported) { if (!state.tokens.next.identifier) { return; } - advance(); + if (!preserve) { + advance(); + } var curr = state.tokens.curr; var val = state.tokens.curr.value; + if (exported) { + state.tokens.curr.exported = true; + } + if (!isReserved(curr)) { return val; } @@ -2880,22 +2797,35 @@ var JSHINT = (function () { // 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); + // `exported` means that the identifier token should be exported. + function identifier(fnparam, prop, exported) { + var i = optionalidentifier(fnparam, prop, false, exported); if (i) { return i; } - if (state.tokens.curr.id === "function" && state.tokens.next.id === "(") { - warning("W025"); + + // parameter destructuring with rest operator + if (state.tokens.next.value === "...") { + if (!state.option.esnext) { + warning("W119", state.tokens.next, "spread/rest operator"); + } } 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(s) { + function reachable(controlToken) { var i = 0, t; - if (state.tokens.next.id !== ";" || noreach) { + if (state.tokens.next.id !== ";" || controlToken.inBracelessBlock) { return; } for (;;) { @@ -2915,7 +2845,7 @@ var JSHINT = (function () { break; } - warning("W027", t, t.value, s); + warning("W027", t, t.value, controlToken.value); break; } } @@ -3052,7 +2982,7 @@ var JSHINT = (function () { } - function statements(startLine) { + function statements() { var a = [], p; while (!state.tokens.next.reach && state.tokens.next.id !== "(end)") { @@ -3065,7 +2995,7 @@ var JSHINT = (function () { advance(";"); } else { - a.push(statement(startLine === state.tokens.next.line)); + a.push(statement()); } } return a; @@ -3080,53 +3010,46 @@ var JSHINT = (function () { function directives() { var i, p, pn; - for (;;) { - if (state.tokens.next.id === "(string)") { - p = peek(0); - if (p.id === "(endline)") { - i = 1; - do { - pn = peek(i); - i = i + 1; - } while (pn.id === "(endline)"); - - if (pn.id !== ";") { - if (pn.id !== "(string)" && pn.id !== "(number)" && - pn.id !== "(regexp)" && pn.identifier !== true && - pn.id !== "}") { - break; - } - warning("W033", state.tokens.next); - } else { - p = pn; - } - } else if (p.id === "}") { - // Directive with no other statements, warn about missing semicolon - warning("W033", p); - } else if (p.id !== ";") { - break; + 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 + return; + } else if (!state.option.asi || pn.value === "(") { + // string -> ( is not a valid production + warning("W033", state.tokens.next); } - - advance(); - if (state.directive[state.tokens.curr.value]) { - warning("W034", state.tokens.curr, state.tokens.curr.value); - } - - if (state.tokens.curr.value === "use strict") { - if (!state.option["(explicitNewcap)"]) - state.option.newcap = true; - state.option.undef = true; - } - - // there's no directive negation, so always set to true - state.directive[state.tokens.curr.value] = true; - - if (p.id === ";") { - advance(";"); - } - continue; + } else if (p.id === "." || p.id === "[") { + return; + } else if (p.id !== ";") { + warning("W033", p); + } + + advance(); + if (state.directive[state.tokens.curr.value]) { + warning("W034", state.tokens.curr, state.tokens.curr.value); + } + + if (state.tokens.curr.value === "use strict") { + if (!state.option["(explicitNewcap)"]) { + state.option.newcap = true; + } + state.option.undef = true; + } + + // there's no directive negation, so always set to true + state.directive[state.tokens.curr.value] = true; + + if (p.id === ";") { + advance(";"); } - break; } } @@ -3191,7 +3114,7 @@ var JSHINT = (function () { } } - a = statements(line); + a = statements(); metrics.statementCount += a.length; @@ -3240,12 +3163,11 @@ var JSHINT = (function () { warning("W116", state.tokens.next, "{", state.tokens.next.value); } - noreach = true; + state.tokens.next.inBracelessBlock = true; indent += state.option.indent; // test indentation only if statement is in new line a = [statement()]; indent -= state.option.indent; - noreach = false; delete funct["(nolet)"]; } @@ -3268,7 +3190,7 @@ var JSHINT = (function () { if (!ordinary || !state.option.funcscope) scope = s; inblock = b; if (ordinary && state.option.noempty && (!a || a.length === 0)) { - warning("W035"); + warning("W035", state.tokens.prev); } metrics.nestedBlockDepth -= 1; return a; @@ -3308,10 +3230,6 @@ var JSHINT = (function () { return this; }); - type("(template)", function () { - return this; - }); - state.syntax["(identifier)"] = { type: "(identifier)", lbp: 0, @@ -3323,6 +3241,18 @@ var JSHINT = (function () { var f; var block; + // 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 (typeof s === "function") { // Protection against accidental inheritance. s = undefined; @@ -3360,33 +3290,6 @@ var JSHINT = (function () { warning("W037", state.tokens.curr, v); break; } - } else if (funct["(global)"]) { - // The name is not defined in the function. If we are in the global - // scope, then we have an undefined variable. - // - // Operators typeof and delete do not raise runtime errors even if - // the base object of a reference is null so no need to display warning - // if we're inside of typeof or delete. - - if (typeof predefined[v] !== "boolean") { - // Attempting to subscript a null reference will throw an - // error, even within the typeof and delete operators - if (!(anonname === "typeof" || anonname === "delete") || - (state.tokens.next && (state.tokens.next.value === "." || - state.tokens.next.value === "["))) { - - // if we're in a list comprehension, variables are declared - // locally and used before being defined. So we check - // the presence of the given variable in the comp array - // before declaring it undefined. - - if (!funct["(comparray)"].check(v)) { - isundef(funct, "W117", state.tokens.curr, v); - } - } - } - - note_implied(state.tokens.curr); } else { // If the name is already defined in the current // function, but not as outer, then there is a scope error. @@ -3413,19 +3316,20 @@ var JSHINT = (function () { warning("W039", state.tokens.curr, v); note_implied(state.tokens.curr); } else if (typeof s !== "object") { - // Operators typeof and delete do not raise runtime errors even - // if the base object of a reference is null so no need to - // - // display warning if we're inside of typeof or delete. - // Attempting to subscript a null reference will throw an - // error, even within the typeof and delete operators - if (!(anonname === "typeof" || anonname === "delete") || - (state.tokens.next && - (state.tokens.next.value === "." || state.tokens.next.value === "["))) { + // if we're in a list comprehension, variables are declared + // locally and used before being defined. So we check + // the presence of the given variable in the comp array + // before declaring it undefined. + if (!funct["(comparray)"].check(v)) { isundef(funct, "W117", state.tokens.curr, v); } - funct[v] = true; + + // Explicitly mark the variable as used within function scopes + if (!funct["(global)"]) { + funct[v] = true; + } + note_implied(state.tokens.curr); } else { switch (s[v]) { @@ -3460,6 +3364,21 @@ var JSHINT = (function () { } }; + state.syntax["(template)"] = { + type: "(template)", + lbp: 0, + identifier: false, + fud: doTemplateLiteral + }; + + type("(template middle)", function () { + return this; + }); + + type("(template tail)", function () { + return this; + }); + type("(regexp)", function () { return this; }); @@ -3494,7 +3413,8 @@ var JSHINT = (function () { reservevar("Infinity"); reservevar("null"); reservevar("this", function (x) { - if (state.directive["use strict"] && !state.option.validthis && ((funct["(statement)"] && + if (state.directive["use strict"] && !isMethod() && + !state.option.validthis && ((funct["(statement)"] && funct["(name)"].charAt(0) > "Z") || funct["(global)"])) { warning("W040", x); } @@ -3511,12 +3431,12 @@ var JSHINT = (function () { }; assignop("%=", "assignmod", 20); - bitwiseassignop("&=", "assignbitand", 20); - bitwiseassignop("|=", "assignbitor", 20); - bitwiseassignop("^=", "assignbitxor", 20); - bitwiseassignop("<<=", "assignshiftleft", 20); - bitwiseassignop(">>=", "assignshiftright", 20); - bitwiseassignop(">>>=", "assignshiftrightunsigned", 20); + bitwiseassignop("&="); + bitwiseassignop("|="); + bitwiseassignop("^="); + bitwiseassignop("<<="); + bitwiseassignop(">>="); + bitwiseassignop(">>>="); infix(",", function (left, that) { var expr; that.exprs = [left]; @@ -3667,11 +3587,11 @@ var JSHINT = (function () { infix("/", "div", 140); infix("%", "mod", 140); - suffix("++", "postinc"); + suffix("++"); prefix("++", "preinc"); state.syntax["++"].exps = true; - suffix("--", "postdec"); + suffix("--"); prefix("--", "predec"); state.syntax["--"].exps = true; prefix("delete", function () { @@ -3680,6 +3600,12 @@ var JSHINT = (function () { warning("W051"); } this.first = p; + + // The `delete` operator accepts unresolvable references when not in strict + // mode, so the operand may be undefined. + if (p.identifier && !state.directive["use strict"]) { + p.forgiveUndef = true; + } return this; }).exps = true; @@ -3692,8 +3618,8 @@ var JSHINT = (function () { }); prefix("...", function () { - if (!state.option.inESNext()) { - warning("W104", this, "spread/rest operator"); + if (!state.option.esnext) { + warning("W119", this, "spread/rest operator"); } if (!state.tokens.next.identifier) { error("E030", state.tokens.next, state.tokens.next.value); @@ -3716,7 +3642,17 @@ var JSHINT = (function () { return this; }); - prefix("typeof", "typeof"); + prefix("typeof", (function () { + var p = expression(150); + this.first = p; + + // The `typeof` operator accepts unresolvable references, so the operand + // may be undefined. + if (p.identifier) { + p.forgiveUndef = true; + } + return this; + })); prefix("new", function () { var c = expression(155), i; if (c && c.id !== "function") { @@ -3730,6 +3666,11 @@ var JSHINT = (function () { case "JSON": warning("W053", state.tokens.prev, c.value); break; + case "Symbol": + if (state.option.esnext) { + warning("W053", state.tokens.prev, c.value); + } + break; case "Function": if (!state.option.evil) { warning("W054"); @@ -3808,7 +3749,7 @@ var JSHINT = (function () { if (left) { if (left.type === "(identifier)") { if (left.value.match(/^[A-Z]([A-Z0-9_$]*[a-z][A-Za-z0-9_$]*)?$/)) { - if ("Number String Boolean Date Object Error".indexOf(left.value) === -1) { + if ("Number String Boolean Date Object Error Symbol".indexOf(left.value) === -1) { if (left.value === "Math") { warning("W063", left); } else if (state.option.newcap) { @@ -3862,8 +3803,8 @@ var JSHINT = (function () { } if (!left.identifier && left.id !== "." && left.id !== "[" && left.id !== "(" && left.id !== "&&" && left.id !== "||" && - left.id !== "?") { - warning("W067", left); + left.id !== "?" && !(state.option.esnext && left["(name)"])) { + warning("W067", that); } } @@ -3873,13 +3814,11 @@ var JSHINT = (function () { prefix("(", function () { var bracket, brackets = []; - var pn, pn1, i = 0; - var ret; + var pn = state.tokens.next, pn1, i = -1; + var ret, triggerFnExpr; var parens = 1; do { - pn = peek(i); - if (pn.value === "(") { parens += 1; } else if (pn.value === ")") { @@ -3887,19 +3826,27 @@ var JSHINT = (function () { } i += 1; - pn1 = peek(i); - } while (!(parens === 0 && pn.value === ")") && - pn1.value !== "=>" && pn1.value !== ";" && pn1.type !== "(end)"); + pn1 = pn; + pn = peek(i); + } while (!(parens === 0 && pn1.value === ")") && + pn.value !== ";" && pn.type !== "(end)"); if (state.tokens.next.id === "function") { - state.tokens.next.immed = true; + triggerFnExpr = state.tokens.next.immed = true; + } + + // If the balanced grouping operator is followed by a "fat arrow", the + // current token marks the beginning of a "fat arrow" function and parsing + // should proceed accordingly. + if (pn.value === "=>") { + return doFunction(null, null, null, { parsedParen: true }); } var exprs = []; if (state.tokens.next.id !== ")") { for (;;) { - if (pn1.value === "=>" && _.contains(["{", "["], state.tokens.next.value)) { + if (pn.value === "=>" && _.contains(["{", "["], state.tokens.next.value)) { bracket = state.tokens.next; bracket.left = destructuringExpression(); brackets.push(bracket); @@ -3919,7 +3866,7 @@ var JSHINT = (function () { advance(")", this); if (state.option.immed && exprs[0] && exprs[0].id === "function") { if (state.tokens.next.id !== "(" && - (state.tokens.next.id !== "." || (peek().value !== "call" && peek().value !== "apply"))) { + state.tokens.next.id !== "." && state.tokens.next.id !== "[") { warning("W068", this); } } @@ -3938,6 +3885,7 @@ var JSHINT = (function () { } if (ret) { ret.paren = true; + ret.triggerFnExpr = triggerFnExpr; } return ret; }); @@ -4024,7 +3972,7 @@ var JSHINT = (function () { } prefix("[", function () { - var blocktype = lookupBlockType(true); + var blocktype = lookupBlockType(); if (blocktype.isCompArray) { if (!state.option.inESNext()) { warning("W119", state.tokens.curr, "array comprehension"); @@ -4043,8 +3991,19 @@ var JSHINT = (function () { } while (state.tokens.next.id !== "(end)") { while (state.tokens.next.id === ",") { - if (!state.option.inES5()) - warning("W070"); + if (!state.option.elision) { + if (!state.option.inES5()) { + // Maintain compat with old options --- ES5 mode without + // elision=true will warn once per comma + warning("W070"); + } else { + warning("W128"); + do { + advance(","); + } while(state.tokens.next.id === ","); + continue; + } + } advance(","); } @@ -4068,20 +4027,45 @@ var JSHINT = (function () { } advance("]", this); return this; - }, 160); + }); - function property_name() { - var id = optionalidentifier(false, true); + function isMethod() { + return funct["(statement)"] && funct["(statement)"].type === "class" || + funct["(context)"] && funct["(context)"]["(verb)"] === "class"; + } + + + function isPropertyName(token) { + return token.identifier || token.id === "(string)" || token.id === "(number)"; + } + + + function propertyName(preserveOrToken) { + var id; + var preserve = true; + if (typeof preserveOrToken === "object") { + id = preserveOrToken; + } else { + preserve = preserveOrToken; + id = optionalidentifier(false, true, preserve); + } if (!id) { if (state.tokens.next.id === "(string)") { id = state.tokens.next.value; - advance(); + if (!preserve) { + advance(); + } } else if (state.tokens.next.id === "(number)") { id = state.tokens.next.value.toString(); - advance(); + if (!preserve) { + advance(); + } } + } else if (typeof id === "object") { + if (id.id === "(string)" || id.id === "(identifier)") id = id.value; + else if (id.id === "(number)") id = id.value.toString(); } if (id === "hasOwnProperty") { @@ -4091,40 +4075,25 @@ var JSHINT = (function () { return id; } - function functionparams(parsed) { - var curr, next; + function functionparams(fatarrow) { + var next; var params = []; var ident; var tokens = []; var t; var pastDefault = false; + var loneArg = fatarrow && fatarrow.loneArg; - if (parsed) { - if (Array.isArray(parsed)) { - for (var i in parsed) { - curr = parsed[i]; - if (curr.value === "...") { - if (!state.option.inESNext()) { - warning("W104", curr, "spread/rest operator"); - } - continue; - } else if (curr.value !== ",") { - params.push(curr.value); - addlabel(curr.value, { type: "unused", token: curr }); - } - } - return params; - } else { - if (parsed.identifier === true) { - addlabel(parsed.value, { type: "unused", token: parsed }); - return [parsed]; - } - } + if (loneArg && loneArg.identifier === true) { + addlabel(loneArg.value, { type: "unused", token: loneArg }); + return [loneArg]; } next = state.tokens.next; - advance("("); + if (!fatarrow || !fatarrow.parsedParen) { + advance("("); + } if (state.tokens.next.id === ")") { advance(")"); @@ -4142,8 +4111,8 @@ var JSHINT = (function () { } } } else if (state.tokens.next.value === "...") { - if (!state.option.inESNext()) { - warning("W104", state.tokens.next, "spread/rest operator"); + if (!state.option.esnext) { + warning("W119", state.tokens.next, "spread/rest operator"); } advance("..."); ident = identifier(true); @@ -4234,7 +4203,34 @@ var JSHINT = (function () { return funct; } - function doFunction(name, statement, generator, fatarrowparams) { + function doTemplateLiteral() { + while (state.tokens.next.type !== "(template tail)" && state.tokens.next.id !== "(end)") { + advance(); + if (state.tokens.next.type === "(template tail)") { + break; + } else if (state.tokens.next.type !== "(template middle)" && + state.tokens.next.type !== "(end)") { + expression(10); // should probably have different rbp? + } + } + return { + id: "(template)", + type: "(template)" + }; + } + + /** + * @param {Object} [fatarrow] In the case that the function being parsed + * takes the "fat arrow" form, this object will + * contain details about the in-progress parsing + * operation. + * @param {Token} [fatarrow.loneArg] The argument to the function in cases + * where it was defined using the single- + * argument shorthand. + * @param {bool} [fatarrow.parsedParen] Whether the opening parenthesis has + * already been parsed. + */ + function doFunction(name, statement, generator, fatarrow) { var f; var oldOption = state.option; var oldIgnored = state.ignored; @@ -4244,7 +4240,7 @@ var JSHINT = (function () { state.ignored = Object.create(state.ignored); scope = Object.create(scope); - funct = functor(name || "\"" + anonname + "\"", state.tokens.next, scope, { + funct = functor(name || state.nameStack.infer(), state.tokens.next, scope, { "(statement)": statement, "(context)": funct, "(generator)": generator ? true : null @@ -4259,21 +4255,23 @@ var JSHINT = (function () { addlabel(name, { type: "function" }); } - funct["(params)"] = functionparams(fatarrowparams); + funct["(params)"] = functionparams(fatarrow); funct["(metrics)"].verifyMaxParametersPerFunction(funct["(params)"]); - // So we parse fat-arrow functions after we encounter =>. So basically - // doFunction is called with the left side of => as its last argument. - // This means that the parser, at that point, had already added its - // arguments to the undefs array and here we undo that. + if (fatarrow) { + if (!state.option.esnext) { + warning("W119", state.tokens.curr, "arrow function syntax (=>)"); + } - JSHINT.undefs = _.filter(JSHINT.undefs, function (item) { - return !_.contains(_.union(fatarrowparams), item[2]); - }); + if (!fatarrow.loneArg) { + advance("=>"); + } + } - block(false, true, true, fatarrowparams ? true : false); + block(false, true, true, !!fatarrow); - if (!state.option.noyield && generator && funct["(generator)"] !== "yielded") { + if (!state.option.noyield && generator && + funct["(generator)"] !== "yielded") { warning("W124", state.tokens.curr); } @@ -4369,46 +4367,25 @@ var JSHINT = (function () { } } + /** + * @param {object} props Collection of property descriptors for a given + * object. + */ + function checkProperties(props) { + // Check for lonely setters if in the ES5 mode. + if (state.option.inES5()) { + for (var name in props) { + if (_.has(props, name) && props[name].setterToken && !props[name].getterToken) { + warning("W078", props[name].setterToken); + } + } + } + } (function (x) { - x.nud = function (isclassdef) { - var b, f, i, p, t, g; + x.nud = function () { + var b, f, i, p, t, g, nextVal; var props = {}; // All properties, including accessors - var tag = ""; - - function saveProperty(name, tkn) { - if (props[name] && _.has(props, name)) - warning("W075", state.tokens.next, i); - else - props[name] = {}; - - props[name].basic = true; - props[name].basictkn = tkn; - } - - function saveSetter(name, tkn) { - if (props[name] && _.has(props, name)) { - if (props[name].basic || props[name].setter) - warning("W075", state.tokens.next, i); - } else { - props[name] = {}; - } - - props[name].setter = true; - props[name].setterToken = tkn; - } - - function saveGetter(name) { - if (props[name] && _.has(props, name)) { - if (props[name].basic || props[name].getter) - warning("W075", state.tokens.next, i); - } else { - props[name] = {}; - } - - props[name].getter = true; - props[name].getterToken = state.tokens.curr; - } b = state.tokens.curr.line !== state.tokens.next.line; if (b) { @@ -4423,19 +4400,15 @@ var JSHINT = (function () { break; } - if (isclassdef && state.tokens.next.value === "static") { - advance("static"); - tag = "static "; - } + nextVal = state.tokens.next.value; + if (peek().id !== ":" && (nextVal === "get" || nextVal === "set")) { + advance(nextVal); - if (state.tokens.next.value === "get" && peek().id !== ":") { - advance("get"); - - if (!state.option.inES5(!isclassdef)) { + if (!state.option.inES5()) { error("E034"); } - i = property_name(); + i = propertyName(); // ES6 allows for get() {...} and set() {...} method // definition shorthand syntax, so we don't produce an error @@ -4444,16 +4417,10 @@ var JSHINT = (function () { error("E035"); } - // It is a Syntax Error if PropName of MethodDefinition is - // "constructor" and SpecialMethod of MethodDefinition is true. - if (isclassdef && i === "constructor") { - error("E049", state.tokens.next, "class getter method", i); - } - // We don't want to save this getter unless it's an actual getter // and not an ES6 concise method if (i) { - saveGetter(tag + i); + saveAccessor(nextVal, props, i, state.tokens.curr); } t = state.tokens.next; @@ -4461,43 +4428,9 @@ var JSHINT = (function () { p = f["(params)"]; // Don't warn about getter/setter pairs if this is an ES6 concise method - if (i && p) { + if (nextVal === "get" && i && p) { warning("W076", t, p[0], i); - } - } else if (state.tokens.next.value === "set" && peek().id !== ":") { - advance("set"); - - if (!state.option.inES5(!isclassdef)) { - error("E034"); - } - - i = property_name(); - - // ES6 allows for get() {...} and set() {...} method - // definition shorthand syntax, so we don't produce an error - // if the esnext option is enabled. - if (!i && !state.option.inESNext()) { - error("E035"); - } - - // It is a Syntax Error if PropName of MethodDefinition is - // "constructor" and SpecialMethod of MethodDefinition is true. - if (isclassdef && i === "constructor") { - error("E049", state.tokens.next, "class setter method", i); - } - - // We don't want to save this getter unless it's an actual getter - // and not an ES6 concise method - if (i) { - saveSetter(tag + i, state.tokens.next); - } - - t = state.tokens.next; - f = doFunction(); - p = f["(params)"]; - - // Don't warn about getter/setter pairs if this is an ES6 concise method - if (i && (!p || p.length !== 1)) { + } else if (nextVal === "set" && i && (!p || p.length !== 1)) { warning("W077", t, i); } } else { @@ -4509,33 +4442,43 @@ var JSHINT = (function () { advance("*"); g = true; } - i = property_name(); - saveProperty(tag + i, state.tokens.next); - - if (typeof i !== "string") { - break; - } - - if (state.tokens.next.value === "(") { + if (state.tokens.next.identifier && + (peekIgnoreEOL().id === "," || peekIgnoreEOL().id === "}")) { if (!state.option.inESNext()) { - warning("W104", state.tokens.curr, "concise methods"); + warning("W104", state.tokens.next, "object short notation"); } - doFunction(i, undefined, g); - } else if (!isclassdef) { - advance(":"); + i = propertyName(true); + saveProperty(props, i, state.tokens.next); + expression(10); + } else { + if (state.tokens.next.id === "[") { + i = computedPropertyName(); + state.nameStack.set(i); + } else { + state.nameStack.set(state.tokens.next); + i = propertyName(); + saveProperty(props, i, state.tokens.next); + + if (typeof i !== "string") { + break; + } + } + + if (state.tokens.next.value === "(") { + if (!state.option.inESNext()) { + warning("W104", state.tokens.curr, "concise methods"); + } + doFunction(null, undefined, g); + } else { + advance(":"); + expression(10); + } } } - // It is a Syntax Error if PropName of MethodDefinition is "prototype". - if (isclassdef && i === "prototype") { - error("E049", state.tokens.next, "class method", i); - } countMember(i); - if (isclassdef) { - tag = ""; - continue; - } + if (state.tokens.next.id === ",") { comma({ allowTrailing: true, property: true }); if (state.tokens.next.id === ",") { @@ -4552,14 +4495,8 @@ var JSHINT = (function () { } advance("}", this); - // Check for lonely setters if in the ES5 mode. - if (state.option.inES5()) { - for (var name in props) { - if (_.has(props, name) && props[name].setter && !props[name].getter) { - warning("W078", props[name].setterToken); - } - } - } + checkProperties(props); + return this; }; x.fud = function () { @@ -4747,12 +4684,15 @@ var JSHINT = (function () { this.first = this.first.concat(names); if (state.tokens.next.id === "=") { + state.nameStack.set(state.tokens.curr); advance("="); if (state.tokens.next.id === "undefined") { warning("W080", state.tokens.prev, state.tokens.prev.value); } if (peek(0).id === "=" && state.tokens.next.identifier) { - warning("W120", state.tokens.next, state.tokens.next.value); + if (!funct["(params)"] || funct["(params)"].indexOf(state.tokens.next.value) === -1) { + warning("W120", state.tokens.next, state.tokens.next.value); + } } value = expression(10); if (lone) { @@ -4868,6 +4808,8 @@ var JSHINT = (function () { } else if (state.tokens.next.identifier && state.tokens.next.value !== "extends") { // BindingIdentifier(opt) this.name = identifier(); + } else { + this.name = state.nameStack.infer(); } classtail(this); return this; @@ -4886,10 +4828,97 @@ var JSHINT = (function () { state.directive["use strict"] = true; advance("{"); // ClassBody(opt) - c.body = state.syntax["{"].nud(true); + c.body = classbody(c); + advance("}"); state.directive["use strict"] = strictness; } + function classbody(c) { + var name; + var isStatic; + var getset; + var props = {}; + var staticProps = {}; + var computed; + for (var i = 0; state.tokens.next.id !== "}"; ++i) { + name = state.tokens.next; + isStatic = false; + getset = null; + if (name.id === "[") { + name = computedPropertyName(); + } else if (isPropertyName(name)) { + // Non-Computed PropertyName + advance(); + computed = false; + if (name.identifier && name.value === "static") { + if (isPropertyName(state.tokens.next) || state.tokens.next.id === "[") { + computed = state.tokens.next.id === "["; + isStatic = true; + name = state.tokens.next; + if (state.tokens.next.id === "[") { + name = computedPropertyName(); + } else advance(); + } + } + + if (name.identifier && (name.value === "get" || name.value === "set")) { + if (isPropertyName(state.tokens.next) || state.tokens.next.id === "[") { + computed = state.tokens.next.id === "["; + getset = name; + name = state.tokens.next; + if (state.tokens.next.id === "[") { + name = computedPropertyName(); + } else advance(); + } + } + } else { + warning("W052", state.tokens.next, state.tokens.next.value || state.tokens.next.type); + advance(); + continue; + } + + if (!checkPunctuators(state.tokens.next, ["("])) { + // error --- class properties must be methods + error("E054", state.tokens.next, state.tokens.next.value); + while (state.tokens.next.id !== "}" && + !checkPunctuators(state.tokens.next, ["("])) { + advance(); + } + if (state.tokens.next.value !== "(") { + doFunction(undefined, c, false, null); + } + } + + if (!computed) { + // We don't know how to determine if we have duplicate computed property names :( + if (getset) { + saveAccessor( + getset.value, isStatic ? staticProps : props, name.value, name, true, isStatic); + } else { + if (name.value === "constructor") { + state.nameStack.set(c); + } else { + state.nameStack.set(name); + } + saveProperty(isStatic ? staticProps : props, name.value, name, true, isStatic); + } + } + + if (getset && name.value === "constructor") { + var propDesc = getset.value === "get" ? "class getter method" : "class setter method"; + error("E049", name, propDesc, "constructor"); + } else if (name.value === "prototype") { + error("E049", name, "class method", "prototype"); + } + + propertyName(name); + + doFunction(null, c, false, null); + } + + checkProperties(props); + } + blockstmt("function", function () { var generator = false; if (state.tokens.next.value === "*") { @@ -4904,7 +4933,12 @@ var JSHINT = (function () { warning("W082", state.tokens.curr); } - var i = identifier(); + var i = optionalidentifier(); + + if (i === undefined) { + warning("W025"); + } + if (funct[i] === "const") { warning("E011", null, i); } @@ -4919,6 +4953,7 @@ var JSHINT = (function () { prefix("function", function () { var generator = false; + if (state.tokens.next.value === "*") { if (!state.option.inESNext()) { warning("W119", state.tokens.curr, "function*"); @@ -4926,10 +4961,20 @@ var JSHINT = (function () { advance("*"); generator = true; } + var i = optionalidentifier(); - doFunction(i, undefined, generator); + var fn = doFunction(i, undefined, generator); + + function isVariable(name) { return name[0] !== "("; } + function isLocal(name) { return fn[name] === "var"; } + if (!state.option.loopfunc && funct["(loopage)"]) { - warning("W083"); + // If the function we just parsed accesses any non-local variables + // trigger a warning. Otherwise, the function is safe even within + // a loop. + if (_.some(fn, function (val, name) { return isVariable(name) && !isLocal(name); })) { + warning("W083"); + } } return this; }); @@ -4939,14 +4984,38 @@ var JSHINT = (function () { increaseComplexityCount(); state.condition = true; advance("("); - checkCondAssignment(expression(0)); + var expr = expression(0); + checkCondAssignment(expr); + + // When the if is within a for-in loop, check if the condition + // starts with a negation operator + var forinifcheck = null; + if (state.option.forin && state.forinifcheckneeded) { + state.forinifcheckneeded = false; // We only need to analyze the first if inside the loop + forinifcheck = state.forinifchecks[state.forinifchecks.length - 1]; + if (expr.type === "(punctuator)" && expr.value === "!") { + forinifcheck.type = "(negative)"; + } else { + forinifcheck.type = "(positive)"; + } + } + advance(")", t); state.condition = false; - block(true, true); + var s = block(true, true); + + // When the if is within a for-in loop and the condition has a negative form, + // check if the body contains nothing but a continue statement + if (forinifcheck && forinifcheck.type === "(negative)") { + if (s && s.length === 1 && s[0].type === "(identifier)" && s[0].value === "continue") { + forinifcheck.type = "(negative-with-continue)"; + } + } + if (state.tokens.next.id === "else") { advance("else"); if (state.tokens.next.id === "if" || state.tokens.next.id === "switch") { - statement(true); + statement(); } else { block(true, true); } @@ -5254,7 +5323,9 @@ var JSHINT = (function () { case "var": break; default: - if (!funct["(blockscope)"].getlabel(state.tokens.next.value)) + var ident = state.tokens.next.value; + if (!funct["(blockscope)"].getlabel(ident) && + !(scope[ident] || {})[ident]) warning("W088", state.tokens.next, state.tokens.next.value); } advance(); @@ -5262,11 +5333,41 @@ var JSHINT = (function () { advance(nextop.value); expression(20); advance(")", t); - s = block(true, true); - if (state.option.forin && s && (s.length > 1 || typeof s[0] !== "object" || - s[0].value !== "if")) { - warning("W089", this); + + if (nextop.value === "in" && state.option.forin) { + state.forinifcheckneeded = true; + + if (state.forinifchecks === undefined) { + state.forinifchecks = []; + } + + // Push a new for-in-if check onto the stack. The type will be modified + // when the loop's body is parsed and a suitable if statement exists. + state.forinifchecks.push({ + type: "(none)" + }); } + + s = block(true, true); + + if (nextop.value === "in" && state.option.forin) { + if (state.forinifchecks && state.forinifchecks.length > 0) { + var check = state.forinifchecks.pop(); + + if (// No if statement or not the first statement in loop body + s && s.length > 0 && (typeof s[0] !== "object" || s[0].value !== "if") || + // Positive if statement is not the only one in loop body + check.type === "(positive)" && s.length > 1 || + // Negative if statement but no continue + check.type === "(negative)") { + warning("W089", this); + } + } + + // Reset the flag in case no if statement was contained in the loop body + state.forinifcheckneeded = false; + } + funct["(breakage)"] -= 1; funct["(loopage)"] -= 1; } else { @@ -5346,7 +5447,9 @@ var JSHINT = (function () { advance(); } } - reachable("break"); + + reachable(this); + return this; }).exps = true; @@ -5373,7 +5476,9 @@ var JSHINT = (function () { } else if (!funct["(loopage)"]) { warning("W052", state.tokens.next, this.value); } - reachable("continue"); + + reachable(this); + return this; }).exps = true; @@ -5395,7 +5500,9 @@ var JSHINT = (function () { nolinebreak(this); // always warn (Line breaking error) } } - reachable("return"); + + reachable(this); + return this; }).exps = true; @@ -5413,8 +5520,17 @@ var JSHINT = (function () { warning("W104", state.tokens.curr, "yield"); } funct["(generator)"] = "yielded"; + var delegatingYield = false; + + if (state.tokens.next.value === "*") { + delegatingYield = true; + advance("*"); + } + if (this.line === state.tokens.next.line || !state.option.inMoz(true)) { - if (state.tokens.next.id !== ";" && !state.tokens.next.reach && state.tokens.next.nud) { + if (delegatingYield || + (state.tokens.next.id !== ";" && !state.tokens.next.reach && state.tokens.next.nud)) { + nobreaknonadjacent(state.tokens.curr, state.tokens.next); this.first = expression(10); @@ -5438,7 +5554,9 @@ var JSHINT = (function () { stmt("throw", function () { nolinebreak(this); this.first = expression(20); - reachable("throw"); + + reachable(this); + return this; }).exps = true; @@ -5448,12 +5566,23 @@ var JSHINT = (function () { } if (state.tokens.next.type === "(string)") { + // ModuleSpecifier :: StringLiteral advance("(string)"); return this; } + if (state.tokens.next.identifier) { + // ImportClause :: ImportedDefaultBinding this.name = identifier(); addlabel(this.name, { type: "unused", token: state.tokens.curr }); + } else if (state.tokens.next.id === "*") { + // ImportClause :: NameSpaceImport + advance("*"); + advance("as"); + if (state.tokens.next.identifier) { + this.name = identifier(); + addlabel(this.name, { type: "unused", token: state.tokens.curr }); + } } else { advance("{"); for (;;) { @@ -5486,17 +5615,33 @@ var JSHINT = (function () { } } + // FromClause advance("from"); advance("(string)"); return this; }).exps = true; stmt("export", function () { + var ok = true; if (!state.option.inESNext()) { warning("W119", state.tokens.curr, "export"); + ok = false; + } + + if (!funct["(global)"] || !funct["(blockscope)"].atTop()) { + error("E053", state.tokens.curr); + ok = false; + } + + if (state.tokens.next.value === "*") { + advance("*"); + advance("from"); + advance("(string)"); + return this; } if (state.tokens.next.type === "default") { + state.nameStack.set(state.tokens.next); advance("default"); if (state.tokens.next.id === "function" || state.tokens.next.id === "class") { this.block = true; @@ -5509,7 +5654,11 @@ var JSHINT = (function () { if (state.tokens.next.value === "{") { advance("{"); for (;;) { - exported[identifier()] = true; + var id; + exported[id = identifier(false, false, ok)] = ok; + if (ok) { + funct["(blockscope)"].setExported(id); + } if (state.tokens.next.value === ",") { advance(","); @@ -5526,25 +5675,30 @@ var JSHINT = (function () { if (state.tokens.next.id === "var") { advance("var"); - exported[state.tokens.next.value] = true; + exported[state.tokens.next.value] = ok; + state.tokens.next.exported = true; state.syntax["var"].fud.call(state.syntax["var"].fud); } else if (state.tokens.next.id === "let") { advance("let"); - exported[state.tokens.next.value] = true; + exported[state.tokens.next.value] = ok; + state.tokens.next.exported = true; state.syntax["let"].fud.call(state.syntax["let"].fud); } else if (state.tokens.next.id === "const") { advance("const"); - exported[state.tokens.next.value] = true; + exported[state.tokens.next.value] = ok; + state.tokens.next.exported = true; state.syntax["const"].fud.call(state.syntax["const"].fud); } else if (state.tokens.next.id === "function") { this.block = true; advance("function"); - exported[state.tokens.next.value] = true; + exported[state.tokens.next.value] = ok; + state.tokens.next.exported = true; state.syntax["function"].fud(); } else if (state.tokens.next.id === "class") { this.block = true; advance("class"); - exported[state.tokens.next.value] = true; + exported[state.tokens.next.value] = ok; + state.tokens.next.exported = true; state.syntax["class"].fud(); } else { error("E024", state.tokens.next, state.tokens.next.value); @@ -5584,7 +5738,7 @@ var JSHINT = (function () { FutureReservedWord("transient"); FutureReservedWord("volatile"); - // this function is used to determine wether a squarebracket or a curlybracket + // this function is used to determine whether a squarebracket or a curlybracket // expression is a comprehension array, destructuring assignment or a json value. var lookupBlockType = function () { @@ -5592,15 +5746,15 @@ var JSHINT = (function () { var i = -1; var bracketStack = 0; var ret = {}; - if (_.contains(["[", "{"], state.tokens.curr.value)) + if (checkPunctuators(state.tokens.curr, ["[", "{"])) bracketStack += 1; do { pn = (i === -1) ? state.tokens.next : peek(i); pn1 = peek(i + 1); i = i + 1; - if (_.contains(["[", "{"], pn.value)) { + if (checkPunctuators(pn, ["[", "{"])) { bracketStack += 1; - } else if (_.contains(["]", "}"], pn.value)) { + } else if (checkPunctuators(pn, ["]", "}"])) { bracketStack -= 1; } if (pn.identifier && pn.value === "for" && bracketStack === 1) { @@ -5608,10 +5762,15 @@ var JSHINT = (function () { ret.notJson = true; break; } - if (_.contains(["}", "]"], pn.value) && pn1.value === "=" && bracketStack === 0) { - ret.isDestAssign = true; - ret.notJson = true; - break; + if (checkPunctuators(pn, ["}", "]"]) && bracketStack === 0) { + if (pn1.value === "=") { + ret.isDestAssign = true; + ret.notJson = true; + break; + } else if (pn1.value === ".") { + ret.notJson = true; + break; + } } if (pn.value === ";") { ret.isBlock = true; @@ -5621,6 +5780,74 @@ var JSHINT = (function () { return ret; }; + function saveProperty(props, name, tkn, isClass, isStatic) { + var msg = ["key", "class method", "static class method"]; + msg = msg[(isClass || false) + (isStatic || false)]; + if (tkn.identifier) { + name = tkn.value; + } + + if (props[name] && _.has(props, name)) { + warning("W075", state.tokens.next, msg, name); + } else { + props[name] = {}; + } + + props[name].basic = true; + props[name].basictkn = tkn; + } + + /** + * @param {string} accessorType - Either "get" or "set" + * @param {object} props - a collection of all properties of the object to + * which the current accessor is being assigned + * @param {object} tkn - the identifier token representing the accessor name + * @param {boolean} isClass - whether the accessor is part of an ES6 Class + * definition + * @param {boolean} isStatic - whether the accessor is a static method + */ + function saveAccessor(accessorType, props, name, tkn, isClass, isStatic) { + var flagName = accessorType === "get" ? "getterToken" : "setterToken"; + var msg = ""; + + if (isClass) { + if (isStatic) { + msg += "static "; + } + msg += accessorType + "ter method"; + } else { + msg = "key"; + } + + state.tokens.curr.accessorType = accessorType; + state.nameStack.set(tkn); + + if (props[name] && _.has(props, name)) { + if (props[name].basic || props[name][flagName]) { + warning("W075", state.tokens.next, msg, name); + } + } else { + props[name] = {}; + } + + props[name][flagName] = tkn; + } + + function computedPropertyName() { + advance("["); + if (!state.option.esnext) { + warning("W119", state.tokens.curr, "computed property names"); + } + var value = expression(10); + advance("]"); + return value; + } + + // Test whether a given token is a punctuator matching one of the specified values + function checkPunctuators(token, values) { + return token.type === "(punctuator)" && _.contains(values, token.value); + } + // Check whether this function has been reached for a destructuring assign with undeclared values function destructuringAssignOrJsonValue() { // lookup for the assignment (esnext only) @@ -5686,7 +5913,7 @@ var JSHINT = (function () { unstack: function () { _current.variables.filter(function (v) { if (v.unused) - warning("W098", v.token, v.value); + warning("W098", v.token, v.raw_text || v.value); if (v.undef) isundef(v.funct, "W117", v.token, v.value); }); @@ -5839,6 +6066,10 @@ var JSHINT = (function () { if (_current[t]["(type)"] === "unused") { if (state.option.unused) { var tkn = _current[t]["(token)"]; + // Don't report exported labels as unused + if (tkn.exported) { + continue; + } var line = tkn.line; var chr = tkn.character; warningAt("W098", line, chr, t); @@ -5883,6 +6114,19 @@ var JSHINT = (function () { } }, + atTop: function () { + return _variables.length === 1; + }, + + setExported: function (id) { + if (funct["(blockscope)"].atTop()) { + var item = _current[id]; + if (item && item["(token)"]) { + item["(token)"].exported = true; + } + } + }, + current: { has: function (t) { return _.has(_current, t); @@ -5895,9 +6139,13 @@ var JSHINT = (function () { }; }; + var escapeRegex = function(str) { + return str.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&"); + }; + // The actual JSHINT function itself. var itself = function (s, o, g) { - var i, k, x; + var i, k, x, reIgnoreStr, reIgnore; var optionKeys; var newOptionObj = {}; var newIgnoredObj = {}; @@ -5941,6 +6189,8 @@ var JSHINT = (function () { if (item[0] === "-") { slice = item.slice(1); JSHINT.blacklist[slice] = slice; + // remove from predefined if there + delete predefined[slice]; } else { prop = Object.getOwnPropertyDescriptor(o.predef, item); predefined[item] = prop ? prop.value : false; @@ -6034,6 +6284,28 @@ var JSHINT = (function () { state.tokens.prev = state.tokens.curr = state.tokens.next = state.syntax["(begin)"]; + if (o && o.ignoreDelimiters) { + + if (!Array.isArray(o.ignoreDelimiters)) { + o.ignoreDelimiters = [o.ignoreDelimiters]; + } + + o.ignoreDelimiters.forEach(function (delimiterPair) { + if (!delimiterPair.start || !delimiterPair.end) + return; + + reIgnoreStr = escapeRegex(delimiterPair.start) + + "[\\s\\S]*?" + + escapeRegex(delimiterPair.end); + + reIgnore = new RegExp(reIgnoreStr, "ig"); + + s = s.replace(reIgnore, function(match) { + return match.replace(/./g, " "); + }); + }); + } + lex = new Lexer(s); lex.on("warning", function (ev) { @@ -6088,8 +6360,10 @@ var JSHINT = (function () { directives(); if (state.directive["use strict"]) { - if (!state.option.globalstrict && !(state.option.node || state.option.phantom)) { - warning("W097", state.tokens.prev); + if (!state.option.globalstrict) { + if (!(state.option.node || state.option.phantom || state.option.browserify)) { + warning("W097", state.tokens.prev); + } } } @@ -6139,7 +6413,8 @@ var JSHINT = (function () { var warnUnused = function (name, tkn, type, unused_opt) { var line = tkn.line; - var chr = tkn.character; + var chr = tkn.from; + var raw_name = tkn.raw_text || name; if (unused_opt === undefined) { unused_opt = state.option.unused; @@ -6157,7 +6432,9 @@ var JSHINT = (function () { if (unused_opt) { if (warnable_types[unused_opt] && warnable_types[unused_opt].indexOf(type) !== -1) { - warningAt("W098", line, chr, name); + if (!tkn.exported) { + warningAt("W098", line, chr, raw_name); + } } } @@ -6197,7 +6474,7 @@ var JSHINT = (function () { for (i = 0; i < JSHINT.undefs.length; i += 1) { k = JSHINT.undefs[i].slice(0); - if (markDefined(k[2].value, k[0])) { + if (markDefined(k[2].value, k[0]) || k[2].forgiveUndef) { clearImplied(k[2].value, k[2].line); } else if (state.option.undef) { warning.apply(warning, k.slice(1)); @@ -6384,7 +6661,7 @@ if (typeof exports === "object" && exports) { } }, -{"./lex.js":4,"./messages.js":5,"./reg.js":6,"./state.js":7,"./style.js":8,"./vars.js":9,"events":10,"underscore":2}], +{"./lex.js":4,"./messages.js":5,"./options.js":7,"./reg.js":8,"./state.js":9,"./style.js":10,"./vars.js":11,"events":12,"underscore":2}], 4:[function(_dereq_,module,exports){ /* * Lexical analysis and token construction. @@ -6415,7 +6692,9 @@ var Token = { NullLiteral: 7, BooleanLiteral: 8, RegExp: 9, - TemplateLiteral: 10 + TemplateHead: 10, + TemplateMiddle: 11, + TemplateTail: 12 }; // Object that handles postponed lexing verifications that checks the parsed @@ -6495,6 +6774,9 @@ function Lexer(source) { this.from = 1; this.input = ""; this.inComment = false; + this.inTemplate = false; + this.templateLine = null; + this.templateChar = null; for (var i = 0; i < state.option.indent; i += 1) { state.tab += " "; @@ -6704,13 +6986,10 @@ Lexer.prototype = { }; } - // Special case: /=. We need to make sure that this is an - // operator and not a regular expression. + // Special case: /=. if (ch1 === "/") { - if (ch2 === "=" && /\/=(?!(\S*\/[gim]?))/.test(this.input)) { - // /= is not a part of a regular expression, return it as a - // punctuator. + if (ch2 === "=") { return { type: Token.Punctuator, value: "/=" @@ -6758,6 +7037,8 @@ Lexer.prototype = { value += "*/"; } + body = body.replace(/\n/g, " "); + special.forEach(function (str) { if (isSpecial) { return; @@ -6769,13 +7050,14 @@ Lexer.prototype = { return; } - if (body.substr(0, str.length) === str) { + if (body.charAt(str.length) === " " && body.substr(0, str.length) === str) { isSpecial = true; label = label + str; body = body.substr(str.length); } - if (!isSpecial && body.charAt(0) === " " && body.substr(1, str.length) === str) { + if (!isSpecial && body.charAt(0) === " " && body.charAt(str.length + 1) === " " && + body.substr(1, str.length) === str) { isSpecial = true; label = label + " " + str; body = body.substr(str.length + 1); @@ -6919,7 +7201,7 @@ Lexer.prototype = { return (/^[0-9a-fA-F]$/).test(str); } - var readUnicodeEscapeSequence = function () { + var readUnicodeEscapeSequence = function() { /*jshint validthis:true */ index += 1; @@ -6999,6 +7281,12 @@ Lexer.prototype = { return null; }.bind(this); + function removeEscapeSequences(id) { + return id.replace(/\\u([0-9a-fA-F]{4})/g, function(m0, codepoint) { + return String.fromCharCode(parseInt(codepoint, 16)); + }); + } + char = getIdentifierStart(); if (char === null) { return null; @@ -7029,7 +7317,9 @@ Lexer.prototype = { return { type: type, - value: id + value: removeEscapeSequences(id), + text: id, + tokenLength: id.length }; }, @@ -7048,6 +7338,9 @@ Lexer.prototype = { var length = this.input.length; var char = this.peek(index); var bad; + var isAllowedDigit = isDecimalDigit; + var base = 10; + var isLegacy = false; function isDecimalDigit(str) { return (/^[0-9]$/).test(str); @@ -7057,6 +7350,10 @@ Lexer.prototype = { return (/^[0-7]$/).test(str); } + function isBinaryDigit(str) { + return (/^[01]$/).test(str); + } + function isHexDigit(str) { return (/^[0-9a-fA-F]$/).test(str); } @@ -7080,81 +7377,64 @@ Lexer.prototype = { if (value === "0") { // Base-16 numbers. if (char === "x" || char === "X") { + isAllowedDigit = isHexDigit; + base = 16; + index += 1; value += char; - - while (index < length) { - char = this.peek(index); - if (!isHexDigit(char)) { - break; - } - value += char; - index += 1; - } - - if (value.length <= 2) { // 0x - return { - type: Token.NumericLiteral, - value: value, - isMalformed: true - }; - } - - if (index < length) { - char = this.peek(index); - if (isIdentifierStart(char)) { - return null; - } - } - - return { - type: Token.NumericLiteral, - value: value, - base: 16, - isMalformed: false - }; } // Base-8 numbers. - if (isOctalDigit(char)) { + if (char === "o" || char === "O") { + isAllowedDigit = isOctalDigit; + base = 8; + + if (!state.option.esnext) { + this.trigger("warning", { + code: "W119", + line: this.line, + character: this.char, + data: [ "Octal integer literal" ] + }); + } + index += 1; value += char; + } + + // Base-2 numbers. + if (char === "b" || char === "B") { + isAllowedDigit = isBinaryDigit; + base = 2; + + if (!state.option.esnext) { + this.trigger("warning", { + code: "W119", + line: this.line, + character: this.char, + data: [ "Binary integer literal" ] + }); + } + + index += 1; + value += char; + } + + // Legacy base-8 numbers. + if (isOctalDigit(char)) { + isAllowedDigit = isOctalDigit; + base = 8; + isLegacy = true; bad = false; - while (index < length) { - char = this.peek(index); - - // Numbers like '019' (note the 9) are not valid octals - // but we still parse them and mark as malformed. - - if (isDecimalDigit(char)) { - bad = true; - } else if (!isOctalDigit(char)) { - break; - } - value += char; - index += 1; - } - - if (index < length) { - char = this.peek(index); - if (isIdentifierStart(char)) { - return null; - } - } - - return { - type: Token.NumericLiteral, - value: value, - base: 8, - isMalformed: false - }; + index += 1; + value += char; } // Decimal numbers that start with '0' such as '09' are illegal // but we still parse them and return as malformed. - if (isDecimalDigit(char)) { + if (!isOctalDigit(char) && isDecimalDigit(char)) { index += 1; value += char; } @@ -7162,12 +7442,42 @@ Lexer.prototype = { while (index < length) { char = this.peek(index); - if (!isDecimalDigit(char)) { + + if (isLegacy && isDecimalDigit(char)) { + // Numbers like '019' (note the 9) are not valid octals + // but we still parse them and mark as malformed. + bad = true; + } else if (!isAllowedDigit(char)) { break; } value += char; index += 1; } + + if (isAllowedDigit !== isDecimalDigit) { + if (!isLegacy && value.length <= 2) { // 0x + return { + type: Token.NumericLiteral, + value: value, + isMalformed: true + }; + } + + if (index < length) { + char = this.peek(index); + if (isIdentifierStart(char)) { + return null; + } + } + + return { + type: Token.NumericLiteral, + value: value, + base: base, + isLegacy: isLegacy, + isMalformed: false + }; + } } // Decimal digits. @@ -7226,64 +7536,178 @@ Lexer.prototype = { return { type: Token.NumericLiteral, value: value, - base: 10, + base: base, isMalformed: !isFinite(value) }; }, + + // Assumes previously parsed character was \ (=== '\\') and was not skipped. + scanEscapeSequence: function (checks) { + var allowNewLine = false; + var jump = 1; + this.skip(); + var char = this.peek(); + + switch (char) { + case "'": + this.triggerAsync("warning", { + code: "W114", + line: this.line, + character: this.char, + data: [ "\\'" ] + }, checks, function () {return state.jsonMode; }); + break; + case "b": + char = "\\b"; + break; + case "f": + char = "\\f"; + break; + case "n": + char = "\\n"; + break; + case "r": + char = "\\r"; + break; + case "t": + char = "\\t"; + break; + case "0": + char = "\\0"; + + // Octal literals fail in strict mode. + // Check if the number is between 00 and 07. + var n = parseInt(this.peek(1), 10); + this.triggerAsync("warning", { + code: "W115", + line: this.line, + character: this.char + }, checks, + function () { return n >= 0 && n <= 7 && state.directive["use strict"]; }); + break; + case "u": + char = String.fromCharCode(parseInt(this.input.substr(1, 4), 16)); + jump = 5; + break; + case "v": + this.triggerAsync("warning", { + code: "W114", + line: this.line, + character: this.char, + data: [ "\\v" ] + }, checks, function () { return state.jsonMode; }); + + char = "\v"; + break; + case "x": + var x = parseInt(this.input.substr(1, 2), 16); + + this.triggerAsync("warning", { + code: "W114", + line: this.line, + character: this.char, + data: [ "\\x-" ] + }, checks, function () { return state.jsonMode; }); + + char = String.fromCharCode(x); + jump = 3; + break; + case "\\": + char = "\\\\"; + break; + case "\"": + char = "\\\""; + break; + case "/": + break; + case "": + allowNewLine = true; + char = ""; + break; + } + + return {char: char, jump: jump, allowNewLine: allowNewLine}; + }, + /* * Extract a template literal out of the next sequence of characters * and/or lines or return 'null' if its not possible. Since template * literals can span across multiple lines, this method has to move * the char pointer. */ - scanTemplateLiteral: function () { + scanTemplateLiteral: function (checks) { + var tokenType; + var value = ''; + var ch; + // String must start with a backtick. - if (!state.option.esnext || this.peek() !== "`") { + if (!this.inTemplate) { + if (!state.option.esnext || this.peek() !== "`") { + return null; + } + this.templateLine = this.line; + this.templateChar = this.char; + this.skip(1); + } else if (this.peek() !== '}') { + // If we're in a template, and we don't have a '}', lex something else instead. return null; } - var startLine = this.line; - var startChar = this.char; - var jump = 1; - var value = ""; - - // For now, do not perform any linting of the content of the template - // string. Just skip until the next backtick is found. - this.skip(); - while (this.peek() !== "`") { - while (this.peek() === "") { - // End of line --- For template literals in ES6, no backslash is - // required to precede newlines. + while ((ch = this.peek()) === "") { if (!this.nextLine()) { + // Unclosed template literal --- point to the starting line, or the EOF? + tokenType = this.inTemplate ? Token.TemplateHead : Token.TemplateMiddle; + this.inTemplate = false; this.trigger("error", { code: "E052", - line: startLine, - character: startChar + line: this.templateLine, + character: this.templateChar }); - return { - type: Token.TemplateLiteral, + type: tokenType, value: value, isUnclosed: true }; } - value += "\n"; } - // TODO: do more interesting linting here, similar to string literal - // linting. - var char = this.peek(); - this.skip(jump); - value += char; + if (ch === '$' && this.peek(1) === '{') { + value += '${'; + tokenType = value.charAt(0) === '}' ? Token.TemplateMiddle : Token.TemplateHead; + // Either TokenHead or TokenMiddle --- depending on if the initial value + // is '}' or not. + this.skip(2); + this.inTemplate = true; + return { + type: tokenType, + value: value, + isUnclosed: false + }; + } else if (ch === '\\') { + var escape = this.scanEscapeSequence(checks); + value += escape.char; + this.skip(escape.jump); + } else if (ch === '`') { + break; + } else { + // Otherwise, append the value and continue. + value += ch; + this.skip(1); + } } - this.skip(); + // Final value is either TokenTail or NoSubstititionTemplate --- essentially a string + tokenType = this.inTemplate ? Token.TemplateTail : Token.StringLiteral; + this.inTemplate = false; + this.skip(1); + return { - type: Token.TemplateLiteral, + type: tokenType, value: value, - isUnclosed: false + isUnclosed: false, + quote: "`" }; }, @@ -7394,101 +7818,11 @@ Lexer.prototype = { } // Special treatment for some escaped characters. - if (char === "\\") { - this.skip(); - char = this.peek(); - - switch (char) { - case "'": - this.triggerAsync("warning", { - code: "W114", - line: this.line, - character: this.char, - data: [ "\\'" ] - }, checks, function () {return state.jsonMode; }); - break; - case "b": - char = "\\b"; - break; - case "f": - char = "\\f"; - break; - case "n": - char = "\\n"; - break; - case "r": - char = "\\r"; - break; - case "t": - char = "\\t"; - break; - case "0": - char = "\\0"; - - // Octal literals fail in strict mode. - // Check if the number is between 00 and 07. - var n = parseInt(this.peek(1), 10); - this.triggerAsync("warning", { - code: "W115", - line: this.line, - character: this.char - }, checks, - function () { return n >= 0 && n <= 7 && state.directive["use strict"]; }); - break; - case "u": - char = String.fromCharCode(parseInt(this.input.substr(1, 4), 16)); - jump = 5; - break; - case "v": - this.triggerAsync("warning", { - code: "W114", - line: this.line, - character: this.char, - data: [ "\\v" ] - }, checks, function () { return state.jsonMode; }); - - char = "\v"; - break; - case "x": - var x = parseInt(this.input.substr(1, 2), 16); - - this.triggerAsync("warning", { - code: "W114", - line: this.line, - character: this.char, - data: [ "\\x-" ] - }, checks, function () { return state.jsonMode; }); - - char = String.fromCharCode(x); - jump = 3; - break; - case "\\": - char = "\\\\"; - break; - case "\"": - char = "\\\""; - break; - case "/": - break; - case "": - allowNewLine = true; - char = ""; - break; - case "!": - if (value.slice(value.length - 2) === "<") { - break; - } - - /*falls through */ - default: - // Weird escaping. - this.trigger("warning", { - code: "W044", - line: this.line, - character: this.char - }); - } + var parsed = this.scanEscapeSequence(checks); + char = parsed.char; + jump = parsed.jump; + allowNewLine = parsed.allowNewLine; } value += char; @@ -7672,7 +8006,7 @@ Lexer.prototype = { }, /* - * Scan for any occurence of non-breaking spaces. Non-breaking spaces + * Scan for any occurrence of non-breaking spaces. Non-breaking spaces * can be mistakenly typed on OS X with option-space. Non UTF-8 web * pages with non-breaking pages produce syntax errors. */ @@ -7711,7 +8045,7 @@ Lexer.prototype = { var match = this.scanComments() || this.scanStringLiteral(checks) || - this.scanTemplateLiteral(); + this.scanTemplateLiteral(checks); if (match) { return match; @@ -7727,7 +8061,7 @@ Lexer.prototype = { this.scanNumericLiteral(); if (match) { - this.skip(match.value.length); + this.skip(match.tokenLength || match.value.length); return match; } @@ -7769,7 +8103,7 @@ Lexer.prototype = { // If we are ignoring linter errors, replace the input with empty string // if it doesn't already at least start or end a multi-line comment if (state.ignoreLinterErrors === true) { - if (!startsWith("/*", "//") && !endsWith("*/")) { + if (!startsWith("/*", "//") && !(this.inComment && endsWith("*/"))) { this.input = ""; } } @@ -7814,7 +8148,7 @@ Lexer.prototype = { /* * Produce the next token. This function is called by advance() to get - * the next token. It retuns a token in a JSLint-compatible format. + * the next token. It returns a token in a JSLint-compatible format. */ token: function () { /*jshint loopfunc:true */ @@ -7851,7 +8185,7 @@ Lexer.prototype = { } // Produce a token object. - var create = function (type, value, isProperty) { + var create = function (type, value, isProperty, token) { /*jshint validthis:true */ var obj; @@ -7866,6 +8200,8 @@ Lexer.prototype = { case "~": case "#": case "]": + case "++": + case "--": this.prereg = false; break; default: @@ -7900,6 +8236,7 @@ Lexer.prototype = { obj.line = this.line; obj.character = this.char; obj.from = this.from; + if (obj.identifier && token) obj.raw_text = token.text || token.value; if (isProperty && obj.identifier) { obj.isProperty = isProperty; @@ -7945,8 +8282,8 @@ Lexer.prototype = { return create("(string)", token.value); - case Token.TemplateLiteral: - this.trigger("Template", { + case Token.TemplateHead: + this.trigger("TemplateHead", { line: this.line, char: this.char, from: this.from, @@ -7954,12 +8291,31 @@ Lexer.prototype = { }); return create("(template)", token.value); + case Token.TemplateMiddle: + this.trigger("TemplateMiddle", { + line: this.line, + char: this.char, + from: this.from, + value: token.value + }); + return create("(template middle)", token.value); + + case Token.TemplateTail: + this.trigger("TemplateTail", { + line: this.line, + char: this.char, + from: this.from, + value: token.value + }); + return create("(template tail)", token.value); + case Token.Identifier: this.trigger("Identifier", { line: this.line, char: this.char, from: this.form, name: token.value, + raw_name: token.text, isProperty: state.tokens.curr.id === "." }); @@ -7967,7 +8323,7 @@ Lexer.prototype = { case Token.Keyword: case Token.NullLiteral: case Token.BooleanLiteral: - return create("(identifier)", token.value, state.tokens.curr.id === "."); + return create("(identifier)", token.value, state.tokens.curr.id === ".", token); case Token.NumericLiteral: if (token.isMalformed) { @@ -7991,7 +8347,7 @@ Lexer.prototype = { line: this.line, character: this.char }, checks, function () { - return state.directive["use strict"] && token.base === 8; + return state.directive["use strict"] && token.base === 8 && token.isLegacy; }); this.trigger("Number", { @@ -8039,7 +8395,7 @@ Lexer.prototype = { exports.Lexer = Lexer; }, -{"../data/ascii-identifier-data.js":1,"./reg.js":6,"./state.js":7,"events":10,"underscore":2}], +{"../data/ascii-identifier-data.js":1,"./reg.js":8,"./state.js":9,"events":12,"underscore":2}], 5:[function(_dereq_,module,exports){ "use strict"; @@ -8110,7 +8466,9 @@ var errors = { E049: "A {a} cannot be named '{b}'.", E050: "Mozilla requires the yield expression to be parenthesized here.", E051: "Regular parameters cannot come after default parameters.", - E052: "Unclosed template literal." + E052: "Unclosed template literal.", + E053: "Export declaration must be in global scope.", + E054: "Class properties must be methods. Expected '(' but instead saw '{a}'." }; var warnings = { @@ -8156,7 +8514,7 @@ var warnings = { W041: "Use '{a}' to compare with '{b}'.", W042: "Avoid EOL escaping.", W043: "Bad escaping of EOL. Use option multistr if needed.", - W044: "Bad or unnecessary escaping.", + W044: "Bad or unnecessary escaping.", /* TODO(caitp): remove W044 */ W045: "Bad number '{a}'.", W046: "Don't use extra leading zeros '{a}'.", W047: "A trailing decimal point can be confused with a dot: '{a}'.", @@ -8189,7 +8547,7 @@ var warnings = { W072: "This function has too many parameters. ({a})", W073: "Blocks are nested too deeply. ({a})", W074: "This function's cyclomatic complexity is too high. ({a})", - W075: "Duplicate key '{a}'.", + W075: "Duplicate {a} '{b}'.", W076: "Unexpected parameter '{a}' in get {b} function.", W077: "Expected a single parameter in set {a} function.", W078: "Setter is defined without getter.", @@ -8240,7 +8598,10 @@ var warnings = { W122: "Invalid typeof value '{a}'", W123: "'{a}' is already defined in outer scope.", W124: "A generator function shall contain a yield statement.", - W125: "This line contains non-breaking spaces: http://jshint.com/doc/options/#nonbsp" + W125: "This line contains non-breaking spaces: http://jshint.com/doc/options/#nonbsp", + W126: "Grouping operator is unnecessary for lone expressions.", + W127: "Unexpected use of a comma operator.", + W128: "Empty array elements require elision=true." }; var info = { @@ -8268,13 +8629,978 @@ _.each(info, function (desc, code) { }, {"underscore":2}], 6:[function(_dereq_,module,exports){ +"use strict"; + +function NameStack() { + this._stack = []; +} + +Object.defineProperty(NameStack.prototype, "length", { + get: function() { + return this._stack.length; + } +}); + +/** + * Create a new entry in the stack. Useful for tracking names across + * expressions. + */ +NameStack.prototype.push = function() { + this._stack.push(null); +}; + +/** + * Discard the most recently-created name on the stack. + */ +NameStack.prototype.pop = function() { + this._stack.pop(); +}; + +/** + * Update the most recent name on the top of the stack. + * + * @param {object} token The token to consider as the source for the most + * recent name. + */ +NameStack.prototype.set = function(token) { + this._stack[this.length - 1] = token; +}; + +/** + * Generate a string representation of the most recent name. + * + * @returns {string} + */ +NameStack.prototype.infer = function() { + var nameToken = this._stack[this.length - 1]; + var prefix = ""; + var type; + + // During expected operation, the topmost entry on the stack will only + // reflect the current function's name when the function is declared without + // the `function` keyword (i.e. for in-line accessor methods). In other + // cases, the `function` expression itself will introduce an empty entry on + // the top of the stack, and this should be ignored. + if (!nameToken || nameToken.type === "class") { + nameToken = this._stack[this.length - 2]; + } + + if (!nameToken) { + return "(empty)"; + } + + type = nameToken.type; + + if (type !== "(string)" && type !== "(number)" && type !== "(identifier)" && type !== "default") { + return "(expression)"; + } + + if (nameToken.accessorType) { + prefix = nameToken.accessorType + " "; + } + + return prefix + nameToken.value; +}; + +module.exports = NameStack; + +}, +{}], +7:[function(_dereq_,module,exports){ +"use strict"; + +// These are the JSHint boolean options. +exports.bool = { + enforcing: { + + /** + * This option prohibits the use of bitwise operators such as `^` (XOR), + * `|` (OR) and others. Bitwise operators are very rare in JavaScript + * programs and quite often `&` is simply a mistyped `&&`. + */ + bitwise : true, + + /** + * + * This options prohibits overwriting prototypes of native objects such as + * `Array`, `Date` and so on. + * + * // jshint freeze:true + * Array.prototype.count = function (value) { return 4; }; + * // -> Warning: Extending prototype of native object: 'Array'. + */ + freeze : true, + + /** + * This option allows you to force all variable names to use either + * camelCase style or UPPER_CASE with underscores. + */ + camelcase : true, + + /** + * This option requires you to always put curly braces around blocks in + * loops and conditionals. JavaScript allows you to omit curly braces when + * the block consists of only one statement, for example: + * + * while (day) + * shuffle(); + * + * However, in some circumstances, it can lead to bugs (you'd think that + * `sleep()` is a part of the loop while in reality it is not): + * + * while (day) + * shuffle(); + * sleep(); + */ + curly : true, + + /** + * This options prohibits the use of `==` and `!=` in favor of `===` and + * `!==`. The former try to coerce values before comparing them which can + * lead to some unexpected results. The latter don't do any coercion so + * they are generally safer. If you would like to learn more about type + * coercion in JavaScript, we recommend [Truth, Equality and + * JavaScript](http://javascriptweblog.wordpress.com/2011/02/07/truth-equality-and-javascript/) + * by Angus Croll. + */ + eqeqeq : true, + + /** + * This option suppresses warnings about invalid `typeof` operator values. + * This operator has only [a limited set of possible return + * values](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/typeof). + * By default, JSHint warns when you compare its result with an invalid + * value which often can be a typo. + * + * // 'fuction' instead of 'function' + * if (typeof a == "fuction") { // Invalid typeof value 'fuction' + * // ... + * } + * + * Do not use this option unless you're absolutely sure you don't want + * these checks. + */ + notypeof : true, + + /** + * This option tells JSHint that your code needs to adhere to ECMAScript 3 + * specification. Use this option if you need your program to be executable + * in older browsers—such as Internet Explorer 6/7/8/9—and other legacy + * JavaScript environments. + */ + es3 : true, + + /** + * This option enables syntax first defined in [the ECMAScript 5.1 + * specification](http://es5.github.io/). This includes allowing reserved + * keywords as object properties. + */ + es5 : true, + + /** + * This option requires all `for in` loops to filter object's items. The + * for in statement allows for looping through the names of all of the + * properties of an object including those inherited through the prototype + * chain. This behavior can lead to unexpected items in your object so it + * is generally safer to always filter inherited properties out as shown in + * the example: + * + * for (key in obj) { + * if (obj.hasOwnProperty(key)) { + * // We are sure that obj[key] belongs to the object and was not inherited. + * } + * } + * + * For more in-depth understanding of `for in` loops in JavaScript, read + * [Exploring JavaScript for-in + * loops](http://javascriptweblog.wordpress.com/2011/01/04/exploring-javascript-for-in-loops/) + * by Angus Croll. + */ + forin : true, + + /** + * This option suppresses warnings about declaring variables inside of + * control + * structures while accessing them later from the outside. Even though + * JavaScript has only two real scopes—global and function—such practice + * leads to confusion among people new to the language and hard-to-debug + * bugs. This is why, by default, JSHint warns about variables that are + * used outside of their intended scope. + * + * function test() { + * if (true) { + * var x = 0; + * } + * + * x += 1; // Default: 'x' used out of scope. + * // No warning when funcscope:true + * } + */ + funcscope : true, + + /** + * This option suppresses warnings about the use of global strict mode. + * Global strict mode can break third-party widgets so it is not + * recommended. + * + * For more info about strict mode see the `strict` option. + */ + globalstrict: true, + + /** + * This option prohibits the use of immediate function invocations without + * wrapping them in parentheses. Wrapping parentheses assists readers of + * your code in understanding that the expression is the result of a + * function, and not the function itself. + */ + immed : true, + + /** + * This option suppresses warnings about the `__iterator__` property. This + * property is not supported by all browsers so use it carefully. + */ + iterator : true, + + /** + * This option requires you to capitalize names of constructor functions. + * Capitalizing functions that are intended to be used with `new` operator + * is just a convention that helps programmers to visually distinguish + * constructor functions from other types of functions to help spot + * mistakes when using `this`. + * + * Not doing so won't break your code in any browsers or environments but + * it will be a bit harder to figure out—by reading the code—if the + * function was supposed to be used with or without new. And this is + * important because when the function that was intended to be used with + * `new` is used without it, `this` will point to the global object instead + * of a new object. + */ + newcap : true, + + /** + * This option prohibits the use of `arguments.caller` and + * `arguments.callee`. Both `.caller` and `.callee` make quite a few + * optimizations impossible so they were deprecated in future versions of + * JavaScript. In fact, ECMAScript 5 forbids the use of `arguments.callee` + * in strict mode. + */ + noarg : true, + + /** + * This option prohibits the use of the comma operator. When misused, the + * comma operator can obscure the value of a statement and promote + * incorrect code. + */ + nocomma : true, + + /** + * This option warns when you have an empty block in your code. JSLint was + * originally warning for all empty blocks and we simply made it optional. + * There were no studies reporting that empty blocks in JavaScript break + * your code in any way. + */ + noempty : true, + + /** + * This option warns about "non-breaking whitespace" characters. These + * characters can be entered with option-space on Mac computers and have a + * potential of breaking non-UTF8 web pages. + */ + nonbsp : true, + + /** + * This option prohibits the use of constructor functions for side-effects. + * Some people like to call constructor functions without assigning its + * result to any variable: + * + * new MyConstructor(); + * + * There is no advantage in this approach over simply calling + * `MyConstructor` since the object that the operator `new` creates isn't + * used anywhere so you should generally avoid constructors like this one. + */ + nonew : true, + + /** + * This option prohibits the use of explicitly undeclared variables. This + * option is very useful for spotting leaking and mistyped variables. + * + * // jshint undef:true + * + * function test() { + * var myVar = 'Hello, World'; + * console.log(myvar); // Oops, typoed here. JSHint with undef will complain + * } + * + * If your variable is defined in another file, you can use the `global` + * directive to tell JSHint about it. + */ + undef : true, + + /** + * This option prohibits the use of the grouping operator for + * single-expression statements. This unecessary usage commonly reflects + * a misunderstanding of unary operators, for example: + * + * // jshint singleGroups: true + * + * delete(obj.attr); // Warning: Grouping operator is unnecessary for lone expressions. + */ + singleGroups: false, + + /** + * This option is a short hand for the most strict JSHint configuration. It + * enables all enforcing options and disables all relaxing options. + */ + enforceall : false + }, + relaxing: { + + /** + * This option suppresses warnings about missing semicolons. There is a lot + * of FUD about semicolon spread by quite a few people in the community. + * The common myths are that semicolons are required all the time (they are + * not) and that they are unreliable. JavaScript has rules about semicolons + * which are followed by *all* browsers so it is up to you to decide + * whether you should or should not use semicolons in your code. + * + * For more information about semicolons in JavaScript read [An Open Letter + * to JavaScript Leaders Regarding + * Semicolons](http://blog.izs.me/post/2353458699/an-open-letter-to-javascript-leaders-regarding) + * by Isaac Schlueter and [JavaScript Semicolon + * Insertion](http://inimino.org/~inimino/blog/javascript_semicolons). + */ + asi : true, + + /** + * This option suppresses warnings about multi-line strings. Multi-line + * strings can be dangerous in JavaScript because all hell breaks loose if + * you accidentally put a whitespace in between the escape character (`\`) + * and a new line. + * + * Note that even though this option allows correct multi-line strings, it + * still warns about multi-line strings without escape characters or with + * anything in between the escape character and a whitespace. + * + * // jshint multistr:true + * + * var text = "Hello\ + * World"; // All good. + * + * text = "Hello + * World"; // Warning, no escape character. + * + * text = "Hello\ + * World"; // Warning, there is a space after \ + */ + multistr : true, + + /** + * This option suppresses warnings about the `debugger` statements in your + * code. + */ + debug : true, + + /** + * This option suppresses warnings about the use of assignments in cases + * where comparisons are expected. More often than not, code like `if (a = + * 10) {}` is a typo. However, it can be useful in cases like this one: + * + * for (var i = 0, person; person = people[i]; i++) {} + * + * You can silence this error on a per-use basis by surrounding the assignment + * with parenthesis, such as: + * + * for (var i = 0, person; (person = people[i]); i++) {} + */ + boss : true, + + /** + * This option defines globals available when your core is running inside + * of the PhantomJS runtime environment. [PhantomJS](http://phantomjs.org/) + * is a headless WebKit scriptable with a JavaScript API. It has fast and + * native support for various web standards: DOM handling, CSS selector, + * JSON, Canvas, and SVG. + */ + phantom : true, + + /** + * This option suppresses warnings about the use of `eval`. The use of + * `eval` is discouraged because it can make your code vulnerable to + * various injection attacks and it makes it hard for JavaScript + * interpreter to do certain optimizations. + */ + evil : true, + + /** + * This option prohibits the use of unary increment and decrement + * operators. Some people think that `++` and `--` reduces the quality of + * their coding styles and there are programming languages—such as + * Python—that go completely without these operators. + */ + plusplus : true, + + /** + * This option suppresses warnings about the `__proto__` property. + */ + proto : true, + + /** + * This option suppresses warnings about the use of script-targeted + * URLs—such as `javascript:...`. + */ + scripturl : true, + + /** + * This option requires all functions to run in ECMAScript 5's strict mode. + * [Strict mode](https://developer.mozilla.org/en/JavaScript/Strict_mode) + * is a way to opt in to a restricted variant of JavaScript. Strict mode + * eliminates some JavaScript pitfalls that didn't cause errors by changing + * them to produce errors. It also fixes mistakes that made it difficult + * for the JavaScript engines to perform certain optimizations. + * + * *Note:* This option enables strict mode for function scope only. It + * *prohibits* the global scoped strict mode because it might break + * third-party widgets on your page. If you really want to use global + * strict mode, see the *globalstrict* option. + */ + strict : true, + + /** + * This option suppresses warnings about using `[]` notation when it can be + * expressed in dot notation: `person['name']` vs. `person.name`. + */ + sub : true, + + /** + * This option suppresses warnings about "weird" constructions like + * `new function () { ... }` and `new Object;`. Such constructions are + * sometimes used to produce singletons in JavaScript: + * + * var singleton = new function() { + * var privateVar; + * + * this.publicMethod = function () {} + * this.publicMethod2 = function () {} + * }; + */ + supernew : true, + + /** + * This option suppresses most of the warnings about possibly unsafe line + * breakings in your code. It doesn't suppress warnings about comma-first + * coding style. To suppress those you have to use `laxcomma` (see below). + */ + laxbreak : true, + + /** + * This option suppresses warnings about comma-first coding style: + * + * var obj = { + * name: 'Anton' + * , handle: 'valueof' + * , role: 'SW Engineer' + * }; + */ + laxcomma : true, + + /** + * This option suppresses warnings about possible strict violations when + * the code is running in strict mode and you use `this` in a + * non-constructor function. You should use this option—in a function scope + * only—when you are positive that your use of `this` is valid in the + * strict mode (for example, if you call your function using + * `Function.call`). + * + * **Note:** This option can be used only inside of a function scope. + * JSHint will fail with an error if you will try to set this option + * globally. + */ + validthis : true, + + /** + * This option suppresses warnings about the use of the `with` statement. + * The semantics of the `with` statement can cause confusion among + * developers and accidental definition of global variables. + * + * More info: + * + * * [with Statement Considered + * Harmful](http://yuiblog.com/blog/2006/04/11/with-statement-considered-harmful/) + */ + withstmt : true, + + /** + * This options tells JSHint that your code uses Mozilla JavaScript + * extensions. Unless you develop specifically for the Firefox web browser + * you don't need this option. + * + * More info: + * + * * [New in JavaScript + * 1.7](https://developer.mozilla.org/en-US/docs/JavaScript/New_in_JavaScript/1.7) + */ + moz : true, + + /** + * This option suppresses warnings about generator functions with no + * `yield` statement in them. + */ + noyield : true, + + /** + * This option suppresses warnings about `== null` comparisons. Such + * comparisons are often useful when you want to check if a variable is + * `null` or `undefined`. + */ + eqnull : true, + + /** + * This option suppresses warnings about missing semicolons, but only when + * the semicolon is omitted for the last statement in a one-line block: + * + * var name = (function() { return 'Anton' }()); + * + * This is a very niche use case that is useful only when you use automatic + * JavaScript code generators. + */ + lastsemic : true, + + /** + * This option suppresses warnings about functions inside of loops. + * Defining functions inside of loops can lead to bugs such as this one: + * + * var nums = []; + * + * for (var i = 0; i < 10; i++) { + * nums[i] = function (j) { + * return i + j; + * }; + * } + * + * nums[0](2); // Prints 12 instead of 2 + * + * To fix the code above you need to copy the value of `i`: + * + * var nums = []; + * + * for (var i = 0; i < 10; i++) { + * (function (i) { + * nums[i] = function (j) { + * return i + j; + * }; + * }(i)); + * } + */ + loopfunc : true, + + /** + * This option suppresses warnings about the use of expressions where + * normally you would expect to see assignments or function calls. Most of + * the time, such code is a typo. However, it is not forbidden by the spec + * and that's why this warning is optional. + */ + expr : true, + + /** + * This option tells JSHint that your code uses ECMAScript 6 specific + * syntax. Note that these features are not finalized yet and not all + * browsers implement them. + * + * More info: + * + * * [Draft Specification for ES.next (ECMA-262 Ed. + * 6)](http://wiki.ecmascript.org/doku.php?id=harmony:specification_drafts) + */ + esnext : true, + + /** + * This option tells JSHint that your code uses ES3 array elision elements, + * or empty elements (for example, `[1, , , 4, , , 7]`). + */ + elision : true, + }, + + // Third party globals + environments: { + + /** + * This option defines globals exposed by the + * [MooTools](http://mootools.net/) JavaScript framework. + */ + mootools : true, + + /** + * This option defines globals exposed by + * [CouchDB](http://couchdb.apache.org/). CouchDB is a document-oriented + * database that can be queried and indexed in a MapReduce fashion using + * JavaScript. + */ + couch : true, + + /** + * This option defines globals exposed by [the Jasmine unit testing + * framework](https://jasmine.github.io/). + */ + jasmine : true, + + /** + * This option defines globals exposed by the [jQuery](http://jquery.com/) + * JavaScript library. + */ + jquery : true, + + /** + * This option defines globals available when your code is running inside + * of the Node runtime environment. [Node.js](http://nodejs.org/) is a + * server-side JavaScript environment that uses an asynchronous + * event-driven model. This option also skips some warnings that make sense + * in the browser environments but don't make sense in Node such as + * file-level `use strict` pragmas and `console.log` statements. + */ + node : true, + + /** + * This option defines globals exposed by [the QUnit unit testing + * framework](http://qunitjs.com/). + */ + qunit : true, + + /** + * This option defines globals available when your code is running inside + * of the Rhino runtime environment. [Rhino](http://www.mozilla.org/rhino/) + * is an open-source implementation of JavaScript written entirely in Java. + */ + rhino : true, + + /** + * This option defines globals exposed by [the ShellJS + * library](http://documentup.com/arturadib/shelljs). + */ + shelljs : true, + + /** + * This option defines globals exposed by the + * [Prototype](http://www.prototypejs.org/) JavaScript framework. + */ + prototypejs : true, + + /** + * This option defines globals exposed by the [YUI](http://yuilibrary.com/) + * JavaScript framework. + */ + yui : true, + + /** + * This option defines globals exposed by the "BDD" and "TDD" UIs of the + * [Mocha unit testing framework](http://mochajs.org/). + */ + mocha : true, + + /** + * This option defines globals available when your code is running as a + * script for the [Windows Script + * Host](http://en.wikipedia.org/wiki/Windows_Script_Host). + */ + wsh : true, + + /** + * This option defines globals available when your code is running inside + * of a Web Worker. [Web + * Workers](https://developer.mozilla.org/en/Using_web_workers) provide a + * simple means for web content to run scripts in background threads. + */ + worker : true, + + /** + * This option defines non-standard but widely adopted globals such as + * `escape` and `unescape`. + */ + nonstandard : true, + + /** + * This option defines globals exposed by modern browsers: all the way from + * good old `document` and `navigator` to the HTML5 `FileReader` and other + * new developments in the browser world. + * + * **Note:** This option doesn't expose variables like `alert` or + * `console`. See option `devel` for more information. + */ + browser : true, + + /** + * This option defines globals available when using [the Browserify + * tool](http://browserify.org/) to build a project. + */ + browserify : true, + + /** + * This option defines globals that are usually used for logging poor-man's + * debugging: `console`, `alert`, etc. It is usually a good idea to not + * ship them in production because, for example, `console.log` breaks in + * legacy versions of Internet Explorer. + */ + devel : true, + + /** + * This option defines globals exposed by the [Dojo + * Toolkit](http://dojotoolkit.org/). + */ + dojo : true, + + /** + * This option defines globals for typed array constructors. + * + * More info: + * + * * [JavaScript typed + * arrays](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays) + */ + typed : true + }, + + // Obsolete options + obsolete: { + onecase : true, // if one case switch statements should be allowed + regexp : true, // if the . should not be allowed in regexp literals + regexdash : true // if unescaped first/last dash (-) inside brackets + // should be tolerated + } +}; + +// These are the JSHint options that can take any value +// (we use this object to detect invalid options) +exports.val = { + + /** + * This option lets you set the maximum length of a line. + */ + maxlen : false, + + /** + * This option sets a specific tab width for your code. + */ + indent : false, + + /** + * This options allows you to set the maximum amount of warnings JSHint will + * produce before giving up. Default is 50. + */ + maxerr : false, + + predef : false, // predef is deprecated and being replaced by globals + + /** + * This option can be used to specify a white list of global variables that + * are not formally defined in the source code. This is most useful when + * combined with the `undef` option in order to suppress warnings for + * project-specific global variables. + * + * Setting an entry to `true` enables reading and writing to that variable. + * Setting it to `false` will trigger JSHint to consider that variable + * read-only. + * + * See also the "environment" options: a set of options to be used as short + * hand for enabling global variables defined in common JavaScript + * environments. + */ + globals : false, + + /** + * This option enforces the consistency of quotation marks used throughout + * your code. It accepts three values: `true` if you don't want to enforce + * one particular style but want some consistency, `"single"` if you want to + * allow only single quotes and `"double"` if you want to allow only double + * quotes. + */ + quotmark : false, + + scope : false, + + /** + * This option lets you set the max number of statements allowed per function: + * + * // jshint maxstatements:4 + * + * function main() { + * var i = 0; + * var j = 0; + * + * // Function declarations count as one statement. Their bodies + * // don't get taken into account for the outer function. + * function inner() { + * var i2 = 1; + * var j2 = 1; + * + * return i2 + j2; + * } + * + * j = i + j; + * return j; // JSHint: Too many statements per function. (5) + * } + */ + maxstatements: false, + + /** + * This option lets you control how nested do you want your blocks to be: + * + * // jshint maxdepth:2 + * + * function main(meaning) { + * var day = true; + * + * if (meaning === 42) { + * while (day) { + * shuffle(); + * + * if (tired) { // JSHint: Blocks are nested too deeply (3). + * sleep(); + * } + * } + * } + * } + */ + maxdepth : false, + + /** + * This option lets you set the max number of formal parameters allowed per + * function: + * + * // jshint maxparams:3 + * + * function login(request, onSuccess) { + * // ... + * } + * + * // JSHint: Too many parameters per function (4). + * function logout(request, isManual, whereAmI, onSuccess) { + * // ... + * } + */ + maxparams : false, + + /** + * This option lets you control cyclomatic complexity throughout your code. + * Cyclomatic complexity measures the number of linearly independent paths + * through a program's source code. Read more about [cyclomatic complexity on + * Wikipedia](http://en.wikipedia.org/wiki/Cyclomatic_complexity). + */ + maxcomplexity: false, + + /** + * This option suppresses warnings about variable shadowing i.e. declaring a + * variable that had been already declared somewhere in the outer scope. + * + * - "inner" - check for variables defined in the same scope only + * - "outer" - check for variables defined in outer scopes as well + * - false - same as inner + * - true - allow variable shadowing + */ + shadow : false, + + /** + * This option warns when you define and never use your variables. It is very + * useful for general code cleanup, especially when used in addition to + * `undef`. + * + * // jshint unused:true + * + * function test(a, b) { + * var c, d = 2; + * + * return a + d; + * } + * + * test(1, 2); + * + * // Line 3: 'b' was defined but never used. + * // Line 4: 'c' was defined but never used. + * + * In addition to that, this option will warn you about unused global + * variables declared via the `global` directive. + * + * This can be set to `vars` to only check for variables, not function + * parameters, or `strict` to check all variables and parameters. The + * default (true) behavior is to allow unused parameters that are followed by + * a used parameter. + */ + unused : true, + + /** + * This option prohibits the use of a variable before it was defined. + * JavaScript has function scope only and, in addition to that, all variables + * are always moved—or hoisted— to the top of the function. This behavior can + * lead to some very nasty bugs and that's why it is safer to always use + * variable only after they have been explicitly defined. + * + * Setting this option to "nofunc" will allow function declarations to be + * ignored. + * + * For more in-depth understanding of scoping and hoisting in JavaScript, + * read [JavaScript Scoping and + * Hoisting](http://www.adequatelygood.com/2010/2/JavaScript-Scoping-and-Hoisting) + * by Ben Cherry. + */ + latedef : false, + + ignore : false, // start/end ignoring lines of code, bypassing the lexer + // start - start ignoring lines, including the current line + // end - stop ignoring lines, starting on the next line + // line - ignore warnings / errors for just a single line + // (this option does not bypass the lexer) + ignoreDelimiters: false // array of start/end delimiters used to ignore + // certain chunks from code +}; + +// These are JSHint boolean options which are shared with JSLint +// where the definition in JSHint is opposite JSLint +exports.inverted = { + bitwise : true, + forin : true, + newcap : true, + plusplus: true, + regexp : true, + undef : true, + + // Inverted and renamed, use JSHint name here + eqeqeq : true, + strict : true +}; + +exports.validNames = Object.keys(exports.val) + .concat(Object.keys(exports.bool.relaxing)) + .concat(Object.keys(exports.bool.enforcing)) + .concat(Object.keys(exports.bool.obsolete)) + .concat(Object.keys(exports.bool.environments)); + +// These are JSHint boolean options which are shared with JSLint +// where the name has been changed but the effect is unchanged +exports.renamed = { + eqeq : "eqeqeq", + windows: "wsh", + sloppy : "strict" +}; + +exports.removed = { + nomen: true, + onevar: true, + passfail: true, + white: true, + gcl: true, + smarttabs: true, + trailing: true +}; + +}, +{}], +8:[function(_dereq_,module,exports){ /* * Regular expressions. Some of these are stupidly long. */ /*jshint maxlen:1000 */ -"use string"; +"use strict"; // Unsafe comment or string (ax) exports.unsafeString = @@ -8309,8 +9635,9 @@ exports.maxlenException = /^(?:(?:\/\/|\/\*|\*) ?)?[^ ]+$/; }, {}], -7:[function(_dereq_,module,exports){ +9:[function(_dereq_,module,exports){ "use strict"; +var NameStack = _dereq_("./name-stack.js"); var state = { syntax: {}, @@ -8331,6 +9658,8 @@ var state = { this.tab = ""; this.cache = {}; // Node.JS doesn't have Map. Sniff. this.ignoredLines = {}; + this.forinifcheckneeded = false; + this.nameStack = new NameStack(); // Blank out non-multi-line-commented lines when ignoring linter errors this.ignoreLinterErrors = false; @@ -8340,8 +9669,8 @@ var state = { exports.state = state; }, -{}], -8:[function(_dereq_,module,exports){ +{"./name-stack.js":6}], +10:[function(_dereq_,module,exports){ "use strict"; exports.register = function (linter) { @@ -8400,12 +9729,18 @@ exports.register = function (linter) { linter.on("String", function style_scanQuotes(data) { var quotmark = linter.getOption("quotmark"); + var esnext = linter.getOption("esnext"); var code; if (!quotmark) { return; } + // If quotmark is enabled, return if this is a template literal. + if (esnext && data.quote === "`") { + return; + } + // If quotmark is set to 'single' warn about all double-quotes. if (quotmark === "single" && data.quote !== "'") { @@ -8488,7 +9823,7 @@ exports.register = function (linter) { }, {}], -9:[function(_dereq_,module,exports){ +11:[function(_dereq_,module,exports){ // jshint -W001 "use strict"; @@ -8516,18 +9851,24 @@ exports.ecmaIdentifiers = { isFinite : false, isNaN : false, JSON : false, + Map : false, Math : false, Number : false, Object : false, + Proxy : false, + Promise : false, parseInt : false, parseFloat : false, RangeError : false, ReferenceError : false, RegExp : false, + Set : false, String : false, SyntaxError : false, TypeError : false, URIError : false, + WeakMap : false, + WeakSet : false }; exports.newEcmaIdentifiers = { @@ -8536,7 +9877,10 @@ exports.newEcmaIdentifiers = { WeakMap : false, WeakSet : false, Proxy : false, - Promise : false + Promise : false, + Reflect : false, + Symbol : false, + System : false }; // Global variables commonly provided by a web browser environment. @@ -8549,9 +9893,11 @@ exports.browser = { atob : false, blur : false, btoa : false, + cancelAnimationFrame : false, CanvasGradient : false, CanvasPattern : false, CanvasRenderingContext2D: false, + CSS : false, clearInterval : false, clearTimeout : false, close : false, @@ -8559,9 +9905,11 @@ exports.browser = { CustomEvent : false, DOMParser : false, defaultStatus : false, + Document : false, document : false, Element : false, ElementTimeControl : false, + Event : false, event : false, FileReader : false, FormData : false, @@ -8624,6 +9972,7 @@ exports.browser = { HTMLVideoElement : false, history : false, Image : false, + Intl : false, length : false, localStorage : false, location : false, @@ -8653,6 +10002,7 @@ exports.browser = { Option : false, parent : false, print : false, + requestAnimationFrame : false, removeEventListener : false, resizeBy : false, resizeTo : false, @@ -8820,12 +10170,26 @@ exports.browser = { SVGViewElement : false, SVGViewSpec : false, SVGZoomAndPan : false, + TextDecoder : false, + TextEncoder : false, TimeEvent : false, top : false, URL : false, + WebGLActiveInfo : false, + WebGLBuffer : false, + WebGLContextEvent : false, + WebGLFramebuffer : false, + WebGLProgram : false, + WebGLRenderbuffer : false, + WebGLRenderingContext: false, + WebGLShader : false, + WebGLShaderPrecisionFormat: false, + WebGLTexture : false, + WebGLUniformLocation : false, WebSocket : false, window : false, Worker : false, + XDomainRequest : false, XMLHttpRequest : false, XMLSerializer : false, XPathEvaluator : false, @@ -8846,9 +10210,10 @@ exports.devel = { }; exports.worker = { - importScripts: true, - postMessage : true, - self : true + importScripts : true, + postMessage : true, + self : true, + FileReaderSync : true }; // Widely adopted global names that are not part of ECMAScript standard @@ -8896,6 +10261,17 @@ exports.node = { clearImmediate: true // v0.9.1+ }; +exports.browserify = { + __filename : false, + __dirname : false, + global : false, + module : false, + require : false, + Buffer : true, + exports : true, + process : true +}; + exports.phantom = { phantom : true, require : true, @@ -9125,10 +10501,12 @@ exports.mocha = { beforeEach : false, afterEach : false, // TDD - suite : false, - test : false, - setup : false, - teardown : false + suite : false, + test : false, + setup : false, + teardown : false, + suiteSetup : false, + suiteTeardown : false }; exports.jasmine = { @@ -9145,12 +10523,18 @@ exports.jasmine = { // Jasmine 1.3 runs : false, waitsFor : false, - waits : false + waits : false, + // Jasmine 2.1 + beforeAll : false, + afterAll : false, + fail : false, + fdescribe : false, + fit : false }; }, {}], -10:[function(_dereq_,module,exports){ +12:[function(_dereq_,module,exports){ // Copyright Joyent, Inc. and other Node contributors. // // Permission is hereby granted, free of charge, to any person obtaining a @@ -9298,7 +10682,10 @@ EventEmitter.prototype.addListener = function(type, listener) { 'leak detected. %d listeners added. ' + 'Use emitter.setMaxListeners() to increase limit.', this._events[type].length); - console.trace(); + if (typeof console.trace === 'function') { + // not supported in IE 10 + console.trace(); + } } }