Index: ossp-pkg/js/ChangeLog RCS File: /v/ossp/cvs/ossp-pkg/js/ChangeLog,v rcsdiff -q -kk '-r1.2' '-r1.3' -u '/v/ossp/cvs/ossp-pkg/js/ChangeLog,v' 2>/dev/null --- ChangeLog 2006/07/23 18:34:15 1.2 +++ ChangeLog 2006/07/23 19:11:48 1.3 @@ -13,6 +13,11 @@ Changes between 1.6.20060722 and 1.6.20060723 (2006-07-22 to 2006-07-23) + o Added jslint (see http://www.jslint.com/) together with some + home-brewn option parser. The result is installed as + a stand-alone "jslint" program. + [Ralf S. Engelschall ] + o Install all src/js*.h headers during "make install" to allow applications to at least optionally poke around in the internals. This also allows an application to call js_InitFileClass() from Index: ossp-pkg/js/Makefile.in RCS File: /v/ossp/cvs/ossp-pkg/js/Makefile.in,v rcsdiff -q -kk '-r1.21' '-r1.22' -u '/v/ossp/cvs/ossp-pkg/js/Makefile.in,v' 2>/dev/null --- Makefile.in 2006/07/23 18:34:15 1.21 +++ Makefile.in 2006/07/23 19:11:48 1.22 @@ -182,6 +182,7 @@ $(SHTOOL) mkdir -f -p -m 755 $(DESTDIR)$(includedir)/js $(SHTOOL) mkdir -f -p -m 755 $(DESTDIR)$(libdir)/pkgconfig $(SHTOOL) install -c -m 755 js $(DESTDIR)$(bindir)/ + $(SHTOOL) install -e 's;#!\./js;#!$(bindir)/js;' -c -m 755 jslint.js $(DESTDIR)$(bindir)/jslint $(SHTOOL) install -c -m 644 js.1 $(DESTDIR)$(mandir)/man1/ $(SHTOOL) install -c -m 644 js.3 $(DESTDIR)$(mandir)/man3/ $(SHTOOL) install -c -m 644 src/js*.h $(DESTDIR)$(includedir)/js/ Index: ossp-pkg/js/jslint.js RCS File: /v/ossp/cvs/ossp-pkg/js/jslint.js,v co -q -kk -p'1.1' '/v/ossp/cvs/ossp-pkg/js/jslint.js,v' | diff -u /dev/null - -L'ossp-pkg/js/jslint.js' 2>/dev/null --- ossp-pkg/js/jslint.js +++ - 2024-05-13 09:04:22.195537901 +0200 @@ -0,0 +1,2542 @@ +#!./js + +/* ============================================================== */ + +// jslint.js +// 2006-06-12 +/* +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. +*/ + + +/* + jslint is a function. It takes two parameters. + + var myResult = jslint(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 HTML text, or a Konfabulator text. + + The second parameter is an optional object of options which control the + operation of jslint. All of the options are booleans. All are optional and + have a default value of false. + + { + browser : true if the standard browser globals should be predefined + cap : true if upper case HTML should be allowed + debug : true if debugger statements should be allowed + evil : true if eval should be allowed + jscript : true if jscript deviations should be allowed + laxLineEnd : true if line breaks should not be checked + passfail : true if the scan should stop on first error + plusplus : true if post increment should not be allowed + undef : true if undefined variables are errors + } + + If it checks out, jslint returns true. Otherwise, it returns false. + + If false, you can inspect jslint.errors to find out the problems. + jslint.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 + } + + If a fatal error was found, a null will be the last element of the + jslint.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 a . + + var myReport = jslint.report(option); + + If the option is true, then the report will be limited to only errors. +*/ + +String.prototype.entityify = function () { + return this. + replace(/&/g, '&'). + replace(//g, '>'); +}; + +String.prototype.isAlpha = function () { + return (this >= 'a' && this <= 'z\uffff') || + (this >= 'A' && this <= 'Z\uffff'); +}; + + +String.prototype.isDigit = function () { + return (this >= '0' && this <= '9'); +}; + + +// We build the application inside a function so that we produce only a single +// global variable. The function will be invoked, its return value is the JSLint +// function itself. + +var jslint; +jslint = function () { + + var anonname, + +// browser contains a set of global names which are commonly provided by a +// web browser environment. + + browser = { + alert: true, + blur: true, + clearInterval: true, + clearTimeout: true, + close: true, + closed: true, + confirm: true, + defaultStatus: true, + document: true, + event: true, + focus: true, + frames: true, + history: true, + Image: true, + length: true, + location: true, + moveBy: true, + moveTo: true, + name: true, + navigator: true, + onblur: true, + onerror: true, + onfocus: true, + onload: true, + onresize: true, + onunload: true, + open: true, + opener: true, + parent: true, + print: true, + prompt: true, + resizeBy: true, + resizeTo: true, + screen: true, + scroll: true, + scrollBy: true, + scrollTo: true, + self: true, + setInterval: true, + setTimeout: true, + status: true, + top: true, + window: true, + XMLHttpRequest: true + }, + funlab, funstack, functions, globals, + +// konfab contains the global names which are provided to a Konfabulator widget. + + konfab = { + alert: true, + animator: true, + appleScript: true, + beep: true, + bytesToUIString: true, + chooseColor: true, + chooseFile: true, + chooseFolder: true, + convertPathToHFS: true, + convertPathToPlatform: true, + closeWidget: true, + CustomAnimation: true, + escape: true, + FadeAnimation: true, + focusWidget: true, + form: true, + include: true, + isApplicationRunning: true, + iTunes: true, + konfabulatorVersion: true, + log: true, + MoveAnimation: true, + openURL: true, + play: true, + popupMenu: true, + print: true, + prompt: true, + reloadWidget: true, + resolvePath: true, + resumeUpdates: true, + RotateAnimation: true, + runCommand: true, + runCommandInBg: true, + saveAs: true, + savePreferences: true, + showWidgetPreferences: true, + sleep: true, + speak: true, + suppressUpdates: true, + tellWidget: true, + unescape: true, + updateNow: true, + yahooCheckLogin: true, + yahooLogin: true, + yahooLogout: true, + COM: true, + filesystem: true, + preferenceGroups: true, + preferences: true, + screen: true, + system: true, + URL: true, + XMLDOM: true, + XMLHttpRequest: true + }, + lines, lookahead, member, noreach, option, prevtoken, stack, + +// standard contains the global names that are provided by standard JavaScript. + + standard = { + Array: true, + Boolean: true, + Date: true, + decodeURI: true, + decodeURIComponent: true, + encodeURI: true, + encodeURIComponent: true, + Error: true, + escape: true, + eval: true, + EvalError: true, + Function: true, + isFinite: true, + isNaN: true, + Math: true, + Number: true, + Object: true, + parseInt: true, + parseFloat: true, + RangeError: true, + ReferenceError: true, + RegExp: true, + String: true, + SyntaxError: true, + TypeError: true, + unescape: true, + URIError: true + }, + syntax = {}, token, verb, +/* + xmode is used to adapt to the exceptions in XML parsing. It can have these + states: + false .js script file + " A " attribute + ' A ' attribute + content The content of a script tag + CDATA A CDATA block +*/ + xmode, +/* + xtype identifies the type of document being analyzed. It can have these + states: + false .js script file + html .html file + widget .kon Konfabulator file +*/ + xtype, +// token + tx = /^([(){}[.,:;'"~]|\](\]>)?|\?>?|==?=?|\/(\*(global|extern)*|=|)|\*[\/=]?|\+[+=]?|-[-=]?|%[=>]?|&[&=]?|\|[|=]?|>>?>?=?|<([\/=%\?]|\!(\[|--)?|<=?)?|\^=?|\!=?=?|[a-zA-Z_$][a-zA-Z0-9_$]*|[0-9]+([xX][0-9a-fA-F]+|\.[0-9]*)?([eE][+-]?[0-9]+)?)/, +// string ending in single quote + sx = /^((\\[^\x00-\x1f]|[^\x00-\x1f'\\])*)'/, + sxx = /^(([^\x00-\x1f'])*)'/, +// string ending in double quote + qx = /^((\\[^\x00-\x1f]|[^\x00-\x1f"\\])*)"/, + qxx = /^(([^\x00-\x1f"])*)"/, +// regular expression + rx = /^(\\[^\x00-\x1f]|\[(\\[^\x00-\x1f]|[^\x00-\x1f\\\/])*\]|[^\x00-\x1f\\\/\[])+\/[gim]*/, +// star slash + lx = /\*\/|\/\*/, +// global identifier + gx = /^([a-zA-Z_$][a-zA-Z0-9_$]*)/, +// identifier + ix = /^([a-zA-Z_$][a-zA-Z0-9_$]*$)/, +// global separators + hx = /^[\x00-\x20,]*(\*\/)?/, +// whitespace + wx = /^\s*(\/\/.*\r*$)?/; + +// Make a new object that inherits from an existing object. + + function object(o) { + function f() {} + f.prototype = o; + return new f(); + } + +// Produce an error warning. + + function warning(m, x, y) { + var l, c, t = typeof x === 'object' ? x : token; + if (typeof x === 'number') { + l = x; + c = y || 0; + } else { + if (t.id === '(end)') { + t = prevtoken; + } + l = t.line || 0; + c = t.from || 0; + } + jslint.errors.push({ + id: '(error)', + reason: m, + evidence: lines[l] || '', + line: l, + character: c + }); + if (option.passfail) { + jslint.errors.push(null); + throw null; + } + } + + function error(m, x, y) { + warning(m, x, y); + jslint.errors.push(null); + throw null; + } + + +// lexical analysis + + var lex = function () { + var character, from, line, s; + +// Private lex methods + + function nextLine() { + line += 1; + if (line >= lines.length) { + return false; + } + character = 0; + s = lines[line]; + return true; + } + +// Produce a token object. The token inherits from a syntax symbol. + + function it(type, value) { + var t; + if (type === '(punctuator)') { + t = syntax[value]; + } else if (type === '(identifier)') { + t = syntax[value]; + if (!t || typeof t != 'object') { + t = syntax[type]; + } + } else { + t = syntax[type]; + } + if (!t || typeof t != 'object') { + error("Unrecognized symbol: '" + value + "' " + type); + } + t = object(t); + if (value || type === '(string)') { + t.value = value; + } + t.line = line; + t.character = character; + t.from = from; + return t; + } + +// Public lex methods + + return { + init: function (source) { + if (typeof source === 'string') { + lines = source.split('\n'); + if (lines.length == 1) { + lines = lines[0].split('\r'); + } + } else { + lines = source; + } + line = 0; + character = 0; + from = 0; + s = lines[0]; + }, + +// token -- this is called by advance to get the next token. + + token: function () { + var c, i, l, r, t; + + function string(x) { + r = x.exec(s); + if (r) { + t = r[1]; + l = r[0].length; + s = s.substr(l); + character += l; + if (xmode == 'script') { + if (t.indexOf('<\/') >= 0) { + warning( + 'Expected "...<\\/..." and instead saw "...<\/...".', token); + } + } + return it('(string)', r[1]); + } else { + for (var j = 0; j < s.length; j += 1) { + var c = s.charAt(j); + if (c < ' ') { + if (c === '\n' || c === '\r') { + break; + } + error("Control character in string: " + + s.substring(0, j), line, character + j); + } + } + error("Unclosed string: " + s, line, character); + } + } + + for (;;) { + if (!s) { + return it(nextLine() ? '(endline)' : '(end)', ''); + } + r = wx.exec(s); + if (!r || !r[0]) { + break; + } + l = r[0].length; + s = s.substr(l); + character += l; + if (s) { + break; + } + } + from = character; + r = tx.exec(s); + if (r) { + t = r[0]; + l = t.length; + s = s.substr(l); + character += l; + c = t.substr(0, 1); + +// identifier + + if (c.isAlpha() || c === '_' || c === '$') { + return it('(identifier)', t); + } + +// number + + if (c.isDigit()) { + if (token.id === '.') { + warning( + "A decimal fraction should have a zero before the decimal point.", + token); + } + if (!isFinite(Number(t))) { + warning("Bad number: '" + t + "'.", + line, character); + } + if (s.substr(0, 1).isAlpha()) { + error("Space is required after a number: '" + + t + "'.", line, character); + } + if (c === '0' && t.substr(1,1).isDigit()) { + warning("Don't use extra leading zeros: '" + + t + "'.", line, character); + } + if (t.substr(t.length - 1) === '.') { + warning( + "A trailing decimal point can be confused with a dot: '" + t + "'.", + line, character); + } + return it('(number)', t); + } + +// string + + if (t === '"') { + return (xmode === '"' || xmode === 'string') ? + it('(punctuator)', t) : + string(xmode === 'xml' ? qxx : qx); + } + if (t === "'") { + return (xmode === "'" || xmode === 'string') ? + it('(punctuator)', t) : + string(xmode === 'xml' ? sxx : sx); + } + +// unbegun comment + + if (t === '/*') { + for (;;) { + i = s.search(lx); + if (i >= 0) { + break; + } + if (!nextLine()) { + error("Unclosed comment.", token); + } + } + character += i + 2; + if (s.substr(i, 1) === '/') { + error("Nested comment."); + } + s = s.substr(i + 2); + return this.token(); + } + +// /*extern + + if (t === '/*extern' || t === '/*global') { + for (;;) { + r = hx.exec(s); + if (r) { + l = r[0].length; + s = s.substr(l); + character += l; + if (r[1] === '*/') { + return this.token(); + } + } + if (s) { + r = gx.exec(s); + if (r) { + l = r[0].length; + s = s.substr(l); + character += l; + globals[r[1]] = true; + } else { + error("Bad extern identifier: '" + + s + "'.", line, character); + } + } else if (!nextLine()) { + error("Unclosed comment."); + } + } + } + +// punctuator + + return it('(punctuator)', t); + } + error("Unexpected token: " + (t || s.substr(0, 1)), + line, character); + }, + +// skip -- skip past the next occurrence of a particular string. +// If the argument is empty, skip to just before the next '<' character. +// This is used to ignore HTML content. Return false if it isn't found. + + skip: function (to) { + if (token.id) { + if (!to) { + to = ''; + if (token.id.substr(0, 1) === '<') { + lookahead.push(token); + return true; + } + } else if (token.id.indexOf(to) >= 0) { + return true; + } + } + prevtoken = token; + token = syntax['(error)']; + for (;;) { + var i = s.indexOf(to || '<'); + if (i >= 0) { + character += i + to.length; + s = s.substr(i + to.length); + return true; + } + if (!nextLine()) { + break; + } + } + return false; + }, + +// regex -- this is called by parse when it sees '/' being used as a prefix. + + regex: function () { + var l, r = rx.exec(s), x; + if (r) { + l = r[0].length; + character += l; + s = s.substr(l); + x = r[1]; + return it('(regex)', x); + } + error("Bad regular expression: " + s); + } + }; + }(); + + function builtin(name) { + return standard[name] === true || + globals[name] === true || + (xtype === 'widget' && konfab[name] === true) || + ((xtype === 'html' || option.browser) && browser[name] === true); + } + + function addlabel(t, type) { + if (t) { + if (typeof funlab[t] === 'string') { + switch (funlab[t]) { + case 'var': + case 'var*': + if (type === 'global') { + funlab[t] = 'var*'; + return; + } + break; + case 'global': + if (type === 'var') { + warning('Var ' + t + + ' was used before it was declared.', prevtoken); + } + if (type === 'var*' || type === 'global') { + return; + } + break; + case 'function': + case 'parameter': + if (type === 'global') { + return; + } + break; + } + warning("Identifier '" + t + "' already declared as " + + funlab[t], prevtoken); + } + funlab[t] = type; + } + } + + +// 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(i) { + var j = 0, t; + if (token == syntax['(error)']) { + return token; + } + if (typeof i === 'undefined') { + i = 0; + } + while (j <= i) { + t = lookahead[j]; + if (!t) { + t = lookahead[j] = lex.token(); + } + j += 1; + } + return t; + } + + + var badbreak = {')': true, ']': true, '++': true, '--': true}; + +// Produce the next token. It looks for programming errors. + + function advance(id, t) { + var l; + switch (prevtoken.id) { + case '(number)': + if (token.id === '.') { + warning( +"A dot following a number can be confused with a decimal point.", prevtoken); + } + break; + case '-': + if (token.id === '-' || token.id === '--') { + warning("Confusing minusses."); + } + break; + case '+': + if (token.id === '+' || token.id === '++') { + warning("Confusing plusses."); + } + break; + } + if (prevtoken.type === '(string)' || prevtoken.identifier) { + anonname = prevtoken.value; + } + + if (id && token.value != id) { + if (t) { + if (token.id === '(end)') { + warning("Unmatched '" + t.id + "'.", t); + } else { + warning("Expected '" + id + "' to match '" + + t.id + "' from line " + (t.line + 1) + + " and instead saw '" + token.value + "'."); + } + } else { + warning("Expected '" + id + "' and instead saw '" + + token.value + "'."); + } + } + prevtoken = token; + for (;;) { + token = lookahead.shift() || lex.token(); + if (token.id === ''); + } else { + error("Unexpected token '') { + if (xmode === 'CDATA') { + xmode = 'script'; + } else { + error("Unexpected token ']]>"); + } + } else if (token.id !== '(endline)') { + break; + } + if (xmode === '"' || xmode === "'") { + error("Missing '" + xmode + "'.", prevtoken); + } + l = !xmode && !option.laxLineEnd && + (prevtoken.type == '(string)' || prevtoken.type == '(number)' || + prevtoken.type == '(identifier)' || badbreak[prevtoken.id]); + } + if (l && token.id != '{' && token.id != '}' && token.id != ']') { + warning( + "Strict line ending error: '" + + prevtoken.value + "'.", prevtoken); + } + if (xtype === 'widget' && xmode === 'script' && token.id) { + l = token.id.charAt(0); + if (l === '<' || l === '&') { + token.nud = token.led = null; + token.lbp = 0; + token.reach = true; + } + } + } + + + function advanceregex() { + token = lex.regex(); + } + + + function beginfunction(i) { + var f = {'(name)': i, '(line)': token.line + 1, '(context)': funlab}; + funstack.push(funlab); + funlab = f; + functions.push(funlab); + } + + + function endfunction() { + funlab = funstack.pop(); + } + + +// This is the heart of JSLint, the Pratt parser. In addition to parsing, it +// is looking for ad hoc lint patterns. We add to Pratt's model .fud, 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 JavaScript. I retained Pratt's +// nomenclature, even though it isn't very descriptive. + +// .nud Null denotation +// .fud First null denotation +// .led Left denotation +// lbp Left binding power +// rbp Right binding power + +// They are key to the parsing method called Top Down Operator Precedence. + + function parse(rbp, initial) { + var l, left, o; + if (token.id && token.id === '/') { + if (prevtoken.id != '(' && prevtoken.id != '=' && + prevtoken.id != ':' && prevtoken.id != ',' && + prevtoken.id != '=') { + warning( +"Expected to see a '(' or '=' or ':' or ',' preceding a regular expression literal, and instead saw '" + + prevtoken.value + "'.", prevtoken); + } + advanceregex(); + } + if (token.id === '(end)') { + warning("Unexpected early end of program", prevtoken); + } + advance(); + if (initial) { + anonname = 'anonymous'; + verb = prevtoken.value; + } + if (initial && prevtoken.fud) { + prevtoken.fud(); + } else { + if (prevtoken.nud) { + o = prevtoken.exps; + left = prevtoken.nud(); + } else { + if (token.type === '(number)' && prevtoken.id === '.') { + warning( +"A leading decimal point can be confused with a dot: ." + token.value, + prevtoken); + } + error("Expected an identifier and instead saw '" + + prevtoken.id + "'.", prevtoken); + } + while (rbp < token.lbp) { + o = token.exps; + advance(); + if (prevtoken.led) { + left = prevtoken.led(left); + } else { + error("Expected an operator and instead saw '" + + prevtoken.id + "'."); + } + } + if (initial && !o) { + warning( +"Expected an assignment or function call and instead saw an expression.", + prevtoken); + } + } + if (l) { + funlab[l] = 'label'; + } + return left; + } + + +// Parasitic constructors for making the symbols that will be inherited by +// tokens. + + function symbol(s, p) { + return syntax[s] || (syntax[s] = {id: s, lbp: p, value: s}); + } + + + 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 prefix(s, f) { + var x = symbol(s, 150); + x.nud = (typeof f === 'function') ? f : function () { + parse(150); + return this; + }; + return x; + } + + + function prefixname(s, f) { + var x = prefix(s, f); + x.identifier = x.reserved = true; + 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) { + return reserve(s, function () { + return this; + }); + } + + + function infix(s, f, p) { + var x = symbol(s, p); + x.led = (typeof f === 'function') ? f : function (left) { + return [f, left, parse(p)]; + }; + return x; + } + + + function assignop(s, f) { + symbol(s, 20).exps = true; + return infix(s, function (left) { + if (left) { + if (left.id === '.' || left.id === '[' || + (left.identifier && !left.reserved)) { + parse(19); + return left; + } + if (left == syntax['function']) { + if (option.jscript) { + parse(19); + return left; + } else { + warning( +"Expected an identifier in an assignment, and instead saw a function invocation.", + prevtoken); + } + } + } + error("Bad assignment.", this); + }, 20); + } + + + function suffix(s, f) { + var x = symbol(s, 150); + x.led = function (left) { + if (option.plusplus) { + warning(this.id + " is considered harmful.", this); + } + return [f, left]; + }; + return x; + } + + + function optionalidentifier() { + if (token.reserved) { + warning("Expected an identifier and instead saw '" + + token.id + "' (a reserved word)."); + } + if (token.identifier) { + advance(); + return prevtoken.value; + } + } + + + function identifier() { + var i = optionalidentifier(); + if (i) { + return i; + } + if (prevtoken.id === 'function' && token.id === '(') { + warning("Missing name in function statement."); + } else { + error("Expected an identifier and instead saw '" + + token.value + "'.", token); + } + } + + + function reachable(s) { + var i = 0, t; + if (token.id != ';' || noreach) { + return; + } + for (;;) { + t = peek(i); + if (t.reach) { + return; + } + if (t.id != '(endline)') { + if (t.id === 'function') { + warning( +"Inner functions should be listed at the top of the outer function.", t); + break; + } + warning("Unreachable '" + t.value + "' after '" + s + + "'.", t); + break; + } + i += 1; + } + } + + + function statement() { + var t = token; + while (t.id === ';') { + warning("Unnecessary semicolon", t); + advance(';'); + t = token; + if (t.id === '}') { + return; + } + } + if (t.identifier && !t.reserved && peek().id === ':') { + advance(); + advance(':'); + addlabel(t.value, 'live*'); + if (!token.labelled) { + warning("Label '" + t.value + + "' on unlabelable statement '" + token.value + "'.", + token); + } + if (t.value.toLowerCase() == 'javascript') { + warning("Label '" + t.value + + "' looks like a javascript url.", + token); + } + token.label = t.value; + t = token; + } + parse(0, true); + if (!t.block) { + if (token.id != ';') { + warning("Missing ';'", prevtoken.line, + prevtoken.from + prevtoken.value.length); + } else { + advance(';'); + } + } + } + + + function statements() { + while (!token.reach) { + statement(); + } + } + + + function block() { + var t = token; + if (token.id === '{') { + advance('{'); + statements(); + advance('}', t); + } else { + warning("Missing '{' before '" + token.value + "'."); + noreach = true; + statement(); + noreach = false; + } + verb = null; + } + + +// An identity function, used by string and number tokens. + + function idValue() { + return this; + } + + + function countMember(m) { + if (typeof member[m] === 'number') { + member[m] += 1; + } else { + member[m] = 1; + } + } + + +// Common HTML attributes that carry scripts. + + var scriptstring = { + onblur: true, + onchange: true, + onclick: true, + ondblclick: true, + onfocus: true, + onkeydown: true, + onkeypress: true, + onkeyup: true, + onload: true, + onmousedown: true, + onmousemove: true, + onmouseout: true, + onmouseover: true, + onmouseup: true, + onreset: true, + onselect: true, + onsubmit: true, + onunload: true + }; + + +// XML types. Currently we support html and widget. + + var xmltype = { + HTML: { + doBegin: function (n) { + if (!option.cap) { + warning("HTML case error."); + } + xmltype.html.doBegin(); + } + }, + html: { + doBegin: function (n) { + xtype = 'html'; + xmltype.html.script = false; + }, + doTagName: function (n, p) { + var i, t = xmltype.html.tag[n], x; + if (!t) { + error('Unrecognized tag: <' + n + '>. ' + + (n === n.toLowerCase() ? + 'Did you mean <' + n.toLowerCase() + '>?' : '')); + } + x = t.parent; + if (x) { + if (x.indexOf(' ' + p + ' ') < 0) { + error('A <' + n + '> must be within <' + x + '>', + prevtoken); + } + } else { + i = stack.length; + do { + if (i <= 0) { + error('A <' + n + '> must be within the body', + prevtoken); + } + i -= 1; + } while (stack[i].name !== 'body'); + } + xmltype.html.script = n === 'script'; + return t.simple; + }, + doAttribute: function (n, a) { + if (n === 'script') { + if (a === 'src') { + xmltype.html.script = false; + return 'string'; + } else if (a === 'language') { + warning("The 'language' attribute is deprecated", + prevtoken); + return false; + } + } + return scriptstring[a] && 'script'; + }, + doIt: function (n) { + return xmltype.html.script ? 'script' : + n !== 'html' && xmltype.html.tag[n].special && 'special'; + }, + tag: { + a: {}, + abbr: {}, + acronym: {}, + address: {}, + applet: {}, + area: {simple: true, parent: ' map '}, + b: {}, + base: {simple: true, parent: ' head '}, + bdo: {}, + big: {}, + blockquote: {}, + body: {parent: ' html noframes '}, + br: {simple: true}, + button: {}, + caption: {parent: ' table '}, + center: {}, + cite: {}, + code: {}, + col: {simple: true, parent: ' table colgroup '}, + colgroup: {parent: ' table '}, + dd: {parent: ' dl '}, + del: {}, + dfn: {}, + dir: {}, + div: {}, + dl: {}, + dt: {parent: ' dl '}, + em: {}, + embed: {}, + fieldset: {}, + font: {}, + form: {}, + frame: {simple: true, parent: ' frameset '}, + frameset: {parent: ' html frameset '}, + h1: {}, + h2: {}, + h3: {}, + h4: {}, + h5: {}, + h6: {}, + head: {parent: ' html '}, + html: {}, + hr: {simple: true}, + i: {}, + iframe: {}, + img: {simple: true}, + input: {simple: true}, + ins: {}, + kbd: {}, + label: {}, + legend: {parent: ' fieldset '}, + li: {parent: ' dir menu ol ul '}, + link: {simple: true, parent: ' head '}, + map: {}, + menu: {}, + meta: {simple: true, parent: ' head noscript '}, + noframes: {parent: ' html body '}, + noscript: {parent: ' html head body frameset '}, + object: {}, + ol: {}, + optgroup: {parent: ' select '}, + option: {parent: ' optgroup select '}, + p: {}, + param: {simple: true, parent: ' applet object '}, + pre: {}, + q: {}, + samp: {}, + script: {parent: +' head body p div span abbr acronym address bdo blockquote cite code del dfn em ins kbd pre samp strong th td var '}, + select: {}, + small: {}, + span: {}, + strong: {}, + style: {parent: ' head ', special: true}, + sub: {}, + sup: {}, + table: {}, + tbody: {parent: ' table '}, + td: {parent: ' tr '}, + textarea: {}, + tfoot: {parent: ' table '}, + th: {parent: ' tr '}, + thead: {parent: ' table '}, + title: {parent: ' head '}, + tr: {parent: ' table tbody thead tfoot '}, + tt: {}, + u: {}, + ul: {}, + 'var': {} + } + }, + widget: { + doBegin: function (n) { + xtype = 'widget'; + }, + doTagName: function (n, p) { + var t = xmltype.widget.tag[n]; + if (!t) { + error('Unrecognized tag: <' + n + '>. '); + } + var x = t.parent; + if (x.indexOf(' ' + p + ' ') < 0) { + error('A <' + n + '> must be within <' + x + '>', prevtoken); + } + }, + doAttribute: function (n, a) { + var t = xmltype.widget.tag[a]; + if (!t) { + error('Unrecognized attribute: <' + n + ' ' + a + '>. '); + } + var x = t.parent; + if (x.indexOf(' ' + n + ' ') < 0) { + error('Attribute ' + a + ' does not belong in <' + + n + '>'); + } + return t.script ? 'script' : a === 'name' ? 'define' : 'string'; + }, + doIt: function (n) { + var x = xmltype.widget.tag[n]; + return x && x.script && 'script'; + }, + tag: { + "about-box": {parent: ' widget '}, + "about-image": {parent: ' about-box '}, + "about-text": {parent: ' about-box '}, + "about-version": {parent: ' about-box '}, + action: {parent: ' widget ', script: true}, + alignment: {parent: ' image text textarea window '}, + author: {parent: ' widget '}, + autoHide: {parent: ' scrollbar '}, + bgColor: {parent: ' text textarea '}, + bgOpacity: {parent: ' text textarea '}, + checked: {parent: ' image '}, + clipRect: {parent: ' image '}, + color: {parent: ' about-text about-version shadow text textarea '}, + contextMenuItems: {parent: ' frame image text textarea window '}, + colorize: {parent: ' image '}, + columns: {parent: ' textarea '}, + company: {parent: ' widget '}, + copyright: {parent: ' widget '}, + data: {parent: ' about-text about-version text textarea '}, + debug: {parent: ' widget '}, + defaultValue: {parent: ' preference '}, + defaultTracking: {parent: ' widget '}, + description: {parent: ' preference '}, + directory: {parent: ' preference '}, + editable: {parent: ' textarea '}, + enabled: {parent: ' menuItem '}, + extension: {parent: ' preference '}, + file: {parent: ' action preference '}, + fillMode: {parent: ' image '}, + font: {parent: ' about-text about-version text textarea '}, + frame: {parent: ' frame window '}, + group: {parent: ' preference '}, + hAlign: {parent: ' frame image scrollbar text textarea '}, + height: {parent: ' frame image scrollbar text textarea window '}, + hidden: {parent: ' preference '}, + hLineSize: {parent: ' frame '}, + hOffset: {parent: ' about-text about-version frame image scrollbar shadow text textarea window '}, + hotkey: {parent: ' widget '}, + hRegistrationPoint: {parent: ' image '}, + hslAdjustment: {parent: ' image '}, + hslTinting: {parent: ' image '}, + hScrollBar: {parent: ' frame '}, + icon: {parent: ' preferenceGroup '}, + image: {parent: ' about-box frame window widget '}, + interval: {parent: ' action timer '}, + key: {parent: ' hotkey '}, + kind: {parent: ' preference '}, + level: {parent: ' window '}, + lines: {parent: ' textarea '}, + loadingSrc: {parent: ' image '}, + max: {parent: ' scrollbar '}, + maxLength: {parent: ' preference '}, + menuItem: {parent: ' contextMenuItems '}, + min: {parent: ' scrollbar '}, + minimumVersion: {parent: ' widget '}, + minLength: {parent: ' preference '}, + missingSrc: {parent: ' image '}, + modifier: {parent: ' hotkey '}, + name: {parent: ' hotkey image preference preferenceGroup text textarea timer window '}, + notSaved: {parent: ' preference '}, + onContextMenu: {parent: ' frame image text textarea window ', script: true}, + onDragDrop: {parent: ' frame image text textarea ', script: true}, + onDragEnter: {parent: ' frame image text textarea ', script: true}, + onDragExit: {parent: ' frame image text textarea ', script: true}, + onFirstDisplay: {parent: ' window ', script: true}, + onGainFocus: {parent: ' textarea window ', script: true}, + onKeyDown: {parent: ' hotkey text textarea ', script: true}, + onKeyPress: {parent: ' textarea ', script: true}, + onKeyUp: {parent: ' hotkey text textarea ', script: true}, + onImageLoaded: {parent: ' image ', script: true}, + onLoseFocus: {parent: ' textarea window ', script: true}, + onMouseDown: {parent: ' frame image text textarea ', script: true}, + onMouseEnter: {parent: ' frame image text textarea ', script: true}, + onMouseExit: {parent: ' frame image text textarea ', script: true}, + onMouseMove: {parent: ' frame image text ', script: true}, + onMouseUp: {parent: ' frame image text textarea ', script: true}, + onMouseWheel: {parent: ' frame ', script: true}, + onMultiClick: {parent: ' frame image text textarea window ', script: true}, + onSelect: {parent: ' menuItem ', script: true}, + onTimerFired: {parent: ' timer ', script: true}, + onValueChanged: {parent: ' scrollbar ', script: true}, + opacity: {parent: ' frame image scrollbar shadow text textarea window '}, + option: {parent: ' preference widget '}, + optionValue: {parent: ' preference '}, + order: {parent: ' preferenceGroup '}, + orientation: {parent: ' scrollbar '}, + pageSize: {parent: ' scrollbar '}, + preference: {parent: ' widget '}, + preferenceGroup: {parent: ' widget '}, + remoteAsync: {parent: ' image '}, + requiredPlatform: {parent: ' widget '}, + rotation: {parent: ' image '}, + scrollX: {parent: ' frame '}, + scrollY: {parent: ' frame '}, + secure: {parent: ' preference textarea '}, + scrollbar: {parent: ' text textarea '}, + shadow: {parent: ' about-text text window '}, + size: {parent: ' about-text about-version text textarea '}, + spellcheck: {parent: ' textarea '}, + src: {parent: ' image '}, + srcHeight: {parent: ' image '}, + srcWidth: {parent: ' image '}, + style: {parent: ' about-text about-version preference text textarea '}, + text: {parent: ' frame window '}, + textarea: {parent: ' frame window '}, + timer: {parent: ' widget '}, + thumbColor: {parent: ' scrollbar '}, + ticking: {parent: ' timer '}, + ticks: {parent: ' preference '}, + tickLabel: {parent: ' preference '}, + tileOrigin: {parent: ' image '}, + title: {parent: ' menuItem preference preferenceGroup window '}, + tooltip: {parent: ' image text textarea '}, + truncation: {parent: ' text '}, + tracking: {parent: ' image '}, + trigger: {parent: ' action '}, + truncation: {parent: ' text textarea '}, + type: {parent: ' preference '}, + useFileIcon: {parent: ' image '}, + vAlign: {parent: ' frame image scrollbar text textarea '}, + value: {parent: ' preference scrollbar '}, + version: {parent: ' widget '}, + visible: {parent: ' frame image scrollbar text textarea window '}, + vLineSize: {parent: ' frame '}, + vOffset: {parent: ' about-text about-version frame image scrollbar shadow text textarea window '}, + vRegistrationPoint: {parent: ' image '}, + vScrollBar: {parent: ' frame '}, + width: {parent: ' frame image scrollbar text textarea window '}, + window: {parent: ' widget '}, + zOrder: {parent: ' frame image scrollbar text textarea '} + } + } + }; + + function xmlword(tag) { + var w = token.value; + if (!token.identifier) { + if (token.id === '<') { + error(tag ? "Expected < and saw '<'" : "Missing '>'", + prevtoken); + } else { + warning("Missing quotes", prevtoken); + } + } + advance(); + while (token.id === '-' || token.id === ':') { + w += token.id; + advance(); + if (!token.identifier) { + error('Bad name: ' + w + token.value); + } + w += token.value; + advance(); + } + return w; + } + + function xml() { + var a, e, n, q, t; + xmode = 'xml'; + stack = []; + for (;;) { + switch (token.value) { + case '<': + advance('<'); + t = token; + n = xmlword(true); + t.name = n; + if (!xtype) { + if (xmltype[n]) { + xmltype[n].doBegin(); + n = xtype; + e = false; + } else { + error("Unrecognized <" + n + ">"); + } + } else { + if (option.cap && xtype === 'html') { + n = n.toLowerCase(); + } + e = xmltype[xtype].doTagName(n, stack[stack.length - 1].type); + } + t.type = n; + for (;;) { + if (token.id === '/') { + advance('/'); + e = true; + break; + } + if (token.id && token.id.substr(0, 1) === '>') { + break; + } + a = xmlword(); + switch (xmltype[xtype].doAttribute(n, a)) { + case 'script': + xmode = 'string'; + advance('='); + q = token.id; + if (q !== '"' && q !== "'") { + error('Missing quote.'); + } + xmode = q; + advance(q); + statements(); + if (token.id !== q) { + error( + 'Missing close quote on script attribute'); + } + xmode = 'xml'; + advance(q); + break; + case 'value': + advance('='); + if (!token.identifier && + token.type != '(string)' && + token.type != '(number)') { + error('Bad value: ' + token.value); + } + advance(); + break; + case 'string': + advance('='); + if (token.type !== '(string)') { + error('Bad value: ' + token.value); + } + advance(); + break; + case 'define': + advance('='); + if (token.type !== '(string)') { + error('Bad value: ' + token.value); + } + addlabel(token.value, 'global'); + advance(); + break; + default: + if (token.id === '=') { + advance('='); + if (!token.identifier && + token.type != '(string)' && + token.type != '(number)') { + } + advance(); + } + } + } + switch (xmltype[xtype].doIt(n)) { + case 'script': + xmode = 'script'; + advance('>'); + statements(); + xmode = 'xml'; + break; + case 'special': + e = true; + n = ''; + if (!lex.skip(n)) { + error("Missing " + n, t); + } + break; + default: + lex.skip('>'); + } + if (!e) { + stack.push(t); + } + break; + case ''); + } + if (t.name != n) { + error('Expected and instead saw '); + } + if (token.id !== '>') { + error("Expected '>'"); + } + lex.skip('>'); + break; + case '') { + break; + } + if (token.id === '<' || token.id === '(end)') { + error("Missing '>'.", prevtoken); + } + } + lex.skip('>'); + break; + case ''); + break; + case '<%': + lex.skip('%>'); + break; + case '') { + break; + } + if (token.id === '' || token.id === '(end)') { + error("Missing '?>'.", prevtoken); + } + } + lex.skip('?>'); + break; + case '<=': + case '<<': + case '<<=': + error("Expected '<'."); + break; + case '(end)': + return; + } + if (!lex.skip('')) { + if (stack.length) { + t = stack.pop(); + error('Missing ', t); + } + return; + } + advance(); + } + } + + +// Build the syntax table by declaring the syntactic elements of the language. + + type('(number)', idValue); + type('(string)', idValue); + + syntax['(identifier)'] = { + type: '(identifier)', + lbp: 0, + identifier: true, + nud: function () { + if (option.undef && !builtin(this.value) && + xmode !== '"' && xmode !== "'") { + var c = funlab; + while (!c[this.value]) { + c = c['(context)']; + if (!c) { + warning("Undefined variable: " + this.value, + prevtoken); + break; + } + } + } + addlabel(this.value, 'global'); + return this; + }, + led: function () { + error("Expected an operator and instead saw '" + + token.value + "'."); + } + }; + + type('(regex)', function () { + return [this.id, this.value, this.flags]; + }); + + delim('(endline)'); + delim('(begin)'); + delim('(end)').reach = true; + delim(''); + delim('?>'); + delim('(error)').reach = true; + delim('}').reach = true; + delim(')'); + delim(']'); + delim(']]>').reach = true; + delim('"').reach = true; + delim("'").reach = true; + delim(';'); + delim(':').reach = true; + delim(','); + reserve('else'); + reserve('case').reach = true; + reserve('default').reach = true; + reserve('catch'); + reserve('finally'); + reservevar('arguments'); + reservevar('false'); + reservevar('Infinity'); + reservevar('NaN'); + reservevar('null'); + reservevar('this'); + reservevar('true'); + reservevar('undefined'); + assignop('=', 'assign', 20); + assignop('+=', 'assignadd', 20); + assignop('-=', 'assignsub', 20); + assignop('*=', 'assignmult', 20); + assignop('/=', 'assigndiv', 20).nud = function () { + warning( + "A regular expression literal can be confused with '/='."); + }; + assignop('%=', 'assignmod', 20); + assignop('&=', 'assignbitand', 20); + assignop('|=', 'assignbitor', 20); + assignop('^=', 'assignbitxor', 20); + assignop('<<=', 'assignshiftleft', 20); + assignop('>>=', 'assignshiftright', 20); + assignop('>>>=', 'assignshiftrightunsigned', 20); + infix('?', function (left) { + parse(10); + advance(':'); + parse(10); + }, 30); + + infix('||', 'or', 40); + infix('&&', 'and', 50); + infix('|', 'bitor', 70); + infix('^', 'bitxor', 80); + infix('&', 'bitand', 90); + infix('==', function (left) { + var t = token; + if ( (t.type === '(number)' && !+t.value) || + (t.type === '(string)' && !t.value) || + t.type === 'true' || t.type === 'false' || + t.type === 'undefined' || t.type === 'null') { + warning("Use '===' to compare with '" + t.value + "'.", t); + } + return ['==', left, parse(100)]; + }, 100); + infix('===', 'equalexact', 100); + infix('!=', function (left) { + var t = token; + if ( (t.type === '(number)' && !+t.value) || + (t.type === '(string)' && !t.value) || + t.type === 'true' || t.type === 'false' || + t.type === 'undefined' || t.type === 'null') { + warning("Use '!==' to compare with '" + t.value + "'.", t); + } + return ['!=', left, parse(100)]; + }, 100); + infix('!==', 'notequalexact', 100); + infix('<', 'less', 110); + infix('>', 'greater', 110); + infix('<=', 'lessequal', 110); + infix('>=', 'greaterequal', 110); + infix('<<', 'shiftleft', 120); + infix('>>', 'shiftright', 120); + infix('>>>', 'shiftrightunsigned', 120); + infix('in', 'in', 120); + infix('instanceof', 'instanceof', 120); + infix('+', 'addconcat', 130); + prefix('+', 'num'); + infix('-', 'sub', 130); + prefix('-', 'neg'); + infix('*', 'mult', 140); + infix('/', 'div', 140); + infix('%', 'mod', 140); + + suffix('++', 'postinc'); + prefix('++', 'preinc'); + syntax['++'].exps = true; + + suffix('--', 'postdec'); + prefix('--', 'predec'); + syntax['--'].exps = true; + prefixname('delete', function () { + parse(0); + }).exps = true; + + + prefix('~', 'bitnot'); + prefix('!', 'not'); + prefixname('typeof', 'typeof'); + prefixname('new', function () { + var c = parse(155); + if (c) { + if (c.identifier) { + switch (c.value) { + case 'Object': + warning('Use the object literal notation {}.', prevtoken); + break; + case 'Array': + warning('Use the array literal notation [].', prevtoken); + break; + case 'Number': + case 'String': + case 'Boolean': + warning("Do not use the " + c.value + + " function as a constructor.", prevtoken); + break; + case 'Function': + if (!option.evil) { + warning('The Function constructor is eval.'); + } + } + } else { + if (c.id !== '.' && c.id !== '[' && c.id !== '(') { + warning('Bad constructor', prevtoken); + } + } + } else { + warning('Weird construction.', this); + } + if (token.id === '(') { + advance('('); + if (token.id !== ')') { + for (;;) { + parse(10); + if (token.id !== ',') { + break; + } + advance(','); + } + } + advance(')'); + } else { + warning("Missing '()' invoking a constructor."); + } + return syntax['function']; + }); + syntax['new'].exps = true; + + infix('.', function (left) { + var m = identifier(); + if (typeof m === 'string') { + countMember(m); + } + if (!option.evil && left && left.value === 'document' && + (m === 'write' || m === 'writeln')) { + warning("document.write can be a form of eval.", left); + } + this.left = left; + this.right = m; + return this; + }, 160); + + infix('(', function (left) { + var n = 0, p = []; + if (token.id !== ')') { + for (;;) { + p[p.length] = parse(10); + n += 1; + if (token.id !== ',') { + break; + } + advance(','); + } + } + advance(')'); + 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') { + warning("eval is evil", left); + } else if (p[0] && p[0].id === '(string)' && + (left.value === 'setTimeout' || + left.value === 'setInterval')) { + warning( + "Implied eval is evil. Use a function argument instead of a string", left); + } + } + if (!left.identifier && left.id !== '.' && + left.id !== '[' && left.id !== '(') { + warning('Bad invocation.', left); + } + + } + return syntax['function']; + }, 155).exps = true; + + prefix('(', function () { + parse(0); + advance(')', this); + }); + + infix('[', function (left) { + var e = parse(0); + if (e && e.type === '(string)') { + countMember(e.value); + if (ix.test(e.value)) { + var s = syntax[e.value]; + if (!s || !s.reserved) { + warning("This is better written in dot notation.", e); + } + } + } + advance(']', this); + this.left = left; + this.right = e; + return this; + }, 160); + + prefix('[', function () { + if (token.id === ']') { + advance(']'); + return; + } + for (;;) { + parse(10); + if (token.id === ',') { + advance(','); + if (token.id === ']' || token.id === ',') { + warning('Extra comma.', prevtoken); + } + } else { + advance(']', this); + return; + } + } + }, 160); + + (function (x) { + x.nud = function () { + var i; + if (token.id === '}') { + advance('}'); + return; + } + for (;;) { + i = optionalidentifier(true); + if (!i && (token.id === '(string)' || token.id === '(number)')) { + i = token.id; + advance(); + } + if (!i) { + error("Expected an identifier and instead saw '" + + token.value + "'."); + } + if (typeof i.value === 'string') { + countMember(i.value); + } + advance(':'); + parse(10); + if (token.id === ',') { + advance(','); + if (token.id === ',' || token.id === '}') { + warning("Extra comma."); + } + } else { + advance('}', this); + return; + } + } + }; + x.fud = function () { + error( + "Expected to see a statement and instead saw a block."); + }; + })(delim('{')); + + + function varstatement() { + for (;;) { + addlabel(identifier(), 'var'); + if (token.id === '=') { + advance('='); + parse(20); + } + if (token.id === ',') { + advance(','); + } else { + return; + } + } + } + + + stmt('var', varstatement); + + + function functionparams() { + var t = token; + advance('('); + if (token.id === ')') { + advance(')'); + return; + } + for (;;) { + addlabel(identifier(), 'parameter'); + if (token.id === ',') { + advance(','); + } else { + advance(')', t); + return; + } + } + } + + + blockstmt('function', function () { + var i = identifier(); + addlabel(i, 'var*'); + beginfunction(i); + addlabel(i, 'function'); + functionparams(); + block(); + endfunction(); + }); + + prefixname('function', function () { + var i = optionalidentifier() || ('"' + anonname + '"'); + beginfunction(i); + addlabel(i, 'function'); + functionparams(); + block(); + endfunction(); + }); + + blockstmt('if', function () { + var t = token; + advance('('); + parse(20); + advance(')', t); + block(); + if (token.id === 'else') { + advance('else'); + if (token.id === 'if' || token.id === 'switch') { + statement(); + } else { + block(); + } + } + }); + + blockstmt('try', function () { + var b; + block(); + if (token.id === 'catch') { + advance('catch'); + beginfunction('"catch"'); + functionparams(); + block(); + endfunction(); + b = true; + } + if (token.id === 'finally') { + advance('finally'); + beginfunction('"finally"'); + block(); + endfunction(); + return; + } else if (!b) { + error("Expected 'catch' or 'finally' and instead saw '" + + token.value + "'."); + } + }); + + blockstmt('while', function () { + var t= token; + advance('('); + parse(20); + advance(')', t); + block(); + }).labelled = true; + + reserve('with'); + + blockstmt('switch', function () { + var t = token; + advance('('); + var g = false; + parse(20); + advance(')', t); + t = token; + advance('{'); + for (;;) { + switch (token.id) { + case 'case': + switch (verb) { + case 'break': + case 'case': + case 'continue': + case 'return': + case 'switch': + case 'throw': + break; + default: + warning( + "Expected a 'break' statement before 'case'.", + prevtoken); + } + advance('case'); + parse(20); + g = true; + advance(':'); + verb = 'case'; + break; + case 'default': + switch (verb) { + case 'break': + case 'continue': + case 'return': + case 'throw': + break; + default: + warning( + "Expected a 'break' statement before 'default'.", + prevtoken); + } + advance('default'); + g = true; + advance(':'); + break; + case '}': + advance('}', t); + return; + default: + if (g) { + statements(); + } else { + error("Expected to see 'case' and instead saw '" + + token.value + "'."); + } + } + } + }).labelled = true; + + stmt('debugger', function () { + if (!option.debug) { + warning("All debugger statements should be removed."); + } + }); + + stmt('do', function () { + block(); + advance('while'); + var t = token; + advance('('); + parse(20); + advance(')', t); + }).labelled = true; + + blockstmt('for', function () { + var t = token; + advance('('); + if (peek(token.id === 'var' ? 1 : 0).id === 'in') { + if (token.id === 'var') { + advance('var'); + addlabel(identifier(), 'var'); + } else { + advance(); + } + advance('in'); + parse(20); + advance(')', t); + block(); + return; + } else { + if (token.id != ';') { + if (token.id === 'var') { + advance('var'); + varstatement(); + } else { + for (;;) { + parse(0); + if (token.id !== ',') { + break; + } + advance(','); + } + } + } + advance(';'); + if (token.id != ';') { + parse(20); + } + advance(';'); + if (token.id === ';') { + error("Expected to see ')' and instead saw ';'"); + } + if (token.id != ')') { + for (;;) { + parse(0); + if (token.id !== ',') { + break; + } + advance(','); + } + } + advance(')', t); + block(); + } + }).labelled = true; + + + function nolinebreak(t) { + if (t.line !== token.line) { + warning("Statement broken badly.", t); + } + } + + + stmt('break', function () { + nolinebreak(this); + if (funlab[token.value] === 'live*') { + advance(); + } + reachable('break'); + }); + + + stmt('continue', function () { + nolinebreak(this); + if (funlab[token.id] === 'live*') { + advance(); + } + reachable('continue'); + }); + + + stmt('return', function () { + nolinebreak(this); + if (token.id != ';' && !token.reach) { + parse(20); + } + reachable('return'); + }); + + + stmt('throw', function () { + nolinebreak(this); + parse(20); + reachable('throw'); + }); + + +// Superfluous reserved words + + reserve('abstract'); + reserve('boolean'); + reserve('byte'); + reserve('char'); + reserve('class'); + reserve('const'); + reserve('double'); + reserve('enum'); + reserve('export'); + reserve('extends'); + reserve('final'); + reserve('float'); + reserve('goto'); + reserve('implements'); + reserve('import'); + reserve('int'); + reserve('interface'); + reserve('long'); + reserve('native'); + reserve('package'); + reserve('private'); + reserve('protected'); + reserve('public'); + reserve('short'); + reserve('static'); + reserve('super'); + reserve('synchronized'); + reserve('throws'); + reserve('transient'); + reserve('void'); + reserve('volatile'); + + +// The actual jslint function itself. + + var j = function (s, o) { + option = o; + if (!o) { + option = {}; + } + jslint.errors = []; + globals = {}; + functions = []; + xmode = false; + xtype = ''; + stack = null; + funlab = {}; + member = {}; + funstack = []; + lookahead = []; + lex.init(s); + + prevtoken = token = syntax['(begin)']; + try { + advance(); + if (token.value.charAt(0) === '<') { + xml(); + } else { + statements(); + advance('(end)'); + } + } catch (e) { + if (e) { + jslint.errors.push({ + reason: "JSLint error: " + e.description, + line: token.line, + character: token.from, + evidence: token.value + }); + } + } + return jslint.errors.length === 0; + }; + + +// Report generator. + + j.report = function (option) { + var a = [], c, cc, f, i, k, o = [], s; + + function detail(h) { + if (s.length) { + o.push('
' + h + ':  ' + s.sort().join(', ') + + '
'); + } + } + + k = jslint.errors.length; + if (k) { + o.push( + '
Error:
'); + for (i = 0; i < k; i += 1) { + c = jslint.errors[i]; + if (c) { + o.push('

Problem at line ' + (c.line + 1) + + ' character ' + (c.character + 1) + + ': ' + c.reason.entityify() + + '

' + c.evidence.entityify() + + '

'); + } + } + o.push('
'); + if (!c) { + return o.join(''); + } + } + + if (!option) { + for (k in member) { + a.push(k); + } + if (a.length) { + a = a.sort(); + o.push( + ''); + for (i = 0; i < a.length; i += 1) { + o.push(''); + } + o.push('
MembersOccurrences
', a[i], '', member[a[i]], + '
'); + } + for (i = 0; i < functions.length; ++i) { + f = functions[i]; + for (k in f) { + if (f[k] === 'global') { + c = f['(context)']; + for (;;) { + cc = c['(context)']; + if (!cc) { + if ((!funlab[k] || funlab[k] === 'var?') && + !builtin(k)) { + funlab[k] = 'var?'; + f[k] = 'global?'; + } + break; + } + if (c[k] === 'parameter!' || c[k] === 'var!') { + f[k] = 'var.'; + break; + } + if (c[k] === 'var' || c[k] === 'var*' || + c[k] === 'var!') { + f[k] = 'var.'; + c[k] = 'var!'; + break; + } + if (c[k] === 'parameter') { + f[k] = 'var.'; + c[k] = 'parameter!'; + break; + } + c = cc; + } + } + } + } + s = []; + for (k in funlab) { + c = funlab[k]; + if (typeof c === 'string' && c.substr(0, 3) === 'var') { + if (c === 'var?') { + s.push('' + k + ' (?)'); + } else { + s.push('' + k + ''); + } + } + } + detail('Global'); + if (functions.length) { + o.push('
Function:
    '); + } + for (i = 0; i < functions.length; i += 1) { + f = functions[i]; + o.push('
  1. ' + (f['(name)'] || '') + ''); + s = []; + for (k in f) { + if (k.charAt(0) != '(') { + switch (f[k]) { + case 'parameter': + s.push('' + k + ''); + break; + case 'parameter!': + s.push('' + k + + ' (closure)'); + break; + } + } + } + detail('Parameter'); + s = []; + for (k in f) { + if (k.charAt(0) != '(') { + switch(f[k]) { + case 'var': + s.push('' + k + + ' (unused)'); + break; + case 'var*': + s.push('' + k + ''); + break; + case 'var!': + s.push('' + k + + ' (closure)'); + break; + case 'var.': + s.push('' + k + + ' (outer)'); + break; + } + } + } + detail('Var'); + s = []; + c = f['(context)']; + for (k in f) { + if (k.charAt(0) != '(' && f[k].substr(0, 6) === 'global') { + if (f[k] === 'global?') { + s.push('' + k + ' (?)'); + } else { + s.push('' + k + ''); + } + } + } + detail('Global'); + s = []; + for (k in f) { + if (k.charAt(0) != '(' && f[k] === 'label') { + s.push(k); + } + } + detail('Label'); + o.push('
  2. '); + } + if (functions.length) { + o.push('
'); + } + } + return o.join(''); + }; + + return j; + +}(); + +/* ============================================================== */ + +var options = { + "browser" : false, + "cap" : false, + "debug" : false, + "evil" : false, + "jscript" : false, + "laxLineEnd" : false, + "passfail" : false, + "plusplus" : false, + "undef" : false +}; + +function die(str) { + print("jslint:ERROR: " + str); + quit(); +} + +function usage() { + print("jslint:USAGE: jslint file.js"); + quit(); +} + +var i; +for (i = 0; i < arguments.length; i++) { + if ( arguments[i].substring(0, 1) != '-' + && arguments[i].substring(0, 1) != '+') + break; + if (options[arguments[i].substring(1)] == undefined) + die("invalid command line option \"" + arguments[i] + "\""); + options[arguments[i].substring(1)] = + (arguments[i].substring(0, 1) == "-" ? true : false); +} +if (arguments[i] == undefined) { + usage() +} + +var file = new File(arguments[i]); +file.open("read"); +var script = file.readAll(); +file.close(); + +if (!jslint(script, { passfail: true })) { + var e = jslint.errors[0]; + print('jslint: line ' + (e.line + 1) + ' character ' + (e.character + 1) + ': ' + e.reason); + print((e.evidence || ''). replace(/^\s*(\S*(\s+\S+)*)\s*$/, "$1")); + quit(); +} +