forked from Mirrors/freeswitch
fff98f6316
git-svn-id: http://svn.freeswitch.org/svn/freeswitch/trunk@3693 d0543943-73ff-0310-b7d9-9358b9ac24b2
2543 lines
80 KiB
JavaScript
2543 lines
80 KiB
JavaScript
#!./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 <body>.
|
|
|
|
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, '<').
|
|
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 === '<![') {
|
|
if (xtype === 'html') {
|
|
error("Unexpected token '<!['");
|
|
}
|
|
if (xmode === 'script') {
|
|
token = lex.token();
|
|
if (token.value !== 'CDATA') {
|
|
error("Expected 'CDATA'");
|
|
}
|
|
token = lex.token();
|
|
if (token.id !== '[') {
|
|
error("Expected '['");
|
|
}
|
|
xmode = 'CDATA';
|
|
} else if (xmode === 'xml') {
|
|
lex.skip(']]>');
|
|
} else {
|
|
error("Unexpected token '<!['");
|
|
}
|
|
} else if (token.id === ']]>') {
|
|
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 = '</' + t.name + '>';
|
|
if (!lex.skip(n)) {
|
|
error("Missing " + n, t);
|
|
}
|
|
break;
|
|
default:
|
|
lex.skip('>');
|
|
}
|
|
if (!e) {
|
|
stack.push(t);
|
|
}
|
|
break;
|
|
case '</':
|
|
advance('</');
|
|
n = xmlword(true);
|
|
t = stack.pop();
|
|
if (!t) {
|
|
error('Unexpected close tag: </' + n + '>');
|
|
}
|
|
if (t.name != n) {
|
|
error('Expected </' + t.name +
|
|
'> and instead saw </' + n + '>');
|
|
}
|
|
if (token.id !== '>') {
|
|
error("Expected '>'");
|
|
}
|
|
lex.skip('>');
|
|
break;
|
|
case '<!':
|
|
for (;;) {
|
|
advance();
|
|
if (token.id === '>') {
|
|
break;
|
|
}
|
|
if (token.id === '<' || token.id === '(end)') {
|
|
error("Missing '>'.", prevtoken);
|
|
}
|
|
}
|
|
lex.skip('>');
|
|
break;
|
|
case '<!--':
|
|
lex.skip('-->');
|
|
break;
|
|
case '<%':
|
|
lex.skip('%>');
|
|
break;
|
|
case '<?':
|
|
for (;;) {
|
|
advance();
|
|
if (token.id === '?>') {
|
|
break;
|
|
}
|
|
if (token.id === '<?' || token.id === '<' ||
|
|
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.name + '>', 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('</').reach = true;
|
|
delim('<![').reach = true;
|
|
delim('<%');
|
|
delim('<?');
|
|
delim('<!');
|
|
delim('<!--');
|
|
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('<div>' + h + ': ' + s.sort().join(', ') +
|
|
'</div>');
|
|
}
|
|
}
|
|
|
|
k = jslint.errors.length;
|
|
if (k) {
|
|
o.push(
|
|
'<div style="background-color: mistyrose;">Error:<blockquote>');
|
|
for (i = 0; i < k; i += 1) {
|
|
c = jslint.errors[i];
|
|
if (c) {
|
|
o.push('<p>Problem at line ' + (c.line + 1) +
|
|
' character ' + (c.character + 1) +
|
|
': ' + c.reason.entityify() +
|
|
'</p><p><tt>' + c.evidence.entityify() +
|
|
'</tt></p>');
|
|
}
|
|
}
|
|
o.push('</blockquote></div>');
|
|
if (!c) {
|
|
return o.join('');
|
|
}
|
|
}
|
|
|
|
if (!option) {
|
|
for (k in member) {
|
|
a.push(k);
|
|
}
|
|
if (a.length) {
|
|
a = a.sort();
|
|
o.push(
|
|
'<table><tbody><tr><th>Members</th><th>Occurrences</th></tr>');
|
|
for (i = 0; i < a.length; i += 1) {
|
|
o.push('<tr><td><tt>', a[i], '</tt></td><td>', member[a[i]],
|
|
'</td></tr>');
|
|
}
|
|
o.push('</tbody></table>');
|
|
}
|
|
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('<tt>' + k + '</tt><small> (?)</small>');
|
|
} else {
|
|
s.push('<tt>' + k + '</tt>');
|
|
}
|
|
}
|
|
}
|
|
detail('Global');
|
|
if (functions.length) {
|
|
o.push('<br>Function:<ol style="padding-left:0.5in">');
|
|
}
|
|
for (i = 0; i < functions.length; i += 1) {
|
|
f = functions[i];
|
|
o.push('<li value=' +
|
|
f['(line)'] + '><tt>' + (f['(name)'] || '') + '</tt>');
|
|
s = [];
|
|
for (k in f) {
|
|
if (k.charAt(0) != '(') {
|
|
switch (f[k]) {
|
|
case 'parameter':
|
|
s.push('<tt>' + k + '</tt>');
|
|
break;
|
|
case 'parameter!':
|
|
s.push('<tt>' + k +
|
|
'</tt><small> (closure)</small>');
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
detail('Parameter');
|
|
s = [];
|
|
for (k in f) {
|
|
if (k.charAt(0) != '(') {
|
|
switch(f[k]) {
|
|
case 'var':
|
|
s.push('<tt>' + k +
|
|
'</tt><small> (unused)</small>');
|
|
break;
|
|
case 'var*':
|
|
s.push('<tt>' + k + '</tt>');
|
|
break;
|
|
case 'var!':
|
|
s.push('<tt>' + k +
|
|
'</tt><small> (closure)</small>');
|
|
break;
|
|
case 'var.':
|
|
s.push('<tt>' + k +
|
|
'</tt><small> (outer)</small>');
|
|
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('<tt>' + k + '</tt><small> (?)</small>');
|
|
} else {
|
|
s.push('<tt>' + k + '</tt>');
|
|
}
|
|
}
|
|
}
|
|
detail('Global');
|
|
s = [];
|
|
for (k in f) {
|
|
if (k.charAt(0) != '(' && f[k] === 'label') {
|
|
s.push(k);
|
|
}
|
|
}
|
|
detail('Label');
|
|
o.push('</li>');
|
|
}
|
|
if (functions.length) {
|
|
o.push('</ol>');
|
|
}
|
|
}
|
|
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();
|
|
}
|
|
|