mirror of https://github.com/gogits/gogs.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
4523 lines
160 KiB
4523 lines
160 KiB
/*! |
|
* JSHint, by JSHint Community. |
|
* |
|
* Licensed under the same slightly modified MIT license that JSLint is. |
|
* It stops evil-doers everywhere. |
|
* |
|
* JSHint is a derivative work of JSLint: |
|
* |
|
* Copyright (c) 2002 Douglas Crockford (www.JSLint.com) |
|
* |
|
* Permission is hereby granted, free of charge, to any person obtaining |
|
* a copy of this software and associated documentation files (the "Software"), |
|
* to deal in the Software without restriction, including without limitation |
|
* the rights to use, copy, modify, merge, publish, distribute, sublicense, |
|
* and/or sell copies of the Software, and to permit persons to whom |
|
* the Software is furnished to do so, subject to the following conditions: |
|
* |
|
* The above copyright notice and this permission notice shall be included |
|
* in all copies or substantial portions of the Software. |
|
* |
|
* The Software shall be used for Good, not Evil. |
|
* |
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
|
* DEALINGS IN THE SOFTWARE. |
|
* |
|
* JSHint was forked from 2010-12-16 edition of JSLint. |
|
* |
|
*/ |
|
|
|
/* |
|
JSHINT is a global function. It takes two parameters. |
|
|
|
var myResult = JSHINT(source, option); |
|
|
|
The first parameter is either a string or an array of strings. If it is a |
|
string, it will be split on '\n' or '\r'. If it is an array of strings, it |
|
is assumed that each string represents one line. The source can be a |
|
JavaScript text or a JSON text. |
|
|
|
The second parameter is an optional object of options which control the |
|
operation of JSHINT. Most of the options are booleans: They are all |
|
optional and have a default value of false. One of the options, predef, |
|
can be an array of names, which will be used to declare global variables, |
|
or an object whose keys are used as global names, with a boolean value |
|
that determines if they are assignable. |
|
|
|
If it checks out, JSHINT returns true. Otherwise, it returns false. |
|
|
|
If false, you can inspect JSHINT.errors to find out the problems. |
|
JSHINT.errors is an array of objects containing these members: |
|
|
|
{ |
|
line : The line (relative to 0) at which the lint was found |
|
character : The character (relative to 0) at which the lint was found |
|
reason : The problem |
|
evidence : The text line in which the problem occurred |
|
raw : The raw message before the details were inserted |
|
a : The first detail |
|
b : The second detail |
|
c : The third detail |
|
d : The fourth detail |
|
} |
|
|
|
If a fatal error was found, a null will be the last element of the |
|
JSHINT.errors array. |
|
|
|
You can request a Function Report, which shows all of the functions |
|
and the parameters and vars that they use. This can be used to find |
|
implied global variables and other problems. The report is in HTML and |
|
can be inserted in an HTML <body>. |
|
|
|
var myReport = JSHINT.report(limited); |
|
|
|
If limited is true, then the report will be limited to only errors. |
|
|
|
You can request a data structure which contains JSHint's results. |
|
|
|
var myData = JSHINT.data(); |
|
|
|
It returns a structure with this form: |
|
|
|
{ |
|
errors: [ |
|
{ |
|
line: NUMBER, |
|
character: NUMBER, |
|
reason: STRING, |
|
evidence: STRING |
|
} |
|
], |
|
functions: [ |
|
name: STRING, |
|
line: NUMBER, |
|
last: NUMBER, |
|
param: [ |
|
STRING |
|
], |
|
closure: [ |
|
STRING |
|
], |
|
var: [ |
|
STRING |
|
], |
|
exception: [ |
|
STRING |
|
], |
|
outer: [ |
|
STRING |
|
], |
|
unused: [ |
|
STRING |
|
], |
|
global: [ |
|
STRING |
|
], |
|
label: [ |
|
STRING |
|
] |
|
], |
|
globals: [ |
|
STRING |
|
], |
|
member: { |
|
STRING: NUMBER |
|
}, |
|
unused: [ |
|
{ |
|
name: STRING, |
|
line: NUMBER |
|
} |
|
], |
|
implieds: [ |
|
{ |
|
name: STRING, |
|
line: NUMBER |
|
} |
|
], |
|
urls: [ |
|
STRING |
|
], |
|
json: BOOLEAN |
|
} |
|
|
|
Empty arrays will not be included. |
|
|
|
*/ |
|
|
|
/*jshint |
|
evil: true, nomen: false, onevar: false, regexp: false, strict: true, boss: true, |
|
undef: true, maxlen: 100, indent:4 |
|
*/ |
|
|
|
/*members "\b", "\t", "\n", "\f", "\r", "!=", "!==", "\"", "%", "(begin)", |
|
"(breakage)", "(context)", "(error)", "(global)", "(identifier)", "(last)", |
|
"(line)", "(loopage)", "(name)", "(onevar)", "(params)", "(scope)", |
|
"(statement)", "(verb)", "*", "+", "++", "-", "--", "\/", "<", "<=", "==", |
|
"===", ">", ">=", $, $$, $A, $F, $H, $R, $break, $continue, $w, Abstract, Ajax, |
|
__filename, __dirname, ActiveXObject, Array, ArrayBuffer, ArrayBufferView, Audio, |
|
Autocompleter, Assets, Boolean, Builder, Buffer, Browser, COM, CScript, Canvas, |
|
CustomAnimation, Class, Control, Chain, Color, Cookie, Core, DataView, Date, |
|
Debug, Draggable, Draggables, Droppables, Document, DomReady, DOMReady, DOMParser, Drag, |
|
E, Enumerator, Enumerable, Element, Elements, Error, Effect, EvalError, Event, |
|
Events, FadeAnimation, Field, Flash, Float32Array, Float64Array, Form, |
|
FormField, Frame, FormData, Function, Fx, GetObject, Group, Hash, HotKey, |
|
HTMLElement, HTMLAnchorElement, HTMLBaseElement, HTMLBlockquoteElement, |
|
HTMLBodyElement, HTMLBRElement, HTMLButtonElement, HTMLCanvasElement, HTMLDirectoryElement, |
|
HTMLDivElement, HTMLDListElement, HTMLFieldSetElement, |
|
HTMLFontElement, HTMLFormElement, HTMLFrameElement, HTMLFrameSetElement, |
|
HTMLHeadElement, HTMLHeadingElement, HTMLHRElement, HTMLHtmlElement, |
|
HTMLIFrameElement, HTMLImageElement, HTMLInputElement, HTMLIsIndexElement, |
|
HTMLLabelElement, HTMLLayerElement, HTMLLegendElement, HTMLLIElement, |
|
HTMLLinkElement, HTMLMapElement, HTMLMenuElement, HTMLMetaElement, |
|
HTMLModElement, HTMLObjectElement, HTMLOListElement, HTMLOptGroupElement, |
|
HTMLOptionElement, HTMLParagraphElement, HTMLParamElement, HTMLPreElement, |
|
HTMLQuoteElement, HTMLScriptElement, HTMLSelectElement, HTMLStyleElement, |
|
HtmlTable, HTMLTableCaptionElement, HTMLTableCellElement, HTMLTableColElement, |
|
HTMLTableElement, HTMLTableRowElement, HTMLTableSectionElement, |
|
HTMLTextAreaElement, HTMLTitleElement, HTMLUListElement, HTMLVideoElement, |
|
Iframe, IframeShim, Image, Int16Array, Int32Array, Int8Array, |
|
Insertion, InputValidator, JSON, Keyboard, Locale, LN10, LN2, LOG10E, LOG2E, |
|
MAX_VALUE, MIN_VALUE, Mask, Math, MenuItem, MessageChannel, MessageEvent, MessagePort, |
|
MoveAnimation, MooTools, Native, NEGATIVE_INFINITY, Number, Object, ObjectRange, Option, |
|
Options, OverText, PI, POSITIVE_INFINITY, PeriodicalExecuter, Point, Position, Prototype, |
|
RangeError, Rectangle, ReferenceError, RegExp, ResizeAnimation, Request, RotateAnimation, |
|
SQRT1_2, SQRT2, ScrollBar, ScriptEngine, ScriptEngineBuildVersion, |
|
ScriptEngineMajorVersion, ScriptEngineMinorVersion, Scriptaculous, Scroller, |
|
Slick, Slider, Selector, SharedWorker, String, Style, SyntaxError, Sortable, Sortables, |
|
SortableObserver, Sound, Spinner, System, Swiff, Text, TextArea, Template, |
|
Timer, Tips, Type, TypeError, Toggle, Try, "use strict", unescape, URI, URIError, URL, |
|
VBArray, WSH, WScript, XDomainRequest, Web, Window, XMLDOM, XMLHttpRequest, XMLSerializer, |
|
XPathEvaluator, XPathException, XPathExpression, XPathNamespace, XPathNSResolver, XPathResult, |
|
"\\", a, addEventListener, address, alert, apply, applicationCache, arguments, arity, asi, atob, |
|
b, basic, basicToken, bitwise, block, blur, boolOptions, boss, browser, btoa, c, call, callee, |
|
caller, cases, charAt, charCodeAt, character, clearInterval, clearTimeout, |
|
close, closed, closure, comment, condition, confirm, console, constructor, |
|
content, couch, create, css, curly, d, data, datalist, dd, debug, decodeURI, |
|
decodeURIComponent, defaultStatus, defineClass, deserialize, devel, document, |
|
dojo, dijit, dojox, define, else, emit, encodeURI, encodeURIComponent, |
|
entityify, eqeq, eqeqeq, eqnull, errors, es5, escape, esnext, eval, event, evidence, evil, |
|
ex, exception, exec, exps, expr, exports, FileReader, first, floor, focus, |
|
forin, fragment, frames, from, fromCharCode, fud, funcscope, funct, function, functions, |
|
g, gc, getComputedStyle, getRow, getter, getterToken, GLOBAL, global, globals, globalstrict, |
|
hasOwnProperty, help, history, i, id, identifier, immed, implieds, importPackage, include, |
|
indent, indexOf, init, ins, instanceOf, isAlpha, isApplicationRunning, isArray, |
|
isDigit, isFinite, isNaN, iterator, java, join, jshint, |
|
JSHINT, json, jquery, jQuery, keys, label, labelled, last, lastsemic, laxbreak, laxcomma, |
|
latedef, lbp, led, left, length, line, load, loadClass, localStorage, location, |
|
log, loopfunc, m, match, maxerr, maxlen, member,message, meta, module, moveBy, |
|
moveTo, mootools, multistr, name, navigator, new, newcap, noarg, node, noempty, nomen, |
|
nonew, nonstandard, nud, onbeforeunload, onblur, onerror, onevar, onecase, onfocus, |
|
onload, onresize, onunload, open, openDatabase, openURL, opener, opera, options, outer, param, |
|
parent, parseFloat, parseInt, passfail, plusplus, predef, print, process, prompt, |
|
proto, prototype, prototypejs, provides, push, quit, range, raw, reach, reason, regexp, |
|
readFile, readUrl, regexdash, removeEventListener, replace, report, require, |
|
reserved, resizeBy, resizeTo, resolvePath, resumeUpdates, respond, rhino, right, |
|
runCommand, scroll, screen, scripturl, scrollBy, scrollTo, scrollbar, search, seal, |
|
send, serialize, sessionStorage, setInterval, setTimeout, setter, setterToken, shift, slice, |
|
smarttabs, sort, spawn, split, stack, status, start, strict, sub, substr, supernew, shadow, |
|
supplant, sum, sync, test, toLowerCase, toString, toUpperCase, toint32, token, top, trailing, |
|
type, typeOf, Uint16Array, Uint32Array, Uint8Array, undef, undefs, unused, urls, validthis, |
|
value, valueOf, var, vars, version, WebSocket, withstmt, white, window, windows, Worker, wsh*/ |
|
|
|
/*global exports: false */ |
|
|
|
// We build the application inside a function so that we produce only a single |
|
// global variable. That function will be invoked immediately, and its return |
|
// value is the JSHINT function itself. |
|
|
|
var JSHINT = (function () { |
|
"use strict"; |
|
|
|
var anonname, // The guessed name for anonymous functions. |
|
|
|
// These are operators that should not be used with the ! operator. |
|
|
|
bang = { |
|
'<' : true, |
|
'<=' : true, |
|
'==' : true, |
|
'===': true, |
|
'!==': true, |
|
'!=' : true, |
|
'>' : true, |
|
'>=' : true, |
|
'+' : true, |
|
'-' : true, |
|
'*' : true, |
|
'/' : true, |
|
'%' : true |
|
}, |
|
|
|
// 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 |
|
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 |
|
es5 : true, // if ES5 syntax should be allowed |
|
esnext : true, // if es.next 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 |
|
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. |
|
latedef : true, // if the use before definition should not be tolerated |
|
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 |
|
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 |
|
nonew : true, // if using `new` for side-effects should be disallowed |
|
nonstandard : true, // if non-standard (but widely adopted) globals should |
|
// be predefined |
|
nomen : true, // if names should be checked |
|
onevar : true, // if only one var statement per function should be |
|
// allowed |
|
onecase : true, // if one case switch statements should be allowed |
|
passfail : true, // if the scan should stop on first error |
|
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 |
|
regexdash : true, // if unescaped first/last dash (-) inside brackets |
|
// should be tolerated |
|
regexp : true, // if the . should not be allowed in regexp literals |
|
rhino : true, // if the Rhino environment globals should be predefined |
|
undef : true, // if variables should be declared before used |
|
scripturl : true, // if script-targeted URLs should be tolerated |
|
shadow : true, // if variable shadowing should be tolerated |
|
smarttabs : true, // if smarttabs should be tolerated |
|
// (http://www.emacswiki.org/emacs/SmartTabs) |
|
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 |
|
trailing : true, // if trailing whitespace rules apply |
|
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 |
|
white : true, // if strict whitespace rules apply |
|
wsh : true // if the Windows Scripting Host environment globals |
|
// should be predefined |
|
}, |
|
|
|
// 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 |
|
}, |
|
|
|
// 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, |
|
nomen : true, |
|
plusplus : true, |
|
regexp : true, |
|
undef : true, |
|
white : true, |
|
|
|
// Inverted and renamed, use JSHint name here |
|
eqeqeq : true, |
|
onevar : 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", |
|
vars : "onevar", |
|
windows : "wsh" |
|
}, |
|
|
|
|
|
// browser contains a set of global names which are commonly provided by a |
|
// web browser environment. |
|
browser = { |
|
ArrayBuffer : false, |
|
ArrayBufferView : false, |
|
Audio : false, |
|
addEventListener : false, |
|
applicationCache : false, |
|
atob : false, |
|
blur : false, |
|
btoa : false, |
|
clearInterval : false, |
|
clearTimeout : false, |
|
close : false, |
|
closed : false, |
|
DataView : false, |
|
DOMParser : false, |
|
defaultStatus : false, |
|
document : false, |
|
event : false, |
|
FileReader : false, |
|
Float32Array : false, |
|
Float64Array : false, |
|
FormData : false, |
|
focus : false, |
|
frames : false, |
|
getComputedStyle : false, |
|
HTMLElement : false, |
|
HTMLAnchorElement : false, |
|
HTMLBaseElement : false, |
|
HTMLBlockquoteElement : false, |
|
HTMLBodyElement : false, |
|
HTMLBRElement : false, |
|
HTMLButtonElement : false, |
|
HTMLCanvasElement : false, |
|
HTMLDirectoryElement : false, |
|
HTMLDivElement : false, |
|
HTMLDListElement : false, |
|
HTMLFieldSetElement : false, |
|
HTMLFontElement : false, |
|
HTMLFormElement : false, |
|
HTMLFrameElement : false, |
|
HTMLFrameSetElement : false, |
|
HTMLHeadElement : false, |
|
HTMLHeadingElement : false, |
|
HTMLHRElement : false, |
|
HTMLHtmlElement : false, |
|
HTMLIFrameElement : false, |
|
HTMLImageElement : false, |
|
HTMLInputElement : false, |
|
HTMLIsIndexElement : false, |
|
HTMLLabelElement : false, |
|
HTMLLayerElement : false, |
|
HTMLLegendElement : false, |
|
HTMLLIElement : false, |
|
HTMLLinkElement : false, |
|
HTMLMapElement : false, |
|
HTMLMenuElement : false, |
|
HTMLMetaElement : false, |
|
HTMLModElement : false, |
|
HTMLObjectElement : false, |
|
HTMLOListElement : false, |
|
HTMLOptGroupElement : false, |
|
HTMLOptionElement : false, |
|
HTMLParagraphElement : false, |
|
HTMLParamElement : false, |
|
HTMLPreElement : false, |
|
HTMLQuoteElement : false, |
|
HTMLScriptElement : false, |
|
HTMLSelectElement : false, |
|
HTMLStyleElement : false, |
|
HTMLTableCaptionElement : false, |
|
HTMLTableCellElement : false, |
|
HTMLTableColElement : false, |
|
HTMLTableElement : false, |
|
HTMLTableRowElement : false, |
|
HTMLTableSectionElement : false, |
|
HTMLTextAreaElement : false, |
|
HTMLTitleElement : false, |
|
HTMLUListElement : false, |
|
HTMLVideoElement : false, |
|
history : false, |
|
Int16Array : false, |
|
Int32Array : false, |
|
Int8Array : false, |
|
Image : false, |
|
length : false, |
|
localStorage : false, |
|
location : false, |
|
MessageChannel : false, |
|
MessageEvent : false, |
|
MessagePort : false, |
|
moveBy : false, |
|
moveTo : false, |
|
name : false, |
|
navigator : false, |
|
onbeforeunload : true, |
|
onblur : true, |
|
onerror : true, |
|
onfocus : true, |
|
onload : true, |
|
onresize : true, |
|
onunload : true, |
|
open : false, |
|
openDatabase : false, |
|
opener : false, |
|
Option : false, |
|
parent : false, |
|
print : false, |
|
removeEventListener : false, |
|
resizeBy : false, |
|
resizeTo : false, |
|
screen : false, |
|
scroll : false, |
|
scrollBy : false, |
|
scrollTo : false, |
|
sessionStorage : false, |
|
setInterval : false, |
|
setTimeout : false, |
|
SharedWorker : false, |
|
status : false, |
|
top : false, |
|
Uint16Array : false, |
|
Uint32Array : false, |
|
Uint8Array : false, |
|
WebSocket : false, |
|
window : false, |
|
Worker : false, |
|
XMLHttpRequest : false, |
|
XMLSerializer : false, |
|
XPathEvaluator : false, |
|
XPathException : false, |
|
XPathExpression : false, |
|
XPathNamespace : false, |
|
XPathNSResolver : false, |
|
XPathResult : false |
|
}, |
|
|
|
couch = { |
|
"require" : false, |
|
respond : false, |
|
getRow : false, |
|
emit : false, |
|
send : false, |
|
start : false, |
|
sum : false, |
|
log : false, |
|
exports : false, |
|
module : false, |
|
provides : false |
|
}, |
|
|
|
devel = { |
|
alert : false, |
|
confirm : false, |
|
console : false, |
|
Debug : false, |
|
opera : false, |
|
prompt : false |
|
}, |
|
|
|
dojo = { |
|
dojo : false, |
|
dijit : false, |
|
dojox : false, |
|
define : false, |
|
"require" : false |
|
}, |
|
|
|
escapes = { |
|
'\b': '\\b', |
|
'\t': '\\t', |
|
'\n': '\\n', |
|
'\f': '\\f', |
|
'\r': '\\r', |
|
'"' : '\\"', |
|
'/' : '\\/', |
|
'\\': '\\\\' |
|
}, |
|
|
|
funct, // The current function |
|
|
|
functionicity = [ |
|
'closure', 'exception', 'global', 'label', |
|
'outer', 'unused', 'var' |
|
], |
|
|
|
functions, // All of the functions |
|
|
|
global, // The global scope |
|
implied, // Implied globals |
|
inblock, |
|
indent, |
|
jsonmode, |
|
|
|
jquery = { |
|
'$' : false, |
|
jQuery : false |
|
}, |
|
|
|
lines, |
|
lookahead, |
|
member, |
|
membersOnly, |
|
|
|
mootools = { |
|
'$' : false, |
|
'$$' : false, |
|
Assets : false, |
|
Browser : false, |
|
Chain : false, |
|
Class : false, |
|
Color : false, |
|
Cookie : false, |
|
Core : false, |
|
Document : false, |
|
DomReady : false, |
|
DOMReady : false, |
|
Drag : false, |
|
Element : false, |
|
Elements : false, |
|
Event : false, |
|
Events : false, |
|
Fx : false, |
|
Group : false, |
|
Hash : false, |
|
HtmlTable : false, |
|
Iframe : false, |
|
IframeShim : false, |
|
InputValidator : false, |
|
instanceOf : false, |
|
Keyboard : false, |
|
Locale : false, |
|
Mask : false, |
|
MooTools : false, |
|
Native : false, |
|
Options : false, |
|
OverText : false, |
|
Request : false, |
|
Scroller : false, |
|
Slick : false, |
|
Slider : false, |
|
Sortables : false, |
|
Spinner : false, |
|
Swiff : false, |
|
Tips : false, |
|
Type : false, |
|
typeOf : false, |
|
URI : false, |
|
Window : false |
|
}, |
|
|
|
nexttoken, |
|
|
|
node = { |
|
__filename : false, |
|
__dirname : false, |
|
Buffer : false, |
|
console : false, |
|
exports : false, |
|
GLOBAL : false, |
|
global : false, |
|
module : false, |
|
process : false, |
|
require : false, |
|
setTimeout : false, |
|
clearTimeout : false, |
|
setInterval : false, |
|
clearInterval : false |
|
}, |
|
|
|
noreach, |
|
option, |
|
predefined, // Global variables defined by option |
|
prereg, |
|
prevtoken, |
|
|
|
prototypejs = { |
|
'$' : false, |
|
'$$' : false, |
|
'$A' : false, |
|
'$F' : false, |
|
'$H' : false, |
|
'$R' : false, |
|
'$break' : false, |
|
'$continue' : false, |
|
'$w' : false, |
|
Abstract : false, |
|
Ajax : false, |
|
Class : false, |
|
Enumerable : false, |
|
Element : false, |
|
Event : false, |
|
Field : false, |
|
Form : false, |
|
Hash : false, |
|
Insertion : false, |
|
ObjectRange : false, |
|
PeriodicalExecuter: false, |
|
Position : false, |
|
Prototype : false, |
|
Selector : false, |
|
Template : false, |
|
Toggle : false, |
|
Try : false, |
|
Autocompleter : false, |
|
Builder : false, |
|
Control : false, |
|
Draggable : false, |
|
Draggables : false, |
|
Droppables : false, |
|
Effect : false, |
|
Sortable : false, |
|
SortableObserver : false, |
|
Sound : false, |
|
Scriptaculous : false |
|
}, |
|
|
|
rhino = { |
|
defineClass : false, |
|
deserialize : false, |
|
gc : false, |
|
help : false, |
|
importPackage: false, |
|
"java" : false, |
|
load : false, |
|
loadClass : false, |
|
print : false, |
|
quit : false, |
|
readFile : false, |
|
readUrl : false, |
|
runCommand : false, |
|
seal : false, |
|
serialize : false, |
|
spawn : false, |
|
sync : false, |
|
toint32 : false, |
|
version : false |
|
}, |
|
|
|
scope, // The current scope |
|
stack, |
|
|
|
// standard contains the global names that are provided by the |
|
// ECMAScript standard. |
|
standard = { |
|
Array : false, |
|
Boolean : false, |
|
Date : false, |
|
decodeURI : false, |
|
decodeURIComponent : false, |
|
encodeURI : false, |
|
encodeURIComponent : false, |
|
Error : false, |
|
'eval' : false, |
|
EvalError : false, |
|
Function : false, |
|
hasOwnProperty : false, |
|
isFinite : false, |
|
isNaN : false, |
|
JSON : false, |
|
Math : false, |
|
Number : false, |
|
Object : false, |
|
parseInt : false, |
|
parseFloat : false, |
|
RangeError : false, |
|
ReferenceError : false, |
|
RegExp : false, |
|
String : false, |
|
SyntaxError : false, |
|
TypeError : false, |
|
URIError : false |
|
}, |
|
|
|
// widely adopted global names that are not part of ECMAScript standard |
|
nonstandard = { |
|
escape : false, |
|
unescape : false |
|
}, |
|
|
|
standard_member = { |
|
E : true, |
|
LN2 : true, |
|
LN10 : true, |
|
LOG2E : true, |
|
LOG10E : true, |
|
MAX_VALUE : true, |
|
MIN_VALUE : true, |
|
NEGATIVE_INFINITY : true, |
|
PI : true, |
|
POSITIVE_INFINITY : true, |
|
SQRT1_2 : true, |
|
SQRT2 : true |
|
}, |
|
|
|
directive, |
|
syntax = {}, |
|
tab, |
|
token, |
|
urls, |
|
useESNextSyntax, |
|
warnings, |
|
|
|
wsh = { |
|
ActiveXObject : true, |
|
Enumerator : true, |
|
GetObject : true, |
|
ScriptEngine : true, |
|
ScriptEngineBuildVersion : true, |
|
ScriptEngineMajorVersion : true, |
|
ScriptEngineMinorVersion : true, |
|
VBArray : true, |
|
WSH : true, |
|
WScript : true, |
|
XDomainRequest : true |
|
}; |
|
|
|
// Regular expressions. Some of these are stupidly long. |
|
var ax, cx, tx, nx, nxg, lx, ix, jx, ft; |
|
(function () { |
|
/*jshint maxlen:300 */ |
|
|
|
// unsafe comment or string |
|
ax = /@cc|<\/?|script|\]\s*\]|<\s*!|</i; |
|
|
|
// unsafe characters that are silently deleted by one or more browsers |
|
cx = /[\u0000-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/; |
|
|
|
// token |
|
tx = /^\s*([(){}\[.,:;'"~\?\]#@]|==?=?|\/(\*(jshint|jslint|members?|global)?|=|\/)?|\*[\/=]?|\+(?:=|\++)?|-(?:=|-+)?|%=?|&[&=]?|\|[|=]?|>>?>?=?|<([\/=!]|\!(\[|--)?|<=?)?|\^=?|\!=?=?|[a-zA-Z_$][a-zA-Z0-9_$]*|[0-9]+([xX][0-9a-fA-F]+|\.[0-9]*)?([eE][+\-]?[0-9]+)?)/; |
|
|
|
// characters in strings that need escapement |
|
nx = /[\u0000-\u001f&<"\/\\\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/; |
|
nxg = /[\u0000-\u001f&<"\/\\\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; |
|
|
|
// star slash |
|
lx = /\*\/|\/\*/; |
|
|
|
// identifier |
|
ix = /^([a-zA-Z_$][a-zA-Z0-9_$]*)$/; |
|
|
|
// javascript url |
|
jx = /^(?:javascript|jscript|ecmascript|vbscript|mocha|livescript)\s*:/i; |
|
|
|
// catches /* falls through */ comments |
|
ft = /^\s*\/\*\s*falls\sthrough\s*\*\/\s*$/; |
|
}()); |
|
|
|
function F() {} // Used by Object.create |
|
|
|
function is_own(object, name) { |
|
|
|
// The object.hasOwnProperty method fails when the property under consideration |
|
// is named 'hasOwnProperty'. So we have to use this more convoluted form. |
|
|
|
return Object.prototype.hasOwnProperty.call(object, name); |
|
} |
|
|
|
function checkOption(name, t) { |
|
if (valOptions[name] === undefined && boolOptions[name] === undefined) { |
|
warning("Bad option: '" + name + "'.", t); |
|
} |
|
} |
|
|
|
// Provide critical ES5 functions to ES3. |
|
|
|
if (typeof Array.isArray !== 'function') { |
|
Array.isArray = function (o) { |
|
return Object.prototype.toString.apply(o) === '[object Array]'; |
|
}; |
|
} |
|
|
|
if (typeof Object.create !== 'function') { |
|
Object.create = function (o) { |
|
F.prototype = o; |
|
return new F(); |
|
}; |
|
} |
|
|
|
if (typeof Object.keys !== 'function') { |
|
Object.keys = function (o) { |
|
var a = [], k; |
|
for (k in o) { |
|
if (is_own(o, k)) { |
|
a.push(k); |
|
} |
|
} |
|
return a; |
|
}; |
|
} |
|
|
|
// Non standard methods |
|
|
|
if (typeof String.prototype.entityify !== 'function') { |
|
String.prototype.entityify = function () { |
|
return this |
|
.replace(/&/g, '&') |
|
.replace(/</g, '<') |
|
.replace(/>/g, '>'); |
|
}; |
|
} |
|
|
|
if (typeof String.prototype.isAlpha !== 'function') { |
|
String.prototype.isAlpha = function () { |
|
return (this >= 'a' && this <= 'z\uffff') || |
|
(this >= 'A' && this <= 'Z\uffff'); |
|
}; |
|
} |
|
|
|
if (typeof String.prototype.isDigit !== 'function') { |
|
String.prototype.isDigit = function () { |
|
return (this >= '0' && this <= '9'); |
|
}; |
|
} |
|
|
|
if (typeof String.prototype.supplant !== 'function') { |
|
String.prototype.supplant = function (o) { |
|
return this.replace(/\{([^{}]*)\}/g, function (a, b) { |
|
var r = o[b]; |
|
return typeof r === 'string' || typeof r === 'number' ? r : a; |
|
}); |
|
}; |
|
} |
|
|
|
if (typeof String.prototype.name !== 'function') { |
|
String.prototype.name = function () { |
|
|
|
// If the string looks like an identifier, then we can return it as is. |
|
// If the string contains no control characters, no quote characters, and no |
|
// backslash characters, then we can simply slap some quotes around it. |
|
// Otherwise we must also replace the offending characters with safe |
|
// sequences. |
|
|
|
if (ix.test(this)) { |
|
return this; |
|
} |
|
if (nx.test(this)) { |
|
return '"' + this.replace(nxg, function (a) { |
|
var c = escapes[a]; |
|
if (c) { |
|
return c; |
|
} |
|
return '\\u' + ('0000' + a.charCodeAt().toString(16)).slice(-4); |
|
}) + '"'; |
|
} |
|
return '"' + this + '"'; |
|
}; |
|
} |
|
|
|
|
|
function combine(t, o) { |
|
var n; |
|
for (n in o) { |
|
if (is_own(o, n)) { |
|
t[n] = o[n]; |
|
} |
|
} |
|
} |
|
|
|
function assume() { |
|
if (option.couch) { |
|
combine(predefined, couch); |
|
} |
|
|
|
if (option.rhino) { |
|
combine(predefined, rhino); |
|
} |
|
|
|
if (option.prototypejs) { |
|
combine(predefined, prototypejs); |
|
} |
|
|
|
if (option.node) { |
|
combine(predefined, node); |
|
option.globalstrict = true; |
|
} |
|
|
|
if (option.devel) { |
|
combine(predefined, devel); |
|
} |
|
|
|
if (option.dojo) { |
|
combine(predefined, dojo); |
|
} |
|
|
|
if (option.browser) { |
|
combine(predefined, browser); |
|
} |
|
|
|
if (option.nonstandard) { |
|
combine(predefined, nonstandard); |
|
} |
|
|
|
if (option.jquery) { |
|
combine(predefined, jquery); |
|
} |
|
|
|
if (option.mootools) { |
|
combine(predefined, mootools); |
|
} |
|
|
|
if (option.wsh) { |
|
combine(predefined, wsh); |
|
} |
|
|
|
if (option.esnext) { |
|
useESNextSyntax(); |
|
} |
|
|
|
if (option.globalstrict && option.strict !== false) { |
|
option.strict = true; |
|
} |
|
} |
|
|
|
|
|
// Produce an error warning. |
|
function quit(message, line, chr) { |
|
var percentage = Math.floor((line / lines.length) * 100); |
|
|
|
throw { |
|
name: 'JSHintError', |
|
line: line, |
|
character: chr, |
|
message: message + " (" + percentage + "% scanned).", |
|
raw: message |
|
}; |
|
} |
|
|
|
function isundef(scope, m, t, a) { |
|
return JSHINT.undefs.push([scope, m, t, a]); |
|
} |
|
|
|
function warning(m, t, a, b, c, d) { |
|
var ch, l, w; |
|
t = t || nexttoken; |
|
if (t.id === '(end)') { // `~ |
|
t = token; |
|
} |
|
l = t.line || 0; |
|
ch = t.from || 0; |
|
w = { |
|
id: '(error)', |
|
raw: m, |
|
evidence: lines[l - 1] || '', |
|
line: l, |
|
character: ch, |
|
a: a, |
|
b: b, |
|
c: c, |
|
d: d |
|
}; |
|
w.reason = m.supplant(w); |
|
JSHINT.errors.push(w); |
|
if (option.passfail) { |
|
quit('Stopping. ', l, ch); |
|
} |
|
warnings += 1; |
|
if (warnings >= option.maxerr) { |
|
quit("Too many errors.", l, ch); |
|
} |
|
return w; |
|
} |
|
|
|
function warningAt(m, l, ch, a, b, c, d) { |
|
return warning(m, { |
|
line: l, |
|
from: ch |
|
}, a, b, c, d); |
|
} |
|
|
|
function error(m, t, a, b, c, d) { |
|
var w = warning(m, t, a, b, c, d); |
|
} |
|
|
|
function errorAt(m, l, ch, a, b, c, d) { |
|
return error(m, { |
|
line: l, |
|
from: ch |
|
}, a, b, c, d); |
|
} |
|
|
|
|
|
|
|
// lexical analysis and token construction |
|
|
|
var lex = (function lex() { |
|
var character, from, line, s; |
|
|
|
// Private lex methods |
|
|
|
function nextLine() { |
|
var at, |
|
tw; // trailing whitespace check |
|
|
|
if (line >= lines.length) |
|
return false; |
|
|
|
character = 1; |
|
s = lines[line]; |
|
line += 1; |
|
|
|
// If smarttabs option is used check for spaces followed by tabs only. |
|
// Otherwise check for any occurence of mixed tabs and spaces. |
|
if (option.smarttabs) |
|
at = s.search(/ \t/); |
|
else |
|
at = s.search(/ \t|\t /); |
|
|
|
if (at >= 0) |
|
warningAt("Mixed spaces and tabs.", line, at + 1); |
|
|
|
s = s.replace(/\t/g, tab); |
|
at = s.search(cx); |
|
|
|
if (at >= 0) |
|
warningAt("Unsafe character.", line, at); |
|
|
|
if (option.maxlen && option.maxlen < s.length) |
|
warningAt("Line too long.", line, s.length); |
|
|
|
// Check for trailing whitespaces |
|
tw = option.trailing && s.match(/^(.*?)\s+$/); |
|
if (tw && !/^\s+$/.test(s)) { |
|
warningAt("Trailing whitespace.", line, tw[1].length + 1); |
|
} |
|
return true; |
|
} |
|
|
|
// Produce a token object. The token inherits from a syntax symbol. |
|
|
|
function it(type, value) { |
|
var i, t; |
|
if (type === '(color)' || type === '(range)') { |
|
t = {type: type}; |
|
} else if (type === '(punctuator)' || |
|
(type === '(identifier)' && is_own(syntax, value))) { |
|
t = syntax[value] || syntax['(error)']; |
|
} else { |
|
t = syntax[type]; |
|
} |
|
t = Object.create(t); |
|
if (type === '(string)' || type === '(range)') { |
|
if (!option.scripturl && jx.test(value)) { |
|
warningAt("Script URL.", line, from); |
|
} |
|
} |
|
if (type === '(identifier)') { |
|
t.identifier = true; |
|
if (value === '__proto__' && !option.proto) { |
|
warningAt("The '{a}' property is deprecated.", |
|
line, from, value); |
|
} else if (value === '__iterator__' && !option.iterator) { |
|
warningAt("'{a}' is only available in JavaScript 1.7.", |
|
line, from, value); |
|
} else if (option.nomen && (value.charAt(0) === '_' || |
|
value.charAt(value.length - 1) === '_')) { |
|
if (!option.node || token.id === '.' || |
|
(value !== '__dirname' && value !== '__filename')) { |
|
warningAt("Unexpected {a} in '{b}'.", line, from, "dangling '_'", value); |
|
} |
|
} |
|
} |
|
t.value = value; |
|
t.line = line; |
|
t.character = character; |
|
t.from = from; |
|
i = t.id; |
|
if (i !== '(endline)') { |
|
prereg = i && |
|
(('(,=:[!&|?{};'.indexOf(i.charAt(i.length - 1)) >= 0) || |
|
i === 'return' || |
|
i === 'case'); |
|
} |
|
return t; |
|
} |
|
|
|
// Public lex methods |
|
return { |
|
init: function (source) { |
|
if (typeof source === 'string') { |
|
lines = source |
|
.replace(/\r\n/g, '\n') |
|
.replace(/\r/g, '\n') |
|
.split('\n'); |
|
} else { |
|
lines = source; |
|
} |
|
|
|
// If the first line is a shebang (#!), make it a blank and move on. |
|
// Shebangs are used by Node scripts. |
|
if (lines[0] && lines[0].substr(0, 2) === '#!') |
|
lines[0] = ''; |
|
|
|
line = 0; |
|
nextLine(); |
|
from = 1; |
|
}, |
|
|
|
range: function (begin, end) { |
|
var c, value = ''; |
|
from = character; |
|
if (s.charAt(0) !== begin) { |
|
errorAt("Expected '{a}' and instead saw '{b}'.", |
|
line, character, begin, s.charAt(0)); |
|
} |
|
for (;;) { |
|
s = s.slice(1); |
|
character += 1; |
|
c = s.charAt(0); |
|
switch (c) { |
|
case '': |
|
errorAt("Missing '{a}'.", line, character, c); |
|
break; |
|
case end: |
|
s = s.slice(1); |
|
character += 1; |
|
return it('(range)', value); |
|
case '\\': |
|
warningAt("Unexpected '{a}'.", line, character, c); |
|
} |
|
value += c; |
|
} |
|
|
|
}, |
|
|
|
|
|
// token -- this is called by advance to get the next token |
|
token: function () { |
|
var b, c, captures, d, depth, high, i, l, low, q, t, isLiteral, isInRange, n; |
|
|
|
function match(x) { |
|
var r = x.exec(s), r1; |
|
if (r) { |
|
l = r[0].length; |
|
r1 = r[1]; |
|
c = r1.charAt(0); |
|
s = s.substr(l); |
|
from = character + l - r1.length; |
|
character += l; |
|
return r1; |
|
} |
|
} |
|
|
|
function string(x) { |
|
var c, j, r = '', allowNewLine = false; |
|
|
|
if (jsonmode && x !== '"') { |
|
warningAt("Strings must use doublequote.", |
|
line, character); |
|
} |
|
|
|
function esc(n) { |
|
var i = parseInt(s.substr(j + 1, n), 16); |
|
j += n; |
|
if (i >= 32 && i <= 126 && |
|
i !== 34 && i !== 92 && i !== 39) { |
|
warningAt("Unnecessary escapement.", line, character); |
|
} |
|
character += n; |
|
c = String.fromCharCode(i); |
|
} |
|
j = 0; |
|
unclosedString: for (;;) { |
|
while (j >= s.length) { |
|
j = 0; |
|
|
|
var cl = line, cf = from; |
|
if (!nextLine()) { |
|
errorAt("Unclosed string.", cl, cf); |
|
break unclosedString; |
|
} |
|
|
|
if (allowNewLine) { |
|
allowNewLine = false; |
|
} else { |
|
warningAt("Unclosed string.", cl, cf); |
|
} |
|
} |
|
c = s.charAt(j); |
|
if (c === x) { |
|
character += 1; |
|
s = s.substr(j + 1); |
|
return it('(string)', r, x); |
|
} |
|
if (c < ' ') { |
|
if (c === '\n' || c === '\r') { |
|
break; |
|
} |
|
warningAt("Control character in string: {a}.", |
|
line, character + j, s.slice(0, j)); |
|
} else if (c === '\\') { |
|
j += 1; |
|
character += 1; |
|
c = s.charAt(j); |
|
n = s.charAt(j + 1); |
|
switch (c) { |
|
case '\\': |
|
case '"': |
|
case '/': |
|
break; |
|
case '\'': |
|
if (jsonmode) { |
|
warningAt("Avoid \\'.", line, character); |
|
} |
|
break; |
|
case 'b': |
|
c = '\b'; |
|
break; |
|
case 'f': |
|
c = '\f'; |
|
break; |
|
case 'n': |
|
c = '\n'; |
|
break; |
|
case 'r': |
|
c = '\r'; |
|
break; |
|
case 't': |
|
c = '\t'; |
|
break; |
|
case '0': |
|
c = '\0'; |
|
// Octal literals fail in strict mode |
|
// check if the number is between 00 and 07 |
|
// where 'n' is the token next to 'c' |
|
if (n >= 0 && n <= 7 && directive["use strict"]) { |
|
warningAt( |
|
"Octal literals are not allowed in strict mode.", |
|
line, character); |
|
} |
|
break; |
|
case 'u': |
|
esc(4); |
|
break; |
|
case 'v': |
|
if (jsonmode) { |
|
warningAt("Avoid \\v.", line, character); |
|
} |
|
c = '\v'; |
|
break; |
|
case 'x': |
|
if (jsonmode) { |
|
warningAt("Avoid \\x-.", line, character); |
|
} |
|
esc(2); |
|
break; |
|
case '': |
|
// last character is escape character |
|
// always allow new line if escaped, but show |
|
// warning if option is not set |
|
allowNewLine = true; |
|
if (option.multistr) { |
|
if (jsonmode) { |
|
warningAt("Avoid EOL escapement.", line, character); |
|
} |
|
c = ''; |
|
character -= 1; |
|
break; |
|
} |
|
warningAt("Bad escapement of EOL. Use option multistr if needed.", |
|
line, character); |
|
break; |
|
default: |
|
warningAt("Bad escapement.", line, character); |
|
} |
|
} |
|
r += c; |
|
character += 1; |
|
j += 1; |
|
} |
|
} |
|
|
|
for (;;) { |
|
if (!s) { |
|
return it(nextLine() ? '(endline)' : '(end)', ''); |
|
} |
|
t = match(tx); |
|
if (!t) { |
|
t = ''; |
|
c = ''; |
|
while (s && s < '!') { |
|
s = s.substr(1); |
|
} |
|
if (s) { |
|
errorAt("Unexpected '{a}'.", line, character, s.substr(0, 1)); |
|
s = ''; |
|
} |
|
} else { |
|
|
|
// identifier |
|
|
|
if (c.isAlpha() || c === '_' || c === '$') { |
|
return it('(identifier)', t); |
|
} |
|
|
|
// number |
|
|
|
if (c.isDigit()) { |
|
if (!isFinite(Number(t))) { |
|
warningAt("Bad number '{a}'.", |
|
line, character, t); |
|
} |
|
if (s.substr(0, 1).isAlpha()) { |
|
warningAt("Missing space after '{a}'.", |
|
line, character, t); |
|
} |
|
if (c === '0') { |
|
d = t.substr(1, 1); |
|
if (d.isDigit()) { |
|
if (token.id !== '.') { |
|
warningAt("Don't use extra leading zeros '{a}'.", |
|
line, character, t); |
|
} |
|
} else if (jsonmode && (d === 'x' || d === 'X')) { |
|
warningAt("Avoid 0x-. '{a}'.", |
|
line, character, t); |
|
} |
|
} |
|
if (t.substr(t.length - 1) === '.') { |
|
warningAt( |
|
"A trailing decimal point can be confused with a dot '{a}'.", line, character, t); |
|
} |
|
return it('(number)', t); |
|
} |
|
switch (t) { |
|
|
|
// string |
|
|
|
case '"': |
|
case "'": |
|
return string(t); |
|
|
|
// // comment |
|
|
|
case '//': |
|
s = ''; |
|
token.comment = true; |
|
break; |
|
|
|
// /* comment |
|
|
|
case '/*': |
|
for (;;) { |
|
i = s.search(lx); |
|
if (i >= 0) { |
|
break; |
|
} |
|
if (!nextLine()) { |
|
errorAt("Unclosed comment.", line, character); |
|
} |
|
} |
|
character += i + 2; |
|
if (s.substr(i, 1) === '/') { |
|
errorAt("Nested comment.", line, character); |
|
} |
|
s = s.substr(i + 2); |
|
token.comment = true; |
|
break; |
|
|
|
// /*members /*jshint /*global |
|
|
|
case '/*members': |
|
case '/*member': |
|
case '/*jshint': |
|
case '/*jslint': |
|
case '/*global': |
|
case '*/': |
|
return { |
|
value: t, |
|
type: 'special', |
|
line: line, |
|
character: character, |
|
from: from |
|
}; |
|
|
|
case '': |
|
break; |
|
// / |
|
case '/': |
|
if (token.id === '/=') { |
|
errorAt("A regular expression literal can be confused with '/='.", |
|
line, from); |
|
} |
|
if (prereg) { |
|
depth = 0; |
|
captures = 0; |
|
l = 0; |
|
for (;;) { |
|
b = true; |
|
c = s.charAt(l); |
|
l += 1; |
|
switch (c) { |
|
case '': |
|
errorAt("Unclosed regular expression.", line, from); |
|
return quit('Stopping.', line, from); |
|
case '/': |
|
if (depth > 0) { |
|
warningAt("{a} unterminated regular expression " + |
|
"group(s).", line, from + l, depth); |
|
} |
|
c = s.substr(0, l - 1); |
|
q = { |
|
g: true, |
|
i: true, |
|
m: true |
|
}; |
|
while (q[s.charAt(l)] === true) { |
|
q[s.charAt(l)] = false; |
|
l += 1; |
|
} |
|
character += l; |
|
s = s.substr(l); |
|
q = s.charAt(0); |
|
if (q === '/' || q === '*') { |
|
errorAt("Confusing regular expression.", |
|
line, from); |
|
} |
|
return it('(regexp)', c); |
|
case '\\': |
|
c = s.charAt(l); |
|
if (c < ' ') { |
|
warningAt( |
|
"Unexpected control character in regular expression.", line, from + l); |
|
} else if (c === '<') { |
|
warningAt( |
|
"Unexpected escaped character '{a}' in regular expression.", line, from + l, c); |
|
} |
|
l += 1; |
|
break; |
|
case '(': |
|
depth += 1; |
|
b = false; |
|
if (s.charAt(l) === '?') { |
|
l += 1; |
|
switch (s.charAt(l)) { |
|
case ':': |
|
case '=': |
|
case '!': |
|
l += 1; |
|
break; |
|
default: |
|
warningAt( |
|
"Expected '{a}' and instead saw '{b}'.", line, from + l, ':', s.charAt(l)); |
|
} |
|
} else { |
|
captures += 1; |
|
} |
|
break; |
|
case '|': |
|
b = false; |
|
break; |
|
case ')': |
|
if (depth === 0) { |
|
warningAt("Unescaped '{a}'.", |
|
line, from + l, ')'); |
|
} else { |
|
depth -= 1; |
|
} |
|
break; |
|
case ' ': |
|
q = 1; |
|
while (s.charAt(l) === ' ') { |
|
l += 1; |
|
q += 1; |
|
} |
|
if (q > 1) { |
|
warningAt( |
|
"Spaces are hard to count. Use {{a}}.", line, from + l, q); |
|
} |
|
break; |
|
case '[': |
|
c = s.charAt(l); |
|
if (c === '^') { |
|
l += 1; |
|
if (option.regexp) { |
|
warningAt("Insecure '{a}'.", |
|
line, from + l, c); |
|
} else if (s.charAt(l) === ']') { |
|
errorAt("Unescaped '{a}'.", |
|
line, from + l, '^'); |
|
} |
|
} |
|
if (c === ']') { |
|
warningAt("Empty class.", line, |
|
from + l - 1); |
|
} |
|
isLiteral = false; |
|
isInRange = false; |
|
klass: do { |
|
c = s.charAt(l); |
|
l += 1; |
|
switch (c) { |
|
case '[': |
|
case '^': |
|
warningAt("Unescaped '{a}'.", |
|
line, from + l, c); |
|
if (isInRange) { |
|
isInRange = false; |
|
} else { |
|
isLiteral = true; |
|
} |
|
break; |
|
case '-': |
|
if (isLiteral && !isInRange) { |
|
isLiteral = false; |
|
isInRange = true; |
|
} else if (isInRange) { |
|
isInRange = false; |
|
} else if (s.charAt(l) === ']') { |
|
isInRange = true; |
|
} else { |
|
if (option.regexdash !== (l === 2 || (l === 3 && |
|
s.charAt(1) === '^'))) { |
|
warningAt("Unescaped '{a}'.", |
|
line, from + l - 1, '-'); |
|
} |
|
isLiteral = true; |
|
} |
|
break; |
|
case ']': |
|
if (isInRange && !option.regexdash) { |
|
warningAt("Unescaped '{a}'.", |
|
line, from + l - 1, '-'); |
|
} |
|
break klass; |
|
case '\\': |
|
c = s.charAt(l); |
|
if (c < ' ') { |
|
warningAt( |
|
"Unexpected control character in regular expression.", line, from + l); |
|
} else if (c === '<') { |
|
warningAt( |
|
"Unexpected escaped character '{a}' in regular expression.", line, from + l, c); |
|
} |
|
l += 1; |
|
|
|
// \w, \s and \d are never part of a character range |
|
if (/[wsd]/i.test(c)) { |
|
if (isInRange) { |
|
warningAt("Unescaped '{a}'.", |
|
line, from + l, '-'); |
|
isInRange = false; |
|
} |
|
isLiteral = false; |
|
} else if (isInRange) { |
|
isInRange = false; |
|
} else { |
|
isLiteral = true; |
|
} |
|
break; |
|
case '/': |
|
warningAt("Unescaped '{a}'.", |
|
line, from + l - 1, '/'); |
|
|
|
if (isInRange) { |
|
isInRange = false; |
|
} else { |
|
isLiteral = true; |
|
} |
|
break; |
|
case '<': |
|
if (isInRange) { |
|
isInRange = false; |
|
} else { |
|
isLiteral = true; |
|
} |
|
break; |
|
default: |
|
if (isInRange) { |
|
isInRange = false; |
|
} else { |
|
isLiteral = true; |
|
} |
|
} |
|
} while (c); |
|
break; |
|
case '.': |
|
if (option.regexp) { |
|
warningAt("Insecure '{a}'.", line, |
|
from + l, c); |
|
} |
|
break; |
|
case ']': |
|
case '?': |
|
case '{': |
|
case '}': |
|
case '+': |
|
case '*': |
|
warningAt("Unescaped '{a}'.", line, |
|
from + l, c); |
|
} |
|
if (b) { |
|
switch (s.charAt(l)) { |
|
case '?': |
|
case '+': |
|
case '*': |
|
l += 1; |
|
if (s.charAt(l) === '?') { |
|
l += 1; |
|
} |
|
break; |
|
case '{': |
|
l += 1; |
|
c = s.charAt(l); |
|
if (c < '0' || c > '9') { |
|
warningAt( |
|
"Expected a number and instead saw '{a}'.", line, from + l, c); |
|
} |
|
l += 1; |
|
low = +c; |
|
for (;;) { |
|
c = s.charAt(l); |
|
if (c < '0' || c > '9') { |
|
break; |
|
} |
|
l += 1; |
|
low = +c + (low * 10); |
|
} |
|
high = low; |
|
if (c === ',') { |
|
l += 1; |
|
high = Infinity; |
|
c = s.charAt(l); |
|
if (c >= '0' && c <= '9') { |
|
l += 1; |
|
high = +c; |
|
for (;;) { |
|
c = s.charAt(l); |
|
if (c < '0' || c > '9') { |
|
break; |
|
} |
|
l += 1; |
|
high = +c + (high * 10); |
|
} |
|
} |
|
} |
|
if (s.charAt(l) !== '}') { |
|
warningAt( |
|
"Expected '{a}' and instead saw '{b}'.", line, from + l, '}', c); |
|
} else { |
|
l += 1; |
|
} |
|
if (s.charAt(l) === '?') { |
|
l += 1; |
|
} |
|
if (low > high) { |
|
warningAt( |
|
"'{a}' should not be greater than '{b}'.", line, from + l, low, high); |
|
} |
|
} |
|
} |
|
} |
|
c = s.substr(0, l - 1); |
|
character += l; |
|
s = s.substr(l); |
|
return it('(regexp)', c); |
|
} |
|
return it('(punctuator)', t); |
|
|
|
// punctuator |
|
|
|
case '#': |
|
return it('(punctuator)', t); |
|
default: |
|
return it('(punctuator)', t); |
|
} |
|
} |
|
} |
|
} |
|
}; |
|
}()); |
|
|
|
|
|
function addlabel(t, type) { |
|
|
|
if (t === 'hasOwnProperty') { |
|
warning("'hasOwnProperty' is a really bad name."); |
|
} |
|
|
|
// Define t in the current function in the current scope. |
|
if (is_own(funct, t) && !funct['(global)']) { |
|
if (funct[t] === true) { |
|
if (option.latedef) |
|
warning("'{a}' was used before it was defined.", nexttoken, t); |
|
} else { |
|
if (!option.shadow && type !== "exception") |
|
warning("'{a}' is already defined.", nexttoken, t); |
|
} |
|
} |
|
|
|
funct[t] = type; |
|
if (funct['(global)']) { |
|
global[t] = funct; |
|
if (is_own(implied, t)) { |
|
if (option.latedef) |
|
warning("'{a}' was used before it was defined.", nexttoken, t); |
|
delete implied[t]; |
|
} |
|
} else { |
|
scope[t] = funct; |
|
} |
|
} |
|
|
|
|
|
function doOption() { |
|
var b, obj, filter, o = nexttoken.value, t, tn, v; |
|
|
|
switch (o) { |
|
case '*/': |
|
error("Unbegun comment."); |
|
break; |
|
case '/*members': |
|
case '/*member': |
|
o = '/*members'; |
|
if (!membersOnly) { |
|
membersOnly = {}; |
|
} |
|
obj = membersOnly; |
|
break; |
|
case '/*jshint': |
|
case '/*jslint': |
|
obj = option; |
|
filter = boolOptions; |
|
break; |
|
case '/*global': |
|
obj = predefined; |
|
break; |
|
default: |
|
error("What?"); |
|
} |
|
|
|
t = lex.token(); |
|
loop: for (;;) { |
|
for (;;) { |
|
if (t.type === 'special' && t.value === '*/') { |
|
break loop; |
|
} |
|
if (t.id !== '(endline)' && t.id !== ',') { |
|
break; |
|
} |
|
t = lex.token(); |
|
} |
|
if (t.type !== '(string)' && t.type !== '(identifier)' && |
|
o !== '/*members') { |
|
error("Bad option.", t); |
|
} |
|
|
|
v = lex.token(); |
|
if (v.id === ':') { |
|
v = lex.token(); |
|
|
|
if (obj === membersOnly) { |
|
error("Expected '{a}' and instead saw '{b}'.", |
|
t, '*/', ':'); |
|
} |
|
|
|
if (o === '/*jshint') { |
|
checkOption(t.value, t); |
|
} |
|
|
|
if (t.value === 'indent' && (o === '/*jshint' || o === '/*jslint')) { |
|
b = +v.value; |
|
if (typeof b !== 'number' || !isFinite(b) || b <= 0 || |
|
Math.floor(b) !== b) { |
|
error("Expected a small integer and instead saw '{a}'.", |
|
v, v.value); |
|
} |
|
obj.white = true; |
|
obj.indent = b; |
|
} else if (t.value === 'maxerr' && (o === '/*jshint' || o === '/*jslint')) { |
|
b = +v.value; |
|
if (typeof b !== 'number' || !isFinite(b) || b <= 0 || |
|
Math.floor(b) !== b) { |
|
error("Expected a small integer and instead saw '{a}'.", |
|
v, v.value); |
|
} |
|
obj.maxerr = b; |
|
} else if (t.value === 'maxlen' && (o === '/*jshint' || o === '/*jslint')) { |
|
b = +v.value; |
|
if (typeof b !== 'number' || !isFinite(b) || b <= 0 || |
|
Math.floor(b) !== b) { |
|
error("Expected a small integer and instead saw '{a}'.", |
|
v, v.value); |
|
} |
|
obj.maxlen = b; |
|
} else if (t.value === 'validthis') { |
|
if (funct['(global)']) { |
|
error("Option 'validthis' can't be used in a global scope."); |
|
} else { |
|
if (v.value === 'true' || v.value === 'false') |
|
obj[t.value] = v.value === 'true'; |
|
else |
|
error("Bad option value.", v); |
|
} |
|
} else if (v.value === 'true' || v.value === 'false') { |
|
if (o === '/*jslint') { |
|
tn = renamedOptions[t.value] || t.value; |
|
obj[tn] = v.value === 'true'; |
|
if (invertedOptions[tn] !== undefined) { |
|
obj[tn] = !obj[tn]; |
|
} |
|
} else { |
|
obj[t.value] = v.value === 'true'; |
|
} |
|
} else { |
|
error("Bad option value.", v); |
|
} |
|
t = lex.token(); |
|
} else { |
|
if (o === '/*jshint' || o === '/*jslint') { |
|
error("Missing option value.", t); |
|
} |
|
obj[t.value] = false; |
|
t = v; |
|
} |
|
} |
|
if (filter) { |
|
assume(); |
|
} |
|
} |
|
|
|
|
|
// We need a peek function. If it has an argument, it peeks that much farther |
|
// ahead. It is used to distinguish |
|
// for ( var i in ... |
|
// from |
|
// for ( var i = ... |
|
|
|
function peek(p) { |
|
var i = p || 0, j = 0, t; |
|
|
|
while (j <= i) { |
|
t = lookahead[j]; |
|
if (!t) { |
|
t = lookahead[j] = lex.token(); |
|
} |
|
j += 1; |
|
} |
|
return t; |
|
} |
|
|
|
|
|
|
|
// Produce the next token. It looks for programming errors. |
|
|
|
function advance(id, t) { |
|
switch (token.id) { |
|
case '(number)': |
|
if (nexttoken.id === '.') { |
|
warning("A dot following a number can be confused with a decimal point.", token); |
|
} |
|
break; |
|
case '-': |
|
if (nexttoken.id === '-' || nexttoken.id === '--') { |
|
warning("Confusing minusses."); |
|
} |
|
break; |
|
case '+': |
|
if (nexttoken.id === '+' || nexttoken.id === '++') { |
|
warning("Confusing plusses."); |
|
} |
|
break; |
|
} |
|
|
|
if (token.type === '(string)' || token.identifier) { |
|
anonname = token.value; |
|
} |
|
|
|
if (id && nexttoken.id !== id) { |
|
if (t) { |
|
if (nexttoken.id === '(end)') { |
|
warning("Unmatched '{a}'.", t, t.id); |
|
} else { |
|
warning("Expected '{a}' to match '{b}' from line {c} and instead saw '{d}'.", |
|
nexttoken, id, t.id, t.line, nexttoken.value); |
|
} |
|
} else if (nexttoken.type !== '(identifier)' || |
|
nexttoken.value !== id) { |
|
warning("Expected '{a}' and instead saw '{b}'.", |
|
nexttoken, id, nexttoken.value); |
|
} |
|
} |
|
|
|
prevtoken = token; |
|
token = nexttoken; |
|
for (;;) { |
|
nexttoken = lookahead.shift() || lex.token(); |
|
if (nexttoken.id === '(end)' || nexttoken.id === '(error)') { |
|
return; |
|
} |
|
if (nexttoken.type === 'special') { |
|
doOption(); |
|
} else { |
|
if (nexttoken.id !== '(endline)') { |
|
break; |
|
} |
|
} |
|
} |
|
} |
|
|
|
|
|
// This is the heart of JSHINT, the Pratt parser. In addition to parsing, it |
|
// is looking for ad hoc lint patterns. We add .fud to Pratt's model, which is |
|
// like .nud except that it is only used on the first token of a statement. |
|
// Having .fud makes it much easier to define statement-oriented languages like |
|
// JavaScript. I retained Pratt's nomenclature. |
|
|
|
// .nud Null denotation |
|
// .fud First null denotation |
|
// .led Left denotation |
|
// lbp Left binding power |
|
// rbp Right binding power |
|
|
|
// They are elements of the parsing method called Top Down Operator Precedence. |
|
|
|
function expression(rbp, initial) { |
|
var left, isArray = false, isObject = false; |
|
|
|
if (nexttoken.id === '(end)') |
|
error("Unexpected early end of program.", token); |
|
|
|
advance(); |
|
if (initial) { |
|
anonname = 'anonymous'; |
|
funct['(verb)'] = token.value; |
|
} |
|
if (initial === true && token.fud) { |
|
left = token.fud(); |
|
} else { |
|
if (token.nud) { |
|
left = token.nud(); |
|
} else { |
|
if (nexttoken.type === '(number)' && token.id === '.') { |
|
warning("A leading decimal point can be confused with a dot: '.{a}'.", |
|
token, nexttoken.value); |
|
advance(); |
|
return token; |
|
} else { |
|
error("Expected an identifier and instead saw '{a}'.", |
|
token, token.id); |
|
} |
|
} |
|
while (rbp < nexttoken.lbp) { |
|
isArray = token.value === 'Array'; |
|
isObject = token.value === 'Object'; |
|
|
|
// #527, new Foo.Array(), Foo.Array(), new Foo.Object(), Foo.Object() |
|
// Line breaks in IfStatement heads exist to satisfy the checkJSHint |
|
// "Line too long." error. |
|
if (left && (left.value || (left.first && left.first.value))) { |
|
// If the left.value is not "new", or the left.first.value is a "." |
|
// then safely assume that this is not "new Array()" and possibly |
|
// not "new Object()"... |
|
if (left.value !== 'new' || |
|
(left.first && left.first.value && left.first.value === '.')) { |
|
isArray = false; |
|
// ...In the case of Object, if the left.value and token.value |
|
// are not equal, then safely assume that this not "new Object()" |
|
if (left.value !== token.value) { |
|
isObject = false; |
|
} |
|
} |
|
} |
|
|
|
advance(); |
|
if (isArray && token.id === '(' && nexttoken.id === ')') |
|
warning("Use the array literal notation [].", token); |
|
if (isObject && token.id === '(' && nexttoken.id === ')') |
|
warning("Use the object literal notation {}.", token); |
|
if (token.led) { |
|
left = token.led(left); |
|
} else { |
|
error("Expected an operator and instead saw '{a}'.", |
|
token, token.id); |
|
} |
|
} |
|
} |
|
return left; |
|
} |
|
|
|
|
|
// Functions for conformance of style. |
|
|
|
function adjacent(left, right) { |
|
left = left || token; |
|
right = right || nexttoken; |
|
if (option.white) { |
|
if (left.character !== right.from && left.line === right.line) { |
|
left.from += (left.character - left.from); |
|
warning("Unexpected space after '{a}'.", left, left.value); |
|
} |
|
} |
|
} |
|
|
|
function nobreak(left, right) { |
|
left = left || token; |
|
right = right || nexttoken; |
|
if (option.white && (left.character !== right.from || left.line !== right.line)) { |
|
warning("Unexpected space before '{a}'.", right, right.value); |
|
} |
|
} |
|
|
|
function nospace(left, right) { |
|
left = left || token; |
|
right = right || nexttoken; |
|
if (option.white && !left.comment) { |
|
if (left.line === right.line) { |
|
adjacent(left, right); |
|
} |
|
} |
|
} |
|
|
|
function nonadjacent(left, right) { |
|
if (option.white) { |
|
left = left || token; |
|
right = right || nexttoken; |
|
if (left.line === right.line && left.character === right.from) { |
|
left.from += (left.character - left.from); |
|
warning("Missing space after '{a}'.", |
|
left, left.value); |
|
} |
|
} |
|
} |
|
|
|
function nobreaknonadjacent(left, right) { |
|
left = left || token; |
|
right = right || nexttoken; |
|
if (!option.laxbreak && left.line !== right.line) { |
|
warning("Bad line breaking before '{a}'.", right, right.id); |
|
} else if (option.white) { |
|
left = left || token; |
|
right = right || nexttoken; |
|
if (left.character === right.from) { |
|
left.from += (left.character - left.from); |
|
warning("Missing space after '{a}'.", |
|
left, left.value); |
|
} |
|
} |
|
} |
|
|
|
function indentation(bias) { |
|
var i; |
|
if (option.white && nexttoken.id !== '(end)') { |
|
i = indent + (bias || 0); |
|
if (nexttoken.from !== i) { |
|
warning( |
|
"Expected '{a}' to have an indentation at {b} instead at {c}.", |
|
nexttoken, nexttoken.value, i, nexttoken.from); |
|
} |
|
} |
|
} |
|
|
|
function nolinebreak(t) { |
|
t = t || token; |
|
if (t.line !== nexttoken.line) { |
|
warning("Line breaking error '{a}'.", t, t.value); |
|
} |
|
} |
|
|
|
|
|
function comma() { |
|
if (token.line !== nexttoken.line) { |
|
if (!option.laxcomma) { |
|
if (comma.first) { |
|
warning("Comma warnings can be turned off with 'laxcomma'"); |
|
comma.first = false; |
|
} |
|
warning("Bad line breaking before '{a}'.", token, nexttoken.id); |
|
} |
|
} else if (!token.comment && token.character !== nexttoken.from && option.white) { |
|
token.from += (token.character - token.from); |
|
warning("Unexpected space after '{a}'.", token, token.value); |
|
} |
|
advance(','); |
|
nonadjacent(token, nexttoken); |
|
} |
|
|
|
|
|
// Functional constructors for making the symbols that will be inherited by |
|
// tokens. |
|
|
|
function symbol(s, p) { |
|
var x = syntax[s]; |
|
if (!x || typeof x !== 'object') { |
|
syntax[s] = x = { |
|
id: s, |
|
lbp: p, |
|
value: s |
|
}; |
|
} |
|
return x; |
|
} |
|
|
|
|
|
function delim(s) { |
|
return symbol(s, 0); |
|
} |
|
|
|
|
|
function stmt(s, f) { |
|
var x = delim(s); |
|
x.identifier = x.reserved = true; |
|
x.fud = f; |
|
return x; |
|
} |
|
|
|
|
|
function blockstmt(s, f) { |
|
var x = stmt(s, f); |
|
x.block = true; |
|
return x; |
|
} |
|
|
|
|
|
function reserveName(x) { |
|
var c = x.id.charAt(0); |
|
if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) { |
|
x.identifier = x.reserved = true; |
|
} |
|
return x; |
|
} |
|
|
|
|
|
function prefix(s, f) { |
|
var x = symbol(s, 150); |
|
reserveName(x); |
|
x.nud = (typeof f === 'function') ? f : function () { |
|
this.right = expression(150); |
|
this.arity = 'unary'; |
|
if (this.id === '++' || this.id === '--') { |
|
if (option.plusplus) { |
|
warning("Unexpected use of '{a}'.", this, this.id); |
|
} else if ((!this.right.identifier || this.right.reserved) && |
|
this.right.id !== '.' && this.right.id !== '[') { |
|
warning("Bad operand.", this); |
|
} |
|
} |
|
return this; |
|
}; |
|
return x; |
|
} |
|
|
|
|
|
function type(s, f) { |
|
var x = delim(s); |
|
x.type = s; |
|
x.nud = f; |
|
return x; |
|
} |
|
|
|
|
|
function reserve(s, f) { |
|
var x = type(s, f); |
|
x.identifier = x.reserved = true; |
|
return x; |
|
} |
|
|
|
|
|
function reservevar(s, v) { |
|
return reserve(s, function () { |
|
if (typeof v === 'function') { |
|
v(this); |
|
} |
|
return this; |
|
}); |
|
} |
|
|
|
|
|
function infix(s, f, p, w) { |
|
var x = symbol(s, p); |
|
reserveName(x); |
|
x.led = function (left) { |
|
if (!w) { |
|
nobreaknonadjacent(prevtoken, token); |
|
nonadjacent(token, nexttoken); |
|
} |
|
if (s === "in" && left.id === "!") { |
|
warning("Confusing use of '{a}'.", left, '!'); |
|
} |
|
if (typeof f === 'function') { |
|
return f(left, this); |
|
} else { |
|
this.left = left; |
|
this.right = expression(p); |
|
return this; |
|
} |
|
}; |
|
return x; |
|
} |
|
|
|
|
|
function relation(s, f) { |
|
var x = symbol(s, 100); |
|
x.led = function (left) { |
|
nobreaknonadjacent(prevtoken, token); |
|
nonadjacent(token, nexttoken); |
|
var right = expression(100); |
|
if ((left && left.id === 'NaN') || (right && right.id === 'NaN')) { |
|
warning("Use the isNaN function to compare with NaN.", this); |
|
} else if (f) { |
|
f.apply(this, [left, right]); |
|
} |
|
if (left.id === '!') { |
|
warning("Confusing use of '{a}'.", left, '!'); |
|
} |
|
if (right.id === '!') { |
|
warning("Confusing use of '{a}'.", right, '!'); |
|
} |
|
this.left = left; |
|
this.right = right; |
|
return this; |
|
}; |
|
return x; |
|
} |
|
|
|
|
|
function isPoorRelation(node) { |
|
return node && |
|
((node.type === '(number)' && +node.value === 0) || |
|
(node.type === '(string)' && node.value === '') || |
|
(node.type === 'null' && !option.eqnull) || |
|
node.type === 'true' || |
|
node.type === 'false' || |
|
node.type === 'undefined'); |
|
} |
|
|
|
|
|
function assignop(s, f) { |
|
symbol(s, 20).exps = true; |
|
return infix(s, function (left, that) { |
|
var l; |
|
that.left = left; |
|
if (predefined[left.value] === false && |
|
scope[left.value]['(global)'] === true) { |
|
warning("Read only.", left); |
|
} else if (left['function']) { |
|
warning("'{a}' is a function.", left, left.value); |
|
} |
|
if (left) { |
|
if (option.esnext && funct[left.value] === 'const') { |
|
warning("Attempting to override '{a}' which is a constant", left, left.value); |
|
} |
|
if (left.id === '.' || left.id === '[') { |
|
if (!left.left || left.left.value === 'arguments') { |
|
warning('Bad assignment.', that); |
|
} |
|
that.right = expression(19); |
|
return that; |
|
} else if (left.identifier && !left.reserved) { |
|
if (funct[left.value] === 'exception') { |
|
warning("Do not assign to the exception parameter.", left); |
|
} |
|
that.right = expression(19); |
|
return that; |
|
} |
|
if (left === syntax['function']) { |
|
warning( |
|
"Expected an identifier in an assignment and instead saw a function invocation.", |
|
token); |
|
} |
|
} |
|
error("Bad assignment.", that); |
|
}, 20); |
|
} |
|
|
|
|
|
function bitwise(s, f, p) { |
|
var x = symbol(s, p); |
|
reserveName(x); |
|
x.led = (typeof f === 'function') ? f : function (left) { |
|
if (option.bitwise) { |
|
warning("Unexpected use of '{a}'.", this, this.id); |
|
} |
|
this.left = left; |
|
this.right = expression(p); |
|
return this; |
|
}; |
|
return x; |
|
} |
|
|
|
|
|
function bitwiseassignop(s) { |
|
symbol(s, 20).exps = true; |
|
return infix(s, function (left, that) { |
|
if (option.bitwise) { |
|
warning("Unexpected use of '{a}'.", that, that.id); |
|
} |
|
nonadjacent(prevtoken, token); |
|
nonadjacent(token, nexttoken); |
|
if (left) { |
|
if (left.id === '.' || left.id === '[' || |
|
(left.identifier && !left.reserved)) { |
|
expression(19); |
|
return that; |
|
} |
|
if (left === syntax['function']) { |
|
warning( |
|
"Expected an identifier in an assignment, and instead saw a function invocation.", |
|
token); |
|
} |
|
return that; |
|
} |
|
error("Bad assignment.", that); |
|
}, 20); |
|
} |
|
|
|
|
|
function suffix(s, f) { |
|
var x = symbol(s, 150); |
|
x.led = function (left) { |
|
if (option.plusplus) { |
|
warning("Unexpected use of '{a}'.", this, this.id); |
|
} else if ((!left.identifier || left.reserved) && |
|
left.id !== '.' && left.id !== '[') { |
|
warning("Bad operand.", this); |
|
} |
|
this.left = left; |
|
return this; |
|
}; |
|
return x; |
|
} |
|
|
|
|
|
// fnparam means that this identifier is being defined as a function |
|
// argument (see identifier()) |
|
function optionalidentifier(fnparam) { |
|
if (nexttoken.identifier) { |
|
advance(); |
|
if (token.reserved && !option.es5) { |
|
// `undefined` as a function param is a common pattern to protect |
|
// against the case when somebody does `undefined = true` and |
|
// help with minification. More info: https://gist.github.com/315916 |
|
if (!fnparam || token.value !== 'undefined') { |
|
warning("Expected an identifier and instead saw '{a}' (a reserved word).", |
|
token, token.id); |
|
} |
|
} |
|
return token.value; |
|
} |
|
} |
|
|
|
// fnparam means that this identifier is being defined as a function |
|
// argument |
|
function identifier(fnparam) { |
|
var i = optionalidentifier(fnparam); |
|
if (i) { |
|
return i; |
|
} |
|
if (token.id === 'function' && nexttoken.id === '(') { |
|
warning("Missing name in function declaration."); |
|
} else { |
|
error("Expected an identifier and instead saw '{a}'.", |
|
nexttoken, nexttoken.value); |
|
} |
|
} |
|
|
|
|
|
function reachable(s) { |
|
var i = 0, t; |
|
if (nexttoken.id !== ';' || noreach) { |
|
return; |
|
} |
|
for (;;) { |
|
t = peek(i); |
|
if (t.reach) { |
|
return; |
|
} |
|
if (t.id !== '(endline)') { |
|
if (t.id === 'function') { |
|
if (!option.latedef) { |
|
break; |
|
} |
|
warning( |
|
"Inner functions should be listed at the top of the outer function.", t); |
|
break; |
|
} |
|
warning("Unreachable '{a}' after '{b}'.", t, t.value, s); |
|
break; |
|
} |
|
i += 1; |
|
} |
|
} |
|
|
|
|
|
function statement(noindent) { |
|
var i = indent, r, s = scope, t = nexttoken; |
|
|
|
if (t.id === ";") { |
|
advance(";"); |
|
return; |
|
} |
|
|
|
// Is this a labelled statement? |
|
|
|
if (t.identifier && !t.reserved && peek().id === ':') { |
|
advance(); |
|
advance(':'); |
|
scope = Object.create(s); |
|
addlabel(t.value, 'label'); |
|
if (!nexttoken.labelled) { |
|
warning("Label '{a}' on {b} statement.", |
|
nexttoken, t.value, nexttoken.value); |
|
} |
|
if (jx.test(t.value + ':')) { |
|
warning("Label '{a}' looks like a javascript url.", |
|
t, t.value); |
|
} |
|
nexttoken.label = t.value; |
|
t = nexttoken; |
|
} |
|
|
|
// Parse the statement. |
|
|
|
if (!noindent) { |
|
indentation(); |
|
} |
|
r = expression(0, true); |
|
|
|
// Look for the final semicolon. |
|
if (!t.block) { |
|
if (!option.expr && (!r || !r.exps)) { |
|
warning("Expected an assignment or function call and instead saw an expression.", |
|
token); |
|
} else if (option.nonew && r.id === '(' && r.left.id === 'new') { |
|
warning("Do not use 'new' for side effects."); |
|
} |
|
|
|
if (nexttoken.id === ',') { |
|
return comma(); |
|
} |
|
|
|
if (nexttoken.id !== ';') { |
|
if (!option.asi) { |
|
// If this is the last statement in a block that ends on |
|
// the same line *and* option lastsemic is on, ignore the warning. |
|
// Otherwise, complain about missing semicolon. |
|
if (!option.lastsemic || nexttoken.id !== '}' || |
|
nexttoken.line !== token.line) { |
|
warningAt("Missing semicolon.", token.line, token.character); |
|
} |
|
} |
|
} else { |
|
adjacent(token, nexttoken); |
|
advance(';'); |
|
nonadjacent(token, nexttoken); |
|
} |
|
} |
|
|
|
// Restore the indentation. |
|
|
|
indent = i; |
|
scope = s; |
|
return r; |
|
} |
|
|
|
|
|
function statements(startLine) { |
|
var a = [], f, p; |
|
|
|
while (!nexttoken.reach && nexttoken.id !== '(end)') { |
|
if (nexttoken.id === ';') { |
|
p = peek(); |
|
if (!p || p.id !== "(") { |
|
warning("Unnecessary semicolon."); |
|
} |
|
advance(';'); |
|
} else { |
|
a.push(statement(startLine === nexttoken.line)); |
|
} |
|
} |
|
return a; |
|
} |
|
|
|
|
|
/* |
|
* read all directives |
|
* recognizes a simple form of asi, but always |
|
* warns, if it is used |
|
*/ |
|
function directives() { |
|
var i, p, pn; |
|
|
|
for (;;) { |
|
if (nexttoken.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("Missing semicolon.", nexttoken); |
|
} else { |
|
p = pn; |
|
} |
|
} else if (p.id === "}") { |
|
// directive with no other statements, warn about missing semicolon |
|
warning("Missing semicolon.", p); |
|
} else if (p.id !== ";") { |
|
break; |
|
} |
|
|
|
indentation(); |
|
advance(); |
|
if (directive[token.value]) { |
|
warning("Unnecessary directive \"{a}\".", token, token.value); |
|
} |
|
|
|
if (token.value === "use strict") { |
|
option.newcap = true; |
|
option.undef = true; |
|
} |
|
|
|
// there's no directive negation, so always set to true |
|
directive[token.value] = true; |
|
|
|
if (p.id === ";") { |
|
advance(";"); |
|
} |
|
continue; |
|
} |
|
break; |
|
} |
|
} |
|
|
|
|
|
/* |
|
* Parses a single block. A block is a sequence of statements wrapped in |
|
* braces. |
|
* |
|
* ordinary - true for everything but function bodies and try blocks. |
|
* stmt - true if block can be a single statement (e.g. in if/for/while). |
|
* isfunc - true if block is a function body |
|
*/ |
|
function block(ordinary, stmt, isfunc) { |
|
var a, |
|
b = inblock, |
|
old_indent = indent, |
|
m, |
|
s = scope, |
|
t, |
|
line, |
|
d; |
|
|
|
inblock = ordinary; |
|
if (!ordinary || !option.funcscope) scope = Object.create(scope); |
|
nonadjacent(token, nexttoken); |
|
t = nexttoken; |
|
|
|
if (nexttoken.id === '{') { |
|
advance('{'); |
|
line = token.line; |
|
if (nexttoken.id !== '}') { |
|
indent += option.indent; |
|
while (!ordinary && nexttoken.from > indent) { |
|
indent += option.indent; |
|
} |
|
|
|
if (isfunc) { |
|
m = {}; |
|
for (d in directive) { |
|
if (is_own(directive, d)) { |
|
m[d] = directive[d]; |
|
} |
|
} |
|
directives(); |
|
|
|
if (option.strict && funct['(context)']['(global)']) { |
|
if (!m["use strict"] && !directive["use strict"]) { |
|
warning("Missing \"use strict\" statement."); |
|
} |
|
} |
|
} |
|
|
|
a = statements(line); |
|
|
|
if (isfunc) { |
|
directive = m; |
|
} |
|
|
|
indent -= option.indent; |
|
if (line !== nexttoken.line) { |
|
indentation(); |
|
} |
|
} else if (line !== nexttoken.line) { |
|
indentation(); |
|
} |
|
advance('}', t); |
|
indent = old_indent; |
|
} else if (!ordinary) { |
|
error("Expected '{a}' and instead saw '{b}'.", |
|
nexttoken, '{', nexttoken.value); |
|
} else { |
|
if (!stmt || option.curly) |
|
warning("Expected '{a}' and instead saw '{b}'.", |
|
nexttoken, '{', nexttoken.value); |
|
|
|
noreach = true; |
|
indent += option.indent; |
|
// test indentation only if statement is in new line |
|
a = [statement(nexttoken.line === token.line)]; |
|
indent -= option.indent; |
|
noreach = false; |
|
} |
|
funct['(verb)'] = null; |
|
if (!ordinary || !option.funcscope) scope = s; |
|
inblock = b; |
|
if (ordinary && option.noempty && (!a || a.length === 0)) { |
|
warning("Empty block."); |
|
} |
|
return a; |
|
} |
|
|
|
|
|
function countMember(m) { |
|
if (membersOnly && typeof membersOnly[m] !== 'boolean') { |
|
warning("Unexpected /*member '{a}'.", token, m); |
|
} |
|
if (typeof member[m] === 'number') { |
|
member[m] += 1; |
|
} else { |
|
member[m] = 1; |
|
} |
|
} |
|
|
|
|
|
function note_implied(token) { |
|
var name = token.value, line = token.line, a = implied[name]; |
|
if (typeof a === 'function') { |
|
a = false; |
|
} |
|
|
|
if (!a) { |
|
a = [line]; |
|
implied[name] = a; |
|
} else if (a[a.length - 1] !== line) { |
|
a.push(line); |
|
} |
|
} |
|
|
|
|
|
// Build the syntax table by declaring the syntactic elements of the language. |
|
|
|
type('(number)', function () { |
|
return this; |
|
}); |
|
|
|
type('(string)', function () { |
|
return this; |
|
}); |
|
|
|
syntax['(identifier)'] = { |
|
type: '(identifier)', |
|
lbp: 0, |
|
identifier: true, |
|
nud: function () { |
|
var v = this.value, |
|
s = scope[v], |
|
f; |
|
|
|
if (typeof s === 'function') { |
|
// Protection against accidental inheritance. |
|
s = undefined; |
|
} else if (typeof s === 'boolean') { |
|
f = funct; |
|
funct = functions[0]; |
|
addlabel(v, 'var'); |
|
s = funct; |
|
funct = f; |
|
} |
|
|
|
// The name is in scope and defined in the current function. |
|
if (funct === s) { |
|
// Change 'unused' to 'var', and reject labels. |
|
switch (funct[v]) { |
|
case 'unused': |
|
funct[v] = 'var'; |
|
break; |
|
case 'unction': |
|
funct[v] = 'function'; |
|
this['function'] = true; |
|
break; |
|
case 'function': |
|
this['function'] = true; |
|
break; |
|
case 'label': |
|
warning("'{a}' is a statement label.", token, 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 (option.undef && 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') || |
|
(nexttoken && (nexttoken.value === '.' || nexttoken.value === '['))) { |
|
|
|
isundef(funct, "'{a}' is not defined.", token, v); |
|
} |
|
} |
|
note_implied(token); |
|
} else { |
|
// If the name is already defined in the current |
|
// function, but not as outer, then there is a scope error. |
|
|
|
switch (funct[v]) { |
|
case 'closure': |
|
case 'function': |
|
case 'var': |
|
case 'unused': |
|
warning("'{a}' used out of scope.", token, v); |
|
break; |
|
case 'label': |
|
warning("'{a}' is a statement label.", token, v); |
|
break; |
|
case 'outer': |
|
case 'global': |
|
break; |
|
default: |
|
// If the name is defined in an outer function, make an outer entry, |
|
// and if it was unused, make it var. |
|
if (s === true) { |
|
funct[v] = true; |
|
} else if (s === null) { |
|
warning("'{a}' is not allowed.", token, v); |
|
note_implied(token); |
|
} 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. |
|
if (option.undef) { |
|
// Attempting to subscript a null reference will throw an |
|
// error, even within the typeof and delete operators |
|
if (!(anonname === 'typeof' || anonname === 'delete') || |
|
(nexttoken && |
|
(nexttoken.value === '.' || nexttoken.value === '['))) { |
|
|
|
isundef(funct, "'{a}' is not defined.", token, v); |
|
} |
|
} |
|
funct[v] = true; |
|
note_implied(token); |
|
} else { |
|
switch (s[v]) { |
|
case 'function': |
|
case 'unction': |
|
this['function'] = true; |
|
s[v] = 'closure'; |
|
funct[v] = s['(global)'] ? 'global' : 'outer'; |
|
break; |
|
case 'var': |
|
case 'unused': |
|
s[v] = 'closure'; |
|
funct[v] = s['(global)'] ? 'global' : 'outer'; |
|
break; |
|
case 'closure': |
|
case 'parameter': |
|
funct[v] = s['(global)'] ? 'global' : 'outer'; |
|
break; |
|
case 'label': |
|
warning("'{a}' is a statement label.", token, v); |
|
} |
|
} |
|
} |
|
} |
|
return this; |
|
}, |
|
led: function () { |
|
error("Expected an operator and instead saw '{a}'.", |
|
nexttoken, nexttoken.value); |
|
} |
|
}; |
|
|
|
type('(regexp)', function () { |
|
return this; |
|
}); |
|
|
|
|
|
// ECMAScript parser |
|
|
|
delim('(endline)'); |
|
delim('(begin)'); |
|
delim('(end)').reach = true; |
|
delim('</').reach = true; |
|
delim('<!'); |
|
delim('<!--'); |
|
delim('-->'); |
|
delim('(error)').reach = true; |
|
delim('}').reach = true; |
|
delim(')'); |
|
delim(']'); |
|
delim('"').reach = true; |
|
delim("'").reach = true; |
|
delim(';'); |
|
delim(':').reach = true; |
|
delim(','); |
|
delim('#'); |
|
delim('@'); |
|
reserve('else'); |
|
reserve('case').reach = true; |
|
reserve('catch'); |
|
reserve('default').reach = true; |
|
reserve('finally'); |
|
reservevar('arguments', function (x) { |
|
if (directive['use strict'] && funct['(global)']) { |
|
warning("Strict violation.", x); |
|
} |
|
}); |
|
reservevar('eval'); |
|
reservevar('false'); |
|
reservevar('Infinity'); |
|
reservevar('NaN'); |
|
reservevar('null'); |
|
reservevar('this', function (x) { |
|
if (directive['use strict'] && !option.validthis && ((funct['(statement)'] && |
|
funct['(name)'].charAt(0) > 'Z') || funct['(global)'])) { |
|
warning("Possible strict violation.", x); |
|
} |
|
}); |
|
reservevar('true'); |
|
reservevar('undefined'); |
|
assignop('=', 'assign', 20); |
|
assignop('+=', 'assignadd', 20); |
|
assignop('-=', 'assignsub', 20); |
|
assignop('*=', 'assignmult', 20); |
|
assignop('/=', 'assigndiv', 20).nud = function () { |
|
error("A regular expression literal can be confused with '/='."); |
|
}; |
|
assignop('%=', 'assignmod', 20); |
|
bitwiseassignop('&=', 'assignbitand', 20); |
|
bitwiseassignop('|=', 'assignbitor', 20); |
|
bitwiseassignop('^=', 'assignbitxor', 20); |
|
bitwiseassignop('<<=', 'assignshiftleft', 20); |
|
bitwiseassignop('>>=', 'assignshiftright', 20); |
|
bitwiseassignop('>>>=', 'assignshiftrightunsigned', 20); |
|
infix('?', function (left, that) { |
|
that.left = left; |
|
that.right = expression(10); |
|
advance(':'); |
|
that['else'] = expression(10); |
|
return that; |
|
}, 30); |
|
|
|
infix('||', 'or', 40); |
|
infix('&&', 'and', 50); |
|
bitwise('|', 'bitor', 70); |
|
bitwise('^', 'bitxor', 80); |
|
bitwise('&', 'bitand', 90); |
|
relation('==', function (left, right) { |
|
var eqnull = option.eqnull && (left.value === 'null' || right.value === 'null'); |
|
|
|
if (!eqnull && option.eqeqeq) |
|
warning("Expected '{a}' and instead saw '{b}'.", this, '===', '=='); |
|
else if (isPoorRelation(left)) |
|
warning("Use '{a}' to compare with '{b}'.", this, '===', left.value); |
|
else if (isPoorRelation(right)) |
|
warning("Use '{a}' to compare with '{b}'.", this, '===', right.value); |
|
|
|
return this; |
|
}); |
|
relation('==='); |
|
relation('!=', function (left, right) { |
|
var eqnull = option.eqnull && |
|
(left.value === 'null' || right.value === 'null'); |
|
|
|
if (!eqnull && option.eqeqeq) { |
|
warning("Expected '{a}' and instead saw '{b}'.", |
|
this, '!==', '!='); |
|
} else if (isPoorRelation(left)) { |
|
warning("Use '{a}' to compare with '{b}'.", |
|
this, '!==', left.value); |
|
} else if (isPoorRelation(right)) { |
|
warning("Use '{a}' to compare with '{b}'.", |
|
this, '!==', right.value); |
|
} |
|
return this; |
|
}); |
|
relation('!=='); |
|
relation('<'); |
|
relation('>'); |
|
relation('<='); |
|
relation('>='); |
|
bitwise('<<', 'shiftleft', 120); |
|
bitwise('>>', 'shiftright', 120); |
|
bitwise('>>>', 'shiftrightunsigned', 120); |
|
infix('in', 'in', 120); |
|
infix('instanceof', 'instanceof', 120); |
|
infix('+', function (left, that) { |
|
var right = expression(130); |
|
if (left && right && left.id === '(string)' && right.id === '(string)') { |
|
left.value += right.value; |
|
left.character = right.character; |
|
if (!option.scripturl && jx.test(left.value)) { |
|
warning("JavaScript URL.", left); |
|
} |
|
return left; |
|
} |
|
that.left = left; |
|
that.right = right; |
|
return that; |
|
}, 130); |
|
prefix('+', 'num'); |
|
prefix('+++', function () { |
|
warning("Confusing pluses."); |
|
this.right = expression(150); |
|
this.arity = 'unary'; |
|
return this; |
|
}); |
|
infix('+++', function (left) { |
|
warning("Confusing pluses."); |
|
this.left = left; |
|
this.right = expression(130); |
|
return this; |
|
}, 130); |
|
infix('-', 'sub', 130); |
|
prefix('-', 'neg'); |
|
prefix('---', function () { |
|
warning("Confusing minuses."); |
|
this.right = expression(150); |
|
this.arity = 'unary'; |
|
return this; |
|
}); |
|
infix('---', function (left) { |
|
warning("Confusing minuses."); |
|
this.left = left; |
|
this.right = expression(130); |
|
return this; |
|
}, 130); |
|
infix('*', 'mult', 140); |
|
infix('/', 'div', 140); |
|
infix('%', 'mod', 140); |
|
|
|
suffix('++', 'postinc'); |
|
prefix('++', 'preinc'); |
|
syntax['++'].exps = true; |
|
|
|
suffix('--', 'postdec'); |
|
prefix('--', 'predec'); |
|
syntax['--'].exps = true; |
|
prefix('delete', function () { |
|
var p = expression(0); |
|
if (!p || (p.id !== '.' && p.id !== '[')) { |
|
warning("Variables should not be deleted."); |
|
} |
|
this.first = p; |
|
return this; |
|
}).exps = true; |
|
|
|
prefix('~', function () { |
|
if (option.bitwise) { |
|
warning("Unexpected '{a}'.", this, '~'); |
|
} |
|
expression(150); |
|
return this; |
|
}); |
|
|
|
prefix('!', function () { |
|
this.right = expression(150); |
|
this.arity = 'unary'; |
|
if (bang[this.right.id] === true) { |
|
warning("Confusing use of '{a}'.", this, '!'); |
|
} |
|
return this; |
|
}); |
|
prefix('typeof', 'typeof'); |
|
prefix('new', function () { |
|
var c = expression(155), i; |
|
if (c && c.id !== 'function') { |
|
if (c.identifier) { |
|
c['new'] = true; |
|
switch (c.value) { |
|
case 'Number': |
|
case 'String': |
|
case 'Boolean': |
|
case 'Math': |
|
case 'JSON': |
|
warning("Do not use {a} as a constructor.", token, c.value); |
|
break; |
|
case 'Function': |
|
if (!option.evil) { |
|
warning("The Function constructor is eval."); |
|
} |
|
break; |
|
case 'Date': |
|
case 'RegExp': |
|
break; |
|
default: |
|
if (c.id !== 'function') { |
|
i = c.value.substr(0, 1); |
|
if (option.newcap && (i < 'A' || i > 'Z')) { |
|
warning("A constructor name should start with an uppercase letter.", |
|
token); |
|
} |
|
} |
|
} |
|
} else { |
|
if (c.id !== '.' && c.id !== '[' && c.id !== '(') { |
|
warning("Bad constructor.", token); |
|
} |
|
} |
|
} else { |
|
if (!option.supernew) |
|
warning("Weird construction. Delete 'new'.", this); |
|
} |
|
adjacent(token, nexttoken); |
|
if (nexttoken.id !== '(' && !option.supernew) { |
|
warning("Missing '()' invoking a constructor."); |
|
} |
|
this.first = c; |
|
return this; |
|
}); |
|
syntax['new'].exps = true; |
|
|
|
prefix('void').exps = true; |
|
|
|
infix('.', function (left, that) { |
|
adjacent(prevtoken, token); |
|
nobreak(); |
|
var m = identifier(); |
|
if (typeof m === 'string') { |
|
countMember(m); |
|
} |
|
that.left = left; |
|
that.right = m; |
|
if (left && left.value === 'arguments' && (m === 'callee' || m === 'caller')) { |
|
if (option.noarg) |
|
warning("Avoid arguments.{a}.", left, m); |
|
else if (directive['use strict']) |
|
error('Strict violation.'); |
|
} else if (!option.evil && left && left.value === 'document' && |
|
(m === 'write' || m === 'writeln')) { |
|
warning("document.write can be a form of eval.", left); |
|
} |
|
if (!option.evil && (m === 'eval' || m === 'execScript')) { |
|
warning('eval is evil.'); |
|
} |
|
return that; |
|
}, 160, true); |
|
|
|
infix('(', function (left, that) { |
|
if (prevtoken.id !== '}' && prevtoken.id !== ')') { |
|
nobreak(prevtoken, token); |
|
} |
|
nospace(); |
|
if (option.immed && !left.immed && left.id === 'function') { |
|
warning("Wrap an immediate function invocation in parentheses " + |
|
"to assist the reader in understanding that the expression " + |
|
"is the result of a function, and not the function itself."); |
|
} |
|
var n = 0, |
|
p = []; |
|
if (left) { |
|
if (left.type === '(identifier)') { |
|
if (left.value.match(/^[A-Z]([A-Z0-9_$]*[a-z][A-Za-z0-9_$]*)?$/)) { |
|
if (left.value !== 'Number' && left.value !== 'String' && |
|
left.value !== 'Boolean' && |
|
left.value !== 'Date') { |
|
if (left.value === 'Math') { |
|
warning("Math is not a function.", left); |
|
} else if (option.newcap) { |
|
warning( |
|
"Missing 'new' prefix when invoking a constructor.", left); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
if (nexttoken.id !== ')') { |
|
for (;;) { |
|
p[p.length] = expression(10); |
|
n += 1; |
|
if (nexttoken.id !== ',') { |
|
break; |
|
} |
|
comma(); |
|
} |
|
} |
|
advance(')'); |
|
nospace(prevtoken, token); |
|
if (typeof left === 'object') { |
|
if (left.value === 'parseInt' && n === 1) { |
|
warning("Missing radix parameter.", left); |
|
} |
|
if (!option.evil) { |
|
if (left.value === 'eval' || left.value === 'Function' || |
|
left.value === 'execScript') { |
|
warning("eval is evil.", left); |
|
} else if (p[0] && p[0].id === '(string)' && |
|
(left.value === 'setTimeout' || |
|
left.value === 'setInterval')) { |
|
warning( |
|
"Implied eval is evil. Pass a function instead of a string.", left); |
|
} |
|
} |
|
if (!left.identifier && left.id !== '.' && left.id !== '[' && |
|
left.id !== '(' && left.id !== '&&' && left.id !== '||' && |
|
left.id !== '?') { |
|
warning("Bad invocation.", left); |
|
} |
|
} |
|
that.left = left; |
|
return that; |
|
}, 155, true).exps = true; |
|
|
|
prefix('(', function () { |
|
nospace(); |
|
if (nexttoken.id === 'function') { |
|
nexttoken.immed = true; |
|
} |
|
var v = expression(0); |
|
advance(')', this); |
|
nospace(prevtoken, token); |
|
if (option.immed && v.id === 'function') { |
|
if (nexttoken.id === '(' || |
|
(nexttoken.id === '.' && (peek().value === 'call' || peek().value === 'apply'))) { |
|
warning( |
|
"Move the invocation into the parens that contain the function.", nexttoken); |
|
} else { |
|
warning( |
|
"Do not wrap function literals in parens unless they are to be immediately invoked.", |
|
this); |
|
} |
|
} |
|
return v; |
|
}); |
|
|
|
infix('[', function (left, that) { |
|
nobreak(prevtoken, token); |
|
nospace(); |
|
var e = expression(0), s; |
|
if (e && e.type === '(string)') { |
|
if (!option.evil && (e.value === 'eval' || e.value === 'execScript')) { |
|
warning("eval is evil.", that); |
|
} |
|
countMember(e.value); |
|
if (!option.sub && ix.test(e.value)) { |
|
s = syntax[e.value]; |
|
if (!s || !s.reserved) { |
|
warning("['{a}'] is better written in dot notation.", |
|
e, e.value); |
|
} |
|
} |
|
} |
|
advance(']', that); |
|
nospace(prevtoken, token); |
|
that.left = left; |
|
that.right = e; |
|
return that; |
|
}, 160, true); |
|
|
|
prefix('[', function () { |
|
var b = token.line !== nexttoken.line; |
|
this.first = []; |
|
if (b) { |
|
indent += option.indent; |
|
if (nexttoken.from === indent + option.indent) { |
|
indent += option.indent; |
|
} |
|
} |
|
while (nexttoken.id !== '(end)') { |
|
while (nexttoken.id === ',') { |
|
warning("Extra comma."); |
|
advance(','); |
|
} |
|
if (nexttoken.id === ']') { |
|
break; |
|
} |
|
if (b && token.line !== nexttoken.line) { |
|
indentation(); |
|
} |
|
this.first.push(expression(10)); |
|
if (nexttoken.id === ',') { |
|
comma(); |
|
if (nexttoken.id === ']' && !option.es5) { |
|
warning("Extra comma.", token); |
|
break; |
|
} |
|
} else { |
|
break; |
|
} |
|
} |
|
if (b) { |
|
indent -= option.indent; |
|
indentation(); |
|
} |
|
advance(']', this); |
|
return this; |
|
}, 160); |
|
|
|
|
|
function property_name() { |
|
var id = optionalidentifier(true); |
|
if (!id) { |
|
if (nexttoken.id === '(string)') { |
|
id = nexttoken.value; |
|
advance(); |
|
} else if (nexttoken.id === '(number)') { |
|
id = nexttoken.value.toString(); |
|
advance(); |
|
} |
|
} |
|
return id; |
|
} |
|
|
|
|
|
function functionparams() { |
|
var i, t = nexttoken, p = []; |
|
advance('('); |
|
nospace(); |
|
if (nexttoken.id === ')') { |
|
advance(')'); |
|
return; |
|
} |
|
for (;;) { |
|
i = identifier(true); |
|
p.push(i); |
|
addlabel(i, 'parameter'); |
|
if (nexttoken.id === ',') { |
|
comma(); |
|
} else { |
|
advance(')', t); |
|
nospace(prevtoken, token); |
|
return p; |
|
} |
|
} |
|
} |
|
|
|
|
|
function doFunction(i, statement) { |
|
var f, |
|
oldOption = option, |
|
oldScope = scope; |
|
|
|
option = Object.create(option); |
|
scope = Object.create(scope); |
|
|
|
funct = { |
|
'(name)' : i || '"' + anonname + '"', |
|
'(line)' : nexttoken.line, |
|
'(context)' : funct, |
|
'(breakage)' : 0, |
|
'(loopage)' : 0, |
|
'(scope)' : scope, |
|
'(statement)': statement |
|
}; |
|
f = funct; |
|
token.funct = funct; |
|
functions.push(funct); |
|
if (i) { |
|
addlabel(i, 'function'); |
|
} |
|
funct['(params)'] = functionparams(); |
|
|
|
block(false, false, true); |
|
scope = oldScope; |
|
option = oldOption; |
|
funct['(last)'] = token.line; |
|
funct = funct['(context)']; |
|
return f; |
|
} |
|
|
|
|
|
(function (x) { |
|
x.nud = function () { |
|
var b, f, i, j, p, t; |
|
var props = {}; // All properties, including accessors |
|
|
|
function saveProperty(name, token) { |
|
if (props[name] && is_own(props, name)) |
|
warning("Duplicate member '{a}'.", nexttoken, i); |
|
else |
|
props[name] = {}; |
|
|
|
props[name].basic = true; |
|
props[name].basicToken = token; |
|
} |
|
|
|
function saveSetter(name, token) { |
|
if (props[name] && is_own(props, name)) { |
|
if (props[name].basic || props[name].setter) |
|
warning("Duplicate member '{a}'.", nexttoken, i); |
|
} else { |
|
props[name] = {}; |
|
} |
|
|
|
props[name].setter = true; |
|
props[name].setterToken = token; |
|
} |
|
|
|
function saveGetter(name) { |
|
if (props[name] && is_own(props, name)) { |
|
if (props[name].basic || props[name].getter) |
|
warning("Duplicate member '{a}'.", nexttoken, i); |
|
} else { |
|
props[name] = {}; |
|
} |
|
|
|
props[name].getter = true; |
|
props[name].getterToken = token; |
|
} |
|
|
|
b = token.line !== nexttoken.line; |
|
if (b) { |
|
indent += option.indent; |
|
if (nexttoken.from === indent + option.indent) { |
|
indent += option.indent; |
|
} |
|
} |
|
for (;;) { |
|
if (nexttoken.id === '}') { |
|
break; |
|
} |
|
if (b) { |
|
indentation(); |
|
} |
|
if (nexttoken.value === 'get' && peek().id !== ':') { |
|
advance('get'); |
|
if (!option.es5) { |
|
error("get/set are ES5 features."); |
|
} |
|
i = property_name(); |
|
if (!i) { |
|
error("Missing property name."); |
|
} |
|
saveGetter(i); |
|
t = nexttoken; |
|
adjacent(token, nexttoken); |
|
f = doFunction(); |
|
p = f['(params)']; |
|
if (p) { |
|
warning("Unexpected parameter '{a}' in get {b} function.", t, p[0], i); |
|
} |
|
adjacent(token, nexttoken); |
|
} else if (nexttoken.value === 'set' && peek().id !== ':') { |
|
advance('set'); |
|
if (!option.es5) { |
|
error("get/set are ES5 features."); |
|
} |
|
i = property_name(); |
|
if (!i) { |
|
error("Missing property name."); |
|
} |
|
saveSetter(i, nexttoken); |
|
t = nexttoken; |
|
adjacent(token, nexttoken); |
|
f = doFunction(); |
|
p = f['(params)']; |
|
if (!p || p.length !== 1) { |
|
warning("Expected a single parameter in set {a} function.", t, i); |
|
} |
|
} else { |
|
i = property_name(); |
|
saveProperty(i, nexttoken); |
|
if (typeof i !== 'string') { |
|
break; |
|
} |
|
advance(':'); |
|
nonadjacent(token, nexttoken); |
|
expression(10); |
|
} |
|
|
|
countMember(i); |
|
if (nexttoken.id === ',') { |
|
comma(); |
|
if (nexttoken.id === ',') { |
|
warning("Extra comma.", token); |
|
} else if (nexttoken.id === '}' && !option.es5) { |
|
warning("Extra comma.", token); |
|
} |
|
} else { |
|
break; |
|
} |
|
} |
|
if (b) { |
|
indent -= option.indent; |
|
indentation(); |
|
} |
|
advance('}', this); |
|
|
|
// Check for lonely setters if in the ES5 mode. |
|
if (option.es5) { |
|
for (var name in props) { |
|
if (is_own(props, name) && props[name].setter && !props[name].getter) { |
|
warning("Setter is defined without getter.", props[name].setterToken); |
|
} |
|
} |
|
} |
|
return this; |
|
}; |
|
x.fud = function () { |
|
error("Expected to see a statement and instead saw a block.", token); |
|
}; |
|
}(delim('{'))); |
|
|
|
// This Function is called when esnext option is set to true |
|
// it adds the `const` statement to JSHINT |
|
|
|
useESNextSyntax = function () { |
|
var conststatement = stmt('const', function (prefix) { |
|
var id, name, value; |
|
|
|
this.first = []; |
|
for (;;) { |
|
nonadjacent(token, nexttoken); |
|
id = identifier(); |
|
if (funct[id] === "const") { |
|
warning("const '" + id + "' has already been declared"); |
|
} |
|
if (funct['(global)'] && predefined[id] === false) { |
|
warning("Redefinition of '{a}'.", token, id); |
|
} |
|
addlabel(id, 'const'); |
|
if (prefix) { |
|
break; |
|
} |
|
name = token; |
|
this.first.push(token); |
|
|
|
if (nexttoken.id !== "=") { |
|
warning("const " + |
|
"'{a}' is initialized to 'undefined'.", token, id); |
|
} |
|
|
|
if (nexttoken.id === '=') { |
|
nonadjacent(token, nexttoken); |
|
advance('='); |
|
nonadjacent(token, nexttoken); |
|
if (nexttoken.id === 'undefined') { |
|
warning("It is not necessary to initialize " + |
|
"'{a}' to 'undefined'.", token, id); |
|
} |
|
if (peek(0).id === '=' && nexttoken.identifier) { |
|
error("Constant {a} was not declared correctly.", |
|
nexttoken, nexttoken.value); |
|
} |
|
value = expression(0); |
|
name.first = value; |
|
} |
|
|
|
if (nexttoken.id !== ',') { |
|
break; |
|
} |
|
comma(); |
|
} |
|
return this; |
|
}); |
|
conststatement.exps = true; |
|
}; |
|
|
|
var varstatement = stmt('var', function (prefix) { |
|
// JavaScript does not have block scope. It only has function scope. So, |
|
// declaring a variable in a block can have unexpected consequences. |
|
var id, name, value; |
|
|
|
if (funct['(onevar)'] && option.onevar) { |
|
warning("Too many var statements."); |
|
} else if (!funct['(global)']) { |
|
funct['(onevar)'] = true; |
|
} |
|
this.first = []; |
|
for (;;) { |
|
nonadjacent(token, nexttoken); |
|
id = identifier(); |
|
if (option.esnext && funct[id] === "const") { |
|
warning("const '" + id + "' has already been declared"); |
|
} |
|
if (funct['(global)'] && predefined[id] === false) { |
|
warning("Redefinition of '{a}'.", token, id); |
|
} |
|
addlabel(id, 'unused'); |
|
if (prefix) { |
|
break; |
|
} |
|
name = token; |
|
this.first.push(token); |
|
if (nexttoken.id === '=') { |
|
nonadjacent(token, nexttoken); |
|
advance('='); |
|
nonadjacent(token, nexttoken); |
|
if (nexttoken.id === 'undefined') { |
|
warning("It is not necessary to initialize '{a}' to 'undefined'.", token, id); |
|
} |
|
if (peek(0).id === '=' && nexttoken.identifier) { |
|
error("Variable {a} was not declared correctly.", |
|
nexttoken, nexttoken.value); |
|
} |
|
value = expression(0); |
|
name.first = value; |
|
} |
|
if (nexttoken.id !== ',') { |
|
break; |
|
} |
|
comma(); |
|
} |
|
return this; |
|
}); |
|
varstatement.exps = true; |
|
|
|
blockstmt('function', function () { |
|
if (inblock) { |
|
warning("Function declarations should not be placed in blocks. " + |
|
"Use a function expression or move the statement to the top of " + |
|
"the outer function.", token); |
|
|
|
} |
|
var i = identifier(); |
|
if (option.esnext && funct[i] === "const") { |
|
warning("const '" + i + "' has already been declared"); |
|
} |
|
adjacent(token, nexttoken); |
|
addlabel(i, 'unction'); |
|
doFunction(i, true); |
|
if (nexttoken.id === '(' && nexttoken.line === token.line) { |
|
error( |
|
"Function declarations are not invocable. Wrap the whole function invocation in parens."); |
|
} |
|
return this; |
|
}); |
|
|
|
prefix('function', function () { |
|
var i = optionalidentifier(); |
|
if (i) { |
|
adjacent(token, nexttoken); |
|
} else { |
|
nonadjacent(token, nexttoken); |
|
} |
|
doFunction(i); |
|
if (!option.loopfunc && funct['(loopage)']) { |
|
warning("Don't make functions within a loop."); |
|
} |
|
return this; |
|
}); |
|
|
|
blockstmt('if', function () { |
|
var t = nexttoken; |
|
advance('('); |
|
nonadjacent(this, t); |
|
nospace(); |
|
expression(20); |
|
if (nexttoken.id === '=') { |
|
if (!option.boss) |
|
warning("Expected a conditional expression and instead saw an assignment."); |
|
advance('='); |
|
expression(20); |
|
} |
|
advance(')', t); |
|
nospace(prevtoken, token); |
|
block(true, true); |
|
if (nexttoken.id === 'else') { |
|
nonadjacent(token, nexttoken); |
|
advance('else'); |
|
if (nexttoken.id === 'if' || nexttoken.id === 'switch') { |
|
statement(true); |
|
} else { |
|
block(true, true); |
|
} |
|
} |
|
return this; |
|
}); |
|
|
|
blockstmt('try', function () { |
|
var b, e, s; |
|
|
|
block(false); |
|
if (nexttoken.id === 'catch') { |
|
advance('catch'); |
|
nonadjacent(token, nexttoken); |
|
advance('('); |
|
s = scope; |
|
scope = Object.create(s); |
|
e = nexttoken.value; |
|
if (nexttoken.type !== '(identifier)') { |
|
warning("Expected an identifier and instead saw '{a}'.", |
|
nexttoken, e); |
|
} else { |
|
addlabel(e, 'exception'); |
|
} |
|
advance(); |
|
advance(')'); |
|
block(false); |
|
b = true; |
|
scope = s; |
|
} |
|
if (nexttoken.id === 'finally') { |
|
advance('finally'); |
|
block(false); |
|
return; |
|
} else if (!b) { |
|
error("Expected '{a}' and instead saw '{b}'.", |
|
nexttoken, 'catch', nexttoken.value); |
|
} |
|
return this; |
|
}); |
|
|
|
blockstmt('while', function () { |
|
var t = nexttoken; |
|
funct['(breakage)'] += 1; |
|
funct['(loopage)'] += 1; |
|
advance('('); |
|
nonadjacent(this, t); |
|
nospace(); |
|
expression(20); |
|
if (nexttoken.id === '=') { |
|
if (!option.boss) |
|
warning("Expected a conditional expression and instead saw an assignment."); |
|
advance('='); |
|
expression(20); |
|
} |
|
advance(')', t); |
|
nospace(prevtoken, token); |
|
block(true, true); |
|
funct['(breakage)'] -= 1; |
|
funct['(loopage)'] -= 1; |
|
return this; |
|
}).labelled = true; |
|
|
|
blockstmt('with', function () { |
|
var t = nexttoken; |
|
if (directive['use strict']) { |
|
error("'with' is not allowed in strict mode.", token); |
|
} else if (!option.withstmt) { |
|
warning("Don't use 'with'.", token); |
|
} |
|
|
|
advance('('); |
|
nonadjacent(this, t); |
|
nospace(); |
|
expression(0); |
|
advance(')', t); |
|
nospace(prevtoken, token); |
|
block(true, true); |
|
|
|
return this; |
|
}); |
|
|
|
blockstmt('switch', function () { |
|
var t = nexttoken, |
|
g = false; |
|
funct['(breakage)'] += 1; |
|
advance('('); |
|
nonadjacent(this, t); |
|
nospace(); |
|
this.condition = expression(20); |
|
advance(')', t); |
|
nospace(prevtoken, token); |
|
nonadjacent(token, nexttoken); |
|
t = nexttoken; |
|
advance('{'); |
|
nonadjacent(token, nexttoken); |
|
indent += option.indent; |
|
this.cases = []; |
|
for (;;) { |
|
switch (nexttoken.id) { |
|
case 'case': |
|
switch (funct['(verb)']) { |
|
case 'break': |
|
case 'case': |
|
case 'continue': |
|
case 'return': |
|
case 'switch': |
|
case 'throw': |
|
break; |
|
default: |
|
// You can tell JSHint that you don't use break intentionally by |
|
// adding a comment /* falls through */ on a line just before |
|
// the next `case`. |
|
if (!ft.test(lines[nexttoken.line - 2])) { |
|
warning( |
|
"Expected a 'break' statement before 'case'.", |
|
token); |
|
} |
|
} |
|
indentation(-option.indent); |
|
advance('case'); |
|
this.cases.push(expression(20)); |
|
g = true; |
|
advance(':'); |
|
funct['(verb)'] = 'case'; |
|
break; |
|
case 'default': |
|
switch (funct['(verb)']) { |
|
case 'break': |
|
case 'continue': |
|
case 'return': |
|
case 'throw': |
|
break; |
|
default: |
|
if (!ft.test(lines[nexttoken.line - 2])) { |
|
warning( |
|
"Expected a 'break' statement before 'default'.", |
|
token); |
|
} |
|
} |
|
indentation(-option.indent); |
|
advance('default'); |
|
g = true; |
|
advance(':'); |
|
break; |
|
case '}': |
|
indent -= option.indent; |
|
indentation(); |
|
advance('}', t); |
|
if (this.cases.length === 1 || this.condition.id === 'true' || |
|
this.condition.id === 'false') { |
|
if (!option.onecase) |
|
warning("This 'switch' should be an 'if'.", this); |
|
} |
|
funct['(breakage)'] -= 1; |
|
funct['(verb)'] = undefined; |
|
return; |
|
case '(end)': |
|
error("Missing '{a}'.", nexttoken, '}'); |
|
return; |
|
default: |
|
if (g) { |
|
switch (token.id) { |
|
case ',': |
|
error("Each value should have its own case label."); |
|
return; |
|
case ':': |
|
g = false; |
|
statements(); |
|
break; |
|
default: |
|
error("Missing ':' on a case clause.", token); |
|
return; |
|
} |
|
} else { |
|
if (token.id === ':') { |
|
advance(':'); |
|
error("Unexpected '{a}'.", token, ':'); |
|
statements(); |
|
} else { |
|
error("Expected '{a}' and instead saw '{b}'.", |
|
nexttoken, 'case', nexttoken.value); |
|
return; |
|
} |
|
} |
|
} |
|
} |
|
}).labelled = true; |
|
|
|
stmt('debugger', function () { |
|
if (!option.debug) { |
|
warning("All 'debugger' statements should be removed."); |
|
} |
|
return this; |
|
}).exps = true; |
|
|
|
(function () { |
|
var x = stmt('do', function () { |
|
funct['(breakage)'] += 1; |
|
funct['(loopage)'] += 1; |
|
this.first = block(true); |
|
advance('while'); |
|
var t = nexttoken; |
|
nonadjacent(token, t); |
|
advance('('); |
|
nospace(); |
|
expression(20); |
|
if (nexttoken.id === '=') { |
|
if (!option.boss) |
|
warning("Expected a conditional expression and instead saw an assignment."); |
|
advance('='); |
|
expression(20); |
|
} |
|
advance(')', t); |
|
nospace(prevtoken, token); |
|
funct['(breakage)'] -= 1; |
|
funct['(loopage)'] -= 1; |
|
return this; |
|
}); |
|
x.labelled = true; |
|
x.exps = true; |
|
}()); |
|
|
|
blockstmt('for', function () { |
|
var s, t = nexttoken; |
|
funct['(breakage)'] += 1; |
|
funct['(loopage)'] += 1; |
|
advance('('); |
|
nonadjacent(this, t); |
|
nospace(); |
|
if (peek(nexttoken.id === 'var' ? 1 : 0).id === 'in') { |
|
if (nexttoken.id === 'var') { |
|
advance('var'); |
|
varstatement.fud.call(varstatement, true); |
|
} else { |
|
switch (funct[nexttoken.value]) { |
|
case 'unused': |
|
funct[nexttoken.value] = 'var'; |
|
break; |
|
case 'var': |
|
break; |
|
default: |
|
warning("Bad for in variable '{a}'.", |
|
nexttoken, nexttoken.value); |
|
} |
|
advance(); |
|
} |
|
advance('in'); |
|
expression(20); |
|
advance(')', t); |
|
s = block(true, true); |
|
if (option.forin && s && (s.length > 1 || typeof s[0] !== 'object' || |
|
s[0].value !== 'if')) { |
|
warning("The body of a for in should be wrapped in an if statement to filter " + |
|
"unwanted properties from the prototype.", this); |
|
} |
|
funct['(breakage)'] -= 1; |
|
funct['(loopage)'] -= 1; |
|
return this; |
|
} else { |
|
if (nexttoken.id !== ';') { |
|
if (nexttoken.id === 'var') { |
|
advance('var'); |
|
varstatement.fud.call(varstatement); |
|
} else { |
|
for (;;) { |
|
expression(0, 'for'); |
|
if (nexttoken.id !== ',') { |
|
break; |
|
} |
|
comma(); |
|
} |
|
} |
|
} |
|
nolinebreak(token); |
|
advance(';'); |
|
if (nexttoken.id !== ';') { |
|
expression(20); |
|
if (nexttoken.id === '=') { |
|
if (!option.boss) |
|
warning("Expected a conditional expression and instead saw an assignment."); |
|
advance('='); |
|
expression(20); |
|
} |
|
} |
|
nolinebreak(token); |
|
advance(';'); |
|
if (nexttoken.id === ';') { |
|
error("Expected '{a}' and instead saw '{b}'.", |
|
nexttoken, ')', ';'); |
|
} |
|
if (nexttoken.id !== ')') { |
|
for (;;) { |
|
expression(0, 'for'); |
|
if (nexttoken.id !== ',') { |
|
break; |
|
} |
|
comma(); |
|
} |
|
} |
|
advance(')', t); |
|
nospace(prevtoken, token); |
|
block(true, true); |
|
funct['(breakage)'] -= 1; |
|
funct['(loopage)'] -= 1; |
|
return this; |
|
} |
|
}).labelled = true; |
|
|
|
|
|
stmt('break', function () { |
|
var v = nexttoken.value; |
|
|
|
if (funct['(breakage)'] === 0) |
|
warning("Unexpected '{a}'.", nexttoken, this.value); |
|
|
|
if (!option.asi) |
|
nolinebreak(this); |
|
|
|
if (nexttoken.id !== ';') { |
|
if (token.line === nexttoken.line) { |
|
if (funct[v] !== 'label') { |
|
warning("'{a}' is not a statement label.", nexttoken, v); |
|
} else if (scope[v] !== funct) { |
|
warning("'{a}' is out of scope.", nexttoken, v); |
|
} |
|
this.first = nexttoken; |
|
advance(); |
|
} |
|
} |
|
reachable('break'); |
|
return this; |
|
}).exps = true; |
|
|
|
|
|
stmt('continue', function () { |
|
var v = nexttoken.value; |
|
|
|
if (funct['(breakage)'] === 0) |
|
warning("Unexpected '{a}'.", nexttoken, this.value); |
|
|
|
if (!option.asi) |
|
nolinebreak(this); |
|
|
|
if (nexttoken.id !== ';') { |
|
if (token.line === nexttoken.line) { |
|
if (funct[v] !== 'label') { |
|
warning("'{a}' is not a statement label.", nexttoken, v); |
|
} else if (scope[v] !== funct) { |
|
warning("'{a}' is out of scope.", nexttoken, v); |
|
} |
|
this.first = nexttoken; |
|
advance(); |
|
} |
|
} else if (!funct['(loopage)']) { |
|
warning("Unexpected '{a}'.", nexttoken, this.value); |
|
} |
|
reachable('continue'); |
|
return this; |
|
}).exps = true; |
|
|
|
|
|
stmt('return', function () { |
|
if (this.line === nexttoken.line) { |
|
if (nexttoken.id === '(regexp)') |
|
warning("Wrap the /regexp/ literal in parens to disambiguate the slash operator."); |
|
|
|
if (nexttoken.id !== ';' && !nexttoken.reach) { |
|
nonadjacent(token, nexttoken); |
|
if (peek().value === "=" && !option.boss) { |
|
warningAt("Did you mean to return a conditional instead of an assignment?", |
|
token.line, token.character + 1); |
|
} |
|
this.first = expression(0); |
|
} |
|
} else if (!option.asi) { |
|
nolinebreak(this); // always warn (Line breaking error) |
|
} |
|
reachable('return'); |
|
return this; |
|
}).exps = true; |
|
|
|
|
|
stmt('throw', function () { |
|
nolinebreak(this); |
|
nonadjacent(token, nexttoken); |
|
this.first = expression(20); |
|
reachable('throw'); |
|
return this; |
|
}).exps = true; |
|
|
|
// Superfluous reserved words |
|
|
|
reserve('class'); |
|
reserve('const'); |
|
reserve('enum'); |
|
reserve('export'); |
|
reserve('extends'); |
|
reserve('import'); |
|
reserve('super'); |
|
|
|
reserve('let'); |
|
reserve('yield'); |
|
reserve('implements'); |
|
reserve('interface'); |
|
reserve('package'); |
|
reserve('private'); |
|
reserve('protected'); |
|
reserve('public'); |
|
reserve('static'); |
|
|
|
|
|
// Parse JSON |
|
|
|
function jsonValue() { |
|
|
|
function jsonObject() { |
|
var o = {}, t = nexttoken; |
|
advance('{'); |
|
if (nexttoken.id !== '}') { |
|
for (;;) { |
|
if (nexttoken.id === '(end)') { |
|
error("Missing '}' to match '{' from line {a}.", |
|
nexttoken, t.line); |
|
} else if (nexttoken.id === '}') { |
|
warning("Unexpected comma.", token); |
|
break; |
|
} else if (nexttoken.id === ',') { |
|
error("Unexpected comma.", nexttoken); |
|
} else if (nexttoken.id !== '(string)') { |
|
warning("Expected a string and instead saw {a}.", |
|
nexttoken, nexttoken.value); |
|
} |
|
if (o[nexttoken.value] === true) { |
|
warning("Duplicate key '{a}'.", |
|
nexttoken, nexttoken.value); |
|
} else if ((nexttoken.value === '__proto__' && |
|
!option.proto) || (nexttoken.value === '__iterator__' && |
|
!option.iterator)) { |
|
warning("The '{a}' key may produce unexpected results.", |
|
nexttoken, nexttoken.value); |
|
} else { |
|
o[nexttoken.value] = true; |
|
} |
|
advance(); |
|
advance(':'); |
|
jsonValue(); |
|
if (nexttoken.id !== ',') { |
|
break; |
|
} |
|
advance(','); |
|
} |
|
} |
|
advance('}'); |
|
} |
|
|
|
function jsonArray() { |
|
var t = nexttoken; |
|
advance('['); |
|
if (nexttoken.id !== ']') { |
|
for (;;) { |
|
if (nexttoken.id === '(end)') { |
|
error("Missing ']' to match '[' from line {a}.", |
|
nexttoken, t.line); |
|
} else if (nexttoken.id === ']') { |
|
warning("Unexpected comma.", token); |
|
break; |
|
} else if (nexttoken.id === ',') { |
|
error("Unexpected comma.", nexttoken); |
|
} |
|
jsonValue(); |
|
if (nexttoken.id !== ',') { |
|
break; |
|
} |
|
advance(','); |
|
} |
|
} |
|
advance(']'); |
|
} |
|
|
|
switch (nexttoken.id) { |
|
case '{': |
|
jsonObject(); |
|
break; |
|
case '[': |
|
jsonArray(); |
|
break; |
|
case 'true': |
|
case 'false': |
|
case 'null': |
|
case '(number)': |
|
case '(string)': |
|
advance(); |
|
break; |
|
case '-': |
|
advance('-'); |
|
if (token.character !== nexttoken.from) { |
|
warning("Unexpected space after '-'.", token); |
|
} |
|
adjacent(token, nexttoken); |
|
advance('(number)'); |
|
break; |
|
default: |
|
error("Expected a JSON value.", nexttoken); |
|
} |
|
} |
|
|
|
|
|
// The actual JSHINT function itself. |
|
|
|
var itself = function (s, o, g) { |
|
var a, i, k, x, |
|
optionKeys, |
|
newOptionObj = {}; |
|
|
|
JSHINT.errors = []; |
|
JSHINT.undefs = []; |
|
predefined = Object.create(standard); |
|
combine(predefined, g || {}); |
|
if (o) { |
|
a = o.predef; |
|
if (a) { |
|
if (Array.isArray(a)) { |
|
for (i = 0; i < a.length; i += 1) { |
|
predefined[a[i]] = true; |
|
} |
|
} else if (typeof a === 'object') { |
|
k = Object.keys(a); |
|
for (i = 0; i < k.length; i += 1) { |
|
predefined[k[i]] = !!a[k[i]]; |
|
} |
|
} |
|
} |
|
optionKeys = Object.keys(o); |
|
for (x = 0; x < optionKeys.length; x++) { |
|
newOptionObj[optionKeys[x]] = o[optionKeys[x]]; |
|
} |
|
} |
|
|
|
option = newOptionObj; |
|
|
|
option.indent = option.indent || 4; |
|
option.maxerr = option.maxerr || 50; |
|
|
|
tab = ''; |
|
for (i = 0; i < option.indent; i += 1) { |
|
tab += ' '; |
|
} |
|
indent = 1; |
|
global = Object.create(predefined); |
|
scope = global; |
|
funct = { |
|
'(global)': true, |
|
'(name)': '(global)', |
|
'(scope)': scope, |
|
'(breakage)': 0, |
|
'(loopage)': 0 |
|
}; |
|
functions = [funct]; |
|
urls = []; |
|
stack = null; |
|
member = {}; |
|
membersOnly = null; |
|
implied = {}; |
|
inblock = false; |
|
lookahead = []; |
|
jsonmode = false; |
|
warnings = 0; |
|
lex.init(s); |
|
prereg = true; |
|
directive = {}; |
|
|
|
prevtoken = token = nexttoken = syntax['(begin)']; |
|
|
|
// Check options |
|
for (var name in o) { |
|
if (is_own(o, name)) { |
|
checkOption(name, token); |
|
} |
|
} |
|
|
|
assume(); |
|
|
|
// combine the passed globals after we've assumed all our options |
|
combine(predefined, g || {}); |
|
|
|
//reset values |
|
comma.first = true; |
|
|
|
try { |
|
advance(); |
|
switch (nexttoken.id) { |
|
case '{': |
|
case '[': |
|
option.laxbreak = true; |
|
jsonmode = true; |
|
jsonValue(); |
|
break; |
|
default: |
|
directives(); |
|
if (directive["use strict"] && !option.globalstrict) { |
|
warning("Use the function form of \"use strict\".", prevtoken); |
|
} |
|
|
|
statements(); |
|
} |
|
advance('(end)'); |
|
|
|
var markDefined = function (name, context) { |
|
do { |
|
if (typeof context[name] === 'string') { |
|
// JSHINT marks unused variables as 'unused' and |
|
// unused function declaration as 'unction'. This |
|
// code changes such instances back 'var' and |
|
// 'closure' so that the code in JSHINT.data() |
|
// doesn't think they're unused. |
|
|
|
if (context[name] === 'unused') |
|
context[name] = 'var'; |
|
else if (context[name] === 'unction') |
|
context[name] = 'closure'; |
|
|
|
return true; |
|
} |
|
|
|
context = context['(context)']; |
|
} while (context); |
|
|
|
return false; |
|
}; |
|
|
|
var clearImplied = function (name, line) { |
|
if (!implied[name]) |
|
return; |
|
|
|
var newImplied = []; |
|
for (var i = 0; i < implied[name].length; i += 1) { |
|
if (implied[name][i] !== line) |
|
newImplied.push(implied[name][i]); |
|
} |
|
|
|
if (newImplied.length === 0) |
|
delete implied[name]; |
|
else |
|
implied[name] = newImplied; |
|
}; |
|
|
|
// Check queued 'x is not defined' instances to see if they're still undefined. |
|
for (i = 0; i < JSHINT.undefs.length; i += 1) { |
|
k = JSHINT.undefs[i].slice(0); |
|
|
|
if (markDefined(k[2].value, k[0])) { |
|
clearImplied(k[2].value, k[2].line); |
|
} else { |
|
warning.apply(warning, k.slice(1)); |
|
} |
|
} |
|
} catch (e) { |
|
if (e) { |
|
var nt = nexttoken || {}; |
|
JSHINT.errors.push({ |
|
raw : e.raw, |
|
reason : e.message, |
|
line : e.line || nt.line, |
|
character : e.character || nt.from |
|
}, null); |
|
} |
|
} |
|
|
|
return JSHINT.errors.length === 0; |
|
}; |
|
|
|
// Data summary. |
|
itself.data = function () { |
|
|
|
var data = { functions: [], options: option }, fu, globals, implieds = [], f, i, j, |
|
members = [], n, unused = [], v; |
|
if (itself.errors.length) { |
|
data.errors = itself.errors; |
|
} |
|
|
|
if (jsonmode) { |
|
data.json = true; |
|
} |
|
|
|
for (n in implied) { |
|
if (is_own(implied, n)) { |
|
implieds.push({ |
|
name: n, |
|
line: implied[n] |
|
}); |
|
} |
|
} |
|
if (implieds.length > 0) { |
|
data.implieds = implieds; |
|
} |
|
|
|
if (urls.length > 0) { |
|
data.urls = urls; |
|
} |
|
|
|
globals = Object.keys(scope); |
|
if (globals.length > 0) { |
|
data.globals = globals; |
|
} |
|
for (i = 1; i < functions.length; i += 1) { |
|
f = functions[i]; |
|
fu = {}; |
|
for (j = 0; j < functionicity.length; j += 1) { |
|
fu[functionicity[j]] = []; |
|
} |
|
for (n in f) { |
|
if (is_own(f, n) && n.charAt(0) !== '(') { |
|
v = f[n]; |
|
if (v === 'unction') { |
|
v = 'unused'; |
|
} |
|
if (Array.isArray(fu[v])) { |
|
fu[v].push(n); |
|
if (v === 'unused') { |
|
unused.push({ |
|
name: n, |
|
line: f['(line)'], |
|
'function': f['(name)'] |
|
}); |
|
} |
|
} |
|
} |
|
} |
|
for (j = 0; j < functionicity.length; j += 1) { |
|
if (fu[functionicity[j]].length === 0) { |
|
delete fu[functionicity[j]]; |
|
} |
|
} |
|
fu.name = f['(name)']; |
|
fu.param = f['(params)']; |
|
fu.line = f['(line)']; |
|
fu.last = f['(last)']; |
|
data.functions.push(fu); |
|
} |
|
|
|
if (unused.length > 0) { |
|
data.unused = unused; |
|
} |
|
|
|
members = []; |
|
for (n in member) { |
|
if (typeof member[n] === 'number') { |
|
data.member = member; |
|
break; |
|
} |
|
} |
|
|
|
return data; |
|
}; |
|
|
|
itself.report = function (option) { |
|
var data = itself.data(); |
|
|
|
var a = [], c, e, err, f, i, k, l, m = '', n, o = [], s; |
|
|
|
function detail(h, array) { |
|
var b, i, singularity; |
|
if (array) { |
|
o.push('<div><i>' + h + '</i> '); |
|
array = array.sort(); |
|
for (i = 0; i < array.length; i += 1) { |
|
if (array[i] !== singularity) { |
|
singularity = array[i]; |
|
o.push((b ? ', ' : '') + singularity); |
|
b = true; |
|
} |
|
} |
|
o.push('</div>'); |
|
} |
|
} |
|
|
|
|
|
if (data.errors || data.implieds || data.unused) { |
|
err = true; |
|
o.push('<div id=errors><i>Error:</i>'); |
|
if (data.errors) { |
|
for (i = 0; i < data.errors.length; i += 1) { |
|
c = data.errors[i]; |
|
if (c) { |
|
e = c.evidence || ''; |
|
o.push('<p>Problem' + (isFinite(c.line) ? ' at line ' + |
|
c.line + ' character ' + c.character : '') + |
|
': ' + c.reason.entityify() + |
|
'</p><p class=evidence>' + |
|
(e && (e.length > 80 ? e.slice(0, 77) + '...' : |
|
e).entityify()) + '</p>'); |
|
} |
|
} |
|
} |
|
|
|
if (data.implieds) { |
|
s = []; |
|
for (i = 0; i < data.implieds.length; i += 1) { |
|
s[i] = '<code>' + data.implieds[i].name + '</code> <i>' + |
|
data.implieds[i].line + '</i>'; |
|
} |
|
o.push('<p><i>Implied global:</i> ' + s.join(', ') + '</p>'); |
|
} |
|
|
|
if (data.unused) { |
|
s = []; |
|
for (i = 0; i < data.unused.length; i += 1) { |
|
s[i] = '<code><u>' + data.unused[i].name + '</u></code> <i>' + |
|
data.unused[i].line + '</i> <code>' + |
|
data.unused[i]['function'] + '</code>'; |
|
} |
|
o.push('<p><i>Unused variable:</i> ' + s.join(', ') + '</p>'); |
|
} |
|
if (data.json) { |
|
o.push('<p>JSON: bad.</p>'); |
|
} |
|
o.push('</div>'); |
|
} |
|
|
|
if (!option) { |
|
|
|
o.push('<br><div id=functions>'); |
|
|
|
if (data.urls) { |
|
detail("URLs<br>", data.urls, '<br>'); |
|
} |
|
|
|
if (data.json && !err) { |
|
o.push('<p>JSON: good.</p>'); |
|
} else if (data.globals) { |
|
o.push('<div><i>Global</i> ' + |
|
data.globals.sort().join(', ') + '</div>'); |
|
} else { |
|
o.push('<div><i>No new global variables introduced.</i></div>'); |
|
} |
|
|
|
for (i = 0; i < data.functions.length; i += 1) { |
|
f = data.functions[i]; |
|
|
|
o.push('<br><div class=function><i>' + f.line + '-' + |
|
f.last + '</i> ' + (f.name || '') + '(' + |
|
(f.param ? f.param.join(', ') : '') + ')</div>'); |
|
detail('<big><b>Unused</b></big>', f.unused); |
|
detail('Closure', f.closure); |
|
detail('Variable', f['var']); |
|
detail('Exception', f.exception); |
|
detail('Outer', f.outer); |
|
detail('Global', f.global); |
|
detail('Label', f.label); |
|
} |
|
|
|
if (data.member) { |
|
a = Object.keys(data.member); |
|
if (a.length) { |
|
a = a.sort(); |
|
m = '<br><pre id=members>/*members '; |
|
l = 10; |
|
for (i = 0; i < a.length; i += 1) { |
|
k = a[i]; |
|
n = k.name(); |
|
if (l + n.length > 72) { |
|
o.push(m + '<br>'); |
|
m = ' '; |
|
l = 1; |
|
} |
|
l += n.length + 2; |
|
if (data.member[k] === 1) { |
|
n = '<i>' + n + '</i>'; |
|
} |
|
if (i < a.length - 1) { |
|
n += ', '; |
|
} |
|
m += n; |
|
} |
|
o.push(m + '<br>*/</pre>'); |
|
} |
|
o.push('</div>'); |
|
} |
|
} |
|
return o.join(''); |
|
}; |
|
|
|
itself.jshint = itself; |
|
|
|
return itself; |
|
}()); |
|
|
|
// Make JSHINT a Node module, if possible. |
|
if (typeof exports === 'object' && exports) |
|
exports.JSHINT = JSHINT;
|
|
|