6
Copyright (c) 2002 Douglas Crockford (www.JSLint.com)
8
Permission is hereby granted, free of charge, to any person obtaining a copy of
9
this software and associated documentation files (the "Software"), to deal in
10
the Software without restriction, including without limitation the rights to
11
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
12
of the Software, and to permit persons to whom the Software is furnished to do
13
so, subject to the following conditions:
15
The above copyright notice and this permission notice shall be included in all
16
copies or substantial portions of the Software.
18
The Software shall be used for Good, not Evil.
20
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
30
JSLINT is a global function. It takes two parameters.
32
var myResult = JSLINT(source, option);
34
The first parameter is either a string or an array of strings. If it is a
35
string, it will be split on '\n' or '\r'. If it is an array of strings, it
36
is assumed that each string represents one line. The source can be a
37
JavaScript text, or HTML text, or a Konfabulator text.
39
The second parameter is an optional object of options which control the
40
operation of JSLINT. Most of the options are booleans: They are all are
41
optional and have a default value of false.
43
If it checks out, JSLINT returns true. Otherwise, it returns false.
45
If false, you can inspect JSLINT.errors to find out the problems.
46
JSLINT.errors is an array of objects containing these members:
49
line : The line (relative to 0) at which the lint was found
50
character : The character (relative to 0) at which the lint was found
52
evidence : The text line in which the problem occurred
53
raw : The raw message before the details were inserted
60
If a fatal error was found, a null will be the last element of the
63
You can request a Function Report, which shows all of the functions
64
and the parameters and vars that they use. This can be used to find
65
implied global variables and other problems. The report is in HTML and
66
can be inserted in an HTML <body>.
68
var myReport = JSLINT.report(limited);
70
If limited is true, then the report will be limited to only errors.
73
/*jslint evil: true, nomen: false, onevar: false */
79
// We build the application inside a function so that we produce only a single
80
// global variable. The function will be invoked, its return value is the JSLINT
81
// application itself.
85
JSLINT = function () {
86
var adsafe_id, // The widget's ADsafe id.
87
adsafe_may, // The widget may load approved scripts.
88
adsafe_went, // ADSAFE.go has been called.
89
anonname, // The guessed name for anonymous functions.
90
approved, // ADsafe approved urls.
106
// These are members that should not be permitted in third party ads.
108
banned = { // the member names that ADsafe prohibits.
123
// These are the JSLint boolean options.
126
adsafe : true, // if ADsafe should be enforced
127
bitwise : true, // if bitwise operators should not be allowed
128
browser : true, // if the standard browser globals should be predefined
129
cap : true, // if upper case HTML should be allowed
130
css : true, // if CSS workarounds should be tolerated
131
debug : true, // if debugger statements should be allowed
132
eqeqeq : true, // if === should be required
133
evil : true, // if eval should be allowed
134
forin : true, // if for in statements must filter
135
fragment : true, // if HTML fragments should be allowed
136
laxbreak : true, // if line breaks should not be checked
137
nomen : true, // if names should be checked
138
on : true, // if HTML event handlers should be allowed
139
onevar : true, // if only one var statement per function should be allowed
140
passfail : true, // if the scan should stop on first error
141
plusplus : true, // if increment/decrement should not be allowed
142
regexp : true, // if the . should not be allowed in regexp literals
143
rhino : true, // if the Rhino environment globals should be predefined
144
undef : true, // if variables should be declared before used
145
safe : true, // if use of some browser features should be restricted
146
sidebar : true, // if the System object should be predefined
147
strict : true, // require the "use strict"; pragma
148
sub : true, // if all forms of subscript notation are tolerated
149
white : true, // if strict whitespace rules apply
150
widget : true // if the Yahoo Widgets globals should be predefined
153
// browser contains a set of global names which are commonly provided by a
154
// web browser environment.
159
clearInterval : true,
166
defaultStatus : true,
171
getComputedStyle: true,
205
XMLHttpRequest : true
213
"antiquewhite": true,
220
"blanchedalmond": true,
229
"cornflowerblue": true,
235
"darkgoldenrod": true,
240
"darkolivegreen": true,
245
"darkseagreen": true,
246
"darkslateblue": true,
247
"darkslategray": true,
248
"darkturquoise": true,
272
"lavenderblush": true,
274
"lemonchiffon": true,
278
"lightgoldenrodyellow": true,
282
"lightseagreen": true,
283
"lightskyblue": true,
284
"lightslategray": true,
285
"lightsteelblue": true,
292
"mediumaquamarine": true,
294
"mediumorchid": true,
295
"mediumpurple": true,
296
"mediumseagreen": true,
297
"mediumslateblue": true,
298
"mediumspringgreen": true,
299
"mediumturquoise": true,
300
"mediumvioletred": true,
301
"midnightblue": true,
313
"palegoldenrod": true,
315
"paleturquoise": true,
316
"palevioletred": true,
378
funct, // The current function
379
functions, // All of the functions
381
global, // The global scope
388
area: {empty: true, parent: ' map '},
390
base: {empty: true, parent: ' head '},
394
body: {parent: ' html noframes '},
397
canvas: {parent: ' body p div th td '},
398
caption: {parent: ' table '},
402
col: {empty: true, parent: ' table colgroup '},
403
colgroup: {parent: ' table '},
404
dd: {parent: ' dl '},
410
dt: {parent: ' dl '},
416
frame: {empty: true, parent: ' frameset '},
417
frameset: {parent: ' html frameset '},
424
head: {parent: ' html '},
430
input: {empty: true},
434
legend: {parent: ' fieldset '},
435
li: {parent: ' dir menu ol ul '},
436
link: {empty: true, parent: ' head '},
439
meta: {empty: true, parent: ' head noframes noscript '},
440
noframes: {parent: ' html body '},
441
noscript: {parent: ' body head noframes '},
444
optgroup: {parent: ' select '},
445
option: {parent: ' optgroup select '},
447
param: {empty: true, parent: ' applet object '},
451
script: {empty: true, parent: ' body div frame head iframe p pre span '},
456
style: {parent: ' head ', empty: true},
460
tbody: {parent: ' table '},
461
td: {parent: ' tr '},
463
tfoot: {parent: ' table '},
464
th: {parent: ' tr '},
465
thead: {parent: ' table '},
466
title: {parent: ' head '},
467
tr: {parent: ' table tbody thead tfoot '},
475
implied, // Implied globals
486
predefined, // Global variables defined by option
498
'first-letter' : true,
524
scope, // The current scope
533
// standard contains the global names that are provided by the
534
// ECMAScript standard.
541
decodeURIComponent : true,
543
encodeURIComponent : true,
557
ReferenceError : true,
576
NEGATIVE_INFINITY : true,
577
POSITIVE_INFINITY : true
586
// widget contains the global names which are provided to a Yahoo
587
// (fna Konfabulator) widget.
595
bytesToUIString : true,
602
convertPathToHFS : true,
603
convertPathToPlatform : true,
604
CustomAnimation : true,
606
FadeAnimation : true,
615
isApplicationRunning : true,
617
konfabulatorVersion : true,
620
MoveAnimation : true,
625
preferenceGroups : true,
632
resumeUpdates : true,
633
RotateAnimation : true,
635
runCommandInBg : true,
637
savePreferences : true,
640
showWidgetPreferences : true,
643
suppressUpdates : true,
655
XMLHttpRequest : true,
656
yahooCheckLogin : true,
661
// xmode is used to adapt to the exceptions in html parsing.
662
// It can have these states:
663
// false .js script file
674
// unsafe comment or string
675
ax = /@cc|<\/?|script|\]*s\]|<\s*!|</i,
677
cx = /[\u0000-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/,
679
tx = /^\s*([(){}\[.,:;'"~\?\]#@]|==?=?|\/(\*(global|extern|jslint|member|members)?|=|\/)?|\*[\/=]?|\+[+=]?|-[\-=]?|%=?|&[&=]?|\|[|=]?|>>?>?=?|<([\/=]|\!(\[|--)?|<=?)?|\^=?|\!=?=?|[a-zA-Z_$][a-zA-Z0-9_$]*|[0-9]+([xX][0-9a-fA-F]+|\.[0-9]*)?([eE][+\-]?[0-9]+)?)/,
681
hx = /^\s*(['"=>\/&#]|<[\/!]?|[a-zA-Z][a-zA-Z0-9_\-]*|--)/,
683
ox = /[>&]|<[\/!]?|--/,
687
ix = /^([a-zA-Z_$][a-zA-Z0-9_$]*)$/,
689
jx = /^(?:javascript|jscript|ecmascript|vbscript|mocha|livescript)\s*:/i,
691
ux = /&|\+|\u00AD|\.\.|\/\*|%[^;]|base64|url|expression|data|mailto/i,
693
sx = /^\s*([{:#*%.=,>+\[\]@()"';*]|[a-zA-Z0-9_][a-zA-Z0-9_\-]*|<\/|\/\*)/,
694
ssx = /^\s*([@#!"'};:\-\/%.=,+\[\]()*_]|[a-zA-Z][a-zA-Z0-9._\-]*|\d+(?:\.\d+)?|<\/)/,
705
if (typeof Object.create !== 'function') {
706
Object.create = function (o) {
711
Object.prototype.union = function (o) {
714
if (o.hasOwnProperty(n)) {
720
String.prototype.entityify = function () {
722
replace(/&/g, '&').
723
replace(/</g, '<').
724
replace(/>/g, '>');
727
String.prototype.isAlpha = function () {
728
return (this >= 'a' && this <= 'z\uffff') ||
729
(this >= 'A' && this <= 'Z\uffff');
733
String.prototype.isDigit = function () {
734
return (this >= '0' && this <= '9');
738
String.prototype.supplant = function (o) {
739
return this.replace(/\{([^{}]*)\}/g, function (a, b) {
741
return typeof r === 'string' || typeof r === 'number' ? r : a;
745
String.prototype.name = function () {
747
// If the string looks like an identifier, then we can return it as is.
748
// If the string contains no control characters, no quote characters, and no
749
// backslash characters, then we can simply slap some quotes around it.
750
// Otherwise we must also replace the offending characters with safe
757
if (/[&<"\/\\\x00-\x1f]/.test(this)) {
758
return '"' + this.replace(/[&<"\/\\\x00-\x1f]/g, function (a) {
765
Math.floor(c / 16).toString(16) +
766
(c % 16).toString(16);
769
return '"' + this + '"';
776
predefined.union(rhino);
778
if (option.browser || option.sidebar) {
779
predefined.union(browser);
781
if (option.sidebar) {
782
predefined.union(sidebar);
785
predefined.union(widget);
791
// Produce an error warning.
793
function quit(m, l, ch) {
798
message: m + " (" + Math.floor((l / lines.length) * 100) +
803
function warning(m, t, a, b, c, d) {
806
if (t.id === '(end)') { // `~
814
evidence: lines[l] || '',
822
w.reason = m.supplant(w);
823
JSLINT.errors.push(w);
824
if (option.passfail) {
825
quit('Stopping. ', l, ch);
828
if (warnings === 50) {
829
quit("Too many errors.", l, ch);
834
function warningAt(m, l, ch, a, b, c, d) {
841
function error(m, t, a, b, c, d) {
842
var w = warning(m, t, a, b, c, d);
843
quit("Stopping, unable to continue.", w.line, w.character);
846
function errorAt(m, l, ch, a, b, c, d) {
857
var lex = function lex() {
858
var character, from, line, s;
860
// Private lex methods
862
function nextLine() {
865
if (line >= lines.length) {
869
s = lines[line].replace(/\t/g, tab);
872
warningAt("Unsafe character.", line, at);
877
// Produce a token object. The token inherits from a syntax symbol.
879
function it(type, value) {
881
if (type === '(color)') {
883
} else if (type === '(punctuator)' ||
884
(type === '(identifier)' && syntax.hasOwnProperty(value))) {
887
// Mozilla bug workaround.
895
t = Object.create(t);
896
if (type === '(string)' || type === '(range)') {
897
if (jx.test(value)) {
898
warningAt("Script URL.", line, from);
901
if (type === '(identifier)') {
903
if (option.nomen && value.charAt(0) === '_') {
904
warningAt("Unexpected '_' in '{a}'.", line, from, value);
909
t.character = character;
912
if (i !== '(endline)') {
914
(('(,=:[!&|?{};'.indexOf(i.charAt(i.length - 1)) >= 0) ||
920
// Public lex methods
923
init: function (source) {
924
if (typeof source === 'string') {
926
replace(/\r\n/g, '\n').
927
replace(/\r/g, '\n').
937
range: function (begin, end) {
940
if (s.charAt(0) !== begin) {
941
errorAt("Expected '{a}' and instead saw '{b}'.",
942
line, character, begin, s.charAt(0));
950
errorAt("Missing '{a}'.", line, character, c);
955
return it('(range)', value);
960
warningAt("Unexpected '{a}'.", line, character, c);
967
// token -- this is called by advance to get the next token.
970
var b, c, captures, d, depth, high, i, l, low, q, t;
973
var r = x.exec(s), r1;
980
from = character - r1.length;
988
if (jsonmode && x !== '"') {
989
warningAt("Strings must use doublequote.",
993
if (xquote === x || (xmode === 'scriptstring' && !xquote)) {
994
return it('(punctuator)', x);
998
var i = parseInt(s.substr(j + 1, n), 16);
1000
if (i >= 32 && i <= 127 &&
1001
i !== 34 && i !== 92 && i !== 39) {
1002
warningAt("Unnecessary escapement.", line, character);
1005
c = String.fromCharCode(i);
1009
while (j >= s.length) {
1011
if (xmode !== 'html' || !nextLine()) {
1012
errorAt("Unclosed string.", line, from);
1018
s = s.substr(j + 1);
1019
return it('(string)', r, x);
1022
if (c === '\n' || c === '\r') {
1025
warningAt("Control character in string: {a}.",
1026
line, character + j, s.slice(0, j));
1027
} else if (c === xquote) {
1028
warningAt("Bad HTML string", line, character + j);
1029
} else if (c === '<') {
1030
if (option.safe && xmode === 'html') {
1031
warningAt("ADsafe string violation.",
1032
line, character + j);
1033
} else if (s.charAt(j + 1) === '/' && (xmode || option.safe)) {
1034
warningAt("Expected '<\\/' and instead saw '</'.", line, character);
1035
} else if (s.charAt(j + 1) === '!' && (xmode || option.safe)) {
1036
warningAt("Unexpected '<!' in a string.", line, character);
1038
} else if (c === '\\') {
1039
if (xmode === 'html') {
1041
warningAt("ADsafe string violation.",
1042
line, character + j);
1044
} else if (xmode === 'styleproperty') {
1049
warningAt("Escapement in style string.",
1050
line, character + j);
1058
warningAt("Bad HTML string", line,
1089
warningAt("Avoid \\x-.", line, character);
1094
warningAt("Bad escapement.", line, character);
1106
return it(nextLine() ? '(endline)' : '(end)', '');
1108
while (xmode === 'outer') {
1118
return it('(end)', '');
1122
t = match(rx[xmode] || tx);
1124
if (xmode === 'html') {
1125
return it('(error)', s.charAt(0));
1129
while (s && s < '!') {
1133
errorAt("Unexpected '{a}'.",
1134
line, character, s.substr(0, 1));
1141
if (c.isAlpha() || c === '_' || c === '$') {
1142
return it('(identifier)', t);
1148
if (xmode !== 'style' && !isFinite(Number(t))) {
1149
warningAt("Bad number '{a}'.",
1150
line, character, t);
1152
if (xmode !== 'styleproperty' && s.substr(0, 1).isAlpha()) {
1153
warningAt("Missing space after '{a}'.",
1154
line, character, t);
1159
if (token.id !== '.' && xmode !== 'styleproperty') {
1160
warningAt("Don't use extra leading zeros '{a}'.",
1161
line, character, t);
1163
} else if (jsonmode && (d === 'x' || d === 'X')) {
1164
warningAt("Avoid 0x-. '{a}'.",
1165
line, character, t);
1168
if (t.substr(t.length - 1) === '.') {
1170
"A trailing decimal point can be confused with a dot '{a}'.",
1171
line, character, t);
1173
return it('(number)', t);
1186
if (src || (xmode && xmode !== 'script')) {
1187
warningAt("Unexpected comment.", line, character);
1188
} else if (xmode === 'script' && /<\s*\//i.test(s)) {
1189
warningAt("Unexpected <\/ in comment.", line, character);
1190
} else if ((option.safe || xmode === 'script') && ax.test(s)) {
1191
warningAt("Dangerous comment.", line, character);
1194
token.comment = true;
1200
if (src || (xmode && xmode !== 'script' && xmode !== 'style' && xmode !== 'styleproperty')) {
1201
warningAt("Unexpected comment.", line, character);
1203
if (option.safe && ax.test(s)) {
1204
warningAt("ADsafe comment violation.", line, character);
1212
errorAt("Unclosed comment.", line, character);
1214
if (option.safe && ax.test(s)) {
1215
warningAt("ADsafe comment violation.", line, character);
1220
if (s.substr(i, 1) === '/') {
1221
errorAt("Nested comment.", line, character);
1223
s = s.substr(i + 2);
1224
token.comment = true;
1227
// /*global /*extern /*members /*jslint */
1239
character: character,
1257
errorAt("Unclosed regular expression.", line, from);
1261
warningAt("Unescaped '{a}'.", line, from + l, '/');
1263
c = s.substr(0, l - 1);
1269
while (q[s.charAt(l)] === true) {
1270
q[s.charAt(l)] = false;
1275
return it('(regex)', c);
1279
warningAt("Unexpected control character in regular expression.", line, from + l);
1280
} else if (c === '<') {
1281
warningAt("Unexpected escaped character '{a}' in regular expression.", line, from + l, c);
1288
if (s.charAt(l) === '?') {
1290
switch (s.charAt(l)) {
1297
warningAt("Expected '{a}' and instead saw '{b}'.", line, from + l, ':', s.charAt(l));
1305
warningAt("Unescaped '{a}'.", line, from + l, ')');
1312
while (s.charAt(l) === ' ') {
1317
warningAt("Spaces are hard to count. Use {{a}}.", line, from + l, q);
1321
if (s.charAt(l) === '^') {
1331
warningAt("Unescaped '{a}'.", line, from + l, c);
1338
warningAt("Unescaped '{a}'.", line, from + l, '-');
1344
warningAt("Unescaped '{a}'.", line, from + l - 1, '-');
1350
warningAt("Unexpected control character in regular expression.", line, from + l);
1351
} else if (c === '<') {
1352
warningAt("Unexpected escaped character '{a}' in regular expression.", line, from + l, c);
1358
warningAt("Unescaped '{a}'.", line, from + l - 1, '/');
1362
if (xmode === 'script') {
1364
if (c === '!' || c === '/') {
1365
warningAt("HTML confusion in regular expression '<{a}'.", line, from + l, c);
1376
if (option.regexp) {
1377
warningAt("Unexpected '{a}'.", line, from + l, c);
1386
warningAt("Unescaped '{a}'.", line, from + l, c);
1389
if (xmode === 'script') {
1391
if (c === '!' || c === '/') {
1392
warningAt("HTML confusion in regular expression '<{a}'.", line, from + l, c);
1397
switch (s.charAt(l)) {
1402
if (s.charAt(l) === '?') {
1409
if (c < '0' || c > '9') {
1410
warningAt("Expected a number and instead saw '{a}'.", line, from + l, c);
1416
if (c < '0' || c > '9') {
1420
low = +c + (low * 10);
1427
if (c >= '0' && c <= '9') {
1432
if (c < '0' || c > '9') {
1436
high = +c + (high * 10);
1440
if (s.charAt(l) !== '}') {
1441
warningAt("Expected '{a}' and instead saw '{b}'.", line, from + l, '}', c);
1445
if (s.charAt(l) === '?') {
1449
warningAt("'{a}' should not be greater than '{b}'.", line, from + l, low, high);
1454
c = s.substr(0, l - 1);
1457
return it('(regex)', c);
1459
return it('(punctuator)', t);
1464
if (xmode === 'html' || xmode === 'styleproperty') {
1467
if ((c < '0' || c > '9') &&
1468
(c < 'a' || c > 'f') &&
1469
(c < 'A' || c > 'F')) {
1476
if (t.length !== 4 && t.length !== 7) {
1477
warningAt("Bad hex color '{a}'.", line,
1480
return it('(color)', t);
1482
return it('(punctuator)', t);
1484
if (xmode === 'outer' && c === '&') {
1494
if (!((c >= '0' && c <= '9') ||
1495
(c >= 'a' && c <= 'z') ||
1497
errorAt("Bad entity", line, from + l,
1503
return it('(punctuator)', t);
1512
function addlabel(t, type) {
1514
if (t === 'hasOwnProperty') {
1515
error("'hasOwnProperty' is a really bad name.");
1517
if (option.safe && funct['(global)']) {
1518
warning('ADsafe global: ' + t + '.', token);
1521
// Define t in the current function in the current scope.
1523
if (funct.hasOwnProperty(t)) {
1524
warning(funct[t] === true ?
1525
"'{a}' was used before it was defined." :
1526
"'{a}' is already defined.",
1530
if (type === 'label') {
1532
} else if (funct['(global)']) {
1534
if (implied.hasOwnProperty(t)) {
1535
warning("'{a}' was used before it was defined.", nexttoken, t);
1539
funct['(scope)'][t] = funct;
1544
function doOption() {
1545
var b, obj, filter, o = nexttoken.value, t, v;
1548
error("Unbegun comment.");
1553
warning("ADsafe restriction.");
1567
warning("ADsafe restriction.");
1570
filter = boolOptions;
1577
while (t.id === '(endline)') {
1580
if (t.type === 'special' && t.value === '*/') {
1583
if (t.type !== '(string)' && t.type !== '(identifier)' &&
1584
o !== '/*members') {
1585
error("Bad option.", t);
1588
if (filter[t.value] !== true) {
1589
error("Bad option.", t);
1593
error("Expected '{a}' and instead saw '{b}'.",
1597
if (v.value === 'true') {
1599
} else if (v.value === 'false') {
1602
error("Expected '{a}' and instead saw '{b}'.",
1603
t, 'true', t.value);
1616
// We need a peek function. If it has an argument, it peeks that much farther
1617
// ahead. It is used to distinguish
1618
// for ( var i in ...
1620
// for ( var i = ...
1623
var i = p || 0, j = 0, t;
1628
t = lookahead[j] = lex.token();
1637
// Produce the next token. It looks for programming errors.
1639
function advance(id, t) {
1643
if (nexttoken.id === '.') {
1645
"A dot following a number can be confused with a decimal point.", token);
1649
if (nexttoken.id === '-' || nexttoken.id === '--') {
1650
warning("Confusing minusses.");
1654
if (nexttoken.id === '+' || nexttoken.id === '++') {
1655
warning("Confusing plusses.");
1659
if (token.type === '(string)' || token.identifier) {
1660
anonname = token.value;
1663
if (id && nexttoken.id !== id) {
1665
if (nexttoken.id === '(end)') {
1666
warning("Unmatched '{a}'.", t, t.id);
1668
warning("Expected '{a}' to match '{b}' from line {c} and instead saw '{d}'.",
1669
nexttoken, id, t.id, t.line + 1, nexttoken.value);
1671
} else if (nexttoken.type !== '(identifier)' ||
1672
nexttoken.value !== id) {
1673
warning("Expected '{a}' and instead saw '{b}'.",
1674
nexttoken, id, nexttoken.value);
1680
nexttoken = lookahead.shift() || lex.token();
1681
if (nexttoken.id === '(end)' || nexttoken.id === '(error)') {
1684
if (nexttoken.type === 'special') {
1687
if (nexttoken.id !== '(endline)') {
1690
l = !xmode && !option.laxbreak &&
1691
(token.type === '(string)' || token.type === '(number)' ||
1692
token.type === '(identifier)' || badbreak[token.id]);
1696
switch (nexttoken.id) {
1709
warning("Line breaking error '{a}'.", token, ')');
1713
warning("Line breaking error '{a}'.",
1714
token, token.value);
1720
// This is the heart of JSLINT, the Pratt parser. In addition to parsing, it
1721
// is looking for ad hoc lint patterns. We add to Pratt's model .fud, which is
1722
// like nud except that it is only used on the first token of a statement.
1723
// Having .fud makes it much easier to define JavaScript. I retained Pratt's
1726
// .nud Null denotation
1727
// .fud First null denotation
1728
// .led Left denotation
1729
// lbp Left binding power
1730
// rbp Right binding power
1732
// They are key to the parsing method called Top Down Operator Precedence.
1734
function parse(rbp, initial) {
1736
if (nexttoken.id === '(end)') {
1737
error("Unexpected early end of program.", token);
1740
if (option.safe && predefined[token.value] === true &&
1741
(nexttoken.id !== '(' && nexttoken.id !== '.')) {
1742
warning('ADsafe violation.', token);
1745
anonname = 'anonymous';
1746
funct['(verb)'] = token.value;
1748
if (initial === true && token.fud) {
1755
if (nexttoken.type === '(number)' && token.id === '.') {
1757
"A leading decimal point can be confused with a dot: '.{a}'.",
1758
token, nexttoken.value);
1762
error("Expected an identifier and instead saw '{a}'.",
1766
while (rbp < nexttoken.lbp) {
1770
left = token.led(left);
1772
error("Expected an operator and instead saw '{a}'.",
1776
if (initial && !o) {
1778
"Expected an assignment or function call and instead saw an expression.",
1782
if (!option.evil && left && left.value === 'eval') {
1783
warning("eval is evil.", left);
1789
// Functions for conformance of style.
1791
function adjacent(left, right) {
1792
left = left || token;
1793
right = right || nexttoken;
1794
if (option.white || xmode === 'styleproperty' || xmode === 'style') {
1795
if (left.character !== right.from && left.line === right.line) {
1796
warning("Unexpected space after '{a}'.",
1797
nexttoken, left.value);
1803
function nospace(left, right) {
1804
left = left || token;
1805
right = right || nexttoken;
1806
if (option.white && !left.comment) {
1807
if (left.line === right.line) {
1808
adjacent(left, right);
1814
function nonadjacent(left, right) {
1815
left = left || token;
1816
right = right || nexttoken;
1818
if (left.character === right.from) {
1819
warning("Missing space after '{a}'.",
1820
nexttoken, left.value);
1825
function indentation(bias) {
1827
if (option.white && nexttoken.id !== '(end)') {
1828
i = indent + (bias || 0);
1829
if (nexttoken.from !== i) {
1830
warning("Expected '{a}' to have an indentation of {b} instead of {c}.",
1831
nexttoken, nexttoken.value, i, nexttoken.from);
1836
function nolinebreak(t) {
1837
if (t.line !== nexttoken.line) {
1838
warning("Line breaking error '{a}'.", t, t.id);
1843
// Parasitic constructors for making the symbols that will be inherited by
1846
function symbol(s, p) {
1848
if (!x || typeof x !== 'object') {
1860
return symbol(s, 0);
1864
function stmt(s, f) {
1866
x.identifier = x.reserved = true;
1872
function blockstmt(s, f) {
1879
function reserveName(x) {
1880
var c = x.id.charAt(0);
1881
if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
1882
x.identifier = x.reserved = true;
1888
function prefix(s, f) {
1889
var x = symbol(s, 150);
1891
x.nud = (typeof f === 'function') ? f : function () {
1892
if (option.plusplus && (this.id === '++' || this.id === '--')) {
1893
warning("Unexpected use of '{a}'.", this, this.id);
1895
this.right = parse(150);
1896
this.arity = 'unary';
1903
function type(s, f) {
1911
function reserve(s, f) {
1913
x.identifier = x.reserved = true;
1918
function reservevar(s) {
1919
return reserve(s, function () {
1920
if (this.id === 'this') {
1922
warning("ADsafe violation.", this);
1930
function infix(s, f, p) {
1931
var x = symbol(s, p);
1933
x.led = (typeof f === 'function') ? f : function (left) {
1934
nonadjacent(prevtoken, token);
1935
nonadjacent(token, nexttoken);
1937
this.right = parse(p);
1944
function relation(s, f) {
1945
var x = symbol(s, 100);
1946
x.led = function (left) {
1947
nonadjacent(prevtoken, token);
1948
nonadjacent(token, nexttoken);
1949
var right = parse(100);
1950
if ((left && left.id === 'NaN') || (right && right.id === 'NaN')) {
1951
warning("Use the isNaN function to compare with NaN.", this);
1953
f.apply(this, [left, right]);
1963
function isPoorRelation(node) {
1964
var n = +node.value; // Safari workaround
1965
return (node.type === '(number)' && !n) ||
1966
(node.type === '(string)' && !node.value) ||
1967
node.type === 'true' ||
1968
node.type === 'false' ||
1969
node.type === 'undefined' ||
1970
node.type === 'null';
1974
function assignop(s, f) {
1975
symbol(s, 20).exps = true;
1976
return infix(s, function (left) {
1979
nonadjacent(prevtoken, token);
1980
nonadjacent(token, nexttoken);
1984
if (predefined[l.value] === true) {
1985
warning('ADsafe violation.', l);
1991
if (left.id === '.' || left.id === '[') {
1992
if (left.left.value === 'arguments') {
1993
warning('Bad assignment.', this);
1995
this.right = parse(19);
1997
} else if (left.identifier && !left.reserved) {
1998
this.right = parse(19);
2001
if (left === syntax['function']) {
2003
"Expected an identifier in an assignment and instead saw a function invocation.",
2007
error("Bad assignment.", this);
2011
function bitwise(s, f, p) {
2012
var x = symbol(s, p);
2014
x.led = (typeof f === 'function') ? f : function (left) {
2015
if (option.bitwise) {
2016
warning("Unexpected use of '{a}'.", this, this.id);
2018
nonadjacent(prevtoken, token);
2019
nonadjacent(token, nexttoken);
2021
this.right = parse(p);
2027
function bitwiseassignop(s) {
2028
symbol(s, 20).exps = true;
2029
return infix(s, function (left) {
2030
if (option.bitwise) {
2031
warning("Unexpected use of '{a}'.", this, this.id);
2033
nonadjacent(prevtoken, token);
2034
nonadjacent(token, nexttoken);
2036
if (left.id === '.' || left.id === '[' ||
2037
(left.identifier && !left.reserved)) {
2041
if (left === syntax['function']) {
2043
"Expected an identifier in an assignment, and instead saw a function invocation.",
2047
error("Bad assignment.", this);
2052
function suffix(s, f) {
2053
var x = symbol(s, 150);
2054
x.led = function (left) {
2055
if (option.plusplus) {
2056
warning("Unexpected use of '{a}'.", this, this.id);
2065
function optionalidentifier() {
2066
if (nexttoken.reserved) {
2067
warning("Expected an identifier and instead saw '{a}' (a reserved word).",
2068
nexttoken, nexttoken.id);
2070
if (nexttoken.identifier) {
2077
function identifier() {
2078
var i = optionalidentifier();
2082
if (token.id === 'function' && nexttoken.id === '(') {
2083
warning("Missing name in function statement.");
2085
error("Expected an identifier and instead saw '{a}'.",
2086
nexttoken, nexttoken.value);
2090
function reachable(s) {
2092
if (nexttoken.id !== ';' || noreach) {
2100
if (t.id !== '(endline)') {
2101
if (t.id === 'function') {
2103
"Inner functions should be listed at the top of the outer function.", t);
2106
warning("Unreachable '{a}' after '{b}'.", t, t.value, s);
2114
function statement(noindent) {
2115
var i = indent, r, s = scope, t = nexttoken;
2117
// We don't like the empty statement.
2120
warning("Unnecessary semicolon.", t);
2125
// Is this a labelled statement?
2127
if (t.identifier && !t.reserved && peek().id === ':') {
2130
scope = Object.create(s);
2131
addlabel(t.value, 'label');
2132
if (!nexttoken.labelled) {
2133
warning("Label '{a}' on {b} statement.",
2134
nexttoken, t.value, nexttoken.value);
2136
if (jx.test(t.value + ':')) {
2137
warning("Label '{a}' looks like a javascript url.",
2140
nexttoken.label = t.value;
2144
// Parse the statement.
2151
// Look for the final semicolon.
2154
if (nexttoken.id !== ';') {
2155
warningAt("Missing semicolon.", token.line,
2156
token.from + token.value.length);
2158
adjacent(token, nexttoken);
2160
nonadjacent(token, nexttoken);
2164
// Restore the indentation.
2172
function statements(begin) {
2175
if (option.strict && nexttoken.type !== '(string)') {
2176
warning('Missing "use strict" statement.',
2179
if (nexttoken.type === '(string)' &&
2180
nexttoken.value === 'use strict') {
2185
if (option.adsafe) {
2189
if (nexttoken.value !== 'ADSAFE' ||
2190
peek(0).id !== '.' ||
2191
(peek(1).value !== 'id' &&
2192
peek(1).value !== 'go')) {
2193
error('ADsafe violation: Missing ADSAFE.id or ADSAFE.go.',
2197
if (nexttoken.value === 'ADSAFE' &&
2198
peek(0).id === '.' &&
2199
peek(1).value === 'id') {
2201
error('ADsafe violation.', nexttoken);
2207
if (nexttoken.value !== adsafe_id) {
2208
error('ADsafe violation: id does not match.', nexttoken);
2210
advance('(string)');
2217
if (nexttoken.value === 'ADSAFE') {
2222
advance('(string)');
2229
error("ADsafe lib violation.");
2233
while (!nexttoken.reach && nexttoken.id !== '(end)') {
2234
if (nexttoken.id === ';') {
2235
warning("Unnecessary semicolon.");
2238
a.push(statement());
2246
var a, b = inblock, s = scope, t;
2249
scope = Object.create(scope);
2251
nonadjacent(token, nexttoken);
2253
if (nexttoken.id === '{') {
2255
if (nexttoken.id !== '}' || token.line !== nexttoken.line) {
2256
indent += option.indent;
2257
if (!f && nexttoken.from === indent + option.indent) {
2258
indent += option.indent;
2261
indent -= option.indent;
2266
warning("Expected '{a}' and instead saw '{b}'.",
2267
nexttoken, '{', nexttoken.value);
2272
funct['(verb)'] = null;
2279
// An identity function, used by string and number tokens.
2281
function idValue() {
2286
function countMember(m) {
2287
if (membersOnly && membersOnly[m] !== true) {
2288
warning("Unexpected /*member '{a}'.", nexttoken, m);
2290
if (typeof member[m] === 'number') {
2297
function note_implied(token) {
2298
var name = token.value, line = token.line + 1, a = implied[name];
2302
} else if (a[a.length - 1] !== line) {
2310
function cssName() {
2311
if (nexttoken.identifier) {
2317
function cssNumber() {
2318
if (nexttoken.id === '-') {
2320
advance('(number)');
2322
if (nexttoken.type === '(number)') {
2328
function cssString() {
2329
if (nexttoken.type === '(string)') {
2335
function cssColor() {
2337
if (nexttoken.identifier) {
2338
if (nexttoken.value === 'rgb') {
2341
for (i = 0; i < 3; i += 1) {
2342
number = nexttoken.value;
2343
if (nexttoken.type !== '(number)' || number < 0) {
2344
warning("Expected a positive number and instead saw '{a}'",
2349
if (nexttoken.id === '%') {
2352
warning("Expected a percentage and instead saw '{a}'",
2357
warning("Expected a small number and instead saw '{a}'",
2365
} else if (cssColorData[nexttoken.value] === true) {
2369
} else if (nexttoken.type === '(color)') {
2376
function cssLength() {
2377
if (nexttoken.id === '-') {
2381
if (nexttoken.type === '(number)') {
2383
if (nexttoken.type !== '(string)' &&
2384
cssLengthData[nexttoken.value] === true) {
2387
} else if (+token.value !== 0) {
2388
warning("Expected a linear unit and instead saw '{a}'.",
2389
nexttoken, nexttoken.value);
2396
function cssLineHeight() {
2397
if (nexttoken.id === '-') {
2401
if (nexttoken.type === '(number)') {
2403
if (nexttoken.type !== '(string)' &&
2404
cssLengthData[nexttoken.value] === true) {
2413
function cssWidth() {
2414
if (nexttoken.identifier) {
2415
switch (nexttoken.value) {
2427
function cssMargin() {
2428
if (nexttoken.identifier) {
2429
if (nexttoken.value === 'auto') {
2438
function cssAttr() {
2439
if (nexttoken.identifier && nexttoken.value === 'attr') {
2442
if (!nexttoken.identifier) {
2443
warning("Expected a name and instead saw '{a}'.",
2444
nexttoken, nexttoken.value);
2453
function cssCommaList() {
2454
while (nexttoken.id !== ';') {
2455
if (!cssName() && !cssString()) {
2456
warning("Expected a name and instead saw '{a}'.",
2457
nexttoken, nexttoken.value);
2459
if (nexttoken.id !== ',') {
2466
function cssCounter() {
2467
if (nexttoken.identifier && nexttoken.value === 'counter') {
2470
if (!nexttoken.identifier) {
2473
if (nexttoken.id === ',') {
2475
if (nexttoken.type !== '(string)') {
2476
warning("Expected a string and instead saw '{a}'.",
2477
nexttoken, nexttoken.value);
2484
if (nexttoken.identifier && nexttoken.value === 'counters') {
2487
if (!nexttoken.identifier) {
2488
warning("Expected a name and instead saw '{a}'.",
2489
nexttoken, nexttoken.value);
2492
if (nexttoken.id === ',') {
2494
if (nexttoken.type !== '(string)') {
2495
warning("Expected a string and instead saw '{a}'.",
2496
nexttoken, nexttoken.value);
2500
if (nexttoken.id === ',') {
2502
if (nexttoken.type !== '(string)') {
2503
warning("Expected a string and instead saw '{a}'.",
2504
nexttoken, nexttoken.value);
2515
function cssShape() {
2517
if (nexttoken.identifier && nexttoken.value === 'rect') {
2520
for (i = 0; i < 4; i += 1) {
2522
warning("Expected a number and instead saw '{a}'.",
2523
nexttoken, nexttoken.value);
2535
if (nexttoken.identifier && nexttoken.value === 'url') {
2536
nexttoken = lex.range('(', ')');
2537
url = nexttoken.value;
2539
if (option.safe && ux.test(url)) {
2540
error("ADsafe URL violation.");
2548
cssAny = [cssUrl, function () {
2550
if (nexttoken.identifier) {
2551
switch (nexttoken.value.toLowerCase()) {
2556
warning("Unexpected expression '{a}'.",
2557
nexttoken, nexttoken.value);
2564
if (nexttoken.id === ';' || nexttoken.id === '!' ||
2565
nexttoken.id === '(end)' || nexttoken.id === '}') {
2574
'none', 'hidden', 'dotted', 'dashed', 'solid', 'double', 'ridge',
2578
cssAttributeData = {
2580
true, 'background-attachment', 'background-color',
2581
'background-image', 'background-position', 'background-repeat'
2583
'background-attachment': ['scroll', 'fixed'],
2584
'background-color': ['transparent', cssColor],
2585
'background-image': ['none', cssUrl],
2586
'background-position': [
2587
2, [cssLength, 'top', 'bottom', 'left', 'right', 'center']
2589
'background-repeat': [
2590
'repeat', 'repeat-x', 'repeat-y', 'no-repeat'
2592
'border': [true, 'border-color', 'border-style', 'border-width'],
2593
'border-bottom': [true, 'border-bottom-color', 'border-bottom-style', 'border-bottom-width'],
2594
'border-bottom-color': cssColor,
2595
'border-bottom-style': cssBorderStyle,
2596
'border-bottom-width': cssWidth,
2597
'border-collapse': ['collapse', 'separate'],
2598
'border-color': ['transparent', 4, cssColor],
2600
true, 'border-left-color', 'border-left-style', 'border-left-width'
2602
'border-left-color': cssColor,
2603
'border-left-style': cssBorderStyle,
2604
'border-left-width': cssWidth,
2606
true, 'border-right-color', 'border-right-style', 'border-right-width'
2608
'border-right-color': cssColor,
2609
'border-right-style': cssBorderStyle,
2610
'border-right-width': cssWidth,
2611
'border-spacing': [2, cssLength],
2612
'border-style': [4, cssBorderStyle],
2614
true, 'border-top-color', 'border-top-style', 'border-top-width'
2616
'border-top-color': cssColor,
2617
'border-top-style': cssBorderStyle,
2618
'border-top-width': cssWidth,
2619
'border-width': [4, cssWidth],
2620
bottom: [cssLength, 'auto'],
2621
'caption-side' : ['bottom', 'left', 'right', 'top'],
2622
clear: ['both', 'left', 'none', 'right'],
2623
clip: [cssShape, 'auto'],
2626
'open-quote', 'close-quote', 'no-open-quote', 'no-close-quote',
2627
cssString, cssUrl, cssCounter, cssAttr
2629
'counter-increment': [
2636
cssUrl, 'auto', 'crosshair', 'default', 'e-resize', 'help', 'move',
2637
'n-resize', 'ne-resize', 'nw-resize', 'pointer', 's-resize',
2638
'se-resize', 'sw-resize', 'w-resize', 'text', 'wait'
2640
direction: ['ltr', 'rtl'],
2642
'block', 'compact', 'inline', 'inline-block', 'inline-table',
2643
'list-item', 'marker', 'none', 'run-in', 'table', 'table-caption',
2644
'table-column', 'table-column-group', 'table-footer-group',
2645
'table-header-group', 'table-row', 'table-row-group'
2647
'empty-cells': ['show', 'hide'],
2648
'float': ['left', 'none', 'right'],
2650
'caption', 'icon', 'menu', 'message-box', 'small-caption', 'status-bar',
2651
true, 'font-size', 'font-style', 'font-weight', 'font-family'
2653
'font-family': cssCommaList,
2655
'xx-small', 'x-small', 'small', 'medium', 'large', 'x-large',
2656
'xx-large', 'larger', 'smaller', cssLength
2658
'font-size-adjust': ['none', cssNumber],
2660
'normal', 'wider', 'narrower', 'ultra-condensed',
2661
'extra-condensed', 'condensed', 'semi-condensed',
2662
'semi-expanded', 'expanded', 'extra-expanded'
2665
'normal', 'italic', 'oblique'
2668
'normal', 'small-caps'
2671
'normal', 'bold', 'bolder', 'lighter', cssNumber
2673
height: [cssLength, 'auto'],
2674
left: [cssLength, 'auto'],
2675
'letter-spacing': ['normal', cssLength],
2676
'line-height': ['normal', cssLineHeight],
2678
true, 'list-style-image', 'list-style-position', 'list-style-type'
2680
'list-style-image': ['none', cssUrl],
2681
'list-style-position': ['inside', 'outside'],
2682
'list-style-type': [
2683
'circle', 'disc', 'square', 'decimal', 'decimal-leading-zero',
2684
'lower-roman', 'upper-roman', 'lower-greek', 'lower-alpha',
2685
'lower-latin', 'upper-alpha', 'upper-latin', 'hebrew', 'katakana',
2686
'hiragana-iroha', 'katakana-oroha', 'none'
2688
margin: [4, cssMargin],
2689
'margin-bottom': cssMargin,
2690
'margin-left': cssMargin,
2691
'margin-right': cssMargin,
2692
'margin-top': cssMargin,
2693
'marker-offset': [cssLength, 'auto'],
2694
'max-height': [cssLength, 'none'],
2695
'max-width': [cssLength, 'none'],
2696
'min-height': cssLength,
2697
'min-width': cssLength,
2699
outline: [true, 'outline-color', 'outline-style', 'outline-width'],
2700
'outline-color': ['invert', cssColor],
2702
'dashed', 'dotted', 'double', 'groove', 'inset', 'none',
2703
'outset', 'ridge', 'solid'
2705
'outline-width': cssWidth,
2706
overflow: ['auto', 'hidden', 'scroll', 'visible'],
2707
padding: [4, cssLength],
2708
'padding-bottom': cssLength,
2709
'padding-left': cssLength,
2710
'padding-right': cssLength,
2711
'padding-top': cssLength,
2712
position: ['absolute', 'fixed', 'relative', 'static'],
2713
quotes: [8, cssString],
2714
right: [cssLength, 'auto'],
2715
'table-layout': ['auto', 'fixed'],
2716
'text-align': ['center', 'justify', 'left', 'right'],
2717
'text-decoration': ['none', 'underline', 'overline', 'line-through', 'blink'],
2718
'text-indent': cssLength,
2719
'text-shadow': ['none', 4, [cssColor, cssLength]],
2720
'text-transform': ['capitalize', 'uppercase', 'lowercase', 'none'],
2721
top: [cssLength, 'auto'],
2722
'unicode-bidi': ['normal', 'embed', 'bidi-override'],
2724
'baseline', 'bottom', 'sub', 'super', 'top', 'text-top', 'middle',
2725
'text-bottom', cssLength
2727
visibility: ['visible', 'hidden', 'collapse'],
2728
'white-space': ['normal', 'pre', 'nowrap'],
2729
width: [cssLength, 'auto'],
2730
'word-spacing': ['normal', cssLength],
2731
'z-index': ['auto', cssNumber]
2734
function styleAttribute() {
2736
while (nexttoken.id === '*' || nexttoken.id === '#' || nexttoken.value === '_') {
2738
warning("Unexpected '{a}'.", nexttoken, nexttoken.value);
2742
if (nexttoken.id === '-') {
2744
warning("Unexpected '{a}'.", nexttoken, nexttoken.value);
2747
if (!nexttoken.identifier) {
2748
warning("Expected a non-standard style attribute and instead saw '{a}'.",
2749
nexttoken, nexttoken.value);
2754
if (!nexttoken.identifier) {
2755
warning("Excepted a style attribute, and instead saw '{a}'.",
2756
nexttoken, nexttoken.value);
2758
if (cssAttributeData.hasOwnProperty(nexttoken.value)) {
2759
v = cssAttributeData[nexttoken.value];
2763
warning("Unrecognized style attribute '{a}'.",
2764
nexttoken, nexttoken.value);
2773
function styleValue(v) {
2785
if (nexttoken.identifier && nexttoken.value === v) {
2792
if (i >= v.length) {
2799
} else if (typeof vi === 'number') {
2808
if (styleValue(vi)) {
2823
for (i = start; i < v.length; i += 1) {
2825
if (styleValue(cssAttributeData[v[i]])) {
2839
function substyle() {
2842
if (nexttoken.id === '}' || nexttoken.id === '(end)' ||
2843
xquote && nexttoken.id === xquote) {
2846
while (nexttoken.id === ';') {
2847
warning("Misplaced ';'.");
2850
v = styleAttribute();
2852
if (nexttoken.identifier && nexttoken.value === 'inherit') {
2857
while (nexttoken.id !== ';' && nexttoken.id !== '!' &&
2858
nexttoken.id !== '}' && nexttoken.id !== '(end)' &&
2859
nexttoken.id !== xquote) {
2860
warning("Unexpected token '{a}'.", nexttoken, nexttoken.value);
2863
if (nexttoken.id === '!') {
2866
if (nexttoken.identifier && nexttoken.value === 'important') {
2869
warning("Expected '{a}' and instead saw '{b}'.",
2870
nexttoken, 'important', nexttoken.value);
2873
if (nexttoken.id === '}' || nexttoken.id === xquote) {
2874
warning("Missing '{a}'.", nexttoken, ';');
2881
function stylePattern() {
2883
if (nexttoken.id === '{') {
2884
warning("Expected a style pattern, and instead saw '{a}'.", nexttoken,
2886
} else if (nexttoken.id === '@') {
2888
name = nexttoken.value;
2889
if (nexttoken.identifier && atrule[name] === true) {
2893
warning("Expected an at-rule, and instead saw @{a}.", nexttoken, name);
2896
if (nexttoken.identifier) {
2897
if (!htmltag.hasOwnProperty(nexttoken.value)) {
2898
warning("Expected a tagName, and instead saw {a}.",
2899
nexttoken, nexttoken.value);
2903
switch (nexttoken.id) {
2907
if (!nexttoken.identifier ||
2908
!htmltag.hasOwnProperty(nexttoken.value)) {
2909
warning("Expected a tagName, and instead saw {a}.",
2910
nexttoken, nexttoken.value);
2916
if (pseudorule[nexttoken.value] !== true) {
2917
warning("Expected a pseudo, and instead saw :{a}.",
2918
nexttoken, nexttoken.value);
2921
if (nexttoken.value === 'lang') {
2923
if (!nexttoken.identifier) {
2924
warning("Expected a lang code, and instead saw :{a}.",
2925
nexttoken, nexttoken.value);
2932
if (!nexttoken.identifier) {
2933
warning("Expected an id, and instead saw #{a}.",
2934
nexttoken, nexttoken.value);
2943
if (!nexttoken.identifier) {
2944
warning("Expected a class, and instead saw #.{a}.",
2945
nexttoken, nexttoken.value);
2951
if (!nexttoken.identifier) {
2952
warning("Expected an attribute, and instead saw [{a}].",
2953
nexttoken, nexttoken.value);
2956
if (nexttoken.id === '=' || nexttoken.id === '~=' ||
2957
nexttoken.id === '|=') {
2959
if (nexttoken.type !== '(string)') {
2960
warning("Expected a string, and instead saw {a}.",
2961
nexttoken, nexttoken.value);
2968
error("Expected a CSS selector, and instead saw {a}.",
2969
nexttoken, nexttoken.value);
2972
if (nexttoken.id === '</' || nexttoken.id === '{' ||
2973
nexttoken.id === '(end)') {
2976
if (nexttoken.id === ',') {
2983
while (nexttoken.id !== '</' && nexttoken.id !== '(end)') {
2985
xmode = 'styleproperty';
2986
if (nexttoken.id === ';') {
3000
function doBegin(n) {
3001
if (n !== 'html' && !option.fragment) {
3002
if (n === 'div' && option.adsafe) {
3003
error("ADSAFE: Use the fragment option.");
3005
error("Expected '{a}' and instead saw '{b}'.",
3009
if (option.adsafe) {
3011
error("Currently, ADsafe does not operate on whole HTML documents. It operates on <div> fragments and .js files.", token);
3013
if (option.fragment) {
3015
error("ADsafe violation: Wrap the widget in a div.", token);
3018
error("Use the fragment option.", token);
3021
option.browser = true;
3025
function doAttribute(n, a, v) {
3028
u = typeof v === 'string' ? v.toUpperCase() : '';
3029
if (ids[u] === true) {
3030
warning("Duplicate id='{a}'.", nexttoken, v);
3033
if (option.adsafe) {
3035
if (v.slice(0, adsafe_id.length) !== adsafe_id) {
3036
warning("ADsafe violation: An id must have a '{a}' prefix",
3037
nexttoken, adsafe_id);
3038
} else if (!/^[A-Z]+_[A-Z]+$/.test(v)) {
3039
warning("ADSAFE violation: bad id.");
3043
if (!/^[A-Z]+_$/.test(v)) {
3044
warning("ADSAFE violation: bad id.");
3048
} else if (a === 'href' || a === 'background' ||
3049
a === 'content' || a === 'data' ||
3050
a.indexOf('src') >= 0 || a.indexOf('url') >= 0) {
3051
if (option.safe && ux.test(v)) {
3052
error("ADsafe URL violation.");
3055
} else if (a === 'for') {
3056
if (option.adsafe) {
3058
if (v.slice(0, adsafe_id.length) !== adsafe_id) {
3059
warning("ADsafe violation: An id must have a '{a}' prefix",
3060
nexttoken, adsafe_id);
3061
} else if (!/^[A-Z]+_[A-Z]+$/.test(v)) {
3062
warning("ADSAFE violation: bad id.");
3065
warning("ADSAFE violation: bad id.");
3068
} else if (a === 'name') {
3069
if (option.adsafe && v.indexOf('_') >= 0) {
3070
warning("ADsafe name violation.");
3075
function doTag(n, a) {
3076
var i, t = htmltag[n], x;
3079
error("Unrecognized tag '<{a}>'.",
3081
n === n.toLowerCase() ? n :
3082
n + ' (capitalization error)');
3084
if (stack.length > 0) {
3086
error("Too many <html> tags.", token);
3090
if (x.indexOf(' ' + stack[stack.length - 1].name + ' ') < 0) {
3091
error("A '<{a}>' must be within '<{b}>'.",
3094
} else if (!option.adsafe || !option.fragment) {
3098
error("A '<{a}>' must be within '<{b}>'.",
3102
} while (stack[i].name !== 'body');
3107
if (option.adsafe && stack.length === 1 && !adsafe_id) {
3108
warning("ADSAFE violation: missing ID_.");
3114
indent = nexttoken.from;
3116
warning("lang is deprecated.", token);
3118
if (option.adsafe && stack.length !== 1) {
3119
warning("ADsafe script placement violation.", token);
3122
if (option.adsafe && (!adsafe_may || !approved[a.src])) {
3123
warning("ADsafe unapproved script source.", token);
3126
warning("type is unnecessary.", token);
3130
error("ADsafe script violation.", token);
3132
statements('script');
3136
if (!nexttoken.identifier && nexttoken.value !== 'script') {
3137
warning("Expected '{a}' and instead saw '{b}'.",
3138
nexttoken, 'script', nexttoken.value);
3149
if (!nexttoken.identifier && nexttoken.value !== 'style') {
3150
warning("Expected '{a}' and instead saw '{b}'.",
3151
nexttoken, 'style', nexttoken.value);
3171
warning("Bad input type.");
3173
if (option.adsafe && a.autocomplete !== 'off') {
3174
warning("ADsafe autocomplete violation.");
3187
if (option.adsafe) {
3188
warning("ADsafe violation: Disallowed tag: " + n);
3195
function closetag(n) {
3196
return '</' + n + '>';
3200
var a, attributes, e, n, q, t, v, wmode;
3205
switch (nexttoken.value) {
3211
if (!t.identifier) {
3212
warning("Bad identifier {a}.", t, t.value);
3216
n = n.toLowerCase();
3225
if (typeof v !== 'object') {
3226
error("Unrecognized tag '<{a}>'.", t, n);
3231
if (nexttoken.id === '/') {
3233
if (nexttoken.id !== '>') {
3234
warning("Expected '{a}' and instead saw '{b}'.",
3235
nexttoken, '>', nexttoken.value);
3239
if (nexttoken.id && nexttoken.id.substr(0, 1) === '>') {
3242
if (!nexttoken.identifier) {
3243
if (nexttoken.id === '(end)' || nexttoken.id === '(error)') {
3244
error("Missing '>'.", nexttoken);
3246
warning("Bad identifier.");
3248
a = nexttoken.value;
3250
if (!option.cap && a !== a.toLowerCase()) {
3251
warning("Attribute '{a}' not all lower case.", nexttoken, a);
3253
a = a.toLowerCase();
3255
if (attributes.hasOwnProperty(a)) {
3256
warning("Attribute '{a}' repeated.", nexttoken, a);
3258
if (a.slice(0, 2) === 'on') {
3260
warning("Avoid HTML event handlers.");
3262
xmode = 'scriptstring';
3265
if (q !== '"' && q !== "'") {
3266
error("Missing quote.");
3269
wmode = option.white;
3270
option.white = false;
3273
option.white = wmode;
3274
if (nexttoken.id !== q) {
3275
error("Missing close quote on script attribute.");
3281
} else if (a === 'style') {
3282
xmode = 'scriptstring';
3285
if (q !== '"' && q !== "'") {
3286
error("Missing quote.");
3288
xmode = 'styleproperty';
3297
if (nexttoken.id === '=') {
3299
v = nexttoken.value;
3300
if (!nexttoken.identifier &&
3301
nexttoken.id !== '"' &&
3302
nexttoken.id !== '\'' &&
3303
nexttoken.type !== '(string)' &&
3304
nexttoken.type !== '(number)' &&
3305
nexttoken.type !== '(color)') {
3306
warning("Expected an attribute value and instead saw '{a}'.", token, a);
3314
doAttribute(n, a, v);
3316
doTag(n, attributes);
3326
if (!nexttoken.identifier) {
3327
warning("Bad identifier.");
3329
n = nexttoken.value;
3331
n = n.toLowerCase();
3335
error("Unexpected '{a}'.", nexttoken, closetag(n));
3339
error("Unexpected '{a}'.", nexttoken, closetag(n));
3342
error("Expected '{a}' and instead saw '{b}'.",
3343
nexttoken, closetag(t.name), closetag(n));
3345
if (nexttoken.id !== '>') {
3346
error("Missing '{a}'.", nexttoken, '>');
3353
error("ADsafe HTML violation.");
3359
if (nexttoken.id === '>') {
3362
if (nexttoken.id === '<' || nexttoken.id === '(end)') {
3363
error("Missing '{a}'.", token, '>');
3365
if (nexttoken.id === '--') {
3370
warning("Misshapen HTML comment.");
3378
if (nexttoken.id === '(end)') {
3379
error("Missing '{a}'.", nexttoken, '</html>');
3380
} else if (nexttoken.id !== '--' && nexttoken.id !== '#') {
3381
error("Unexpected '{a}'.", nexttoken, nexttoken.value);
3386
if (stack && stack.length === 0) {
3390
if (nexttoken.id !== '(end)') {
3391
error("Unexpected material after the end.");
3396
// Build the syntax table by declaring the syntactic elements of the language.
3398
type('(number)', idValue);
3399
type('(string)', idValue);
3401
syntax['(identifier)'] = {
3402
type: '(identifier)',
3409
// The name is in scope and defined in the current function.
3411
if (s && (s === funct || s === funct['(global)'])) {
3413
// If we are not also in the global scope, change 'unused' to 'var',
3414
// and reject labels.
3416
if (!funct['(global)']) {
3422
warning("'{a}' is a statement label.", token, v);
3427
// The name is not defined in the function. If we are in the global scope,
3428
// then we have an undefined variable.
3430
} else if (funct['(global)']) {
3432
warning("'{a}' is undefined.", token, v);
3434
note_implied(token);
3436
// If the name is already defined in the current
3437
// function, but not as outer, then there is a scope error.
3445
warning("'{a}' used out of scope.", token, v);
3448
warning("'{a}' is a statement label.", token, v);
3455
// If the name is defined in an outer function, make an outer entry, and if
3456
// it was unused, make it var.
3460
} else if (typeof s !== 'object') {
3462
warning("'{a}' is undefined.", token, v);
3466
note_implied(token);
3480
warning("'{a}' is a statement label.", token, v);
3488
error("Expected an operator and instead saw '{a}'.",
3489
nexttoken, nexttoken.value);
3493
type('(regex)', function () {
3499
delim('(end)').reach = true;
3500
delim('</').reach = true;
3502
delim('(error)').reach = true;
3503
delim('}').reach = true;
3506
delim('"').reach = true;
3507
delim("'").reach = true;
3509
delim(':').reach = true;
3514
reserve('case').reach = true;
3516
reserve('default').reach = true;
3518
reservevar('arguments');
3520
reservevar('false');
3521
reservevar('Infinity');
3526
reservevar('undefined');
3527
assignop('=', 'assign', 20);
3528
assignop('+=', 'assignadd', 20);
3529
assignop('-=', 'assignsub', 20);
3530
assignop('*=', 'assignmult', 20);
3531
assignop('/=', 'assigndiv', 20).nud = function () {
3532
error("A regular expression literal can be confused with '/='.");
3534
assignop('%=', 'assignmod', 20);
3535
bitwiseassignop('&=', 'assignbitand', 20);
3536
bitwiseassignop('|=', 'assignbitor', 20);
3537
bitwiseassignop('^=', 'assignbitxor', 20);
3538
bitwiseassignop('<<=', 'assignshiftleft', 20);
3539
bitwiseassignop('>>=', 'assignshiftright', 20);
3540
bitwiseassignop('>>>=', 'assignshiftrightunsigned', 20);
3541
infix('?', function (left) {
3547
infix('||', 'or', 40);
3548
infix('&&', 'and', 50);
3549
bitwise('|', 'bitor', 70);
3550
bitwise('^', 'bitxor', 80);
3551
bitwise('&', 'bitand', 90);
3552
relation('==', function (left, right) {
3553
if (option.eqeqeq) {
3554
warning("Expected '{a}' and instead saw '{b}'.",
3556
} else if (isPoorRelation(left)) {
3557
warning("Use '{a}' to compare with '{b}'.",
3558
this, '===', left.value);
3559
} else if (isPoorRelation(right)) {
3560
warning("Use '{a}' to compare with '{b}'.",
3561
this, '===', right.value);
3566
relation('!=', function (left, right) {
3567
if (option.eqeqeq) {
3568
warning("Expected '{a}' and instead saw '{b}'.",
3570
} else if (isPoorRelation(left)) {
3571
warning("Use '{a}' to compare with '{b}'.",
3572
this, '!==', left.value);
3573
} else if (isPoorRelation(right)) {
3574
warning("Use '{a}' to compare with '{b}'.",
3575
this, '!==', right.value);
3584
bitwise('<<', 'shiftleft', 120);
3585
bitwise('>>', 'shiftright', 120);
3586
bitwise('>>>', 'shiftrightunsigned', 120);
3587
infix('in', 'in', 120);
3588
infix('instanceof', 'instanceof', 120);
3589
infix('+', function (left) {
3590
nonadjacent(prevtoken, token);
3591
nonadjacent(token, nexttoken);
3592
var right = parse(130);
3593
if (left && right && left.id === '(string)' && right.id === '(string)') {
3594
left.value += right.value;
3595
left.character = right.character;
3596
if (jx.test(left.value)) {
3597
warning("JavaScript URL.", left);
3606
infix('-', 'sub', 130);
3608
infix('*', 'mult', 140);
3609
infix('/', 'div', 140);
3610
infix('%', 'mod', 140);
3612
suffix('++', 'postinc');
3613
prefix('++', 'preinc');
3614
syntax['++'].exps = true;
3616
suffix('--', 'postdec');
3617
prefix('--', 'predec');
3618
syntax['--'].exps = true;
3619
prefix('delete', function () {
3621
if (p.id !== '.' && p.id !== '[') {
3622
warning("Expected '{a}' and instead saw '{b}'.",
3623
nexttoken, '.', nexttoken.value);
3628
prefix('~', function () {
3629
if (option.bitwise) {
3630
warning("Unexpected '{a}'.", this, '~');
3636
prefix('typeof', 'typeof');
3637
prefix('new', function () {
3638
var c = parse(155), i;
3639
if (c && c.id !== 'function') {
3644
warning("Use the object literal notation {}.", token);
3647
warning("Use the array literal notation [].", token);
3653
warning("Do not use the {a} function as a constructor.",
3658
warning("The Function constructor is eval.");
3665
if (c.id !== 'function') {
3666
i = c.value.substr(0, 1);
3667
if (i < 'A' || i > 'Z') {
3669
"A constructor name should start with an uppercase letter.",
3675
if (c.id !== '.' && c.id !== '[' && c.id !== '(') {
3676
warning("Bad constructor.", token);
3680
warning("Weird construction. Delete 'new'.", this);
3682
adjacent(token, nexttoken);
3683
if (nexttoken.id !== '(') {
3684
warning("Missing '()' invoking a constructor.");
3689
syntax['new'].exps = true;
3691
infix('.', function (left) {
3692
adjacent(prevtoken, token);
3693
var t = this, m = identifier();
3694
if (typeof m === 'string') {
3699
if (!option.evil && left && left.value === 'document' &&
3700
(m === 'write' || m === 'writeln')) {
3701
warning("document.write can be a form of eval.", left);
3703
if (option.adsafe) {
3704
if (left && left.value === 'ADSAFE') {
3705
if (m === 'id' || m === 'lib') {
3706
warning("ADsafe violation.", this);
3707
} else if (m === 'go') {
3708
if (xmode !== 'script') {
3709
warning("ADsafe violation.", this);
3710
} else if (adsafe_went || nexttoken.id !== '(' ||
3711
peek(0).id !== '(string)' ||
3712
peek(0).value !== adsafe_id ||
3713
peek(1).id !== ',') {
3714
error("ADsafe violation: go.", this);
3721
if (banned[m] === true) {
3722
warning("ADsafe restricted word '{a}'.", token, m);
3724
if (predefined[left.value] !== true ||
3725
nexttoken.id === '(') {
3728
if (standard_member[m] === true) {
3729
if (nexttoken.id === '.') {
3730
warning("ADsafe violation.", this);
3734
if (nexttoken.id !== '.') {
3735
warning("ADsafe violation.", this);
3743
if (typeof m === 'string') {
3751
infix('(', function (left) {
3752
adjacent(prevtoken, token);
3757
if (left.type === '(identifier)') {
3758
if (left.value.match(/^[A-Z]([A-Z0-9_$]*[a-z][A-Za-z0-9_$]*)?$/)) {
3759
if (left.value !== 'Number' && left.value !== 'String' &&
3760
left.value !== 'Boolean' && left.value !== 'Date') {
3761
if (left.value === 'Math') {
3762
warning("Math is not a function.", left);
3764
warning("Missing 'new' prefix when invoking a constructor.",
3769
} else if (left.id === '.') {
3770
if (option.safe && left.left.value === 'Math' && left.right === 'random') {
3771
warning("ADsafe violation.", left);
3775
if (nexttoken.id !== ')') {
3777
p[p.length] = parse(10);
3779
if (nexttoken.id !== ',') {
3783
nonadjacent(token, nexttoken);
3787
nospace(prevtoken, token);
3788
if (typeof left === 'object') {
3789
if (left.value === 'parseInt' && n === 1) {
3790
warning("Missing radix parameter.", left);
3793
if (left.value === 'eval' || left.value === 'Function' || left.value === 'execScript') {
3794
warning("eval is evil.", left);
3795
} else if (p[0] && p[0].id === '(string)' &&
3796
(left.value === 'setTimeout' ||
3797
left.value === 'setInterval')) {
3799
"Implied eval is evil. Pass a function instead of a string.", left);
3802
if (!left.identifier && left.id !== '.' && left.id !== '[' &&
3803
left.id !== '(' && left.id !== '&&' && left.id !== '||' &&
3805
warning("Bad invocation.", left);
3811
}, 155).exps = true;
3813
prefix('(', function () {
3817
nospace(prevtoken, token);
3818
if (v && (v.id === 'function' || v.id === 'new' || v.id === '.' ||
3819
v.id === '[' || v.id === '(')) {
3820
warning("Parens are not needed here.", this);
3823
}).fud = function () {
3828
if (v.id === 'function') {
3830
return token.led(v);
3833
nospace(prevtoken, token);
3837
infix('[', function (left) {
3839
var e = parse(0), s;
3840
if (e && e.type === '(string)') {
3841
if (option.safe && banned[e.value] === true) {
3842
warning("ADsafe restricted word '{a}'.", this, e.value);
3844
countMember(e.value);
3845
if (!option.sub && ix.test(e.value)) {
3846
s = syntax[e.value];
3847
if (!s || !s.reserved) {
3848
warning("['{a}'] is better written in dot notation.",
3852
} else if (!e || (e.type !== '(number)' &&
3853
(e.id !== '+' || e.arity !== 'unary'))) {
3855
warning('ADsafe subscripting.');
3859
nospace(prevtoken, token);
3865
prefix('[', function () {
3866
if (nexttoken.id === ']') {
3870
var b = token.line !== nexttoken.line;
3872
indent += option.indent;
3873
if (nexttoken.from === indent + option.indent) {
3874
indent += option.indent;
3878
if (b && token.line !== nexttoken.line) {
3882
if (nexttoken.id === ',') {
3883
adjacent(token, nexttoken);
3885
if (nexttoken.id === ',') {
3886
warning("Extra comma.", token);
3887
} else if (nexttoken.id === ']') {
3888
warning("Extra comma.", token);
3891
nonadjacent(token, nexttoken);
3894
indent -= option.indent;
3905
x.nud = function () {
3907
if (nexttoken.id === '}') {
3911
b = token.line !== nexttoken.line;
3913
indent += option.indent;
3914
if (nexttoken.from === indent + option.indent) {
3915
indent += option.indent;
3922
i = optionalidentifier(true);
3924
if (nexttoken.id === '(string)') {
3925
i = nexttoken.value;
3930
} else if (nexttoken.id === '(number)') {
3931
i = nexttoken.value.toString();
3934
error("Expected '{a}' and instead saw '{b}'.",
3935
nexttoken, '}', nexttoken.value);
3940
nonadjacent(token, nexttoken);
3942
if (nexttoken.id === ',') {
3943
adjacent(token, nexttoken);
3945
if (nexttoken.id === ',' || nexttoken.id === '}') {
3946
warning("Extra comma.", token);
3948
nonadjacent(token, nexttoken);
3951
indent -= option.indent;
3959
x.fud = function () {
3960
error("Expected to see a statement and instead saw a block.", token);
3965
function varstatement(prefix) {
3967
// JavaScript does not have block scope. It only has function scope. So,
3968
// declaring a variable in a block can have unexpected consequences.
3970
if (funct['(onevar)'] && option.onevar) {
3971
warning("Too many var statements.");
3972
} else if (!funct['(global)']) {
3973
funct['(onevar)'] = true;
3976
nonadjacent(token, nexttoken);
3977
addlabel(identifier(), 'unused');
3981
if (nexttoken.id === '=') {
3982
nonadjacent(token, nexttoken);
3984
nonadjacent(token, nexttoken);
3985
if (peek(0).id === '=') {
3986
error("Variable {a} was not declared correctly.",
3987
nexttoken, nexttoken.value);
3991
if (nexttoken.id !== ',') {
3994
adjacent(token, nexttoken);
3996
nonadjacent(token, nexttoken);
4001
stmt('var', varstatement);
4003
stmt('new', function () {
4004
error("'new' should not be used as a statement.");
4008
function functionparams() {
4009
var i, t = nexttoken, p = [];
4012
if (nexttoken.id === ')') {
4014
nospace(prevtoken, token);
4020
addlabel(i, 'parameter');
4021
if (nexttoken.id === ',') {
4023
nonadjacent(token, nexttoken);
4026
nospace(prevtoken, token);
4027
return p.join(', ');
4032
function doFunction(i) {
4034
scope = Object.create(s);
4036
'(name)' : i || '"' + anonname + '"',
4037
'(line)' : nexttoken.line + 1,
4038
'(context)' : funct,
4043
functions.push(funct);
4045
addlabel(i, 'function');
4047
funct['(params)'] = functionparams();
4051
funct = funct['(context)'];
4055
blockstmt('function', function () {
4058
"Function statements cannot be placed in blocks. Use a function expression or move the statement to the top of the outer function.", token);
4061
var i = identifier();
4062
adjacent(token, nexttoken);
4063
addlabel(i, 'unused');
4065
if (nexttoken.id === '(' && nexttoken.line === token.line) {
4067
"Function statements are not invocable. Wrap the function expression in parens.");
4071
prefix('function', function () {
4072
var i = optionalidentifier();
4074
adjacent(token, nexttoken);
4076
nonadjacent(token, nexttoken);
4079
if (funct['(loopage)'] && nexttoken.id !== '(') {
4080
warning("Be careful when making functions within a loop. Consider putting the function in a closure.");
4085
blockstmt('if', function () {
4088
nonadjacent(this, t);
4091
if (nexttoken.id === '=') {
4092
warning("Expected a conditional expression and instead saw an assignment.");
4097
nospace(prevtoken, token);
4099
if (nexttoken.id === 'else') {
4100
nonadjacent(token, nexttoken);
4102
if (nexttoken.id === 'if' || nexttoken.id === 'switch') {
4111
blockstmt('try', function () {
4113
if (option.adsafe) {
4114
warning("ADsafe try violation.", this);
4117
if (nexttoken.id === 'catch') {
4119
nonadjacent(token, nexttoken);
4122
scope = Object.create(s);
4123
e = nexttoken.value;
4124
if (nexttoken.type !== '(identifier)') {
4125
warning("Expected an identifier and instead saw '{a}'.",
4128
addlabel(e, 'unused');
4136
if (nexttoken.id === 'finally') {
4141
error("Expected '{a}' and instead saw '{b}'.",
4142
nexttoken, 'catch', nexttoken.value);
4146
blockstmt('while', function () {
4148
funct['(breakage)'] += 1;
4149
funct['(loopage)'] += 1;
4151
nonadjacent(this, t);
4154
if (nexttoken.id === '=') {
4155
warning("Expected a conditional expression and instead saw an assignment.");
4160
nospace(prevtoken, token);
4162
funct['(breakage)'] -= 1;
4163
funct['(loopage)'] -= 1;
4168
blockstmt('switch', function () {
4171
funct['(breakage)'] += 1;
4173
nonadjacent(this, t);
4175
this.condition = parse(20);
4177
nospace(prevtoken, token);
4178
nonadjacent(token, nexttoken);
4181
nonadjacent(token, nexttoken);
4182
indent += option.indent;
4185
switch (nexttoken.id) {
4187
switch (funct['(verb)']) {
4197
"Expected a 'break' statement before 'case'.",
4200
indentation(-option.indent);
4202
this.cases.push(parse(20));
4205
funct['(verb)'] = 'case';
4208
switch (funct['(verb)']) {
4216
"Expected a 'break' statement before 'default'.",
4219
indentation(-option.indent);
4225
indent -= option.indent;
4228
if (this.cases.length === 1 || this.condition.id === 'true' ||
4229
this.condition.id === 'false') {
4230
warning("This 'switch' should be an 'if'.", this);
4232
funct['(breakage)'] -= 1;
4233
funct['(verb)'] = undefined;
4236
error("Missing '{a}'.", nexttoken, '}');
4242
error("Each value should have its own case label.");
4248
error("Missing ':' on a case clause.", token);
4251
error("Expected '{a}' and instead saw '{b}'.",
4252
nexttoken, 'case', nexttoken.value);
4258
stmt('debugger', function () {
4259
if (!option.debug) {
4260
warning("All 'debugger' statements should be removed.");
4264
stmt('do', function () {
4265
funct['(breakage)'] += 1;
4266
funct['(loopage)'] += 1;
4270
nonadjacent(token, t);
4274
if (nexttoken.id === '=') {
4275
warning("Expected a conditional expression and instead saw an assignment.");
4280
nospace(prevtoken, token);
4281
funct['(breakage)'] -= 1;
4282
funct['(loopage)'] -= 1;
4285
blockstmt('for', function () {
4286
var s, t = nexttoken;
4287
funct['(breakage)'] += 1;
4288
funct['(loopage)'] += 1;
4290
nonadjacent(this, t);
4292
if (peek(nexttoken.id === 'var' ? 1 : 0).id === 'in') {
4293
if (nexttoken.id === 'var') {
4303
if (!option.forin && (s.length > 1 || typeof s[0] !== 'object' ||
4304
s[0].value !== 'if')) {
4305
warning("The body of a for in should be wrapped in an if statement to filter unwanted properties from the prototype.", this);
4307
funct['(breakage)'] -= 1;
4308
funct['(loopage)'] -= 1;
4311
if (nexttoken.id !== ';') {
4312
if (nexttoken.id === 'var') {
4318
if (nexttoken.id !== ',') {
4326
if (nexttoken.id !== ';') {
4328
if (nexttoken.id === '=') {
4329
warning("Expected a conditional expression and instead saw an assignment.");
4335
if (nexttoken.id === ';') {
4336
error("Expected '{a}' and instead saw '{b}'.",
4337
nexttoken, ')', ';');
4339
if (nexttoken.id !== ')') {
4342
if (nexttoken.id !== ',') {
4349
nospace(prevtoken, token);
4351
funct['(breakage)'] -= 1;
4352
funct['(loopage)'] -= 1;
4357
stmt('break', function () {
4358
var v = nexttoken.value;
4359
if (funct['(breakage)'] === 0) {
4360
warning("Unexpected '{a}'.", nexttoken, this.value);
4363
if (nexttoken.id !== ';') {
4364
if (funct[v] !== 'label') {
4365
warning("'{a}' is not a statement label.", nexttoken, v);
4366
} else if (scope[v] !== funct) {
4367
warning("'{a}' is out of scope.", nexttoken, v);
4375
stmt('continue', function () {
4376
var v = nexttoken.value;
4378
if (nexttoken.id !== ';') {
4379
if (funct[v] !== 'label') {
4380
warning("'{a}' is not a statement label.", nexttoken, v);
4381
} else if (scope[v] !== funct) {
4382
warning("'{a}' is out of scope.", nexttoken, v);
4386
reachable('continue');
4390
stmt('return', function () {
4392
if (nexttoken.id !== ';' && !nexttoken.reach) {
4393
nonadjacent(token, nexttoken);
4396
reachable('return');
4400
stmt('throw', function () {
4402
nonadjacent(token, nexttoken);
4409
// Superfluous reserved words
4421
function jsonValue() {
4423
function jsonObject() {
4426
if (nexttoken.id !== '}') {
4428
if (nexttoken.id === '(end)') {
4429
error("Missing '}' to match '{' from line {a}.",
4430
nexttoken, t.line + 1);
4431
} else if (nexttoken.id === '}') {
4432
warning("Unexpected comma.", token);
4434
} else if (nexttoken.id === ',') {
4435
error("Unexpected comma.", nexttoken);
4436
} else if (nexttoken.id !== '(string)') {
4437
warning("Expected a string and instead saw {a}.",
4438
nexttoken, nexttoken.value);
4443
if (nexttoken.id !== ',') {
4452
function jsonArray() {
4455
if (nexttoken.id !== ']') {
4457
if (nexttoken.id === '(end)') {
4458
error("Missing ']' to match '[' from line {a}.",
4459
nexttoken, t.line + 1);
4460
} else if (nexttoken.id === ']') {
4461
warning("Unexpected comma.", token);
4463
} else if (nexttoken.id === ',') {
4464
error("Unexpected comma.", nexttoken);
4467
if (nexttoken.id !== ',') {
4476
switch (nexttoken.id) {
4492
if (token.character !== nexttoken.from) {
4493
warning("Unexpected space after '-'.", token);
4495
adjacent(token, nexttoken);
4496
advance('(number)');
4499
error("Expected a JSON value.", nexttoken);
4504
// The actual JSLINT function itself.
4506
var itself = function (s, o) {
4509
predefined = Object.create(standard);
4512
if (a instanceof Array) {
4513
for (i = 0; i < a.length; i += 1) {
4514
predefined[a[i]] = true;
4536
predefined.Date = false;
4537
predefined['eval'] = false;
4538
predefined.Function = false;
4539
predefined.Object = false;
4540
predefined.ADSAFE = true;
4546
option.indent = option.indent || 4;
4549
adsafe_went = false;
4551
if (option.approved) {
4552
for (i = 0; i < option.approved.length; i += 1) {
4553
approved[option.approved[i]] = option.approved[i];
4556
approved.test = 'test'; ///////////////////////////////////////
4558
for (i = 0; i < option.indent; i += 1) {
4562
global = Object.create(predefined);
4566
'(name)': '(global)',
4587
prevtoken = token = nexttoken = syntax['(begin)'];
4592
if (nexttoken.value.charAt(0) === '<') {
4594
if (option.adsafe && !adsafe_went) {
4595
warning("ADsafe violation: Missing ADSAFE.go.", this);
4598
switch (nexttoken.id) {
4601
option.laxbreak = true;
4612
if (token.id !== '@' || !nexttoken.identifier ||
4613
nexttoken.value !== 'charset') {
4614
error('A css file should begin with @charset "UTF-8";');
4617
if (nexttoken.type !== '(string)' &&
4618
nexttoken.value !== 'UTF-8') {
4619
error('A css file should begin with @charset "UTF-8";');
4627
if (option.adsafe && option.fragment) {
4628
warning("ADsafe violation.", this);
4636
JSLINT.errors.push({
4638
line : e.line || nexttoken.line,
4639
character : e.character || nexttoken.from
4643
return JSLINT.errors.length === 0;
4646
function to_array(o) {
4649
if (o.hasOwnProperty(k)) {
4656
// Report generator.
4658
itself.report = function (option, sep) {
4659
var a = [], c, e, f, i, k, l, m = '', n, o = [], s, v, cl, va, un, ou, gl, la;
4661
function detail(h, s, sep) {
4663
o.push('<div><i>' + h + '</i> ' +
4664
s.sort().join(sep || ', ') + '</div>');
4668
s = to_array(implied);
4670
k = JSLINT.errors.length;
4671
if (k || s.length > 0) {
4672
o.push('<div id=errors><i>Error:</i>');
4675
for (i = 0; i < s.length; i += 1) {
4676
s[i] = '<code>' + s[i] + '</code> <i>' +
4677
implied[s[i]].join(' ') +
4680
o.push('<p><i>Implied global:</i> ' + s.join(', ') + '</p>');
4683
for (i = 0; i < k; i += 1) {
4684
c = JSLINT.errors[i];
4686
e = c.evidence || '';
4687
o.push('<p>Problem' + (isFinite(c.line) ? ' at line ' + (c.line + 1) +
4688
' character ' + (c.character + 1) : '') +
4689
': ' + c.reason.entityify() +
4690
'</p><p class=evidence>' +
4691
(e && (e.length > 80 ? e.slice(0, 77) + '...' :
4692
e).entityify()) + '</p>');
4703
o.push('<br><div id=functions>');
4705
if (urls.length > 0) {
4706
detail("URLs<br>", urls, '<br>');
4709
s = to_array(scope);
4710
if (s.length === 0) {
4713
o.push('<p>JSON: good.</p>');
4715
o.push('<p>JSON: bad.</p>');
4718
o.push('<div><i>No new global variables introduced.</i></div>');
4721
o.push('<div><i>Global</i> ' + s.sort().join(', ') + '</div>');
4724
for (i = 0; i < functions.length; i += 1) {
4733
if (f.hasOwnProperty(k) && k.charAt(0) !== '(') {
4757
o.push('<br><div class=function><i>' + f['(line)'] + '</i> ' +
4758
(f['(name)'] || '') + '(' +
4759
(f['(params)'] || '') + ')</div>');
4760
detail('Closure', cl);
4761
detail('Variable', va);
4762
detail('<big><b>Unused</b></big>', un);
4763
detail('Label', la);
4764
detail('Outer', ou);
4765
detail('Global', gl);
4769
if (typeof member[k] === 'number') {
4775
m = '<br><pre>/*members ';
4777
for (i = 0; i < a.length; i += 1) {
4780
if (l + n.length > 72) {
4786
if (member[k] === 1) {
4787
n = '<i>' + n + '</i>';
4789
if (i < a.length - 1) {
4794
o.push(m + '<br>*/</pre>');
b'\\ No newline at end of file'