diff --git a/README.markdown b/README.markdown index 0824dd3..ac34458 100644 --- a/README.markdown +++ b/README.markdown @@ -5,6 +5,11 @@ This is a set of utility files that wraps JSLint/Rhino, enabling easy linting on the command line, and automated reporting of linting errors via a continuous integration system like Hudson. +## Dependencies: +* [node] npm packages + * [node] jshint + * [node] csslint + Usage ----- diff --git a/lib/csslint-rhino.js b/lib/csslint-rhino.js new file mode 100644 index 0000000..c544e6c --- /dev/null +++ b/lib/csslint-rhino.js @@ -0,0 +1,7981 @@ +/*! +CSSLint +Copyright (c) 2011 Nicole Sullivan and Nicholas C. Zakas. All rights reserved. + +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 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. + +*/ +/* Build time: 8-September-2011 08:50:44 */ +var CSSLint = (function(){ +/*! +Parser-Lib +Copyright (c) 2009-2011 Nicholas C. Zakas. All rights reserved. + +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 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. + +*/ +/* Build time: 19-July-2011 01:46:47 */ +var parserlib = {}; +(function(){ + +/** + * A generic base to inherit from for any object + * that needs event handling. + * @class EventTarget + * @constructor + */ +function EventTarget(){ + + /** + * The array of listeners for various events. + * @type Object + * @property _listeners + * @private + */ + this._listeners = {}; +} + +EventTarget.prototype = { + + //restore constructor + constructor: EventTarget, + + /** + * Adds a listener for a given event type. + * @param {String} type The type of event to add a listener for. + * @param {Function} listener The function to call when the event occurs. + * @return {void} + * @method addListener + */ + addListener: function(type, listener){ + if (!this._listeners[type]){ + this._listeners[type] = []; + } + + this._listeners[type].push(listener); + }, + + /** + * Fires an event based on the passed-in object. + * @param {Object|String} event An object with at least a 'type' attribute + * or a string indicating the event name. + * @return {void} + * @method fire + */ + fire: function(event){ + if (typeof event == "string"){ + event = { type: event }; + } + if (!event.target){ + event.target = this; + } + + if (!event.type){ + throw new Error("Event object missing 'type' property."); + } + + if (this._listeners[event.type]){ + + //create a copy of the array and use that so listeners can't chane + var listeners = this._listeners[event.type].concat(); + for (var i=0, len=listeners.length; i < len; i++){ + listeners[i].call(this, event); + } + } + }, + + /** + * Removes a listener for a given event type. + * @param {String} type The type of event to remove a listener from. + * @param {Function} listener The function to remove from the event. + * @return {void} + * @method removeListener + */ + removeListener: function(type, listener){ + if (this._listeners[type]){ + var listeners = this._listeners[type]; + for (var i=0, len=listeners.length; i < len; i++){ + if (listeners[i] === listener){ + listeners.splice(i, 1); + break; + } + } + + + } + } +}; +/** + * Convenient way to read through strings. + * @namespace parserlib.util + * @class StringReader + * @constructor + * @param {String} text The text to read. + */ +function StringReader(text){ + + /** + * The input text with line endings normalized. + * @property _input + * @type String + * @private + */ + this._input = text.replace(/\n\r?/g, "\n"); + + + /** + * The row for the character to be read next. + * @property _line + * @type int + * @private + */ + this._line = 1; + + + /** + * The column for the character to be read next. + * @property _col + * @type int + * @private + */ + this._col = 1; + + /** + * The index of the character in the input to be read next. + * @property _cursor + * @type int + * @private + */ + this._cursor = 0; +} + +StringReader.prototype = { + + //restore constructor + constructor: StringReader, + + //------------------------------------------------------------------------- + // Position info + //------------------------------------------------------------------------- + + /** + * Returns the column of the character to be read next. + * @return {int} The column of the character to be read next. + * @method getCol + */ + getCol: function(){ + return this._col; + }, + + /** + * Returns the row of the character to be read next. + * @return {int} The row of the character to be read next. + * @method getLine + */ + getLine: function(){ + return this._line ; + }, + + /** + * Determines if you're at the end of the input. + * @return {Boolean} True if there's no more input, false otherwise. + * @method eof + */ + eof: function(){ + return (this._cursor == this._input.length); + }, + + //------------------------------------------------------------------------- + // Basic reading + //------------------------------------------------------------------------- + + /** + * Reads the next character without advancing the cursor. + * @param {int} count How many characters to look ahead (default is 1). + * @return {String} The next character or null if there is no next character. + * @method peek + */ + peek: function(count){ + var c = null; + count = (typeof count == "undefined" ? 1 : count); + + //if we're not at the end of the input... + if (this._cursor < this._input.length){ + + //get character and increment cursor and column + c = this._input.charAt(this._cursor + count - 1); + } + + return c; + }, + + /** + * Reads the next character from the input and adjusts the row and column + * accordingly. + * @return {String} The next character or null if there is no next character. + * @method read + */ + read: function(){ + var c = null; + + //if we're not at the end of the input... + if (this._cursor < this._input.length){ + + //if the last character was a newline, increment row count + //and reset column count + if (this._input.charAt(this._cursor) == "\n"){ + this._line++; + this._col=1; + } else { + this._col++; + } + + //get character and increment cursor and column + c = this._input.charAt(this._cursor++); + } + + return c; + }, + + //------------------------------------------------------------------------- + // Misc + //------------------------------------------------------------------------- + + /** + * Saves the current location so it can be returned to later. + * @method mark + * @return {void} + */ + mark: function(){ + this._bookmark = { + cursor: this._cursor, + line: this._line, + col: this._col + }; + }, + + reset: function(){ + if (this._bookmark){ + this._cursor = this._bookmark.cursor; + this._line = this._bookmark.line; + this._col = this._bookmark.col; + delete this._bookmark; + } + }, + + //------------------------------------------------------------------------- + // Advanced reading + //------------------------------------------------------------------------- + + /** + * Reads up to and including the given string. Throws an error if that + * string is not found. + * @param {String} pattern The string to read. + * @return {String} The string when it is found. + * @throws Error when the string pattern is not found. + * @method readTo + */ + readTo: function(pattern){ + + var buffer = "", + c; + + /* + * First, buffer must be the same length as the pattern. + * Then, buffer must end with the pattern or else reach the + * end of the input. + */ + while (buffer.length < pattern.length || buffer.lastIndexOf(pattern) != buffer.length - pattern.length){ + c = this.read(); + if (c){ + buffer += c; + } else { + throw new Error("Expected \"" + pattern + "\" at line " + this._line + ", col " + this._col + "."); + } + } + + return buffer; + + }, + + /** + * Reads characters while each character causes the given + * filter function to return true. The function is passed + * in each character and either returns true to continue + * reading or false to stop. + * @param {Function} filter The function to read on each character. + * @return {String} The string made up of all characters that passed the + * filter check. + * @method readWhile + */ + readWhile: function(filter){ + + var buffer = "", + c = this.read(); + + while(c !== null && filter(c)){ + buffer += c; + c = this.read(); + } + + return buffer; + + }, + + /** + * Reads characters that match either text or a regular expression and + * returns those characters. If a match is found, the row and column + * are adjusted; if no match is found, the reader's state is unchanged. + * reading or false to stop. + * @param {String|RegExp} matchter If a string, then the literal string + * value is searched for. If a regular expression, then any string + * matching the pattern is search for. + * @return {String} The string made up of all characters that matched or + * null if there was no match. + * @method readMatch + */ + readMatch: function(matcher){ + + var source = this._input.substring(this._cursor), + value = null; + + //if it's a string, just do a straight match + if (typeof matcher == "string"){ + if (source.indexOf(matcher) === 0){ + value = this.readCount(matcher.length); + } + } else if (matcher instanceof RegExp){ + if (matcher.test(source)){ + value = this.readCount(RegExp.lastMatch.length); + } + } + + return value; + }, + + + /** + * Reads a given number of characters. If the end of the input is reached, + * it reads only the remaining characters and does not throw an error. + * @param {int} count The number of characters to read. + * @return {String} The string made up the read characters. + * @method readCount + */ + readCount: function(count){ + var buffer = ""; + + while(count--){ + buffer += this.read(); + } + + return buffer; + } + +}; +/** + * Type to use when a syntax error occurs. + * @class SyntaxError + * @namespace parserlib.util + * @constructor + * @param {String} message The error message. + * @param {int} line The line at which the error occurred. + * @param {int} col The column at which the error occurred. + */ +function SyntaxError(message, line, col){ + + /** + * The column at which the error occurred. + * @type int + * @property col + */ + this.col = col; + + /** + * The line at which the error occurred. + * @type int + * @property line + */ + this.line = line; + + /** + * The text representation of the unit. + * @type String + * @property text + */ + this.message = message; + +} + +//inherit from Error +SyntaxError.prototype = new Error(); +/** + * Base type to represent a single syntactic unit. + * @class SyntaxUnit + * @namespace parserlib.util + * @constructor + * @param {String} text The text of the unit. + * @param {int} line The line of text on which the unit resides. + * @param {int} col The column of text on which the unit resides. + */ +function SyntaxUnit(text, line, col){ + + + /** + * The column of text on which the unit resides. + * @type int + * @property col + */ + this.col = col; + + /** + * The line of text on which the unit resides. + * @type int + * @property line + */ + this.line = line; + + /** + * The text representation of the unit. + * @type String + * @property text + */ + this.text = text; + +} + +/** + * Create a new syntax unit based solely on the given token. + * Convenience method for creating a new syntax unit when + * it represents a single token instead of multiple. + * @param {Object} token The token object to represent. + * @return {parserlib.util.SyntaxUnit} The object representing the token. + * @static + * @method fromToken + */ +SyntaxUnit.fromToken = function(token){ + return new SyntaxUnit(token.value, token.startLine, token.startCol); +}; + +SyntaxUnit.prototype = { + + //restore constructor + constructor: SyntaxUnit, + + /** + * Returns the text representation of the unit. + * @return {String} The text representation of the unit. + * @method valueOf + */ + valueOf: function(){ + return this.toString(); + }, + + /** + * Returns the text representation of the unit. + * @return {String} The text representation of the unit. + * @method toString + */ + toString: function(){ + return this.text; + } + +}; +/** + * Generic TokenStream providing base functionality. + * @class TokenStreamBase + * @namespace parserlib.util + * @constructor + * @param {String|StringReader} input The text to tokenize or a reader from + * which to read the input. + */ +function TokenStreamBase(input, tokenData){ + + /** + * The string reader for easy access to the text. + * @type StringReader + * @property _reader + * @private + */ + //this._reader = (typeof input == "string") ? new StringReader(input) : input; + this._reader = input ? new StringReader(input.toString()) : null; + + /** + * Token object for the last consumed token. + * @type Token + * @property _token + * @private + */ + this._token = null; + + /** + * The array of token information. + * @type Array + * @property _tokenData + * @private + */ + this._tokenData = tokenData; + + /** + * Lookahead token buffer. + * @type Array + * @property _lt + * @private + */ + this._lt = []; + + /** + * Lookahead token buffer index. + * @type int + * @property _ltIndex + * @private + */ + this._ltIndex = 0; + + this._ltIndexCache = []; +} + +/** + * Accepts an array of token information and outputs + * an array of token data containing key-value mappings + * and matching functions that the TokenStream needs. + * @param {Array} tokens An array of token descriptors. + * @return {Array} An array of processed token data. + * @method createTokenData + * @static + */ +TokenStreamBase.createTokenData = function(tokens){ + + var nameMap = [], + typeMap = {}, + tokenData = tokens.concat([]), + i = 0, + len = tokenData.length+1; + + tokenData.UNKNOWN = -1; + tokenData.unshift({name:"EOF"}); + + for (; i < len; i++){ + nameMap.push(tokenData[i].name); + tokenData[tokenData[i].name] = i; + if (tokenData[i].text){ + typeMap[tokenData[i].text] = i; + } + } + + tokenData.name = function(tt){ + return nameMap[tt]; + }; + + tokenData.type = function(c){ + return typeMap[c]; + }; + + return tokenData; +}; + +TokenStreamBase.prototype = { + + //restore constructor + constructor: TokenStreamBase, + + //------------------------------------------------------------------------- + // Matching methods + //------------------------------------------------------------------------- + + /** + * Determines if the next token matches the given token type. + * If so, that token is consumed; if not, the token is placed + * back onto the token stream. You can pass in any number of + * token types and this will return true if any of the token + * types is found. + * @param {int|int[]} tokenTypes Either a single token type or an array of + * token types that the next token might be. If an array is passed, + * it's assumed that the token can be any of these. + * @param {variant} channel (Optional) The channel to read from. If not + * provided, reads from the default (unnamed) channel. + * @return {Boolean} True if the token type matches, false if not. + * @method match + */ + match: function(tokenTypes, channel){ + + //always convert to an array, makes things easier + if (!(tokenTypes instanceof Array)){ + tokenTypes = [tokenTypes]; + } + + var tt = this.get(channel), + i = 0, + len = tokenTypes.length; + + while(i < len){ + if (tt == tokenTypes[i++]){ + return true; + } + } + + //no match found, put the token back + this.unget(); + return false; + }, + + /** + * Determines if the next token matches the given token type. + * If so, that token is consumed; if not, an error is thrown. + * @param {int|int[]} tokenTypes Either a single token type or an array of + * token types that the next token should be. If an array is passed, + * it's assumed that the token must be one of these. + * @param {variant} channel (Optional) The channel to read from. If not + * provided, reads from the default (unnamed) channel. + * @return {void} + * @method mustMatch + */ + mustMatch: function(tokenTypes, channel){ + + //always convert to an array, makes things easier + if (!(tokenTypes instanceof Array)){ + tokenTypes = [tokenTypes]; + } + + if (!this.match.apply(this, arguments)){ + token = this.LT(1); + throw new SyntaxError("Expected " + this._tokenData[tokenTypes[0]].name + + " at line " + token.startLine + ", col " + token.startCol + ".", token.startLine, token.startCol); + } + }, + + //------------------------------------------------------------------------- + // Consuming methods + //------------------------------------------------------------------------- + + /** + * Keeps reading from the token stream until either one of the specified + * token types is found or until the end of the input is reached. + * @param {int|int[]} tokenTypes Either a single token type or an array of + * token types that the next token should be. If an array is passed, + * it's assumed that the token must be one of these. + * @param {variant} channel (Optional) The channel to read from. If not + * provided, reads from the default (unnamed) channel. + * @return {void} + * @method advance + */ + advance: function(tokenTypes, channel){ + + while(this.LA(0) != 0 && !this.match(tokenTypes, channel)){ + this.get(); + } + + return this.LA(0); + }, + + /** + * Consumes the next token from the token stream. + * @return {int} The token type of the token that was just consumed. + * @method get + */ + get: function(channel){ + + var tokenInfo = this._tokenData, + reader = this._reader, + value, + i =0, + len = tokenInfo.length, + found = false, + token, + info; + + //check the lookahead buffer first + if (this._lt.length && this._ltIndex >= 0 && this._ltIndex < this._lt.length){ + + i++; + this._token = this._lt[this._ltIndex++]; + info = tokenInfo[this._token.type]; + + //obey channels logic + while((info.channel !== undefined && channel !== info.channel) && + this._ltIndex < this._lt.length){ + this._token = this._lt[this._ltIndex++]; + info = tokenInfo[this._token.type]; + i++; + } + + //here be dragons + if ((info.channel === undefined || channel === info.channel) && + this._ltIndex <= this._lt.length){ + this._ltIndexCache.push(i); + return this._token.type; + } + } + + //call token retriever method + token = this._getToken(); + + //if it should be hidden, don't save a token + if (token.type > -1 && !tokenInfo[token.type].hide){ + + //apply token channel + token.channel = tokenInfo[token.type].channel; + + //save for later + this._token = token; + this._lt.push(token); + + //save space that will be moved (must be done before array is truncated) + this._ltIndexCache.push(this._lt.length - this._ltIndex + i); + + //keep the buffer under 5 items + if (this._lt.length > 5){ + this._lt.shift(); + } + + //also keep the shift buffer under 5 items + if (this._ltIndexCache.length > 5){ + this._ltIndexCache.shift(); + } + + //update lookahead index + this._ltIndex = this._lt.length; + } + + /* + * Skip to the next token if: + * 1. The token type is marked as hidden. + * 2. The token type has a channel specified and it isn't the current channel. + */ + info = tokenInfo[token.type]; + if (info && + (info.hide || + (info.channel !== undefined && channel !== info.channel))){ + return this.get(channel); + } else { + //return just the type + return token.type; + } + }, + + /** + * Looks ahead a certain number of tokens and returns the token type at + * that position. This will throw an error if you lookahead past the + * end of input, past the size of the lookahead buffer, or back past + * the first token in the lookahead buffer. + * @param {int} The index of the token type to retrieve. 0 for the + * current token, 1 for the next, -1 for the previous, etc. + * @return {int} The token type of the token in the given position. + * @method LA + */ + LA: function(index){ + var total = index, + tt; + if (index > 0){ + //TODO: Store 5 somewhere + if (index > 5){ + throw new Error("Too much lookahead."); + } + + //get all those tokens + while(total){ + tt = this.get(); + total--; + } + + //unget all those tokens + while(total < index){ + this.unget(); + total++; + } + } else if (index < 0){ + + if(this._lt[this._ltIndex+index]){ + tt = this._lt[this._ltIndex+index].type; + } else { + throw new Error("Too much lookbehind."); + } + + } else { + tt = this._token.type; + } + + return tt; + + }, + + /** + * Looks ahead a certain number of tokens and returns the token at + * that position. This will throw an error if you lookahead past the + * end of input, past the size of the lookahead buffer, or back past + * the first token in the lookahead buffer. + * @param {int} The index of the token type to retrieve. 0 for the + * current token, 1 for the next, -1 for the previous, etc. + * @return {Object} The token of the token in the given position. + * @method LA + */ + LT: function(index){ + + //lookahead first to prime the token buffer + this.LA(index); + + //now find the token, subtract one because _ltIndex is already at the next index + return this._lt[this._ltIndex+index-1]; + }, + + /** + * Returns the token type for the next token in the stream without + * consuming it. + * @return {int} The token type of the next token in the stream. + * @method peek + */ + peek: function(){ + return this.LA(1); + }, + + /** + * Returns the actual token object for the last consumed token. + * @return {Token} The token object for the last consumed token. + * @method token + */ + token: function(){ + return this._token; + }, + + /** + * Returns the name of the token for the given token type. + * @param {int} tokenType The type of token to get the name of. + * @return {String} The name of the token or "UNKNOWN_TOKEN" for any + * invalid token type. + * @method tokenName + */ + tokenName: function(tokenType){ + if (tokenType < 0 || tokenType > this._tokenData.length){ + return "UNKNOWN_TOKEN"; + } else { + return this._tokenData[tokenType].name; + } + }, + + /** + * Returns the token type value for the given token name. + * @param {String} tokenName The name of the token whose value should be returned. + * @return {int} The token type value for the given token name or -1 + * for an unknown token. + * @method tokenName + */ + tokenType: function(tokenName){ + return this._tokenData[tokenName] || -1; + }, + + /** + * Returns the last consumed token to the token stream. + * @method unget + */ + unget: function(){ + //if (this._ltIndex > -1){ + if (this._ltIndexCache.length){ + this._ltIndex -= this._ltIndexCache.pop();//--; + this._token = this._lt[this._ltIndex - 1]; + } else { + throw new Error("Too much lookahead."); + } + } + +}; + + +parserlib.util = { +StringReader: StringReader, +SyntaxError : SyntaxError, +SyntaxUnit : SyntaxUnit, +EventTarget : EventTarget, +TokenStreamBase : TokenStreamBase +}; +})(); +/* +Parser-Lib +Copyright (c) 2009-2011 Nicholas C. Zakas. All rights reserved. + +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 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. + +*/ +/* Build time: 19-July-2011 01:46:47 */ +(function(){ +var EventTarget = parserlib.util.EventTarget, +TokenStreamBase = parserlib.util.TokenStreamBase, +StringReader = parserlib.util.StringReader, +SyntaxError = parserlib.util.SyntaxError, +SyntaxUnit = parserlib.util.SyntaxUnit; + +var Colors = { + aliceblue :"#f0f8ff", + antiquewhite :"#faebd7", + aqua :"#00ffff", + aquamarine :"#7fffd4", + azure :"#f0ffff", + beige :"#f5f5dc", + bisque :"#ffe4c4", + black :"#000000", + blanchedalmond :"#ffebcd", + blue :"#0000ff", + blueviolet :"#8a2be2", + brown :"#a52a2a", + burlywood :"#deb887", + cadetblue :"#5f9ea0", + chartreuse :"#7fff00", + chocolate :"#d2691e", + coral :"#ff7f50", + cornflowerblue :"#6495ed", + cornsilk :"#fff8dc", + crimson :"#dc143c", + cyan :"#00ffff", + darkblue :"#00008b", + darkcyan :"#008b8b", + darkgoldenrod :"#b8860b", + darkgray :"#a9a9a9", + darkgreen :"#006400", + darkkhaki :"#bdb76b", + darkmagenta :"#8b008b", + darkolivegreen :"#556b2f", + darkorange :"#ff8c00", + darkorchid :"#9932cc", + darkred :"#8b0000", + darksalmon :"#e9967a", + darkseagreen :"#8fbc8f", + darkslateblue :"#483d8b", + darkslategray :"#2f4f4f", + darkturquoise :"#00ced1", + darkviolet :"#9400d3", + deeppink :"#ff1493", + deepskyblue :"#00bfff", + dimgray :"#696969", + dodgerblue :"#1e90ff", + firebrick :"#b22222", + floralwhite :"#fffaf0", + forestgreen :"#228b22", + fuchsia :"#ff00ff", + gainsboro :"#dcdcdc", + ghostwhite :"#f8f8ff", + gold :"#ffd700", + goldenrod :"#daa520", + gray :"#808080", + green :"#008000", + greenyellow :"#adff2f", + honeydew :"#f0fff0", + hotpink :"#ff69b4", + indianred :"#cd5c5c", + indigo :"#4b0082", + ivory :"#fffff0", + khaki :"#f0e68c", + lavender :"#e6e6fa", + lavenderblush :"#fff0f5", + lawngreen :"#7cfc00", + lemonchiffon :"#fffacd", + lightblue :"#add8e6", + lightcoral :"#f08080", + lightcyan :"#e0ffff", + lightgoldenrodyellow :"#fafad2", + lightgrey :"#d3d3d3", + lightgreen :"#90ee90", + lightpink :"#ffb6c1", + lightsalmon :"#ffa07a", + lightseagreen :"#20b2aa", + lightskyblue :"#87cefa", + lightslategray :"#778899", + lightsteelblue :"#b0c4de", + lightyellow :"#ffffe0", + lime :"#00ff00", + limegreen :"#32cd32", + linen :"#faf0e6", + magenta :"#ff00ff", + maroon :"#800000", + mediumaquamarine:"#66cdaa", + mediumblue :"#0000cd", + mediumorchid :"#ba55d3", + mediumpurple :"#9370d8", + mediumseagreen :"#3cb371", + mediumslateblue :"#7b68ee", + mediumspringgreen :"#00fa9a", + mediumturquoise :"#48d1cc", + mediumvioletred :"#c71585", + midnightblue :"#191970", + mintcream :"#f5fffa", + mistyrose :"#ffe4e1", + moccasin :"#ffe4b5", + navajowhite :"#ffdead", + navy :"#000080", + oldlace :"#fdf5e6", + olive :"#808000", + olivedrab :"#6b8e23", + orange :"#ffa500", + orangered :"#ff4500", + orchid :"#da70d6", + palegoldenrod :"#eee8aa", + palegreen :"#98fb98", + paleturquoise :"#afeeee", + palevioletred :"#d87093", + papayawhip :"#ffefd5", + peachpuff :"#ffdab9", + peru :"#cd853f", + pink :"#ffc0cb", + plum :"#dda0dd", + powderblue :"#b0e0e6", + purple :"#800080", + red :"#ff0000", + rosybrown :"#bc8f8f", + royalblue :"#4169e1", + saddlebrown :"#8b4513", + salmon :"#fa8072", + sandybrown :"#f4a460", + seagreen :"#2e8b57", + seashell :"#fff5ee", + sienna :"#a0522d", + silver :"#c0c0c0", + skyblue :"#87ceeb", + slateblue :"#6a5acd", + slategray :"#708090", + snow :"#fffafa", + springgreen :"#00ff7f", + steelblue :"#4682b4", + tan :"#d2b48c", + teal :"#008080", + thistle :"#d8bfd8", + tomato :"#ff6347", + turquoise :"#40e0d0", + violet :"#ee82ee", + wheat :"#f5deb3", + white :"#ffffff", + whitesmoke :"#f5f5f5", + yellow :"#ffff00", + yellowgreen :"#9acd32" +}; +/** + * Represents a selector combinator (whitespace, +, >). + * @namespace parserlib.css + * @class Combinator + * @extends parserlib.util.SyntaxUnit + * @constructor + * @param {String} text The text representation of the unit. + * @param {int} line The line of text on which the unit resides. + * @param {int} col The column of text on which the unit resides. + */ +function Combinator(text, line, col){ + + SyntaxUnit.call(this, text, line, col); + + /** + * The type of modifier. + * @type String + * @property type + */ + this.type = "unknown"; + + //pretty simple + if (/^\s+$/.test(text)){ + this.type = "descendant"; + } else if (text == ">"){ + this.type = "child"; + } else if (text == "+"){ + this.type = "adjacent-sibling"; + } else if (text == "~"){ + this.type = "sibling"; + } + +} + +Combinator.prototype = new SyntaxUnit(); +Combinator.prototype.constructor = Combinator; + + +var Level1Properties = { + + "background": 1, + "background-attachment": 1, + "background-color": 1, + "background-image": 1, + "background-position": 1, + "background-repeat": 1, + + "border": 1, + "border-bottom": 1, + "border-bottom-width": 1, + "border-color": 1, + "border-left": 1, + "border-left-width": 1, + "border-right": 1, + "border-right-width": 1, + "border-style": 1, + "border-top": 1, + "border-top-width": 1, + "border-width": 1, + + "clear": 1, + "color": 1, + "display": 1, + "float": 1, + + "font": 1, + "font-family": 1, + "font-size": 1, + "font-style": 1, + "font-variant": 1, + "font-weight": 1, + + "height": 1, + "letter-spacing": 1, + "line-height": 1, + + "list-style": 1, + "list-style-image": 1, + "list-style-position": 1, + "list-style-type": 1, + + "margin": 1, + "margin-bottom": 1, + "margin-left": 1, + "margin-right": 1, + "margin-top": 1, + + "padding": 1, + "padding-bottom": 1, + "padding-left": 1, + "padding-right": 1, + "padding-top": 1, + + "text-align": 1, + "text-decoration": 1, + "text-indent": 1, + "text-transform": 1, + + "vertical-align": 1, + "white-space": 1, + "width": 1, + "word-spacing": 1 + +}; + +var Level2Properties = { + + //Aural + "azimuth": 1, + "cue-after": 1, + "cue-before": 1, + "cue": 1, + "elevation": 1, + "pause-after": 1, + "pause-before": 1, + "pause": 1, + "pitch-range": 1, + "pitch": 1, + "play-during": 1, + "richness": 1, + "speak-header": 1, + "speak-numeral": 1, + "speak-punctuation": 1, + "speak": 1, + "speech-rate": 1, + "stress": 1, + "voice-family": 1, + "volume": 1, + + //Paged + "orphans": 1, + "page-break-after": 1, + "page-break-before": 1, + "page-break-inside": 1, + "widows": 1, + + //Interactive + "cursor": 1, + "outline-color": 1, + "outline-style": 1, + "outline-width": 1, + "outline": 1, + + //Visual + "background-attachment": 1, + "background-color": 1, + "background-image": 1, + "background-position": 1, + "background-repeat": 1, + "background": 1, + "border-collapse": 1, + "border-color": 1, + "border-spacing": 1, + "border-style": 1, + "border-top": 1, + "border-top-color": 1, + "border-top-style": 1, + "border-top-width": 1, + "border-width": 1, + "border": 1, + "bottom": 1, + "caption-side": 1, + "clear": 1, + "clip": 1, + "color": 1, + "content": 1, + "counter-increment": 1, + "counter-reset": 1, + "direction": 1, + "display": 1, + "empty-cells": 1, + "float": 1, + "font-family": 1, + "font-size": 1, + "font-style": 1, + "font-variant": 1, + "font-weight": 1, + "font": 1, + "height": 1, + "left": 1, + "letter-spacing": 1, + "line-height": 1, + "list-style-image": 1, + "list-style-position": 1, + "list-style-type": 1, + "list-style": 1, + "margin-right": 1, + "margin-top": 1, + "margin": 1, + "max-height": 1, + "max-width": 1, + "min-height": 1, + "min-width": 1, + "overflow": 1, + "padding-top": 1, + "padding": 1, + "position": 1, + "quotes": 1, + "right": 1, + "table-layout": 1, + "text-align": 1, + "text-decoration": 1, + "text-indent": 1, + "text-transform": 1, + "top": 1, + "unicode-bidi": 1, + "vertical-align": 1, + "visibility": 1, + "white-space": 1, + "width": 1, + "word-spacing": 1, + "z-index": 1 +}; +/** + * Represents a media feature, such as max-width:500. + * @namespace parserlib.css + * @class MediaFeature + * @extends parserlib.util.SyntaxUnit + * @constructor + * @param {SyntaxUnit} name The name of the feature. + * @param {SyntaxUnit} value The value of the feature or null if none. + */ +function MediaFeature(name, value){ + + SyntaxUnit.call(this, "(" + name + (value !== null ? ":" + value : "") + ")", name.startLine, name.startCol); + + /** + * The name of the media feature + * @type String + * @property name + */ + this.name = name; + + /** + * The value for the feature or null if there is none. + * @type SyntaxUnit + * @property value + */ + this.value = value; +} + +MediaFeature.prototype = new SyntaxUnit(); +MediaFeature.prototype.constructor = MediaFeature; + +/** + * Represents an individual media query. + * @namespace parserlib.css + * @class MediaQuery + * @extends parserlib.util.SyntaxUnit + * @constructor + * @param {String} modifier The modifier "not" or "only" (or null). + * @param {String} mediaType The type of media (i.e., "print"). + * @param {Array} parts Array of selectors parts making up this selector. + * @param {int} line The line of text on which the unit resides. + * @param {int} col The column of text on which the unit resides. + */ +function MediaQuery(modifier, mediaType, features, line, col){ + + SyntaxUnit.call(this, (modifier ? modifier + " ": "") + (mediaType ? mediaType + " " : "") + features.join(" and "), line, col); + + /** + * The media modifier ("not" or "only") + * @type String + * @property modifier + */ + this.modifier = modifier; + + /** + * The mediaType (i.e., "print") + * @type String + * @property mediaType + */ + this.mediaType = mediaType; + + /** + * The parts that make up the selector. + * @type Array + * @property features + */ + this.features = features; + +} + +MediaQuery.prototype = new SyntaxUnit(); +MediaQuery.prototype.constructor = MediaQuery; + +/** + * A CSS3 parser. + * @namespace parserlib.css + * @class Parser + * @constructor + * @param {Object} options (Optional) Various options for the parser: + * starHack (true|false) to allow IE6 star hack as valid, + * underscoreHack (true|false) to interpret leading underscores + * as IE6-7 targeting for known properties, ieFilters (true|false) + * to indicate that IE < 8 filters should be accepted and not throw + * syntax errors. + */ +function Parser(options){ + + //inherit event functionality + EventTarget.call(this); + + + this.options = options || {}; + + this._tokenStream = null; +} + +Parser.prototype = function(){ + + var proto = new EventTarget(), //new prototype + prop, + additions = { + + //restore constructor + constructor: Parser, + + //----------------------------------------------------------------- + // Grammar + //----------------------------------------------------------------- + + _stylesheet: function(){ + + /* + * stylesheet + * : [ CHARSET_SYM S* STRING S* ';' ]? + * [S|CDO|CDC]* [ import [S|CDO|CDC]* ]* + * [ namespace [S|CDO|CDC]* ]* + * [ [ ruleset | media | page | font_face | keyframes ] [S|CDO|CDC]* ]* + * ; + */ + + var tokenStream = this._tokenStream, + charset = null, + token, + tt; + + this.fire("startstylesheet"); + + //try to read character set + this._charset(); + + this._skipCruft(); + + //try to read imports - may be more than one + while (tokenStream.peek() == Tokens.IMPORT_SYM){ + this._import(); + this._skipCruft(); + } + + //try to read namespaces - may be more than one + while (tokenStream.peek() == Tokens.NAMESPACE_SYM){ + this._namespace(); + this._skipCruft(); + } + + //get the next token + tt = tokenStream.peek(); + + //try to read the rest + while(tt > Tokens.EOF){ + + try { + + switch(tt){ + case Tokens.MEDIA_SYM: + this._media(); + this._skipCruft(); + break; + case Tokens.PAGE_SYM: + this._page(); + this._skipCruft(); + break; + case Tokens.FONT_FACE_SYM: + this._font_face(); + this._skipCruft(); + break; + case Tokens.KEYFRAMES_SYM: + this._keyframes(); + this._skipCruft(); + break; + case Tokens.S: + this._readWhitespace(); + break; + default: + if(!this._ruleset()){ + + //error handling for known issues + switch(tt){ + case Tokens.CHARSET_SYM: + token = tokenStream.LT(1); + this._charset(false); + throw new SyntaxError("@charset not allowed here.", token.startLine, token.startCol); + case Tokens.IMPORT_SYM: + token = tokenStream.LT(1); + this._import(false); + throw new SyntaxError("@import not allowed here.", token.startLine, token.startCol); + case Tokens.NAMESPACE_SYM: + token = tokenStream.LT(1); + this._namespace(false); + throw new SyntaxError("@namespace not allowed here.", token.startLine, token.startCol); + default: + tokenStream.get(); //get the last token + this._unexpectedToken(tokenStream.token()); + } + + } + } + } catch(ex) { + if (ex instanceof SyntaxError && !this.options.strict){ + this.fire({ + type: "error", + error: ex, + message: ex.message, + line: ex.line, + col: ex.col + }); + } else { + throw ex; + } + } + + tt = tokenStream.peek(); + } + + if (tt != Tokens.EOF){ + this._unexpectedToken(tokenStream.token()); + } + + this.fire("endstylesheet"); + }, + + _charset: function(emit){ + var tokenStream = this._tokenStream, + charset, + token, + line, + col; + + if (tokenStream.match(Tokens.CHARSET_SYM)){ + line = tokenStream.token().startLine; + col = tokenStream.token().startCol; + + this._readWhitespace(); + tokenStream.mustMatch(Tokens.STRING); + + token = tokenStream.token(); + charset = token.value; + + this._readWhitespace(); + tokenStream.mustMatch(Tokens.SEMICOLON); + + if (emit !== false){ + this.fire({ + type: "charset", + charset:charset, + line: line, + col: col + }); + } + } + }, + + _import: function(emit){ + /* + * import + * : IMPORT_SYM S* + * [STRING|URI] S* media_query_list? ';' S* + */ + + var tokenStream = this._tokenStream, + tt, + uri, + importToken, + mediaList = []; + + //read import symbol + tokenStream.mustMatch(Tokens.IMPORT_SYM); + importToken = tokenStream.token(); + this._readWhitespace(); + + tokenStream.mustMatch([Tokens.STRING, Tokens.URI]); + + //grab the URI value + uri = tokenStream.token().value.replace(/(?:url\()?["']([^"']+)["']\)?/, "$1"); + + this._readWhitespace(); + + mediaList = this._media_query_list(); + + //must end with a semicolon + tokenStream.mustMatch(Tokens.SEMICOLON); + this._readWhitespace(); + + if (emit !== false){ + this.fire({ + type: "import", + uri: uri, + media: mediaList, + line: importToken.startLine, + col: importToken.startCol + }); + } + + }, + + _namespace: function(emit){ + /* + * namespace + * : NAMESPACE_SYM S* [namespace_prefix S*]? [STRING|URI] S* ';' S* + */ + + var tokenStream = this._tokenStream, + line, + col, + prefix, + uri; + + //read import symbol + tokenStream.mustMatch(Tokens.NAMESPACE_SYM); + line = tokenStream.token().startLine; + col = tokenStream.token().startCol; + this._readWhitespace(); + + //it's a namespace prefix - no _namespace_prefix() method because it's just an IDENT + if (tokenStream.match(Tokens.IDENT)){ + prefix = tokenStream.token().value; + this._readWhitespace(); + } + + tokenStream.mustMatch([Tokens.STRING, Tokens.URI]); + /*if (!tokenStream.match(Tokens.STRING)){ + tokenStream.mustMatch(Tokens.URI); + }*/ + + //grab the URI value + uri = tokenStream.token().value.replace(/(?:url\()?["']([^"']+)["']\)?/, "$1"); + + this._readWhitespace(); + + //must end with a semicolon + tokenStream.mustMatch(Tokens.SEMICOLON); + this._readWhitespace(); + + if (emit !== false){ + this.fire({ + type: "namespace", + prefix: prefix, + uri: uri, + line: line, + col: col + }); + } + + }, + + _media: function(){ + /* + * media + * : MEDIA_SYM S* media_query_list S* '{' S* ruleset* '}' S* + * ; + */ + var tokenStream = this._tokenStream, + line, + col, + mediaList;// = []; + + //look for @media + tokenStream.mustMatch(Tokens.MEDIA_SYM); + line = tokenStream.token().startLine; + col = tokenStream.token().startCol; + + this._readWhitespace(); + + mediaList = this._media_query_list(); + + tokenStream.mustMatch(Tokens.LBRACE); + this._readWhitespace(); + + this.fire({ + type: "startmedia", + media: mediaList, + line: line, + col: col + }); + + while(true) { + if (tokenStream.peek() == Tokens.PAGE_SYM){ + this._page(); + } else if (!this._ruleset()){ + break; + } + } + + tokenStream.mustMatch(Tokens.RBRACE); + this._readWhitespace(); + + this.fire({ + type: "endmedia", + media: mediaList, + line: line, + col: col + }); + }, + + + //CSS3 Media Queries + _media_query_list: function(){ + /* + * media_query_list + * : S* [media_query [ ',' S* media_query ]* ]? + * ; + */ + var tokenStream = this._tokenStream, + mediaList = []; + + + this._readWhitespace(); + + if (tokenStream.peek() == Tokens.IDENT || tokenStream.peek() == Tokens.LPAREN){ + mediaList.push(this._media_query()); + } + + while(tokenStream.match(Tokens.COMMA)){ + this._readWhitespace(); + mediaList.push(this._media_query()); + } + + return mediaList; + }, + + /* + * Note: "expression" in the grammar maps to the _media_expression + * method. + + */ + _media_query: function(){ + /* + * media_query + * : [ONLY | NOT]? S* media_type S* [ AND S* expression ]* + * | expression [ AND S* expression ]* + * ; + */ + var tokenStream = this._tokenStream, + type = null, + ident = null, + token = null, + expressions = []; + + if (tokenStream.match(Tokens.IDENT)){ + ident = tokenStream.token().value.toLowerCase(); + + //since there's no custom tokens for these, need to manually check + if (ident != "only" && ident != "not"){ + tokenStream.unget(); + ident = null; + } else { + token = tokenStream.token(); + } + } + + this._readWhitespace(); + + if (tokenStream.peek() == Tokens.IDENT){ + type = this._media_type(); + if (token === null){ + token = tokenStream.token(); + } + } else if (tokenStream.peek() == Tokens.LPAREN){ + if (token === null){ + token = tokenStream.LT(1); + } + expressions.push(this._media_expression()); + } + + if (type === null && expressions.length === 0){ + return null; + } else { + this._readWhitespace(); + while (tokenStream.match(Tokens.IDENT)){ + if (tokenStream.token().value.toLowerCase() != "and"){ + this._unexpectedToken(tokenStream.token()); + } + + this._readWhitespace(); + expressions.push(this._media_expression()); + } + } + + return new MediaQuery(ident, type, expressions, token.startLine, token.startCol); + }, + + //CSS3 Media Queries + _media_type: function(){ + /* + * media_type + * : IDENT + * ; + */ + return this._media_feature(); + }, + + /** + * Note: in CSS3 Media Queries, this is called "expression". + * Renamed here to avoid conflict with CSS3 Selectors + * definition of "expression". Also note that "expr" in the + * grammar now maps to "expression" from CSS3 selectors. + * @method _media_expression + * @private + */ + _media_expression: function(){ + /* + * expression + * : '(' S* media_feature S* [ ':' S* expr ]? ')' S* + * ; + */ + var tokenStream = this._tokenStream, + feature = null, + token, + expression = null; + + tokenStream.mustMatch(Tokens.LPAREN); + + feature = this._media_feature(); + this._readWhitespace(); + + if (tokenStream.match(Tokens.COLON)){ + this._readWhitespace(); + token = tokenStream.LT(1); + expression = this._expression(); + } + + tokenStream.mustMatch(Tokens.RPAREN); + this._readWhitespace(); + + return new MediaFeature(feature, (expression ? new SyntaxUnit(expression, token.startLine, token.startCol) : null)); + }, + + //CSS3 Media Queries + _media_feature: function(){ + /* + * media_feature + * : IDENT + * ; + */ + var tokenStream = this._tokenStream; + + tokenStream.mustMatch(Tokens.IDENT); + + return SyntaxUnit.fromToken(tokenStream.token()); + }, + + //CSS3 Paged Media + _page: function(){ + /* + * page: + * PAGE_SYM S* IDENT? pseudo_page? S* + * '{' S* [ declaration | margin ]? [ ';' S* [ declaration | margin ]? ]* '}' S* + * ; + */ + var tokenStream = this._tokenStream, + line, + col, + identifier = null, + pseudoPage = null; + + //look for @page + tokenStream.mustMatch(Tokens.PAGE_SYM); + line = tokenStream.token().startLine; + col = tokenStream.token().startCol; + + this._readWhitespace(); + + if (tokenStream.match(Tokens.IDENT)){ + identifier = tokenStream.token().value; + + //The value 'auto' may not be used as a page name and MUST be treated as a syntax error. + if (identifier.toLowerCase() === "auto"){ + this._unexpectedToken(tokenStream.token()); + } + } + + //see if there's a colon upcoming + if (tokenStream.peek() == Tokens.COLON){ + pseudoPage = this._pseudo_page(); + } + + this._readWhitespace(); + + this.fire({ + type: "startpage", + id: identifier, + pseudo: pseudoPage, + line: line, + col: col + }); + + this._readDeclarations(true, true); + + this.fire({ + type: "endpage", + id: identifier, + pseudo: pseudoPage, + line: line, + col: col + }); + + }, + + //CSS3 Paged Media + _margin: function(){ + /* + * margin : + * margin_sym S* '{' declaration [ ';' S* declaration? ]* '}' S* + * ; + */ + var tokenStream = this._tokenStream, + line, + col, + marginSym = this._margin_sym(); + + if (marginSym){ + line = tokenStream.token().startLine; + col = tokenStream.token().startCol; + + this.fire({ + type: "startpagemargin", + margin: marginSym, + line: line, + col: col + }); + + this._readDeclarations(true); + + this.fire({ + type: "endpagemargin", + margin: marginSym, + line: line, + col: col + }); + return true; + } else { + return false; + } + }, + + //CSS3 Paged Media + _margin_sym: function(){ + + /* + * margin_sym : + * TOPLEFTCORNER_SYM | + * TOPLEFT_SYM | + * TOPCENTER_SYM | + * TOPRIGHT_SYM | + * TOPRIGHTCORNER_SYM | + * BOTTOMLEFTCORNER_SYM | + * BOTTOMLEFT_SYM | + * BOTTOMCENTER_SYM | + * BOTTOMRIGHT_SYM | + * BOTTOMRIGHTCORNER_SYM | + * LEFTTOP_SYM | + * LEFTMIDDLE_SYM | + * LEFTBOTTOM_SYM | + * RIGHTTOP_SYM | + * RIGHTMIDDLE_SYM | + * RIGHTBOTTOM_SYM + * ; + */ + + var tokenStream = this._tokenStream; + + if(tokenStream.match([Tokens.TOPLEFTCORNER_SYM, Tokens.TOPLEFT_SYM, + Tokens.TOPCENTER_SYM, Tokens.TOPRIGHT_SYM, Tokens.TOPRIGHTCORNER_SYM, + Tokens.BOTTOMLEFTCORNER_SYM, Tokens.BOTTOMLEFT_SYM, + Tokens.BOTTOMCENTER_SYM, Tokens.BOTTOMRIGHT_SYM, + Tokens.BOTTOMRIGHTCORNER_SYM, Tokens.LEFTTOP_SYM, + Tokens.LEFTMIDDLE_SYM, Tokens.LEFTBOTTOM_SYM, Tokens.RIGHTTOP_SYM, + Tokens.RIGHTMIDDLE_SYM, Tokens.RIGHTBOTTOM_SYM])) + { + return SyntaxUnit.fromToken(tokenStream.token()); + } else { + return null; + } + + }, + + _pseudo_page: function(){ + /* + * pseudo_page + * : ':' IDENT + * ; + */ + + var tokenStream = this._tokenStream; + + tokenStream.mustMatch(Tokens.COLON); + tokenStream.mustMatch(Tokens.IDENT); + + //TODO: CSS3 Paged Media says only "left", "center", and "right" are allowed + + return tokenStream.token().value; + }, + + _font_face: function(){ + /* + * font_face + * : FONT_FACE_SYM S* + * '{' S* declaration [ ';' S* declaration ]* '}' S* + * ; + */ + var tokenStream = this._tokenStream, + line, + col; + + //look for @page + tokenStream.mustMatch(Tokens.FONT_FACE_SYM); + line = tokenStream.token().startLine; + col = tokenStream.token().startCol; + + this._readWhitespace(); + + this.fire({ + type: "startfontface", + line: line, + col: col + }); + + this._readDeclarations(true); + + this.fire({ + type: "endfontface", + line: line, + col: col + }); + }, + + _operator: function(){ + + /* + * operator + * : '/' S* | ',' S* | /( empty )/ + * ; + */ + + var tokenStream = this._tokenStream, + token = null; + + if (tokenStream.match([Tokens.SLASH, Tokens.COMMA])){ + token = tokenStream.token(); + this._readWhitespace(); + } + return token ? PropertyValuePart.fromToken(token) : null; + + }, + + _combinator: function(){ + + /* + * combinator + * : PLUS S* | GREATER S* | TILDE S* | S+ + * ; + */ + + var tokenStream = this._tokenStream, + value = null, + token; + + if(tokenStream.match([Tokens.PLUS, Tokens.GREATER, Tokens.TILDE])){ + token = tokenStream.token(); + value = new Combinator(token.value, token.startLine, token.startCol); + this._readWhitespace(); + } + + return value; + }, + + _unary_operator: function(){ + + /* + * unary_operator + * : '-' | '+' + * ; + */ + + var tokenStream = this._tokenStream; + + if (tokenStream.match([Tokens.MINUS, Tokens.PLUS])){ + return tokenStream.token().value; + } else { + return null; + } + }, + + _property: function(){ + + /* + * property + * : IDENT S* + * ; + */ + + var tokenStream = this._tokenStream, + value = null, + hack = null, + tokenValue, + token, + line, + col; + + //check for star hack - throws error if not allowed + if (tokenStream.peek() == Tokens.STAR && this.options.starHack){ + tokenStream.get(); + token = tokenStream.token(); + hack = token.value; + line = token.startLine; + col = token.startCol; + } + + if(tokenStream.match(Tokens.IDENT)){ + token = tokenStream.token(); + tokenValue = token.value; + + //check for underscore hack - no error if not allowed because it's valid CSS syntax + if (tokenValue.charAt(0) == "_" && this.options.underscoreHack){ + hack = "_"; + tokenValue = tokenValue.substring(1); + } + + value = new PropertyName(tokenValue, hack, (line||token.startLine), (col||token.startCol)); + this._readWhitespace(); + } + + return value; + }, + + //Augmented with CSS3 Selectors + _ruleset: function(){ + /* + * ruleset + * : selectors_group + * '{' S* declaration? [ ';' S* declaration? ]* '}' S* + * ; + */ + + var tokenStream = this._tokenStream, + tt, + selectors; + + + /* + * Error Recovery: If even a single selector fails to parse, + * then the entire ruleset should be thrown away. + */ + try { + selectors = this._selectors_group(); + } catch (ex){ + if (ex instanceof SyntaxError && !this.options.strict){ + + //fire error event + this.fire({ + type: "error", + error: ex, + message: ex.message, + line: ex.line, + col: ex.col + }); + + //skip over everything until closing brace + tt = tokenStream.advance([Tokens.RBRACE]); + if (tt == Tokens.RBRACE){ + //if there's a right brace, the rule is finished so don't do anything + } else { + //otherwise, rethrow the error because it wasn't handled properly + throw ex; + } + + } else { + //not a syntax error, rethrow it + throw ex; + } + + //trigger parser to continue + return true; + } + + //if it got here, all selectors parsed + if (selectors){ + + this.fire({ + type: "startrule", + selectors: selectors, + line: selectors[0].line, + col: selectors[0].col + }); + + this._readDeclarations(true); + + this.fire({ + type: "endrule", + selectors: selectors, + line: selectors[0].line, + col: selectors[0].col + }); + + } + + return selectors; + + }, + + //CSS3 Selectors + _selectors_group: function(){ + + /* + * selectors_group + * : selector [ COMMA S* selector ]* + * ; + */ + var tokenStream = this._tokenStream, + selectors = [], + selector; + + selector = this._selector(); + if (selector !== null){ + + selectors.push(selector); + while(tokenStream.match(Tokens.COMMA)){ + this._readWhitespace(); + selector = this._selector(); + if (selector !== null){ + selectors.push(selector); + } else { + this._unexpectedToken(tokenStream.LT(1)); + } + } + } + + return selectors.length ? selectors : null; + }, + + //CSS3 Selectors + _selector: function(){ + /* + * selector + * : simple_selector_sequence [ combinator simple_selector_sequence ]* + * ; + */ + + var tokenStream = this._tokenStream, + selector = [], + nextSelector = null, + combinator = null, + ws = null; + + //if there's no simple selector, then there's no selector + nextSelector = this._simple_selector_sequence(); + if (nextSelector === null){ + return null; + } + + selector.push(nextSelector); + + do { + + //look for a combinator + combinator = this._combinator(); + + if (combinator !== null){ + selector.push(combinator); + nextSelector = this._simple_selector_sequence(); + + //there must be a next selector + if (nextSelector === null){ + this._unexpectedToken(this.LT(1)); + } else { + + //nextSelector is an instance of SelectorPart + selector.push(nextSelector); + } + } else { + + //if there's not whitespace, we're done + if (this._readWhitespace()){ + + //add whitespace separator + ws = new Combinator(tokenStream.token().value, tokenStream.token().startLine, tokenStream.token().startCol); + + //combinator is not required + combinator = this._combinator(); + + //selector is required if there's a combinator + nextSelector = this._simple_selector_sequence(); + if (nextSelector === null){ + if (combinator !== null){ + this._unexpectedToken(tokenStream.LT(1)); + } + } else { + + if (combinator !== null){ + selector.push(combinator); + } else { + selector.push(ws); + } + + selector.push(nextSelector); + } + } else { + break; + } + + } + } while(true); + + return new Selector(selector, selector[0].line, selector[0].col); + }, + + //CSS3 Selectors + _simple_selector_sequence: function(){ + /* + * simple_selector_sequence + * : [ type_selector | universal ] + * [ HASH | class | attrib | pseudo | negation ]* + * | [ HASH | class | attrib | pseudo | negation ]+ + * ; + */ + + var tokenStream = this._tokenStream, + + //parts of a simple selector + elementName = null, + modifiers = [], + + //complete selector text + selectorText= "", + + //the different parts after the element name to search for + components = [ + //HASH + function(){ + return tokenStream.match(Tokens.HASH) ? + new SelectorSubPart(tokenStream.token().value, "id", tokenStream.token().startLine, tokenStream.token().startCol) : + null; + }, + this._class, + this._attrib, + this._pseudo, + this._negation + ], + i = 0, + len = components.length, + component = null, + found = false, + line, + col; + + + //get starting line and column for the selector + line = tokenStream.LT(1).startLine; + col = tokenStream.LT(1).startCol; + + elementName = this._type_selector(); + if (!elementName){ + elementName = this._universal(); + } + + if (elementName !== null){ + selectorText += elementName; + } + + while(true){ + + //whitespace means we're done + if (tokenStream.peek() === Tokens.S){ + break; + } + + //check for each component + while(i < len && component === null){ + component = components[i++].call(this); + } + + if (component === null){ + + //we don't have a selector + if (selectorText === ""){ + return null; + } else { + break; + } + } else { + i = 0; + modifiers.push(component); + selectorText += component.toString(); + component = null; + } + } + + + return selectorText !== "" ? + new SelectorPart(elementName, modifiers, selectorText, line, col) : + null; + }, + + //CSS3 Selectors + _type_selector: function(){ + /* + * type_selector + * : [ namespace_prefix ]? element_name + * ; + */ + + var tokenStream = this._tokenStream, + ns = this._namespace_prefix(), + elementName = this._element_name(); + + if (!elementName){ + /* + * Need to back out the namespace that was read due to both + * type_selector and universal reading namespace_prefix + * first. Kind of hacky, but only way I can figure out + * right now how to not change the grammar. + */ + if (ns){ + tokenStream.unget(); + if (ns.length > 1){ + tokenStream.unget(); + } + } + + return null; + } else { + if (ns){ + elementName.text = ns + elementName.text; + elementName.col -= ns.length; + } + return elementName; + } + }, + + //CSS3 Selectors + _class: function(){ + /* + * class + * : '.' IDENT + * ; + */ + + var tokenStream = this._tokenStream, + token; + + if (tokenStream.match(Tokens.DOT)){ + tokenStream.mustMatch(Tokens.IDENT); + token = tokenStream.token(); + return new SelectorSubPart("." + token.value, "class", token.startLine, token.startCol - 1); + } else { + return null; + } + + }, + + //CSS3 Selectors + _element_name: function(){ + /* + * element_name + * : IDENT + * ; + */ + + var tokenStream = this._tokenStream, + token; + + if (tokenStream.match(Tokens.IDENT)){ + token = tokenStream.token(); + return new SelectorSubPart(token.value, "elementName", token.startLine, token.startCol); + + } else { + return null; + } + }, + + //CSS3 Selectors + _namespace_prefix: function(){ + /* + * namespace_prefix + * : [ IDENT | '*' ]? '|' + * ; + */ + var tokenStream = this._tokenStream, + value = ""; + + //verify that this is a namespace prefix + if (tokenStream.LA(1) === Tokens.PIPE || tokenStream.LA(2) === Tokens.PIPE){ + + if(tokenStream.match([Tokens.IDENT, Tokens.STAR])){ + value += tokenStream.token().value; + } + + tokenStream.mustMatch(Tokens.PIPE); + value += "|"; + + } + + return value.length ? value : null; + }, + + //CSS3 Selectors + _universal: function(){ + /* + * universal + * : [ namespace_prefix ]? '*' + * ; + */ + var tokenStream = this._tokenStream, + value = "", + ns; + + ns = this._namespace_prefix(); + if(ns){ + value += ns; + } + + if(tokenStream.match(Tokens.STAR)){ + value += "*"; + } + + return value.length ? value : null; + + }, + + //CSS3 Selectors + _attrib: function(){ + /* + * attrib + * : '[' S* [ namespace_prefix ]? IDENT S* + * [ [ PREFIXMATCH | + * SUFFIXMATCH | + * SUBSTRINGMATCH | + * '=' | + * INCLUDES | + * DASHMATCH ] S* [ IDENT | STRING ] S* + * ]? ']' + * ; + */ + + var tokenStream = this._tokenStream, + value = null, + ns, + token; + + if (tokenStream.match(Tokens.LBRACKET)){ + token = tokenStream.token(); + value = token.value; + value += this._readWhitespace(); + + ns = this._namespace_prefix(); + + if (ns){ + value += ns; + } + + tokenStream.mustMatch(Tokens.IDENT); + value += tokenStream.token().value; + value += this._readWhitespace(); + + if(tokenStream.match([Tokens.PREFIXMATCH, Tokens.SUFFIXMATCH, Tokens.SUBSTRINGMATCH, + Tokens.EQUALS, Tokens.INCLUDES, Tokens.DASHMATCH])){ + + value += tokenStream.token().value; + value += this._readWhitespace(); + + tokenStream.mustMatch([Tokens.IDENT, Tokens.STRING]); + value += tokenStream.token().value; + value += this._readWhitespace(); + } + + tokenStream.mustMatch(Tokens.RBRACKET); + + return new SelectorSubPart(value + "]", "attribute", token.startLine, token.startCol); + } else { + return null; + } + }, + + //CSS3 Selectors + _pseudo: function(){ + + /* + * pseudo + * : ':' ':'? [ IDENT | functional_pseudo ] + * ; + */ + + var tokenStream = this._tokenStream, + pseudo = null, + colons = ":", + line, + col; + + if (tokenStream.match(Tokens.COLON)){ + + if (tokenStream.match(Tokens.COLON)){ + colons += ":"; + } + + if (tokenStream.match(Tokens.IDENT)){ + pseudo = tokenStream.token().value; + line = tokenStream.token().startLine; + col = tokenStream.token().startCol - colons.length; + } else if (tokenStream.peek() == Tokens.FUNCTION){ + line = tokenStream.LT(1).startLine; + col = tokenStream.LT(1).startCol - colons.length; + pseudo = this._functional_pseudo(); + } + + if (pseudo){ + pseudo = new SelectorSubPart(colons + pseudo, "pseudo", line, col); + } + } + + return pseudo; + }, + + //CSS3 Selectors + _functional_pseudo: function(){ + /* + * functional_pseudo + * : FUNCTION S* expression ')' + * ; + */ + + var tokenStream = this._tokenStream, + value = null; + + if(tokenStream.match(Tokens.FUNCTION)){ + value = tokenStream.token().value; + value += this._readWhitespace(); + value += this._expression(); + tokenStream.mustMatch(Tokens.RPAREN); + value += ")"; + } + + return value; + }, + + //CSS3 Selectors + _expression: function(){ + /* + * expression + * : [ [ PLUS | '-' | DIMENSION | NUMBER | STRING | IDENT ] S* ]+ + * ; + */ + + var tokenStream = this._tokenStream, + value = ""; + + while(tokenStream.match([Tokens.PLUS, Tokens.MINUS, Tokens.DIMENSION, + Tokens.NUMBER, Tokens.STRING, Tokens.IDENT, Tokens.LENGTH, + Tokens.FREQ, Tokens.ANGLE, Tokens.TIME, + Tokens.RESOLUTION])){ + + value += tokenStream.token().value; + value += this._readWhitespace(); + } + + return value.length ? value : null; + + }, + + //CSS3 Selectors + _negation: function(){ + /* + * negation + * : NOT S* negation_arg S* ')' + * ; + */ + + var tokenStream = this._tokenStream, + line, + col, + value = "", + arg, + subpart = null; + + if (tokenStream.match(Tokens.NOT)){ + value = tokenStream.token().value; + line = tokenStream.token().startLine; + col = tokenStream.token().startCol; + value += this._readWhitespace(); + arg = this._negation_arg(); + value += arg; + value += this._readWhitespace(); + tokenStream.match(Tokens.RPAREN); + value += tokenStream.token().value; + + subpart = new SelectorSubPart(value, "not", line, col); + subpart.args.push(arg); + } + + return subpart; + }, + + //CSS3 Selectors + _negation_arg: function(){ + /* + * negation_arg + * : type_selector | universal | HASH | class | attrib | pseudo + * ; + */ + + var tokenStream = this._tokenStream, + args = [ + this._type_selector, + this._universal, + function(){ + return tokenStream.match(Tokens.HASH) ? + new SelectorSubPart(tokenStream.token().value, "id", tokenStream.token().startLine, tokenStream.token().startCol) : + null; + }, + this._class, + this._attrib, + this._pseudo + ], + arg = null, + i = 0, + len = args.length, + elementName, + line, + col, + part; + + line = tokenStream.LT(1).startLine; + col = tokenStream.LT(1).startCol; + + while(i < len && arg === null){ + + arg = args[i].call(this); + i++; + } + + //must be a negation arg + if (arg === null){ + this._unexpectedToken(tokenStream.LT(1)); + } + + //it's an element name + if (arg.type == "elementName"){ + part = new SelectorPart(arg, [], arg.toString(), line, col); + } else { + part = new SelectorPart(null, [arg], arg.toString(), line, col); + } + + return part; + }, + + _declaration: function(){ + + /* + * declaration + * : property ':' S* expr prio? + * | /( empty )/ + * ; + */ + + var tokenStream = this._tokenStream, + property = null, + expr = null, + prio = null, + error = null, + valid = true; + + property = this._property(); + if (property !== null){ + + tokenStream.mustMatch(Tokens.COLON); + this._readWhitespace(); + + expr = this._expr(); + + //if there's no parts for the value, it's an error + if (!expr || expr.length === 0){ + this._unexpectedToken(tokenStream.LT(1)); + } + + prio = this._prio(); + + try { + this._validateProperty(property, expr); + } catch (ex) { + valid = false; + error = ex; + } + + this.fire({ + type: "property", + property: property, + value: expr, + important: prio, + line: property.line, + col: property.col, + valid: valid, + error: error + }); + + return true; + } else { + return false; + } + }, + + _prio: function(){ + /* + * prio + * : IMPORTANT_SYM S* + * ; + */ + + var tokenStream = this._tokenStream, + result = tokenStream.match(Tokens.IMPORTANT_SYM); + + this._readWhitespace(); + return result; + }, + + _expr: function(){ + /* + * expr + * : term [ operator term ]* + * ; + */ + + var tokenStream = this._tokenStream, + values = [], + //valueParts = [], + value = null, + operator = null; + + value = this._term(); + if (value !== null){ + + values.push(value); + + do { + operator = this._operator(); + + //if there's an operator, keep building up the value parts + if (operator){ + values.push(operator); + } /*else { + //if there's not an operator, you have a full value + values.push(new PropertyValue(valueParts, valueParts[0].line, valueParts[0].col)); + valueParts = []; + }*/ + + value = this._term(); + + if (value === null){ + break; + } else { + values.push(value); + } + } while(true); + } + + //cleanup + /*if (valueParts.length){ + values.push(new PropertyValue(valueParts, valueParts[0].line, valueParts[0].col)); + }*/ + + return values.length > 0 ? new PropertyValue(values, values[0].startLine, values[0].startCol) : null; + }, + + _term: function(){ + + /* + * term + * : unary_operator? + * [ NUMBER S* | PERCENTAGE S* | LENGTH S* | ANGLE S* | + * TIME S* | FREQ S* | function | ie_function ] + * | STRING S* | IDENT S* | URI S* | UNICODERANGE S* | hexcolor + * ; + */ + + var tokenStream = this._tokenStream, + unary = null, + value = null, + line, + col; + + //returns the operator or null + unary = this._unary_operator(); + if (unary !== null){ + line = tokenStream.token().startLine; + col = tokenStream.token().startCol; + } + + //exception for IE filters + if (tokenStream.peek() == Tokens.IE_FUNCTION && this.options.ieFilters){ + + value = this._ie_function(); + if (unary === null){ + line = tokenStream.token().startLine; + col = tokenStream.token().startCol; + } + + //see if there's a simple match + } else if (tokenStream.match([Tokens.NUMBER, Tokens.PERCENTAGE, Tokens.LENGTH, + Tokens.ANGLE, Tokens.TIME, + Tokens.FREQ, Tokens.STRING, Tokens.IDENT, Tokens.URI, Tokens.UNICODE_RANGE])){ + + value = tokenStream.token().value; + if (unary === null){ + line = tokenStream.token().startLine; + col = tokenStream.token().startCol; + } + this._readWhitespace(); + } else { + + //see if it's a color + value = this._hexcolor(); + if (value === null){ + + //if there's no unary, get the start of the next token for line/col info + if (unary === null){ + line = tokenStream.LT(1).startLine; + col = tokenStream.LT(1).startCol; + } + + //has to be a function + if (value === null){ + + /* + * This checks for alpha(opacity=0) style of IE + * functions. IE_FUNCTION only presents progid: style. + */ + if (tokenStream.LA(3) == Tokens.EQUALS && this.options.ieFilters){ + value = this._ie_function(); + } else { + value = this._function(); + } + } + + /*if (value === null){ + return null; + //throw new Error("Expected identifier at line " + tokenStream.token().startLine + ", character " + tokenStream.token().startCol + "."); + }*/ + + } else { + if (unary === null){ + line = tokenStream.token().startLine; + col = tokenStream.token().startCol; + } + } + + } + + return value !== null ? + new PropertyValuePart(unary !== null ? unary + value : value, line, col) : + null; + + }, + + _function: function(){ + + /* + * function + * : FUNCTION S* expr ')' S* + * ; + */ + + var tokenStream = this._tokenStream, + functionText = null, + expr = null; + + if (tokenStream.match(Tokens.FUNCTION)){ + functionText = tokenStream.token().value; + this._readWhitespace(); + expr = this._expr(); + + tokenStream.match(Tokens.RPAREN); + functionText += expr + ")"; + this._readWhitespace(); + } + + return functionText; + }, + + _ie_function: function(){ + + /* (My own extension) + * ie_function + * : IE_FUNCTION S* IDENT '=' term [S* ','? IDENT '=' term]+ ')' S* + * ; + */ + + var tokenStream = this._tokenStream, + functionText = null, + expr = null, + lt; + + //IE function can begin like a regular function, too + if (tokenStream.match([Tokens.IE_FUNCTION, Tokens.FUNCTION])){ + functionText = tokenStream.token().value; + + do { + + if (this._readWhitespace()){ + functionText += tokenStream.token().value; + } + + //might be second time in the loop + if (tokenStream.LA(0) == Tokens.COMMA){ + functionText += tokenStream.token().value; + } + + tokenStream.match(Tokens.IDENT); + functionText += tokenStream.token().value; + + tokenStream.match(Tokens.EQUALS); + functionText += tokenStream.token().value; + + //functionText += this._term(); + lt = tokenStream.peek(); + while(lt != Tokens.COMMA && lt != Tokens.S && lt != Tokens.RPAREN){ + tokenStream.get(); + functionText += tokenStream.token().value; + lt = tokenStream.peek(); + } + } while(tokenStream.match([Tokens.COMMA, Tokens.S])); + + tokenStream.match(Tokens.RPAREN); + functionText += ")"; + this._readWhitespace(); + } + + return functionText; + }, + + _hexcolor: function(){ + /* + * There is a constraint on the color that it must + * have either 3 or 6 hex-digits (i.e., [0-9a-fA-F]) + * after the "#"; e.g., "#000" is OK, but "#abcd" is not. + * + * hexcolor + * : HASH S* + * ; + */ + + var tokenStream = this._tokenStream, + token, + color = null; + + if(tokenStream.match(Tokens.HASH)){ + + //need to do some validation here + + token = tokenStream.token(); + color = token.value; + if (!/#[a-f0-9]{3,6}/i.test(color)){ + throw new SyntaxError("Expected a hex color but found '" + color + "' at line " + token.startLine + ", col " + token.startCol + ".", token.startLine, token.startCol); + } + this._readWhitespace(); + } + + return color; + }, + + //----------------------------------------------------------------- + // Animations methods + //----------------------------------------------------------------- + + _keyframes: function(){ + + /* + * keyframes: + * : KEYFRAMES_SYM S* keyframe_name S* '{' S* keyframe_rule* '}' { + * ; + */ + var tokenStream = this._tokenStream, + token, + tt, + name; + + tokenStream.mustMatch(Tokens.KEYFRAMES_SYM); + this._readWhitespace(); + name = this._keyframe_name(); + + this._readWhitespace(); + tokenStream.mustMatch(Tokens.LBRACE); + + this.fire({ + type: "startkeyframes", + name: name, + line: name.line, + col: name.col + }); + + this._readWhitespace(); + tt = tokenStream.peek(); + + //check for key + while(tt == Tokens.IDENT || tt == Tokens.PERCENTAGE) { + this._keyframe_rule(); + this._readWhitespace(); + tt = tokenStream.peek(); + } + + this.fire({ + type: "endkeyframes", + name: name, + line: name.line, + col: name.col + }); + + this._readWhitespace(); + tokenStream.mustMatch(Tokens.RBRACE); + + }, + + _keyframe_name: function(){ + + /* + * keyframe_name: + * : IDENT + * | STRING + * ; + */ + var tokenStream = this._tokenStream, + token; + + tokenStream.mustMatch([Tokens.IDENT, Tokens.STRING]); + return SyntaxUnit.fromToken(tokenStream.token()); + }, + + _keyframe_rule: function(){ + + /* + * keyframe_rule: + * : key_list S* + * '{' S* declaration [ ';' S* declaration ]* '}' S* + * ; + */ + var tokenStream = this._tokenStream, + token, + keyList = this._key_list(); + + this.fire({ + type: "startkeyframerule", + keys: keyList, + line: keyList[0].line, + col: keyList[0].col + }); + + this._readDeclarations(true); + + this.fire({ + type: "endkeyframerule", + keys: keyList, + line: keyList[0].line, + col: keyList[0].col + }); + + }, + + _key_list: function(){ + + /* + * key_list: + * : key [ S* ',' S* key]* + * ; + */ + var tokenStream = this._tokenStream, + token, + key, + keyList = []; + + //must be least one key + keyList.push(this._key()); + + this._readWhitespace(); + + while(tokenStream.match(Tokens.COMMA)){ + this._readWhitespace(); + keyList.push(this._key()); + this._readWhitespace(); + } + + return keyList; + }, + + _key: function(){ + /* + * There is a restriction that IDENT can be only "from" or "to". + * + * key + * : PERCENTAGE + * | IDENT + * ; + */ + + var tokenStream = this._tokenStream, + token; + + if (tokenStream.match(Tokens.PERCENTAGE)){ + return SyntaxUnit.fromToken(tokenStream.token()); + } else if (tokenStream.match(Tokens.IDENT)){ + token = tokenStream.token(); + + if (/from|to/i.test(token.value)){ + return SyntaxUnit.fromToken(token); + } + + tokenStream.unget(); + } + + //if it gets here, there wasn't a valid token, so time to explode + this._unexpectedToken(tokenStream.LT(1)); + }, + + //----------------------------------------------------------------- + // Helper methods + //----------------------------------------------------------------- + + /** + * Not part of CSS grammar, but useful for skipping over + * combination of white space and HTML-style comments. + * @return {void} + * @method _skipCruft + * @private + */ + _skipCruft: function(){ + while(this._tokenStream.match([Tokens.S, Tokens.CDO, Tokens.CDC])){ + //noop + } + }, + + /** + * Not part of CSS grammar, but this pattern occurs frequently + * in the official CSS grammar. Split out here to eliminate + * duplicate code. + * @param {Boolean} checkStart Indicates if the rule should check + * for the left brace at the beginning. + * @param {Boolean} readMargins Indicates if the rule should check + * for margin patterns. + * @return {void} + * @method _readDeclarations + * @private + */ + _readDeclarations: function(checkStart, readMargins){ + /* + * Reads the pattern + * S* '{' S* declaration [ ';' S* declaration ]* '}' S* + * or + * S* '{' S* [ declaration | margin ]? [ ';' S* [ declaration | margin ]? ]* '}' S* + * Note that this is how it is described in CSS3 Paged Media, but is actually incorrect. + * A semicolon is only necessary following a delcaration is there's another declaration + * or margin afterwards. + */ + var tokenStream = this._tokenStream, + tt; + + + this._readWhitespace(); + + if (checkStart){ + tokenStream.mustMatch(Tokens.LBRACE); + } + + this._readWhitespace(); + + try { + + while(true){ + + if (readMargins && this._margin()){ + //noop + } else if (this._declaration()){ + if (!tokenStream.match(Tokens.SEMICOLON)){ + break; + } + } else { + break; + } + + //if ((!this._margin() && !this._declaration()) || !tokenStream.match(Tokens.SEMICOLON)){ + // break; + //} + this._readWhitespace(); + } + + tokenStream.mustMatch(Tokens.RBRACE); + this._readWhitespace(); + + } catch (ex) { + if (ex instanceof SyntaxError && !this.options.strict){ + + //fire error event + this.fire({ + type: "error", + error: ex, + message: ex.message, + line: ex.line, + col: ex.col + }); + + //see if there's another declaration + tt = tokenStream.advance([Tokens.SEMICOLON, Tokens.RBRACE]); + if (tt == Tokens.SEMICOLON){ + //if there's a semicolon, then there might be another declaration + this._readDeclarations(false, readMargins); + } else if (tt == Tokens.RBRACE){ + //if there's a right brace, the rule is finished so don't do anything + } else { + //otherwise, rethrow the error because it wasn't handled properly + throw ex; + } + + } else { + //not a syntax error, rethrow it + throw ex; + } + } + + }, + + /** + * In some cases, you can end up with two white space tokens in a + * row. Instead of making a change in every function that looks for + * white space, this function is used to match as much white space + * as necessary. + * @method _readWhitespace + * @return {String} The white space if found, empty string if not. + * @private + */ + _readWhitespace: function(){ + + var tokenStream = this._tokenStream, + ws = ""; + + while(tokenStream.match(Tokens.S)){ + ws += tokenStream.token().value; + } + + return ws; + }, + + + /** + * Throws an error when an unexpected token is found. + * @param {Object} token The token that was found. + * @method _unexpectedToken + * @return {void} + * @private + */ + _unexpectedToken: function(token){ + throw new SyntaxError("Unexpected token '" + token.value + "' at line " + token.startLine + ", col " + token.startCol + ".", token.startLine, token.startCol); + }, + + /** + * Helper method used for parsing subparts of a style sheet. + * @return {void} + * @method _verifyEnd + * @private + */ + _verifyEnd: function(){ + if (this._tokenStream.LA(1) != Tokens.EOF){ + this._unexpectedToken(this._tokenStream.LT(1)); + } + }, + + //----------------------------------------------------------------- + // Validation methods + //----------------------------------------------------------------- + _validateProperty: function(property, value){ + var name = property.text.toLowerCase(), + validation, + i, len; + + if (Properties[name]){ + validation = Properties[name]; + if (typeof validation == "object"){ + for (i=0, len=validation.parts.length; i < len; i++){ + if (!validation.parts[i]){ + throw new ValidationError("Unexpected value. Expected only " + validation.parts.length + " values for property '" + property + "'.", + value.line, value.col); + } else if ((new RegExp("^("+validation.parts[i].types.join("|")+")$")).test(value.parts[i].type)){ + if (validation.parts[i][RegExp.$1]){ + if (!validation.parts[i][RegExp.$1].test(value.parts[i])){ + throw new ValidationError("Unexpected value '" + value.parts[i] + + "'.", value.parts[i].line, value.parts[i].col); + } + } + } else { + throw new ValidationError("Unexpected value type " + value.parts[i].type + + ". Expected " + validation.parts[i].types + ".", value.parts[i].line, value.parts[i].col); + } + } + } + + //otherwise, no validation available yet + } else if (name.indexOf("-") !== 0){ //vendor prefixed are ok + throw new ValidationError("Property '" + property + "' isn't recognized.", property.line, property.col); + } + }, + + //----------------------------------------------------------------- + // Parsing methods + //----------------------------------------------------------------- + + parse: function(input){ + this._tokenStream = new TokenStream(input, Tokens); + this._stylesheet(); + }, + + parseStyleSheet: function(input){ + //just passthrough + return this.parse(input); + }, + + parseMediaQuery: function(input){ + this._tokenStream = new TokenStream(input, Tokens); + var result = this._media_query(); + + //if there's anything more, then it's an invalid selector + this._verifyEnd(); + + //otherwise return result + return result; + }, + + /** + * Parses a property value (everything after the semicolon). + * @return {parserlib.css.PropertyValue} The property value. + * @throws parserlib.util.SyntaxError If an unexpected token is found. + * @method parserPropertyValue + */ + parsePropertyValue: function(input){ + + this._tokenStream = new TokenStream(input, Tokens); + this._readWhitespace(); + + var result = this._expr(); + + //okay to have a trailing white space + this._readWhitespace(); + + //if there's anything more, then it's an invalid selector + this._verifyEnd(); + + //otherwise return result + return result; + }, + + /** + * Parses a complete CSS rule, including selectors and + * properties. + * @param {String} input The text to parser. + * @return {Boolean} True if the parse completed successfully, false if not. + * @method parseRule + */ + parseRule: function(input){ + this._tokenStream = new TokenStream(input, Tokens); + + //skip any leading white space + this._readWhitespace(); + + var result = this._ruleset(); + + //skip any trailing white space + this._readWhitespace(); + + //if there's anything more, then it's an invalid selector + this._verifyEnd(); + + //otherwise return result + return result; + }, + + /** + * Parses a single CSS selector (no comma) + * @param {String} input The text to parse as a CSS selector. + * @return {Selector} An object representing the selector. + * @throws parserlib.util.SyntaxError If an unexpected token is found. + * @method parseSelector + */ + parseSelector: function(input){ + + this._tokenStream = new TokenStream(input, Tokens); + + //skip any leading white space + this._readWhitespace(); + + var result = this._selector(); + + //skip any trailing white space + this._readWhitespace(); + + //if there's anything more, then it's an invalid selector + this._verifyEnd(); + + //otherwise return result + return result; + } + + }; + + //copy over onto prototype + for (prop in additions){ + proto[prop] = additions[prop]; + } + + return proto; +}(); + + +/* +nth + : S* [ ['-'|'+']? INTEGER? {N} [ S* ['-'|'+'] S* INTEGER ]? | + ['-'|'+']? INTEGER | {O}{D}{D} | {E}{V}{E}{N} ] S* + ; +*/ +var Validation = { + measurement: { + parts: [ + { + types: ["length", "percentage", "integer", "identifier"], + identifier: /^(auto|inherit)$/i, + integer: /^0$/ + } + ] + }, + oneColor: { + maxParts: 1, + minParts: 1, + parts: [ + { + types: ["color", "identifier"], + identifier: /^(inherit|transparent)$/i + } + ] + } +}; + + + + + + + + + +var Properties = { + + "alignment-adjust": 1, + "alignment-baseline": 1, + "animation": 1, + "animation-delay": 1, + "animation-direction": 1, + "animation-duration": 1, + "animation-iteration-count": 1, + "animation-name": 1, + "animation-play-state": 1, + "animation-timing-function": 1, + "appearance": 1, + "azimuth": 1, + "backface-visibility": 1, + "background": 1, + "background-attachment": 1, + "background-break": 1, + "background-clip": 1, + "background-color": Validation.oneColor, + "background-image": 1, + "background-origin": 1, + "background-position": 1, + "background-repeat": 1, + "background-size": 1, + "baseline-shift": 1, + "binding": 1, + "bleed": 1, + "bookmark-label": 1, + "bookmark-level": 1, + "bookmark-state": 1, + "bookmark-target": 1, + "border": 1, + "border-bottom": 1, + "border-bottom-color": 1, + "border-bottom-left-radius": 1, + "border-bottom-right-radius": 1, + "border-bottom-style": 1, + "border-bottom-width": 1, + "border-collapse": 1, + "border-color": Validation.oneColor, + "border-image": 1, + "border-image-outset": 1, + "border-image-repeat": 1, + "border-image-slice": 1, + "border-image-source": 1, + "border-image-width": 1, + "border-left": 1, + "border-left-color": 1, + "border-left-style": 1, + "border-left-width": 1, + "border-radius": 1, + "border-right": 1, + "border-right-color": 1, + "border-right-style": 1, + "border-right-width": 1, + "border-spacing": 1, + "border-style": 1, + "border-top": 1, + "border-top-color": 1, + "border-top-left-radius": 1, + "border-top-right-radius": 1, + "border-top-style": 1, + "border-top-width": 1, + "border-width": 1, + "bottom": Validation.measurement, + "box-align": 1, + "box-decoration-break": 1, + "box-direction": 1, + "box-flex": 1, + "box-flex-group": 1, + "box-lines": 1, + "box-ordinal-group": 1, + "box-orient": 1, + "box-pack": 1, + "box-shadow": 1, + "box-sizing": 1, + "break-after": 1, + "break-before": 1, + "break-inside": 1, + "caption-side": 1, + "clear": 1, + "clip": 1, + "color": { + parts: [ + { + types: ["color", "identifier"], + identifier: /^inherit$/i + } + ] + }, + "color-profile": 1, + "column-count": 1, + "column-fill": 1, + "column-gap": 1, + "column-rule": 1, + "column-rule-color": 1, + "column-rule-style": 1, + "column-rule-width": 1, + "column-span": 1, + "column-width": 1, + "columns": 1, + "content": 1, + "counter-increment": 1, + "counter-reset": 1, + "crop": 1, + "cue": 1, + "cue-after": 1, + "cue-before": 1, + "cursor": 1, + "direction": 1, + "display": 1, + "dominant-baseline": 1, + "drop-initial-after-adjust": 1, + "drop-initial-after-align": 1, + "drop-initial-before-adjust": 1, + "drop-initial-before-align": 1, + "drop-initial-size": 1, + "drop-initial-value": 1, + "elevation": 1, + "empty-cells": 1, + "fit": 1, + "fit-position": 1, + "float": { + parts: [ + { + types: ["identifier"], + identifier: /^(left|right|none|inherit)$/i + } + ] + }, + + "float-offset": 1, + "font": 1, + "font-family": 1, + "font-size": 1, + "font-size-adjust": 1, + "font-stretch": 1, + "font-style": 1, + "font-variant": 1, + "font-weight": 1, + "grid-columns": 1, + "grid-rows": 1, + "hanging-punctuation": 1, + "height": Validation.measurement, + "hyphenate-after": 1, + "hyphenate-before": 1, + "hyphenate-character": 1, + "hyphenate-lines": 1, + "hyphenate-resource": 1, + "hyphens": 1, + "icon": 1, + "image-orientation": 1, + "image-rendering": 1, + "image-resolution": 1, + "inline-box-align": 1, + "left": Validation.measurement, + "letter-spacing": 1, + "line-height": 1, + "line-stacking": 1, + "line-stacking-ruby": 1, + "line-stacking-shift": 1, + "line-stacking-strategy": 1, + "list-style": 1, + "list-style-image": 1, + "list-style-position": 1, + "list-style-type": 1, + "margin": 1, + "margin-bottom": 1, + "margin-left": 1, + "margin-right": 1, + "margin-top": 1, + "mark": 1, + "mark-after": 1, + "mark-before": 1, + "marks": 1, + "marquee-direction": 1, + "marquee-play-count": 1, + "marquee-speed": 1, + "marquee-style": 1, + "max-height": 1, + "max-width": 1, + "min-height": 1, + "min-width": 1, + "move-to": 1, + "nav-down": 1, + "nav-index": 1, + "nav-left": 1, + "nav-right": 1, + "nav-up": 1, + "opacity": 1, + "orphans": 1, + "outline": 1, + "outline-color": 1, + "outline-offset": 1, + "outline-style": 1, + "outline-width": 1, + "overflow": 1, + "overflow-style": 1, + "overflow-x": 1, + "overflow-y": 1, + "padding": 1, + "padding-bottom": 1, + "padding-left": 1, + "padding-right": 1, + "padding-top": 1, + "page": 1, + "page-break-after": 1, + "page-break-before": 1, + "page-break-inside": 1, + "page-policy": 1, + "pause": 1, + "pause-after": 1, + "pause-before": 1, + "perspective": 1, + "perspective-origin": 1, + "phonemes": 1, + "pitch": 1, + "pitch-range": 1, + "play-during": 1, + "position": 1, + "presentation-level": 1, + "punctuation-trim": 1, + "quotes": 1, + "rendering-intent": 1, + "resize": 1, + "rest": 1, + "rest-after": 1, + "rest-before": 1, + "richness": 1, + "right": Validation.measurement, + "rotation": 1, + "rotation-point": 1, + "ruby-align": 1, + "ruby-overhang": 1, + "ruby-position": 1, + "ruby-span": 1, + "size": 1, + "speak": 1, + "speak-header": 1, + "speak-numeral": 1, + "speak-punctuation": 1, + "speech-rate": 1, + "stress": 1, + "string-set": 1, + "table-layout": 1, + "target": 1, + "target-name": 1, + "target-new": 1, + "target-position": 1, + "text-align": 1, + "text-align-last": 1, + "text-decoration": 1, + "text-emphasis": 1, + "text-height": 1, + "text-indent": 1, + "text-justify": 1, + "text-outline": 1, + "text-shadow": 1, + "text-transform": 1, + "text-wrap": 1, + "top": Validation.measurement, + "transform": 1, + "transform-origin": 1, + "transform-style": 1, + "transition": 1, + "transition-delay": 1, + "transition-duration": 1, + "transition-property": 1, + "transition-timing-function": 1, + "unicode-bidi": 1, + "vertical-align": 1, + "visibility": 1, + "voice-balance": 1, + "voice-duration": 1, + "voice-family": 1, + "voice-pitch": 1, + "voice-pitch-range": 1, + "voice-rate": 1, + "voice-stress": 1, + "voice-volume": 1, + "volume": 1, + "white-space": 1, + "white-space-collapse": 1, + "widows": 1, + "width": Validation.measurement, + "word-break": 1, + "word-spacing": { + minParts: 1, + maxParts: 1, + parts: [ + { + types: ["length", "number", "identifier"], + identifier: /^(normal|inherit)$/, + number: /^0$/ + } + ] + }, + "word-wrap": 1, + "z-index": { + minParts: 1, + maxParts: 1, + parts: [ + { + types: ["length", "identifier"], + identifier: /^(auto|inherit)$/ + } + ] + } + + +}; +/** + * Represents a selector combinator (whitespace, +, >). + * @namespace parserlib.css + * @class PropertyName + * @extends parserlib.util.SyntaxUnit + * @constructor + * @param {String} text The text representation of the unit. + * @param {String} hack The type of IE hack applied ("*", "_", or null). + * @param {int} line The line of text on which the unit resides. + * @param {int} col The column of text on which the unit resides. + */ +function PropertyName(text, hack, line, col){ + + SyntaxUnit.call(this, text, line, col); + + /** + * The type of IE hack applied ("*", "_", or null). + * @type String + * @property hack + */ + this.hack = hack; + +} + +PropertyName.prototype = new SyntaxUnit(); +PropertyName.prototype.constructor = PropertyName; +PropertyName.prototype.toString = function(){ + return (this.hack ? this.hack : "") + this.text; +}; +/** + * Represents a single part of a CSS property value, meaning that it represents + * just everything single part between ":" and ";". If there are multiple values + * separated by commas, this type represents just one of the values. + * @param {String[]} parts An array of value parts making up this value. + * @param {int} line The line of text on which the unit resides. + * @param {int} col The column of text on which the unit resides. + * @namespace parserlib.css + * @class PropertyValue + * @extends parserlib.util.SyntaxUnit + * @constructor + */ +function PropertyValue(parts, line, col){ + + SyntaxUnit.call(this, parts.join(" "), line, col); + + /** + * The parts that make up the selector. + * @type Array + * @property parts + */ + this.parts = parts; + +} + +PropertyValue.prototype = new SyntaxUnit(); +PropertyValue.prototype.constructor = PropertyValue; + +/** + * Represents a single part of a CSS property value, meaning that it represents + * just one part of the data between ":" and ";". + * @param {String} text The text representation of the unit. + * @param {int} line The line of text on which the unit resides. + * @param {int} col The column of text on which the unit resides. + * @namespace parserlib.css + * @class PropertyValuePart + * @extends parserlib.util.SyntaxUnit + * @constructor + */ +function PropertyValuePart(text, line, col){ + + SyntaxUnit.apply(this,arguments); + + /** + * Indicates the type of value unit. + * @type String + * @property type + */ + this.type = "unknown"; + + //figure out what type of data it is + + var temp; + + //it is a measurement? + if (/^([+\-]?[\d\.]+)([a-z]+)$/i.test(text)){ //dimension + this.type = "dimension"; + this.value = +RegExp.$1; + this.units = RegExp.$2; + + //try to narrow down + switch(this.units.toLowerCase()){ + + case "em": + case "rem": + case "ex": + case "px": + case "cm": + case "mm": + case "in": + case "pt": + case "pc": + this.type = "length"; + break; + + case "deg": + case "rad": + case "grad": + this.type = "angle"; + break; + + case "ms": + case "s": + this.type = "time"; + break; + + case "hz": + case "khz": + this.type = "frequency"; + break; + + case "dpi": + case "dpcm": + this.type = "resolution"; + break; + + //default + + } + + } else if (/^([+\-]?[\d\.]+)%$/i.test(text)){ //percentage + this.type = "percentage"; + this.value = +RegExp.$1; + } else if (/^([+\-]?[\d\.]+)%$/i.test(text)){ //percentage + this.type = "percentage"; + this.value = +RegExp.$1; + } else if (/^([+\-]?\d+)$/i.test(text)){ //integer + this.type = "integer"; + this.value = +RegExp.$1; + } else if (/^([+\-]?[\d\.]+)$/i.test(text)){ //number + this.type = "number"; + this.value = +RegExp.$1; + + } else if (/^#([a-f0-9]{3,6})/i.test(text)){ //hexcolor + this.type = "color"; + temp = RegExp.$1; + if (temp.length == 3){ + this.red = parseInt(temp.charAt(0)+temp.charAt(0),16); + this.green = parseInt(temp.charAt(1)+temp.charAt(1),16); + this.blue = parseInt(temp.charAt(2)+temp.charAt(2),16); + } else { + this.red = parseInt(temp.substring(0,2),16); + this.green = parseInt(temp.substring(2,4),16); + this.blue = parseInt(temp.substring(4,6),16); + } + } else if (/^rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/i.test(text)){ //rgb() color with absolute numbers + this.type = "color"; + this.red = +RegExp.$1; + this.green = +RegExp.$2; + this.blue = +RegExp.$3; + } else if (/^rgb\(\s*(\d+)%\s*,\s*(\d+)%\s*,\s*(\d+)%\s*\)/i.test(text)){ //rgb() color with percentages + this.type = "color"; + this.red = +RegExp.$1 * 255 / 100; + this.green = +RegExp.$2 * 255 / 100; + this.blue = +RegExp.$3 * 255 / 100; + } else if (/^url\(["']?([^\)"']+)["']?\)/i.test(text)){ //URI + this.type = "uri"; + this.uri = RegExp.$1; + } else if (/^["'][^"']*["']/.test(text)){ //string + this.type = "string"; + this.value = eval(text); + } else if (Colors[text.toLowerCase()]){ //named color + this.type = "color"; + temp = Colors[text.toLowerCase()].substring(1); + this.red = parseInt(temp.substring(0,2),16); + this.green = parseInt(temp.substring(2,4),16); + this.blue = parseInt(temp.substring(4,6),16); + } else if (/^[\,\/]$/.test(text)){ + this.type = "operator"; + this.value = text; + } else if (/^[a-z\-\u0080-\uFFFF][a-z0-9\-\u0080-\uFFFF]*$/i.test(text)){ + this.type = "identifier"; + this.value = text; + } + +} + +PropertyValuePart.prototype = new SyntaxUnit(); +PropertyValuePart.prototype.constructor = PropertyValue; + +/** + * Create a new syntax unit based solely on the given token. + * Convenience method for creating a new syntax unit when + * it represents a single token instead of multiple. + * @param {Object} token The token object to represent. + * @return {parserlib.css.PropertyValuePart} The object representing the token. + * @static + * @method fromToken + */ +PropertyValuePart.fromToken = function(token){ + return new PropertyValuePart(token.value, token.startLine, token.startCol); +}; +/** + * Represents an entire single selector, including all parts but not + * including multiple selectors (those separated by commas). + * @namespace parserlib.css + * @class Selector + * @extends parserlib.util.SyntaxUnit + * @constructor + * @param {Array} parts Array of selectors parts making up this selector. + * @param {int} line The line of text on which the unit resides. + * @param {int} col The column of text on which the unit resides. + */ +function Selector(parts, line, col){ + + SyntaxUnit.call(this, parts.join(" "), line, col); + + /** + * The parts that make up the selector. + * @type Array + * @property parts + */ + this.parts = parts; + +} + +Selector.prototype = new SyntaxUnit(); +Selector.prototype.constructor = Selector; + +/** + * Represents a single part of a selector string, meaning a single set of + * element name and modifiers. This does not include combinators such as + * spaces, +, >, etc. + * @namespace parserlib.css + * @class SelectorPart + * @extends parserlib.util.SyntaxUnit + * @constructor + * @param {String} elementName The element name in the selector or null + * if there is no element name. + * @param {Array} modifiers Array of individual modifiers for the element. + * May be empty if there are none. + * @param {String} text The text representation of the unit. + * @param {int} line The line of text on which the unit resides. + * @param {int} col The column of text on which the unit resides. + */ +function SelectorPart(elementName, modifiers, text, line, col){ + + SyntaxUnit.call(this, text, line, col); + + /** + * The tag name of the element to which this part + * of the selector affects. + * @type String + * @property elementName + */ + this.elementName = elementName; + + /** + * The parts that come after the element name, such as class names, IDs, + * pseudo classes/elements, etc. + * @type Array + * @property modifiers + */ + this.modifiers = modifiers; + +} + +SelectorPart.prototype = new SyntaxUnit(); +SelectorPart.prototype.constructor = SelectorPart; + +/** + * Represents a selector modifier string, meaning a class name, element name, + * element ID, pseudo rule, etc. + * @namespace parserlib.css + * @class SelectorSubPart + * @extends parserlib.util.SyntaxUnit + * @constructor + * @param {String} text The text representation of the unit. + * @param {String} type The type of selector modifier. + * @param {int} line The line of text on which the unit resides. + * @param {int} col The column of text on which the unit resides. + */ +function SelectorSubPart(text, type, line, col){ + + SyntaxUnit.call(this, text, line, col); + + /** + * The type of modifier. + * @type String + * @property type + */ + this.type = type; + + /** + * Some subparts have arguments, this represents them. + * @type Array + * @property args + */ + this.args = []; + +} + +SelectorSubPart.prototype = new SyntaxUnit(); +SelectorSubPart.prototype.constructor = SelectorSubPart; + + + +var h = /^[0-9a-fA-F]$/, + nonascii = /^[\u0080-\uFFFF]$/, + nl = /\n|\r\n|\r|\f/; + +//----------------------------------------------------------------------------- +// Helper functions +//----------------------------------------------------------------------------- + + +function isHexDigit(c){ + return c != null && h.test(c); +} + +function isDigit(c){ + return c != null && /\d/.test(c); +} + +function isWhitespace(c){ + return c != null && /\s/.test(c); +} + +function isNewLine(c){ + return c != null && nl.test(c); +} + +function isNameStart(c){ + return c != null && (/[a-z_\u0080-\uFFFF\\]/i.test(c)); +} + +function isNameChar(c){ + return c != null && (isNameStart(c) || /[0-9\-\\]/.test(c)); +} + +function isIdentStart(c){ + return c != null && (isNameStart(c) || /\-\\/.test(c)); +} + +function mix(receiver, supplier){ + for (var prop in supplier){ + if (supplier.hasOwnProperty(prop)){ + receiver[prop] = supplier[prop]; + } + } + return receiver; +} + +//----------------------------------------------------------------------------- +// CSS Token Stream +//----------------------------------------------------------------------------- + + +/** + * A token stream that produces CSS tokens. + * @param {String|Reader} input The source of text to tokenize. + * @constructor + * @class TokenStream + * @namespace parserlib.css + */ +function TokenStream(input){ + TokenStreamBase.call(this, input, Tokens); +} + +TokenStream.prototype = mix(new TokenStreamBase(), { + + /** + * Overrides the TokenStreamBase method of the same name + * to produce CSS tokens. + * @param {variant} channel The name of the channel to use + * for the next token. + * @return {Object} A token object representing the next token. + * @method _getToken + * @private + */ + _getToken: function(channel){ + + var c, + reader = this._reader, + token = null, + startLine = reader.getLine(), + startCol = reader.getCol(); + + c = reader.read(); + + + while(c){ + switch(c){ + + /* + * Potential tokens: + * - COMMENT + * - SLASH + * - CHAR + */ + case "/": + + if(reader.peek() == "*"){ + token = this.commentToken(c, startLine, startCol); + } else { + token = this.charToken(c, startLine, startCol); + } + break; + + /* + * Potential tokens: + * - DASHMATCH + * - INCLUDES + * - PREFIXMATCH + * - SUFFIXMATCH + * - SUBSTRINGMATCH + * - CHAR + */ + case "|": + case "~": + case "^": + case "$": + case "*": + if(reader.peek() == "="){ + token = this.comparisonToken(c, startLine, startCol); + } else { + token = this.charToken(c, startLine, startCol); + } + break; + + /* + * Potential tokens: + * - STRING + * - INVALID + */ + case "\"": + case "'": + token = this.stringToken(c, startLine, startCol); + break; + + /* + * Potential tokens: + * - HASH + * - CHAR + */ + case "#": + if (isNameChar(reader.peek())){ + token = this.hashToken(c, startLine, startCol); + } else { + token = this.charToken(c, startLine, startCol); + } + break; + + /* + * Potential tokens: + * - DOT + * - NUMBER + * - DIMENSION + * - PERCENTAGE + */ + case ".": + if (isDigit(reader.peek())){ + token = this.numberToken(c, startLine, startCol); + } else { + token = this.charToken(c, startLine, startCol); + } + break; + + /* + * Potential tokens: + * - CDC + * - MINUS + * - NUMBER + * - DIMENSION + * - PERCENTAGE + */ + case "-": + if (reader.peek() == "-"){ //could be closing HTML-style comment + token = this.htmlCommentEndToken(c, startLine, startCol); + } else if (isNameStart(reader.peek())){ + token = this.identOrFunctionToken(c, startLine, startCol); + } else { + token = this.charToken(c, startLine, startCol); + } + break; + + /* + * Potential tokens: + * - IMPORTANT_SYM + * - CHAR + */ + case "!": + token = this.importantToken(c, startLine, startCol); + break; + + /* + * Any at-keyword or CHAR + */ + case "@": + token = this.atRuleToken(c, startLine, startCol); + break; + + /* + * Potential tokens: + * - NOT + * - CHAR + */ + case ":": + token = this.notToken(c, startLine, startCol); + break; + + /* + * Potential tokens: + * - CDO + * - CHAR + */ + case "<": + token = this.htmlCommentStartToken(c, startLine, startCol); + break; + + /* + * Potential tokens: + * - UNICODE_RANGE + * - URL + * - CHAR + */ + case "U": + case "u": + if (reader.peek() == "+"){ + token = this.unicodeRangeToken(c, startLine, startCol); + break; + } + /*falls through*/ + + default: + + /* + * Potential tokens: + * - NUMBER + * - DIMENSION + * - LENGTH + * - FREQ + * - TIME + * - EMS + * - EXS + * - ANGLE + */ + if (isDigit(c)){ + token = this.numberToken(c, startLine, startCol); + } else + + /* + * Potential tokens: + * - S + */ + if (isWhitespace(c)){ + token = this.whitespaceToken(c, startLine, startCol); + } else + + /* + * Potential tokens: + * - IDENT + */ + if (isIdentStart(c)){ + token = this.identOrFunctionToken(c, startLine, startCol); + } else + + /* + * Potential tokens: + * - CHAR + * - PLUS + */ + { + token = this.charToken(c, startLine, startCol); + } + + + + + + + } + + //make sure this token is wanted + //TODO: check channel + break; + + c = reader.read(); + } + + if (!token && c == null){ + token = this.createToken(Tokens.EOF,null,startLine,startCol); + } + + return token; + }, + + //------------------------------------------------------------------------- + // Methods to create tokens + //------------------------------------------------------------------------- + + /** + * Produces a token based on available data and the current + * reader position information. This method is called by other + * private methods to create tokens and is never called directly. + * @param {int} tt The token type. + * @param {String} value The text value of the token. + * @param {int} startLine The beginning line for the character. + * @param {int} startCol The beginning column for the character. + * @param {Object} options (Optional) Specifies a channel property + * to indicate that a different channel should be scanned + * and/or a hide property indicating that the token should + * be hidden. + * @return {Object} A token object. + * @method createToken + */ + createToken: function(tt, value, startLine, startCol, options){ + var reader = this._reader; + options = options || {}; + + return { + value: value, + type: tt, + channel: options.channel, + hide: options.hide || false, + startLine: startLine, + startCol: startCol, + endLine: reader.getLine(), + endCol: reader.getCol() + }; + }, + + //------------------------------------------------------------------------- + // Methods to create specific tokens + //------------------------------------------------------------------------- + + /** + * Produces a token for any at-rule. If the at-rule is unknown, then + * the token is for a single "@" character. + * @param {String} first The first character for the token. + * @param {int} startLine The beginning line for the character. + * @param {int} startCol The beginning column for the character. + * @return {Object} A token object. + * @method atRuleToken + */ + atRuleToken: function(first, startLine, startCol){ + var rule = first, + reader = this._reader, + tt = Tokens.CHAR, + valid = false, + ident, + c; + + /* + * First, mark where we are. There are only four @ rules, + * so anything else is really just an invalid token. + * Basically, if this doesn't match one of the known @ + * rules, just return '@' as an unknown token and allow + * parsing to continue after that point. + */ + reader.mark(); + + //try to find the at-keyword + ident = this.readName(); + rule = first + ident; + tt = Tokens.type(rule.toLowerCase()); + + //if it's not valid, use the first character only and reset the reader + if (tt == Tokens.CHAR || tt == Tokens.UNKNOWN){ + tt = Tokens.CHAR; + rule = first; + reader.reset(); + } + + return this.createToken(tt, rule, startLine, startCol); + }, + + /** + * Produces a character token based on the given character + * and location in the stream. If there's a special (non-standard) + * token name, this is used; otherwise CHAR is used. + * @param {String} c The character for the token. + * @param {int} startLine The beginning line for the character. + * @param {int} startCol The beginning column for the character. + * @return {Object} A token object. + * @method charToken + */ + charToken: function(c, startLine, startCol){ + var tt = Tokens.type(c); + + if (tt == -1){ + tt = Tokens.CHAR; + } + + return this.createToken(tt, c, startLine, startCol); + }, + + /** + * Produces a character token based on the given character + * and location in the stream. If there's a special (non-standard) + * token name, this is used; otherwise CHAR is used. + * @param {String} first The first character for the token. + * @param {int} startLine The beginning line for the character. + * @param {int} startCol The beginning column for the character. + * @return {Object} A token object. + * @method commentToken + */ + commentToken: function(first, startLine, startCol){ + var reader = this._reader, + comment = this.readComment(first); + + return this.createToken(Tokens.COMMENT, comment, startLine, startCol); + }, + + /** + * Produces a comparison token based on the given character + * and location in the stream. The next character must be + * read and is already known to be an equals sign. + * @param {String} c The character for the token. + * @param {int} startLine The beginning line for the character. + * @param {int} startCol The beginning column for the character. + * @return {Object} A token object. + * @method comparisonToken + */ + comparisonToken: function(c, startLine, startCol){ + var reader = this._reader, + comparison = c + reader.read(), + tt = Tokens.type(comparison) || Tokens.CHAR; + + return this.createToken(tt, comparison, startLine, startCol); + }, + + /** + * Produces a hash token based on the specified information. The + * first character provided is the pound sign (#) and then this + * method reads a name afterward. + * @param {String} first The first character (#) in the hash name. + * @param {int} startLine The beginning line for the character. + * @param {int} startCol The beginning column for the character. + * @return {Object} A token object. + * @method hashToken + */ + hashToken: function(first, startLine, startCol){ + var reader = this._reader, + name = this.readName(first); + + return this.createToken(Tokens.HASH, name, startLine, startCol); + }, + + /** + * Produces a CDO or CHAR token based on the specified information. The + * first character is provided and the rest is read by the function to determine + * the correct token to create. + * @param {String} first The first character in the token. + * @param {int} startLine The beginning line for the character. + * @param {int} startCol The beginning column for the character. + * @return {Object} A token object. + * @method htmlCommentStartToken + */ + htmlCommentStartToken: function(first, startLine, startCol){ + var reader = this._reader, + text = first; + + reader.mark(); + text += reader.readCount(3); + + if (text == ""){ + return this.createToken(Tokens.CDC, text, startLine, startCol); + } else { + reader.reset(); + return this.charToken(first, startLine, startCol); + } + }, + + /** + * Produces an IDENT or FUNCTION token based on the specified information. The + * first character is provided and the rest is read by the function to determine + * the correct token to create. + * @param {String} first The first character in the identifier. + * @param {int} startLine The beginning line for the character. + * @param {int} startCol The beginning column for the character. + * @return {Object} A token object. + * @method identOrFunctionToken + */ + identOrFunctionToken: function(first, startLine, startCol){ + var reader = this._reader, + ident = this.readName(first), + tt = Tokens.IDENT; + + //if there's a left paren immediately after, it's a URI or function + if (reader.peek() == "("){ + ident += reader.read(); + if (ident.toLowerCase() == "url("){ + tt = Tokens.URI; + ident = this.readURI(ident); + + //didn't find a valid URL or there's no closing paren + if (ident.toLowerCase() == "url("){ + tt = Tokens.FUNCTION; + } + } else { + tt = Tokens.FUNCTION; + } + } else if (reader.peek() == ":"){ //might be an IE function + + //IE-specific functions always being with progid: + if (ident.toLowerCase() == "progid"){ + ident += reader.readTo("("); + tt = Tokens.IE_FUNCTION; + } + } + + return this.createToken(tt, ident, startLine, startCol); + }, + + /** + * Produces an IMPORTANT_SYM or CHAR token based on the specified information. The + * first character is provided and the rest is read by the function to determine + * the correct token to create. + * @param {String} first The first character in the token. + * @param {int} startLine The beginning line for the character. + * @param {int} startCol The beginning column for the character. + * @return {Object} A token object. + * @method importantToken + */ + importantToken: function(first, startLine, startCol){ + var reader = this._reader, + important = first, + tt = Tokens.CHAR, + temp, + c; + + reader.mark(); + c = reader.read(); + + while(c){ + + //there can be a comment in here + if (c == "/"){ + + //if the next character isn't a star, then this isn't a valid !important token + if (reader.peek() != "*"){ + break; + } else { + temp = this.readComment(c); + if (temp == ""){ //broken! + break; + } + } + } else if (isWhitespace(c)){ + important += c + this.readWhitespace(); + } else if (/i/i.test(c)){ + temp = reader.readCount(8); + if (/mportant/i.test(temp)){ + important += c + temp; + tt = Tokens.IMPORTANT_SYM; + + } + break; //we're done + } else { + break; + } + + c = reader.read(); + } + + if (tt == Tokens.CHAR){ + reader.reset(); + return this.charToken(first, startLine, startCol); + } else { + return this.createToken(tt, important, startLine, startCol); + } + + + }, + + /** + * Produces a NOT or CHAR token based on the specified information. The + * first character is provided and the rest is read by the function to determine + * the correct token to create. + * @param {String} first The first character in the token. + * @param {int} startLine The beginning line for the character. + * @param {int} startCol The beginning column for the character. + * @return {Object} A token object. + * @method notToken + */ + notToken: function(first, startLine, startCol){ + var reader = this._reader, + text = first; + + reader.mark(); + text += reader.readCount(4); + + if (text.toLowerCase() == ":not("){ + return this.createToken(Tokens.NOT, text, startLine, startCol); + } else { + reader.reset(); + return this.charToken(first, startLine, startCol); + } + }, + + /** + * Produces a number token based on the given character + * and location in the stream. This may return a token of + * NUMBER, EMS, EXS, LENGTH, ANGLE, TIME, FREQ, DIMENSION, + * or PERCENTAGE. + * @param {String} first The first character for the token. + * @param {int} startLine The beginning line for the character. + * @param {int} startCol The beginning column for the character. + * @return {Object} A token object. + * @method numberToken + */ + numberToken: function(first, startLine, startCol){ + var reader = this._reader, + value = this.readNumber(first), + ident, + tt = Tokens.NUMBER, + c = reader.peek(); + + if (isIdentStart(c)){ + ident = this.readName(reader.read()); + value += ident; + + if (/^em$|^ex$|^px$|^gd$|^rem$|^vw$|^vh$|^vm$|^ch$|^cm$|^mm$|^in$|^pt$|^pc$/i.test(ident)){ + tt = Tokens.LENGTH; + } else if (/^deg|^rad$|^grad$/i.test(ident)){ + tt = Tokens.ANGLE; + } else if (/^ms$|^s$/i.test(ident)){ + tt = Tokens.TIME; + } else if (/^hz$|^khz$/i.test(ident)){ + tt = Tokens.FREQ; + } else if (/^dpi$|^dpcm$/i.test(ident)){ + tt = Tokens.RESOLUTION; + } else { + tt = Tokens.DIMENSION; + } + + } else if (c == "%"){ + value += reader.read(); + tt = Tokens.PERCENTAGE; + } + + return this.createToken(tt, value, startLine, startCol); + }, + + /** + * Produces a string token based on the given character + * and location in the stream. Since strings may be indicated + * by single or double quotes, a failure to match starting + * and ending quotes results in an INVALID token being generated. + * The first character in the string is passed in and then + * the rest are read up to and including the final quotation mark. + * @param {String} first The first character in the string. + * @param {int} startLine The beginning line for the character. + * @param {int} startCol The beginning column for the character. + * @return {Object} A token object. + * @method stringToken + */ + stringToken: function(first, startLine, startCol){ + var delim = first, + string = first, + reader = this._reader, + prev = first, + tt = Tokens.STRING, + c = reader.read(); + + while(c){ + string += c; + + //if the delimiter is found with an escapement, we're done. + if (c == delim && prev != "\\"){ + break; + } + + //if there's a newline without an escapement, it's an invalid string + if (isNewLine(reader.peek()) && c != "\\"){ + tt = Tokens.INVALID; + break; + } + + //save previous and get next + prev = c; + c = reader.read(); + } + + //if c is null, that means we're out of input and the string was never closed + if (c == null){ + tt = Tokens.INVALID; + } + + return this.createToken(tt, string, startLine, startCol); + }, + + unicodeRangeToken: function(first, startLine, startCol){ + var reader = this._reader, + value = first, + temp, + tt = Tokens.CHAR; + + //then it should be a unicode range + if (reader.peek() == "+"){ + reader.mark(); + value += reader.read(); + value += this.readUnicodeRangePart(true); + + //ensure there's an actual unicode range here + if (value.length == 2){ + reader.reset(); + } else { + + tt = Tokens.UNICODE_RANGE; + + //if there's a ? in the first part, there can't be a second part + if (value.indexOf("?") == -1){ + + if (reader.peek() == "-"){ + reader.mark(); + temp = reader.read(); + temp += this.readUnicodeRangePart(false); + + //if there's not another value, back up and just take the first + if (temp.length == 1){ + reader.reset(); + } else { + value += temp; + } + } + + } + } + } + + return this.createToken(tt, value, startLine, startCol); + }, + + /** + * Produces a S token based on the specified information. Since whitespace + * may have multiple characters, this consumes all whitespace characters + * into a single token. + * @param {String} first The first character in the token. + * @param {int} startLine The beginning line for the character. + * @param {int} startCol The beginning column for the character. + * @return {Object} A token object. + * @method whitespaceToken + */ + whitespaceToken: function(first, startLine, startCol){ + var reader = this._reader, + value = first + this.readWhitespace(); + return this.createToken(Tokens.S, value, startLine, startCol); + }, + + + + + //------------------------------------------------------------------------- + // Methods to read values from the string stream + //------------------------------------------------------------------------- + + readUnicodeRangePart: function(allowQuestionMark){ + var reader = this._reader, + part = "", + c = reader.peek(); + + //first read hex digits + while(isHexDigit(c) && part.length < 6){ + reader.read(); + part += c; + c = reader.peek(); + } + + //then read question marks if allowed + if (allowQuestionMark){ + while(c == "?" && part.length < 6){ + reader.read(); + part += c; + c = reader.peek(); + } + } + + //there can't be any other characters after this point + + return part; + }, + + readWhitespace: function(){ + var reader = this._reader, + whitespace = "", + c = reader.peek(); + + while(isWhitespace(c)){ + reader.read(); + whitespace += c; + c = reader.peek(); + } + + return whitespace; + }, + readNumber: function(first){ + var reader = this._reader, + number = first, + hasDot = (first == "."), + c = reader.peek(); + + + while(c){ + if (isDigit(c)){ + number += reader.read(); + } else if (c == "."){ + if (hasDot){ + break; + } else { + hasDot = true; + number += reader.read(); + } + } else { + break; + } + + c = reader.peek(); + } + + return number; + }, + readString: function(){ + var reader = this._reader, + delim = reader.read(), + string = delim, + prev = delim, + c = reader.peek(); + + while(c){ + c = reader.read(); + string += c; + + //if the delimiter is found with an escapement, we're done. + if (c == delim && prev != "\\"){ + break; + } + + //if there's a newline without an escapement, it's an invalid string + if (isNewLine(reader.peek()) && c != "\\"){ + string = ""; + break; + } + + //save previous and get next + prev = c; + c = reader.peek(); + } + + //if c is null, that means we're out of input and the string was never closed + if (c == null){ + string = ""; + } + + return string; + }, + readURI: function(first){ + var reader = this._reader, + uri = first, + inner = "", + c = reader.peek(); + + reader.mark(); + + //skip whitespace before + while(c && isWhitespace(c)){ + reader.read(); + c = reader.peek(); + } + + //it's a string + if (c == "'" || c == "\""){ + inner = this.readString(); + } else { + inner = this.readURL(); + } + + c = reader.peek(); + + //skip whitespace after + while(c && isWhitespace(c)){ + reader.read(); + c = reader.peek(); + } + + //if there was no inner value or the next character isn't closing paren, it's not a URI + if (inner == "" || c != ")"){ + uri = first; + reader.reset(); + } else { + uri += inner + reader.read(); + } + + return uri; + }, + readURL: function(){ + var reader = this._reader, + url = "", + c = reader.peek(); + + //TODO: Check for escape and nonascii + while (/^[!#$%&\\*-~]$/.test(c)){ + url += reader.read(); + c = reader.peek(); + } + + return url; + + }, + readName: function(first){ + var reader = this._reader, + ident = first || "", + c = reader.peek(); + + while(true){ + if (c == "\\"){ + ident += this.readEscape(reader.read()); + c = reader.peek(); + } else if(c && isNameChar(c)){ + ident += reader.read(); + c = reader.peek(); + } else { + break; + } + } + + return ident; + }, + + readEscape: function(first){ + var reader = this._reader, + cssEscape = first || "", + i = 0, + c = reader.peek(); + + if (isHexDigit(c)){ + do { + cssEscape += reader.read(); + c = reader.peek(); + } while(c && isHexDigit(c) && ++i < 6); + } + + if (cssEscape.length == 3 && /\s/.test(c) || + cssEscape.length == 7 || cssEscape.length == 1){ + reader.read(); + } else { + c = ""; + } + + return cssEscape + c; + }, + + readComment: function(first){ + var reader = this._reader, + comment = first || "", + c = reader.read(); + + if (c == "*"){ + while(c){ + comment += c; + + //look for end of comment + if (c == "*" && reader.peek() == "/"){ + comment += reader.read(); + break; + } + + c = reader.read(); + } + + return comment; + } else { + return ""; + } + + } +}); + +var Tokens = [ + + /* + * The following token names are defined in CSS3 Grammar: http://www.w3.org/TR/css3-syntax/#lexical + */ + + //HTML-style comments + { name: "CDO"}, + { name: "CDC"}, + + //ignorables + { name: "S", whitespace: true/*, channel: "ws"*/}, + { name: "COMMENT", comment: true, hide: true, channel: "comment" }, + + //attribute equality + { name: "INCLUDES", text: "~="}, + { name: "DASHMATCH", text: "|="}, + { name: "PREFIXMATCH", text: "^="}, + { name: "SUFFIXMATCH", text: "$="}, + { name: "SUBSTRINGMATCH", text: "*="}, + + //identifier types + { name: "STRING"}, + { name: "IDENT"}, + { name: "HASH"}, + + //at-keywords + { name: "IMPORT_SYM", text: "@import"}, + { name: "PAGE_SYM", text: "@page"}, + { name: "MEDIA_SYM", text: "@media"}, + { name: "FONT_FACE_SYM", text: "@font-face"}, + { name: "CHARSET_SYM", text: "@charset"}, + { name: "NAMESPACE_SYM", text: "@namespace"}, + //{ name: "ATKEYWORD"}, + + //CSS3 animations + { name: "KEYFRAMES_SYM", text: [ "@keyframes", "@-webkit-keyframes", "@-moz-keyframes" ] }, + + //important symbol + { name: "IMPORTANT_SYM"}, + + //measurements + { name: "LENGTH"}, + { name: "ANGLE"}, + { name: "TIME"}, + { name: "FREQ"}, + { name: "DIMENSION"}, + { name: "PERCENTAGE"}, + { name: "NUMBER"}, + + //functions + { name: "URI"}, + { name: "FUNCTION"}, + + //Unicode ranges + { name: "UNICODE_RANGE"}, + + /* + * The following token names are defined in CSS3 Selectors: http://www.w3.org/TR/css3-selectors/#selector-syntax + */ + + //invalid string + { name: "INVALID"}, + + //combinators + { name: "PLUS", text: "+" }, + { name: "GREATER", text: ">"}, + { name: "COMMA", text: ","}, + { name: "TILDE", text: "~"}, + + //modifier + { name: "NOT"}, + + /* + * Defined in CSS3 Paged Media + */ + { name: "TOPLEFTCORNER_SYM", text: "@top-left-corner"}, + { name: "TOPLEFT_SYM", text: "@top-left"}, + { name: "TOPCENTER_SYM", text: "@top-center"}, + { name: "TOPRIGHT_SYM", text: "@top-right"}, + { name: "TOPRIGHTCORNER_SYM", text: "@top-right-corner"}, + { name: "BOTTOMLEFTCORNER_SYM", text: "@bottom-left-corner"}, + { name: "BOTTOMLEFT_SYM", text: "@bottom-left"}, + { name: "BOTTOMCENTER_SYM", text: "@bottom-center"}, + { name: "BOTTOMRIGHT_SYM", text: "@bottom-right"}, + { name: "BOTTOMRIGHTCORNER_SYM", text: "@bottom-right-corner"}, + { name: "LEFTTOP_SYM", text: "@left-top"}, + { name: "LEFTMIDDLE_SYM", text: "@left-middle"}, + { name: "LEFTBOTTOM_SYM", text: "@left-bottom"}, + { name: "RIGHTTOP_SYM", text: "@right-top"}, + { name: "RIGHTMIDDLE_SYM", text: "@right-middle"}, + { name: "RIGHTBOTTOM_SYM", text: "@right-bottom"}, + + /* + * The following token names are defined in CSS3 Media Queries: http://www.w3.org/TR/css3-mediaqueries/#syntax + */ + /*{ name: "MEDIA_ONLY", state: "media"}, + { name: "MEDIA_NOT", state: "media"}, + { name: "MEDIA_AND", state: "media"},*/ + { name: "RESOLUTION", state: "media"}, + + /* + * The following token names are not defined in any CSS specification but are used by the lexer. + */ + + //not a real token, but useful for stupid IE filters + { name: "IE_FUNCTION" }, + + //part of CSS3 grammar but not the Flex code + { name: "CHAR" }, + + //TODO: Needed? + //Not defined as tokens, but might as well be + { + name: "PIPE", + text: "|" + }, + { + name: "SLASH", + text: "/" + }, + { + name: "MINUS", + text: "-" + }, + { + name: "STAR", + text: "*" + }, + + { + name: "LBRACE", + text: "{" + }, + { + name: "RBRACE", + text: "}" + }, + { + name: "LBRACKET", + text: "[" + }, + { + name: "RBRACKET", + text: "]" + }, + { + name: "EQUALS", + text: "=" + }, + { + name: "COLON", + text: ":" + }, + { + name: "SEMICOLON", + text: ";" + }, + + { + name: "LPAREN", + text: "(" + }, + { + name: "RPAREN", + text: ")" + }, + { + name: "DOT", + text: "." + } +]; + +(function(){ + + var nameMap = [], + typeMap = {}; + + Tokens.UNKNOWN = -1; + Tokens.unshift({name:"EOF"}); + for (var i=0, len = Tokens.length; i < len; i++){ + nameMap.push(Tokens[i].name); + Tokens[Tokens[i].name] = i; + if (Tokens[i].text){ + if (Tokens[i].text instanceof Array){ + for (var j=0; j < Tokens[i].text.length; j++){ + typeMap[Tokens[i].text[j]] = i; + } + } else { + typeMap[Tokens[i].text] = i; + } + } + } + + Tokens.name = function(tt){ + return nameMap[tt]; + }; + + Tokens.type = function(c){ + return typeMap[c] || -1; + }; + +})(); + + + +/** + * Type to use when a validation error occurs. + * @class ValidationError + * @namespace parserlib.util + * @constructor + * @param {String} message The error message. + * @param {int} line The line at which the error occurred. + * @param {int} col The column at which the error occurred. + */ +function ValidationError(message, line, col){ + + /** + * The column at which the error occurred. + * @type int + * @property col + */ + this.col = col; + + /** + * The line at which the error occurred. + * @type int + * @property line + */ + this.line = line; + + /** + * The text representation of the unit. + * @type String + * @property text + */ + this.message = message; + +} + +//inherit from Error +ValidationError.prototype = new Error(); + +parserlib.css = { +Colors :Colors, +Combinator :Combinator, +Parser :Parser, +PropertyName :PropertyName, +PropertyValue :PropertyValue, +PropertyValuePart :PropertyValuePart, +MediaFeature :MediaFeature, +MediaQuery :MediaQuery, +Selector :Selector, +SelectorPart :SelectorPart, +SelectorSubPart :SelectorSubPart, +TokenStream :TokenStream, +Tokens :Tokens, +ValidationError :ValidationError +}; +})(); + +/** + * Main CSSLint object. + * @class CSSLint + * @static + * @extends parserlib.util.EventTarget + */ +var CSSLint = (function(){ + + var rules = [], + formatters = [], + api = new parserlib.util.EventTarget(); + + api.version = "0.6.1"; + + //------------------------------------------------------------------------- + // Rule Management + //------------------------------------------------------------------------- + + /** + * Adds a new rule to the engine. + * @param {Object} rule The rule to add. + * @method addRule + */ + api.addRule = function(rule){ + rules.push(rule); + rules[rule.id] = rule; + }; + + /** + * Clears all rule from the engine. + * @method clearRules + */ + api.clearRules = function(){ + rules = []; + }; + + /** + * Returns the rule objects. + * @return An array of rule objects. + * @method getRules + */ + api.getRules = function(){ + return [].concat(rules).sort(function(a,b){ + return a.id > b.id ? 1 : 0; + }); + }; + + //------------------------------------------------------------------------- + // Formatters + //------------------------------------------------------------------------- + + /** + * Adds a new formatter to the engine. + * @param {Object} formatter The formatter to add. + * @method addFormatter + */ + api.addFormatter = function(formatter) { + // formatters.push(formatter); + formatters[formatter.id] = formatter; + }; + + /** + * Retrieves a formatter for use. + * @param {String} formatId The name of the format to retrieve. + * @return {Object} The formatter or undefined. + * @method getFormatter + */ + api.getFormatter = function(formatId){ + return formatters[formatId]; + }; + + /** + * Formats the results in a particular format for a single file. + * @param {Object} result The results returned from CSSLint.verify(). + * @param {String} filename The filename for which the results apply. + * @param {String} formatId The name of the formatter to use. + * @return {String} A formatted string for the results. + * @method format + */ + api.format = function(results, filename, formatId) { + var formatter = this.getFormatter(formatId), + result = null; + + if (formatter){ + result = formatter.startFormat(); + result += formatter.formatResults(results, filename); + result += formatter.endFormat(); + } + + return result; + } + + /** + * Indicates if the given format is supported. + * @param {String} formatId The ID of the format to check. + * @return {Boolean} True if the format exists, false if not. + * @method hasFormat + */ + api.hasFormat = function(formatId){ + return formatters.hasOwnProperty(formatId); + }; + + //------------------------------------------------------------------------- + // Verification + //------------------------------------------------------------------------- + + /** + * Starts the verification process for the given CSS text. + * @param {String} text The CSS text to verify. + * @param {Object} ruleset (Optional) List of rules to apply. If null, then + * all rules are used. + * @return {Object} Results of the verification. + * @method verify + */ + api.verify = function(text, ruleset){ + + var i = 0, + len = rules.length, + reporter, + lines, + report, + parser = new parserlib.css.Parser({ starHack: true, ieFilters: true, + underscoreHack: true, strict: false }); + + lines = text.split(/\n\r?/g); + reporter = new Reporter(lines); + + if (!ruleset){ + while (i < len){ + rules[i++].init(parser, reporter); + } + } else { + ruleset.errors = 1; //always report parsing errors + for (i in ruleset){ + if(ruleset.hasOwnProperty(i)){ + if (rules[i]){ + rules[i].init(parser, reporter); + } + } + } + } + + //capture most horrible error type + try { + parser.parse(text); + } catch (ex) { + reporter.error("Fatal error, cannot continue: " + ex.message, ex.line, ex.col); + } + + report = { + messages : reporter.messages, + stats : reporter.stats + }; + + //sort by line numbers, rollups at the bottom + report.messages.sort(function (a, b){ + if (a.rollup && !b.rollup){ + return 1; + } else if (!a.rollup && b.rollup){ + return -1; + } else { + return a.line - b.line; + } + }); + + return report; + }; + + //------------------------------------------------------------------------- + // Publish the API + //------------------------------------------------------------------------- + + return api; + +})(); + +/** + * An instance of Report is used to report results of the + * verification back to the main API. + * @class Reporter + * @constructor + * @param {String[]} lines The text lines of the source. + */ +function Reporter(lines){ + + /** + * List of messages being reported. + * @property messages + * @type String[] + */ + this.messages = []; + + /** + * List of statistics being reported. + * @property stats + * @type String[] + */ + this.stats = []; + + /** + * Lines of code being reported on. Used to provide contextual information + * for messages. + * @property lines + * @type String[] + */ + this.lines = lines; +} + +Reporter.prototype = { + + //restore constructor + constructor: Reporter, + + /** + * Report an error. + * @param {String} message The message to store. + * @param {int} line The line number. + * @param {int} col The column number. + * @param {Object} rule The rule this message relates to. + * @method error + */ + error: function(message, line, col, rule){ + this.messages.push({ + type : "error", + line : line, + col : col, + message : message, + evidence: this.lines[line-1], + rule : rule + }); + }, + + /** + * Report an warning. + * @param {String} message The message to store. + * @param {int} line The line number. + * @param {int} col The column number. + * @param {Object} rule The rule this message relates to. + * @method warn + */ + warn: function(message, line, col, rule){ + this.messages.push({ + type : "warning", + line : line, + col : col, + message : message, + evidence: this.lines[line-1], + rule : rule + }); + }, + + /** + * Report some informational text. + * @param {String} message The message to store. + * @param {int} line The line number. + * @param {int} col The column number. + * @param {Object} rule The rule this message relates to. + * @method info + */ + info: function(message, line, col, rule){ + this.messages.push({ + type : "info", + line : line, + col : col, + message : message, + evidence: this.lines[line-1], + rule : rule + }); + }, + + /** + * Report some rollup error information. + * @param {String} message The message to store. + * @param {Object} rule The rule this message relates to. + * @method rollupError + */ + rollupError: function(message, rule){ + this.messages.push({ + type : "error", + rollup : true, + message : message, + rule : rule + }); + }, + + /** + * Report some rollup warning information. + * @param {String} message The message to store. + * @param {Object} rule The rule this message relates to. + * @method rollupWarn + */ + rollupWarn: function(message, rule){ + this.messages.push({ + type : "warning", + rollup : true, + message : message, + rule : rule + }); + }, + + /** + * Report a statistic. + * @param {String} name The name of the stat to store. + * @param {Variant} value The value of the stat. + * @method stat + */ + stat: function(name, value){ + this.stats[name] = value; + } +}; + +/* + * Utility functions that make life easier. + */ + +/* + * Adds all properties from supplier onto receiver, + * overwriting if the same name already exists on + * reciever. + * @param {Object} The object to receive the properties. + * @param {Object} The object to provide the properties. + * @return {Object} The receiver + */ +function mix(reciever, supplier){ + var prop; + + for (prop in supplier){ + if (supplier.hasOwnProperty(prop)){ + receiver[prop] = supplier[prop]; + } + } + + return prop; +} + +/* + * Polyfill for array indexOf() method. + * @param {Array} values The array to search. + * @param {Variant} value The value to search for. + * @return {int} The index of the value if found, -1 if not. + */ +function indexOf(values, value){ + if (values.indexOf){ + return values.indexOf(value); + } else { + for (var i=0, len=values.length; i < len; i++){ + if (values[i] === value){ + return i; + } + } + return -1; + } +} +/* + * Rule: Don't use adjoining classes (.foo.bar). + */ +CSSLint.addRule({ + + //rule information + id: "adjoining-classes", + name: "Adjoining Classes", + desc: "Don't use adjoining classes.", + browsers: "IE6", + + //initialization + init: function(parser, reporter){ + var rule = this; + parser.addListener("startrule", function(event){ + var selectors = event.selectors, + selector, + part, + modifier, + classCount, + i, j, k; + + for (i=0; i < selectors.length; i++){ + selector = selectors[i]; + for (j=0; j < selector.parts.length; j++){ + part = selector.parts[j]; + if (part instanceof parserlib.css.SelectorPart){ + classCount = 0; + for (k=0; k < part.modifiers.length; k++){ + modifier = part.modifiers[k]; + if (modifier.type == "class"){ + classCount++; + } + if (classCount > 1){ + reporter.warn("Don't use adjoining classes.", part.line, part.col, rule); + } + } + } + } + } + }); + } + +}); +/* + * Rule: Don't use width or height when using padding or border. + */ +CSSLint.addRule({ + + //rule information + id: "box-model", + name: "Box Model", + desc: "Don't use width or height when using padding or border.", + browsers: "All", + + //initialization + init: function(parser, reporter){ + var rule = this, + widthProperties = { + border: 1, + "border-left": 1, + "border-right": 1, + padding: 1, + "padding-left": 1, + "padding-right": 1 + }, + heightProperties = { + border: 1, + "border-bottom": 1, + "border-top": 1, + padding: 1, + "padding-bottom": 1, + "padding-top": 1 + }, + properties; + + parser.addListener("startrule", function(){ + properties = { + }; + }); + + parser.addListener("property", function(event){ + var name = event.property.text.toLowerCase(); + + if (heightProperties[name] || widthProperties[name]){ + if (!/^0\S*$/.test(event.value) && !(name == "border" && event.value == "none")){ + properties[name] = { line: event.property.line, col: event.property.col, value: event.value }; + } + } else { + if (name == "width" || name == "height"){ + properties[name] = 1; + } + } + + }); + + parser.addListener("endrule", function(){ + var prop; + if (properties["height"]){ + for (prop in heightProperties){ + if (heightProperties.hasOwnProperty(prop) && properties[prop]){ + + //special case for padding + if (prop == "padding" && properties[prop].value.parts.length == 2 && properties[prop].value.parts[0].value == 0){ + //noop + } else { + reporter.warn("Broken box model: using height with " + prop + ".", properties[prop].line, properties[prop].col, rule); + } + } + } + } + + if (properties["width"]){ + for (prop in widthProperties){ + if (widthProperties.hasOwnProperty(prop) && properties[prop]){ + + if (prop == "padding" && properties[prop].value.parts.length == 2 && properties[prop].value.parts[1].value == 0){ + //noop + } else { + reporter.warn("Broken box model: using width with " + prop + ".", properties[prop].line, properties[prop].col, rule); + } + } + } + } + + }); + } + +}); +/* + * Rule: Include all compatible vendor prefixes to reach a wider + * range of users. + */ +/*global CSSLint*/ +CSSLint.addRule({ + + //rule information + id: "compatible-vendor-prefixes", + name: "Compatible Vendor Prefixes", + desc: "Include all compatible vendor prefixes to reach a wider range of users.", + browsers: "All", + + //initialization + init: function (parser, reporter) { + var rule = this, + compatiblePrefixes, + properties, + prop, + variations, + prefixed, + i, + len, + arrayPush = Array.prototype.push, + applyTo = []; + + // See http://peter.sh/experiments/vendor-prefixed-css-property-overview/ for details + compatiblePrefixes = { + "animation" : "webkit moz", + "animation-delay" : "webkit moz", + "animation-direction" : "webkit moz", + "animation-duration" : "webkit moz", + "animation-fill-mode" : "webkit moz", + "animation-iteration-count" : "webkit moz", + "animation-name" : "webkit moz", + "animation-play-state" : "webkit moz", + "animation-timing-function" : "webkit moz", + "appearance" : "webkit moz", + "border-end" : "webkit moz", + "border-end-color" : "webkit moz", + "border-end-style" : "webkit moz", + "border-end-width" : "webkit moz", + "border-image" : "webkit moz o", + "border-radius" : "webkit moz", + "border-start" : "webkit moz", + "border-start-color" : "webkit moz", + "border-start-style" : "webkit moz", + "border-start-width" : "webkit moz", + "box-align" : "webkit moz ms", + "box-direction" : "webkit moz ms", + "box-flex" : "webkit moz ms", + "box-lines" : "webkit ms", + "box-ordinal-group" : "webkit moz ms", + "box-orient" : "webkit moz ms", + "box-pack" : "webkit moz ms", + "box-sizing" : "webkit moz", + "box-shadow" : "webkit moz", + "column-count" : "webkit moz", + "column-gap" : "webkit moz", + "column-rule" : "webkit moz", + "column-rule-color" : "webkit moz", + "column-rule-style" : "webkit moz", + "column-rule-width" : "webkit moz", + "column-width" : "webkit moz", + "hyphens" : "epub moz", + "line-break" : "webkit ms", + "margin-end" : "webkit moz", + "margin-start" : "webkit moz", + "marquee-speed" : "webkit wap", + "marquee-style" : "webkit wap", + "padding-end" : "webkit moz", + "padding-start" : "webkit moz", + "tab-size" : "moz o", + "text-size-adjust" : "webkit ms", + "transform" : "webkit moz ms o", + "transform-origin" : "webkit moz ms o", + "transition" : "webkit moz o", + "transition-delay" : "webkit moz o", + "transition-duration" : "webkit moz o", + "transition-property" : "webkit moz o", + "transition-timing-function" : "webkit moz o", + "user-modify" : "webkit moz", + "user-select" : "webkit moz", + "word-break" : "epub ms", + "writing-mode" : "epub ms" + }; + + for (prop in compatiblePrefixes) { + if (compatiblePrefixes.hasOwnProperty(prop)) { + variations = []; + prefixed = compatiblePrefixes[prop].split(' '); + for (i = 0, len = prefixed.length; i < len; i++) { + variations.push('-' + prefixed[i] + '-' + prop); + } + compatiblePrefixes[prop] = variations; + arrayPush.apply(applyTo, variations); + } + } + parser.addListener("startrule", function () { + properties = []; + }); + + parser.addListener("property", function (event) { + var name = event.property.text; + if (applyTo.indexOf(name) > -1) { + properties.push(name); + } + }); + + parser.addListener("endrule", function (event) { + if (!properties.length) { + return; + } + + var propertyGroups = {}, + i, + len, + name, + prop, + variations, + value, + full, + actual, + item, + propertiesSpecified; + + for (i = 0, len = properties.length; i < len; i++) { + name = properties[i]; + + for (prop in compatiblePrefixes) { + if (compatiblePrefixes.hasOwnProperty(prop)) { + variations = compatiblePrefixes[prop]; + if (variations.indexOf(name) > -1) { + if (propertyGroups[prop] === undefined) { + propertyGroups[prop] = { + full : variations.slice(0), + actual : [] + }; + } + if (propertyGroups[prop].actual.indexOf(name) === -1) { + propertyGroups[prop].actual.push(name); + } + } + } + } + } + + for (prop in propertyGroups) { + if (propertyGroups.hasOwnProperty(prop)) { + value = propertyGroups[prop]; + full = value.full; + actual = value.actual; + + if (full.length > actual.length) { + for (i = 0, len = full.length; i < len; i++) { + item = full[i]; + if (actual.indexOf(item) === -1) { + propertiesSpecified = (actual.length === 1) ? actual[0] : (actual.length == 2) ? actual.join(" and ") : actual.join(", "); + reporter.warn("The property " + item + " is compatible with " + propertiesSpecified + " and should be included as well.", event.selectors[0].line, event.selectors[0].col, rule); + } + } + + } + } + } + }); + } +}); +/* + * Rule: Certain properties don't play well with certain display values. + * - float should not be used with inline-block + * - height, width, margin-top, margin-bottom, float should not be used with inline + * - vertical-align should not be used with block + * - margin, float should not be used with table-* + */ +CSSLint.addRule({ + + //rule information + id: "display-property-grouping", + name: "Display Property Grouping", + desc: "Certain properties shouldn't be used with certain display property values.", + browsers: "All", + + //initialization + init: function(parser, reporter){ + var rule = this; + + var propertiesToCheck = { + display: 1, + "float": "none", + height: 1, + width: 1, + margin: 1, + "margin-left": 1, + "margin-right": 1, + "margin-bottom": 1, + "margin-top": 1, + padding: 1, + "padding-left": 1, + "padding-right": 1, + "padding-bottom": 1, + "padding-top": 1, + "vertical-align": 1 + }, + properties; + + parser.addListener("startrule", function(){ + properties = {}; + }); + + parser.addListener("property", function(event){ + var name = event.property.text.toLowerCase(); + + if (propertiesToCheck[name]){ + properties[name] = { value: event.value.text, line: event.property.line, col: event.property.col }; + } + }); + + parser.addListener("endrule", function(){ + + var display = properties.display ? properties.display.value : null; + if (display){ + switch(display){ + + case "inline": + //height, width, margin-top, margin-bottom, float should not be used with inline + reportProperty("height", display); + reportProperty("width", display); + reportProperty("margin", display); + reportProperty("margin-top", display); + reportProperty("margin-bottom", display); + reportProperty("float", display, "display:inline has no effect on floated elements (but may be used to fix the IE6 double-margin bug)."); + break; + + case "block": + //vertical-align should not be used with block + reportProperty("vertical-align", display); + break; + + case "inline-block": + //float should not be used with inline-block + reportProperty("float", display); + break; + + default: + //margin, float should not be used with table + if (display.indexOf("table-") == 0){ + reportProperty("margin", display); + reportProperty("margin-left", display); + reportProperty("margin-right", display); + reportProperty("margin-top", display); + reportProperty("margin-bottom", display); + reportProperty("float", display); + } + + //otherwise do nothing + } + } + + }); + + + function reportProperty(name, display, msg){ + if (properties[name]){ + if (!(typeof propertiesToCheck[name] == "string") || properties[name].value.toLowerCase() != propertiesToCheck[name]){ + reporter.warn(msg || name + " can't be used with display: " + display + ".", properties[name].line, properties[name].col, rule); + } + } + } + } + +}); +/* + * Rule: Duplicate properties must appear one after the other. If an already-defined + * property appears somewhere else in the rule, then it's likely an error. + */ +CSSLint.addRule({ + + //rule information + id: "duplicate-properties", + name: "Duplicate Properties", + desc: "Duplicate properties must appear one after the other.", + browsers: "All", + + //initialization + init: function(parser, reporter){ + var rule = this, + properties, + lastProperty; + + function startRule(event){ + properties = {}; + } + + parser.addListener("startrule", startRule); + parser.addListener("startfontface", startRule); + parser.addListener("startpage", startRule); + + parser.addListener("property", function(event){ + var property = event.property, + name = property.text.toLowerCase(); + + if (properties[name] && (lastProperty != name || properties[name] == event.value.text)){ + reporter.warn("Duplicate property '" + event.property + "' found.", event.line, event.col, rule); + } + + properties[name] = event.value.text; + lastProperty = name; + + }); + + + } + +}); +/* + * Rule: Style rules without any properties defined should be removed. + */ +CSSLint.addRule({ + + //rule information + id: "empty-rules", + name: "Empty Rules", + desc: "Rules without any properties specified should be removed.", + browsers: "All", + + //initialization + init: function(parser, reporter){ + var rule = this, + count = 0; + + parser.addListener("startrule", function(){ + count=0; + }); + + parser.addListener("property", function(){ + count++; + }); + + parser.addListener("endrule", function(event){ + var selectors = event.selectors; + if (count == 0){ + reporter.warn("Rule is empty.", selectors[0].line, selectors[0].col, rule); + } + }); + } + +}); +/* + * Rule: There should be no syntax errors. (Duh.) + */ +CSSLint.addRule({ + + //rule information + id: "errors", + name: "Parsing Errors", + desc: "This rule looks for recoverable syntax errors.", + browsers: "All", + + //initialization + init: function(parser, reporter){ + var rule = this; + + parser.addListener("error", function(event){ + reporter.error(event.message, event.line, event.col, rule); + }); + + } + +}); +/* + * Rule: You shouldn't use more than 10 floats. If you do, there's probably + * room for some abstraction. + */ +CSSLint.addRule({ + + //rule information + id: "floats", + name: "Floats", + desc: "This rule tests if the float property is used too many times", + browsers: "All", + + //initialization + init: function(parser, reporter){ + var rule = this; + var count = 0; + + //count how many times "float" is used + parser.addListener("property", function(event){ + if (event.property.text.toLowerCase() == "float" && + event.value.text.toLowerCase() != "none"){ + count++; + } + }); + + //report the results + parser.addListener("endstylesheet", function(){ + reporter.stat("floats", count); + if (count >= 10){ + reporter.rollupWarn("Too many floats (" + count + "), you're probably using them for layout. Consider using a grid system instead.", rule); + } + }); + } + +}); +/* + * Rule: Avoid too many @font-face declarations in the same stylesheet. + */ +CSSLint.addRule({ + + //rule information + id: "font-faces", + name: "Font Faces", + desc: "Too many different web fonts in the same stylesheet.", + browsers: "All", + + //initialization + init: function(parser, reporter){ + var rule = this, + count = 0; + + + parser.addListener("startfontface", function(){ + count++; + }); + + parser.addListener("endstylesheet", function(){ + if (count > 5){ + reporter.rollupWarn("Too many @font-face declarations (" + count + ").", rule); + } + }); + } + +}); +/* + * Rule: You shouldn't need more than 9 font-size declarations. + */ + +CSSLint.addRule({ + + //rule information + id: "font-sizes", + name: "Font Sizes", + desc: "Checks the number of font-size declarations.", + browsers: "All", + + //initialization + init: function(parser, reporter){ + var rule = this, + count = 0; + + //check for use of "font-size" + parser.addListener("property", function(event){ + if (event.property == "font-size"){ + count++; + } + }); + + //report the results + parser.addListener("endstylesheet", function(){ + reporter.stat("font-sizes", count); + if (count >= 10){ + reporter.rollupWarn("Too many font-size declarations (" + count + "), abstraction needed.", rule); + } + }); + } + +}); +/* + * Rule: When using a vendor-prefixed gradient, make sure to use them all. + */ +CSSLint.addRule({ + + //rule information + id: "gradients", + name: "Gradients", + desc: "When using a vendor-prefixed gradient, make sure to use them all.", + browsers: "All", + + //initialization + init: function(parser, reporter){ + var rule = this, + gradients; + + parser.addListener("startrule", function(){ + gradients = { + moz: 0, + webkit: 0, + ms: 0, + o: 0 + }; + }); + + parser.addListener("property", function(event){ + + if (/\-(moz|ms|o|webkit)(?:\-(?:linear|radial))\-gradient/.test(event.value)){ + gradients[RegExp.$1] = 1; + } + + }); + + parser.addListener("endrule", function(event){ + var missing = []; + + if (!gradients.moz){ + missing.push("Firefox 3.6+"); + } + + if (!gradients.webkit){ + missing.push("Webkit (Safari, Chrome)"); + } + + if (!gradients.ms){ + missing.push("Internet Explorer 10+"); + } + + if (!gradients.o){ + missing.push("Opera 11.1+"); + } + + if (missing.length && missing.length < 4){ + reporter.warn("Missing vendor-prefixed CSS gradients for " + missing.join(", ") + ".", event.selectors[0].line, event.selectors[0].col, rule); + } + + }); + + } + +}); +/* + * Rule: Don't use IDs for selectors. + */ +CSSLint.addRule({ + + //rule information + id: "ids", + name: "IDs", + desc: "Selectors should not contain IDs.", + browsers: "All", + + //initialization + init: function(parser, reporter){ + var rule = this; + parser.addListener("startrule", function(event){ + var selectors = event.selectors, + selector, + part, + modifier, + idCount, + i, j, k; + + for (i=0; i < selectors.length; i++){ + selector = selectors[i]; + idCount = 0; + + for (j=0; j < selector.parts.length; j++){ + part = selector.parts[j]; + if (part instanceof parserlib.css.SelectorPart){ + for (k=0; k < part.modifiers.length; k++){ + modifier = part.modifiers[k]; + if (modifier.type == "id"){ + idCount++; + } + } + } + } + + if (idCount == 1){ + reporter.warn("Don't use IDs in selectors.", selector.line, selector.col, rule); + } else if (idCount > 1){ + reporter.warn(idCount + " IDs in the selector, really?", selector.line, selector.col, rule); + } + } + + }); + } + +}); +/* + * Rule: Don't use @import, use instead. + */ +CSSLint.addRule({ + + //rule information + id: "import", + name: "@import", + desc: "Don't use @import, use instead.", + browsers: "All", + + //initialization + init: function(parser, reporter){ + var rule = this; + + parser.addListener("import", function(event){ + reporter.warn("@import prevents parallel downloads, use instead.", event.line, event.col, rule); + }); + + } + +}); +/* + * Rule: Make sure !important is not overused, this could lead to specificity + * war. Display a warning on !important declarations, an error if it's + * used more at least 10 times. + */ +CSSLint.addRule({ + + //rule information + id: "important", + name: "Important", + desc: "Be careful when using !important declaration", + browsers: "All", + + //initialization + init: function(parser, reporter){ + var rule = this, + count = 0; + + //warn that important is used and increment the declaration counter + parser.addListener("property", function(event){ + if (event.important === true){ + count++; + reporter.warn("Use of !important", event.line, event.col, rule); + } + }); + + //if there are more than 10, show an error + parser.addListener("endstylesheet", function(){ + reporter.stat("important", count); + if (count >= 10){ + reporter.rollupWarn("Too many !important declarations (" + count + "), try to use less than 10 to avoid specifity issues.", rule); + } + }); + } + +}); +/* + * Rule: Properties should be known (listed in CSS3 specification) or + * be a vendor-prefixed property. + */ +CSSLint.addRule({ + + //rule information + id: "known-properties", + name: "Known Properties", + desc: "Properties should be known (listed in CSS specification) or be a vendor-prefixed property.", + browsers: "All", + + //initialization + init: function(parser, reporter){ + var rule = this, + properties = { + + "alignment-adjust": 1, + "alignment-baseline": 1, + "animation": 1, + "animation-delay": 1, + "animation-direction": 1, + "animation-duration": 1, + "animation-fill-mode": 1, + "animation-iteration-count": 1, + "animation-name": 1, + "animation-play-state": 1, + "animation-timing-function": 1, + "appearance": 1, + "azimuth": 1, + "backface-visibility": 1, + "background": 1, + "background-attachment": 1, + "background-break": 1, + "background-clip": 1, + "background-color": 1, + "background-image": 1, + "background-origin": 1, + "background-position": 1, + "background-repeat": 1, + "background-size": 1, + "baseline-shift": 1, + "binding": 1, + "bleed": 1, + "bookmark-label": 1, + "bookmark-level": 1, + "bookmark-state": 1, + "bookmark-target": 1, + "border": 1, + "border-bottom": 1, + "border-bottom-color": 1, + "border-bottom-left-radius": 1, + "border-bottom-right-radius": 1, + "border-bottom-style": 1, + "border-bottom-width": 1, + "border-collapse": 1, + "border-color": 1, + "border-image": 1, + "border-image-outset": 1, + "border-image-repeat": 1, + "border-image-slice": 1, + "border-image-source": 1, + "border-image-width": 1, + "border-left": 1, + "border-left-color": 1, + "border-left-style": 1, + "border-left-width": 1, + "border-radius": 1, + "border-right": 1, + "border-right-color": 1, + "border-right-style": 1, + "border-right-width": 1, + "border-spacing": 1, + "border-style": 1, + "border-top": 1, + "border-top-color": 1, + "border-top-left-radius": 1, + "border-top-right-radius": 1, + "border-top-style": 1, + "border-top-width": 1, + "border-width": 1, + "bottom": 1, + "box-align": 1, + "box-decoration-break": 1, + "box-direction": 1, + "box-flex": 1, + "box-flex-group": 1, + "box-lines": 1, + "box-ordinal-group": 1, + "box-orient": 1, + "box-pack": 1, + "box-shadow": 1, + "box-sizing": 1, + "break-after": 1, + "break-before": 1, + "break-inside": 1, + "caption-side": 1, + "clear": 1, + "clip": 1, + "color": 1, + "color-profile": 1, + "column-count": 1, + "column-fill": 1, + "column-gap": 1, + "column-rule": 1, + "column-rule-color": 1, + "column-rule-style": 1, + "column-rule-width": 1, + "column-span": 1, + "column-width": 1, + "columns": 1, + "content": 1, + "counter-increment": 1, + "counter-reset": 1, + "crop": 1, + "cue": 1, + "cue-after": 1, + "cue-before": 1, + "cursor": 1, + "direction": 1, + "display": 1, + "dominant-baseline": 1, + "drop-initial-after-adjust": 1, + "drop-initial-after-align": 1, + "drop-initial-before-adjust": 1, + "drop-initial-before-align": 1, + "drop-initial-size": 1, + "drop-initial-value": 1, + "elevation": 1, + "empty-cells": 1, + "fit": 1, + "fit-position": 1, + "float": 1, + "float-offset": 1, + "font": 1, + "font-family": 1, + "font-size": 1, + "font-size-adjust": 1, + "font-stretch": 1, + "font-style": 1, + "font-variant": 1, + "font-weight": 1, + "grid-columns": 1, + "grid-rows": 1, + "hanging-punctuation": 1, + "height": 1, + "hyphenate-after": 1, + "hyphenate-before": 1, + "hyphenate-character": 1, + "hyphenate-lines": 1, + "hyphenate-resource": 1, + "hyphens": 1, + "icon": 1, + "image-orientation": 1, + "image-rendering": 1, + "image-resolution": 1, + "inline-box-align": 1, + "left": 1, + "letter-spacing": 1, + "line-height": 1, + "line-stacking": 1, + "line-stacking-ruby": 1, + "line-stacking-shift": 1, + "line-stacking-strategy": 1, + "list-style": 1, + "list-style-image": 1, + "list-style-position": 1, + "list-style-type": 1, + "margin": 1, + "margin-bottom": 1, + "margin-left": 1, + "margin-right": 1, + "margin-top": 1, + "mark": 1, + "mark-after": 1, + "mark-before": 1, + "marks": 1, + "marquee-direction": 1, + "marquee-play-count": 1, + "marquee-speed": 1, + "marquee-style": 1, + "max-height": 1, + "max-width": 1, + "min-height": 1, + "min-width": 1, + "move-to": 1, + "nav-down": 1, + "nav-index": 1, + "nav-left": 1, + "nav-right": 1, + "nav-up": 1, + "opacity": 1, + "orphans": 1, + "outline": 1, + "outline-color": 1, + "outline-offset": 1, + "outline-style": 1, + "outline-width": 1, + "overflow": 1, + "overflow-style": 1, + "overflow-x": 1, + "overflow-y": 1, + "padding": 1, + "padding-bottom": 1, + "padding-left": 1, + "padding-right": 1, + "padding-top": 1, + "page": 1, + "page-break-after": 1, + "page-break-before": 1, + "page-break-inside": 1, + "page-policy": 1, + "pause": 1, + "pause-after": 1, + "pause-before": 1, + "perspective": 1, + "perspective-origin": 1, + "phonemes": 1, + "pitch": 1, + "pitch-range": 1, + "play-during": 1, + "position": 1, + "presentation-level": 1, + "punctuation-trim": 1, + "quotes": 1, + "rendering-intent": 1, + "resize": 1, + "rest": 1, + "rest-after": 1, + "rest-before": 1, + "richness": 1, + "right": 1, + "rotation": 1, + "rotation-point": 1, + "ruby-align": 1, + "ruby-overhang": 1, + "ruby-position": 1, + "ruby-span": 1, + "size": 1, + "speak": 1, + "speak-header": 1, + "speak-numeral": 1, + "speak-punctuation": 1, + "speech-rate": 1, + "stress": 1, + "string-set": 1, + "table-layout": 1, + "target": 1, + "target-name": 1, + "target-new": 1, + "target-position": 1, + "text-align": 1, + "text-align-last": 1, + "text-decoration": 1, + "text-emphasis": 1, + "text-height": 1, + "text-indent": 1, + "text-justify": 1, + "text-outline": 1, + "text-shadow": 1, + "text-transform": 1, + "text-wrap": 1, + "top": 1, + "transform": 1, + "transform-origin": 1, + "transform-style": 1, + "transition": 1, + "transition-delay": 1, + "transition-duration": 1, + "transition-property": 1, + "transition-timing-function": 1, + "unicode-bidi": 1, + "user-modify": 1, + "user-select": 1, + "vertical-align": 1, + "visibility": 1, + "voice-balance": 1, + "voice-duration": 1, + "voice-family": 1, + "voice-pitch": 1, + "voice-pitch-range": 1, + "voice-rate": 1, + "voice-stress": 1, + "voice-volume": 1, + "volume": 1, + "white-space": 1, + "white-space-collapse": 1, + "widows": 1, + "width": 1, + "word-break": 1, + "word-spacing": 1, + "word-wrap": 1, + "z-index": 1, + + //IE + "filter": 1, + "zoom": 1 + }; + + parser.addListener("property", function(event){ + var name = event.property.text.toLowerCase(); + + if (!properties[name] && name.charAt(0) != "-"){ + reporter.error("Unknown property '" + event.property + "'.", event.line, event.col, rule); + } + + }); + } + +}); +/* + * Rule: Don't use classes or IDs with elements (a.foo or a#foo). + */ +CSSLint.addRule({ + + //rule information + id: "overqualified-elements", + name: "Overqualified Elements", + desc: "Don't use classes or IDs with elements (a.foo or a#foo).", + browsers: "All", + + //initialization + init: function(parser, reporter){ + var rule = this, + classes = {}; + + parser.addListener("startrule", function(event){ + var selectors = event.selectors, + selector, + part, + modifier, + i, j, k; + + for (i=0; i < selectors.length; i++){ + selector = selectors[i]; + + for (j=0; j < selector.parts.length; j++){ + part = selector.parts[j]; + if (part instanceof parserlib.css.SelectorPart){ + for (k=0; k < part.modifiers.length; k++){ + modifier = part.modifiers[k]; + if (part.elementName && modifier.type == "id"){ + reporter.warn("Element (" + part + ") is overqualified, just use " + modifier + " without element name.", part.line, part.col, rule); + } else if (modifier.type == "class"){ + + if (!classes[modifier]){ + classes[modifier] = []; + } + classes[modifier].push({ modifier: modifier, part: part }); + } + } + } + } + } + }); + + parser.addListener("endstylesheet", function(){ + + var prop; + for (prop in classes){ + if (classes.hasOwnProperty(prop)){ + + //one use means that this is overqualified + if (classes[prop].length == 1 && classes[prop][0].part.elementName){ + reporter.warn("Element (" + classes[prop][0].part + ") is overqualified, just use " + classes[prop][0].modifier + " without element name.", classes[prop][0].part.line, classes[prop][0].part.col, rule); + } + } + } + }); + } + +}); +/* + * Rule: Headings (h1-h6) should not be qualified (namespaced). + */ +CSSLint.addRule({ + + //rule information + id: "qualified-headings", + name: "Qualified Headings", + desc: "Headings should not be qualified (namespaced).", + browsers: "All", + + //initialization + init: function(parser, reporter){ + var rule = this; + + parser.addListener("startrule", function(event){ + var selectors = event.selectors, + selector, + part, + i, j; + + for (i=0; i < selectors.length; i++){ + selector = selectors[i]; + + for (j=0; j < selector.parts.length; j++){ + part = selector.parts[j]; + if (part instanceof parserlib.css.SelectorPart){ + if (part.elementName && /h[1-6]/.test(part.elementName.toString()) && j > 0){ + reporter.warn("Heading (" + part.elementName + ") should not be qualified.", part.line, part.col, rule); + } + } + } + } + }); + } + +}); +/* + * Rule: Selectors that look like regular expressions are slow and should be avoided. + */ +CSSLint.addRule({ + + //rule information + id: "regex-selectors", + name: "Regex Selectors", + desc: "Selectors that look like regular expressions are slow and should be avoided.", + browsers: "All", + + //initialization + init: function(parser, reporter){ + var rule = this; + + parser.addListener("startrule", function(event){ + var selectors = event.selectors, + selector, + part, + modifier, + i, j, k; + + for (i=0; i < selectors.length; i++){ + selector = selectors[i]; + for (j=0; j < selector.parts.length; j++){ + part = selector.parts[j]; + if (part instanceof parserlib.css.SelectorPart){ + for (k=0; k < part.modifiers.length; k++){ + modifier = part.modifiers[k]; + if (modifier.type == "attribute"){ + if (/([\~\|\^\$\*]=)/.test(modifier)){ + reporter.warn("Attribute selectors with " + RegExp.$1 + " are slow!", modifier.line, modifier.col, rule); + } + } + + } + } + } + } + }); + } + +}); +/* + * Rule: Total number of rules should not exceed x. + */ +CSSLint.addRule({ + + //rule information + id: "rules-count", + name: "Rules Count", + desc: "Track how many rules there are.", + browsers: "All", + + //initialization + init: function(parser, reporter){ + var rule = this, + count = 0; + + //count each rule + parser.addListener("startrule", function(){ + count++; + }); + + parser.addListener("endstylesheet", function(){ + reporter.stat("rule-count", count); + }); + } + +}); +/* + * Rule: Use shorthand properties where possible. + * + */ + +CSSLint.addRule({ + + //rule information + id: "shorthand", + name: "Shorthand Properties", + desc: "Use shorthand properties where possible.", + browsers: "All", + + //initialization + init: function(parser, reporter){ + var rule = this, + prop, i, len, + propertiesToCheck = {}, + properties, + mapping = { + "margin": [ + "margin-top", + "margin-bottom", + "margin-left", + "margin-right" + ], + "padding": [ + "padding-top", + "padding-bottom", + "padding-left", + "padding-right" + ] + }; + + //initialize propertiesToCheck + for (prop in mapping){ + if (mapping.hasOwnProperty(prop)){ + for (i=0, len=mapping[prop].length; i < len; i++){ + propertiesToCheck[mapping[prop][i]] = prop; + } + } + } + + function startRule(event){ + properties = {}; + } + + //event handler for end of rules + function endRule(event){ + + var prop, i, len, total; + + //check which properties this rule has + for (prop in mapping){ + if (mapping.hasOwnProperty(prop)){ + total=0; + + for (i=0, len=mapping[prop].length; i < len; i++){ + total += properties[mapping[prop][i]] ? 1 : 0; + } + + if (total == mapping[prop].length){ + reporter.warn("The properties " + mapping[prop].join(", ") + " can be replaced by " + prop + ".", event.line, event.col, rule); + } + } + } + } + + parser.addListener("startrule", startRule); + parser.addListener("startfontface", startRule); + + //check for use of "font-size" + parser.addListener("property", function(event){ + var name = event.property.toString().toLowerCase(), + value = event.value.parts[0].value; + + if (propertiesToCheck[name]){ + properties[name] = 1; + } + }); + + parser.addListener("endrule", endRule); + parser.addListener("endfontface", endRule); + + } + +}); +/* + * Rule: Don't use text-indent for image replacement if you need to support rtl. + * + */ +/* + * Should we be checking for rtl/ltr? + */ +//Commented out due to lack of tests +CSSLint.addRule({ + + //rule information + id: "text-indent", + name: "Text Indent", + desc: "Checks for text indent less than -99px", + browsers: "All", + + //initialization + init: function(parser, reporter){ + var rule = this, + textIndent = false; + + + function startRule(event){ + textIndent = false; + } + + //event handler for end of rules + function endRule(event){ + if (textIndent){ + reporter.warn("Negative text-indent doesn't work well with RTL. If you use text-indent for image replacement explicitly set text-direction for that item to ltr.", textIndent.line, textIndent.col, rule); + } + } + + parser.addListener("startrule", startRule); + parser.addListener("startfontface", startRule); + + //check for use of "font-size" + parser.addListener("property", function(event){ + var name = event.property.toString().toLowerCase(), + value = event.value; + + if (name == "text-indent" && value.parts[0].value < -99){ + textIndent = event.property; + } else if (name == "direction" && value == "ltr"){ + textIndent = false; + } + }); + + parser.addListener("endrule", endRule); + parser.addListener("endfontface", endRule); + + } + +}); +/* + * Rule: Headings (h1-h6) should be defined only once. + */ +CSSLint.addRule({ + + //rule information + id: "unique-headings", + name: "Unique Headings", + desc: "Headings should be defined only once.", + browsers: "All", + + //initialization + init: function(parser, reporter){ + var rule = this; + + var headings = { + h1: 0, + h2: 0, + h3: 0, + h4: 0, + h5: 0, + h6: 0 + }; + + parser.addListener("startrule", function(event){ + var selectors = event.selectors, + selector, + part, + pseudo, + i, j; + + for (i=0; i < selectors.length; i++){ + selector = selectors[i]; + part = selector.parts[selector.parts.length-1]; + + if (part.elementName && /(h[1-6])/i.test(part.elementName.toString())){ + + for (j=0; j < part.modifiers.length; j++){ + if (part.modifiers[j].type == "pseudo"){ + pseudo = true; + break; + } + } + + if (!pseudo){ + headings[RegExp.$1]++; + if (headings[RegExp.$1] > 1) { + reporter.warn("Heading (" + part.elementName + ") has already been defined.", part.line, part.col, rule); + } + } + } + } + }); + + parser.addListener("endstylesheet", function(event){ + var prop, + messages = []; + + for (var prop in headings){ + if (headings.hasOwnProperty(prop)){ + if (headings[prop] > 1){ + messages.push(headings[prop] + " " + prop + "s"); + } + } + } + + if (messages.length){ + reporter.rollupWarn("You have " + messages.join(", ") + " defined in this stylesheet.", rule); + } + }); + } + +}); +/* + * Rule: Don't use universal selector because it's slow. + */ +CSSLint.addRule({ + + //rule information + id: "universal-selector", + name: "Universal Selector", + desc: "The universal selector (*) is known to be slow.", + browsers: "All", + + //initialization + init: function(parser, reporter){ + var rule = this; + + parser.addListener("startrule", function(event){ + var selectors = event.selectors, + selector, + part, + modifier, + i, j, k; + + for (i=0; i < selectors.length; i++){ + selector = selectors[i]; + + part = selector.parts[selector.parts.length-1]; + if (part.elementName == "*"){ + reporter.warn(rule.desc, part.line, part.col, rule); + } + } + }); + } + +}); +/* + * Rule: When using a vendor-prefixed property, make sure to + * include the standard one. + */ +CSSLint.addRule({ + + //rule information + id: "vendor-prefix", + name: "Vendor Prefix", + desc: "When using a vendor-prefixed property, make sure to include the standard one.", + browsers: "All", + + //initialization + init: function(parser, reporter){ + var rule = this, + properties, + num, + propertiesToCheck = { + "-webkit-border-radius": "border-radius", + "-webkit-border-top-left-radius": "border-top-left-radius", + "-webkit-border-top-right-radius": "border-top-right-radius", + "-webkit-border-bottom-left-radius": "border-bottom-left-radius", + "-webkit-border-bottom-right-radius": "border-bottom-right-radius", + + "-o-border-radius": "border-radius", + "-o-border-top-left-radius": "border-top-left-radius", + "-o-border-top-right-radius": "border-top-right-radius", + "-o-border-bottom-left-radius": "border-bottom-left-radius", + "-o-border-bottom-right-radius": "border-bottom-right-radius", + + "-moz-border-radius": "border-radius", + "-moz-border-radius-topleft": "border-top-left-radius", + "-moz-border-radius-topright": "border-top-right-radius", + "-moz-border-radius-bottomleft": "border-bottom-left-radius", + "-moz-border-radius-bottomright": "border-bottom-right-radius", + + "-moz-column-count": "column-count", + "-webkit-column-count": "column-count", + + "-moz-column-gap": "column-gap", + "-webkit-column-gap": "column-gap", + + "-moz-column-rule": "column-rule", + "-webkit-column-rule": "column-rule", + + "-moz-column-rule-style": "column-rule-style", + "-webkit-column-rule-style": "column-rule-style", + + "-moz-column-rule-color": "column-rule-color", + "-webkit-column-rule-color": "column-rule-color", + + "-moz-column-rule-width": "column-rule-width", + "-webkit-column-rule-width": "column-rule-width", + + "-moz-column-width": "column-width", + "-webkit-column-width": "column-width", + + "-webkit-column-span": "column-span", + "-webkit-columns": "columns", + + "-moz-box-shadow": "box-shadow", + "-webkit-box-shadow": "box-shadow", + + "-moz-transform" : "transform", + "-webkit-transform" : "transform", + "-o-transform" : "transform", + "-ms-transform" : "transform", + + "-moz-transform-origin" : "transform-origin", + "-webkit-transform-origin" : "transform-origin", + "-o-transform-origin" : "transform-origin", + "-ms-transform-origin" : "transform-origin", + + "-moz-box-sizing" : "box-sizing", + "-webkit-box-sizing" : "box-sizing", + + "-moz-user-select" : "user-select", + "-khtml-user-select" : "user-select", + "-webkit-user-select" : "user-select" + }; + + //event handler for beginning of rules + function startRule(){ + properties = {}; + num=1; + } + + //event handler for end of rules + function endRule(event){ + var prop, + i, len, + standard, + needed, + actual, + needsStandard = []; + + for (prop in properties){ + if (propertiesToCheck[prop]){ + needsStandard.push({ actual: prop, needed: propertiesToCheck[prop]}); + } + } + + for (i=0, len=needsStandard.length; i < len; i++){ + needed = needsStandard[i].needed; + actual = needsStandard[i].actual; + + if (!properties[needed]){ + reporter.warn("Missing standard property '" + needed + "' to go along with '" + actual + "'.", event.line, event.col, rule); + } else { + //make sure standard property is last + if (properties[needed][0].pos < properties[actual][0].pos){ + reporter.warn("Standard property '" + needed + "' should come after vendor-prefixed property '" + actual + "'.", event.line, event.col, rule); + } + } + } + + } + + parser.addListener("startrule", startRule); + parser.addListener("startfontface", startRule); + + parser.addListener("property", function(event){ + var name = event.property.text.toLowerCase(); + + if (!properties[name]){ + properties[name] = []; + } + + properties[name].push({ name: event.property, value : event.value, pos:num++ }); + }); + + parser.addListener("endrule", endRule); + parser.addListener("endfontface", endRule); + } + +}); +/* + * Rule: If an element has a width of 100%, be careful when placing within + * an element that has padding. It may look strange. + */ +//Commented out pending further review. +/*CSSLint.addRule({ + + //rule information + id: "width-100", + name: "Width 100%", + desc: "Be careful when using width: 100% on elements.", + browsers: "All", + + //initialization + init: function(parser, reporter){ + var rule = this, + width100, + boxsizing; + + parser.addListener("startrule", function(){ + width100 = null; + boxsizing = false; + }); + + parser.addListener("property", function(event){ + var name = event.property.text.toLowerCase(), + value = event.value; + + if (name == "width" && value == "100%"){ + width100 = event.property; + } else if (name == "box-sizing" || /\-(?:webkit|ms|moz)\-box-sizing/.test(name)){ //means you know what you're doing + boxsizing = true; + } + }); + + parser.addListener("endrule", function(){ + if (width100 && !boxsizing){ + reporter.warn("Elements with a width of 100% may not appear as you expect inside of other elements.", width100.line, width100.col, rule); + } + }); + } + +});*/ +/* + * Rule: You don't need to specify units when a value is 0. + */ +CSSLint.addRule({ + + //rule information + id: "zero-units", + name: "Zero Units", + desc: "You don't need to specify units when a value is 0.", + browsers: "All", + + //initialization + init: function(parser, reporter){ + var rule = this; + + //count how many times "float" is used + parser.addListener("property", function(event){ + var parts = event.value.parts, + i = 0, + len = parts.length; + + while(i < len){ + if ((parts[i].units || parts[i].type == "percentage") && parts[i].value === 0){ + reporter.warn("Values of 0 shouldn't have units specified.", parts[i].line, parts[i].col, rule); + } + i++; + } + + }); + + } + +}); +CSSLint.addFormatter({ + //format information + id: "checkstyle-xml", + name: "Checkstyle XML format", + + startFormat: function(){ + return ""; + }, + + endFormat: function(){ + return ""; + }, + + formatResults: function(results, filename) { + var messages = results.messages, + output = []; + + /** + * Generate a source string for a rule. + * Checkstyle source strings usually resemble Java class names e.g + * net.csslint.SomeRuleName + * @param {Object} rule + * @return rule source as {String} + */ + var generateSource = function(rule) { + if (!rule || !('name' in rule)) { + return ""; + } + return 'net.csslint.' + rule.name.replace(/\s/g,''); + }; + + /** + * Replace special characters before write to output. + * + * Rules: + * - single quotes is the escape sequence for double-quotes + * - < is the escape sequence for < + * - > is the escape sequence for > + * + * @param {String} message to escape + * @return escaped message as {String} + */ + var escapeSpecialCharacters = function(str) { + if (!str || str.constructor !== String) { + return ""; + } + return str.replace(/\"/g, "'").replace(//g, ">"); + }; + + if (messages.length > 0) { + output.push(""); + messages.forEach(function (message, i) { + if (message.rollup) { + //ignore rollups for now + } else { + output.push(""); + } + }); + output.push(""); + } + + return output.join(""); + } +}); +CSSLint.addFormatter({ + //format information + id: "compact", + name: "Compact, 'porcelain' format", + + startFormat: function(){ + return ""; + }, + + endFormat: function(){ + return ""; + }, + + formatResults: function(results, filename) { + var messages = results.messages, + output = "", + pos = filename.lastIndexOf("/"), + shortFilename = filename; + + if (messages.length === 0) { + return shortFilename + ": Lint Free!"; + } + + if (pos == -1){ + pos = filename.lastIndexOf("\\"); + } + if (pos > -1){ + shortFilename = filename.substring(pos+1); + } + + messages.forEach(function (message, i) { + if (message.rollup) { + output += shortFilename + ": " + message.message + "\n"; + } else { + output += shortFilename + ": " + "line " + message.line + + ", col " + message.col + ", " + message.message + "\n"; + } + }); + + return output; + } +}); +CSSLint.addFormatter({ + //format information + id: "csslint-xml", + name: "CSSLint XML format", + + /** + * Return opening root XML tag. + * @return {String} to prepend before all results + */ + startFormat: function(){ + return ""; + }, + + /** + * Return closing root XML tag. + * @return {String} to append after all results + */ + endFormat: function(){ + return ""; + }, + + formatResults: function(results, filename) { + var messages = results.messages, + output = []; + + /** + * Replace special characters before write to output. + * + * Rules: + * - single quotes is the escape sequence for double-quotes + * - < is the escape sequence for < + * - > is the escape sequence for > + * + * @param {String} message to escape + * @return escaped message as {String} + */ + var escapeSpecialCharacters = function(str) { + if (!str || str.constructor !== String) { + return ""; + } + return str.replace(/\"/g, "'").replace(//g, ">"); + }; + + if (messages.length > 0) { + output.push(""); + messages.forEach(function (message, i) { + if (message.rollup) { + output.push(""); + } else { + output.push(""); + } + }); + output.push(""); + } + + return output.join(""); + } +}); +CSSLint.addFormatter({ + //format information + id: "lint-xml", + name: "Lint XML format", + + startFormat: function(){ + return ""; + }, + + endFormat: function(){ + return ""; + }, + + formatResults: function(results, filename) { + var messages = results.messages, + output = []; + + /** + * Replace special characters before write to output. + * + * Rules: + * - single quotes is the escape sequence for double-quotes + * - < is the escape sequence for < + * - > is the escape sequence for > + * + * @param {String} message to escape + * @return escaped message as {String} + */ + var escapeSpecialCharacters = function(str) { + if (!str || str.constructor !== String) { + return ""; + } + return str.replace(/\"/g, "'").replace(//g, ">"); + }; + + if (messages.length > 0) { + + output.push(""); + messages.forEach(function (message, i) { + if (message.rollup) { + output.push(""); + } else { + output.push(""); + } + }); + output.push(""); + } + + return output.join(""); + } +}); +CSSLint.addFormatter({ + //format information + id: "text", + name: "Plain Text", + + startFormat: function(){ + return ""; + }, + + endFormat: function(){ + return ""; + }, + + formatResults: function(results, filename) { + var messages = results.messages; + if (messages.length === 0) { + return "\n\ncsslint: No errors in " + filename + "."; + } + + output = "\n\ncsslint: There are " + messages.length + " problems in " + filename + "."; + var pos = filename.lastIndexOf("/"), + shortFilename = filename; + + if (pos == -1){ + pos = filename.lastIndexOf("\\"); + } + if (pos > -1){ + shortFilename = filename.substring(pos+1); + } + + messages.forEach(function (message, i) { + output = output + "\n\n" + shortFilename; + if (message.rollup) { + output += "\n" + (i+1) + ": " + message.type; + output += "\n" + message.message; + } else { + output += "\n" + (i+1) + ": " + message.type + " at line " + message.line + ", col " + message.col; + output += "\n" + message.message; + output += "\n" + message.evidence; + } + }); + + return output; + } +}); + +return CSSLint; +})(); + +/* + * Encapsulates all of the CLI functionality. The api argument simply + * provides environment-specific functionality. + */ +/*global CSSLint*/ +function cli(api){ + + //------------------------------------------------------------------------- + // Helper functions + //------------------------------------------------------------------------- + + /** + * Returns an array of messages for a particular type. + * @param messages {Array} Array of CSS Lint messages. + * @param type {String} The type of message to filter on. + * @return {Array} An array of matching messages. + */ + function pluckByType(messages, type){ + return messages.filter(function(message) { + return message.type === type; + }); + } + + /** + * Returns a ruleset object based on the CLI options. + * @param options {Object} The CLI options. + * @return {Object} A ruleset object. + */ + function gatherRules(options){ + var ruleset; + + if (options.rules){ + ruleset = {}; + options.rules.split(",").forEach(function(value){ + ruleset[value] = 1; + }); + } + + return ruleset; + } + + /** + * Outputs all available rules to the CLI. + * @return {void} + */ + function printRules(){ + api.print(""); + var rules = CSSLint.getRules(); + rules.forEach(function(rule){ + api.print(rule.id + "\n" + rule.desc + "\n"); + }); + } + + /** + * Given a file name and options, run verification and print formatted output. + * @param {String} name of file to process + * @param {Object} options for processing + * @return {Number} exit code + */ + function processFile(filename, options) { + var input = api.readFile(filename), + result = CSSLint.verify(input, gatherRules(options)), + formatId = options.format || "text", + messages = result.messages || [], + exitCode = 0; + + if (!input) { + api.print("csslint: Could not read file data in " + filename + ". Is the file empty?"); + exitCode = 1; + } else { + api.print(CSSLint.getFormatter(formatId).formatResults(result, filename, formatId)); + + if (messages.length > 0 && pluckByType(messages, "error").length > 0) { + exitCode = 1; + } + } + + return exitCode; + } + + /** + * Outputs the help screen to the CLI. + * @return {void} + */ + function outputHelp(){ + api.print([ + "\nUsage: csslint-rhino.js [options]* [file|dir]*", + " ", + "Global Options", + " --help Displays this information.", + " --format= Indicate which format to use for output.", + " --list-rules Outputs all of the rules available.", + " --rules= Indicate which rules to include.", + " --version Outputs the current version number." + ].join("\n") + "\n"); + } + + /** + * Given an Array of filenames, print wrapping output and process them. + * @param files {Array} filenames list + * @param options {Object} options object + * @return {Number} exit code + */ + function processFiles(files, options){ + var exitCode = 0, + formatId = options.format || "text", + formatter, + output; + + if (!files.length) { + api.print("csslint: No files specified."); + exitCode = 1; + } else { + if (!CSSLint.hasFormat(formatId)){ + api.print("csslint: Unknown format '" + formatId + "'. Cannot proceed."); + exitCode = 1; + } else { + formatter = CSSLint.getFormatter(formatId); + + output = formatter.startFormat(); + if (output){ + api.print(output); + } + + files.forEach(function(file){ + if (exitCode == 0) { + exitCode = processFile(file,options); + } else { + processFile(file,options); + } + }); + + output = formatter.endFormat(); + if (output){ + api.print(output); + } + } + } + return exitCode; + } + + //----------------------------------------------------------------------------- + // Process command line + //----------------------------------------------------------------------------- + + var args = api.args, + argName, + arg = args.shift(), + options = {}, + files = []; + + while(arg){ + if (arg.indexOf("--") === 0){ + argName = arg.substring(2); + options[argName] = true; + + if (argName.indexOf("rules=") > -1){ + options.rules = argName.substring(argName.indexOf("=") + 1); + } else if (argName.indexOf("format=") > -1) { + options.format = argName.substring(argName.indexOf("=") + 1); + } + } else { + + //see if it's a directory or a file + if (api.isDirectory(arg)){ + files = files.concat(api.getFiles(arg)); + } else { + files.push(arg); + } + } + arg = args.shift(); + } + + if (options.help || arguments.length === 0){ + outputHelp(); + api.quit(0); + } + + if (options.version){ + api.print("v" + CSSLint.version); + api.quit(0); + } + + if (options["list-rules"]){ + printRules(); + api.quit(0); + } + + files = api.fixFilenames(files); + + api.quit(processFiles(files,options)); +} +/* + * CSSLint Rhino Command Line Interface + */ + +importPackage(java.io); + +cli({ + args: arguments, + print: print, + quit: quit, + + isDirectory: function(name){ + var dir = new File(name); + return dir.isDirectory(); + }, + + getFiles: function(dir){ + var files = []; + + function traverse(dir) { + var dirList = dir.listFiles(); + dirList.forEach(function (file) { + if (/\.css$/.test(file)) { + files.push(file.toString()); + } else if (file.isDirectory()) { + traverse(file); + } + }); + }; + + traverse(new File(dir)); + + return files; + }, + + fixFilenames: function(files){ + return files; + }, + + readFile: readFile + +}); diff --git a/lib/jshint-rhino.js b/lib/jshint-rhino.js new file mode 100644 index 0000000..d9e419e --- /dev/null +++ b/lib/jshint-rhino.js @@ -0,0 +1,60 @@ +/*jshint boss: true */ + +load("jshint.js"); + +(function (args) { + var name = args[0], + optstr = args[1], // arg1=val1,arg2=val2,... + predef = args[2], // global1=override,global2,global3,... + opts = { rhino: true }, + input; + + if (!name) { + print('Usage: jshint.js file.js'); + quit(1); + } + + if (optstr) { + optstr.split(',').forEach(function (arg) { + var o = arg.split('='); + opts[o[0]] = (function (ov) { + switch (ov) { + case 'true': + return true; + case 'false': + return false; + default: + return ov; + } + })(o[1]); + }); + } + + if (predef) { + opts.predef = {}; + predef.split(',').forEach(function (arg) { + var global = arg.split('='); + opts.predef[global[0]] = (function (override) { + return (override === 'false') ? false : true; + })(global[1]); + }); + } + + input = readFile(name); + + if (!input) { + print('jshint: Couldn\'t open file ' + name); + quit(1); + } + + if (!JSHINT(input, opts)) { + for (var i = 0, err; err = JSHINT.errors[i]; i++) { + print(err.reason + ' (' + name + ':' + err.line + ':' + err.character + ')'); + print('> ' + (err.evidence || '').replace(/^\s*(\S*(\s+\S+)*)\s*$/, "$1")); + print(''); + } + quit(1); + } + + quit(0); +}(arguments)); diff --git a/lib/jshint.js b/lib/jshint.js new file mode 100644 index 0000000..0cd530a --- /dev/null +++ b/lib/jshint.js @@ -0,0 +1,4026 @@ +/*! + * JSHint, by JSHint Community. + * + * Licensed under the same slightly modified MIT license that JSLint is. + * It stops evil-doers everywhere. + * + * JSHint is a derivative work of JSLint: + * + * Copyright (c) 2002 Douglas Crockford (www.JSLint.com) + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom + * the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * The Software shall be used for Good, not Evil. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + * JSHint was forked from 2010-12-16 edition of JSLint. + * + */ + +/* + JSHINT is a global function. It takes two parameters. + + var myResult = JSHINT(source, option); + + The first parameter is either a string or an array of strings. If it is a + string, it will be split on '\n' or '\r'. If it is an array of strings, it + is assumed that each string represents one line. The source can be a + JavaScript text or a JSON text. + + The second parameter is an optional object of options which control the + operation of JSHINT. Most of the options are booleans: They are all + optional and have a default value of false. One of the options, predef, + can be an array of names, which will be used to declare global variables, + or an object whose keys are used as global names, with a boolean value + that determines if they are assignable. + + If it checks out, JSHINT returns true. Otherwise, it returns false. + + If false, you can inspect JSHINT.errors to find out the problems. + JSHINT.errors is an array of objects containing these members: + + { + line : The line (relative to 0) at which the lint was found + character : The character (relative to 0) at which the lint was found + reason : The problem + evidence : The text line in which the problem occurred + raw : The raw message before the details were inserted + a : The first detail + b : The second detail + c : The third detail + d : The fourth detail + } + + If a fatal error was found, a null will be the last element of the + JSHINT.errors array. + + You can request a Function Report, which shows all of the functions + and the parameters and vars that they use. This can be used to find + implied global variables and other problems. The report is in HTML and + can be inserted in an HTML . + + var myReport = JSHINT.report(limited); + + If limited is true, then the report will be limited to only errors. + + You can request a data structure which contains JSHint's results. + + var myData = JSHINT.data(); + + It returns a structure with this form: + + { + errors: [ + { + line: NUMBER, + character: NUMBER, + reason: STRING, + evidence: STRING + } + ], + functions: [ + name: STRING, + line: NUMBER, + last: NUMBER, + param: [ + STRING + ], + closure: [ + STRING + ], + var: [ + STRING + ], + exception: [ + STRING + ], + outer: [ + STRING + ], + unused: [ + STRING + ], + global: [ + STRING + ], + label: [ + STRING + ] + ], + globals: [ + STRING + ], + member: { + STRING: NUMBER + }, + unuseds: [ + { + name: STRING, + line: NUMBER + } + ], + implieds: [ + { + name: STRING, + line: NUMBER + } + ], + urls: [ + STRING + ], + json: BOOLEAN + } + + Empty arrays will not be included. + +*/ + +/*jshint + evil: true, nomen: false, onevar: false, regexp: false, strict: true, boss: true, + undef: true, maxlen: 100 +*/ + +/*members "\b", "\t", "\n", "\f", "\r", "!=", "!==", "\"", "%", "(begin)", + "(breakage)", "(context)", "(error)", "(global)", "(identifier)", "(last)", + "(line)", "(loopage)", "(name)", "(onevar)", "(params)", "(scope)", + "(statement)", "(verb)", "*", "+", "++", "-", "--", "\/", "<", "<=", "==", + "===", ">", ">=", $, $$, $A, $F, $H, $R, $break, $continue, $w, Abstract, Ajax, + __filename, __dirname, ActiveXObject, Array, ArrayBuffer, ArrayBufferView, Audio, + Autocompleter, Assets, Boolean, Builder, Buffer, Browser, COM, CScript, Canvas, + CustomAnimation, Class, Control, Chain, Color, Cookie, Core, DataView, Date, + Debug, Draggable, Draggables, Droppables, Document, DomReady, DOMReady, Drag, + E, Enumerator, Enumerable, Element, Elements, Error, Effect, EvalError, Event, + Events, FadeAnimation, Field, Flash, Float32Array, Float64Array, Form, + FormField, Frame, FormData, Function, Fx, GetObject, Group, Hash, HotKey, + HTMLElement, HTMLAnchorElement, HTMLBaseElement, HTMLBlockquoteElement, + HTMLBodyElement, HTMLBRElement, HTMLButtonElement, HTMLCanvasElement, HTMLDirectoryElement, + HTMLDivElement, HTMLDListElement, HTMLFieldSetElement, + HTMLFontElement, HTMLFormElement, HTMLFrameElement, HTMLFrameSetElement, + HTMLHeadElement, HTMLHeadingElement, HTMLHRElement, HTMLHtmlElement, + HTMLIFrameElement, HTMLImageElement, HTMLInputElement, HTMLIsIndexElement, + HTMLLabelElement, HTMLLayerElement, HTMLLegendElement, HTMLLIElement, + HTMLLinkElement, HTMLMapElement, HTMLMenuElement, HTMLMetaElement, + HTMLModElement, HTMLObjectElement, HTMLOListElement, HTMLOptGroupElement, + HTMLOptionElement, HTMLParagraphElement, HTMLParamElement, HTMLPreElement, + HTMLQuoteElement, HTMLScriptElement, HTMLSelectElement, HTMLStyleElement, + HtmlTable, HTMLTableCaptionElement, HTMLTableCellElement, HTMLTableColElement, + HTMLTableElement, HTMLTableRowElement, HTMLTableSectionElement, + HTMLTextAreaElement, HTMLTitleElement, HTMLUListElement, HTMLVideoElement + Iframe, IframeShim, Image, Int16Array, Int32Array, Int8Array, + Insertion, InputValidator, JSON, Keyboard, Locale, LN10, LN2, LOG10E, LOG2E, + MAX_VALUE, MIN_VALUE, Mask, Math, MenuItem, MoveAnimation, MooTools, Native, + NEGATIVE_INFINITY, Number, Object, ObjectRange, Option, Options, OverText, PI, + POSITIVE_INFINITY, PeriodicalExecuter, Point, Position, Prototype, RangeError, + Rectangle, ReferenceError, RegExp, ResizeAnimation, Request, RotateAnimation, + SQRT1_2, SQRT2, ScrollBar, ScriptEngine, ScriptEngineBuildVersion, + ScriptEngineMajorVersion, ScriptEngineMinorVersion, Scriptaculous, Scroller, + Slick, Slider, Selector, SharedWorker, String, Style, SyntaxError, Sortable, Sortables, + SortableObserver, Sound, Spinner, System, Swiff, Text, TextArea, Template, + Timer, Tips, Type, TypeError, Toggle, Try, unescape, URI, URIError, URL, VBArray, WSH, + WScript, Web, Window, XMLDOM, XMLHttpRequest, XPathEvaluator, XPathException, + XPathExpression, XPathNamespace, XPathNSResolver, XPathResult, "\\", a, + addEventListener, address, alert, apply, applicationCache, arguments, arity, + asi, b, bitwise, block, blur, boolOptions, boss, browser, c, call, callee, + caller, cases, charAt, charCodeAt, character, clearInterval, clearTimeout, + close, closed, closure, comment, condition, confirm, console, constructor, + content, couch, create, css, curly, d, data, datalist, dd, debug, decodeURI, + decodeURIComponent, defaultStatus, defineClass, deserialize, devel, document, + dojo, dijit, dojox, define, edition, else, emit, encodeURI, encodeURIComponent, + entityify, eqeqeq, eqnull, errors, es5, escape, eval, event, evidence, evil, + ex, exception, exec, exps, expr, exports, FileReader, first, floor, focus, + forin, fragment, frames, from, fromCharCode, fud, funct, function, functions, + g, gc, getComputedStyle, getRow, GLOBAL, global, globals, globalstrict, + hasOwnProperty, help, history, i, id, identifier, immed, implieds, include, + indent, indexOf, init, ins, instanceOf, isAlpha, isApplicationRunning, isArray, + isDigit, isFinite, isNaN, iterator, join, jshint, + JSHINT, json, jquery, jQuery, keys, label, labelled, last, lastsemic, laxbreak, + latedef, lbp, led, left, length, line, load, loadClass, localStorage, location, + log, loopfunc, m, match, maxerr, maxlen, member,message, meta, module, moveBy, + moveTo, mootools, name, navigator, new, newcap, noarg, node, noempty, nomen, + nonew, nonstandard, nud, onbeforeunload, onblur, onerror, onevar, onecase, onfocus, + onload, onresize, onunload, open, openDatabase, openURL, opener, opera, options, outer, param, + parent, parseFloat, parseInt, passfail, plusplus, predef, print, process, prompt, + proto, prototype, prototypejs, push, quit, range, raw, reach, reason, regexp, + readFile, readUrl, regexdash, removeEventListener, replace, report, require, + reserved, resizeBy, resizeTo, resolvePath, resumeUpdates, respond, rhino, right, + runCommand, scroll, screen, scripturl, scrollBy, scrollTo, scrollbar, search, seal, + send, serialize, sessionStorage, setInterval, setTimeout, shift, slice, sort,spawn, + split, stack, status, start, strict, sub, substr, supernew, shadow, supplant, sum, + sync, test, toLowerCase, toString, toUpperCase, toint32, token, top, trailing, type, + typeOf, Uint16Array, Uint32Array, Uint8Array, undef, unused, urls, validthis, value, valueOf, + var, version, WebSocket, white, window, Worker, wsh*/ + +/*global exports: false */ + +// We build the application inside a function so that we produce only a single +// global variable. That function will be invoked immediately, and its return +// value is the JSHINT function itself. + +var JSHINT = (function () { + "use strict"; + + var anonname, // The guessed name for anonymous functions. + +// These are operators that should not be used with the ! operator. + + bang = { + '<' : true, + '<=' : true, + '==' : true, + '===': true, + '!==': true, + '!=' : true, + '>' : true, + '>=' : true, + '+' : true, + '-' : true, + '*' : true, + '/' : true, + '%' : true + }, + + // These are the JSHint boolean options. + boolOptions = { + asi : true, // if automatic semicolon insertion should be tolerated + bitwise : true, // if bitwise operators should not be allowed + boss : true, // if advanced usage of assignments should be allowed + browser : true, // if the standard browser globals should be predefined + couch : true, // if CouchDB globals should be predefined + curly : true, // if curly braces around all blocks should be required + debug : true, // if debugger statements should be allowed + devel : true, // if logging globals should be predefined (console, + // alert, etc.) + dojo : true, // if Dojo Toolkit globals should be predefined + eqeqeq : true, // if === should be required + eqnull : true, // if == null comparisons should be tolerated + es5 : true, // if ES5 syntax should be allowed + evil : true, // if eval should be allowed + expr : true, // if ExpressionStatement should be allowed as Programs + forin : true, // if for in statements must filter + globalstrict: true, // if global "use strict"; should be allowed (also + // enables 'strict') + immed : true, // if immediate invocations must be wrapped in parens + iterator : true, // if the `__iterator__` property should be disallowed + jquery : true, // if jQuery globals should be predefined + lastsemic : true, // if semicolons may be ommitted for the trailing + // statements inside of a one-line blocks. + latedef : true, // if the use before definition should not be tolerated + laxbreak : true, // if line breaks should not be checked + loopfunc : true, // if functions should be allowed to be defined within + // loops + mootools : true, // if MooTools globals should be predefined + newcap : true, // if constructor names must be capitalized + noarg : true, // if arguments.caller and arguments.callee should be + // disallowed + node : true, // if the Node.js environment globals should be + // predefined + noempty : true, // if empty blocks should be disallowed + nonew : true, // if using `new` for side-effects should be disallowed + nonstandard : true, // if non-standard (but widely adopted) globals should + // be predefined + nomen : true, // if names should be checked + onevar : true, // if only one var statement per function should be + // allowed + onecase : true, // if one case switch statements should be allowed + passfail : true, // if the scan should stop on first error + plusplus : true, // if increment/decrement should not be allowed + proto : true, // if the `__proto__` property should be disallowed + prototypejs : true, // if Prototype and Scriptaculous globals should be + // predefined + regexdash : true, // if unescaped last dash (-) inside brackets should be + // tolerated + regexp : true, // if the . should not be allowed in regexp literals + rhino : true, // if the Rhino environment globals should be predefined + undef : true, // if variables should be declared before used + scripturl : true, // if script-targeted URLs should be tolerated + shadow : true, // if variable shadowing should be tolerated + strict : true, // require the "use strict"; pragma + sub : true, // if all forms of subscript notation are tolerated + supernew : true, // if `new function () { ... };` and `new Object;` + // should be tolerated + trailing : true, // if trailing whitespace rules apply + validthis : true, // if 'this' inside a non-constructor function is valid. + // This is a function scoped option only. + white : true, // if strict whitespace rules apply + wsh : true // if the Windows Scripting Host environment globals + // should be predefined + }, + + // browser contains a set of global names which are commonly provided by a + // web browser environment. + browser = { + ArrayBuffer : false, + ArrayBufferView : false, + Audio : false, + addEventListener : false, + applicationCache : false, + blur : false, + clearInterval : false, + clearTimeout : false, + close : false, + closed : false, + DataView : false, + defaultStatus : false, + document : false, + event : false, + FileReader : false, + Float32Array : false, + Float64Array : false, + FormData : false, + focus : false, + frames : false, + getComputedStyle : false, + HTMLElement : false, + HTMLAnchorElement : false, + HTMLBaseElement : false, + HTMLBlockquoteElement : false, + HTMLBodyElement : false, + HTMLBRElement : false, + HTMLButtonElement : false, + HTMLCanvasElement : false, + HTMLDirectoryElement : false, + HTMLDivElement : false, + HTMLDListElement : false, + HTMLFieldSetElement : false, + HTMLFontElement : false, + HTMLFormElement : false, + HTMLFrameElement : false, + HTMLFrameSetElement : false, + HTMLHeadElement : false, + HTMLHeadingElement : false, + HTMLHRElement : false, + HTMLHtmlElement : false, + HTMLIFrameElement : false, + HTMLImageElement : false, + HTMLInputElement : false, + HTMLIsIndexElement : false, + HTMLLabelElement : false, + HTMLLayerElement : false, + HTMLLegendElement : false, + HTMLLIElement : false, + HTMLLinkElement : false, + HTMLMapElement : false, + HTMLMenuElement : false, + HTMLMetaElement : false, + HTMLModElement : false, + HTMLObjectElement : false, + HTMLOListElement : false, + HTMLOptGroupElement : false, + HTMLOptionElement : false, + HTMLParagraphElement : false, + HTMLParamElement : false, + HTMLPreElement : false, + HTMLQuoteElement : false, + HTMLScriptElement : false, + HTMLSelectElement : false, + HTMLStyleElement : false, + HTMLTableCaptionElement : false, + HTMLTableCellElement : false, + HTMLTableColElement : false, + HTMLTableElement : false, + HTMLTableRowElement : false, + HTMLTableSectionElement : false, + HTMLTextAreaElement : false, + HTMLTitleElement : false, + HTMLUListElement : false, + HTMLVideoElement : false, + history : false, + Int16Array : false, + Int32Array : false, + Int8Array : false, + Image : false, + length : false, + localStorage : false, + location : false, + moveBy : false, + moveTo : false, + name : false, + navigator : false, + onbeforeunload : true, + onblur : true, + onerror : true, + onfocus : true, + onload : true, + onresize : true, + onunload : true, + open : false, + openDatabase : false, + opener : false, + Option : false, + parent : false, + print : false, + removeEventListener : false, + resizeBy : false, + resizeTo : false, + screen : false, + scroll : false, + scrollBy : false, + scrollTo : false, + sessionStorage : false, + setInterval : false, + setTimeout : false, + SharedWorker : false, + status : false, + top : false, + Uint16Array : false, + Uint32Array : false, + Uint8Array : false, + WebSocket : false, + window : false, + Worker : false, + XMLHttpRequest : false, + XPathEvaluator : false, + XPathException : false, + XPathExpression : false, + XPathNamespace : false, + XPathNSResolver : false, + XPathResult : false + }, + + couch = { + "require" : false, + respond : false, + getRow : false, + emit : false, + send : false, + start : false, + sum : false, + log : false, + exports : false, + module : false + }, + + devel = { + alert : false, + confirm : false, + console : false, + Debug : false, + opera : false, + prompt : false + }, + + dojo = { + dojo : false, + dijit : false, + dojox : false, + define : false, + "require" : false + }, + + escapes = { + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\f': '\\f', + '\r': '\\r', + '"' : '\\"', + '/' : '\\/', + '\\': '\\\\' + }, + + funct, // The current function + + functionicity = [ + 'closure', 'exception', 'global', 'label', + 'outer', 'unused', 'var' + ], + + functions, // All of the functions + + global, // The global scope + implied, // Implied globals + inblock, + indent, + jsonmode, + + jquery = { + '$' : false, + jQuery : false + }, + + lines, + lookahead, + member, + membersOnly, + + mootools = { + '$' : false, + '$$' : false, + Assets : false, + Browser : false, + Chain : false, + Class : false, + Color : false, + Cookie : false, + Core : false, + Document : false, + DomReady : false, + DOMReady : false, + Drag : false, + Element : false, + Elements : false, + Event : false, + Events : false, + Fx : false, + Group : false, + Hash : false, + HtmlTable : false, + Iframe : false, + IframeShim : false, + InputValidator : false, + instanceOf : false, + Keyboard : false, + Locale : false, + Mask : false, + MooTools : false, + Native : false, + Options : false, + OverText : false, + Request : false, + Scroller : false, + Slick : false, + Slider : false, + Sortables : false, + Spinner : false, + Swiff : false, + Tips : false, + Type : false, + typeOf : false, + URI : false, + Window : false + }, + + nexttoken, + + node = { + __filename : false, + __dirname : false, + Buffer : false, + console : false, + exports : false, + GLOBAL : false, + global : false, + module : false, + process : false, + require : false + }, + + noreach, + option, + predefined, // Global variables defined by option + prereg, + prevtoken, + + prototypejs = { + '$' : false, + '$$' : false, + '$A' : false, + '$F' : false, + '$H' : false, + '$R' : false, + '$break' : false, + '$continue' : false, + '$w' : false, + Abstract : false, + Ajax : false, + Class : false, + Enumerable : false, + Element : false, + Event : false, + Field : false, + Form : false, + Hash : false, + Insertion : false, + ObjectRange : false, + PeriodicalExecuter: false, + Position : false, + Prototype : false, + Selector : false, + Template : false, + Toggle : false, + Try : false, + Autocompleter : false, + Builder : false, + Control : false, + Draggable : false, + Draggables : false, + Droppables : false, + Effect : false, + Sortable : false, + SortableObserver : false, + Sound : false, + Scriptaculous : false + }, + + rhino = { + defineClass : false, + deserialize : false, + gc : false, + help : false, + load : false, + loadClass : false, + print : false, + quit : false, + readFile : false, + readUrl : false, + runCommand : false, + seal : false, + serialize : false, + spawn : false, + sync : false, + toint32 : false, + version : false + }, + + scope, // The current scope + src, + stack, + + // standard contains the global names that are provided by the + // ECMAScript standard. + standard = { + Array : false, + Boolean : false, + Date : false, + decodeURI : false, + decodeURIComponent : false, + encodeURI : false, + encodeURIComponent : false, + Error : false, + 'eval' : false, + EvalError : false, + Function : false, + hasOwnProperty : false, + isFinite : false, + isNaN : false, + JSON : false, + Math : false, + Number : false, + Object : false, + parseInt : false, + parseFloat : false, + RangeError : false, + ReferenceError : false, + RegExp : false, + String : false, + SyntaxError : false, + TypeError : false, + URIError : false + }, + + // widely adopted global names that are not part of ECMAScript standard + nonstandard = { + escape : false, + unescape : false + }, + + standard_member = { + E : true, + LN2 : true, + LN10 : true, + LOG2E : true, + LOG10E : true, + MAX_VALUE : true, + MIN_VALUE : true, + NEGATIVE_INFINITY : true, + PI : true, + POSITIVE_INFINITY : true, + SQRT1_2 : true, + SQRT2 : true + }, + + strict_mode, + syntax = {}, + tab, + token, + urls, + warnings, + + wsh = { + ActiveXObject : true, + Enumerator : true, + GetObject : true, + ScriptEngine : true, + ScriptEngineBuildVersion : true, + ScriptEngineMajorVersion : true, + ScriptEngineMinorVersion : true, + VBArray : true, + WSH : true, + WScript : true + }; + + // Regular expressions. Some of these are stupidly long. + var ax, cx, tx, nx, nxg, lx, ix, jx, ft; + (function () { + /*jshint maxlen:300 */ + + // unsafe comment or string + ax = /@cc|<\/?|script|\]\s*\]|<\s*!|</i; + + // unsafe characters that are silently deleted by one or more browsers + cx = /[\u0000-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/; + + // token + tx = /^\s*([(){}\[.,:;'"~\?\]#@]|==?=?|\/(\*(jshint|jslint|members?|global)?|=|\/)?|\*[\/=]?|\+(?:=|\++)?|-(?:=|-+)?|%=?|&[&=]?|\|[|=]?|>>?>?=?|<([\/=!]|\!(\[|--)?|<=?)?|\^=?|\!=?=?|[a-zA-Z_$][a-zA-Z0-9_$]*|[0-9]+([xX][0-9a-fA-F]+|\.[0-9]*)?([eE][+\-]?[0-9]+)?)/; + + // characters in strings that need escapement + nx = /[\u0000-\u001f&<"\/\\\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/; + nxg = /[\u0000-\u001f&<"\/\\\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; + + // star slash + lx = /\*\/|\/\*/; + + // identifier + ix = /^([a-zA-Z_$][a-zA-Z0-9_$]*)$/; + + // javascript url + jx = /^(?:javascript|jscript|ecmascript|vbscript|mocha|livescript)\s*:/i; + + // catches /* falls through */ comments + ft = /^\s*\/\*\s*falls\sthrough\s*\*\/\s*$/; + }()); + + function F() {} // Used by Object.create + + function is_own(object, name) { + +// The object.hasOwnProperty method fails when the property under consideration +// is named 'hasOwnProperty'. So we have to use this more convoluted form. + + return Object.prototype.hasOwnProperty.call(object, name); + } + +// Provide critical ES5 functions to ES3. + + if (typeof Array.isArray !== 'function') { + Array.isArray = function (o) { + return Object.prototype.toString.apply(o) === '[object Array]'; + }; + } + + if (typeof Object.create !== 'function') { + Object.create = function (o) { + F.prototype = o; + return new F(); + }; + } + + if (typeof Object.keys !== 'function') { + Object.keys = function (o) { + var a = [], k; + for (k in o) { + if (is_own(o, k)) { + a.push(k); + } + } + return a; + }; + } + +// Non standard methods + + if (typeof String.prototype.entityify !== 'function') { + String.prototype.entityify = function () { + return this + .replace(/&/g, '&') + .replace(//g, '>'); + }; + } + + if (typeof String.prototype.isAlpha !== 'function') { + String.prototype.isAlpha = function () { + return (this >= 'a' && this <= 'z\uffff') || + (this >= 'A' && this <= 'Z\uffff'); + }; + } + + if (typeof String.prototype.isDigit !== 'function') { + String.prototype.isDigit = function () { + return (this >= '0' && this <= '9'); + }; + } + + if (typeof String.prototype.supplant !== 'function') { + String.prototype.supplant = function (o) { + return this.replace(/\{([^{}]*)\}/g, function (a, b) { + var r = o[b]; + return typeof r === 'string' || typeof r === 'number' ? r : a; + }); + }; + } + + if (typeof String.prototype.name !== 'function') { + String.prototype.name = function () { + +// If the string looks like an identifier, then we can return it as is. +// If the string contains no control characters, no quote characters, and no +// backslash characters, then we can simply slap some quotes around it. +// Otherwise we must also replace the offending characters with safe +// sequences. + + if (ix.test(this)) { + return this; + } + if (nx.test(this)) { + return '"' + this.replace(nxg, function (a) { + var c = escapes[a]; + if (c) { + return c; + } + return '\\u' + ('0000' + a.charCodeAt().toString(16)).slice(-4); + }) + '"'; + } + return '"' + this + '"'; + }; + } + + + function combine(t, o) { + var n; + for (n in o) { + if (is_own(o, n)) { + t[n] = o[n]; + } + } + } + + function assume() { + if (option.couch) + combine(predefined, couch); + + if (option.rhino) + combine(predefined, rhino); + + if (option.prototypejs) + combine(predefined, prototypejs); + + if (option.node) + combine(predefined, node); + + if (option.devel) + combine(predefined, devel); + + if (option.dojo) + combine(predefined, dojo); + + if (option.browser) + combine(predefined, browser); + + if (option.nonstandard) + combine(predefined, nonstandard); + + if (option.jquery) + combine(predefined, jquery); + + if (option.mootools) + combine(predefined, mootools); + + if (option.wsh) + combine(predefined, wsh); + + if (option.globalstrict && option.strict !== false) + option.strict = true; + } + + + // Produce an error warning. + function quit(message, line, chr) { + var percentage = Math.floor((line / lines.length) * 100); + + throw { + name: 'JSHintError', + line: line, + character: chr, + message: message + " (" + percentage + "% scanned)." + }; + } + + function warning(m, t, a, b, c, d) { + var ch, l, w; + t = t || nexttoken; + if (t.id === '(end)') { // `~ + t = token; + } + l = t.line || 0; + ch = t.from || 0; + w = { + id: '(error)', + raw: m, + evidence: lines[l - 1] || '', + line: l, + character: ch, + a: a, + b: b, + c: c, + d: d + }; + w.reason = m.supplant(w); + JSHINT.errors.push(w); + if (option.passfail) { + quit('Stopping. ', l, ch); + } + warnings += 1; + if (warnings >= option.maxerr) { + quit("Too many errors.", l, ch); + } + return w; + } + + function warningAt(m, l, ch, a, b, c, d) { + return warning(m, { + line: l, + from: ch + }, a, b, c, d); + } + + function error(m, t, a, b, c, d) { + var w = warning(m, t, a, b, c, d); + } + + function errorAt(m, l, ch, a, b, c, d) { + return error(m, { + line: l, + from: ch + }, a, b, c, d); + } + + + +// lexical analysis and token construction + + var lex = (function lex() { + var character, from, line, s; + +// Private lex methods + + function nextLine() { + var at, + tw; // trailing whitespace check + + if (line >= lines.length) + return false; + + character = 1; + s = lines[line]; + line += 1; + at = s.search(/ \t/); + + if (at >= 0) + warningAt("Mixed spaces and tabs.", line, at + 1); + + s = s.replace(/\t/g, tab); + at = s.search(cx); + + if (at >= 0) + warningAt("Unsafe character.", line, at); + + if (option.maxlen && option.maxlen < s.length) + warningAt("Line too long.", line, s.length); + + // Check for trailing whitespaces + tw = s.search(/\s+$/); + if (option.trailing && ~tw && !~s.search(/^\s+$/)) + warningAt("Trailing whitespace.", line, tw); + + return true; + } + +// Produce a token object. The token inherits from a syntax symbol. + + function it(type, value) { + var i, t; + if (type === '(color)' || type === '(range)') { + t = {type: type}; + } else if (type === '(punctuator)' || + (type === '(identifier)' && is_own(syntax, value))) { + t = syntax[value] || syntax['(error)']; + } else { + t = syntax[type]; + } + t = Object.create(t); + if (type === '(string)' || type === '(range)') { + if (!option.scripturl && jx.test(value)) { + warningAt("Script URL.", line, from); + } + } + if (type === '(identifier)') { + t.identifier = true; + if (value === '__proto__' && !option.proto) { + warningAt("The '{a}' property is deprecated.", + line, from, value); + } else if (value === '__iterator__' && !option.iterator) { + warningAt("'{a}' is only available in JavaScript 1.7.", + line, from, value); + } else if (option.nomen && (value.charAt(0) === '_' || + value.charAt(value.length - 1) === '_')) { + if (!option.node || token.id == '.' || + (value != '__dirname' && value != '__filename')) { + warningAt("Unexpected {a} in '{b}'.", line, from, "dangling '_'", value); + } + } + } + t.value = value; + t.line = line; + t.character = character; + t.from = from; + i = t.id; + if (i !== '(endline)') { + prereg = i && + (('(,=:[!&|?{};'.indexOf(i.charAt(i.length - 1)) >= 0) || + i === 'return'); + } + return t; + } + + // Public lex methods + return { + init: function (source) { + if (typeof source === 'string') { + lines = source + .replace(/\r\n/g, '\n') + .replace(/\r/g, '\n') + .split('\n'); + } else { + lines = source; + } + + // If the first line is a shebang (#!), make it a blank and move on. + // Shebangs are used by Node scripts. + if (lines[0] && lines[0].substr(0, 2) == '#!') + lines[0] = ''; + + line = 0; + nextLine(); + from = 1; + }, + + range: function (begin, end) { + var c, value = ''; + from = character; + if (s.charAt(0) !== begin) { + errorAt("Expected '{a}' and instead saw '{b}'.", + line, character, begin, s.charAt(0)); + } + for (;;) { + s = s.slice(1); + character += 1; + c = s.charAt(0); + switch (c) { + case '': + errorAt("Missing '{a}'.", line, character, c); + break; + case end: + s = s.slice(1); + character += 1; + return it('(range)', value); + case '\\': + warningAt("Unexpected '{a}'.", line, character, c); + } + value += c; + } + + }, + + + // token -- this is called by advance to get the next token + token: function () { + var b, c, captures, d, depth, high, i, l, low, q, t; + + function match(x) { + var r = x.exec(s), r1; + if (r) { + l = r[0].length; + r1 = r[1]; + c = r1.charAt(0); + s = s.substr(l); + from = character + l - r1.length; + character += l; + return r1; + } + } + + function string(x) { + var c, j, r = ''; + + if (jsonmode && x !== '"') { + warningAt("Strings must use doublequote.", + line, character); + } + + function esc(n) { + var i = parseInt(s.substr(j + 1, n), 16); + j += n; + if (i >= 32 && i <= 126 && + i !== 34 && i !== 92 && i !== 39) { + warningAt("Unnecessary escapement.", line, character); + } + character += n; + c = String.fromCharCode(i); + } + j = 0; + for (;;) { + while (j >= s.length) { + j = 0; + if (!nextLine()) { + errorAt("Unclosed string.", line, from); + } + } + c = s.charAt(j); + if (c === x) { + character += 1; + s = s.substr(j + 1); + return it('(string)', r, x); + } + if (c < ' ') { + if (c === '\n' || c === '\r') { + break; + } + warningAt("Control character in string: {a}.", + line, character + j, s.slice(0, j)); + } else if (c === '\\') { + j += 1; + character += 1; + c = s.charAt(j); + switch (c) { + case '\\': + case '"': + case '/': + break; + case '\'': + if (jsonmode) { + warningAt("Avoid \\'.", line, character); + } + break; + case 'b': + c = '\b'; + break; + case 'f': + c = '\f'; + break; + case 'n': + c = '\n'; + break; + case 'r': + c = '\r'; + break; + case 't': + c = '\t'; + break; + case 'u': + esc(4); + break; + case 'v': + if (jsonmode) { + warningAt("Avoid \\v.", line, character); + } + c = '\v'; + break; + case 'x': + if (jsonmode) { + warningAt("Avoid \\x-.", line, character); + } + esc(2); + break; + default: + warningAt("Bad escapement.", line, character); + } + } + r += c; + character += 1; + j += 1; + } + } + + for (;;) { + if (!s) { + return it(nextLine() ? '(endline)' : '(end)', ''); + } + t = match(tx); + if (!t) { + t = ''; + c = ''; + while (s && s < '!') { + s = s.substr(1); + } + if (s) { + errorAt("Unexpected '{a}'.", line, character, s.substr(0, 1)); + } + } else { + + // identifier + + if (c.isAlpha() || c === '_' || c === '$') { + return it('(identifier)', t); + } + + // number + + if (c.isDigit()) { + if (!isFinite(Number(t))) { + warningAt("Bad number '{a}'.", + line, character, t); + } + if (s.substr(0, 1).isAlpha()) { + warningAt("Missing space after '{a}'.", + line, character, t); + } + if (c === '0') { + d = t.substr(1, 1); + if (d.isDigit()) { + if (token.id !== '.') { + warningAt("Don't use extra leading zeros '{a}'.", + line, character, t); + } + } else if (jsonmode && (d === 'x' || d === 'X')) { + warningAt("Avoid 0x-. '{a}'.", + line, character, t); + } + } + if (t.substr(t.length - 1) === '.') { + warningAt( +"A trailing decimal point can be confused with a dot '{a}'.", line, character, t); + } + return it('(number)', t); + } + switch (t) { + + // string + + case '"': + case "'": + return string(t); + + // // comment + + case '//': + if (src) { + warningAt("Unexpected comment.", line, character); + } + s = ''; + token.comment = true; + break; + + // /* comment + + case '/*': + if (src) { + warningAt("Unexpected comment.", line, character); + } + for (;;) { + i = s.search(lx); + if (i >= 0) { + break; + } + if (!nextLine()) { + errorAt("Unclosed comment.", line, character); + } + } + character += i + 2; + if (s.substr(i, 1) === '/') { + errorAt("Nested comment.", line, character); + } + s = s.substr(i + 2); + token.comment = true; + break; + + // /*members /*jshint /*global + + case '/*members': + case '/*member': + case '/*jshint': + case '/*jslint': + case '/*global': + case '*/': + return { + value: t, + type: 'special', + line: line, + character: character, + from: from + }; + + case '': + break; + // / + case '/': + if (token.id === '/=') { + errorAt( +"A regular expression literal can be confused with '/='.", line, from); + } + if (prereg) { + depth = 0; + captures = 0; + l = 0; + for (;;) { + b = true; + c = s.charAt(l); + l += 1; + switch (c) { + case '': + errorAt("Unclosed regular expression.", + line, from); + return; + case '/': + if (depth > 0) { + warningAt("Unescaped '{a}'.", + line, from + l, '/'); + } + c = s.substr(0, l - 1); + q = { + g: true, + i: true, + m: true + }; + while (q[s.charAt(l)] === true) { + q[s.charAt(l)] = false; + l += 1; + } + character += l; + s = s.substr(l); + q = s.charAt(0); + if (q === '/' || q === '*') { + errorAt("Confusing regular expression.", + line, from); + } + return it('(regexp)', c); + case '\\': + c = s.charAt(l); + if (c < ' ') { + warningAt( +"Unexpected control character in regular expression.", line, from + l); + } else if (c === '<') { + warningAt( +"Unexpected escaped character '{a}' in regular expression.", line, from + l, c); + } + l += 1; + break; + case '(': + depth += 1; + b = false; + if (s.charAt(l) === '?') { + l += 1; + switch (s.charAt(l)) { + case ':': + case '=': + case '!': + l += 1; + break; + default: + warningAt( +"Expected '{a}' and instead saw '{b}'.", line, from + l, ':', s.charAt(l)); + } + } else { + captures += 1; + } + break; + case '|': + b = false; + break; + case ')': + if (depth === 0) { + warningAt("Unescaped '{a}'.", + line, from + l, ')'); + } else { + depth -= 1; + } + break; + case ' ': + q = 1; + while (s.charAt(l) === ' ') { + l += 1; + q += 1; + } + if (q > 1) { + warningAt( +"Spaces are hard to count. Use {{a}}.", line, from + l, q); + } + break; + case '[': + c = s.charAt(l); + if (c === '^') { + l += 1; + if (option.regexp) { + warningAt("Insecure '{a}'.", + line, from + l, c); + } else if (s.charAt(l) === ']') { + errorAt("Unescaped '{a}'.", + line, from + l, '^'); + } + } + q = false; + if (c === ']') { + warningAt("Empty class.", line, + from + l - 1); + q = true; + } +klass: do { + c = s.charAt(l); + l += 1; + switch (c) { + case '[': + case '^': + warningAt("Unescaped '{a}'.", + line, from + l, c); + q = true; + break; + case '-': + if (q) { + q = false; + } else { + warningAt("Unescaped '{a}'.", + line, from + l, '-'); + q = true; + } + break; + case ']': + if (!q && !option.regexdash) { + warningAt("Unescaped '{a}'.", + line, from + l - 1, '-'); + } + break klass; + case '\\': + c = s.charAt(l); + if (c < ' ') { + warningAt( +"Unexpected control character in regular expression.", line, from + l); + } else if (c === '<') { + warningAt( +"Unexpected escaped character '{a}' in regular expression.", line, from + l, c); + } + l += 1; + q = true; + break; + case '/': + warningAt("Unescaped '{a}'.", + line, from + l - 1, '/'); + q = true; + break; + case '<': + q = true; + break; + default: + q = true; + } + } while (c); + break; + case '.': + if (option.regexp) { + warningAt("Insecure '{a}'.", line, + from + l, c); + } + break; + case ']': + case '?': + case '{': + case '}': + case '+': + case '*': + warningAt("Unescaped '{a}'.", line, + from + l, c); + } + if (b) { + switch (s.charAt(l)) { + case '?': + case '+': + case '*': + l += 1; + if (s.charAt(l) === '?') { + l += 1; + } + break; + case '{': + l += 1; + c = s.charAt(l); + if (c < '0' || c > '9') { + warningAt( +"Expected a number and instead saw '{a}'.", line, from + l, c); + } + l += 1; + low = +c; + for (;;) { + c = s.charAt(l); + if (c < '0' || c > '9') { + break; + } + l += 1; + low = +c + (low * 10); + } + high = low; + if (c === ',') { + l += 1; + high = Infinity; + c = s.charAt(l); + if (c >= '0' && c <= '9') { + l += 1; + high = +c; + for (;;) { + c = s.charAt(l); + if (c < '0' || c > '9') { + break; + } + l += 1; + high = +c + (high * 10); + } + } + } + if (s.charAt(l) !== '}') { + warningAt( +"Expected '{a}' and instead saw '{b}'.", line, from + l, '}', c); + } else { + l += 1; + } + if (s.charAt(l) === '?') { + l += 1; + } + if (low > high) { + warningAt( +"'{a}' should not be greater than '{b}'.", line, from + l, low, high); + } + } + } + } + c = s.substr(0, l - 1); + character += l; + s = s.substr(l); + return it('(regexp)', c); + } + return it('(punctuator)', t); + + // punctuator + + case '#': + return it('(punctuator)', t); + default: + return it('(punctuator)', t); + } + } + } + } + }; + }()); + + + function addlabel(t, type) { + + if (t === 'hasOwnProperty') { + warning("'hasOwnProperty' is a really bad name."); + } + +// Define t in the current function in the current scope. + + if (is_own(funct, t) && !funct['(global)']) { + if (funct[t] === true) { + if (option.latedef) + warning("'{a}' was used before it was defined.", nexttoken, t); + } else { + if (!option.shadow) + warning("'{a}' is already defined.", nexttoken, t); + } + } + + funct[t] = type; + if (funct['(global)']) { + global[t] = funct; + if (is_own(implied, t)) { + if (option.latedef) + warning("'{a}' was used before it was defined.", nexttoken, t); + delete implied[t]; + } + } else { + scope[t] = funct; + } + } + + + function doOption() { + var b, obj, filter, o = nexttoken.value, t, v; + switch (o) { + case '*/': + error("Unbegun comment."); + break; + case '/*members': + case '/*member': + o = '/*members'; + if (!membersOnly) { + membersOnly = {}; + } + obj = membersOnly; + break; + case '/*jshint': + case '/*jslint': + obj = option; + filter = boolOptions; + break; + case '/*global': + obj = predefined; + break; + default: + error("What?"); + } + t = lex.token(); +loop: for (;;) { + for (;;) { + if (t.type === 'special' && t.value === '*/') { + break loop; + } + if (t.id !== '(endline)' && t.id !== ',') { + break; + } + t = lex.token(); + } + if (t.type !== '(string)' && t.type !== '(identifier)' && + o !== '/*members') { + error("Bad option.", t); + } + v = lex.token(); + if (v.id === ':') { + v = lex.token(); + if (obj === membersOnly) { + error("Expected '{a}' and instead saw '{b}'.", + t, '*/', ':'); + } + if (t.value === 'indent' && (o === '/*jshint' || o === '/*jslint')) { + b = +v.value; + if (typeof b !== 'number' || !isFinite(b) || b <= 0 || + Math.floor(b) !== b) { + error("Expected a small integer and instead saw '{a}'.", + v, v.value); + } + obj.white = true; + obj.indent = b; + } else if (t.value === 'maxerr' && (o === '/*jshint' || o === '/*jslint')) { + b = +v.value; + if (typeof b !== 'number' || !isFinite(b) || b <= 0 || + Math.floor(b) !== b) { + error("Expected a small integer and instead saw '{a}'.", + v, v.value); + } + obj.maxerr = b; + } else if (t.value === 'maxlen' && (o === '/*jshint' || o === '/*jslint')) { + b = +v.value; + if (typeof b !== 'number' || !isFinite(b) || b <= 0 || + Math.floor(b) !== b) { + error("Expected a small integer and instead saw '{a}'.", + v, v.value); + } + obj.maxlen = b; + } else if (t.value == 'validthis') { + if (funct['(global)']) { + error("Option 'validthis' can't be used in a global scope."); + } else { + if (v.value === 'true' || v.value === 'false') + obj[t.value] = v.value === 'true'; + else + error("Bad option value.", v); + } + } else if (v.value === 'true') { + obj[t.value] = true; + } else if (v.value === 'false') { + obj[t.value] = false; + } else { + error("Bad option value.", v); + } + t = lex.token(); + } else { + if (o === '/*jshint' || o === '/*jslint') { + error("Missing option value.", t); + } + obj[t.value] = false; + t = v; + } + } + if (filter) { + assume(); + } + } + + +// We need a peek function. If it has an argument, it peeks that much farther +// ahead. It is used to distinguish +// for ( var i in ... +// from +// for ( var i = ... + + function peek(p) { + var i = p || 0, j = 0, t; + + while (j <= i) { + t = lookahead[j]; + if (!t) { + t = lookahead[j] = lex.token(); + } + j += 1; + } + return t; + } + + + +// Produce the next token. It looks for programming errors. + + function advance(id, t) { + switch (token.id) { + case '(number)': + if (nexttoken.id === '.') { + warning("A dot following a number can be confused with a decimal point.", token); + } + break; + case '-': + if (nexttoken.id === '-' || nexttoken.id === '--') { + warning("Confusing minusses."); + } + break; + case '+': + if (nexttoken.id === '+' || nexttoken.id === '++') { + warning("Confusing plusses."); + } + break; + } + + if (token.type === '(string)' || token.identifier) { + anonname = token.value; + } + + if (id && nexttoken.id !== id) { + if (t) { + if (nexttoken.id === '(end)') { + warning("Unmatched '{a}'.", t, t.id); + } else { + warning("Expected '{a}' to match '{b}' from line {c} and instead saw '{d}'.", + nexttoken, id, t.id, t.line, nexttoken.value); + } + } else if (nexttoken.type !== '(identifier)' || + nexttoken.value !== id) { + warning("Expected '{a}' and instead saw '{b}'.", + nexttoken, id, nexttoken.value); + } + } + + prevtoken = token; + token = nexttoken; + for (;;) { + nexttoken = lookahead.shift() || lex.token(); + if (nexttoken.id === '(end)' || nexttoken.id === '(error)') { + return; + } + if (nexttoken.type === 'special') { + doOption(); + } else { + if (nexttoken.id !== '(endline)') { + break; + } + } + } + } + + +// This is the heart of JSHINT, the Pratt parser. In addition to parsing, it +// is looking for ad hoc lint patterns. We add .fud to Pratt's model, which is +// like .nud except that it is only used on the first token of a statement. +// Having .fud makes it much easier to define statement-oriented languages like +// JavaScript. I retained Pratt's nomenclature. + +// .nud Null denotation +// .fud First null denotation +// .led Left denotation +// lbp Left binding power +// rbp Right binding power + +// They are elements of the parsing method called Top Down Operator Precedence. + + function expression(rbp, initial) { + var left, isArray = false; + + if (nexttoken.id === '(end)') + error("Unexpected early end of program.", token); + + advance(); + if (initial) { + anonname = 'anonymous'; + funct['(verb)'] = token.value; + } + if (initial === true && token.fud) { + left = token.fud(); + } else { + if (token.nud) { + left = token.nud(); + } else { + if (nexttoken.type === '(number)' && token.id === '.') { + warning("A leading decimal point can be confused with a dot: '.{a}'.", + token, nexttoken.value); + advance(); + return token; + } else { + error("Expected an identifier and instead saw '{a}'.", + token, token.id); + } + } + while (rbp < nexttoken.lbp) { + isArray = token.value == 'Array'; + advance(); + if (isArray && token.id == '(' && nexttoken.id == ')') + warning("Use the array literal notation [].", token); + if (token.led) { + left = token.led(left); + } else { + error("Expected an operator and instead saw '{a}'.", + token, token.id); + } + } + } + return left; + } + + +// Functions for conformance of style. + + function adjacent(left, right) { + left = left || token; + right = right || nexttoken; + if (option.white) { + if (left.character !== right.from && left.line === right.line) { + warning("Unexpected space after '{a}'.", right, left.value); + } + } + } + + function nobreak(left, right) { + left = left || token; + right = right || nexttoken; + if (option.white && (left.character !== right.from || left.line !== right.line)) { + warning("Unexpected space before '{a}'.", right, right.value); + } + } + + function nospace(left, right) { + left = left || token; + right = right || nexttoken; + if (option.white && !left.comment) { + if (left.line === right.line) { + adjacent(left, right); + } + } + } + + function nonadjacent(left, right) { + if (option.white) { + left = left || token; + right = right || nexttoken; + if (left.line === right.line && left.character === right.from) { + warning("Missing space after '{a}'.", + nexttoken, left.value); + } + } + } + + function nobreaknonadjacent(left, right) { + left = left || token; + right = right || nexttoken; + if (!option.laxbreak && left.line !== right.line) { + warning("Bad line breaking before '{a}'.", right, right.id); + } else if (option.white) { + left = left || token; + right = right || nexttoken; + if (left.character === right.from) { + warning("Missing space after '{a}'.", + nexttoken, left.value); + } + } + } + + function indentation(bias) { + var i; + if (option.white && nexttoken.id !== '(end)') { + i = indent + (bias || 0); + if (nexttoken.from !== i) { + warning( +"Expected '{a}' to have an indentation at {b} instead at {c}.", + nexttoken, nexttoken.value, i, nexttoken.from); + } + } + } + + function nolinebreak(t) { + t = t || token; + if (t.line !== nexttoken.line) { + warning("Line breaking error '{a}'.", t, t.value); + } + } + + + function comma() { + if (token.line !== nexttoken.line) { + if (!option.laxbreak) { + warning("Bad line breaking before '{a}'.", token, nexttoken.id); + } + } else if (token.character !== nexttoken.from && option.white) { + warning("Unexpected space after '{a}'.", nexttoken, token.value); + } + advance(','); + nonadjacent(token, nexttoken); + } + + +// Functional constructors for making the symbols that will be inherited by +// tokens. + + function symbol(s, p) { + var x = syntax[s]; + if (!x || typeof x !== 'object') { + syntax[s] = x = { + id: s, + lbp: p, + value: s + }; + } + return x; + } + + + function delim(s) { + return symbol(s, 0); + } + + + function stmt(s, f) { + var x = delim(s); + x.identifier = x.reserved = true; + x.fud = f; + return x; + } + + + function blockstmt(s, f) { + var x = stmt(s, f); + x.block = true; + return x; + } + + + function reserveName(x) { + var c = x.id.charAt(0); + if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) { + x.identifier = x.reserved = true; + } + return x; + } + + + function prefix(s, f) { + var x = symbol(s, 150); + reserveName(x); + x.nud = (typeof f === 'function') ? f : function () { + this.right = expression(150); + this.arity = 'unary'; + if (this.id === '++' || this.id === '--') { + if (option.plusplus) { + warning("Unexpected use of '{a}'.", this, this.id); + } else if ((!this.right.identifier || this.right.reserved) && + this.right.id !== '.' && this.right.id !== '[') { + warning("Bad operand.", this); + } + } + return this; + }; + return x; + } + + + function type(s, f) { + var x = delim(s); + x.type = s; + x.nud = f; + return x; + } + + + function reserve(s, f) { + var x = type(s, f); + x.identifier = x.reserved = true; + return x; + } + + + function reservevar(s, v) { + return reserve(s, function () { + if (typeof v === 'function') { + v(this); + } + return this; + }); + } + + + function infix(s, f, p, w) { + var x = symbol(s, p); + reserveName(x); + x.led = function (left) { + if (!w) { + nobreaknonadjacent(prevtoken, token); + nonadjacent(token, nexttoken); + } + if (typeof f === 'function') { + return f(left, this); + } else { + this.left = left; + this.right = expression(p); + return this; + } + }; + return x; + } + + + function relation(s, f) { + var x = symbol(s, 100); + x.led = function (left) { + nobreaknonadjacent(prevtoken, token); + nonadjacent(token, nexttoken); + var right = expression(100); + if ((left && left.id === 'NaN') || (right && right.id === 'NaN')) { + warning("Use the isNaN function to compare with NaN.", this); + } else if (f) { + f.apply(this, [left, right]); + } + if (left.id === '!') { + warning("Confusing use of '{a}'.", left, '!'); + } + if (right.id === '!') { + warning("Confusing use of '{a}'.", left, '!'); + } + this.left = left; + this.right = right; + return this; + }; + return x; + } + + + function isPoorRelation(node) { + return node && + ((node.type === '(number)' && +node.value === 0) || + (node.type === '(string)' && node.value === '') || + (node.type === 'null' && !option.eqnull) || + node.type === 'true' || + node.type === 'false' || + node.type === 'undefined'); + } + + + function assignop(s, f) { + symbol(s, 20).exps = true; + return infix(s, function (left, that) { + var l; + that.left = left; + if (predefined[left.value] === false && + scope[left.value]['(global)'] === true) { + warning("Read only.", left); + } else if (left['function']) { + warning("'{a}' is a function.", left, left.value); + } + if (left) { + if (left.id === '.' || left.id === '[') { + if (!left.left || left.left.value === 'arguments') { + warning('Bad assignment.', that); + } + that.right = expression(19); + return that; + } else if (left.identifier && !left.reserved) { + if (funct[left.value] === 'exception') { + warning("Do not assign to the exception parameter.", left); + } + that.right = expression(19); + return that; + } + if (left === syntax['function']) { + warning( +"Expected an identifier in an assignment and instead saw a function invocation.", + token); + } + } + error("Bad assignment.", that); + }, 20); + } + + + function bitwise(s, f, p) { + var x = symbol(s, p); + reserveName(x); + x.led = (typeof f === 'function') ? f : function (left) { + if (option.bitwise) { + warning("Unexpected use of '{a}'.", this, this.id); + } + this.left = left; + this.right = expression(p); + return this; + }; + return x; + } + + + function bitwiseassignop(s) { + symbol(s, 20).exps = true; + return infix(s, function (left, that) { + if (option.bitwise) { + warning("Unexpected use of '{a}'.", that, that.id); + } + nonadjacent(prevtoken, token); + nonadjacent(token, nexttoken); + if (left) { + if (left.id === '.' || left.id === '[' || + (left.identifier && !left.reserved)) { + expression(19); + return that; + } + if (left === syntax['function']) { + warning( +"Expected an identifier in an assignment, and instead saw a function invocation.", + token); + } + return that; + } + error("Bad assignment.", that); + }, 20); + } + + + function suffix(s, f) { + var x = symbol(s, 150); + x.led = function (left) { + if (option.plusplus) { + warning("Unexpected use of '{a}'.", this, this.id); + } else if ((!left.identifier || left.reserved) && + left.id !== '.' && left.id !== '[') { + warning("Bad operand.", this); + } + this.left = left; + return this; + }; + return x; + } + + + // fnparam means that this identifier is being defined as a function + // argument (see identifier()) + function optionalidentifier(fnparam) { + if (nexttoken.identifier) { + advance(); + if (token.reserved && !option.es5) { + // `undefined` as a function param is a common pattern to protect + // against the case when somebody does `undefined = true` and + // help with minification. More info: https://gist.github.com/315916 + if (!fnparam || token.value != 'undefined') { + warning("Expected an identifier and instead saw '{a}' (a reserved word).", + token, token.id); + } + } + return token.value; + } + } + + // fnparam means that this identifier is being defined as a function + // argument + function identifier(fnparam) { + var i = optionalidentifier(fnparam); + if (i) { + return i; + } + if (token.id === 'function' && nexttoken.id === '(') { + warning("Missing name in function declaration."); + } else { + error("Expected an identifier and instead saw '{a}'.", + nexttoken, nexttoken.value); + } + } + + + function reachable(s) { + var i = 0, t; + if (nexttoken.id !== ';' || noreach) { + return; + } + for (;;) { + t = peek(i); + if (t.reach) { + return; + } + if (t.id !== '(endline)') { + if (t.id === 'function') { + warning( +"Inner functions should be listed at the top of the outer function.", t); + break; + } + warning("Unreachable '{a}' after '{b}'.", t, t.value, s); + break; + } + i += 1; + } + } + + + function statement(noindent) { + var i = indent, r, s = scope, t = nexttoken; + +// We don't like the empty statement. + + if (t.id === ';') { + warning("Unnecessary semicolon.", t); + advance(';'); + return; + } + +// Is this a labelled statement? + + if (t.identifier && !t.reserved && peek().id === ':') { + advance(); + advance(':'); + scope = Object.create(s); + addlabel(t.value, 'label'); + if (!nexttoken.labelled) { + warning("Label '{a}' on {b} statement.", + nexttoken, t.value, nexttoken.value); + } + if (jx.test(t.value + ':')) { + warning("Label '{a}' looks like a javascript url.", + t, t.value); + } + nexttoken.label = t.value; + t = nexttoken; + } + +// Parse the statement. + + if (!noindent) { + indentation(); + } + r = expression(0, true); + + // Look for the final semicolon. + if (!t.block) { + if (!option.expr && (!r || !r.exps)) { + warning("Expected an assignment or function call and instead saw an expression.", + token); + } else if (option.nonew && r.id === '(' && r.left.id === 'new') { + warning("Do not use 'new' for side effects."); + } + + if (nexttoken.id !== ';') { + if (!option.asi) { + // If this is the last statement in a block that ends on + // the same line *and* option lastsemic is on, ignore the warning. + // Otherwise, complain about missing semicolon. + if (!option.lastsemic || nexttoken.id != '}' || + nexttoken.line != token.line) { + warningAt("Missing semicolon.", token.line, token.from + + token.value.length); + } + } + if (!option.asi && !(option.lastsemic && nexttoken.id == '}' && + nexttoken.line == token.line)) { + + } + } else { + adjacent(token, nexttoken); + advance(';'); + nonadjacent(token, nexttoken); + } + } + +// Restore the indentation. + + indent = i; + scope = s; + return r; + } + + + function use_strict() { + if (nexttoken.value === 'use strict') { + if (strict_mode) { + warning("Unnecessary \"use strict\"."); + } + advance(); + advance(';'); + strict_mode = true; + option.newcap = true; + option.undef = true; + return true; + } else { + return false; + } + } + + + function statements(begin) { + var a = [], f, p; + + while (!nexttoken.reach && nexttoken.id !== '(end)') { + if (nexttoken.id === ';') { + warning("Unnecessary semicolon."); + advance(';'); + } else { + a.push(statement()); + } + } + return a; + } + + + /* + * Parses a single block. A block is a sequence of statements wrapped in + * braces. + * + * ordinary - true for everything but function bodies and try blocks. + * stmt - true if block can be a single statement (e.g. in if/for/while). + */ + function block(ordinary, stmt) { + var a, + b = inblock, + old_indent = indent, + m = strict_mode, + s = scope, + t; + + inblock = ordinary; + scope = Object.create(scope); + nonadjacent(token, nexttoken); + t = nexttoken; + + if (nexttoken.id === '{') { + advance('{'); + if (nexttoken.id !== '}' || token.line !== nexttoken.line) { + indent += option.indent; + while (!ordinary && nexttoken.from > indent) { + indent += option.indent; + } + if (!ordinary && !use_strict() && !m && option.strict && + funct['(context)']['(global)']) { + warning("Missing \"use strict\" statement."); + } + a = statements(); + strict_mode = m; + indent -= option.indent; + indentation(); + } + advance('}', t); + indent = old_indent; + } else if (!ordinary) { + error("Expected '{a}' and instead saw '{b}'.", + nexttoken, '{', nexttoken.value); + } else { + if (!stmt || option.curly) + warning("Expected '{a}' and instead saw '{b}'.", + nexttoken, '{', nexttoken.value); + + noreach = true; + a = [statement()]; + noreach = false; + } + funct['(verb)'] = null; + scope = s; + inblock = b; + if (ordinary && option.noempty && (!a || a.length === 0)) { + warning("Empty block."); + } + return a; + } + + + function countMember(m) { + if (membersOnly && typeof membersOnly[m] !== 'boolean') { + warning("Unexpected /*member '{a}'.", token, m); + } + if (typeof member[m] === 'number') { + member[m] += 1; + } else { + member[m] = 1; + } + } + + + function note_implied(token) { + var name = token.value, line = token.line, a = implied[name]; + if (typeof a === 'function') { + a = false; + } + if (!a) { + a = [line]; + implied[name] = a; + } else if (a[a.length - 1] !== line) { + a.push(line); + } + } + + // Build the syntax table by declaring the syntactic elements of the language. + + type('(number)', function () { + return this; + }); + + type('(string)', function () { + return this; + }); + + syntax['(identifier)'] = { + type: '(identifier)', + lbp: 0, + identifier: true, + nud: function () { + var v = this.value, + s = scope[v], + f; + + if (typeof s === 'function') { + // Protection against accidental inheritance. + s = undefined; + } else if (typeof s === 'boolean') { + f = funct; + funct = functions[0]; + addlabel(v, 'var'); + s = funct; + funct = f; + } + + // The name is in scope and defined in the current function. + if (funct === s) { + // Change 'unused' to 'var', and reject labels. + switch (funct[v]) { + case 'unused': + funct[v] = 'var'; + break; + case 'unction': + funct[v] = 'function'; + this['function'] = true; + break; + case 'function': + this['function'] = true; + break; + case 'label': + warning("'{a}' is a statement label.", token, v); + break; + } + } else if (funct['(global)']) { + // The name is not defined in the function. If we are in the global + // scope, then we have an undefined variable. + // + // Operators typeof and delete do not raise runtime errors even if + // the base object of a reference is null so no need to display warning + // if we're inside of typeof or delete. + if (anonname != 'typeof' && anonname != 'delete' && + option.undef && typeof predefined[v] !== 'boolean') { + warning("'{a}' is not defined.", token, v); + } + note_implied(token); + } else { + // If the name is already defined in the current + // function, but not as outer, then there is a scope error. + + switch (funct[v]) { + case 'closure': + case 'function': + case 'var': + case 'unused': + warning("'{a}' used out of scope.", token, v); + break; + case 'label': + warning("'{a}' is a statement label.", token, v); + break; + case 'outer': + case 'global': + break; + default: + // If the name is defined in an outer function, make an outer entry, + // and if it was unused, make it var. + if (s === true) { + funct[v] = true; + } else if (s === null) { + warning("'{a}' is not allowed.", token, v); + note_implied(token); + } else if (typeof s !== 'object') { + // Operators typeof and delete do not raise runtime errors even + // if the base object of a reference is null so no need to + // display warning if we're inside of typeof or delete. + if (anonname != 'typeof' && anonname != 'delete' && option.undef) { + warning("'{a}' is not defined.", token, v); + } else { + funct[v] = true; + } + note_implied(token); + } else { + switch (s[v]) { + case 'function': + case 'unction': + this['function'] = true; + s[v] = 'closure'; + funct[v] = s['(global)'] ? 'global' : 'outer'; + break; + case 'var': + case 'unused': + s[v] = 'closure'; + funct[v] = s['(global)'] ? 'global' : 'outer'; + break; + case 'closure': + case 'parameter': + funct[v] = s['(global)'] ? 'global' : 'outer'; + break; + case 'label': + warning("'{a}' is a statement label.", token, v); + } + } + } + } + return this; + }, + led: function () { + error("Expected an operator and instead saw '{a}'.", + nexttoken, nexttoken.value); + } + }; + + type('(regexp)', function () { + return this; + }); + + +// ECMAScript parser + + delim('(endline)'); + delim('(begin)'); + delim('(end)').reach = true; + delim(''); + delim('(error)').reach = true; + delim('}').reach = true; + delim(')'); + delim(']'); + delim('"').reach = true; + delim("'").reach = true; + delim(';'); + delim(':').reach = true; + delim(','); + delim('#'); + delim('@'); + reserve('else'); + reserve('case').reach = true; + reserve('catch'); + reserve('default').reach = true; + reserve('finally'); + reservevar('arguments', function (x) { + if (strict_mode && funct['(global)']) { + warning("Strict violation.", x); + } + }); + reservevar('eval'); + reservevar('false'); + reservevar('Infinity'); + reservevar('NaN'); + reservevar('null'); + reservevar('this', function (x) { + if (strict_mode && !option.validthis && ((funct['(statement)'] && + funct['(name)'].charAt(0) > 'Z') || funct['(global)'])) { + warning("Possible strict violation.", x); + } + }); + reservevar('true'); + reservevar('undefined'); + assignop('=', 'assign', 20); + assignop('+=', 'assignadd', 20); + assignop('-=', 'assignsub', 20); + assignop('*=', 'assignmult', 20); + assignop('/=', 'assigndiv', 20).nud = function () { + error("A regular expression literal can be confused with '/='."); + }; + assignop('%=', 'assignmod', 20); + bitwiseassignop('&=', 'assignbitand', 20); + bitwiseassignop('|=', 'assignbitor', 20); + bitwiseassignop('^=', 'assignbitxor', 20); + bitwiseassignop('<<=', 'assignshiftleft', 20); + bitwiseassignop('>>=', 'assignshiftright', 20); + bitwiseassignop('>>>=', 'assignshiftrightunsigned', 20); + infix('?', function (left, that) { + that.left = left; + that.right = expression(10); + advance(':'); + that['else'] = expression(10); + return that; + }, 30); + + infix('||', 'or', 40); + infix('&&', 'and', 50); + bitwise('|', 'bitor', 70); + bitwise('^', 'bitxor', 80); + bitwise('&', 'bitand', 90); + relation('==', function (left, right) { + var eqnull = option.eqnull && (left.value == 'null' || right.value == 'null'); + + if (!eqnull && option.eqeqeq) + warning("Expected '{a}' and instead saw '{b}'.", this, '===', '=='); + else if (isPoorRelation(left)) + warning("Use '{a}' to compare with '{b}'.", this, '===', left.value); + else if (isPoorRelation(right)) + warning("Use '{a}' to compare with '{b}'.", this, '===', right.value); + + return this; + }); + relation('==='); + relation('!=', function (left, right) { + var eqnull = option.eqnull && + (left.value == 'null' || right.value == 'null'); + + if (!eqnull && option.eqeqeq) { + warning("Expected '{a}' and instead saw '{b}'.", + this, '!==', '!='); + } else if (isPoorRelation(left)) { + warning("Use '{a}' to compare with '{b}'.", + this, '!==', left.value); + } else if (isPoorRelation(right)) { + warning("Use '{a}' to compare with '{b}'.", + this, '!==', right.value); + } + return this; + }); + relation('!=='); + relation('<'); + relation('>'); + relation('<='); + relation('>='); + bitwise('<<', 'shiftleft', 120); + bitwise('>>', 'shiftright', 120); + bitwise('>>>', 'shiftrightunsigned', 120); + infix('in', 'in', 120); + infix('instanceof', 'instanceof', 120); + infix('+', function (left, that) { + var right = expression(130); + if (left && right && left.id === '(string)' && right.id === '(string)') { + left.value += right.value; + left.character = right.character; + if (!option.scripturl && jx.test(left.value)) { + warning("JavaScript URL.", left); + } + return left; + } + that.left = left; + that.right = right; + return that; + }, 130); + prefix('+', 'num'); + prefix('+++', function () { + warning("Confusing pluses."); + this.right = expression(150); + this.arity = 'unary'; + return this; + }); + infix('+++', function (left) { + warning("Confusing pluses."); + this.left = left; + this.right = expression(130); + return this; + }, 130); + infix('-', 'sub', 130); + prefix('-', 'neg'); + prefix('---', function () { + warning("Confusing minuses."); + this.right = expression(150); + this.arity = 'unary'; + return this; + }); + infix('---', function (left) { + warning("Confusing minuses."); + this.left = left; + this.right = expression(130); + return this; + }, 130); + infix('*', 'mult', 140); + infix('/', 'div', 140); + infix('%', 'mod', 140); + + suffix('++', 'postinc'); + prefix('++', 'preinc'); + syntax['++'].exps = true; + + suffix('--', 'postdec'); + prefix('--', 'predec'); + syntax['--'].exps = true; + prefix('delete', function () { + var p = expression(0); + if (!p || (p.id !== '.' && p.id !== '[')) { + warning("Variables should not be deleted."); + } + this.first = p; + return this; + }).exps = true; + + prefix('~', function () { + if (option.bitwise) { + warning("Unexpected '{a}'.", this, '~'); + } + expression(150); + return this; + }); + + prefix('!', function () { + this.right = expression(150); + this.arity = 'unary'; + if (bang[this.right.id] === true) { + warning("Confusing use of '{a}'.", this, '!'); + } + return this; + }); + prefix('typeof', 'typeof'); + prefix('new', function () { + var c = expression(155), i; + if (c && c.id !== 'function') { + if (c.identifier) { + c['new'] = true; + switch (c.value) { + case 'Object': + warning("Use the object literal notation {}.", token); + break; + case 'Number': + case 'String': + case 'Boolean': + case 'Math': + case 'JSON': + warning("Do not use {a} as a constructor.", token, c.value); + break; + case 'Function': + if (!option.evil) { + warning("The Function constructor is eval."); + } + break; + case 'Date': + case 'RegExp': + break; + default: + if (c.id !== 'function') { + i = c.value.substr(0, 1); + if (option.newcap && (i < 'A' || i > 'Z')) { + warning("A constructor name should start with "+ + "an uppercase letter.", token); + } + } + } + } else { + if (c.id !== '.' && c.id !== '[' && c.id !== '(') { + warning("Bad constructor.", token); + } + } + } else { + if (!option.supernew) + warning("Weird construction. Delete 'new'.", this); + } + adjacent(token, nexttoken); + if (nexttoken.id !== '(' && !option.supernew) { + warning("Missing '()' invoking a constructor."); + } + this.first = c; + return this; + }); + syntax['new'].exps = true; + + prefix('void').exps = true; + + infix('.', function (left, that) { + adjacent(prevtoken, token); + nobreak(); + var m = identifier(); + if (typeof m === 'string') { + countMember(m); + } + that.left = left; + that.right = m; + if (left && left.value === 'arguments' && (m === 'callee' || m === 'caller')) { + if (option.noarg) + warning("Avoid arguments.{a}.", left, m); + else if (strict_mode) + error('Strict violation.'); + } else if (!option.evil && left && left.value === 'document' && + (m === 'write' || m === 'writeln')) { + warning("document.write can be a form of eval.", left); + } + if (!option.evil && (m === 'eval' || m === 'execScript')) { + warning('eval is evil.'); + } + return that; + }, 160, true); + + infix('(', function (left, that) { + if (prevtoken.id !== '}' && prevtoken.id !== ')') { + nobreak(prevtoken, token); + } + nospace(); + if (option.immed && !left.immed && left.id === 'function') { + warning("Wrap an immediate function invocation in parentheses " + + "to assist the reader in understanding that the expression " + + "is the result of a function, and not the function itself."); + } + var n = 0, + p = []; + if (left) { + if (left.type === '(identifier)') { + if (left.value.match(/^[A-Z]([A-Z0-9_$]*[a-z][A-Za-z0-9_$]*)?$/)) { + if (left.value !== 'Number' && left.value !== 'String' && + left.value !== 'Boolean' && + left.value !== 'Date') { + if (left.value === 'Math') { + warning("Math is not a function.", left); + } else if (option.newcap) { + warning( +"Missing 'new' prefix when invoking a constructor.", left); + } + } + } + } + } + if (nexttoken.id !== ')') { + for (;;) { + p[p.length] = expression(10); + n += 1; + if (nexttoken.id !== ',') { + break; + } + comma(); + } + } + advance(')'); + nospace(prevtoken, token); + if (typeof left === 'object') { + if (left.value === 'parseInt' && n === 1) { + warning("Missing radix parameter.", left); + } + if (!option.evil) { + if (left.value === 'eval' || left.value === 'Function' || + left.value === 'execScript') { + warning("eval is evil.", left); + } else if (p[0] && p[0].id === '(string)' && + (left.value === 'setTimeout' || + left.value === 'setInterval')) { + warning( + "Implied eval is evil. Pass a function instead of a string.", left); + } + } + if (!left.identifier && left.id !== '.' && left.id !== '[' && + left.id !== '(' && left.id !== '&&' && left.id !== '||' && + left.id !== '?') { + warning("Bad invocation.", left); + } + } + that.left = left; + return that; + }, 155, true).exps = true; + + prefix('(', function () { + nospace(); + if (nexttoken.id === 'function') { + nexttoken.immed = true; + } + var v = expression(0); + advance(')', this); + nospace(prevtoken, token); + if (option.immed && v.id === 'function') { + if (nexttoken.id === '(') { + warning( +"Move the invocation into the parens that contain the function.", nexttoken); + } else { + warning( +"Do not wrap function literals in parens unless they are to be immediately invoked.", + this); + } + } + return v; + }); + + infix('[', function (left, that) { + nobreak(prevtoken, token); + nospace(); + var e = expression(0), s; + if (e && e.type === '(string)') { + if (!option.evil && (e.value === 'eval' || e.value === 'execScript')) { + warning("eval is evil.", that); + } + countMember(e.value); + if (!option.sub && ix.test(e.value)) { + s = syntax[e.value]; + if (!s || !s.reserved) { + warning("['{a}'] is better written in dot notation.", + e, e.value); + } + } + } + advance(']', that); + nospace(prevtoken, token); + that.left = left; + that.right = e; + return that; + }, 160, true); + + prefix('[', function () { + var b = token.line !== nexttoken.line; + this.first = []; + if (b) { + indent += option.indent; + if (nexttoken.from === indent + option.indent) { + indent += option.indent; + } + } + while (nexttoken.id !== '(end)') { + while (nexttoken.id === ',') { + warning("Extra comma."); + advance(','); + } + if (nexttoken.id === ']') { + break; + } + if (b && token.line !== nexttoken.line) { + indentation(); + } + this.first.push(expression(10)); + if (nexttoken.id === ',') { + comma(); + if (nexttoken.id === ']' && !option.es5) { + warning("Extra comma.", token); + break; + } + } else { + break; + } + } + if (b) { + indent -= option.indent; + indentation(); + } + advance(']', this); + return this; + }, 160); + + + function property_name() { + var id = optionalidentifier(true); + if (!id) { + if (nexttoken.id === '(string)') { + id = nexttoken.value; + advance(); + } else if (nexttoken.id === '(number)') { + id = nexttoken.value.toString(); + advance(); + } + } + return id; + } + + + function functionparams() { + var i, t = nexttoken, p = []; + advance('('); + nospace(); + if (nexttoken.id === ')') { + advance(')'); + nospace(prevtoken, token); + return; + } + for (;;) { + i = identifier(true); + p.push(i); + addlabel(i, 'parameter'); + if (nexttoken.id === ',') { + comma(); + } else { + advance(')', t); + nospace(prevtoken, token); + return p; + } + } + } + + + function doFunction(i, statement) { + var f, + oldOption = option, + oldScope = scope; + + option = Object.create(option); + scope = Object.create(scope); + + funct = { + '(name)' : i || '"' + anonname + '"', + '(line)' : nexttoken.line, + '(context)' : funct, + '(breakage)' : 0, + '(loopage)' : 0, + '(scope)' : scope, + '(statement)': statement + }; + f = funct; + token.funct = funct; + functions.push(funct); + if (i) { + addlabel(i, 'function'); + } + funct['(params)'] = functionparams(); + + block(false); + scope = oldScope; + option = oldOption; + funct['(last)'] = token.line; + funct = funct['(context)']; + return f; + } + + + (function (x) { + x.nud = function () { + var b, f, i, j, p, seen = {}, t; + + b = token.line !== nexttoken.line; + if (b) { + indent += option.indent; + if (nexttoken.from === indent + option.indent) { + indent += option.indent; + } + } + for (;;) { + if (nexttoken.id === '}') { + break; + } + if (b) { + indentation(); + } + if (nexttoken.value === 'get' && peek().id !== ':') { + advance('get'); + if (!option.es5) { + error("get/set are ES5 features."); + } + i = property_name(); + if (!i) { + error("Missing property name."); + } + t = nexttoken; + adjacent(token, nexttoken); + f = doFunction(); + if (!option.loopfunc && funct['(loopage)']) { + warning("Don't make functions within a loop.", t); + } + p = f['(params)']; + if (p) { + warning("Unexpected parameter '{a}' in get {b} function.", t, p[0], i); + } + adjacent(token, nexttoken); + advance(','); + indentation(); + advance('set'); + j = property_name(); + if (i !== j) { + error("Expected {a} and instead saw {b}.", token, i, j); + } + t = nexttoken; + adjacent(token, nexttoken); + f = doFunction(); + p = f['(params)']; + if (!p || p.length !== 1 || p[0] !== 'value') { + warning("Expected (value) in set {a} function.", t, i); + } + } else { + i = property_name(); + if (typeof i !== 'string') { + break; + } + advance(':'); + nonadjacent(token, nexttoken); + expression(10); + } + if (seen[i] === true) { + warning("Duplicate member '{a}'.", nexttoken, i); + } + seen[i] = true; + countMember(i); + if (nexttoken.id === ',') { + comma(); + if (nexttoken.id === ',') { + warning("Extra comma.", token); + } else if (nexttoken.id === '}' && !option.es5) { + warning("Extra comma.", token); + } + } else { + break; + } + } + if (b) { + indent -= option.indent; + indentation(); + } + advance('}', this); + return this; + }; + x.fud = function () { + error("Expected to see a statement and instead saw a block.", token); + }; + }(delim('{'))); + + var varstatement = stmt('var', function (prefix) { + // JavaScript does not have block scope. It only has function scope. So, + // declaring a variable in a block can have unexpected consequences. + var id, name, value; + + if (funct['(onevar)'] && option.onevar) { + warning("Too many var statements."); + } else if (!funct['(global)']) { + funct['(onevar)'] = true; + } + this.first = []; + for (;;) { + nonadjacent(token, nexttoken); + id = identifier(); + if (funct['(global)'] && predefined[id] === false) { + warning("Redefinition of '{a}'.", token, id); + } + addlabel(id, 'unused'); + if (prefix) { + break; + } + name = token; + this.first.push(token); + if (nexttoken.id === '=') { + nonadjacent(token, nexttoken); + advance('='); + nonadjacent(token, nexttoken); + if (nexttoken.id === 'undefined') { + warning("It is not necessary to initialize '{a}' to 'undefined'.", token, id); + } + if (peek(0).id === '=' && nexttoken.identifier) { + error("Variable {a} was not declared correctly.", + nexttoken, nexttoken.value); + } + value = expression(0); + name.first = value; + } + if (nexttoken.id !== ',') { + break; + } + comma(); + } + return this; + }); + varstatement.exps = true; + + blockstmt('function', function () { + if (inblock) { + warning("Function declarations should not be placed in blocks. " + + "Use a function expression or move the statement to the top of " + + "the outer function.", token); + + } + var i = identifier(); + adjacent(token, nexttoken); + addlabel(i, 'unction'); + doFunction(i, true); + if (nexttoken.id === '(' && nexttoken.line === token.line) { + error( +"Function declarations are not invocable. Wrap the whole function invocation in parens."); + } + return this; + }); + + prefix('function', function () { + var i = optionalidentifier(); + if (i) { + adjacent(token, nexttoken); + } else { + nonadjacent(token, nexttoken); + } + doFunction(i); + if (!option.loopfunc && funct['(loopage)']) { + warning("Don't make functions within a loop."); + } + return this; + }); + + blockstmt('if', function () { + var t = nexttoken; + advance('('); + nonadjacent(this, t); + nospace(); + expression(20); + if (nexttoken.id === '=') { + if (!option.boss) + warning("Expected a conditional expression and instead saw an assignment."); + advance('='); + expression(20); + } + advance(')', t); + nospace(prevtoken, token); + block(true, true); + if (nexttoken.id === 'else') { + nonadjacent(token, nexttoken); + advance('else'); + if (nexttoken.id === 'if' || nexttoken.id === 'switch') { + statement(true); + } else { + block(true, true); + } + } + return this; + }); + + blockstmt('try', function () { + var b, e, s; + + block(false); + if (nexttoken.id === 'catch') { + advance('catch'); + nonadjacent(token, nexttoken); + advance('('); + s = scope; + scope = Object.create(s); + e = nexttoken.value; + if (nexttoken.type !== '(identifier)') { + warning("Expected an identifier and instead saw '{a}'.", + nexttoken, e); + } else { + addlabel(e, 'exception'); + } + advance(); + advance(')'); + block(false); + b = true; + scope = s; + } + if (nexttoken.id === 'finally') { + advance('finally'); + block(false); + return; + } else if (!b) { + error("Expected '{a}' and instead saw '{b}'.", + nexttoken, 'catch', nexttoken.value); + } + return this; + }); + + blockstmt('while', function () { + var t = nexttoken; + funct['(breakage)'] += 1; + funct['(loopage)'] += 1; + advance('('); + nonadjacent(this, t); + nospace(); + expression(20); + if (nexttoken.id === '=') { + if (!option.boss) + warning("Expected a conditional expression and instead saw an assignment."); + advance('='); + expression(20); + } + advance(')', t); + nospace(prevtoken, token); + block(true, true); + funct['(breakage)'] -= 1; + funct['(loopage)'] -= 1; + return this; + }).labelled = true; + + reserve('with'); + + blockstmt('switch', function () { + var t = nexttoken, + g = false; + funct['(breakage)'] += 1; + advance('('); + nonadjacent(this, t); + nospace(); + this.condition = expression(20); + advance(')', t); + nospace(prevtoken, token); + nonadjacent(token, nexttoken); + t = nexttoken; + advance('{'); + nonadjacent(token, nexttoken); + indent += option.indent; + this.cases = []; + for (;;) { + switch (nexttoken.id) { + case 'case': + switch (funct['(verb)']) { + case 'break': + case 'case': + case 'continue': + case 'return': + case 'switch': + case 'throw': + break; + default: + // You can tell JSHint that you don't use break intentionally by + // adding a comment /* falls through */ on a line just before + // the next `case`. + if (!ft.test(lines[nexttoken.line - 2])) { + warning( + "Expected a 'break' statement before 'case'.", + token); + } + } + indentation(-option.indent); + advance('case'); + this.cases.push(expression(20)); + g = true; + advance(':'); + funct['(verb)'] = 'case'; + break; + case 'default': + switch (funct['(verb)']) { + case 'break': + case 'continue': + case 'return': + case 'throw': + break; + default: + if (!ft.test(lines[nexttoken.line - 2])) { + warning( + "Expected a 'break' statement before 'default'.", + token); + } + } + indentation(-option.indent); + advance('default'); + g = true; + advance(':'); + break; + case '}': + indent -= option.indent; + indentation(); + advance('}', t); + if (this.cases.length === 1 || this.condition.id === 'true' || + this.condition.id === 'false') { + if (!option.onecase) + warning("This 'switch' should be an 'if'.", this); + } + funct['(breakage)'] -= 1; + funct['(verb)'] = undefined; + return; + case '(end)': + error("Missing '{a}'.", nexttoken, '}'); + return; + default: + if (g) { + switch (token.id) { + case ',': + error("Each value should have its own case label."); + return; + case ':': + statements(); + break; + default: + error("Missing ':' on a case clause.", token); + } + } else { + error("Expected '{a}' and instead saw '{b}'.", + nexttoken, 'case', nexttoken.value); + } + } + } + }).labelled = true; + + stmt('debugger', function () { + if (!option.debug) { + warning("All 'debugger' statements should be removed."); + } + return this; + }).exps = true; + + (function () { + var x = stmt('do', function () { + funct['(breakage)'] += 1; + funct['(loopage)'] += 1; + this.first = block(true); + advance('while'); + var t = nexttoken; + nonadjacent(token, t); + advance('('); + nospace(); + expression(20); + if (nexttoken.id === '=') { + if (!option.boss) + warning("Expected a conditional expression and instead saw an assignment."); + advance('='); + expression(20); + } + advance(')', t); + nospace(prevtoken, token); + funct['(breakage)'] -= 1; + funct['(loopage)'] -= 1; + return this; + }); + x.labelled = true; + x.exps = true; + }()); + + blockstmt('for', function () { + var s, t = nexttoken; + funct['(breakage)'] += 1; + funct['(loopage)'] += 1; + advance('('); + nonadjacent(this, t); + nospace(); + if (peek(nexttoken.id === 'var' ? 1 : 0).id === 'in') { + if (nexttoken.id === 'var') { + advance('var'); + varstatement.fud.call(varstatement, true); + } else { + switch (funct[nexttoken.value]) { + case 'unused': + funct[nexttoken.value] = 'var'; + break; + case 'var': + break; + default: + warning("Bad for in variable '{a}'.", + nexttoken, nexttoken.value); + } + advance(); + } + advance('in'); + expression(20); + advance(')', t); + s = block(true, true); + if (option.forin && (s.length > 1 || typeof s[0] !== 'object' || + s[0].value !== 'if')) { + warning("The body of a for in should be wrapped in an if statement to filter " + + "unwanted properties from the prototype.", this); + } + funct['(breakage)'] -= 1; + funct['(loopage)'] -= 1; + return this; + } else { + if (nexttoken.id !== ';') { + if (nexttoken.id === 'var') { + advance('var'); + varstatement.fud.call(varstatement); + } else { + for (;;) { + expression(0, 'for'); + if (nexttoken.id !== ',') { + break; + } + comma(); + } + } + } + nolinebreak(token); + advance(';'); + if (nexttoken.id !== ';') { + expression(20); + if (nexttoken.id === '=') { + if (!option.boss) + warning("Expected a conditional expression and instead saw an assignment."); + advance('='); + expression(20); + } + } + nolinebreak(token); + advance(';'); + if (nexttoken.id === ';') { + error("Expected '{a}' and instead saw '{b}'.", + nexttoken, ')', ';'); + } + if (nexttoken.id !== ')') { + for (;;) { + expression(0, 'for'); + if (nexttoken.id !== ',') { + break; + } + comma(); + } + } + advance(')', t); + nospace(prevtoken, token); + block(true, true); + funct['(breakage)'] -= 1; + funct['(loopage)'] -= 1; + return this; + } + }).labelled = true; + + + stmt('break', function () { + var v = nexttoken.value; + + if (funct['(breakage)'] === 0) + warning("Unexpected '{a}'.", nexttoken, this.value); + + if (!option.asi) + nolinebreak(this); + + if (nexttoken.id !== ';') { + if (token.line === nexttoken.line) { + if (funct[v] !== 'label') { + warning("'{a}' is not a statement label.", nexttoken, v); + } else if (scope[v] !== funct) { + warning("'{a}' is out of scope.", nexttoken, v); + } + this.first = nexttoken; + advance(); + } + } + reachable('break'); + return this; + }).exps = true; + + + stmt('continue', function () { + var v = nexttoken.value; + + if (funct['(breakage)'] === 0) + warning("Unexpected '{a}'.", nexttoken, this.value); + + if (!option.asi) + nolinebreak(this); + + if (nexttoken.id !== ';') { + if (token.line === nexttoken.line) { + if (funct[v] !== 'label') { + warning("'{a}' is not a statement label.", nexttoken, v); + } else if (scope[v] !== funct) { + warning("'{a}' is out of scope.", nexttoken, v); + } + this.first = nexttoken; + advance(); + } + } else if (!funct['(loopage)']) { + warning("Unexpected '{a}'.", nexttoken, this.value); + } + reachable('continue'); + return this; + }).exps = true; + + + stmt('return', function () { + if (!option.asi) + nolinebreak(this); + + if (nexttoken.id === '(regexp)') + warning("Wrap the /regexp/ literal in parens to disambiguate the slash operator."); + + if (this.line === nexttoken.line || !option.asi) { + if (nexttoken.id !== ';' && !nexttoken.reach) { + nonadjacent(token, nexttoken); + this.first = expression(20); + } + } + + reachable('return'); + return this; + }).exps = true; + + + stmt('throw', function () { + nolinebreak(this); + nonadjacent(token, nexttoken); + this.first = expression(20); + reachable('throw'); + return this; + }).exps = true; + +// Superfluous reserved words + + reserve('class'); + reserve('const'); + reserve('enum'); + reserve('export'); + reserve('extends'); + reserve('import'); + reserve('super'); + + reserve('let'); + reserve('yield'); + reserve('implements'); + reserve('interface'); + reserve('package'); + reserve('private'); + reserve('protected'); + reserve('public'); + reserve('static'); + + +// Parse JSON + + function jsonValue() { + + function jsonObject() { + var o = {}, t = nexttoken; + advance('{'); + if (nexttoken.id !== '}') { + for (;;) { + if (nexttoken.id === '(end)') { + error("Missing '}' to match '{' from line {a}.", + nexttoken, t.line); + } else if (nexttoken.id === '}') { + warning("Unexpected comma.", token); + break; + } else if (nexttoken.id === ',') { + error("Unexpected comma.", nexttoken); + } else if (nexttoken.id !== '(string)') { + warning("Expected a string and instead saw {a}.", + nexttoken, nexttoken.value); + } + if (o[nexttoken.value] === true) { + warning("Duplicate key '{a}'.", + nexttoken, nexttoken.value); + } else if ((nexttoken.value === '__proto__' && + !option.proto) || (nexttoken.value === '__iterator__' && + !option.iterator)) { + warning("The '{a}' key may produce unexpected results.", + nexttoken, nexttoken.value); + } else { + o[nexttoken.value] = true; + } + advance(); + advance(':'); + jsonValue(); + if (nexttoken.id !== ',') { + break; + } + advance(','); + } + } + advance('}'); + } + + function jsonArray() { + var t = nexttoken; + advance('['); + if (nexttoken.id !== ']') { + for (;;) { + if (nexttoken.id === '(end)') { + error("Missing ']' to match '[' from line {a}.", + nexttoken, t.line); + } else if (nexttoken.id === ']') { + warning("Unexpected comma.", token); + break; + } else if (nexttoken.id === ',') { + error("Unexpected comma.", nexttoken); + } + jsonValue(); + if (nexttoken.id !== ',') { + break; + } + advance(','); + } + } + advance(']'); + } + + switch (nexttoken.id) { + case '{': + jsonObject(); + break; + case '[': + jsonArray(); + break; + case 'true': + case 'false': + case 'null': + case '(number)': + case '(string)': + advance(); + break; + case '-': + advance('-'); + if (token.character !== nexttoken.from) { + warning("Unexpected space after '-'.", token); + } + adjacent(token, nexttoken); + advance('(number)'); + break; + default: + error("Expected a JSON value.", nexttoken); + } + } + + +// The actual JSHINT function itself. + + var itself = function (s, o, g) { + var a, i, k; + JSHINT.errors = []; + predefined = Object.create(standard); + combine(predefined, g || {}); + if (o) { + a = o.predef; + if (a) { + if (Array.isArray(a)) { + for (i = 0; i < a.length; i += 1) { + predefined[a[i]] = true; + } + } else if (typeof a === 'object') { + k = Object.keys(a); + for (i = 0; i < k.length; i += 1) { + predefined[k[i]] = !!a[k[i]]; + } + } + } + option = o; + } else { + option = {}; + } + option.indent = option.indent || 4; + option.maxerr = option.maxerr || 50; + + tab = ''; + for (i = 0; i < option.indent; i += 1) { + tab += ' '; + } + indent = 1; + global = Object.create(predefined); + scope = global; + funct = { + '(global)': true, + '(name)': '(global)', + '(scope)': scope, + '(breakage)': 0, + '(loopage)': 0 + }; + functions = [funct]; + urls = []; + src = false; + stack = null; + member = {}; + membersOnly = null; + implied = {}; + inblock = false; + lookahead = []; + jsonmode = false; + warnings = 0; + lex.init(s); + prereg = true; + strict_mode = false; + + prevtoken = token = nexttoken = syntax['(begin)']; + assume(); + + try { + advance(); + switch (nexttoken.id) { + case '{': + case '[': + option.laxbreak = true; + jsonmode = true; + jsonValue(); + break; + default: + if (nexttoken.value === 'use strict') { + if (!option.globalstrict) + warning("Use the function form of \"use strict\"."); + use_strict(); + } + + statements('lib'); + } + advance('(end)'); + } catch (e) { + if (e) { + JSHINT.errors.push({ + reason : e.message, + line : e.line || nexttoken.line, + character : e.character || nexttoken.from + }, null); + } + } + return JSHINT.errors.length === 0; + }; + + // Data summary. + itself.data = function () { + + var data = { functions: [], options: option }, fu, globals, implieds = [], f, i, j, + members = [], n, unused = [], v; + if (itself.errors.length) { + data.errors = itself.errors; + } + + if (jsonmode) { + data.json = true; + } + + for (n in implied) { + if (is_own(implied, n)) { + implieds.push({ + name: n, + line: implied[n] + }); + } + } + if (implieds.length > 0) { + data.implieds = implieds; + } + + if (urls.length > 0) { + data.urls = urls; + } + + globals = Object.keys(scope); + if (globals.length > 0) { + data.globals = globals; + } + + for (i = 1; i < functions.length; i += 1) { + f = functions[i]; + fu = {}; + for (j = 0; j < functionicity.length; j += 1) { + fu[functionicity[j]] = []; + } + for (n in f) { + if (is_own(f, n) && n.charAt(0) !== '(') { + v = f[n]; + if (v === 'unction') { + v = 'unused'; + } + if (Array.isArray(fu[v])) { + fu[v].push(n); + if (v === 'unused') { + unused.push({ + name: n, + line: f['(line)'], + 'function': f['(name)'] + }); + } + } + } + } + for (j = 0; j < functionicity.length; j += 1) { + if (fu[functionicity[j]].length === 0) { + delete fu[functionicity[j]]; + } + } + fu.name = f['(name)']; + fu.param = f['(params)']; + fu.line = f['(line)']; + fu.last = f['(last)']; + data.functions.push(fu); + } + + if (unused.length > 0) { + data.unused = unused; + } + + members = []; + for (n in member) { + if (typeof member[n] === 'number') { + data.member = member; + break; + } + } + + return data; + }; + + itself.report = function (option) { + var data = itself.data(); + + var a = [], c, e, err, f, i, k, l, m = '', n, o = [], s; + + function detail(h, array) { + var b, i, singularity; + if (array) { + o.push('
' + h + ' '); + array = array.sort(); + for (i = 0; i < array.length; i += 1) { + if (array[i] !== singularity) { + singularity = array[i]; + o.push((b ? ', ' : '') + singularity); + b = true; + } + } + o.push('
'); + } + } + + + if (data.errors || data.implieds || data.unused) { + err = true; + o.push('
Error:'); + if (data.errors) { + for (i = 0; i < data.errors.length; i += 1) { + c = data.errors[i]; + if (c) { + e = c.evidence || ''; + o.push('

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

' + + (e && (e.length > 80 ? e.slice(0, 77) + '...' : + e).entityify()) + '

'); + } + } + } + + if (data.implieds) { + s = []; + for (i = 0; i < data.implieds.length; i += 1) { + s[i] = '' + data.implieds[i].name + ' ' + + data.implieds[i].line + ''; + } + o.push('

Implied global: ' + s.join(', ') + '

'); + } + + if (data.unused) { + s = []; + for (i = 0; i < data.unused.length; i += 1) { + s[i] = '' + data.unused[i].name + ' ' + + data.unused[i].line + ' ' + + data.unused[i]['function'] + ''; + } + o.push('

Unused variable: ' + s.join(', ') + '

'); + } + if (data.json) { + o.push('

JSON: bad.

'); + } + o.push('
'); + } + + if (!option) { + + o.push('
'); + + if (data.urls) { + detail("URLs
", data.urls, '
'); + } + + if (data.json && !err) { + o.push('

JSON: good.

'); + } else if (data.globals) { + o.push('
Global ' + + data.globals.sort().join(', ') + '
'); + } else { + o.push('
No new global variables introduced.
'); + } + + for (i = 0; i < data.functions.length; i += 1) { + f = data.functions[i]; + + o.push('
' + f.line + '-' + + f.last + ' ' + (f.name || '') + '(' + + (f.param ? f.param.join(', ') : '') + ')
'); + detail('Unused', f.unused); + detail('Closure', f.closure); + detail('Variable', f['var']); + detail('Exception', f.exception); + detail('Outer', f.outer); + detail('Global', f.global); + detail('Label', f.label); + } + + if (data.member) { + a = Object.keys(data.member); + if (a.length) { + a = a.sort(); + m = '
/*members ';
+                    l = 10;
+                    for (i = 0; i < a.length; i += 1) {
+                        k = a[i];
+                        n = k.name();
+                        if (l + n.length > 72) {
+                            o.push(m + '
'); + m = ' '; + l = 1; + } + l += n.length + 2; + if (data.member[k] === 1) { + n = '' + n + ''; + } + if (i < a.length - 1) { + n += ', '; + } + m += n; + } + o.push(m + '
*/
'); + } + o.push('
'); + } + } + return o.join(''); + }; + + itself.jshint = itself; + itself.edition = '2011-04-16'; + + return itself; +}()); + +// Make JSHINT a Node module, if possible. +if (typeof exports == 'object' && exports) + exports.JSHINT = JSHINT; diff --git a/lib/node.js b/lib/node.js deleted file mode 100644 index 879e5d4..0000000 --- a/lib/node.js +++ /dev/null @@ -1,70 +0,0 @@ -// v8.js -// 2009-09-11: Based on Douglas Crockford's Rhino edition -// -// I've made a few changes, specifically the ability to parse -// one file, while displaying the name of another. -// - -/*global JSLINT */ -/*jslint rhino: true, strict: false, onevar: false, white: false, browser: true, - laxbreak: true, undef: true, nomen: false, eqeqeq: true, plusplus: false, bitwise: true,regexp: true, newcap: true, immed: true */ - -(function ( argv ) { - var e, i, input, fileToParse, fileToDisplay, defaults, - sys = require( "sys" ), - fs = require( "fs" ); - - argv.shift(); // drop "node" - argv.shift(); // drop this script's name - - if ( !argv[ 0 ] ) { - sys.puts("Usage: jslint.js file.js [realfilename.js]"); - process.exit( 1 ); - } - - fileToParse = argv[ 0 ]; - fileToDisplay = argv[ 1 ] ? argv[ 1 ] : argv[ 0 ]; - - input = fs.readFile( fileToParse, function ( err, data ) { - if ( err || !data ) { - sys.puts("jslint: Couldn't open file '" + fileToParse + "'."); - process.exit(1); - } - - defaults = { - bitwise: true, // Allow bitwise operators - browser: true, // Assume a browser ( http://www.JSLint.com/lint.html#browser ) - css: true, // Tolerate CSS workarounds ( http://www.JSLint.com/lint.html#css ) - eqeqeq: true, // Require `===` && `!==` - immed: true, // Immediate invocations must be wrapped in parens. - laxbreak: true, // Tolerate "sloppy" line breaks - newcap: true, // Require initial caps for constructors ( http://www.JSLint.com/lint.html#new ) - nomen: false, // Allow dangling `_` in identifiers - onevar: false, // Allow multiple `var` statements. - plusplus: false, // Allow `++` and `--` - regexp: true, // Disallow `.` and `[^...]` in regex - strict: false, // Don't require `use strict;` - undef: true, // Disallow undeclared variables ( http://www.JSLint.com/lint.html#undefined ) - white: false // Don't apply strict whitespace rules - }; - - if ( !JSLINT( data.toString(), defaults ) ) { - for ( i = 0, numErrors = JSLINT.errors.length; i < numErrors; i += 1 ) { - e = JSLINT.errors[ i ]; - if ( e ) { - sys.puts( - '[' + fileToDisplay + '] Lint at line ' + e.line + ' character ' + - e.character + ': ' + e.reason - ); - sys.puts( - ( e.evidence || '' ).replace( /^\s+|\s+$/, "" ) - ); - } - } - process.exit( 2 ); - } else { - sys.puts("jslint: No problems found in " + fileToDisplay); - process.exit( 0 ); - } - } ); -}( process.ARGV ) ); diff --git a/lib/node_jslint.js b/lib/node_jslint.js deleted file mode 100644 index 070b7f2..0000000 --- a/lib/node_jslint.js +++ /dev/null @@ -1,6735 +0,0 @@ -// jslint.js -// 2011-05-01 - -// 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 global 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 JSON text, or a CSS text. - -// The second parameter is an optional object of options that control the -// operation of JSLINT. Most of the options are booleans: They are all -// optional and have a default value of false. One of the options, predef, -// can be an array of names, which will be used to declare global variables, -// or an object whose keys are used as global names, with a boolean value -// that determines if they are assignable. - -// If it checks out, 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 properties: - -// { -// line : The line (relative to 0) at which the lint was found -// character : The character (relative to 0) at which the lint was found -// reason : The problem -// evidence : The text line in which the problem occurred -// raw : The raw message before the details were inserted -// a : The first detail -// b : The second detail -// c : The third detail -// d : The fourth detail -// } - -// If a stopping error was found, a null will be the last element of the -// JSLINT.errors array. A stopping error means that JSLint was not confident -// enough to continue. It does not necessarily mean that the error was -// especially heinous. - -// You can request a Function Report, which shows all of the functions -// and the parameters and vars that they use. This can be used to find -// implied global variables and other problems. The report is in HTML and -// can be inserted in an HTML . - -// var myReport = JSLINT.report(errors_only); - -// If errors_only is true, then the report will be limited to only errors. - -// You can request a data structure that contains JSLint's results. - -// var myData = JSLINT.data(); - -// It returns a structure with this form: - -// { -// errors: [ -// { -// line: NUMBER, -// character: NUMBER, -// reason: STRING, -// evidence: STRING -// } -// ], -// functions: [ -// name: STRING, -// line: NUMBER, -// last: NUMBER, -// param: [ -// TOKEN -// ], -// closure: [ -// STRING -// ], -// var: [ -// STRING -// ], -// exception: [ -// STRING -// ], -// outer: [ -// STRING -// ], -// unused: [ -// STRING -// ], -// global: [ -// STRING -// ], -// label: [ -// STRING -// ] -// ], -// globals: [ -// STRING -// ], -// member: { -// STRING: NUMBER -// }, -// unuseds: [ -// { -// name: STRING, -// line: NUMBER -// } -// ], -// implieds: [ -// { -// name: STRING, -// line: NUMBER -// } -// ], -// urls: [ -// STRING -// ], -// json: BOOLEAN -// } - -// Empty arrays will not be included. - -// You can obtain the parse tree that JSLint constructed while parsing. The -// latest tree is kept in JSLINT.tree. A nice stringication can be produced -// with - -// JSON.stringify(JSLINT.tree, [ -// 'value', 'arity', 'name', 'first', -// 'second', 'third', 'block', 'else' -// ], 4)); - -// JSLint provides three directives. They look like slashstar comments, and -// allow for setting options, declaring global variables, and establishing a -// set of allowed property names. - -// These directives respect function scope. - -// The jslint directive is a special comment that can set one or more options. -// The current option set is - -// adsafe true, if ADsafe rules should be enforced -// bitwise true, if bitwise operators should not be allowed -// browser true, if the standard browser globals should be predefined -// cap true, if upper case HTML should be allowed -// 'continue' true, if the continuation statement should be tolerated -// css true, if CSS workarounds should be tolerated -// debug true, if debugger statements should be allowed -// devel true, if logging should be allowed (console, alert, etc.) -// es5 true, if ES5 syntax should be allowed -// evil true, if eval should be allowed -// forin true, if for in statements need not filter -// fragment true, if HTML fragments should be allowed -// indent the indentation factor -// maxerr the maximum number of errors to allow -// maxlen the maximum length of a source line -// newcap true, if constructor names must be capitalized -// node true, if Node.js globals should be predefined -// nomen true, if names should be checked -// on true, if HTML event handlers should be allowed -// onevar true, if only one var statement per function should be allowed -// passfail true, if the scan should stop on first error -// plusplus true, if increment/decrement should not be allowed -// regexp true, if the . should not be allowed in regexp literals -// rhino true, if the Rhino environment globals should be predefined -// undef true, if variables should be declared before used -// unparam true, if unused parameters should be tolerated -// safe true, if use of some browser features should be restricted -// windows true, if MS Windows-specific globals should be predefined -// strict true, require the 'use strict'; pragma -// sub true, if all forms of subscript notation are tolerated -// white true, if strict whitespace rules apply -// widget true if the Yahoo Widgets globals should be predefined - -// For example: - -/*jslint - evil: true, nomen: false, onevar: false, regexp: false, strict: true -*/ - -// The properties directive declares an exclusive list of property names. -// Any properties named in the program that are not in the list will -// produce a warning. - -// For example: - -/*properties "\b", "\t", "\n", "\f", "\r", "!=", "!==", "\"", "%", - "&", "'", "(begin)", "(breakage)", "(context)", "(error)", - "(global)", "(identifier)", "(line)", "(loopage)", "(name)", "(onevar)", - "(params)", "(scope)", "(statement)", "(token)", "(verb)", ")", "*", - "+", "-", "/", ";", "<", "<<", "<=", "==", "===", ">", - ">=", ">>", ">>>", ADSAFE, ActiveXObject, Array, - Boolean, Buffer, COM, CScript, Canvas, CustomAnimation, Date, Debug, E, - Enumerator, Error, EvalError, FadeAnimation, Flash, FormField, Frame, - Function, HotKey, Image, JSON, LN10, LN2, LOG10E, LOG2E, MAX_VALUE, - MIN_VALUE, Math, MenuItem, MoveAnimation, NEGATIVE_INFINITY, Number, - Object, Option, PI, POSITIVE_INFINITY, Point, RangeError, Rectangle, - ReferenceError, RegExp, ResizeAnimation, RotateAnimation, SQRT1_2, - SQRT2, ScrollBar, Storage, String, Style, SyntaxError, System, Text, - TextArea, Timer, TypeError, URIError, URL, VBArray, WScript, Web, - Window, XMLDOM, XMLHttpRequest, "\\", "^", __dirname, __filename, a, - a_function, a_label, a_not_allowed, a_not_defined, a_scope, abbr, - acronym, activeborder, activecaption, address, adsafe, adsafe_a, - adsafe_autocomplete, adsafe_bad_id, adsafe_div, adsafe_fragment, - adsafe_go, adsafe_html, adsafe_id, adsafe_id_go, adsafe_lib, - adsafe_lib_second, adsafe_missing_id, adsafe_name_a, adsafe_placement, - adsafe_prefix_a, adsafe_script, adsafe_source, adsafe_subscript_a, - adsafe_tag, alert, aliceblue, all, already_defined, and, animator, - antiquewhite, appleScript, applet, apply, approved, appworkspace, aqua, - aquamarine, area, arguments, arity, article, aside, assign, - assign_exception, assignment_function_expression, at, attribute_case_a, - audio, autocomplete, avoid_a, azure, b, background, - "background-attachment", "background-color", "background-image", - "background-position", "background-repeat", bad_assignment, bad_color_a, - bad_constructor, bad_entity, bad_html, bad_id_a, bad_in_a, - bad_invocation, bad_name_a, bad_new, bad_number, bad_operand, bad_type, - bad_url, bad_wrap, base, bdo, beep, beige, big, bisque, bitwise, black, - blanchedalmond, block, blockquote, blue, blueviolet, body, border, - "border-bottom", "border-bottom-color", "border-bottom-style", - "border-bottom-width", "border-collapse", "border-color", "border-left", - "border-left-color", "border-left-style", "border-left-width", - "border-right", "border-right-color", "border-right-style", - "border-right-width", "border-spacing", "border-style", "border-top", - "border-top-color", "border-top-style", "border-top-width", - "border-width", bottom, br, braille, brown, browser, burlywood, button, - buttonface, buttonhighlight, buttonshadow, buttontext, bytesToUIString, - c, cadetblue, call, callee, caller, canvas, cap, caption, - "caption-side", captiontext, center, charAt, charCodeAt, character, - chartreuse, chocolate, chooseColor, chooseFile, chooseFolder, cite, - clear, clearInterval, clearTimeout, clip, closeWidget, closure, cm, - code, col, colgroup, color, combine_var, command, comment, comments, - concat, conditional_assignment, confirm, confusing_a, confusing_regexp, - console, constructor, constructor_name_a, content, continue, control_a, - convertPathToHFS, convertPathToPlatform, coral, cornflowerblue, - cornsilk, "counter-increment", "counter-reset", create, crimson, css, - cursor, cyan, d, dangerous_comment, dangling_a, darkblue, darkcyan, - darkgoldenrod, darkgray, darkgreen, darkkhaki, darkmagenta, - darkolivegreen, darkorange, darkorchid, darkred, darksalmon, - darkseagreen, darkslateblue, darkslategray, darkturquoise, darkviolet, - data, datalist, dd, debug, decodeURI, decodeURIComponent, deeppink, - deepskyblue, defineClass, del, deleted, deserialize, details, devel, - dfn, dialog, dimgray, dir, direction, display, disrupt, div, dl, - document, dodgerblue, dt, duplicate_a, edge, edition, else, em, embed, - embossed, empty, "empty-cells", empty_block, empty_case, empty_class, - encodeURI, encodeURIComponent, entityify, errors, es5, escape, eval, - event, evidence, evil, ex, exception, exec, expected_a, - expected_a_at_b_c, expected_a_b, expected_a_b_from_c_d, expected_at_a, - expected_attribute_a, expected_attribute_value_a, expected_class_a, - expected_fraction_a, expected_id_a, expected_identifier_a, - expected_identifier_a_reserved, expected_lang_a, expected_linear_a, - expected_media_a, expected_name_a, expected_nonstandard_style_attribute, - expected_number_a, expected_operator_a, expected_percent_a, - expected_positive_a, expected_pseudo_a, expected_selector_a, - expected_small_a, expected_space_a_b, expected_string_a, - expected_style_attribute, expected_style_pattern, expected_tagname_a, - fieldset, figure, filesystem, filter, firebrick, first, float, floor, - floralwhite, focusWidget, font, "font-family", "font-size", - "font-size-adjust", "font-stretch", "font-style", "font-variant", - "font-weight", footer, for_if, forestgreen, forin, form, fragment, - frame, frames, frameset, from, fromCharCode, fuchsia, fud, funct, - function, function_block, function_eval, function_loop, - function_statement, function_strict, functions, g, gainsboro, gc, - get_set, ghostwhite, global, globals, gold, goldenrod, gray, graytext, - green, greenyellow, h1, h2, h3, h4, h5, h6, handheld, hasOwnProperty, - head, header, height, help, hgroup, highlight, highlighttext, history, - honeydew, hotpink, hr, "hta:application", html, html_confusion_a, - html_handlers, i, iTunes, id, identifier, identifier_function, iframe, - img, immed, implied_evil, implieds, in, inactiveborder, inactivecaption, - inactivecaptiontext, include, indent, indexOf, indianred, indigo, - infix_in, infobackground, infotext, init, input, ins, insecure_a, - isAlpha, isApplicationRunning, isArray, isDigit, isFinite, isNaN, ivory, - join, jslint, json, kbd, keygen, keys, khaki, konfabulatorVersion, - label, label_a_b, labeled, lang, lastIndexOf, lavender, lavenderblush, - lawngreen, lbp, leading_decimal_a, led, left, legend, lemonchiffon, - length, "letter-spacing", li, lib, lightblue, lightcoral, lightcyan, - lightgoldenrodyellow, lightgreen, lightpink, lightsalmon, lightseagreen, - lightskyblue, lightslategray, lightsteelblue, lightyellow, lime, - limegreen, line, "line-height", linen, link, "list-style", - "list-style-image", "list-style-position", "list-style-type", load, - loadClass, localStorage, location, log, m, magenta, map, margin, - "margin-bottom", "margin-left", "margin-right", "margin-top", mark, - "marker-offset", maroon, match, "max-height", "max-width", maxerr, - maxlen, md5, mediumaquamarine, mediumblue, mediumorchid, mediumpurple, - mediumseagreen, mediumslateblue, mediumspringgreen, mediumturquoise, - mediumvioletred, member, menu, menutext, message, meta, meter, - midnightblue, "min-height", "min-width", mintcream, missing_a, - missing_a_after_b, missing_option, missing_property, missing_space_a_b, - missing_url, missing_use_strict, mistyrose, mixed, mm, moccasin, mode, - module, move_invocation, move_var, name, name_function, nav, - navajowhite, navigator, navy, nested_comment, newcap, next, node, - noframes, nomen, noscript, not, not_a_constructor, not_a_defined, - not_a_function, not_a_label, not_a_scope, not_greater, nud, object, ol, - oldlace, olive, olivedrab, on, onevar, opacity, open, openURL, opera, - optgroup, option, orange, orangered, orchid, outer, outline, - "outline-color", "outline-style", "outline-width", output, overflow, - "overflow-x", "overflow-y", p, padding, "padding-bottom", - "padding-left", "padding-right", "padding-top", "page-break-after", - "page-break-before", palegoldenrod, palegreen, paleturquoise, - palevioletred, papayawhip, param, parameter_a_get_b, parameter_set_a, - paren, parent, parseFloat, parseInt, passfail, pc, peachpuff, peru, - pink, play, plum, plusplus, pop, popupMenu, position, postscript, - powderblue, pre, predef, preferenceGroups, preferences, prev, print, - process, progress, projection, prompt, prototype, pt, purple, push, px, - q, querystring, quit, quote, quotes, radix, random, range, raw, - readFile, readUrl, read_only, reason, red, redefinition_a, regexp, - reloadWidget, replace, report, require, reserved, reserved_a, - resolvePath, resumeUpdates, rhino, right, rosybrown, royalblue, rp, rt, - ruby, runCommand, runCommandInBg, saddlebrown, safe, salmon, samp, - sandybrown, saveAs, savePreferences, scanned_a_b, screen, script, - scrollbar, seagreen, seal, search, seashell, second, section, select, - serialize, sessionStorage, setInterval, setTimeout, shift, - showWidgetPreferences, sienna, silver, skyblue, slash_equal, slateblue, - slategray, sleep, slice, small, snow, sort, source, span, spawn, speak, - speech, split, springgreen, src, stack, statement_block, steelblue, - stopping, strange_loop, strict, strong, style, styleproperty, sub, - subscript, substr, sup, supplant, suppressUpdates, sync, system, table, - "table-layout", tag_a_in_b, tan, tbody, td, teal, tellWidget, test, - "text-align", "text-decoration", "text-indent", "text-shadow", - "text-transform", textarea, tfoot, th, thead, third, thistle, - threeddarkshadow, threedface, threedhighlight, threedlightshadow, - threedshadow, thru, time, title, toLowerCase, toString, toUpperCase, - toint32, token, tomato, too_long, too_many, top, tr, trailing_decimal_a, - tree, tt, tty, turquoise, tv, type, typeof, u, ul, unclosed, - unclosed_comment, unclosed_regexp, undef, unescape, unescaped_a, - unexpected_a, unexpected_char_a_b, unexpected_comment, - unexpected_property_a, unexpected_space_a_b, "unicode-bidi", - unnecessary_initialize, unnecessary_use, unreachable_a_b, - unrecognized_style_attribute_a, unrecognized_tag_a, unparam, unsafe, unused, - unwatch, updateNow, url, urls, use_array, use_braces, use_object, use_param, - used_before_a, util, value, valueOf, var, var_a_not, version, - "vertical-align", video, violet, visibility, was, watch, - weird_assignment, weird_condition, weird_new, weird_program, - weird_relation, weird_ternary, wheat, white, "white-space", whitesmoke, - widget, width, window, windowframe, windows, windowtext, "word-spacing", - "word-wrap", wrap, wrap_immediate, wrap_regexp, write_is_wrong, - yahooCheckLogin, yahooLogin, yahooLogout, yellow, yellowgreen, - "z-index", "|", "~" - */ - -// The global directive is used to declare global variables that can -// be accessed by the program. If a declaration is true, then the variable -// is writeable. Otherwise, it is read-only. - -// We build the application inside a function so that we produce only a single -// global variable. That function will be invoked immediately, and its return -// value is the JSLINT function itself. That function is also an object that -// can contain data and other functions. - -var JSLINT = (function () { - 'use strict'; - - var adsafe_id, // The widget's ADsafe id. - adsafe_infix = { - '-': true, - '*': true, - '/': true, - '%': true, - '&': true, - '|': true, - '^': true, - '<<': true, - '>>': true, - '>>>': true - }, - adsafe_prefix = { - '-': true, - '+': true, - '~': true, - 'typeof': true - }, - adsafe_may, // The widget may load approved scripts. - adsafe_top, // At the top of the widget script. - adsafe_went, // ADSAFE.go has been called. - anonname, // The guessed name for anonymous functions. - approved, // ADsafe approved urls. - -// These are operators that should not be used with the ! operator. - - bang = { - '<' : true, - '<=' : true, - '==' : true, - '===': true, - '!==': true, - '!=' : true, - '>' : true, - '>=' : true, - '+' : true, - '-' : true, - '*' : true, - '/' : true, - '%' : true - }, - -// These are property names that should not be permitted in the safe subset. - - banned = { - 'arguments' : true, - callee : true, - caller : true, - constructor : true, - 'eval' : true, - prototype : true, - stack : true, - unwatch : true, - valueOf : true, - watch : true - }, - begin, // The root token - -// browser contains a set of global names that are commonly provided by a -// web browser environment. - - browser = { - clearInterval : false, - clearTimeout : false, - document : false, - event : false, - frames : false, - history : false, - Image : false, - localStorage : false, - location : false, - name : false, - navigator : false, - Option : false, - parent : false, - screen : false, - sessionStorage : false, - setInterval : false, - setTimeout : false, - Storage : false, - window : false, - XMLHttpRequest : false - }, - -// bundle contains the text messages. - - bundle = { - a_function: "'{a}' is a function.", - a_label: "'{a}' is a statement label.", - a_not_allowed: "'{a}' is not allowed.", - a_not_defined: "'{a}' is not defined.", - a_scope: "'{a}' used out of scope.", - adsafe: "ADsafe violation.", - adsafe_a: "ADsafe violation: '{a}'.", - adsafe_autocomplete: "ADsafe autocomplete violation.", - adsafe_bad_id: "ADSAFE violation: bad id.", - adsafe_div: "ADsafe violation: Wrap the widget in a div.", - adsafe_fragment: "ADSAFE: Use the fragment option.", - adsafe_go: "ADsafe violation: Misformed ADSAFE.go.", - adsafe_html: "Currently, ADsafe does not operate on whole HTML " + - "documents. It operates on
fragments and .js files.", - adsafe_id: "ADsafe violation: id does not match.", - adsafe_id_go: "ADsafe violation: Missing ADSAFE.id or ADSAFE.go.", - adsafe_lib: "ADsafe lib violation.", - adsafe_lib_second: "ADsafe: The second argument to lib must be a function.", - adsafe_missing_id: "ADSAFE violation: missing ID_.", - adsafe_name_a: "ADsafe name violation: '{a}'.", - adsafe_placement: "ADsafe script placement violation.", - adsafe_prefix_a: "ADsafe violation: An id must have a '{a}' prefix", - adsafe_script: "ADsafe script violation.", - adsafe_source: "ADsafe unapproved script source.", - adsafe_subscript_a: "ADsafe subscript '{a}'.", - adsafe_tag: "ADsafe violation: Disallowed tag '{a}'.", - already_defined: "'{a}' is already defined.", - and: "The '&&' subexpression should be wrapped in parens.", - assign_exception: "Do not assign to the exception parameter.", - assignment_function_expression: "Expected an assignment or " + - "function call and instead saw an expression.", - attribute_case_a: "Attribute '{a}' not all lower case.", - avoid_a: "Avoid '{a}'.", - bad_assignment: "Bad assignment.", - bad_color_a: "Bad hex color '{a}'.", - bad_constructor: "Bad constructor.", - bad_entity: "Bad entity.", - bad_html: "Bad HTML string", - bad_id_a: "Bad id: '{a}'.", - bad_in_a: "Bad for in variable '{a}'.", - bad_invocation: "Bad invocation.", - bad_name_a: "Bad name: '{a}'.", - bad_new: "Do not use 'new' for side effects.", - bad_number: "Bad number '{a}'.", - bad_operand: "Bad operand.", - bad_type: "Bad type.", - bad_url: "Bad url string.", - bad_wrap: "Do not wrap function literals in parens unless they " + - "are to be immediately invoked.", - combine_var: "Combine this with the previous 'var' statement.", - conditional_assignment: "Expected a conditional expression and " + - "instead saw an assignment.", - confusing_a: "Confusing use of '{a}'.", - confusing_regexp: "Confusing regular expression.", - constructor_name_a: "A constructor name '{a}' should start with " + - "an uppercase letter.", - control_a: "Unexpected control character '{a}'.", - css: "A css file should begin with @charset 'UTF-8';", - dangling_a: "Unexpected dangling '_' in '{a}'.", - dangerous_comment: "Dangerous comment.", - deleted: "Only properties should be deleted.", - duplicate_a: "Duplicate '{a}'.", - empty_block: "Empty block.", - empty_case: "Empty case.", - empty_class: "Empty class.", - evil: "eval is evil.", - expected_a: "Expected '{a}'.", - expected_a_b: "Expected '{a}' and instead saw '{b}'.", - expected_a_b_from_c_d: "Expected '{a}' to match '{b}' from line " + - "{c} and instead saw '{d}'.", - expected_at_a: "Expected an at-rule, and instead saw @{a}.", - expected_a_at_b_c: "Expected '{a}' at column {b}, not column {c}.", - expected_attribute_a: "Expected an attribute, and instead saw [{a}].", - expected_attribute_value_a: "Expected an attribute value and " + - "instead saw '{a}'.", - expected_class_a: "Expected a class, and instead saw .{a}.", - expected_fraction_a: "Expected a number between 0 and 1 and " + - "instead saw '{a}'", - expected_id_a: "Expected an id, and instead saw #{a}.", - expected_identifier_a: "Expected an identifier and instead saw '{a}'.", - expected_identifier_a_reserved: "Expected an identifier and " + - "instead saw '{a}' (a reserved word).", - expected_linear_a: "Expected a linear unit and instead saw '{a}'.", - expected_lang_a: "Expected a lang code, and instead saw :{a}.", - expected_media_a: "Expected a CSS media type, and instead saw '{a}'.", - expected_name_a: "Expected a name and instead saw '{a}'.", - expected_nonstandard_style_attribute: "Expected a non-standard " + - "style attribute and instead saw '{a}'.", - expected_number_a: "Expected a number and instead saw '{a}'.", - expected_operator_a: "Expected an operator and instead saw '{a}'.", - expected_percent_a: "Expected a percentage and instead saw '{a}'", - expected_positive_a: "Expected a positive number and instead saw '{a}'", - expected_pseudo_a: "Expected a pseudo, and instead saw :{a}.", - expected_selector_a: "Expected a CSS selector, and instead saw {a}.", - expected_small_a: "Expected a small number and instead saw '{a}'", - expected_space_a_b: "Expected exactly one space between '{a}' and '{b}'.", - expected_string_a: "Expected a string and instead saw {a}.", - expected_style_attribute: "Excepted a style attribute, and instead saw '{a}'.", - expected_style_pattern: "Expected a style pattern, and instead saw '{a}'.", - expected_tagname_a: "Expected a tagName, and instead saw {a}.", - for_if: "The body of a for in should be wrapped in an if " + - "statement to filter unwanted properties from the prototype.", - function_block: "Function statements should not be placed in blocks. " + - "Use a function expression or move the statement to the top of " + - "the outer function.", - function_eval: "The Function constructor is eval.", - function_loop: "Don't make functions within a loop.", - function_statement: "Function statements are not invocable. " + - "Wrap the whole function invocation in parens.", - function_strict: "Use the function form of 'use strict'.", - get_set: "get/set are ES5 features.", - html_confusion_a: "HTML confusion in regular expression '<{a}'.", - html_handlers: "Avoid HTML event handlers.", - identifier_function: "Expected an identifier in an assignment " + - "and instead saw a function invocation.", - implied_evil: "Implied eval is evil. Pass a function instead of a string.", - infix_in: "Unexpected 'in'. Compare with undefined, or use the " + - "hasOwnProperty method instead.", - insecure_a: "Insecure '{a}'.", - isNaN: "Use the isNaN function to compare with NaN.", - label_a_b: "Label '{a}' on '{b}' statement.", - lang: "lang is deprecated.", - leading_decimal_a: "A leading decimal point can be confused with a dot: '.{a}'.", - missing_a: "Missing '{a}'.", - missing_a_after_b: "Missing '{a}' after '{b}'.", - missing_option: "Missing option value.", - missing_property: "Missing property name.", - missing_space_a_b: "Missing space between '{a}' and '{b}'.", - missing_url: "Missing url.", - missing_use_strict: "Missing 'use strict' statement.", - mixed: "Mixed spaces and tabs.", - move_invocation: "Move the invocation into the parens that " + - "contain the function.", - move_var: "Move 'var' declarations to the top of the function.", - name_function: "Missing name in function statement.", - nested_comment: "Nested comment.", - not: "Nested not.", - not_a_constructor: "Do not use {a} as a constructor.", - not_a_defined: "'{a}' has not been fully defined yet.", - not_a_function: "'{a}' is not a function.", - not_a_label: "'{a}' is not a label.", - not_a_scope: "'{a}' is out of scope.", - not_greater: "'{a}' should not be greater than '{b}'.", - parameter_a_get_b: "Unexpected parameter '{a}' in get {b} function.", - parameter_set_a: "Expected parameter (value) in set {a} function.", - radix: "Missing radix parameter.", - read_only: "Read only.", - redefinition_a: "Redefinition of '{a}'.", - reserved_a: "Reserved name '{a}'.", - scanned_a_b: "{a} ({b}% scanned).", - slash_equal: "A regular expression literal can be confused with '/='.", - statement_block: "Expected to see a statement and instead saw a block.", - stopping: "Stopping. ", - strange_loop: "Strange loop.", - strict: "Strict violation.", - subscript: "['{a}'] is better written in dot notation.", - tag_a_in_b: "A '<{a}>' must be within '<{b}>'.", - too_long: "Line too long.", - too_many: "Too many errors.", - trailing_decimal_a: "A trailing decimal point can be confused " + - "with a dot: '.{a}'.", - type: "type is unnecessary.", - unclosed: "Unclosed string.", - unclosed_comment: "Unclosed comment.", - unclosed_regexp: "Unclosed regular expression.", - unescaped_a: "Unescaped '{a}'.", - unexpected_a: "Unexpected '{a}'.", - unexpected_char_a_b: "Unexpected character '{a}' in {b}.", - unexpected_comment: "Unexpected comment.", - unexpected_property_a: "Unexpected /*property*/ '{a}'.", - unexpected_space_a_b: "Unexpected space between '{a}' and '{b}'.", - unnecessary_initialize: "It is not necessary to initialize '{a}' " + - "to 'undefined'.", - unnecessary_use: "Unnecessary 'use strict'.", - unreachable_a_b: "Unreachable '{a}' after '{b}'.", - unrecognized_style_attribute_a: "Unrecognized style attribute '{a}'.", - unrecognized_tag_a: "Unrecognized tag '<{a}>'.", - unsafe: "Unsafe character.", - url: "JavaScript URL.", - use_array: "Use the array literal notation [].", - use_braces: "Spaces are hard to count. Use {{a}}.", - use_object: "Use the object literal notation {}.", - use_param: "Use a named parameter.", - used_before_a: "'{a}' was used before it was defined.", - var_a_not: "Variable {a} was not declared correctly.", - weird_assignment: "Weird assignment.", - weird_condition: "Weird condition.", - weird_new: "Weird construction. Delete 'new'.", - weird_program: "Weird program.", - weird_relation: "Weird relation.", - weird_ternary: "Weird ternary.", - wrap_immediate: "Wrap an immediate function invocation in parentheses " + - "to assist the reader in understanding that the expression " + - "is the result of a function, and not the function itself.", - wrap_regexp: "Wrap the /regexp/ literal in parens to " + - "disambiguate the slash operator.", - write_is_wrong: "document.write can be a form of eval." - }, - comments_off, - css_attribute_data, - css_any, - - css_colorData = { - "aliceblue" : true, - "antiquewhite" : true, - "aqua" : true, - "aquamarine" : true, - "azure" : true, - "beige" : true, - "bisque" : true, - "black" : true, - "blanchedalmond" : true, - "blue" : true, - "blueviolet" : true, - "brown" : true, - "burlywood" : true, - "cadetblue" : true, - "chartreuse" : true, - "chocolate" : true, - "coral" : true, - "cornflowerblue" : true, - "cornsilk" : true, - "crimson" : true, - "cyan" : true, - "darkblue" : true, - "darkcyan" : true, - "darkgoldenrod" : true, - "darkgray" : true, - "darkgreen" : true, - "darkkhaki" : true, - "darkmagenta" : true, - "darkolivegreen" : true, - "darkorange" : true, - "darkorchid" : true, - "darkred" : true, - "darksalmon" : true, - "darkseagreen" : true, - "darkslateblue" : true, - "darkslategray" : true, - "darkturquoise" : true, - "darkviolet" : true, - "deeppink" : true, - "deepskyblue" : true, - "dimgray" : true, - "dodgerblue" : true, - "firebrick" : true, - "floralwhite" : true, - "forestgreen" : true, - "fuchsia" : true, - "gainsboro" : true, - "ghostwhite" : true, - "gold" : true, - "goldenrod" : true, - "gray" : true, - "green" : true, - "greenyellow" : true, - "honeydew" : true, - "hotpink" : true, - "indianred" : true, - "indigo" : true, - "ivory" : true, - "khaki" : true, - "lavender" : true, - "lavenderblush" : true, - "lawngreen" : true, - "lemonchiffon" : true, - "lightblue" : true, - "lightcoral" : true, - "lightcyan" : true, - "lightgoldenrodyellow" : true, - "lightgreen" : true, - "lightpink" : true, - "lightsalmon" : true, - "lightseagreen" : true, - "lightskyblue" : true, - "lightslategray" : true, - "lightsteelblue" : true, - "lightyellow" : true, - "lime" : true, - "limegreen" : true, - "linen" : true, - "magenta" : true, - "maroon" : true, - "mediumaquamarine" : true, - "mediumblue" : true, - "mediumorchid" : true, - "mediumpurple" : true, - "mediumseagreen" : true, - "mediumslateblue" : true, - "mediumspringgreen" : true, - "mediumturquoise" : true, - "mediumvioletred" : true, - "midnightblue" : true, - "mintcream" : true, - "mistyrose" : true, - "moccasin" : true, - "navajowhite" : true, - "navy" : true, - "oldlace" : true, - "olive" : true, - "olivedrab" : true, - "orange" : true, - "orangered" : true, - "orchid" : true, - "palegoldenrod" : true, - "palegreen" : true, - "paleturquoise" : true, - "palevioletred" : true, - "papayawhip" : true, - "peachpuff" : true, - "peru" : true, - "pink" : true, - "plum" : true, - "powderblue" : true, - "purple" : true, - "red" : true, - "rosybrown" : true, - "royalblue" : true, - "saddlebrown" : true, - "salmon" : true, - "sandybrown" : true, - "seagreen" : true, - "seashell" : true, - "sienna" : true, - "silver" : true, - "skyblue" : true, - "slateblue" : true, - "slategray" : true, - "snow" : true, - "springgreen" : true, - "steelblue" : true, - "tan" : true, - "teal" : true, - "thistle" : true, - "tomato" : true, - "turquoise" : true, - "violet" : true, - "wheat" : true, - "white" : true, - "whitesmoke" : true, - "yellow" : true, - "yellowgreen" : true, - - "activeborder" : true, - "activecaption" : true, - "appworkspace" : true, - "background" : true, - "buttonface" : true, - "buttonhighlight" : true, - "buttonshadow" : true, - "buttontext" : true, - "captiontext" : true, - "graytext" : true, - "highlight" : true, - "highlighttext" : true, - "inactiveborder" : true, - "inactivecaption" : true, - "inactivecaptiontext" : true, - "infobackground" : true, - "infotext" : true, - "menu" : true, - "menutext" : true, - "scrollbar" : true, - "threeddarkshadow" : true, - "threedface" : true, - "threedhighlight" : true, - "threedlightshadow" : true, - "threedshadow" : true, - "window" : true, - "windowframe" : true, - "windowtext" : true - }, - - css_border_style, - css_break, - - css_lengthData = { - '%': true, - 'cm': true, - 'em': true, - 'ex': true, - 'in': true, - 'mm': true, - 'pc': true, - 'pt': true, - 'px': true - }, - - css_media, - css_overflow, - - devel = { - alert : false, - confirm : false, - console : false, - Debug : false, - opera : false, - prompt : false - }, - - escapes = { - '\b': '\\b', - '\t': '\\t', - '\n': '\\n', - '\f': '\\f', - '\r': '\\r', - '"' : '\\"', - '/' : '\\/', - '\\': '\\\\' - }, - - funct, // The current function - - functionicity = [ - 'closure', 'exception', 'global', 'label', 'outer', 'unused', 'var' - ], - - functions, // All of the functions - global, // The global scope - html_tag = { - a: {}, - abbr: {}, - acronym: {}, - address: {}, - applet: {}, - area: {empty: true, parent: ' map '}, - article: {}, - aside: {}, - audio: {}, - b: {}, - base: {empty: true, parent: ' head '}, - bdo: {}, - big: {}, - blockquote: {}, - body: {parent: ' html noframes '}, - br: {empty: true}, - button: {}, - canvas: {parent: ' body p div th td '}, - caption: {parent: ' table '}, - center: {}, - cite: {}, - code: {}, - col: {empty: true, parent: ' table colgroup '}, - colgroup: {parent: ' table '}, - command: {parent: ' menu '}, - datalist: {}, - dd: {parent: ' dl '}, - del: {}, - details: {}, - dialog: {}, - dfn: {}, - dir: {}, - div: {}, - dl: {}, - dt: {parent: ' dl '}, - em: {}, - embed: {}, - fieldset: {}, - figure: {}, - font: {}, - footer: {}, - form: {}, - frame: {empty: true, parent: ' frameset '}, - frameset: {parent: ' html frameset '}, - h1: {}, - h2: {}, - h3: {}, - h4: {}, - h5: {}, - h6: {}, - head: {parent: ' html '}, - header: {}, - hgroup: {}, - hr: {empty: true}, - 'hta:application': - {empty: true, parent: ' head '}, - html: {parent: '*'}, - i: {}, - iframe: {}, - img: {empty: true}, - input: {empty: true}, - ins: {}, - kbd: {}, - keygen: {}, - label: {}, - legend: {parent: ' details fieldset figure '}, - li: {parent: ' dir menu ol ul '}, - link: {empty: true, parent: ' head '}, - map: {}, - mark: {}, - menu: {}, - meta: {empty: true, parent: ' head noframes noscript '}, - meter: {}, - nav: {}, - noframes: {parent: ' html body '}, - noscript: {parent: ' body head noframes '}, - object: {}, - ol: {}, - optgroup: {parent: ' select '}, - option: {parent: ' optgroup select '}, - output: {}, - p: {}, - param: {empty: true, parent: ' applet object '}, - pre: {}, - progress: {}, - q: {}, - rp: {}, - rt: {}, - ruby: {}, - samp: {}, - script: {empty: true, parent: ' body div frame head iframe p pre span '}, - section: {}, - select: {}, - small: {}, - span: {}, - source: {}, - strong: {}, - style: {parent: ' head ', empty: true}, - sub: {}, - sup: {}, - table: {}, - tbody: {parent: ' table '}, - td: {parent: ' tr '}, - textarea: {}, - tfoot: {parent: ' table '}, - th: {parent: ' tr '}, - thead: {parent: ' table '}, - time: {}, - title: {parent: ' head '}, - tr: {parent: ' table tbody thead tfoot '}, - tt: {}, - u: {}, - ul: {}, - 'var': {}, - video: {} - }, - - ids, // HTML ids - implied, // Implied globals - in_block, - indent, - json_mode, - lines, - lookahead, - member, - node = { - Buffer : false, - clearInterval: false, - clearTimeout : false, - console : false, - global : false, - module : false, - process : false, - querystring : false, - require : false, - setInterval : false, - setTimeout : false, - util : false, - __filename : false, - __dirname : false - }, - numbery = { - indexOf : true, - lastIndexOf : true, - search : true - }, - properties, - next_token, - older_token, - option, - predefined, // Global variables defined by option - prereg, - prev_token, - regexp_flag = { - g: true, - i: true, - m: true - }, - rhino = { - defineClass : false, - deserialize : false, - gc : false, - help : false, - load : false, - loadClass : false, - print : false, - quit : false, - readFile : false, - readUrl : false, - runCommand : false, - seal : false, - serialize : false, - spawn : false, - sync : false, - toint32 : false, - version : false - }, - - scope, // The current scope - semicolon_coda = { - ';' : true, - '"' : true, - '\'': true, - ')' : true - }, - src, - stack, - -// standard contains the global names that are provided by the -// ECMAScript standard. - - standard = { - Array : false, - Boolean : false, - Date : false, - decodeURI : false, - decodeURIComponent : false, - encodeURI : false, - encodeURIComponent : false, - Error : false, - 'eval' : false, - EvalError : false, - Function : false, - hasOwnProperty : false, - isFinite : false, - isNaN : false, - JSON : false, - Math : false, - Number : false, - Object : false, - parseInt : false, - parseFloat : false, - RangeError : false, - ReferenceError : false, - RegExp : false, - String : false, - SyntaxError : false, - TypeError : false, - URIError : false - }, - - standard_property = { - E : true, - LN2 : true, - LN10 : true, - LOG2E : true, - LOG10E : true, - MAX_VALUE : true, - MIN_VALUE : true, - NEGATIVE_INFINITY : true, - PI : true, - POSITIVE_INFINITY : true, - SQRT1_2 : true, - SQRT2 : true - }, - - strict_mode, - syntax = {}, - tab, - token, - urls, - var_mode, - warnings, - -// widget contains the global names which are provided to a Yahoo -// (fna Konfabulator) widget. - - widget = { - alert : true, - animator : true, - appleScript : true, - beep : true, - bytesToUIString : true, - Canvas : true, - chooseColor : true, - chooseFile : true, - chooseFolder : true, - closeWidget : true, - COM : true, - convertPathToHFS : true, - convertPathToPlatform : true, - CustomAnimation : true, - escape : true, - FadeAnimation : true, - filesystem : true, - Flash : true, - focusWidget : true, - form : true, - FormField : true, - Frame : true, - HotKey : true, - Image : true, - include : true, - isApplicationRunning : true, - iTunes : true, - konfabulatorVersion : true, - log : true, - md5 : true, - MenuItem : true, - MoveAnimation : true, - openURL : true, - play : true, - Point : true, - popupMenu : true, - preferenceGroups : true, - preferences : true, - print : true, - prompt : true, - random : true, - Rectangle : true, - reloadWidget : true, - ResizeAnimation : true, - resolvePath : true, - resumeUpdates : true, - RotateAnimation : true, - runCommand : true, - runCommandInBg : true, - saveAs : true, - savePreferences : true, - screen : true, - ScrollBar : true, - showWidgetPreferences : true, - sleep : true, - speak : true, - Style : true, - suppressUpdates : true, - system : true, - tellWidget : true, - Text : true, - TextArea : true, - Timer : true, - unescape : true, - updateNow : true, - URL : true, - Web : true, - widget : true, - Window : true, - XMLDOM : true, - XMLHttpRequest : true, - yahooCheckLogin : true, - yahooLogin : true, - yahooLogout : true - }, - - windows = { - ActiveXObject: false, - CScript : false, - Debug : false, - Enumerator : false, - System : false, - VBArray : false, - WScript : false - }, - -// xmode is used to adapt to the exceptions in html parsing. -// It can have these states: -// false .js script file -// html -// outer -// script -// style -// scriptstring -// styleproperty - - xmode, - xquote, - -// Regular expressions. Some of these are stupidly long. - -// unsafe comment or string - ax = /@cc|<\/?|script|\]\s*\]|<\s*!|</i, -// unsafe characters that are silently deleted by one or more browsers - cx = /[\u0000-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/, -// query characters for ids - dx = /[\[\]\/\\"'*<>.&:(){}+=#]/, -// html token - hx = /^\s*(['"=>\/&#]|<(?:\/|\!(?:--)?)?|[a-zA-Z][a-zA-Z0-9_\-:]*|[0-9]+|--)/, -// identifier - ix = /^([a-zA-Z_$][a-zA-Z0-9_$]*)$/, -// javascript url - jx = /^(?:javascript|jscript|ecmascript|vbscript|mocha|livescript)\s*:/i, -// star slash - lx = /\*\/|\/\*/, -// characters in strings that need escapement - nx = /[\u0000-\u001f"\\\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, -// outer html token - ox = /[>&]|<[\/!]?|--/, -// attributes characters - qx = /[^a-zA-Z0-9+\-_\/ ]/, -// style - sx = /^\s*([{:#%.=,>+\[\]@()"';]|\*=?|\$=|\|=|\^=|~=|[a-zA-Z_][a-zA-Z0-9_\-]*|[0-9]+|<\/|\/\*)/, - ssx = /^\s*([@#!"'};:\-%.=,+\[\]()*_]|[a-zA-Z][a-zA-Z0-9._\-]*|\/\*?|\d+(?:\.\d+)?|<\/)/, -// token - tx = /^\s*([(){}\[.,:;'"~\?\]#@]|==?=?|\/(\*(jslint|properties|property|members?|globals?)?|=|\/)?|\*[\/=]?|\+(?:=|\++)?|-(?:=|-+)?|%=?|&[&=]?|\|[|=]?|>>?>?=?|<([\/=!]|\!(\[|--)?|<=?)?|\^=?|\!=?=?|[a-zA-Z_$][a-zA-Z0-9_$]*|[0-9]+([xX][0-9a-fA-F]+|\.[0-9]*)?([eE][+\-]?[0-9]+)?)/, -// url badness - ux = /&|\+|\u00AD|\.\.|\/\*|%[^;]|base64|url|expression|data|mailto/i, - - rx = { - outer: hx, - html: hx, - style: sx, - styleproperty: ssx - }; - - - function return_this() { - return this; - } - - function F() {} // Used by Object.create - -// Provide critical ES5 functions to ES3. - - if (typeof Array.prototype.filter !== 'function') { - Array.prototype.filter = function (f) { - var i, length = this.length, result = []; - for (i = 0; i < length; i += 1) { - try { - result.push(f(this[i])); - } catch (ignore) { - } - } - return result; - }; - } - - if (typeof Array.isArray !== 'function') { - Array.isArray = function (o) { - return Object.prototype.toString.apply(o) === '[object Array]'; - }; - } - - if (!Object.hasOwnProperty('create')) { - Object.create = function (o) { - F.prototype = o; - return new F(); - }; - } - - if (typeof Object.keys !== 'function') { - Object.keys = function (o) { - var array = [], key; - for (key in o) { - if (Object.prototype.hasOwnProperty.call(o, key)) { - array.push(key); - } - } - return array; - }; - } - -// Substandard methods - - if (typeof String.prototype.entityify !== 'function') { - String.prototype.entityify = function () { - return this - .replace(/&/g, '&') - .replace(//g, '>'); - }; - } - - if (typeof String.prototype.isAlpha !== 'function') { - String.prototype.isAlpha = function () { - return (this >= 'a' && this <= 'z\uffff') || - (this >= 'A' && this <= 'Z\uffff'); - }; - } - - if (typeof String.prototype.isDigit !== 'function') { - String.prototype.isDigit = function () { - return (this >= '0' && this <= '9'); - }; - } - - if (typeof String.prototype.supplant !== 'function') { - String.prototype.supplant = function (o) { - return this.replace(/\{([^{}]*)\}/g, function (a, b) { - var replacement = o[b]; - return typeof replacement === 'string' || - typeof replacement === 'number' ? replacement : a; - }); - }; - } - - - function sanitize(a) { - -// Escapify a troublesome character. - - return escapes[a] ? escapes[a] : - '\\u' + ('0000' + a.charCodeAt().toString(16)).slice(-4); - } - - - function combine(a, b) { - var name; - for (name in b) { - if (Object.prototype.hasOwnProperty.call(b, name)) { - a[name] = b[name]; - } - } - } - - function assume() { - if (!option.safe) { - if (option.rhino) { - combine(predefined, rhino); - } - if (option.devel) { - combine(predefined, devel); - } - if (option.browser) { - combine(predefined, browser); - } - if (option.windows) { - combine(predefined, windows); - } - if (option.node) { - combine(predefined, node); - } - if (option.widget) { - combine(predefined, widget); - } - } - } - - -// Produce an error warning. - - function quit(message, line, character) { - throw { - name: 'JSLintError', - line: line, - character: character, - message: bundle.scanned_a_b.supplant({ - a: message, - b: Math.floor((line / lines.length) * 100) - }) - }; - } - - function warn(message, offender, a, b, c, d) { - var character, line, warning; - offender = offender || next_token; // `~ - line = offender.line || 0; - character = offender.from || 0; - warning = { - id: '(error)', - raw: bundle[message] || message, - evidence: lines[line - 1] || '', - line: line, - character: character, - a: a || offender.value, - b: b, - c: c, - d: d - }; - warning.reason = warning.raw.supplant(warning); - JSLINT.errors.push(warning); - if (option.passfail) { - quit(bundle.stopping, line, character); - } - warnings += 1; - if (warnings >= option.maxerr) { - quit(bundle.too_many, line, character); - } - return warning; - } - - function warn_at(message, line, character, a, b, c, d) { - return warn(message, { - line: line, - from: character - }, a, b, c, d); - } - - function stop(message, offender, a, b, c, d) { - var warning = warn(message, offender, a, b, c, d); - quit(bundle.stopping, warning.line, warning.character); - } - - function stop_at(message, line, character, a, b, c, d) { - return stop(message, { - line: line, - from: character - }, a, b, c, d); - } - - function expected_at(at) { - if (option.white && next_token.from !== at) { - warn('expected_a_at_b_c', next_token, next_token.value, at, - next_token.from); - } - } - - function aint(it, name, expected) { - if (it[name] !== expected) { - warn('expected_a_b', it, expected, it[name]); - return true; - } else { - return false; - } - } - - -// lexical analysis and token construction - - var lex = (function lex() { - var character, from, line, source_row; - -// Private lex methods - - function collect_comment(comment, quote, line, at) { - var comment_object = { - comment: comment, - quote: quote, - at: at, - line: line - }; - if (comments_off || src || (xmode && xmode !== 'script' && - xmode !== 'style' && xmode !== 'styleproperty')) { - warn_at('unexpected_comment', line, character); - } else if (xmode === 'script' && /<\//i.test(source_row)) { - warn_at('unexpected_a', line, character, '<\/'); - } else if (option.safe && ax.test(comment)) { - warn_at('dangerous_comment', line, at); - } - if (older_token.comments) { - older_token.comments.push(comment_object); - } else { - older_token.comments = [comment_object]; - } - JSLINT.comments.push(comment_object); - } - - function next_line() { - var at; - if (line >= lines.length) { - return false; - } - character = 1; - source_row = lines[line]; - line += 1; - at = source_row.search(/ \t/); - if (at >= 0) { - warn_at('mixed', line, at + 1); - } - source_row = source_row.replace(/\t/g, tab); - at = source_row.search(cx); - if (at >= 0) { - warn_at('unsafe', line, at); - } - if (option.maxlen && option.maxlen < source_row.length) { - warn_at('too_long', line, source_row.length); - } - return true; - } - -// Produce a token object. The token inherits from a syntax symbol. - - function it(type, value, quote) { - var id, the_token; - if (type === '(string)' || type === '(range)') { - if (jx.test(value)) { - warn_at('url', line, from); - } - } - the_token = Object.create(syntax[( - type === '(punctuator)' || - (type === '(identifier)' && - Object.prototype.hasOwnProperty.call(syntax, value)) ? - value : - type - )] || syntax['(error)']); - if (type === '(identifier)') { - the_token.identifier = true; - if (value === '__iterator__' || value === '__proto__') { - stop_at('reserved_a', line, from, value); - } else if (option.nomen && - (value.charAt(0) === '_' || - value.charAt(value.length - 1) === '_')) { - warn_at('dangling_a', line, from, value); - } - } - if (value !== undefined) { - the_token.value = value; - } - if (quote) { - the_token.quote = quote; - } - the_token.line = line; - the_token.from = from; - the_token.thru = character; - the_token.prev = older_token; - id = the_token.id; - prereg = id && ( - ('(,=:[!&|?{};'.indexOf(id.charAt(id.length - 1)) >= 0) || - id === 'return' - ); - older_token.next = the_token; - older_token = the_token; - return the_token; - } - -// Public lex methods - - return { - init: function (source) { - if (typeof source === 'string') { - lines = source - .replace(/\r\n/g, '\n') - .replace(/\r/g, '\n') - .split('\n'); - } else { - lines = source; - } - line = 0; - next_line(); - from = 1; - }, - - range: function (begin, end) { - var c, value = ''; - from = character; - if (source_row.charAt(0) !== begin) { - stop_at('expected_a_b', line, character, begin, - source_row.charAt(0)); - } - for (;;) { - source_row = source_row.slice(1); - character += 1; - c = source_row.charAt(0); - switch (c) { - case '': - stop_at('missing_a', line, character, c); - break; - case end: - source_row = source_row.slice(1); - character += 1; - return it('(range)', value); - case xquote: - case '\\': - warn_at('unexpected_a', line, character, c); - break; - } - value += c; - } - }, - -// token -- this is called by advance to get the next token. - - token: function () { - var b, c, captures, digit, depth, flag, high, i, j, length, low, quote, symbol; - - function match(x) { - var exec = x.exec(source_row), first; - if (exec) { - length = exec[0].length; - first = exec[1]; - c = first.charAt(0); - source_row = source_row.substr(length); - from = character + length - first.length; - character += length; - return first; - } - } - - function string(x) { - var c, j, r = ''; - - function hex(n) { - var i = parseInt(source_row.substr(j + 1, n), 16); - j += n; - if (i >= 32 && i <= 126 && - i !== 34 && i !== 92 && i !== 39) { - warn_at('unexpected_a', line, character, '\\'); - } - character += n; - c = String.fromCharCode(i); - } - - if (json_mode && x !== '"') { - warn_at('expected_a', line, character, '"'); - } - - if (xquote === x || (xmode === 'scriptstring' && !xquote)) { - return it('(punctuator)', x); - } - - j = 0; - for (;;) { - while (j >= source_row.length) { - j = 0; - if (xmode !== 'html' || !next_line()) { - stop_at('unclosed', line, from); - } - } - c = source_row.charAt(j); - if (c === x) { - character += 1; - source_row = source_row.substr(j + 1); - return it('(string)', r, x); - } - if (c < ' ') { - if (c === '\n' || c === '\r') { - break; - } - warn_at('control_a', - line, character + j, source_row.slice(0, j)); - } else if (c === xquote) { - warn_at('bad_html', line, character + j); - } else if (c === '<') { - if (option.safe && xmode === 'html') { - warn_at('adsafe_a', line, character + j, c); - } else if (source_row.charAt(j + 1) === '/' && (xmode || option.safe)) { - warn_at('expected_a_b', line, character, - '<\\/', ' 0) { - character += 1; - source_row = source_row.slice(i); - break; - } else { - if (!next_line()) { - return it('(end)', ''); - } - } - } - symbol = match(rx[xmode] || tx); - if (!symbol) { - symbol = ''; - c = ''; - while (source_row && source_row < '!') { - source_row = source_row.substr(1); - } - if (source_row) { - if (xmode === 'html') { - return it('(error)', source_row.charAt(0)); - } else { - stop_at('unexpected_a', - line, character, source_row.substr(0, 1)); - } - } - } else { - -// identifier - - if (c.isAlpha() || c === '_' || c === '$') { - return it('(identifier)', symbol); - } - -// number - - if (c.isDigit()) { - if (xmode !== 'style' && - xmode !== 'styleproperty' && - source_row.substr(0, 1).isAlpha()) { - warn_at('expected_space_a_b', - line, character, c, source_row.charAt(0)); - } - if (c === '0') { - digit = symbol.substr(1, 1); - if (digit.isDigit()) { - if (token.id !== '.' && xmode !== 'styleproperty') { - warn_at('unexpected_a', - line, character, symbol); - } - } else if (json_mode && (digit === 'x' || digit === 'X')) { - warn_at('unexpected_a', line, character, '0x'); - } - } - if (symbol.substr(symbol.length - 1) === '.') { - warn_at('trailing_decimal_a', line, - character, symbol); - } - if (xmode !== 'style') { - digit = +symbol; - if (!isFinite(digit)) { - warn_at('bad_number', line, character, symbol); - } - symbol = digit; - } - return it('(number)', symbol); - } - switch (symbol) { - -// string - - case '"': - case "'": - return string(symbol); - -// // comment - - case '//': - collect_comment(source_row, '//', line, character); - source_row = ''; - break; - -// /* comment - - case '/*': - quote = '/*'; - for (;;) { - i = source_row.search(lx); - if (i >= 0) { - break; - } - collect_comment(source_row, quote, line, character); - quote = ''; - if (!next_line()) { - stop_at('unclosed_comment', line, character); - } - } - collect_comment(source_row.slice(0, i), quote, character, line); - character += i + 2; - if (source_row.substr(i, 1) === '/') { - stop_at('nested_comment', line, character); - } - source_row = source_row.substr(i + 2); - break; - - case '': - break; -// / - case '/': - if (token.id === '/=') { - stop_at( - bundle.slash_equal, - line, - from - ); - } - if (prereg) { - depth = 0; - captures = 0; - length = 0; - for (;;) { - b = true; - c = source_row.charAt(length); - length += 1; - switch (c) { - case '': - stop_at('unclosed_regexp', line, from); - return; - case '/': - if (depth > 0) { - warn_at('unescaped_a', - line, from + length, '/'); - } - c = source_row.substr(0, length - 1); - flag = Object.create(regexp_flag); - while (flag[source_row.charAt(length)] === true) { - flag[source_row.charAt(length)] = false; - length += 1; - } - if (source_row.charAt(length).isAlpha()) { - stop_at('unexpected_a', - line, from, source_row.charAt(length)); - } - character += length; - source_row = source_row.substr(length); - quote = source_row.charAt(0); - if (quote === '/' || quote === '*') { - stop_at('confusing_regexp', - line, from); - } - return it('(regexp)', c); - case '\\': - c = source_row.charAt(length); - if (c < ' ') { - warn_at('control_a', - line, from + length, String(c)); - } else if (c === '<') { - warn_at( - bundle.unexpected_a, - line, - from + length, - '\\' - ); - } - length += 1; - break; - case '(': - depth += 1; - b = false; - if (source_row.charAt(length) === '?') { - length += 1; - switch (source_row.charAt(length)) { - case ':': - case '=': - case '!': - length += 1; - break; - default: - warn_at( - bundle.expected_a_b, - line, - from + length, - ':', - source_row.charAt(length) - ); - } - } else { - captures += 1; - } - break; - case '|': - b = false; - break; - case ')': - if (depth === 0) { - warn_at('unescaped_a', - line, from + length, ')'); - } else { - depth -= 1; - } - break; - case ' ': - j = 1; - while (source_row.charAt(length) === ' ') { - length += 1; - j += 1; - } - if (j > 1) { - warn_at('use_braces', - line, from + length, j); - } - break; - case '[': - c = source_row.charAt(length); - if (c === '^') { - length += 1; - if (option.regexp) { - warn_at('insecure_a', - line, from + length, c); - } else if (source_row.charAt(length) === ']') { - stop_at('unescaped_a', - line, from + length, '^'); - } - } - quote = false; - if (c === ']') { - warn_at('empty_class', line, - from + length - 1); - quote = true; - } -klass: do { - c = source_row.charAt(length); - length += 1; - switch (c) { - case '[': - case '^': - warn_at('unescaped_a', - line, from + length, c); - quote = true; - break; - case '-': - if (quote) { - quote = false; - } else { - warn_at('unescaped_a', - line, from + length, '-'); - quote = true; - } - break; - case ']': - if (!quote) { - warn_at('unescaped_a', - line, from + length - 1, '-'); - } - break klass; - case '\\': - c = source_row.charAt(length); - if (c < ' ') { - warn_at( - bundle.control_a, - line, - from + length, - String(c) - ); - } else if (c === '<') { - warn_at( - bundle.unexpected_a, - line, - from + length, - '\\' - ); - } - length += 1; - quote = true; - break; - case '/': - warn_at('unescaped_a', - line, from + length - 1, '/'); - quote = true; - break; - case '<': - if (xmode === 'script') { - c = source_row.charAt(length); - if (c === '!' || c === '/') { - warn_at( - bundle.html_confusion_a, - line, - from + length, - c - ); - } - } - quote = true; - break; - default: - quote = true; - } - } while (c); - break; - case '.': - if (option.regexp) { - warn_at('insecure_a', line, - from + length, c); - } - break; - case ']': - case '?': - case '{': - case '}': - case '+': - case '*': - warn_at('unescaped_a', line, - from + length, c); - break; - case '<': - if (xmode === 'script') { - c = source_row.charAt(length); - if (c === '!' || c === '/') { - warn_at( - bundle.html_confusion_a, - line, - from + length, - c - ); - } - } - break; - } - if (b) { - switch (source_row.charAt(length)) { - case '?': - case '+': - case '*': - length += 1; - if (source_row.charAt(length) === '?') { - length += 1; - } - break; - case '{': - length += 1; - c = source_row.charAt(length); - if (c < '0' || c > '9') { - warn_at( - bundle.expected_number_a, - line, - from + length, - c - ); - } - length += 1; - low = +c; - for (;;) { - c = source_row.charAt(length); - if (c < '0' || c > '9') { - break; - } - length += 1; - low = +c + (low * 10); - } - high = low; - if (c === ',') { - length += 1; - high = Infinity; - c = source_row.charAt(length); - if (c >= '0' && c <= '9') { - length += 1; - high = +c; - for (;;) { - c = source_row.charAt(length); - if (c < '0' || c > '9') { - break; - } - length += 1; - high = +c + (high * 10); - } - } - } - if (source_row.charAt(length) !== '}') { - warn_at( - bundle.expected_a_b, - line, - from + length, - '}', - c - ); - } else { - length += 1; - } - if (source_row.charAt(length) === '?') { - length += 1; - } - if (low > high) { - warn_at( - bundle.not_greater, - line, - from + length, - low, - high - ); - } - break; - } - } - } - c = source_row.substr(0, length - 1); - character += length; - source_row = source_row.substr(length); - return it('(regexp)', c); - } - return it('(punctuator)', symbol); - -// punctuator - - case ''); - } - character += 3; - source_row = source_row.slice(i + 3); - break; - case '#': - if (xmode === 'html' || xmode === 'styleproperty') { - for (;;) { - c = source_row.charAt(0); - if ((c < '0' || c > '9') && - (c < 'a' || c > 'f') && - (c < 'A' || c > 'F')) { - break; - } - character += 1; - source_row = source_row.substr(1); - symbol += c; - } - if (symbol.length !== 4 && symbol.length !== 7) { - warn_at('bad_color_a', line, - from + length, symbol); - } - return it('(color)', symbol); - } - return it('(punctuator)', symbol); - - default: - if (xmode === 'outer' && c === '&') { - character += 1; - source_row = source_row.substr(1); - for (;;) { - c = source_row.charAt(0); - character += 1; - source_row = source_row.substr(1); - if (c === ';') { - break; - } - if (!((c >= '0' && c <= '9') || - (c >= 'a' && c <= 'z') || - c === '#')) { - stop_at('bad_entity', line, from + length, - character); - } - } - break; - } - return it('(punctuator)', symbol); - } - } - } - } - }; - }()); - - - function add_label(symbol, type) { - - if (option.safe && funct['(global)'] && - typeof predefined[symbol] !== 'boolean') { - warn('adsafe_a', token, symbol); - } else if (symbol === 'hasOwnProperty') { - warn('bad_name_a', token, symbol); - } - -// Define symbol in the current function in the current scope. - - if (Object.prototype.hasOwnProperty.call(funct, symbol) && !funct['(global)']) { - warn(funct[symbol] === true ? - bundle.used_before_a : - bundle.already_defined, - next_token, symbol); - } - funct[symbol] = type; - if (funct['(global)']) { - if (global[symbol] === false) { - warn('read_only'); - } - global[symbol] = true; - if (Object.prototype.hasOwnProperty.call(implied, symbol)) { - warn('used_before_a', next_token, symbol); - delete implied[symbol]; - } - } else { - scope[symbol] = funct; - } - } - - - function peek(distance) { - -// Peek ahead to a future token. The distance is how far ahead to look. The -// default is the next token. - - var found, slot = 0; - - distance = distance || 0; - while (slot <= distance) { - found = lookahead[slot]; - if (!found) { - found = lookahead[slot] = lex.token(); - } - slot += 1; - } - return found; - } - - - function discard(it) { - -// The token will not be included in the parse tree, so move the comments -// that are attached to the token to tokens that are in the tree. - - it = it || token; - if (it.comments) { - var prev = it.prev; - while (prev.comments === null) { - prev = prev.prev; - } - if (prev.comments) { - prev.comments = prev.comments.concat(it.comments); - } else { - prev.comments = it.comments; - } - } - it.comments = null; - } - - - function advance(id, match) { - -// Produce the next token, also looking for programming errors. - - if (indent) { - -// In indentation checking was requested, then inspect all of the line breakings. -// The var statement is tricky because the names might be aligned or not. We -// look at the first line break after the var to determine the programmer's -// intention. - - if (var_mode && next_token.line !== token.line) { - if ((var_mode !== indent || !next_token.edge) && - next_token.from === indent.at - - (next_token.edge ? option.indent : 0)) { - var dent = indent; - for (;;) { - dent.at -= option.indent; - if (dent === var_mode) { - break; - } - dent = dent.was; - } - dent.open = false; - } - var_mode = false; - } - if (indent.open) { - -// If the token is an edge. - - if (next_token.edge) { - if (next_token.edge === 'label') { - expected_at(1); - } else if (next_token.edge === 'case') { - expected_at(indent.at - option.indent); - } else if (indent.mode !== 'array' || next_token.line !== token.line) { - expected_at(indent.at); - } - -// If the token is not an edge, but is the first token on the line. - - } else if (next_token.line !== token.line) { - if (next_token.from < indent.at + (indent.mode === - 'expression' ? 0 : option.indent)) { - expected_at(indent.at + option.indent); - } - indent.wrap = true; - } - } else if (next_token.line !== token.line) { - if (next_token.edge) { - expected_at(indent.at); - } else { - indent.wrap = true; - if (indent.mode === 'statement' || indent.mode === 'var') { - expected_at(indent.at + option.indent); - } else if (next_token.from < indent.at + (indent.mode === - 'expression' ? 0 : option.indent)) { - expected_at(indent.at + option.indent); - } - } - } - } - - switch (token.id) { - case '(number)': - if (next_token.id === '.') { - warn('trailing_decimal_a'); - } - break; - case '-': - if (next_token.id === '-' || next_token.id === '--') { - warn('confusing_a'); - } - break; - case '+': - if (next_token.id === '+' || next_token.id === '++') { - warn('confusing_a'); - } - break; - } - if (token.arity === 'string' || token.identifier) { - anonname = token.value; - } - - if (id && next_token.id !== id) { - if (match) { - warn('expected_a_b_from_c_d', next_token, id, - match.id, match.line, next_token.value); - } else if (!next_token.identifier || next_token.value !== id) { - warn('expected_a_b', next_token, id, next_token.value); - } - } - prev_token = token; - token = next_token; - next_token = lookahead.shift() || lex.token(); - if (token.id === '(end)') { - discard(); - } - } - - - function directive() { - var command = this.id, - name, - old_comments_off = comments_off, - old_option_white = option.white, - value; - if (next_token.line === token.line && next_token.from === token.thru) { - warn('missing_space_a_b', next_token, token.value, next_token.value); - } - comments_off = true; - option.white = false; - if (lookahead.length > 0 || next_token.comments) { - warn('unexpected_a', this); - } - switch (command) { - case '/*properties': - case '/*property': - case '/*members': - case '/*member': - command = '/*properties'; - if (!properties) { - properties = {}; - } - break; - case '/*jslint': - if (option.safe) { - warn('adsafe_a', this); - } - break; - case '/*globals': - case '/*global': - command = '/*global'; - if (option.safe) { - warn('adsafe_a', this); - } - break; - default: - stop('unpexpected_a', this); - } -loop: for (;;) { - for (;;) { - if (next_token.id === '*/') { - break loop; - } - if (next_token.id !== ',') { - break; - } - advance(); - } - if (next_token.arity !== 'string' && !next_token.identifier) { - stop('unexpected_a', next_token); - } - name = next_token.value; - advance(); - switch (command) { - case '/*global': - if (next_token.id === ':') { - advance(':'); - switch (next_token.id) { - case 'true': - if (typeof scope[name] === 'object' || - global[name] === false) { - stop('unexpected_a'); - } - global[name] = true; - advance('true'); - break; - case 'false': - if (typeof scope[name] === 'object') { - stop('unexpected_a'); - } - global[name] = false; - advance('false'); - break; - default: - stop('unexpected_a'); - } - } else { - if (typeof scope[name] === 'object') { - stop('unexpected_a'); - } - global[name] = false; - } - break; - case '/*jslint': - if (next_token.id !== ':') { - stop('expected_a_b', next_token, ':', next_token.value); - } - advance(':'); - switch (name) { - case 'indent': - value = +next_token.value; - if (typeof value !== 'number' || - !isFinite(value) || value < 0 || - Math.floor(value) !== value) { - stop('expected_small_a'); - } - if (value > 0) { - old_option_white = true; - } - option.indent = value; - break; - case 'maxerr': - value = +next_token.value; - if (typeof value !== 'number' || - !isFinite(value) || - value <= 0 || - Math.floor(value) !== value) { - stop('expected_small_a', next_token); - } - option.maxerr = value; - break; - case 'maxlen': - value = +next_token.value; - if (typeof value !== 'number' || !isFinite(value) || value < 0 || - Math.floor(value) !== value) { - stop('expected_small_a'); - } - option.maxlen = value; - break; - case 'white': - if (next_token.id === 'true') { - old_option_white = true; - } else if (next_token.id === 'false') { - old_option_white = false; - } else { - stop('unexpected_a'); - } - break; - default: - if (next_token.id === 'true') { - option[name] = true; - } else if (next_token.id === 'false') { - option[name] = false; - } else { - stop('unexpected_a'); - } - } - advance(); - break; - case '/*properties': - properties[name] = true; - break; - default: - stop('unexpected_a'); - } - } - if (command === '/*jslint') { - assume(); - } - comments_off = old_comments_off; - advance('*/'); - option.white = old_option_white; - } - - -// Indentation intention - - function edge(mode) { - next_token.edge = !indent || (indent.open && (mode || true)); - } - - - function step_in(mode) { - var open, was; - if (typeof mode === 'number') { - indent = { - at: mode, - open: true, - was: was - }; - } else if (!indent) { - indent = { - at: 1, - mode: 'statement', - open: true - }; - } else { - was = indent; - open = mode === 'var' || - (next_token.line !== token.line && mode !== 'statement'); - indent = { - at: (open || mode === 'control' ? - was.at + option.indent : was.at) + - (was.wrap ? option.indent : 0), - mode: mode, - open: open, - was: was - }; - if (mode === 'var' && open) { - var_mode = indent; - } - } - } - - function step_out(id, symbol) { - if (id) { - if (indent && indent.open) { - indent.at -= option.indent; - edge(); - } - advance(id, symbol); - } - if (indent) { - indent = indent.was; - } - } - -// Functions for conformance of whitespace. - - function one_space(left, right) { - left = left || token; - right = right || next_token; - if (right.id !== '(end)' && option.white && - (token.line !== right.line || - token.thru + 1 !== right.from)) { - warn('expected_space_a_b', right, token.value, right.value); - } - } - - function one_space_only(left, right) { - left = left || token; - right = right || next_token; - if (right.id !== '(end)' && (left.line !== right.line || - (option.white && left.thru + 1 !== right.from))) { - warn('expected_space_a_b', right, left.value, right.value); - } - } - - function no_space(left, right) { - left = left || token; - right = right || next_token; - if ((option.white || xmode === 'styleproperty' || xmode === 'style') && - left.thru !== right.from && left.line === right.line) { - warn('unexpected_space_a_b', right, left.value, right.value); - } - } - - function no_space_only(left, right) { - left = left || token; - right = right || next_token; - if (right.id !== '(end)' && (left.line !== right.line || - (option.white && left.thru !== right.from))) { - warn('unexpected_space_a_b', right, left.value, right.value); - } - } - - function spaces(left, right) { - if (option.white) { - left = left || token; - right = right || next_token; - if (left.thru === right.from && left.line === right.line) { - warn('missing_space_a_b', right, left.value, right.value); - } - } - } - - function comma() { - if (next_token.id !== ',') { - warn_at('expected_a_b', token.line, token.thru, ',', next_token.value); - } else { - if (option.white) { - no_space_only(); - } - advance(','); - discard(); - spaces(); - } - } - - - function semicolon() { - if (next_token.id !== ';') { - warn_at('expected_a_b', token.line, token.thru, ';', next_token.value); - } else { - if (option.white) { - no_space_only(); - } - advance(';'); - discard(); - if (semicolon_coda[next_token.id] !== true) { - spaces(); - } - } - } - - function use_strict() { - if (next_token.value === 'use strict') { - if (strict_mode) { - warn('unnecessary_use'); - } - edge(); - advance(); - semicolon(); - strict_mode = true; - option.newcap = true; - option.undef = true; - return true; - } else { - return false; - } - } - - - function are_similar(a, b) { - if (a === b) { - return true; - } - if (Array.isArray(a)) { - if (Array.isArray(b) && a.length === b.length) { - var i; - for (i = 0; i < a.length; i += 1) { - if (!are_similar(a[i], b[i])) { - return false; - } - } - return true; - } - return false; - } - if (Array.isArray(b)) { - return false; - } - if (a.arity === b.arity && a.value === b.value) { - switch (a.arity) { - case 'prefix': - case 'suffix': - case undefined: - return are_similar(a.first, b.first); - case 'infix': - return are_similar(a.first, b.first) && - are_similar(a.second, b.second); - case 'ternary': - return are_similar(a.first, b.first) && - are_similar(a.second, b.second) && - are_similar(a.third, b.third); - case 'function': - case 'regexp': - return false; - default: - return true; - } - } else { - if (a.id === '.' && b.id === '[' && b.arity === 'infix') { - return a.second.value === b.second.value && b.second.arity === 'string'; - } else if (a.id === '[' && a.arity === 'infix' && b.id === '.') { - return a.second.value === b.second.value && a.second.arity === 'string'; - } - } - return false; - } - - -// This is the heart of JSLINT, the Pratt parser. In addition to parsing, it -// is looking for ad hoc lint patterns. We add .fud to Pratt's model, which is -// like .nud except that it is only used on the first token of a statement. -// Having .fud makes it much easier to define statement-oriented languages like -// JavaScript. I retained Pratt's nomenclature. - -// .nud Null denotation -// .fud First null denotation -// .led Left denotation -// lbp Left binding power -// rbp Right binding power - -// They are elements of the parsing method called Top Down Operator Precedence. - - function expression(rbp, initial) { - -// rbp is the right binding power. -// initial indicates that this is the first expression of a statement. - - var left; - if (next_token.id === '(end)') { - stop('unexpected_a', token, next_token.id); - } - advance(); - if (option.safe && typeof predefined[token.value] === 'boolean' && - (next_token.id !== '(' && next_token.id !== '.')) { - warn('adsafe', token); - } - if (initial) { - anonname = 'anonymous'; - funct['(verb)'] = token.value; - } - if (initial === true && token.fud) { - left = token.fud(); - } else { - if (token.nud) { - left = token.nud(); - } else { - if (next_token.arity === 'number' && token.id === '.') { - warn('leading_decimal_a', token, - next_token.value); - advance(); - return token; - } else { - stop('expected_identifier_a', token, token.id); - } - } - while (rbp < next_token.lbp) { - advance(); - if (token.led) { - left = token.led(left); - } else { - stop('expected_operator_a', token, token.id); - } - } - } - return left; - } - - -// Functional constructors for making the symbols that will be inherited by -// tokens. - - function symbol(s, p) { - var x = syntax[s]; - if (!x || typeof x !== 'object') { - syntax[s] = x = { - id: s, - lbp: p, - value: s - }; - } - return x; - } - - - function delim(s) { - return symbol(s, 0); - } - - - function postscript(x) { - x.postscript = true; - return x; - } - - function ultimate(s) { - var x = symbol(s, 0); - x.from = 1; - x.thru = 1; - x.line = 0; - x.edge = true; - s.value = s; - return postscript(x); - } - - - function stmt(s, f) { - var x = delim(s); - x.identifier = x.reserved = true; - x.fud = f; - return x; - } - - function labeled_stmt(s, f) { - var x = stmt(s, f); - x.labeled = true; - } - - function disrupt_stmt(s, f) { - var x = stmt(s, f); - x.disrupt = true; - } - - - function reserve_name(x) { - var c = x.id.charAt(0); - if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) { - x.identifier = x.reserved = true; - } - return x; - } - - - function prefix(s, f) { - var x = symbol(s, 150); - reserve_name(x); - x.nud = (typeof f === 'function') ? f : function () { - if (s === 'typeof') { - one_space(); - } else { - no_space_only(); - } - this.first = expression(150); - this.arity = 'prefix'; - if (this.id === '++' || this.id === '--') { - if (option.plusplus) { - warn('unexpected_a', this); - } else if ((!this.first.identifier || this.first.reserved) && - this.first.id !== '.' && this.first.id !== '[') { - warn('bad_operand', this); - } - } - return this; - }; - return x; - } - - - function type(s, arity, nud) { - var x = delim(s); - x.arity = arity; - if (nud) { - x.nud = nud; - } - return x; - } - - - function reserve(s, f) { - var x = delim(s); - x.identifier = x.reserved = true; - if (typeof f === 'function') { - x.nud = f; - } - return x; - } - - - function reservevar(s, v) { - return reserve(s, function () { - if (typeof v === 'function') { - v(this); - } - return this; - }); - } - - - function infix(s, p, f, w) { - var x = symbol(s, p); - reserve_name(x); - x.led = function (left) { - this.arity = 'infix'; - if (!w) { - spaces(prev_token, token); - spaces(); - } - if (typeof f === 'function') { - return f(left, this); - } else { - this.first = left; - this.second = expression(p); - return this; - } - }; - return x; - } - - function expected_relation(node, message) { - if (node.assign) { - warn(message || bundle.conditional_assignment, node); - } - return node; - } - - function expected_condition(node, message) { - switch (node.id) { - case '[': - case '-': - if (node.arity !== 'infix') { - warn(message || bundle.weird_condition, node); - } - break; - case 'false': - case 'function': - case 'Infinity': - case 'NaN': - case 'null': - case 'true': - case 'undefined': - case 'void': - case '(number)': - case '(regexp)': - case '(string)': - case '{': - warn(message || bundle.weird_condition, node); - break; - case '(': - if (node.first.id === '.' && numbery[node.first.second.value] === true) { - warn(message || bundle.weird_condition, node); - } - break; - } - return node; - } - - function check_relation(node) { - switch (node.arity) { - case 'prefix': - switch (node.id) { - case '{': - case '[': - warn('unexpected_a', node); - break; - case '!': - warn('confusing_a', node); - break; - } - break; - case 'function': - case 'regexp': - warn('unexpected_a', node); - break; - default: - if (node.id === 'NaN') { - warn('isnan', node); - } - } - return node; - } - - - function relation(s, eqeq) { - var x = infix(s, 100, function (left, that) { - check_relation(left); - if (eqeq) { - warn('expected_a_b', that, eqeq, that.id); - } - var right = expression(100); - if (are_similar(left, right) || - ((left.arity === 'string' || left.arity === 'number') && - (right.arity === 'string' || right.arity === 'number'))) { - warn('weird_relation', that); - } - that.first = left; - that.second = check_relation(right); - return that; - }); - return x; - } - - - function assignop(s, bit) { - var x = infix(s, 20, function (left, that) { - var l; - if (option.bitwise && bit) { - warn('unexpected_a', that); - } - that.first = left; - if (funct[left.value] === false) { - warn('read_only', left); - } else if (left['function']) { - warn('a_function', left); - } - if (option.safe) { - l = left; - do { - if (typeof predefined[l.value] === 'boolean') { - warn('adsafe', l); - } - l = l.first; - } while (l); - } - if (left) { - if (left === syntax['function']) { - warn('identifier_function', token); - } - if (left.id === '.' || left.id === '[') { - if (!left.first || left.first.value === 'arguments') { - warn('bad_assignment', that); - } - that.second = expression(19); - if (that.id === '=' && are_similar(that.first, that.second)) { - warn('weird_assignment', that); - } - return that; - } else if (left.identifier && !left.reserved) { - if (funct[left.value] === 'exception') { - warn('assign_exception', left); - } - that.second = expression(19); - if (that.id === '=' && are_similar(that.first, that.second)) { - warn('weird_assignment', that); - } - return that; - } - } - stop('bad_assignment', that); - }); - x.assign = true; - return x; - } - - - function bitwise(s, p) { - return infix(s, p, function (left, that) { - if (option.bitwise) { - warn('unexpected_a', that); - } - that.first = left; - that.second = expression(p); - return that; - }); - } - - - function suffix(s) { - var x = symbol(s, 150); - x.led = function (left) { - no_space_only(prev_token, token); - if (option.plusplus) { - warn('unexpected_a', this); - } else if ((!left.identifier || left.reserved) && - left.id !== '.' && left.id !== '[') { - warn('bad_operand', this); - } - this.first = left; - this.arity = 'suffix'; - return this; - }; - return x; - } - - - function optional_identifier() { - if (next_token.identifier) { - advance(); - if (option.safe && banned[token.value]) { - warn('adsafe_a', token); - } else if (token.reserved && !option.es5) { - warn('expected_identifier_a_reserved', token); - } - return token.value; - } - } - - - function identifier() { - var i = optional_identifier(); - if (i) { - return i; - } - if (token.id === 'function' && next_token.id === '(') { - warn('name_function'); - } else { - stop('expected_identifier_a'); - } - } - - - function statement() { - -// Usually a statement starts a line. Exceptions include the var statement in the -// initialization part of a for statement, and an if after an else. - - var label, old_scope = scope, the_statement; - -// We don't like the empty statement. - - if (next_token.id === ';') { - warn('unexpected_a'); - semicolon(); - return; - } - -// Is this a labeled statement? - - if (next_token.identifier && !next_token.reserved && peek().id === ':') { - edge('label'); - label = next_token; - advance(); - discard(); - advance(':'); - discard(); - scope = Object.create(old_scope); - add_label(label.value, 'label'); - if (next_token.labeled !== true) { - warn('label_a_b', next_token, label.value, next_token.value); - } - if (jx.test(label.value + ':')) { - warn('url', label); - } - next_token.label = label; - } - -// Parse the statement. - - edge(); - step_in('statement'); - the_statement = expression(0, true); - if (the_statement) { - -// Look for the final semicolon. - - if (the_statement.arity === 'statement') { - if (the_statement.id === 'switch' || - (the_statement.block && the_statement.id !== 'do')) { - spaces(); - } else { - semicolon(); - } - } else { - -// If this is an expression statement, determine if it is acceptble. -// We do not like -// new Blah(); -// statments. If it is to be used at all, new should only be used to make -// objects, not side effects. The expression statements we do like do -// assignment or invocation or delete. - - if (the_statement.id === '(') { - if (the_statement.first.id === 'new') { - warn('bad_new'); - } - } else if (!the_statement.assign && - the_statement.id !== 'delete' && - the_statement.id !== '++' && - the_statement.id !== '--') { - warn('assignment_function_expression', token); - } - semicolon(); - } - } - step_out(); - scope = old_scope; - return the_statement; - } - - - function statements() { - var array = [], disruptor, the_statement; - -// A disrupt statement may not be followed by any other statement. -// If the last statement is disrupt, then the sequence is disrupt. - - while (next_token.postscript !== true) { - if (next_token.id === ';') { - warn('unexpected_a', next_token); - semicolon(); - } else { - if (disruptor) { - warn('unreachable_a_b', next_token, next_token.value, - disruptor.value); - disruptor = null; - } - the_statement = statement(); - if (the_statement) { - array.push(the_statement); - if (the_statement.disrupt) { - disruptor = the_statement; - array.disrupt = true; - } - } - } - } - return array; - } - - - function block(ordinary) { - -// array block is array sequence of statements wrapped in braces. -// ordinary is false for function bodies and try blocks. -// ordinary is true for if statements, while, etc. - - var array, - curly = next_token, - old_inblock = in_block, - old_scope = scope, - old_strict_mode = strict_mode; - - in_block = ordinary; - scope = Object.create(scope); - spaces(); - if (next_token.id === '{') { - advance('{'); - step_in(); - if (!ordinary && !use_strict() && !old_strict_mode && - option.strict && funct['(context)']['(global)']) { - warn('missing_use_strict'); - } - array = statements(); - strict_mode = old_strict_mode; - step_out('}', curly); - discard(); - } else if (!ordinary) { - stop('expected_a_b', next_token, '{', next_token.value); - } else { - warn('expected_a_b', next_token, '{', next_token.value); - array = [statement()]; - array.disrupt = array[0].disrupt; - } - funct['(verb)'] = null; - scope = old_scope; - in_block = old_inblock; - if (ordinary && array.length === 0) { - warn('empty_block'); - } - return array; - } - - - function tally_property(name) { - if (properties && typeof properties[name] !== 'boolean') { - warn('unexpected_property_a', token, name); - } - if (typeof member[name] === 'number') { - member[name] += 1; - } else { - member[name] = 1; - } - } - - - function note_implied(token) { - var name = token.value, line = token.line, a = implied[name]; - if (typeof a === 'function') { - a = false; - } - if (!a) { - a = [line]; - implied[name] = a; - } else if (a[a.length - 1] !== line) { - a.push(line); - } - } - - -// ECMAScript parser - - syntax['(identifier)'] = { - type: '(identifier)', - lbp: 0, - identifier: true, - nud: function () { - var variable = this.value, - site = scope[variable]; - if (typeof site === 'function') { - site = undefined; - } - -// The name is in scope and defined in the current function. - - if (funct === site) { - -// Change 'unused' to 'var', and reject labels. - - switch (funct[variable]) { - case 'error': - warn('unexpected_a', token); - funct[variable] = 'var'; - break; - case 'unused': - funct[variable] = 'var'; - break; - case 'unparam': - funct[variable] = 'parameter'; - break; - case 'unction': - funct[variable] = 'function'; - this['function'] = true; - break; - case 'function': - this['function'] = true; - break; - case 'label': - warn('a_label', token, variable); - break; - } - -// The name is not defined in the function. If we are in the global scope, -// then we have an undefined variable. - - } else if (funct['(global)']) { - if (typeof global[variable] === 'boolean') { - funct[variable] = global[variable]; - } else { - if (option.undef) { - warn('not_a_defined', token, variable); - } else { - note_implied(token); - } - } - -// If the name is already defined in the current -// function, but not as outer, then there is a scope error. - - } else { - switch (funct[variable]) { - case 'closure': - case 'function': - case 'var': - case 'unused': - warn('a_scope', token, variable); - break; - case 'label': - warn('a_label', token, variable); - break; - case 'outer': - case true: - case false: - break; - default: - -// If the name is defined in an outer function, make an outer entry, and if -// it was unused, make it var. - - if (typeof site === 'boolean') { - funct[variable] = site; - functions[0][variable] = true; - } else if (site === null) { - warn('a_not_allowed', token, variable); - note_implied(token); - } else if (typeof site !== 'object') { - if (option.undef) { - warn('a_not_defined', token, variable); - } else { - funct[variable] = true; - } - note_implied(token); - } else { - switch (site[variable]) { - case 'function': - case 'unction': - this['function'] = true; - site[variable] = 'closure'; - funct[variable] = site['(global)'] ? false : 'outer'; - break; - case 'var': - case 'unused': - site[variable] = 'closure'; - funct[variable] = site['(global)'] ? true : 'outer'; - break; - case 'closure': - case 'parameter': - funct[variable] = site['(global)'] ? true : 'outer'; - break; - case 'unparam': - site[variable] = 'parameter'; - funct[variable] = site['(global)'] ? false : 'outer'; - break; - case 'error': - warn('not_a_defined', token); - break; - case 'label': - warn('a_label', token, variable); - break; - } - } - } - } - return this; - }, - led: function () { - stop('expected_operator_a'); - } - }; - -// Build the syntax table by declaring the syntactic elements. - - type('(color)', 'color'); - type('(number)', 'number', return_this); - type('(string)', 'string', return_this); - type('(range)', 'range'); - type('(regexp)', 'regexp', return_this); - - ultimate('(begin)'); - ultimate('(end)'); - ultimate('(error)'); - postscript(delim(''); - postscript(delim('}')); - delim(')'); - delim(']'); - postscript(delim('"')); - postscript(delim('\'')); - delim(';'); - delim(':'); - delim(','); - delim('#'); - delim('@'); - delim('*/'); - postscript(reserve('case')); - reserve('catch'); - postscript(reserve('default')); - reserve('else'); - reserve('finally'); - - reservevar('arguments', function (x) { - if (strict_mode && funct['(global)']) { - warn('strict', x); - } else if (option.safe) { - warn('adsafe', x); - } - }); - reservevar('eval', function (x) { - if (option.safe) { - warn('adsafe', x); - } - }); - reservevar('false'); - reservevar('Infinity'); - reservevar('NaN'); - reservevar('null'); - reservevar('this', function (x) { - if (strict_mode && ((funct['(statement)'] && - funct['(name)'].charAt(0) > 'Z') || funct['(global)'])) { - warn('strict', x); - } else if (option.safe) { - warn('adsafe', x); - } - }); - reservevar('true'); - reservevar('undefined'); - - assignop('='); - assignop('+='); - assignop('-='); - assignop('*='); - assignop('/=').nud = function () { - stop('slash_equal'); - }; - assignop('%='); - assignop('&=', true); - assignop('|=', true); - assignop('^=', true); - assignop('<<=', true); - assignop('>>=', true); - assignop('>>>=', true); - - infix('?', 30, function (left, that) { - that.first = expected_condition(expected_relation(left)); - that.second = expression(0); - spaces(); - advance(':'); - discard(); - spaces(); - that.third = expression(10); - that.arity = 'ternary'; - if (are_similar(that.second, that.third)) { - warn('weird_ternary', that); - } - return that; - }); - - infix('||', 40, function (left, that) { - function paren_check(that) { - if (that.id === '&&' && !that.paren) { - warn('and', that); - } - return that; - } - - that.first = paren_check(expected_condition(expected_relation(left))); - that.second = paren_check(expected_relation(expression(40))); - if (are_similar(that.first, that.second)) { - warn('weird_condition', that); - } - return that; - }); - - infix('&&', 50, function (left, that) { - that.first = expected_condition(expected_relation(left)); - that.second = expected_relation(expression(50)); - if (are_similar(that.first, that.second)) { - warn('weird_condition', that); - } - return that; - }); - - prefix('void', function () { - this.first = expression(0); - if (this.first.arity !== 'number' || this.first.value) { - warn('unexpected_a', this); - return this; - } - return this; - }); - - bitwise('|', 70); - bitwise('^', 80); - bitwise('&', 90); - - relation('==', '==='); - relation('==='); - relation('!=', '!=='); - relation('!=='); - relation('<'); - relation('>'); - relation('<='); - relation('>='); - - bitwise('<<', 120); - bitwise('>>', 120); - bitwise('>>>', 120); - - infix('in', 120, function (left, that) { - warn('infix_in', that); - that.left = left; - that.right = expression(130); - return that; - }); - infix('instanceof', 120); - infix('+', 130, function (left, that) { - if (!left.value) { - if (left.arity === 'number') { - warn('unexpected_a', left); - } else if (left.arity === 'string') { - warn('expected_a_b', left, 'String', '\'\''); - } - } - var right = expression(130); - if (!right.value) { - if (right.arity === 'number') { - warn('unexpected_a', right); - } else if (right.arity === 'string') { - warn('expected_a_b', right, 'String', '\'\''); - } - } - if (left.arity === right.arity && - (left.arity === 'string' || left.arity === 'number')) { - left.value += right.value; - left.thru = right.thru; - if (left.arity === 'string' && jx.test(left.value)) { - warn('url', left); - } - discard(right); - discard(that); - return left; - } - that.first = left; - that.second = right; - return that; - }); - prefix('+', 'num'); - prefix('+++', function () { - warn('confusing_a', token); - this.first = expression(150); - this.arity = 'prefix'; - return this; - }); - infix('+++', 130, function (left) { - warn('confusing_a', token); - this.first = left; - this.second = expression(130); - return this; - }); - infix('-', 130, function (left, that) { - if ((left.arity === 'number' && left.value === 0) || left.arity === 'string') { - warn('unexpected_a', left); - } - var right = expression(130); - if ((right.arity === 'number' && right.value === 0) || right.arity === 'string') { - warn('unexpected_a', left); - } - if (left.arity === right.arity && left.arity === 'number') { - left.value -= right.value; - left.thru = right.thru; - discard(right); - discard(that); - return left; - } - that.first = left; - that.second = right; - return that; - }); - prefix('-'); - prefix('---', function () { - warn('confusing_a', token); - this.first = expression(150); - this.arity = 'prefix'; - return this; - }); - infix('---', 130, function (left) { - warn('confusing_a', token); - this.first = left; - this.second = expression(130); - return this; - }); - infix('*', 140, function (left, that) { - if ((left.arity === 'number' && (left.value === 0 || left.value === 1)) || left.arity === 'string') { - warn('unexpected_a', left); - } - var right = expression(140); - if ((right.arity === 'number' && (right.value === 0 || right.value === 1)) || right.arity === 'string') { - warn('unexpected_a', right); - } - if (left.arity === right.arity && left.arity === 'number') { - left.value *= right.value; - left.thru = right.thru; - discard(right); - discard(that); - return left; - } - that.first = left; - that.second = right; - return that; - }); - infix('/', 140, function (left, that) { - if ((left.arity === 'number' && left.value === 0) || left.arity === 'string') { - warn('unexpected_a', left); - } - var right = expression(140); - if ((right.arity === 'number' && (right.value === 0 || right.value === 1)) || right.arity === 'string') { - warn('unexpected_a', right); - } - if (left.arity === right.arity && left.arity === 'number') { - left.value /= right.value; - left.thru = right.thru; - discard(right); - discard(that); - return left; - } - that.first = left; - that.second = right; - return that; - }); - infix('%', 140, function (left, that) { - if ((left.arity === 'number' && (left.value === 0 || left.value === 1)) || left.arity === 'string') { - warn('unexpected_a', left); - } - var right = expression(140); - if ((right.arity === 'number' && right.value === 0) || right.arity === 'string') { - warn('unexpected_a', right); - } - if (left.arity === right.arity && left.arity === 'number') { - left.value %= right.value; - left.thru = right.thru; - discard(right); - discard(that); - return left; - } - that.first = left; - that.second = right; - return that; - }); - - suffix('++'); - prefix('++'); - - suffix('--'); - prefix('--'); - prefix('delete', function () { - one_space(); - var p = expression(0); - if (!p || (p.id !== '.' && p.id !== '[')) { - warn('deleted'); - } - this.first = p; - return this; - }); - - - prefix('~', function () { - no_space_only(); - if (option.bitwise) { - warn('unexpected_a', this); - } - expression(150); - return this; - }); - prefix('!', function () { - no_space_only(); - this.first = expected_condition(expression(150)); - this.arity = 'prefix'; - if (bang[this.first.id] === true) { - warn('confusing_a', this); - } - return this; - }); - prefix('typeof'); - prefix('new', function () { - one_space(); - var c = expression(160), i, p; - this.first = c; - if (c.id !== 'function') { - if (c.identifier) { - switch (c.value) { - case 'Object': - warn('use_object', token); - break; - case 'Array': - if (next_token.id === '(') { - p = next_token; - p.first = this; - advance('('); - if (next_token.id !== ')') { - p.second = expression(0); - if (p.second.arity !== 'number' || !p.second.value) { - expected_condition(p.second, bundle.use_array); - i = false; - } else { - i = true; - } - while (next_token.id !== ')' && next_token.id !== '(end)') { - if (i) { - warn('use_array', p); - i = false; - } - advance(); - } - } else { - warn('use_array', token); - } - advance(')', p); - discard(); - return p; - } - warn('use_array', token); - break; - case 'Number': - case 'String': - case 'Boolean': - case 'Math': - case 'JSON': - warn('not_a_constructor', c); - break; - case 'Function': - if (!option.evil) { - warn('function_eval'); - } - break; - case 'Date': - case 'RegExp': - break; - default: - if (c.id !== 'function') { - i = c.value.substr(0, 1); - if (option.newcap && (i < 'A' || i > 'Z')) { - warn('constructor_name_a', token); - } - } - } - } else { - if (c.id !== '.' && c.id !== '[' && c.id !== '(') { - warn('bad_constructor', token); - } - } - } else { - warn('weird_new', this); - } - if (next_token.id !== '(') { - warn('missing_a', next_token, '()'); - } - return this; - }); - - infix('(', 160, function (left, that) { - if (indent && indent.mode === 'expression') { - no_space(prev_token, token); - } else { - no_space_only(prev_token, token); - } - if (!left.immed && left.id === 'function') { - warn('wrap_immediate'); - } - var p = []; - if (left) { - if (left.identifier) { - if (left.value.match(/^[A-Z]([A-Z0-9_$]*[a-z][A-Za-z0-9_$]*)?$/)) { - if (left.value !== 'Number' && left.value !== 'String' && - left.value !== 'Boolean' && left.value !== 'Date') { - if (left.value === 'Math' || left.value === 'JSON') { - warn('not_a_function', left); - } else if (left.value === 'Object') { - warn('use_object', token); - } else if (left.value === 'Array' || option.newcap) { - warn('missing_a', left, 'new'); - } - } - } - } else if (left.id === '.') { - if (option.safe && left.first.value === 'Math' && - left.second === 'random') { - warn('adsafe', left); - } else if (left.second.value === 'split' && - left.first.id === '(string)') { - warn('use_array', left.second); - } - } - } - step_in(); - if (next_token.id !== ')') { - no_space(); - for (;;) { - edge(); - p.push(expression(10)); - if (next_token.id !== ',') { - break; - } - comma(); - } - } - no_space(); - step_out(')', that); - if (typeof left === 'object') { - if (left.value === 'parseInt' && p.length === 1) { - warn('radix', left); - } - if (!option.evil) { - if (left.value === 'eval' || left.value === 'Function' || - left.value === 'execScript') { - warn('evil', left); - } else if (p[0] && p[0].arity === 'string' && - (left.value === 'setTimeout' || - left.value === 'setInterval')) { - warn('implied_evil', left); - } - } - if (!left.identifier && left.id !== '.' && left.id !== '[' && - left.id !== '(' && left.id !== '&&' && left.id !== '||' && - left.id !== '?') { - warn('bad_invocation', left); - } - } - that.first = left; - that.second = p; - return that; - }, true); - - prefix('(', function () { - step_in('expression'); - discard(); - no_space(); - edge(); - if (next_token.id === 'function') { - next_token.immed = true; - } - var value = expression(0); - value.paren = true; - no_space(); - step_out(')', this); - discard(); - if (value.id === 'function') { - if (next_token.id === '(') { - warn('move_invocation'); - } else { - warn('bad_wrap', this); - } - } - return value; - }); - - infix('.', 170, function (left, that) { - no_space(prev_token, token); - no_space(); - var name = identifier(); - if (typeof name === 'string') { - tally_property(name); - } - that.first = left; - that.second = token; - if (left && left.value === 'arguments' && - (name === 'callee' || name === 'caller')) { - warn('avoid_a', left, 'arguments.' + name); - } else if (!option.evil && left && left.value === 'document' && - (name === 'write' || name === 'writeln')) { - warn('write_is_wrong', left); - } else if (option.adsafe) { - if (!adsafe_top && left.value === 'ADSAFE') { - if (name === 'id' || name === 'lib') { - warn('adsafe', that); - } else if (name === 'go') { - if (xmode !== 'script') { - warn('adsafe', that); - } else if (adsafe_went || next_token.id !== '(' || - peek(0).arity !== 'string' || - peek(0).value !== adsafe_id || - peek(1).id !== ',') { - stop('adsafe_a', that, 'go'); - } - adsafe_went = true; - adsafe_may = false; - } - } - adsafe_top = false; - } - if (!option.evil && (name === 'eval' || name === 'execScript')) { - warn('evil'); - } else if (option.safe) { - for (;;) { - if (banned[name] === true) { - warn('adsafe_a', token, name); - } - if (typeof predefined[left.value] !== 'boolean' || - next_token.id === '(') { - break; - } - if (standard_property[name] === true) { - if (next_token.id === '.') { - warn('adsafe', that); - } - break; - } - if (next_token.id !== '.') { - warn('adsafe', that); - break; - } - advance('.'); - token.first = that; - token.second = name; - that = token; - name = identifier(); - if (typeof name === 'string') { - tally_property(name); - } - } - } - return that; - }, true); - - infix('[', 170, function (left, that) { - no_space_only(prev_token, token); - no_space(); - step_in(); - edge(); - var e = expression(0), s; - if (e.arity === 'number') { - if (left.id === 'arguments') { - warn('use_param', left); - } - } else if (e.arity === 'string') { - if (option.safe && (banned[e.value] || - e.value.charAt(0) === '_' || e.value.slice(-1) === '_')) { - warn('adsafe_subscript_a', e); - } else if (!option.evil && - (e.value === 'eval' || e.value === 'execScript')) { - warn('evil', e); - } - tally_property(e.value); - if (!option.sub && ix.test(e.value)) { - s = syntax[e.value]; - if (!s || !s.reserved) { - warn('subscript', e); - } - } - } else if (option.safe) { - if (!((e.arity === 'prefix' && adsafe_prefix[e.id] === true) || - (e.arity === 'infix' && adsafe_infix[e.id] === true))) { - warn('adsafe_subscript_a', e); - } - } - step_out(']', that); - discard(); - no_space(prev_token, token); - that.first = left; - that.second = e; - return that; - }, true); - - prefix('[', function () { - this.arity = 'prefix'; - this.first = []; - step_in('array'); - while (next_token.id !== '(end)') { - while (next_token.id === ',') { - warn('unexpected_a', next_token); - advance(','); - discard(); - } - if (next_token.id === ']') { - break; - } - indent.wrap = false; - edge(); - this.first.push(expression(10)); - if (next_token.id === ',') { - comma(); - if (next_token.id === ']' && !option.es5) { - warn('unexpected_a', token); - break; - } - } else { - break; - } - } - step_out(']', this); - discard(); - return this; - }, 170); - - - function property_name() { - var id = optional_identifier(true); - if (!id) { - if (next_token.arity === 'string') { - id = next_token.value; - if (option.safe) { - if (banned[id]) { - warn('adsafe_a'); - } else if (id.charAt(0) === '_' || - id.charAt(id.length - 1) === '_') { - warn('dangling_a'); - } - } - advance(); - } else if (next_token.arity === 'number') { - id = next_token.value.toString(); - advance(); - } - } - return id; - } - - - function function_params() { - var id, paren = next_token, params = []; - advance('('); - step_in(); - discard(); - no_space(); - if (next_token.id === ')') { - no_space(); - step_out(')', paren); - discard(); - return; - } - for (;;) { - edge(); - id = identifier(); - params.push(token); - add_label(id, 'unparam'); - if (next_token.id === ',') { - comma(); - } else { - no_space(); - step_out(')', paren); - discard(); - return params; - } - } - } - - - function do_function(func, name) { - var old_properties = properties, - old_option = option, - old_global = global, - old_scope = scope; - funct = { - '(name)' : name || '\'' + anonname + '\'', - '(line)' : next_token.line, - '(context)' : funct, - '(breakage)' : 0, - '(loopage)' : 0, - '(scope)' : scope, - '(token)' : func - }; - properties = old_properties && Object.create(old_properties); - option = Object.create(old_option); - global = Object.create(old_global); - scope = Object.create(old_scope); - token.funct = funct; - functions.push(funct); - if (name) { - add_label(name, 'function'); - } - func.name = name || ''; - func.first = funct['(params)'] = function_params(); - one_space(); - func.block = block(false); - funct = funct['(context)']; - properties = old_properties; - option = old_option; - global = old_global; - scope = old_scope; - } - - - prefix('{', function () { - var get, i, j, name, p, set, seen = {}; - this.arity = 'prefix'; - this.first = []; - step_in(); - while (next_token.id !== '}') { - indent.wrap = false; - -// JSLint recognizes the ES5 extension for get/set in object literals, -// but requires that they be used in pairs. - - edge(); - if (next_token.value === 'get' && peek().id !== ':') { - if (!option.es5) { - warn('get_set'); - } - get = next_token; - advance('get'); - one_space_only(); - name = next_token; - i = property_name(); - if (!i) { - stop('missing_property'); - } - do_function(get, ''); - if (funct['(loopage)']) { - warn('function_loop', get); - } - p = get.first; - if (p) { - warn('parameter_a_get_b', p[0], p[0].value, i); - } - comma(); - set = next_token; - spaces(); - edge(); - advance('set'); - one_space_only(); - j = property_name(); - if (i !== j) { - stop('expected_a_b', token, i, j || next_token.value); - } - do_function(set, ''); - p = set.first; - if (!p || p.length !== 1) { - stop('parameter_set_a', set, 'value'); - } else if (p[0].value !== 'value') { - stop('expected_a_b', p[0], 'value', p[0].value); - } - name.first = [get, set]; - } else { - name = next_token; - i = property_name(); - if (typeof i !== 'string') { - stop('missing_property'); - } - advance(':'); - discard(); - spaces(); - name.first = expression(10); - } - this.first.push(name); - if (seen[i] === true) { - warn('duplicate_a', next_token, i); - } - seen[i] = true; - tally_property(i); - if (next_token.id !== ',') { - break; - } - for (;;) { - comma(); - if (next_token.id !== ',') { - break; - } - warn('unexpected_a', next_token); - } - if (next_token.id === '}' && !option.es5) { - warn('unexpected_a', token); - } - } - step_out('}', this); - discard(); - return this; - }); - - stmt('{', function () { - discard(); - warn('statement_block'); - this.arity = 'statement'; - this.block = statements(); - this.disrupt = this.block.disrupt; - advance('}', this); - discard(); - return this; - }); - - stmt('/*global', directive); - stmt('/*globals', directive); - stmt('/*jslint', directive); - stmt('/*member', directive); - stmt('/*members', directive); - stmt('/*property', directive); - stmt('/*properties', directive); - - stmt('var', function () { - -// JavaScript does not have block scope. It only has function scope. So, -// declaring a variable in a block can have unexpected consequences. - -// var.first will contain an array, the array containing name tokens -// and assignment tokens. - - var assign, id, name; - - if (funct['(onevar)'] && option.onevar) { - warn('combine_var'); - } else if (!funct['(global)']) { - funct['(onevar)'] = true; - } - this.arity = 'statement'; - this.first = []; - step_in('var'); - for (;;) { - name = next_token; - id = identifier(); - if (funct['(global)'] && predefined[id] === false) { - warn('redefinition_a', token, id); - } - add_label(id, 'error'); - - if (next_token.id === '=') { - assign = next_token; - assign.first = name; - spaces(); - advance('='); - spaces(); - if (next_token.id === 'undefined') { - warn('unnecessary_initialize', token, id); - } - if (peek(0).id === '=' && next_token.identifier) { - stop('var_a_not'); - } - assign.second = expression(0); - assign.arity = 'infix'; - this.first.push(assign); - } else { - this.first.push(name); - } - funct[id] = 'unused'; - if (next_token.id !== ',') { - break; - } - comma(); - indent.wrap = false; - if (var_mode && next_token.line === token.line && - this.first.length === 1) { - var_mode = false; - indent.open = false; - indent.at -= option.indent; - } - spaces(); - edge(); - } - var_mode = false; - step_out(); - return this; - }); - - stmt('function', function () { - one_space(); - if (in_block) { - warn('function_block', token); - } - var i = identifier(); - if (i) { - add_label(i, 'unction'); - no_space(); - } - do_function(this, i, true); - if (next_token.id === '(' && next_token.line === token.line) { - stop('function_statement'); - } - this.arity = 'statement'; - return this; - }); - - prefix('function', function () { - one_space(); - var i = optional_identifier(); - if (i) { - no_space(); - } - do_function(this, i); - if (funct['(loopage)']) { - warn('function_loop'); - } - this.arity = 'function'; - return this; - }); - - stmt('if', function () { - var paren = next_token; - one_space(); - advance('('); - step_in('control'); - discard(); - no_space(); - edge(); - this.arity = 'statement'; - this.first = expected_condition(expected_relation(expression(0))); - no_space(); - step_out(')', paren); - discard(); - one_space(); - this.block = block(true); - if (next_token.id === 'else') { - one_space(); - advance('else'); - discard(); - one_space(); - this['else'] = next_token.id === 'if' || next_token.id === 'switch' ? - statement(true) : block(true); - if (this['else'].disrupt && this.block.disrupt) { - this.disrupt = true; - } - } - return this; - }); - - stmt('try', function () { - -// try.first The catch variable -// try.second The catch clause -// try.third The finally clause -// try.block The try block - - var exception_variable, old_scope, paren; - if (option.adsafe) { - warn('adsafe_a', this); - } - one_space(); - this.arity = 'statement'; - this.block = block(false); - if (next_token.id === 'catch') { - one_space(); - advance('catch'); - discard(); - one_space(); - paren = next_token; - advance('('); - step_in('control'); - discard(); - no_space(); - edge(); - old_scope = scope; - scope = Object.create(old_scope); - exception_variable = next_token.value; - this.first = exception_variable; - if (!next_token.identifier) { - warn('expected_identifier_a', next_token); - } else { - add_label(exception_variable, 'exception'); - } - advance(); - no_space(); - step_out(')', paren); - discard(); - one_space(); - this.second = block(false); - scope = old_scope; - } - if (next_token.id === 'finally') { - discard(); - one_space(); - advance('finally'); - discard(); - one_space(); - this.third = block(false); - } else if (!this.second) { - stop('expected_a_b', next_token, 'catch', next_token.value); - } - return this; - }); - - labeled_stmt('while', function () { - one_space(); - var paren = next_token; - funct['(breakage)'] += 1; - funct['(loopage)'] += 1; - advance('('); - step_in('control'); - discard(); - no_space(); - edge(); - this.arity = 'statement'; - this.first = expected_relation(expression(0)); - if (this.first.id !== 'true') { - expected_condition(this.first, bundle.unexpected_a); - } - no_space(); - step_out(')', paren); - discard(); - one_space(); - this.block = block(true); - if (this.block.disrupt) { - warn('strange_loop', prev_token); - } - funct['(breakage)'] -= 1; - funct['(loopage)'] -= 1; - return this; - }); - - reserve('with'); - - labeled_stmt('switch', function () { - -// switch.first the switch expression -// switch.second the array of cases. A case is 'case' or 'default' token: -// case.first the array of case expressions -// case.second the array of statements -// If all of the arrays of statements are disrupt, then the switch is disrupt. - - var particular, - the_case = next_token, - unbroken = true; - funct['(breakage)'] += 1; - one_space(); - advance('('); - discard(); - no_space(); - step_in(); - this.arity = 'statement'; - this.first = expected_condition(expected_relation(expression(0))); - no_space(); - step_out(')', the_case); - discard(); - one_space(); - advance('{'); - step_in(); - this.second = []; - while (next_token.id === 'case') { - the_case = next_token; - the_case.first = []; - spaces(); - edge('case'); - advance('case'); - for (;;) { - one_space(); - particular = expression(0); - the_case.first.push(particular); - if (particular.id === 'NaN') { - warn('unexpected_a', particular); - } - no_space_only(); - advance(':'); - discard(); - if (next_token.id !== 'case') { - break; - } - spaces(); - edge('case'); - advance('case'); - discard(); - } - spaces(); - the_case.second = statements(); - if (the_case.second && the_case.second.length > 0) { - particular = the_case.second[the_case.second.length - 1]; - if (particular.disrupt) { - if (particular.id === 'break') { - unbroken = false; - } - } else { - warn('missing_a_after_b', next_token, 'break', 'case'); - } - } else { - warn('empty_case'); - } - this.second.push(the_case); - } - if (this.second.length === 0) { - warn('missing_a', next_token, 'case'); - } - if (next_token.id === 'default') { - spaces(); - the_case = next_token; - edge('case'); - advance('default'); - discard(); - no_space_only(); - advance(':'); - discard(); - spaces(); - the_case.second = statements(); - if (the_case.second && the_case.second.length > 0) { - particular = the_case.second[the_case.second.length - 1]; - if (unbroken && particular.disrupt && particular.id !== 'break') { - this.disrupt = true; - } - } - this.second.push(the_case); - } - funct['(breakage)'] -= 1; - spaces(); - step_out('}', this); - return this; - }); - - stmt('debugger', function () { - if (!option.debug) { - warn('unexpected_a', this); - } - this.arity = 'statement'; - return this; - }); - - labeled_stmt('do', function () { - funct['(breakage)'] += 1; - funct['(loopage)'] += 1; - one_space(); - this.arity = 'statement'; - this.block = block(true); - if (this.block.disrupt) { - warn('strange_loop', prev_token); - } - one_space(); - advance('while'); - discard(); - var paren = next_token; - one_space(); - advance('('); - step_in(); - discard(); - no_space(); - edge(); - this.first = expected_condition(expected_relation(expression(0)), bundle.unexpected_a); - no_space(); - step_out(')', paren); - discard(); - funct['(breakage)'] -= 1; - funct['(loopage)'] -= 1; - return this; - }); - - labeled_stmt('for', function () { - var blok, filter, ok = false, paren = next_token, the_in, value; - this.arity = 'statement'; - funct['(breakage)'] += 1; - funct['(loopage)'] += 1; - advance('('); - step_in('control'); - discard(); - spaces(this, paren); - no_space(); - if (next_token.id === 'var') { - stop('move_var'); - } - edge(); - if (peek(0).id === 'in') { - value = next_token; - switch (funct[value.value]) { - case 'unused': - funct[value.value] = 'var'; - break; - case 'var': - break; - default: - warn('bad_in_a', value); - } - advance(); - the_in = next_token; - advance('in'); - the_in.first = value; - the_in.second = expression(20); - step_out(')', paren); - discard(); - this.first = the_in; - blok = block(true); - if (!option.forin) { - if (blok.length === 1 && typeof blok[0] === 'object' && - blok[0].value === 'if' && !blok[0]['else']) { - filter = blok[0].first; - while (filter.id === '&&') { - filter = filter.first; - } - switch (filter.id) { - case '===': - case '!==': - ok = filter.first.id === '[' ? ( - filter.first.first.value === the_in.second.value && - filter.first.second.value === the_in.first.value - ) : ( - filter.first.id === 'typeof' && - filter.first.first.id === '[' && - filter.first.first.first.value === the_in.second.value && - filter.first.first.second.value === the_in.first.value - ); - break; - case '(': - ok = filter.first.id === '.' && (( - filter.first.first.value === the_in.second.value && - filter.first.second.value === 'hasOwnProperty' && - filter.second[0].value === the_in.first.value - ) || ( - filter.first.first.value === 'ADSAFE' && - filter.first.second.value === 'has' && - filter.second[0].value === the_in.second.value && - filter.second[1].value === the_in.first.value - ) || ( - filter.first.first.id === '.' && - filter.first.first.first.id === '.' && - filter.first.first.first.first.value === 'Object' && - filter.first.first.first.second.value === 'prototype' && - filter.first.first.second.value === 'hasOwnProperty' && - filter.first.second.value === 'call' && - filter.second[0].value === the_in.second.value && - filter.second[1].value === the_in.first.value - )); - break; - } - } - if (!ok) { - warn('for_if', this); - } - } - } else { - if (next_token.id !== ';') { - edge(); - this.first = []; - for (;;) { - this.first.push(expression(0, 'for')); - if (next_token.id !== ',') { - break; - } - comma(); - } - } - semicolon(); - if (next_token.id !== ';') { - edge(); - this.second = expected_relation(expression(0)); - if (this.second.id !== 'true') { - expected_condition(this.second, bundle.unexpected_a); - } - } - semicolon(token); - if (next_token.id === ';') { - stop('expected_a_b', next_token, ')', ';'); - } - if (next_token.id !== ')') { - this.third = []; - edge(); - for (;;) { - this.third.push(expression(0, 'for')); - if (next_token.id !== ',') { - break; - } - comma(); - } - } - no_space(); - step_out(')', paren); - discard(); - one_space(); - blok = block(true); - } - if (blok.disrupt) { - warn('strange_loop', prev_token); - } - this.block = blok; - funct['(breakage)'] -= 1; - funct['(loopage)'] -= 1; - return this; - }); - - disrupt_stmt('break', function () { - var label = next_token.value; - this.arity = 'statement'; - if (funct['(breakage)'] === 0) { - warn('unexpected_a', this); - } - if (next_token.identifier && token.line === next_token.line) { - one_space_only(); - if (funct[label] !== 'label') { - warn('not_a_label', next_token); - } else if (scope[label] !== funct) { - warn('not_a_scope', next_token); - } - this.first = next_token; - advance(); - } - return this; - }); - - disrupt_stmt('continue', function () { - if (!option['continue']) { - warn('unexpected_a', this); - } - var label = next_token.value; - this.arity = 'statement'; - if (funct['(breakage)'] === 0) { - warn('unexpected_a', this); - } - if (next_token.identifier && token.line === next_token.line) { - one_space_only(); - if (funct[label] !== 'label') { - warn('not_a_label', next_token); - } else if (scope[label] !== funct) { - warn('not_a_scope', next_token); - } - this.first = next_token; - advance(); - } - return this; - }); - - disrupt_stmt('return', function () { - this.arity = 'statement'; - if (next_token.id !== ';' && next_token.line === token.line) { - one_space_only(); - if (next_token.id === '/' || next_token.id === '(regexp)') { - warn('wrap_regexp'); - } - this.first = expression(20); - } - return this; - }); - - disrupt_stmt('throw', function () { - this.arity = 'statement'; - one_space_only(); - this.first = expression(20); - return this; - }); - - -// Superfluous reserved words - - reserve('class'); - reserve('const'); - reserve('enum'); - reserve('export'); - reserve('extends'); - reserve('import'); - reserve('super'); - -// Harmony reserved words - - reserve('let'); - reserve('yield'); - reserve('implements'); - reserve('interface'); - reserve('package'); - reserve('private'); - reserve('protected'); - reserve('public'); - reserve('static'); - - -// Parse JSON - - function json_value() { - - function json_object() { - var brace = next_token, object = {}; - advance('{'); - if (next_token.id !== '}') { - while (next_token.id !== '(end)') { - while (next_token.id === ',') { - warn('unexpected_a', next_token); - comma(); - } - if (next_token.arity !== 'string') { - warn('expected_string_a'); - } - if (object[next_token.value] === true) { - warn('duplicate_a'); - } else if (next_token.value === '__proto__') { - warn('dangling_a'); - } else { - object[next_token.value] = true; - } - advance(); - advance(':'); - json_value(); - if (next_token.id !== ',') { - break; - } - comma(); - if (next_token.id === '}') { - warn('unexpected_a', token); - break; - } - } - } - advance('}', brace); - } - - function json_array() { - var bracket = next_token; - advance('['); - if (next_token.id !== ']') { - while (next_token.id !== '(end)') { - while (next_token.id === ',') { - warn('unexpected_a', next_token); - comma(); - } - json_value(); - if (next_token.id !== ',') { - break; - } - comma(); - if (next_token.id === ']') { - warn('unexpected_a', token); - break; - } - } - } - advance(']', bracket); - } - - switch (next_token.id) { - case '{': - json_object(); - break; - case '[': - json_array(); - break; - case 'true': - case 'false': - case 'null': - case '(number)': - case '(string)': - advance(); - break; - case '-': - advance('-'); - no_space_only(); - advance('(number)'); - break; - default: - stop('unexpected_a'); - } - } - - -// CSS parsing. - - function css_name() { - if (next_token.identifier) { - advance(); - return true; - } - } - - - function css_number() { - if (next_token.id === '-') { - advance('-'); - no_space_only(); - } - if (next_token.arity === 'number') { - advance('(number)'); - return true; - } - } - - - function css_string() { - if (next_token.arity === 'string') { - advance(); - return true; - } - } - - function css_color() { - var i, number, paren, value; - if (next_token.identifier) { - value = next_token.value; - if (value === 'rgb' || value === 'rgba') { - advance(); - paren = next_token; - advance('('); - for (i = 0; i < 3; i += 1) { - if (i) { - comma(); - } - number = next_token.value; - if (next_token.arity !== 'number' || number < 0) { - warn('expected_positive_a', next_token); - advance(); - } else { - advance(); - if (next_token.id === '%') { - advance('%'); - if (number > 100) { - warn('expected_percent_a', token, number); - } - } else { - if (number > 255) { - warn('expected_small_a', token, number); - } - } - } - } - if (value === 'rgba') { - comma(); - number = +next_token.value; - if (next_token.arity !== 'number' || number < 0 || number > 1) { - warn('expected_fraction_a', next_token); - } - advance(); - if (next_token.id === '%') { - warn('unexpected_a'); - advance('%'); - } - } - advance(')', paren); - return true; - } else if (css_colorData[next_token.value] === true) { - advance(); - return true; - } - } else if (next_token.id === '(color)') { - advance(); - return true; - } - return false; - } - - - function css_length() { - if (next_token.id === '-') { - advance('-'); - no_space_only(); - } - if (next_token.arity === 'number') { - advance(); - if (next_token.arity !== 'string' && - css_lengthData[next_token.value] === true) { - no_space_only(); - advance(); - } else if (+token.value !== 0) { - warn('expected_linear_a'); - } - return true; - } - return false; - } - - - function css_line_height() { - if (next_token.id === '-') { - advance('-'); - no_space_only(); - } - if (next_token.arity === 'number') { - advance(); - if (next_token.arity !== 'string' && - css_lengthData[next_token.value] === true) { - no_space_only(); - advance(); - } - return true; - } - return false; - } - - - function css_width() { - if (next_token.identifier) { - switch (next_token.value) { - case 'thin': - case 'medium': - case 'thick': - advance(); - return true; - } - } else { - return css_length(); - } - } - - - function css_margin() { - if (next_token.identifier) { - if (next_token.value === 'auto') { - advance(); - return true; - } - } else { - return css_length(); - } - } - - function css_attr() { - if (next_token.identifier && next_token.value === 'attr') { - advance(); - advance('('); - if (!next_token.identifier) { - warn('expected_name_a'); - } - advance(); - advance(')'); - return true; - } - return false; - } - - - function css_comma_list() { - while (next_token.id !== ';') { - if (!css_name() && !css_string()) { - warn('expected_name_a'); - } - if (next_token.id !== ',') { - return true; - } - comma(); - } - } - - - function css_counter() { - if (next_token.identifier && next_token.value === 'counter') { - advance(); - advance('('); - advance(); - if (next_token.id === ',') { - comma(); - if (next_token.arity !== 'string') { - warn('expected_string_a'); - } - advance(); - } - advance(')'); - return true; - } - if (next_token.identifier && next_token.value === 'counters') { - advance(); - advance('('); - if (!next_token.identifier) { - warn('expected_name_a'); - } - advance(); - if (next_token.id === ',') { - comma(); - if (next_token.arity !== 'string') { - warn('expected_string_a'); - } - advance(); - } - if (next_token.id === ',') { - comma(); - if (next_token.arity !== 'string') { - warn('expected_string_a'); - } - advance(); - } - advance(')'); - return true; - } - return false; - } - - - function css_shape() { - var i; - if (next_token.identifier && next_token.value === 'rect') { - advance(); - advance('('); - for (i = 0; i < 4; i += 1) { - if (!css_length()) { - warn('expected_number_a'); - break; - } - } - advance(')'); - return true; - } - return false; - } - - - function css_url() { - var c, url; - if (next_token.identifier && next_token.value === 'url') { - next_token = lex.range('(', ')'); - url = next_token.value; - c = url.charAt(0); - if (c === '"' || c === '\'') { - if (url.slice(-1) !== c) { - warn('bad_url'); - } else { - url = url.slice(1, -1); - if (url.indexOf(c) >= 0) { - warn('bad_url'); - } - } - } - if (!url) { - warn('missing_url'); - } - if (option.safe && ux.test(url)) { - stop('adsafe_a', next_token, url); - } - urls.push(url); - advance(); - return true; - } - return false; - } - - - css_any = [css_url, function () { - for (;;) { - if (next_token.identifier) { - switch (next_token.value.toLowerCase()) { - case 'url': - css_url(); - break; - case 'expression': - warn('unexpected_a'); - advance(); - break; - default: - advance(); - } - } else { - if (next_token.id === ';' || next_token.id === '!' || - next_token.id === '(end)' || next_token.id === '}') { - return true; - } - advance(); - } - } - }]; - - - css_border_style = [ - 'none', 'dashed', 'dotted', 'double', 'groove', - 'hidden', 'inset', 'outset', 'ridge', 'solid' - ]; - - css_break = [ - 'auto', 'always', 'avoid', 'left', 'right' - ]; - - css_media = { - 'all': true, - 'braille': true, - 'embossed': true, - 'handheld': true, - 'print': true, - 'projection': true, - 'screen': true, - 'speech': true, - 'tty': true, - 'tv': true - }; - - css_overflow = [ - 'auto', 'hidden', 'scroll', 'visible' - ]; - - css_attribute_data = { - background: [ - true, 'background-attachment', 'background-color', - 'background-image', 'background-position', 'background-repeat' - ], - 'background-attachment': ['scroll', 'fixed'], - 'background-color': ['transparent', css_color], - 'background-image': ['none', css_url], - 'background-position': [ - 2, [css_length, 'top', 'bottom', 'left', 'right', 'center'] - ], - 'background-repeat': [ - 'repeat', 'repeat-x', 'repeat-y', 'no-repeat' - ], - 'border': [true, 'border-color', 'border-style', 'border-width'], - 'border-bottom': [ - true, 'border-bottom-color', 'border-bottom-style', - 'border-bottom-width' - ], - 'border-bottom-color': css_color, - 'border-bottom-style': css_border_style, - 'border-bottom-width': css_width, - 'border-collapse': ['collapse', 'separate'], - 'border-color': ['transparent', 4, css_color], - 'border-left': [ - true, 'border-left-color', 'border-left-style', 'border-left-width' - ], - 'border-left-color': css_color, - 'border-left-style': css_border_style, - 'border-left-width': css_width, - 'border-right': [ - true, 'border-right-color', 'border-right-style', - 'border-right-width' - ], - 'border-right-color': css_color, - 'border-right-style': css_border_style, - 'border-right-width': css_width, - 'border-spacing': [2, css_length], - 'border-style': [4, css_border_style], - 'border-top': [ - true, 'border-top-color', 'border-top-style', 'border-top-width' - ], - 'border-top-color': css_color, - 'border-top-style': css_border_style, - 'border-top-width': css_width, - 'border-width': [4, css_width], - bottom: [css_length, 'auto'], - 'caption-side' : ['bottom', 'left', 'right', 'top'], - clear: ['both', 'left', 'none', 'right'], - clip: [css_shape, 'auto'], - color: css_color, - content: [ - 'open-quote', 'close-quote', 'no-open-quote', 'no-close-quote', - css_string, css_url, css_counter, css_attr - ], - 'counter-increment': [ - css_name, 'none' - ], - 'counter-reset': [ - css_name, 'none' - ], - cursor: [ - css_url, 'auto', 'crosshair', 'default', 'e-resize', 'help', 'move', - 'n-resize', 'ne-resize', 'nw-resize', 'pointer', 's-resize', - 'se-resize', 'sw-resize', 'w-resize', 'text', 'wait' - ], - direction: ['ltr', 'rtl'], - display: [ - 'block', 'compact', 'inline', 'inline-block', 'inline-table', - 'list-item', 'marker', 'none', 'run-in', 'table', 'table-caption', - 'table-cell', 'table-column', 'table-column-group', - 'table-footer-group', 'table-header-group', 'table-row', - 'table-row-group' - ], - 'empty-cells': ['show', 'hide'], - 'float': ['left', 'none', 'right'], - font: [ - 'caption', 'icon', 'menu', 'message-box', 'small-caption', - 'status-bar', true, 'font-size', 'font-style', 'font-weight', - 'font-family' - ], - 'font-family': css_comma_list, - 'font-size': [ - 'xx-small', 'x-small', 'small', 'medium', 'large', 'x-large', - 'xx-large', 'larger', 'smaller', css_length - ], - 'font-size-adjust': ['none', css_number], - 'font-stretch': [ - 'normal', 'wider', 'narrower', 'ultra-condensed', - 'extra-condensed', 'condensed', 'semi-condensed', - 'semi-expanded', 'expanded', 'extra-expanded' - ], - 'font-style': [ - 'normal', 'italic', 'oblique' - ], - 'font-variant': [ - 'normal', 'small-caps' - ], - 'font-weight': [ - 'normal', 'bold', 'bolder', 'lighter', css_number - ], - height: [css_length, 'auto'], - left: [css_length, 'auto'], - 'letter-spacing': ['normal', css_length], - 'line-height': ['normal', css_line_height], - 'list-style': [ - true, 'list-style-image', 'list-style-position', 'list-style-type' - ], - 'list-style-image': ['none', css_url], - 'list-style-position': ['inside', 'outside'], - 'list-style-type': [ - 'circle', 'disc', 'square', 'decimal', 'decimal-leading-zero', - 'lower-roman', 'upper-roman', 'lower-greek', 'lower-alpha', - 'lower-latin', 'upper-alpha', 'upper-latin', 'hebrew', 'katakana', - 'hiragana-iroha', 'katakana-oroha', 'none' - ], - margin: [4, css_margin], - 'margin-bottom': css_margin, - 'margin-left': css_margin, - 'margin-right': css_margin, - 'margin-top': css_margin, - 'marker-offset': [css_length, 'auto'], - 'max-height': [css_length, 'none'], - 'max-width': [css_length, 'none'], - 'min-height': css_length, - 'min-width': css_length, - opacity: css_number, - outline: [true, 'outline-color', 'outline-style', 'outline-width'], - 'outline-color': ['invert', css_color], - 'outline-style': [ - 'dashed', 'dotted', 'double', 'groove', 'inset', 'none', - 'outset', 'ridge', 'solid' - ], - 'outline-width': css_width, - overflow: css_overflow, - 'overflow-x': css_overflow, - 'overflow-y': css_overflow, - padding: [4, css_length], - 'padding-bottom': css_length, - 'padding-left': css_length, - 'padding-right': css_length, - 'padding-top': css_length, - 'page-break-after': css_break, - 'page-break-before': css_break, - position: ['absolute', 'fixed', 'relative', 'static'], - quotes: [8, css_string], - right: [css_length, 'auto'], - 'table-layout': ['auto', 'fixed'], - 'text-align': ['center', 'justify', 'left', 'right'], - 'text-decoration': [ - 'none', 'underline', 'overline', 'line-through', 'blink' - ], - 'text-indent': css_length, - 'text-shadow': ['none', 4, [css_color, css_length]], - 'text-transform': ['capitalize', 'uppercase', 'lowercase', 'none'], - top: [css_length, 'auto'], - 'unicode-bidi': ['normal', 'embed', 'bidi-override'], - 'vertical-align': [ - 'baseline', 'bottom', 'sub', 'super', 'top', 'text-top', 'middle', - 'text-bottom', css_length - ], - visibility: ['visible', 'hidden', 'collapse'], - 'white-space': [ - 'normal', 'nowrap', 'pre', 'pre-line', 'pre-wrap', 'inherit' - ], - width: [css_length, 'auto'], - 'word-spacing': ['normal', css_length], - 'word-wrap': ['break-word', 'normal'], - 'z-index': ['auto', css_number] - }; - - function style_attribute() { - var v; - while (next_token.id === '*' || next_token.id === '#' || - next_token.value === '_') { - if (!option.css) { - warn('unexpected_a'); - } - advance(); - } - if (next_token.id === '-') { - if (!option.css) { - warn('unexpected_a'); - } - advance('-'); - if (!next_token.identifier) { - warn('expected_nonstandard_style_attribute'); - } - advance(); - return css_any; - } else { - if (!next_token.identifier) { - warn('expected_style_attribute'); - } else { - if (Object.prototype.hasOwnProperty.call(css_attribute_data, next_token.value)) { - v = css_attribute_data[next_token.value]; - } else { - v = css_any; - if (!option.css) { - warn('unrecognized_style_attribute_a'); - } - } - } - advance(); - return v; - } - } - - - function style_value(v) { - var i = 0, - n, - once, - match, - round, - start = 0, - vi; - switch (typeof v) { - case 'function': - return v(); - case 'string': - if (next_token.identifier && next_token.value === v) { - advance(); - return true; - } - return false; - } - for (;;) { - if (i >= v.length) { - return false; - } - vi = v[i]; - i += 1; - if (vi === true) { - break; - } else if (typeof vi === 'number') { - n = vi; - vi = v[i]; - i += 1; - } else { - n = 1; - } - match = false; - while (n > 0) { - if (style_value(vi)) { - match = true; - n -= 1; - } else { - break; - } - } - if (match) { - return true; - } - } - start = i; - once = []; - for (;;) { - round = false; - for (i = start; i < v.length; i += 1) { - if (!once[i]) { - if (style_value(css_attribute_data[v[i]])) { - match = true; - round = true; - once[i] = true; - break; - } - } - } - if (!round) { - return match; - } - } - } - - function style_child() { - if (next_token.arity === 'number') { - advance(); - if (next_token.value === 'n' && next_token.identifier) { - no_space_only(); - advance(); - if (next_token.id === '+') { - no_space_only(); - advance('+'); - no_space_only(); - advance('(number)'); - } - } - return; - } else { - if (next_token.identifier && - (next_token.value === 'odd' || next_token.value === 'even')) { - advance(); - return; - } - } - warn('unexpected_a'); - } - - function substyle() { - var v; - for (;;) { - if (next_token.id === '}' || next_token.id === '(end)' || - (xquote && next_token.id === xquote)) { - return; - } - while (next_token.id === ';') { - warn('unexpected_a'); - semicolon(); - } - v = style_attribute(); - advance(':'); - if (next_token.identifier && next_token.value === 'inherit') { - advance(); - } else { - if (!style_value(v)) { - warn('unexpected_a'); - advance(); - } - } - if (next_token.id === '!') { - advance('!'); - no_space_only(); - if (next_token.identifier && next_token.value === 'important') { - advance(); - } else { - warn('expected_a_b', - next_token, 'important', next_token.value); - } - } - if (next_token.id === '}' || next_token.id === xquote) { - warn('expected_a_b', next_token, ';', next_token.value); - } else { - semicolon(); - } - } - } - - function style_selector() { - if (next_token.identifier) { - if (!Object.prototype.hasOwnProperty.call(html_tag, option.cap ? - next_token.value.toLowerCase() : next_token.value)) { - warn('expected_tagname_a'); - } - advance(); - } else { - switch (next_token.id) { - case '>': - case '+': - advance(); - style_selector(); - break; - case ':': - advance(':'); - switch (next_token.value) { - case 'active': - case 'after': - case 'before': - case 'checked': - case 'disabled': - case 'empty': - case 'enabled': - case 'first-child': - case 'first-letter': - case 'first-line': - case 'first-of-type': - case 'focus': - case 'hover': - case 'last-child': - case 'last-of-type': - case 'link': - case 'only-of-type': - case 'root': - case 'target': - case 'visited': - advance(); - break; - case 'lang': - advance(); - advance('('); - if (!next_token.identifier) { - warn('expected_lang_a'); - } - advance(')'); - break; - case 'nth-child': - case 'nth-last-child': - case 'nth-last-of-type': - case 'nth-of-type': - advance(); - advance('('); - style_child(); - advance(')'); - break; - case 'not': - advance(); - advance('('); - if (next_token.id === ':' && peek(0).value === 'not') { - warn('not'); - } - style_selector(); - advance(')'); - break; - default: - warn('expected_pseudo_a'); - } - break; - case '#': - advance('#'); - if (!next_token.identifier) { - warn('expected_id_a'); - } - advance(); - break; - case '*': - advance('*'); - break; - case '.': - advance('.'); - if (!next_token.identifier) { - warn('expected_class_a'); - } - advance(); - break; - case '[': - advance('['); - if (!next_token.identifier) { - warn('expected_attribute_a'); - } - advance(); - if (next_token.id === '=' || next_token.value === '~=' || - next_token.value === '$=' || - next_token.value === '|=' || - next_token.id === '*=' || - next_token.id === '^=') { - advance(); - if (next_token.arity !== 'string') { - warn('expected_string_a'); - } - advance(); - } - advance(']'); - break; - default: - stop('expected_selector_a'); - } - } - } - - function style_pattern() { - if (next_token.id === '{') { - warn('expected_style_pattern'); - } - for (;;) { - style_selector(); - if (next_token.id === '= 0) { - warn('unexpected_char_a_b', token, v.charAt(x), a); - } - ids[u] = true; - } else if (a === 'class' || a === 'type' || a === 'name') { - x = v.search(qx); - if (x >= 0) { - warn('unexpected_char_a_b', token, v.charAt(x), a); - } - ids[u] = true; - } else if (a === 'href' || a === 'background' || - a === 'content' || a === 'data' || - a.indexOf('src') >= 0 || a.indexOf('url') >= 0) { - if (option.safe && ux.test(v)) { - stop('bad_url', next_token, v); - } - urls.push(v); - } else if (a === 'for') { - if (option.adsafe) { - if (adsafe_id) { - if (v.slice(0, adsafe_id.length) !== adsafe_id) { - warn('adsafe_prefix_a', next_token, adsafe_id); - } else if (!/^[A-Z]+_[A-Z]+$/.test(v)) { - warn('adsafe_bad_id'); - } - } else { - warn('adsafe_bad_id'); - } - } - } else if (a === 'name') { - if (option.adsafe && v.indexOf('_') >= 0) { - warn('adsafe_name_a', next_token, v); - } - } - } - - function do_tag(name, attribute) { - var i, tag = html_tag[name], script, x; - src = false; - if (!tag) { - stop( - bundle.unrecognized_tag_a, - next_token, - name === name.toLowerCase() ? name : name + ' (capitalization error)' - ); - } - if (stack.length > 0) { - if (name === 'html') { - stop('unexpected_a', token, name); - } - x = tag.parent; - if (x) { - if (x.indexOf(' ' + stack[stack.length - 1].name + ' ') < 0) { - stop('tag_a_in_b', token, name, x); - } - } else if (!option.adsafe && !option.fragment) { - i = stack.length; - do { - if (i <= 0) { - stop('tag_a_in_b', token, name, 'body'); - } - i -= 1; - } while (stack[i].name !== 'body'); - } - } - switch (name) { - case 'div': - if (option.adsafe && stack.length === 1 && !adsafe_id) { - warn('adsafe_missing_id'); - } - break; - case 'script': - xmode = 'script'; - advance('>'); - if (attribute.lang) { - warn('lang', token); - } - if (option.adsafe && stack.length !== 1) { - warn('adsafe_placement', token); - } - if (attribute.src) { - if (option.adsafe && (!adsafe_may || !approved[attribute.src])) { - warn('adsafe_source', token); - } - if (attribute.type) { - warn('type', token); - } - } else { - step_in(next_token.from); - edge(); - use_strict(); - adsafe_top = true; - script = statements(); - -// JSLint is also the static analyzer for ADsafe. See www.ADsafe.org. - - if (option.adsafe) { - if (adsafe_went) { - stop('adsafe_script', token); - } - if (script.length !== 1 || - aint(script[0], 'id', '(') || - aint(script[0].first, 'id', '.') || - aint(script[0].first.first, 'value', 'ADSAFE') || - aint(script[0].second[0], 'value', adsafe_id)) { - stop('adsafe_id_go'); - } - switch (script[0].first.second.value) { - case 'id': - if (adsafe_may || adsafe_went || - script[0].second.length !== 1) { - stop('adsafe_id', next_token); - } - adsafe_may = true; - break; - case 'go': - if (adsafe_went) { - stop('adsafe_go'); - } - if (script[0].second.length !== 2 || - aint(script[0].second[1], 'id', 'function') || - !script[0].second[1].first || - script[0].second[1].first.length !== 2 || - aint(script[0].second[1].first[0], 'value', 'dom') || - aint(script[0].second[1].first[1], 'value', 'lib')) { - stop('adsafe_go', next_token); - } - adsafe_went = true; - break; - default: - stop('adsafe_id_go'); - } - } - indent = null; - } - xmode = 'html'; - advance(''); - styles(); - xmode = 'html'; - advance(''; - } - - function html() { - var attribute, attributes, is_empty, name, old_white = option.white, - quote, tag_name, tag, wmode; - xmode = 'html'; - xquote = ''; - stack = null; - for (;;) { - switch (next_token.value) { - case '<': - xmode = 'html'; - advance('<'); - attributes = {}; - tag_name = next_token; - if (!tag_name.identifier) { - warn('bad_name_a', tag_name); - } - name = tag_name.value; - if (option.cap) { - name = name.toLowerCase(); - } - tag_name.name = name; - advance(); - if (!stack) { - stack = []; - do_begin(name); - } - tag = html_tag[name]; - if (typeof tag !== 'object') { - stop('unrecognized_tag_a', tag_name, name); - } - is_empty = tag.empty; - tag_name.type = name; - for (;;) { - if (next_token.id === '/') { - advance('/'); - if (next_token.id !== '>') { - warn('expected_a_b', next_token, '>', next_token.value); - } - break; - } - if (next_token.id && next_token.id.substr(0, 1) === '>') { - break; - } - if (!next_token.identifier) { - if (next_token.id === '(end)' || next_token.id === '(error)') { - warn('expected_a_b', next_token, '>', next_token.value); - } - warn('bad_name_a'); - } - option.white = true; - spaces(); - attribute = next_token.value; - option.white = old_white; - advance(); - if (!option.cap && attribute !== attribute.toLowerCase()) { - warn('attribute_case_a', token); - } - attribute = attribute.toLowerCase(); - xquote = ''; - if (Object.prototype.hasOwnProperty.call(attributes, attribute)) { - warn('duplicate_a', token, attribute); - } - if (attribute.slice(0, 2) === 'on') { - if (!option.on) { - warn('html_handlers'); - } - xmode = 'scriptstring'; - advance('='); - quote = next_token.id; - if (quote !== '"' && quote !== '\'') { - stop('expected_a_b', next_token, '"', next_token.value); - } - xquote = quote; - wmode = option.white; - option.white = false; - advance(quote); - use_strict(); - statements(); - option.white = wmode; - if (next_token.id !== quote) { - stop('expected_a_b', next_token, quote, next_token.value); - } - xmode = 'html'; - xquote = ''; - advance(quote); - tag = false; - } else if (attribute === 'style') { - xmode = 'scriptstring'; - advance('='); - quote = next_token.id; - if (quote !== '"' && quote !== '\'') { - stop('expected_a_b', next_token, '"', next_token.value); - } - xmode = 'styleproperty'; - xquote = quote; - advance(quote); - substyle(); - xmode = 'html'; - xquote = ''; - advance(quote); - tag = false; - } else { - if (next_token.id === '=') { - advance('='); - tag = next_token.value; - if (!next_token.identifier && - next_token.id !== '"' && - next_token.id !== '\'' && - next_token.arity !== 'string' && - next_token.arity !== 'number' && - next_token.id !== '(color)') { - warn('expected_attribute_value_a', token, attribute); - } - advance(); - } else { - tag = true; - } - } - attributes[attribute] = tag; - do_attribute(attribute, tag); - } - do_tag(name, attributes); - if (!is_empty) { - stack.push(tag_name); - } - xmode = 'outer'; - advance('>'); - break; - case '') { - stop('expected_a_b', next_token, '>', next_token.value); - } - xmode = 'outer'; - advance('>'); - break; - case '' || next_token.id === '(end)') { - break; - } - if (next_token.value.indexOf('--') >= 0) { - stop('unexpected_a', next_token, '--'); - } - if (next_token.value.indexOf('<') >= 0) { - stop('unexpected_a', next_token, '<'); - } - if (next_token.value.indexOf('>') >= 0) { - stop('unexpected_a', next_token, '>'); - } - } - xmode = 'outer'; - advance('>'); - break; - case '(end)': - return; - default: - if (next_token.id === '(end)') { - stop('missing_a', next_token, - ''); - } else { - advance(); - } - } - if (stack && stack.length === 0 && (option.adsafe || - !option.fragment || next_token.id === '(end)')) { - break; - } - } - if (next_token.id !== '(end)') { - stop('unexpected_a'); - } - } - - -// The actual JSLINT function itself. - - var itself = function (the_source, the_option) { - var i, keys, predef, tree; - JSLINT.comments = []; - JSLINT.errors = []; - JSLINT.tree = ''; - begin = older_token = prev_token = token = next_token = - Object.create(syntax['(begin)']); - predefined = Object.create(standard); - if (the_option) { - option = Object.create(the_option); - predef = option.predef; - if (predef) { - if (Array.isArray(predef)) { - for (i = 0; i < predef.length; i += 1) { - predefined[predef[i]] = true; - } - } else if (typeof predef === 'object') { - keys = Object.keys(predef); - for (i = 0; i < keys.length; i += 1) { - predefined[keys[i]] = !!predef[keys]; - } - } - } - if (option.adsafe) { - option.safe = true; - } - if (option.safe) { - option.browser = - option['continue'] = - option.css = - option.debug = - option.devel = - option.evil = - option.forin = - option.on = - option.rhino = - option.sub = - option.widget = - option.windows = false; - - option.nomen = - option.strict = - option.undef = true; - - predefined.Date = - predefined['eval'] = - predefined.Function = - predefined.Object = null; - - predefined.ADSAFE = - predefined.lib = false; - } - } else { - option = {}; - } - option.indent = +option.indent || 0; - option.maxerr = option.maxerr || 50; - adsafe_id = ''; - adsafe_may = adsafe_top = adsafe_went = false; - approved = {}; - if (option.approved) { - for (i = 0; i < option.approved.length; i += 1) { - approved[option.approved[i]] = option.approved[i]; - } - } else { - approved.test = 'test'; - } - tab = ''; - for (i = 0; i < option.indent; i += 1) { - tab += ' '; - } - global = Object.create(predefined); - scope = global; - funct = { - '(global)': true, - '(name)': '(global)', - '(scope)': scope, - '(breakage)': 0, - '(loopage)': 0 - }; - functions = [funct]; - - comments_off = false; - ids = {}; - implied = {}; - in_block = false; - indent = false; - json_mode = false; - lookahead = []; - member = {}; - properties = null; - prereg = true; - src = false; - stack = null; - strict_mode = false; - urls = []; - var_mode = false; - warnings = 0; - xmode = false; - lex.init(the_source); - - assume(); - - try { - advance(); - if (next_token.arity === 'number') { - stop('unexpected_a'); - } else if (next_token.value.charAt(0) === '<') { - html(); - if (option.adsafe && !adsafe_went) { - warn('adsafe_go', this); - } - } else { - switch (next_token.id) { - case '{': - case '[': - json_mode = true; - json_value(); - break; - case '@': - case '*': - case '#': - case '.': - case ':': - xmode = 'style'; - advance(); - if (token.id !== '@' || !next_token.identifier || - next_token.value !== 'charset' || token.line !== 1 || - token.from !== 1) { - stop('css'); - } - advance(); - if (next_token.arity !== 'string' && - next_token.value !== 'UTF-8') { - stop('css'); - } - advance(); - semicolon(); - styles(); - break; - - default: - if (option.adsafe && option.fragment) { - stop('expected_a_b', - next_token, '
', next_token.value); - } - -// If the first token is predef semicolon, ignore it. This is sometimes used when -// files are intended to be appended to files that may be sloppy. predef sloppy -// file may be depending on semicolon insertion on its last line. - - step_in(1); - if (next_token.id === ';') { - semicolon(); - } - if (next_token.value === 'use strict') { - warn('function_strict'); - use_strict(); - } - adsafe_top = true; - tree = statements(); - begin.first = tree; - JSLINT.tree = begin; - if (option.adsafe && (tree.length !== 1 || - aint(tree[0], 'id', '(') || - aint(tree[0].first, 'id', '.') || - aint(tree[0].first.first, 'value', 'ADSAFE') || - aint(tree[0].first.second, 'value', 'lib') || - tree[0].second.length !== 2 || - tree[0].second[0].arity !== 'string' || - aint(tree[0].second[1], 'id', 'function'))) { - stop('adsafe_lib'); - } - if (tree.disrupt) { - warn('weird_program', prev_token); - } - } - } - indent = null; - advance('(end)'); - } catch (e) { - if (e) { // `~ - JSLINT.errors.push({ - reason : e.message, - line : e.line || next_token.line, - character : e.character || next_token.from - }, null); - } - } - return JSLINT.errors.length === 0; - }; - - -// Data summary. - - itself.data = function () { - var data = {functions: []}, - function_data, - globals, - i, - implieds = [], - j, - kind, - members = [], - name, - the_function, - unused = []; - if (itself.errors.length) { - data.errors = itself.errors; - } - - if (json_mode) { - data.json = true; - } - - for (name in implied) { - if (Object.prototype.hasOwnProperty.call(implied, name)) { - implieds.push({ - name: name, - line: implied[name] - }); - } - } - if (implieds.length > 0) { - data.implieds = implieds; - } - - if (urls.length > 0) { - data.urls = urls; - } - - globals = Object.keys(functions[0]).filter(function (value) { - return value.charAt(0) !== '(' ? value : undefined; - }); - if (globals.length > 0) { - data.globals = globals; - } - - for (i = 1; i < functions.length; i += 1) { - the_function = functions[i]; - function_data = {}; - for (j = 0; j < functionicity.length; j += 1) { - function_data[functionicity[j]] = []; - } - for (name in the_function) { - if (Object.prototype.hasOwnProperty.call(the_function, name)) { - if (name.charAt(0) !== '(') { - kind = the_function[name]; - if (kind === 'unction' || - (kind === 'unparam' && !option.unparam)) { - kind = 'unused'; - } else if (typeof kind === 'boolean') { - kind = 'global'; - } - if (Array.isArray(function_data[kind])) { - function_data[kind].push(name); - if (kind === 'unused') { - unused.push({ - name: name, - line: the_function['(line)'], - 'function': the_function['(name)'] - }); - } - } - } - } - } - for (j = 0; j < functionicity.length; j += 1) { - if (function_data[functionicity[j]].length === 0) { - delete function_data[functionicity[j]]; - } - } - function_data.name = the_function['(name)']; - function_data.param = the_function['(params)']; - function_data.line = the_function['(line)']; - data.functions.push(function_data); - } - - if (unused.length > 0) { - data.unused = unused; - } - - members = []; - for (name in member) { - if (typeof member[name] === 'number') { - data.member = member; - break; - } - } - - return data; - }; - - - itself.report = function (errors_only) { - var data = itself.data(); - - var err, evidence, i, j, key, keys, length, mem = '', name, names, - output = [], snippets, the_function, warning; - - function detail(h, array) { - var comma_needed, i, singularity; - if (array) { - output.push('
' + h + ' '); - array = array.sort(); - for (i = 0; i < array.length; i += 1) { - if (array[i] !== singularity) { - singularity = array[i]; - output.push((comma_needed ? ', ' : '') + singularity); - comma_needed = true; - } - } - output.push('
'); - } - } - - if (data.errors || data.implieds || data.unused) { - err = true; - output.push('
Error:'); - if (data.errors) { - for (i = 0; i < data.errors.length; i += 1) { - warning = data.errors[i]; - if (warning) { - evidence = warning.evidence || ''; - output.push('

Problem' + (isFinite(warning.line) ? ' at line ' + - warning.line + ' character ' + warning.character : '') + - ': ' + warning.reason.entityify() + - '

' + - (evidence && (evidence.length > 80 ? evidence.slice(0, 77) + '...' : - evidence).entityify()) + '

'); - } - } - } - - if (data.implieds) { - snippets = []; - for (i = 0; i < data.implieds.length; i += 1) { - snippets[i] = '' + data.implieds[i].name + ' ' + - data.implieds[i].line + ''; - } - output.push('

Implied global: ' + snippets.join(', ') + '

'); - } - - if (data.unused) { - snippets = []; - for (i = 0; i < data.unused.length; i += 1) { - snippets[i] = '' + data.unused[i].name + ' ' + - data.unused[i].line + ' ' + - data.unused[i]['function'] + ''; - } - output.push('

Unused variable: ' + snippets.join(', ') + '

'); - } - if (data.json) { - output.push('

JSON: bad.

'); - } - output.push('
'); - } - - if (!errors_only) { - - output.push('
'); - - if (data.urls) { - detail("URLs
", data.urls, '
'); - } - - if (xmode === 'style') { - output.push('

CSS.

'); - } else if (data.json && !err) { - output.push('

JSON: good.

'); - } else if (data.globals) { - output.push('
Global ' + - data.globals.sort().join(', ') + '
'); - } else { - output.push('
No new global variables introduced.
'); - } - - for (i = 0; i < data.functions.length; i += 1) { - the_function = data.functions[i]; - names = []; - if (the_function.param) { - for (j = 0; j < the_function.param.length; j += 1) { - names[j] = the_function.param[j].value; - } - } - output.push('
' + the_function.line + ' ' + - (the_function.name || '') + '(' + names.join(', ') + ')
'); - detail('Unused', the_function.unused); - detail('Closure', the_function.closure); - detail('Variable', the_function['var']); - detail('Exception', the_function.exception); - detail('Outer', the_function.outer); - detail('Global', the_function.global); - detail('Label', the_function.label); - } - - if (data.member) { - keys = Object.keys(data.member); - if (keys.length) { - keys = keys.sort(); - mem = '
/*properties ';
-                    length = 13;
-                    for (i = 0; i < keys.length; i += 1) {
-                        key = keys[i];
-                        name = ix.test(key) ? key :
-                            '"' + key.entityify().replace(nx, sanitize) + '"';
-                        if (length + name.length > 72) {
-                            output.push(mem + '
'); - mem = ' '; - length = 1; - } - length += name.length + 2; - if (data.member[key] === 1) { - name = '' + name + ''; - } - if (i < keys.length - 1) { - name += ', '; - } - mem += name; - } - output.push(mem + '
*/
'); - } - output.push('
'); - } - } - return output.join(''); - }; - itself.jslint = itself; - - itself.edition = '2011-05-01'; - - return itself; - -}());// v8.js -// 2009-09-11: Based on Douglas Crockford's Rhino edition -// -// I've made a few changes, specifically the ability to parse -// one file, while displaying the name of another. -// - -/*global JSLINT */ -/*jslint rhino: true, strict: false, onevar: false, white: false, browser: true, - laxbreak: true, undef: true, nomen: false, eqeqeq: true, plusplus: false, bitwise: true,regexp: true, newcap: true, immed: true */ - -(function ( argv ) { - var e, i, input, fileToParse, fileToDisplay, defaults, - sys = require( "sys" ), - fs = require( "fs" ); - - argv.shift(); // drop "node" - argv.shift(); // drop this script's name - - if ( !argv[ 0 ] ) { - sys.puts("Usage: jslint.js file.js [realfilename.js]"); - process.exit( 1 ); - } - - fileToParse = argv[ 0 ]; - fileToDisplay = argv[ 1 ] ? argv[ 1 ] : argv[ 0 ]; - - input = fs.readFile( fileToParse, function ( err, data ) { - if ( err || !data ) { - sys.puts("jslint: Couldn't open file '" + fileToParse + "'."); - process.exit(1); - } - - defaults = { - bitwise: true, // Allow bitwise operators - browser: true, // Assume a browser ( http://www.JSLint.com/lint.html#browser ) - css: true, // Tolerate CSS workarounds ( http://www.JSLint.com/lint.html#css ) - eqeqeq: true, // Require `===` && `!==` - immed: true, // Immediate invocations must be wrapped in parens. - laxbreak: true, // Tolerate "sloppy" line breaks - newcap: true, // Require initial caps for constructors ( http://www.JSLint.com/lint.html#new ) - nomen: false, // Allow dangling `_` in identifiers - onevar: false, // Allow multiple `var` statements. - plusplus: false, // Allow `++` and `--` - regexp: true, // Disallow `.` and `[^...]` in regex - strict: false, // Don't require `use strict;` - undef: true, // Disallow undeclared variables ( http://www.JSLint.com/lint.html#undefined ) - white: false // Don't apply strict whitespace rules - }; - - if ( !JSLINT( data.toString(), defaults ) ) { - for ( i = 0, numErrors = JSLINT.errors.length; i < numErrors; i += 1 ) { - e = JSLINT.errors[ i ]; - if ( e ) { - sys.puts( - '[' + fileToDisplay + '] Lint at line ' + e.line + ' character ' + - e.character + ': ' + e.reason - ); - sys.puts( - ( e.evidence || '' ).replace( /^\s+|\s+$/, "" ) - ); - } - } - process.exit( 2 ); - } else { - sys.puts("jslint: No problems found in " + fileToDisplay); - process.exit( 0 ); - } - } ); -}( process.ARGV ) ); diff --git a/lib/rhino.js b/lib/rhino.js deleted file mode 100644 index fd7fce3..0000000 --- a/lib/rhino.js +++ /dev/null @@ -1,58 +0,0 @@ -// rhino.js -// 2009-09-11: Based on Douglas Crockford's Rhino edition -// -// I've made a few changes, specifically the ability to parse -// one file, while displaying the name of another. -// - -/*global JSLINT */ -/*jslint rhino: true, strict: false, onevar: false, white: false, browser: true, - laxbreak: true, undef: true, nomen: false, eqeqeq: true, plusplus: false, bitwise: true,regexp: true, newcap: true, immed: true */ - -(function (a) { - var e, i, input, fileToParse, fileToDisplay, defaults; - if (!a[0]) { - print("Usage: jslint.js file.js [realfilename.js]"); - quit(1); - } - fileToParse = a[ 0 ]; - fileToDisplay = a[ 1 ] ? a[ 1 ] : a[ 0 ]; - input = readFile( fileToParse ); - if (!input) { - print("jslint: Couldn't open file '" + fileToParse + "'."); - quit(1); - } - defaults = { - bitwise: true, // Allow bitwise operators - browser: true, // Assume a browser ( http://www.JSLint.com/lint.html#browser ) - css: true, // Tolerate CSS workarounds ( http://www.JSLint.com/lint.html#css ) - eqeqeq: true, // Require `===` && `!==` - immed: true, // Immediate invocations must be wrapped in parens. - laxbreak: true, // Tolerate "sloppy" line breaks - newcap: true, // Require initial caps for constructors ( http://www.JSLint.com/lint.html#new ) - nomen: false, // Allow dangling `_` in identifiers - onevar: false, // Allow multiple `var` statements. - plusplus: false, // Allow `++` and `--` - regexp: true, // Disallow `.` and `[^...]` in regex - strict: false, // Don't require `use strict;` - undef: true, // Disallow undeclared variables ( http://www.JSLint.com/lint.html#undefined ) - white: false // Don't apply strict whitespace rules - }; - - if (!JSLINT(input, defaults)) { - for (i = 0; i < JSLINT.errors.length; i += 1) { - e = JSLINT.errors[i]; - if (e) { - print('[' + fileToDisplay + '] Lint at line ' + e.line + ' character ' + - e.character + ': ' + e.reason); - print((e.evidence || ''). - replace(/^\s*(\S*(\s+\S+)*)\s*$/, "$1")); - print(''); - } - } - quit(2); - } else { - print("jslint: No problems found in " + fileToDisplay); - quit(); - } -}(arguments)); diff --git a/lib/rhinoed_jslint.js b/lib/rhinoed_jslint.js deleted file mode 100644 index 763fab5..0000000 --- a/lib/rhinoed_jslint.js +++ /dev/null @@ -1,6723 +0,0 @@ -// jslint.js -// 2011-05-01 - -// 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 global 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 JSON text, or a CSS text. - -// The second parameter is an optional object of options that control the -// operation of JSLINT. Most of the options are booleans: They are all -// optional and have a default value of false. One of the options, predef, -// can be an array of names, which will be used to declare global variables, -// or an object whose keys are used as global names, with a boolean value -// that determines if they are assignable. - -// If it checks out, 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 properties: - -// { -// line : The line (relative to 0) at which the lint was found -// character : The character (relative to 0) at which the lint was found -// reason : The problem -// evidence : The text line in which the problem occurred -// raw : The raw message before the details were inserted -// a : The first detail -// b : The second detail -// c : The third detail -// d : The fourth detail -// } - -// If a stopping error was found, a null will be the last element of the -// JSLINT.errors array. A stopping error means that JSLint was not confident -// enough to continue. It does not necessarily mean that the error was -// especially heinous. - -// You can request a Function Report, which shows all of the functions -// and the parameters and vars that they use. This can be used to find -// implied global variables and other problems. The report is in HTML and -// can be inserted in an HTML . - -// var myReport = JSLINT.report(errors_only); - -// If errors_only is true, then the report will be limited to only errors. - -// You can request a data structure that contains JSLint's results. - -// var myData = JSLINT.data(); - -// It returns a structure with this form: - -// { -// errors: [ -// { -// line: NUMBER, -// character: NUMBER, -// reason: STRING, -// evidence: STRING -// } -// ], -// functions: [ -// name: STRING, -// line: NUMBER, -// last: NUMBER, -// param: [ -// TOKEN -// ], -// closure: [ -// STRING -// ], -// var: [ -// STRING -// ], -// exception: [ -// STRING -// ], -// outer: [ -// STRING -// ], -// unused: [ -// STRING -// ], -// global: [ -// STRING -// ], -// label: [ -// STRING -// ] -// ], -// globals: [ -// STRING -// ], -// member: { -// STRING: NUMBER -// }, -// unuseds: [ -// { -// name: STRING, -// line: NUMBER -// } -// ], -// implieds: [ -// { -// name: STRING, -// line: NUMBER -// } -// ], -// urls: [ -// STRING -// ], -// json: BOOLEAN -// } - -// Empty arrays will not be included. - -// You can obtain the parse tree that JSLint constructed while parsing. The -// latest tree is kept in JSLINT.tree. A nice stringication can be produced -// with - -// JSON.stringify(JSLINT.tree, [ -// 'value', 'arity', 'name', 'first', -// 'second', 'third', 'block', 'else' -// ], 4)); - -// JSLint provides three directives. They look like slashstar comments, and -// allow for setting options, declaring global variables, and establishing a -// set of allowed property names. - -// These directives respect function scope. - -// The jslint directive is a special comment that can set one or more options. -// The current option set is - -// adsafe true, if ADsafe rules should be enforced -// bitwise true, if bitwise operators should not be allowed -// browser true, if the standard browser globals should be predefined -// cap true, if upper case HTML should be allowed -// 'continue' true, if the continuation statement should be tolerated -// css true, if CSS workarounds should be tolerated -// debug true, if debugger statements should be allowed -// devel true, if logging should be allowed (console, alert, etc.) -// es5 true, if ES5 syntax should be allowed -// evil true, if eval should be allowed -// forin true, if for in statements need not filter -// fragment true, if HTML fragments should be allowed -// indent the indentation factor -// maxerr the maximum number of errors to allow -// maxlen the maximum length of a source line -// newcap true, if constructor names must be capitalized -// node true, if Node.js globals should be predefined -// nomen true, if names should be checked -// on true, if HTML event handlers should be allowed -// onevar true, if only one var statement per function should be allowed -// passfail true, if the scan should stop on first error -// plusplus true, if increment/decrement should not be allowed -// regexp true, if the . should not be allowed in regexp literals -// rhino true, if the Rhino environment globals should be predefined -// undef true, if variables should be declared before used -// unparam true, if unused parameters should be tolerated -// safe true, if use of some browser features should be restricted -// windows true, if MS Windows-specific globals should be predefined -// strict true, require the 'use strict'; pragma -// sub true, if all forms of subscript notation are tolerated -// white true, if strict whitespace rules apply -// widget true if the Yahoo Widgets globals should be predefined - -// For example: - -/*jslint - evil: true, nomen: false, onevar: false, regexp: false, strict: true -*/ - -// The properties directive declares an exclusive list of property names. -// Any properties named in the program that are not in the list will -// produce a warning. - -// For example: - -/*properties "\b", "\t", "\n", "\f", "\r", "!=", "!==", "\"", "%", - "&", "'", "(begin)", "(breakage)", "(context)", "(error)", - "(global)", "(identifier)", "(line)", "(loopage)", "(name)", "(onevar)", - "(params)", "(scope)", "(statement)", "(token)", "(verb)", ")", "*", - "+", "-", "/", ";", "<", "<<", "<=", "==", "===", ">", - ">=", ">>", ">>>", ADSAFE, ActiveXObject, Array, - Boolean, Buffer, COM, CScript, Canvas, CustomAnimation, Date, Debug, E, - Enumerator, Error, EvalError, FadeAnimation, Flash, FormField, Frame, - Function, HotKey, Image, JSON, LN10, LN2, LOG10E, LOG2E, MAX_VALUE, - MIN_VALUE, Math, MenuItem, MoveAnimation, NEGATIVE_INFINITY, Number, - Object, Option, PI, POSITIVE_INFINITY, Point, RangeError, Rectangle, - ReferenceError, RegExp, ResizeAnimation, RotateAnimation, SQRT1_2, - SQRT2, ScrollBar, Storage, String, Style, SyntaxError, System, Text, - TextArea, Timer, TypeError, URIError, URL, VBArray, WScript, Web, - Window, XMLDOM, XMLHttpRequest, "\\", "^", __dirname, __filename, a, - a_function, a_label, a_not_allowed, a_not_defined, a_scope, abbr, - acronym, activeborder, activecaption, address, adsafe, adsafe_a, - adsafe_autocomplete, adsafe_bad_id, adsafe_div, adsafe_fragment, - adsafe_go, adsafe_html, adsafe_id, adsafe_id_go, adsafe_lib, - adsafe_lib_second, adsafe_missing_id, adsafe_name_a, adsafe_placement, - adsafe_prefix_a, adsafe_script, adsafe_source, adsafe_subscript_a, - adsafe_tag, alert, aliceblue, all, already_defined, and, animator, - antiquewhite, appleScript, applet, apply, approved, appworkspace, aqua, - aquamarine, area, arguments, arity, article, aside, assign, - assign_exception, assignment_function_expression, at, attribute_case_a, - audio, autocomplete, avoid_a, azure, b, background, - "background-attachment", "background-color", "background-image", - "background-position", "background-repeat", bad_assignment, bad_color_a, - bad_constructor, bad_entity, bad_html, bad_id_a, bad_in_a, - bad_invocation, bad_name_a, bad_new, bad_number, bad_operand, bad_type, - bad_url, bad_wrap, base, bdo, beep, beige, big, bisque, bitwise, black, - blanchedalmond, block, blockquote, blue, blueviolet, body, border, - "border-bottom", "border-bottom-color", "border-bottom-style", - "border-bottom-width", "border-collapse", "border-color", "border-left", - "border-left-color", "border-left-style", "border-left-width", - "border-right", "border-right-color", "border-right-style", - "border-right-width", "border-spacing", "border-style", "border-top", - "border-top-color", "border-top-style", "border-top-width", - "border-width", bottom, br, braille, brown, browser, burlywood, button, - buttonface, buttonhighlight, buttonshadow, buttontext, bytesToUIString, - c, cadetblue, call, callee, caller, canvas, cap, caption, - "caption-side", captiontext, center, charAt, charCodeAt, character, - chartreuse, chocolate, chooseColor, chooseFile, chooseFolder, cite, - clear, clearInterval, clearTimeout, clip, closeWidget, closure, cm, - code, col, colgroup, color, combine_var, command, comment, comments, - concat, conditional_assignment, confirm, confusing_a, confusing_regexp, - console, constructor, constructor_name_a, content, continue, control_a, - convertPathToHFS, convertPathToPlatform, coral, cornflowerblue, - cornsilk, "counter-increment", "counter-reset", create, crimson, css, - cursor, cyan, d, dangerous_comment, dangling_a, darkblue, darkcyan, - darkgoldenrod, darkgray, darkgreen, darkkhaki, darkmagenta, - darkolivegreen, darkorange, darkorchid, darkred, darksalmon, - darkseagreen, darkslateblue, darkslategray, darkturquoise, darkviolet, - data, datalist, dd, debug, decodeURI, decodeURIComponent, deeppink, - deepskyblue, defineClass, del, deleted, deserialize, details, devel, - dfn, dialog, dimgray, dir, direction, display, disrupt, div, dl, - document, dodgerblue, dt, duplicate_a, edge, edition, else, em, embed, - embossed, empty, "empty-cells", empty_block, empty_case, empty_class, - encodeURI, encodeURIComponent, entityify, errors, es5, escape, eval, - event, evidence, evil, ex, exception, exec, expected_a, - expected_a_at_b_c, expected_a_b, expected_a_b_from_c_d, expected_at_a, - expected_attribute_a, expected_attribute_value_a, expected_class_a, - expected_fraction_a, expected_id_a, expected_identifier_a, - expected_identifier_a_reserved, expected_lang_a, expected_linear_a, - expected_media_a, expected_name_a, expected_nonstandard_style_attribute, - expected_number_a, expected_operator_a, expected_percent_a, - expected_positive_a, expected_pseudo_a, expected_selector_a, - expected_small_a, expected_space_a_b, expected_string_a, - expected_style_attribute, expected_style_pattern, expected_tagname_a, - fieldset, figure, filesystem, filter, firebrick, first, float, floor, - floralwhite, focusWidget, font, "font-family", "font-size", - "font-size-adjust", "font-stretch", "font-style", "font-variant", - "font-weight", footer, for_if, forestgreen, forin, form, fragment, - frame, frames, frameset, from, fromCharCode, fuchsia, fud, funct, - function, function_block, function_eval, function_loop, - function_statement, function_strict, functions, g, gainsboro, gc, - get_set, ghostwhite, global, globals, gold, goldenrod, gray, graytext, - green, greenyellow, h1, h2, h3, h4, h5, h6, handheld, hasOwnProperty, - head, header, height, help, hgroup, highlight, highlighttext, history, - honeydew, hotpink, hr, "hta:application", html, html_confusion_a, - html_handlers, i, iTunes, id, identifier, identifier_function, iframe, - img, immed, implied_evil, implieds, in, inactiveborder, inactivecaption, - inactivecaptiontext, include, indent, indexOf, indianred, indigo, - infix_in, infobackground, infotext, init, input, ins, insecure_a, - isAlpha, isApplicationRunning, isArray, isDigit, isFinite, isNaN, ivory, - join, jslint, json, kbd, keygen, keys, khaki, konfabulatorVersion, - label, label_a_b, labeled, lang, lastIndexOf, lavender, lavenderblush, - lawngreen, lbp, leading_decimal_a, led, left, legend, lemonchiffon, - length, "letter-spacing", li, lib, lightblue, lightcoral, lightcyan, - lightgoldenrodyellow, lightgreen, lightpink, lightsalmon, lightseagreen, - lightskyblue, lightslategray, lightsteelblue, lightyellow, lime, - limegreen, line, "line-height", linen, link, "list-style", - "list-style-image", "list-style-position", "list-style-type", load, - loadClass, localStorage, location, log, m, magenta, map, margin, - "margin-bottom", "margin-left", "margin-right", "margin-top", mark, - "marker-offset", maroon, match, "max-height", "max-width", maxerr, - maxlen, md5, mediumaquamarine, mediumblue, mediumorchid, mediumpurple, - mediumseagreen, mediumslateblue, mediumspringgreen, mediumturquoise, - mediumvioletred, member, menu, menutext, message, meta, meter, - midnightblue, "min-height", "min-width", mintcream, missing_a, - missing_a_after_b, missing_option, missing_property, missing_space_a_b, - missing_url, missing_use_strict, mistyrose, mixed, mm, moccasin, mode, - module, move_invocation, move_var, name, name_function, nav, - navajowhite, navigator, navy, nested_comment, newcap, next, node, - noframes, nomen, noscript, not, not_a_constructor, not_a_defined, - not_a_function, not_a_label, not_a_scope, not_greater, nud, object, ol, - oldlace, olive, olivedrab, on, onevar, opacity, open, openURL, opera, - optgroup, option, orange, orangered, orchid, outer, outline, - "outline-color", "outline-style", "outline-width", output, overflow, - "overflow-x", "overflow-y", p, padding, "padding-bottom", - "padding-left", "padding-right", "padding-top", "page-break-after", - "page-break-before", palegoldenrod, palegreen, paleturquoise, - palevioletred, papayawhip, param, parameter_a_get_b, parameter_set_a, - paren, parent, parseFloat, parseInt, passfail, pc, peachpuff, peru, - pink, play, plum, plusplus, pop, popupMenu, position, postscript, - powderblue, pre, predef, preferenceGroups, preferences, prev, print, - process, progress, projection, prompt, prototype, pt, purple, push, px, - q, querystring, quit, quote, quotes, radix, random, range, raw, - readFile, readUrl, read_only, reason, red, redefinition_a, regexp, - reloadWidget, replace, report, require, reserved, reserved_a, - resolvePath, resumeUpdates, rhino, right, rosybrown, royalblue, rp, rt, - ruby, runCommand, runCommandInBg, saddlebrown, safe, salmon, samp, - sandybrown, saveAs, savePreferences, scanned_a_b, screen, script, - scrollbar, seagreen, seal, search, seashell, second, section, select, - serialize, sessionStorage, setInterval, setTimeout, shift, - showWidgetPreferences, sienna, silver, skyblue, slash_equal, slateblue, - slategray, sleep, slice, small, snow, sort, source, span, spawn, speak, - speech, split, springgreen, src, stack, statement_block, steelblue, - stopping, strange_loop, strict, strong, style, styleproperty, sub, - subscript, substr, sup, supplant, suppressUpdates, sync, system, table, - "table-layout", tag_a_in_b, tan, tbody, td, teal, tellWidget, test, - "text-align", "text-decoration", "text-indent", "text-shadow", - "text-transform", textarea, tfoot, th, thead, third, thistle, - threeddarkshadow, threedface, threedhighlight, threedlightshadow, - threedshadow, thru, time, title, toLowerCase, toString, toUpperCase, - toint32, token, tomato, too_long, too_many, top, tr, trailing_decimal_a, - tree, tt, tty, turquoise, tv, type, typeof, u, ul, unclosed, - unclosed_comment, unclosed_regexp, undef, unescape, unescaped_a, - unexpected_a, unexpected_char_a_b, unexpected_comment, - unexpected_property_a, unexpected_space_a_b, "unicode-bidi", - unnecessary_initialize, unnecessary_use, unreachable_a_b, - unrecognized_style_attribute_a, unrecognized_tag_a, unparam, unsafe, unused, - unwatch, updateNow, url, urls, use_array, use_braces, use_object, use_param, - used_before_a, util, value, valueOf, var, var_a_not, version, - "vertical-align", video, violet, visibility, was, watch, - weird_assignment, weird_condition, weird_new, weird_program, - weird_relation, weird_ternary, wheat, white, "white-space", whitesmoke, - widget, width, window, windowframe, windows, windowtext, "word-spacing", - "word-wrap", wrap, wrap_immediate, wrap_regexp, write_is_wrong, - yahooCheckLogin, yahooLogin, yahooLogout, yellow, yellowgreen, - "z-index", "|", "~" - */ - -// The global directive is used to declare global variables that can -// be accessed by the program. If a declaration is true, then the variable -// is writeable. Otherwise, it is read-only. - -// We build the application inside a function so that we produce only a single -// global variable. That function will be invoked immediately, and its return -// value is the JSLINT function itself. That function is also an object that -// can contain data and other functions. - -var JSLINT = (function () { - 'use strict'; - - var adsafe_id, // The widget's ADsafe id. - adsafe_infix = { - '-': true, - '*': true, - '/': true, - '%': true, - '&': true, - '|': true, - '^': true, - '<<': true, - '>>': true, - '>>>': true - }, - adsafe_prefix = { - '-': true, - '+': true, - '~': true, - 'typeof': true - }, - adsafe_may, // The widget may load approved scripts. - adsafe_top, // At the top of the widget script. - adsafe_went, // ADSAFE.go has been called. - anonname, // The guessed name for anonymous functions. - approved, // ADsafe approved urls. - -// These are operators that should not be used with the ! operator. - - bang = { - '<' : true, - '<=' : true, - '==' : true, - '===': true, - '!==': true, - '!=' : true, - '>' : true, - '>=' : true, - '+' : true, - '-' : true, - '*' : true, - '/' : true, - '%' : true - }, - -// These are property names that should not be permitted in the safe subset. - - banned = { - 'arguments' : true, - callee : true, - caller : true, - constructor : true, - 'eval' : true, - prototype : true, - stack : true, - unwatch : true, - valueOf : true, - watch : true - }, - begin, // The root token - -// browser contains a set of global names that are commonly provided by a -// web browser environment. - - browser = { - clearInterval : false, - clearTimeout : false, - document : false, - event : false, - frames : false, - history : false, - Image : false, - localStorage : false, - location : false, - name : false, - navigator : false, - Option : false, - parent : false, - screen : false, - sessionStorage : false, - setInterval : false, - setTimeout : false, - Storage : false, - window : false, - XMLHttpRequest : false - }, - -// bundle contains the text messages. - - bundle = { - a_function: "'{a}' is a function.", - a_label: "'{a}' is a statement label.", - a_not_allowed: "'{a}' is not allowed.", - a_not_defined: "'{a}' is not defined.", - a_scope: "'{a}' used out of scope.", - adsafe: "ADsafe violation.", - adsafe_a: "ADsafe violation: '{a}'.", - adsafe_autocomplete: "ADsafe autocomplete violation.", - adsafe_bad_id: "ADSAFE violation: bad id.", - adsafe_div: "ADsafe violation: Wrap the widget in a div.", - adsafe_fragment: "ADSAFE: Use the fragment option.", - adsafe_go: "ADsafe violation: Misformed ADSAFE.go.", - adsafe_html: "Currently, ADsafe does not operate on whole HTML " + - "documents. It operates on
fragments and .js files.", - adsafe_id: "ADsafe violation: id does not match.", - adsafe_id_go: "ADsafe violation: Missing ADSAFE.id or ADSAFE.go.", - adsafe_lib: "ADsafe lib violation.", - adsafe_lib_second: "ADsafe: The second argument to lib must be a function.", - adsafe_missing_id: "ADSAFE violation: missing ID_.", - adsafe_name_a: "ADsafe name violation: '{a}'.", - adsafe_placement: "ADsafe script placement violation.", - adsafe_prefix_a: "ADsafe violation: An id must have a '{a}' prefix", - adsafe_script: "ADsafe script violation.", - adsafe_source: "ADsafe unapproved script source.", - adsafe_subscript_a: "ADsafe subscript '{a}'.", - adsafe_tag: "ADsafe violation: Disallowed tag '{a}'.", - already_defined: "'{a}' is already defined.", - and: "The '&&' subexpression should be wrapped in parens.", - assign_exception: "Do not assign to the exception parameter.", - assignment_function_expression: "Expected an assignment or " + - "function call and instead saw an expression.", - attribute_case_a: "Attribute '{a}' not all lower case.", - avoid_a: "Avoid '{a}'.", - bad_assignment: "Bad assignment.", - bad_color_a: "Bad hex color '{a}'.", - bad_constructor: "Bad constructor.", - bad_entity: "Bad entity.", - bad_html: "Bad HTML string", - bad_id_a: "Bad id: '{a}'.", - bad_in_a: "Bad for in variable '{a}'.", - bad_invocation: "Bad invocation.", - bad_name_a: "Bad name: '{a}'.", - bad_new: "Do not use 'new' for side effects.", - bad_number: "Bad number '{a}'.", - bad_operand: "Bad operand.", - bad_type: "Bad type.", - bad_url: "Bad url string.", - bad_wrap: "Do not wrap function literals in parens unless they " + - "are to be immediately invoked.", - combine_var: "Combine this with the previous 'var' statement.", - conditional_assignment: "Expected a conditional expression and " + - "instead saw an assignment.", - confusing_a: "Confusing use of '{a}'.", - confusing_regexp: "Confusing regular expression.", - constructor_name_a: "A constructor name '{a}' should start with " + - "an uppercase letter.", - control_a: "Unexpected control character '{a}'.", - css: "A css file should begin with @charset 'UTF-8';", - dangling_a: "Unexpected dangling '_' in '{a}'.", - dangerous_comment: "Dangerous comment.", - deleted: "Only properties should be deleted.", - duplicate_a: "Duplicate '{a}'.", - empty_block: "Empty block.", - empty_case: "Empty case.", - empty_class: "Empty class.", - evil: "eval is evil.", - expected_a: "Expected '{a}'.", - expected_a_b: "Expected '{a}' and instead saw '{b}'.", - expected_a_b_from_c_d: "Expected '{a}' to match '{b}' from line " + - "{c} and instead saw '{d}'.", - expected_at_a: "Expected an at-rule, and instead saw @{a}.", - expected_a_at_b_c: "Expected '{a}' at column {b}, not column {c}.", - expected_attribute_a: "Expected an attribute, and instead saw [{a}].", - expected_attribute_value_a: "Expected an attribute value and " + - "instead saw '{a}'.", - expected_class_a: "Expected a class, and instead saw .{a}.", - expected_fraction_a: "Expected a number between 0 and 1 and " + - "instead saw '{a}'", - expected_id_a: "Expected an id, and instead saw #{a}.", - expected_identifier_a: "Expected an identifier and instead saw '{a}'.", - expected_identifier_a_reserved: "Expected an identifier and " + - "instead saw '{a}' (a reserved word).", - expected_linear_a: "Expected a linear unit and instead saw '{a}'.", - expected_lang_a: "Expected a lang code, and instead saw :{a}.", - expected_media_a: "Expected a CSS media type, and instead saw '{a}'.", - expected_name_a: "Expected a name and instead saw '{a}'.", - expected_nonstandard_style_attribute: "Expected a non-standard " + - "style attribute and instead saw '{a}'.", - expected_number_a: "Expected a number and instead saw '{a}'.", - expected_operator_a: "Expected an operator and instead saw '{a}'.", - expected_percent_a: "Expected a percentage and instead saw '{a}'", - expected_positive_a: "Expected a positive number and instead saw '{a}'", - expected_pseudo_a: "Expected a pseudo, and instead saw :{a}.", - expected_selector_a: "Expected a CSS selector, and instead saw {a}.", - expected_small_a: "Expected a small number and instead saw '{a}'", - expected_space_a_b: "Expected exactly one space between '{a}' and '{b}'.", - expected_string_a: "Expected a string and instead saw {a}.", - expected_style_attribute: "Excepted a style attribute, and instead saw '{a}'.", - expected_style_pattern: "Expected a style pattern, and instead saw '{a}'.", - expected_tagname_a: "Expected a tagName, and instead saw {a}.", - for_if: "The body of a for in should be wrapped in an if " + - "statement to filter unwanted properties from the prototype.", - function_block: "Function statements should not be placed in blocks. " + - "Use a function expression or move the statement to the top of " + - "the outer function.", - function_eval: "The Function constructor is eval.", - function_loop: "Don't make functions within a loop.", - function_statement: "Function statements are not invocable. " + - "Wrap the whole function invocation in parens.", - function_strict: "Use the function form of 'use strict'.", - get_set: "get/set are ES5 features.", - html_confusion_a: "HTML confusion in regular expression '<{a}'.", - html_handlers: "Avoid HTML event handlers.", - identifier_function: "Expected an identifier in an assignment " + - "and instead saw a function invocation.", - implied_evil: "Implied eval is evil. Pass a function instead of a string.", - infix_in: "Unexpected 'in'. Compare with undefined, or use the " + - "hasOwnProperty method instead.", - insecure_a: "Insecure '{a}'.", - isNaN: "Use the isNaN function to compare with NaN.", - label_a_b: "Label '{a}' on '{b}' statement.", - lang: "lang is deprecated.", - leading_decimal_a: "A leading decimal point can be confused with a dot: '.{a}'.", - missing_a: "Missing '{a}'.", - missing_a_after_b: "Missing '{a}' after '{b}'.", - missing_option: "Missing option value.", - missing_property: "Missing property name.", - missing_space_a_b: "Missing space between '{a}' and '{b}'.", - missing_url: "Missing url.", - missing_use_strict: "Missing 'use strict' statement.", - mixed: "Mixed spaces and tabs.", - move_invocation: "Move the invocation into the parens that " + - "contain the function.", - move_var: "Move 'var' declarations to the top of the function.", - name_function: "Missing name in function statement.", - nested_comment: "Nested comment.", - not: "Nested not.", - not_a_constructor: "Do not use {a} as a constructor.", - not_a_defined: "'{a}' has not been fully defined yet.", - not_a_function: "'{a}' is not a function.", - not_a_label: "'{a}' is not a label.", - not_a_scope: "'{a}' is out of scope.", - not_greater: "'{a}' should not be greater than '{b}'.", - parameter_a_get_b: "Unexpected parameter '{a}' in get {b} function.", - parameter_set_a: "Expected parameter (value) in set {a} function.", - radix: "Missing radix parameter.", - read_only: "Read only.", - redefinition_a: "Redefinition of '{a}'.", - reserved_a: "Reserved name '{a}'.", - scanned_a_b: "{a} ({b}% scanned).", - slash_equal: "A regular expression literal can be confused with '/='.", - statement_block: "Expected to see a statement and instead saw a block.", - stopping: "Stopping. ", - strange_loop: "Strange loop.", - strict: "Strict violation.", - subscript: "['{a}'] is better written in dot notation.", - tag_a_in_b: "A '<{a}>' must be within '<{b}>'.", - too_long: "Line too long.", - too_many: "Too many errors.", - trailing_decimal_a: "A trailing decimal point can be confused " + - "with a dot: '.{a}'.", - type: "type is unnecessary.", - unclosed: "Unclosed string.", - unclosed_comment: "Unclosed comment.", - unclosed_regexp: "Unclosed regular expression.", - unescaped_a: "Unescaped '{a}'.", - unexpected_a: "Unexpected '{a}'.", - unexpected_char_a_b: "Unexpected character '{a}' in {b}.", - unexpected_comment: "Unexpected comment.", - unexpected_property_a: "Unexpected /*property*/ '{a}'.", - unexpected_space_a_b: "Unexpected space between '{a}' and '{b}'.", - unnecessary_initialize: "It is not necessary to initialize '{a}' " + - "to 'undefined'.", - unnecessary_use: "Unnecessary 'use strict'.", - unreachable_a_b: "Unreachable '{a}' after '{b}'.", - unrecognized_style_attribute_a: "Unrecognized style attribute '{a}'.", - unrecognized_tag_a: "Unrecognized tag '<{a}>'.", - unsafe: "Unsafe character.", - url: "JavaScript URL.", - use_array: "Use the array literal notation [].", - use_braces: "Spaces are hard to count. Use {{a}}.", - use_object: "Use the object literal notation {}.", - use_param: "Use a named parameter.", - used_before_a: "'{a}' was used before it was defined.", - var_a_not: "Variable {a} was not declared correctly.", - weird_assignment: "Weird assignment.", - weird_condition: "Weird condition.", - weird_new: "Weird construction. Delete 'new'.", - weird_program: "Weird program.", - weird_relation: "Weird relation.", - weird_ternary: "Weird ternary.", - wrap_immediate: "Wrap an immediate function invocation in parentheses " + - "to assist the reader in understanding that the expression " + - "is the result of a function, and not the function itself.", - wrap_regexp: "Wrap the /regexp/ literal in parens to " + - "disambiguate the slash operator.", - write_is_wrong: "document.write can be a form of eval." - }, - comments_off, - css_attribute_data, - css_any, - - css_colorData = { - "aliceblue" : true, - "antiquewhite" : true, - "aqua" : true, - "aquamarine" : true, - "azure" : true, - "beige" : true, - "bisque" : true, - "black" : true, - "blanchedalmond" : true, - "blue" : true, - "blueviolet" : true, - "brown" : true, - "burlywood" : true, - "cadetblue" : true, - "chartreuse" : true, - "chocolate" : true, - "coral" : true, - "cornflowerblue" : true, - "cornsilk" : true, - "crimson" : true, - "cyan" : true, - "darkblue" : true, - "darkcyan" : true, - "darkgoldenrod" : true, - "darkgray" : true, - "darkgreen" : true, - "darkkhaki" : true, - "darkmagenta" : true, - "darkolivegreen" : true, - "darkorange" : true, - "darkorchid" : true, - "darkred" : true, - "darksalmon" : true, - "darkseagreen" : true, - "darkslateblue" : true, - "darkslategray" : true, - "darkturquoise" : true, - "darkviolet" : true, - "deeppink" : true, - "deepskyblue" : true, - "dimgray" : true, - "dodgerblue" : true, - "firebrick" : true, - "floralwhite" : true, - "forestgreen" : true, - "fuchsia" : true, - "gainsboro" : true, - "ghostwhite" : true, - "gold" : true, - "goldenrod" : true, - "gray" : true, - "green" : true, - "greenyellow" : true, - "honeydew" : true, - "hotpink" : true, - "indianred" : true, - "indigo" : true, - "ivory" : true, - "khaki" : true, - "lavender" : true, - "lavenderblush" : true, - "lawngreen" : true, - "lemonchiffon" : true, - "lightblue" : true, - "lightcoral" : true, - "lightcyan" : true, - "lightgoldenrodyellow" : true, - "lightgreen" : true, - "lightpink" : true, - "lightsalmon" : true, - "lightseagreen" : true, - "lightskyblue" : true, - "lightslategray" : true, - "lightsteelblue" : true, - "lightyellow" : true, - "lime" : true, - "limegreen" : true, - "linen" : true, - "magenta" : true, - "maroon" : true, - "mediumaquamarine" : true, - "mediumblue" : true, - "mediumorchid" : true, - "mediumpurple" : true, - "mediumseagreen" : true, - "mediumslateblue" : true, - "mediumspringgreen" : true, - "mediumturquoise" : true, - "mediumvioletred" : true, - "midnightblue" : true, - "mintcream" : true, - "mistyrose" : true, - "moccasin" : true, - "navajowhite" : true, - "navy" : true, - "oldlace" : true, - "olive" : true, - "olivedrab" : true, - "orange" : true, - "orangered" : true, - "orchid" : true, - "palegoldenrod" : true, - "palegreen" : true, - "paleturquoise" : true, - "palevioletred" : true, - "papayawhip" : true, - "peachpuff" : true, - "peru" : true, - "pink" : true, - "plum" : true, - "powderblue" : true, - "purple" : true, - "red" : true, - "rosybrown" : true, - "royalblue" : true, - "saddlebrown" : true, - "salmon" : true, - "sandybrown" : true, - "seagreen" : true, - "seashell" : true, - "sienna" : true, - "silver" : true, - "skyblue" : true, - "slateblue" : true, - "slategray" : true, - "snow" : true, - "springgreen" : true, - "steelblue" : true, - "tan" : true, - "teal" : true, - "thistle" : true, - "tomato" : true, - "turquoise" : true, - "violet" : true, - "wheat" : true, - "white" : true, - "whitesmoke" : true, - "yellow" : true, - "yellowgreen" : true, - - "activeborder" : true, - "activecaption" : true, - "appworkspace" : true, - "background" : true, - "buttonface" : true, - "buttonhighlight" : true, - "buttonshadow" : true, - "buttontext" : true, - "captiontext" : true, - "graytext" : true, - "highlight" : true, - "highlighttext" : true, - "inactiveborder" : true, - "inactivecaption" : true, - "inactivecaptiontext" : true, - "infobackground" : true, - "infotext" : true, - "menu" : true, - "menutext" : true, - "scrollbar" : true, - "threeddarkshadow" : true, - "threedface" : true, - "threedhighlight" : true, - "threedlightshadow" : true, - "threedshadow" : true, - "window" : true, - "windowframe" : true, - "windowtext" : true - }, - - css_border_style, - css_break, - - css_lengthData = { - '%': true, - 'cm': true, - 'em': true, - 'ex': true, - 'in': true, - 'mm': true, - 'pc': true, - 'pt': true, - 'px': true - }, - - css_media, - css_overflow, - - devel = { - alert : false, - confirm : false, - console : false, - Debug : false, - opera : false, - prompt : false - }, - - escapes = { - '\b': '\\b', - '\t': '\\t', - '\n': '\\n', - '\f': '\\f', - '\r': '\\r', - '"' : '\\"', - '/' : '\\/', - '\\': '\\\\' - }, - - funct, // The current function - - functionicity = [ - 'closure', 'exception', 'global', 'label', 'outer', 'unused', 'var' - ], - - functions, // All of the functions - global, // The global scope - html_tag = { - a: {}, - abbr: {}, - acronym: {}, - address: {}, - applet: {}, - area: {empty: true, parent: ' map '}, - article: {}, - aside: {}, - audio: {}, - b: {}, - base: {empty: true, parent: ' head '}, - bdo: {}, - big: {}, - blockquote: {}, - body: {parent: ' html noframes '}, - br: {empty: true}, - button: {}, - canvas: {parent: ' body p div th td '}, - caption: {parent: ' table '}, - center: {}, - cite: {}, - code: {}, - col: {empty: true, parent: ' table colgroup '}, - colgroup: {parent: ' table '}, - command: {parent: ' menu '}, - datalist: {}, - dd: {parent: ' dl '}, - del: {}, - details: {}, - dialog: {}, - dfn: {}, - dir: {}, - div: {}, - dl: {}, - dt: {parent: ' dl '}, - em: {}, - embed: {}, - fieldset: {}, - figure: {}, - font: {}, - footer: {}, - form: {}, - frame: {empty: true, parent: ' frameset '}, - frameset: {parent: ' html frameset '}, - h1: {}, - h2: {}, - h3: {}, - h4: {}, - h5: {}, - h6: {}, - head: {parent: ' html '}, - header: {}, - hgroup: {}, - hr: {empty: true}, - 'hta:application': - {empty: true, parent: ' head '}, - html: {parent: '*'}, - i: {}, - iframe: {}, - img: {empty: true}, - input: {empty: true}, - ins: {}, - kbd: {}, - keygen: {}, - label: {}, - legend: {parent: ' details fieldset figure '}, - li: {parent: ' dir menu ol ul '}, - link: {empty: true, parent: ' head '}, - map: {}, - mark: {}, - menu: {}, - meta: {empty: true, parent: ' head noframes noscript '}, - meter: {}, - nav: {}, - noframes: {parent: ' html body '}, - noscript: {parent: ' body head noframes '}, - object: {}, - ol: {}, - optgroup: {parent: ' select '}, - option: {parent: ' optgroup select '}, - output: {}, - p: {}, - param: {empty: true, parent: ' applet object '}, - pre: {}, - progress: {}, - q: {}, - rp: {}, - rt: {}, - ruby: {}, - samp: {}, - script: {empty: true, parent: ' body div frame head iframe p pre span '}, - section: {}, - select: {}, - small: {}, - span: {}, - source: {}, - strong: {}, - style: {parent: ' head ', empty: true}, - sub: {}, - sup: {}, - table: {}, - tbody: {parent: ' table '}, - td: {parent: ' tr '}, - textarea: {}, - tfoot: {parent: ' table '}, - th: {parent: ' tr '}, - thead: {parent: ' table '}, - time: {}, - title: {parent: ' head '}, - tr: {parent: ' table tbody thead tfoot '}, - tt: {}, - u: {}, - ul: {}, - 'var': {}, - video: {} - }, - - ids, // HTML ids - implied, // Implied globals - in_block, - indent, - json_mode, - lines, - lookahead, - member, - node = { - Buffer : false, - clearInterval: false, - clearTimeout : false, - console : false, - global : false, - module : false, - process : false, - querystring : false, - require : false, - setInterval : false, - setTimeout : false, - util : false, - __filename : false, - __dirname : false - }, - numbery = { - indexOf : true, - lastIndexOf : true, - search : true - }, - properties, - next_token, - older_token, - option, - predefined, // Global variables defined by option - prereg, - prev_token, - regexp_flag = { - g: true, - i: true, - m: true - }, - rhino = { - defineClass : false, - deserialize : false, - gc : false, - help : false, - load : false, - loadClass : false, - print : false, - quit : false, - readFile : false, - readUrl : false, - runCommand : false, - seal : false, - serialize : false, - spawn : false, - sync : false, - toint32 : false, - version : false - }, - - scope, // The current scope - semicolon_coda = { - ';' : true, - '"' : true, - '\'': true, - ')' : true - }, - src, - stack, - -// standard contains the global names that are provided by the -// ECMAScript standard. - - standard = { - Array : false, - Boolean : false, - Date : false, - decodeURI : false, - decodeURIComponent : false, - encodeURI : false, - encodeURIComponent : false, - Error : false, - 'eval' : false, - EvalError : false, - Function : false, - hasOwnProperty : false, - isFinite : false, - isNaN : false, - JSON : false, - Math : false, - Number : false, - Object : false, - parseInt : false, - parseFloat : false, - RangeError : false, - ReferenceError : false, - RegExp : false, - String : false, - SyntaxError : false, - TypeError : false, - URIError : false - }, - - standard_property = { - E : true, - LN2 : true, - LN10 : true, - LOG2E : true, - LOG10E : true, - MAX_VALUE : true, - MIN_VALUE : true, - NEGATIVE_INFINITY : true, - PI : true, - POSITIVE_INFINITY : true, - SQRT1_2 : true, - SQRT2 : true - }, - - strict_mode, - syntax = {}, - tab, - token, - urls, - var_mode, - warnings, - -// widget contains the global names which are provided to a Yahoo -// (fna Konfabulator) widget. - - widget = { - alert : true, - animator : true, - appleScript : true, - beep : true, - bytesToUIString : true, - Canvas : true, - chooseColor : true, - chooseFile : true, - chooseFolder : true, - closeWidget : true, - COM : true, - convertPathToHFS : true, - convertPathToPlatform : true, - CustomAnimation : true, - escape : true, - FadeAnimation : true, - filesystem : true, - Flash : true, - focusWidget : true, - form : true, - FormField : true, - Frame : true, - HotKey : true, - Image : true, - include : true, - isApplicationRunning : true, - iTunes : true, - konfabulatorVersion : true, - log : true, - md5 : true, - MenuItem : true, - MoveAnimation : true, - openURL : true, - play : true, - Point : true, - popupMenu : true, - preferenceGroups : true, - preferences : true, - print : true, - prompt : true, - random : true, - Rectangle : true, - reloadWidget : true, - ResizeAnimation : true, - resolvePath : true, - resumeUpdates : true, - RotateAnimation : true, - runCommand : true, - runCommandInBg : true, - saveAs : true, - savePreferences : true, - screen : true, - ScrollBar : true, - showWidgetPreferences : true, - sleep : true, - speak : true, - Style : true, - suppressUpdates : true, - system : true, - tellWidget : true, - Text : true, - TextArea : true, - Timer : true, - unescape : true, - updateNow : true, - URL : true, - Web : true, - widget : true, - Window : true, - XMLDOM : true, - XMLHttpRequest : true, - yahooCheckLogin : true, - yahooLogin : true, - yahooLogout : true - }, - - windows = { - ActiveXObject: false, - CScript : false, - Debug : false, - Enumerator : false, - System : false, - VBArray : false, - WScript : false - }, - -// xmode is used to adapt to the exceptions in html parsing. -// It can have these states: -// false .js script file -// html -// outer -// script -// style -// scriptstring -// styleproperty - - xmode, - xquote, - -// Regular expressions. Some of these are stupidly long. - -// unsafe comment or string - ax = /@cc|<\/?|script|\]\s*\]|<\s*!|</i, -// unsafe characters that are silently deleted by one or more browsers - cx = /[\u0000-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/, -// query characters for ids - dx = /[\[\]\/\\"'*<>.&:(){}+=#]/, -// html token - hx = /^\s*(['"=>\/&#]|<(?:\/|\!(?:--)?)?|[a-zA-Z][a-zA-Z0-9_\-:]*|[0-9]+|--)/, -// identifier - ix = /^([a-zA-Z_$][a-zA-Z0-9_$]*)$/, -// javascript url - jx = /^(?:javascript|jscript|ecmascript|vbscript|mocha|livescript)\s*:/i, -// star slash - lx = /\*\/|\/\*/, -// characters in strings that need escapement - nx = /[\u0000-\u001f"\\\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, -// outer html token - ox = /[>&]|<[\/!]?|--/, -// attributes characters - qx = /[^a-zA-Z0-9+\-_\/ ]/, -// style - sx = /^\s*([{:#%.=,>+\[\]@()"';]|\*=?|\$=|\|=|\^=|~=|[a-zA-Z_][a-zA-Z0-9_\-]*|[0-9]+|<\/|\/\*)/, - ssx = /^\s*([@#!"'};:\-%.=,+\[\]()*_]|[a-zA-Z][a-zA-Z0-9._\-]*|\/\*?|\d+(?:\.\d+)?|<\/)/, -// token - tx = /^\s*([(){}\[.,:;'"~\?\]#@]|==?=?|\/(\*(jslint|properties|property|members?|globals?)?|=|\/)?|\*[\/=]?|\+(?:=|\++)?|-(?:=|-+)?|%=?|&[&=]?|\|[|=]?|>>?>?=?|<([\/=!]|\!(\[|--)?|<=?)?|\^=?|\!=?=?|[a-zA-Z_$][a-zA-Z0-9_$]*|[0-9]+([xX][0-9a-fA-F]+|\.[0-9]*)?([eE][+\-]?[0-9]+)?)/, -// url badness - ux = /&|\+|\u00AD|\.\.|\/\*|%[^;]|base64|url|expression|data|mailto/i, - - rx = { - outer: hx, - html: hx, - style: sx, - styleproperty: ssx - }; - - - function return_this() { - return this; - } - - function F() {} // Used by Object.create - -// Provide critical ES5 functions to ES3. - - if (typeof Array.prototype.filter !== 'function') { - Array.prototype.filter = function (f) { - var i, length = this.length, result = []; - for (i = 0; i < length; i += 1) { - try { - result.push(f(this[i])); - } catch (ignore) { - } - } - return result; - }; - } - - if (typeof Array.isArray !== 'function') { - Array.isArray = function (o) { - return Object.prototype.toString.apply(o) === '[object Array]'; - }; - } - - if (!Object.hasOwnProperty('create')) { - Object.create = function (o) { - F.prototype = o; - return new F(); - }; - } - - if (typeof Object.keys !== 'function') { - Object.keys = function (o) { - var array = [], key; - for (key in o) { - if (Object.prototype.hasOwnProperty.call(o, key)) { - array.push(key); - } - } - return array; - }; - } - -// Substandard methods - - if (typeof String.prototype.entityify !== 'function') { - String.prototype.entityify = function () { - return this - .replace(/&/g, '&') - .replace(//g, '>'); - }; - } - - if (typeof String.prototype.isAlpha !== 'function') { - String.prototype.isAlpha = function () { - return (this >= 'a' && this <= 'z\uffff') || - (this >= 'A' && this <= 'Z\uffff'); - }; - } - - if (typeof String.prototype.isDigit !== 'function') { - String.prototype.isDigit = function () { - return (this >= '0' && this <= '9'); - }; - } - - if (typeof String.prototype.supplant !== 'function') { - String.prototype.supplant = function (o) { - return this.replace(/\{([^{}]*)\}/g, function (a, b) { - var replacement = o[b]; - return typeof replacement === 'string' || - typeof replacement === 'number' ? replacement : a; - }); - }; - } - - - function sanitize(a) { - -// Escapify a troublesome character. - - return escapes[a] ? escapes[a] : - '\\u' + ('0000' + a.charCodeAt().toString(16)).slice(-4); - } - - - function combine(a, b) { - var name; - for (name in b) { - if (Object.prototype.hasOwnProperty.call(b, name)) { - a[name] = b[name]; - } - } - } - - function assume() { - if (!option.safe) { - if (option.rhino) { - combine(predefined, rhino); - } - if (option.devel) { - combine(predefined, devel); - } - if (option.browser) { - combine(predefined, browser); - } - if (option.windows) { - combine(predefined, windows); - } - if (option.node) { - combine(predefined, node); - } - if (option.widget) { - combine(predefined, widget); - } - } - } - - -// Produce an error warning. - - function quit(message, line, character) { - throw { - name: 'JSLintError', - line: line, - character: character, - message: bundle.scanned_a_b.supplant({ - a: message, - b: Math.floor((line / lines.length) * 100) - }) - }; - } - - function warn(message, offender, a, b, c, d) { - var character, line, warning; - offender = offender || next_token; // `~ - line = offender.line || 0; - character = offender.from || 0; - warning = { - id: '(error)', - raw: bundle[message] || message, - evidence: lines[line - 1] || '', - line: line, - character: character, - a: a || offender.value, - b: b, - c: c, - d: d - }; - warning.reason = warning.raw.supplant(warning); - JSLINT.errors.push(warning); - if (option.passfail) { - quit(bundle.stopping, line, character); - } - warnings += 1; - if (warnings >= option.maxerr) { - quit(bundle.too_many, line, character); - } - return warning; - } - - function warn_at(message, line, character, a, b, c, d) { - return warn(message, { - line: line, - from: character - }, a, b, c, d); - } - - function stop(message, offender, a, b, c, d) { - var warning = warn(message, offender, a, b, c, d); - quit(bundle.stopping, warning.line, warning.character); - } - - function stop_at(message, line, character, a, b, c, d) { - return stop(message, { - line: line, - from: character - }, a, b, c, d); - } - - function expected_at(at) { - if (option.white && next_token.from !== at) { - warn('expected_a_at_b_c', next_token, next_token.value, at, - next_token.from); - } - } - - function aint(it, name, expected) { - if (it[name] !== expected) { - warn('expected_a_b', it, expected, it[name]); - return true; - } else { - return false; - } - } - - -// lexical analysis and token construction - - var lex = (function lex() { - var character, from, line, source_row; - -// Private lex methods - - function collect_comment(comment, quote, line, at) { - var comment_object = { - comment: comment, - quote: quote, - at: at, - line: line - }; - if (comments_off || src || (xmode && xmode !== 'script' && - xmode !== 'style' && xmode !== 'styleproperty')) { - warn_at('unexpected_comment', line, character); - } else if (xmode === 'script' && /<\//i.test(source_row)) { - warn_at('unexpected_a', line, character, '<\/'); - } else if (option.safe && ax.test(comment)) { - warn_at('dangerous_comment', line, at); - } - if (older_token.comments) { - older_token.comments.push(comment_object); - } else { - older_token.comments = [comment_object]; - } - JSLINT.comments.push(comment_object); - } - - function next_line() { - var at; - if (line >= lines.length) { - return false; - } - character = 1; - source_row = lines[line]; - line += 1; - at = source_row.search(/ \t/); - if (at >= 0) { - warn_at('mixed', line, at + 1); - } - source_row = source_row.replace(/\t/g, tab); - at = source_row.search(cx); - if (at >= 0) { - warn_at('unsafe', line, at); - } - if (option.maxlen && option.maxlen < source_row.length) { - warn_at('too_long', line, source_row.length); - } - return true; - } - -// Produce a token object. The token inherits from a syntax symbol. - - function it(type, value, quote) { - var id, the_token; - if (type === '(string)' || type === '(range)') { - if (jx.test(value)) { - warn_at('url', line, from); - } - } - the_token = Object.create(syntax[( - type === '(punctuator)' || - (type === '(identifier)' && - Object.prototype.hasOwnProperty.call(syntax, value)) ? - value : - type - )] || syntax['(error)']); - if (type === '(identifier)') { - the_token.identifier = true; - if (value === '__iterator__' || value === '__proto__') { - stop_at('reserved_a', line, from, value); - } else if (option.nomen && - (value.charAt(0) === '_' || - value.charAt(value.length - 1) === '_')) { - warn_at('dangling_a', line, from, value); - } - } - if (value !== undefined) { - the_token.value = value; - } - if (quote) { - the_token.quote = quote; - } - the_token.line = line; - the_token.from = from; - the_token.thru = character; - the_token.prev = older_token; - id = the_token.id; - prereg = id && ( - ('(,=:[!&|?{};'.indexOf(id.charAt(id.length - 1)) >= 0) || - id === 'return' - ); - older_token.next = the_token; - older_token = the_token; - return the_token; - } - -// Public lex methods - - return { - init: function (source) { - if (typeof source === 'string') { - lines = source - .replace(/\r\n/g, '\n') - .replace(/\r/g, '\n') - .split('\n'); - } else { - lines = source; - } - line = 0; - next_line(); - from = 1; - }, - - range: function (begin, end) { - var c, value = ''; - from = character; - if (source_row.charAt(0) !== begin) { - stop_at('expected_a_b', line, character, begin, - source_row.charAt(0)); - } - for (;;) { - source_row = source_row.slice(1); - character += 1; - c = source_row.charAt(0); - switch (c) { - case '': - stop_at('missing_a', line, character, c); - break; - case end: - source_row = source_row.slice(1); - character += 1; - return it('(range)', value); - case xquote: - case '\\': - warn_at('unexpected_a', line, character, c); - break; - } - value += c; - } - }, - -// token -- this is called by advance to get the next token. - - token: function () { - var b, c, captures, digit, depth, flag, high, i, j, length, low, quote, symbol; - - function match(x) { - var exec = x.exec(source_row), first; - if (exec) { - length = exec[0].length; - first = exec[1]; - c = first.charAt(0); - source_row = source_row.substr(length); - from = character + length - first.length; - character += length; - return first; - } - } - - function string(x) { - var c, j, r = ''; - - function hex(n) { - var i = parseInt(source_row.substr(j + 1, n), 16); - j += n; - if (i >= 32 && i <= 126 && - i !== 34 && i !== 92 && i !== 39) { - warn_at('unexpected_a', line, character, '\\'); - } - character += n; - c = String.fromCharCode(i); - } - - if (json_mode && x !== '"') { - warn_at('expected_a', line, character, '"'); - } - - if (xquote === x || (xmode === 'scriptstring' && !xquote)) { - return it('(punctuator)', x); - } - - j = 0; - for (;;) { - while (j >= source_row.length) { - j = 0; - if (xmode !== 'html' || !next_line()) { - stop_at('unclosed', line, from); - } - } - c = source_row.charAt(j); - if (c === x) { - character += 1; - source_row = source_row.substr(j + 1); - return it('(string)', r, x); - } - if (c < ' ') { - if (c === '\n' || c === '\r') { - break; - } - warn_at('control_a', - line, character + j, source_row.slice(0, j)); - } else if (c === xquote) { - warn_at('bad_html', line, character + j); - } else if (c === '<') { - if (option.safe && xmode === 'html') { - warn_at('adsafe_a', line, character + j, c); - } else if (source_row.charAt(j + 1) === '/' && (xmode || option.safe)) { - warn_at('expected_a_b', line, character, - '<\\/', ' 0) { - character += 1; - source_row = source_row.slice(i); - break; - } else { - if (!next_line()) { - return it('(end)', ''); - } - } - } - symbol = match(rx[xmode] || tx); - if (!symbol) { - symbol = ''; - c = ''; - while (source_row && source_row < '!') { - source_row = source_row.substr(1); - } - if (source_row) { - if (xmode === 'html') { - return it('(error)', source_row.charAt(0)); - } else { - stop_at('unexpected_a', - line, character, source_row.substr(0, 1)); - } - } - } else { - -// identifier - - if (c.isAlpha() || c === '_' || c === '$') { - return it('(identifier)', symbol); - } - -// number - - if (c.isDigit()) { - if (xmode !== 'style' && - xmode !== 'styleproperty' && - source_row.substr(0, 1).isAlpha()) { - warn_at('expected_space_a_b', - line, character, c, source_row.charAt(0)); - } - if (c === '0') { - digit = symbol.substr(1, 1); - if (digit.isDigit()) { - if (token.id !== '.' && xmode !== 'styleproperty') { - warn_at('unexpected_a', - line, character, symbol); - } - } else if (json_mode && (digit === 'x' || digit === 'X')) { - warn_at('unexpected_a', line, character, '0x'); - } - } - if (symbol.substr(symbol.length - 1) === '.') { - warn_at('trailing_decimal_a', line, - character, symbol); - } - if (xmode !== 'style') { - digit = +symbol; - if (!isFinite(digit)) { - warn_at('bad_number', line, character, symbol); - } - symbol = digit; - } - return it('(number)', symbol); - } - switch (symbol) { - -// string - - case '"': - case "'": - return string(symbol); - -// // comment - - case '//': - collect_comment(source_row, '//', line, character); - source_row = ''; - break; - -// /* comment - - case '/*': - quote = '/*'; - for (;;) { - i = source_row.search(lx); - if (i >= 0) { - break; - } - collect_comment(source_row, quote, line, character); - quote = ''; - if (!next_line()) { - stop_at('unclosed_comment', line, character); - } - } - collect_comment(source_row.slice(0, i), quote, character, line); - character += i + 2; - if (source_row.substr(i, 1) === '/') { - stop_at('nested_comment', line, character); - } - source_row = source_row.substr(i + 2); - break; - - case '': - break; -// / - case '/': - if (token.id === '/=') { - stop_at( - bundle.slash_equal, - line, - from - ); - } - if (prereg) { - depth = 0; - captures = 0; - length = 0; - for (;;) { - b = true; - c = source_row.charAt(length); - length += 1; - switch (c) { - case '': - stop_at('unclosed_regexp', line, from); - return; - case '/': - if (depth > 0) { - warn_at('unescaped_a', - line, from + length, '/'); - } - c = source_row.substr(0, length - 1); - flag = Object.create(regexp_flag); - while (flag[source_row.charAt(length)] === true) { - flag[source_row.charAt(length)] = false; - length += 1; - } - if (source_row.charAt(length).isAlpha()) { - stop_at('unexpected_a', - line, from, source_row.charAt(length)); - } - character += length; - source_row = source_row.substr(length); - quote = source_row.charAt(0); - if (quote === '/' || quote === '*') { - stop_at('confusing_regexp', - line, from); - } - return it('(regexp)', c); - case '\\': - c = source_row.charAt(length); - if (c < ' ') { - warn_at('control_a', - line, from + length, String(c)); - } else if (c === '<') { - warn_at( - bundle.unexpected_a, - line, - from + length, - '\\' - ); - } - length += 1; - break; - case '(': - depth += 1; - b = false; - if (source_row.charAt(length) === '?') { - length += 1; - switch (source_row.charAt(length)) { - case ':': - case '=': - case '!': - length += 1; - break; - default: - warn_at( - bundle.expected_a_b, - line, - from + length, - ':', - source_row.charAt(length) - ); - } - } else { - captures += 1; - } - break; - case '|': - b = false; - break; - case ')': - if (depth === 0) { - warn_at('unescaped_a', - line, from + length, ')'); - } else { - depth -= 1; - } - break; - case ' ': - j = 1; - while (source_row.charAt(length) === ' ') { - length += 1; - j += 1; - } - if (j > 1) { - warn_at('use_braces', - line, from + length, j); - } - break; - case '[': - c = source_row.charAt(length); - if (c === '^') { - length += 1; - if (option.regexp) { - warn_at('insecure_a', - line, from + length, c); - } else if (source_row.charAt(length) === ']') { - stop_at('unescaped_a', - line, from + length, '^'); - } - } - quote = false; - if (c === ']') { - warn_at('empty_class', line, - from + length - 1); - quote = true; - } -klass: do { - c = source_row.charAt(length); - length += 1; - switch (c) { - case '[': - case '^': - warn_at('unescaped_a', - line, from + length, c); - quote = true; - break; - case '-': - if (quote) { - quote = false; - } else { - warn_at('unescaped_a', - line, from + length, '-'); - quote = true; - } - break; - case ']': - if (!quote) { - warn_at('unescaped_a', - line, from + length - 1, '-'); - } - break klass; - case '\\': - c = source_row.charAt(length); - if (c < ' ') { - warn_at( - bundle.control_a, - line, - from + length, - String(c) - ); - } else if (c === '<') { - warn_at( - bundle.unexpected_a, - line, - from + length, - '\\' - ); - } - length += 1; - quote = true; - break; - case '/': - warn_at('unescaped_a', - line, from + length - 1, '/'); - quote = true; - break; - case '<': - if (xmode === 'script') { - c = source_row.charAt(length); - if (c === '!' || c === '/') { - warn_at( - bundle.html_confusion_a, - line, - from + length, - c - ); - } - } - quote = true; - break; - default: - quote = true; - } - } while (c); - break; - case '.': - if (option.regexp) { - warn_at('insecure_a', line, - from + length, c); - } - break; - case ']': - case '?': - case '{': - case '}': - case '+': - case '*': - warn_at('unescaped_a', line, - from + length, c); - break; - case '<': - if (xmode === 'script') { - c = source_row.charAt(length); - if (c === '!' || c === '/') { - warn_at( - bundle.html_confusion_a, - line, - from + length, - c - ); - } - } - break; - } - if (b) { - switch (source_row.charAt(length)) { - case '?': - case '+': - case '*': - length += 1; - if (source_row.charAt(length) === '?') { - length += 1; - } - break; - case '{': - length += 1; - c = source_row.charAt(length); - if (c < '0' || c > '9') { - warn_at( - bundle.expected_number_a, - line, - from + length, - c - ); - } - length += 1; - low = +c; - for (;;) { - c = source_row.charAt(length); - if (c < '0' || c > '9') { - break; - } - length += 1; - low = +c + (low * 10); - } - high = low; - if (c === ',') { - length += 1; - high = Infinity; - c = source_row.charAt(length); - if (c >= '0' && c <= '9') { - length += 1; - high = +c; - for (;;) { - c = source_row.charAt(length); - if (c < '0' || c > '9') { - break; - } - length += 1; - high = +c + (high * 10); - } - } - } - if (source_row.charAt(length) !== '}') { - warn_at( - bundle.expected_a_b, - line, - from + length, - '}', - c - ); - } else { - length += 1; - } - if (source_row.charAt(length) === '?') { - length += 1; - } - if (low > high) { - warn_at( - bundle.not_greater, - line, - from + length, - low, - high - ); - } - break; - } - } - } - c = source_row.substr(0, length - 1); - character += length; - source_row = source_row.substr(length); - return it('(regexp)', c); - } - return it('(punctuator)', symbol); - -// punctuator - - case ''); - } - character += 3; - source_row = source_row.slice(i + 3); - break; - case '#': - if (xmode === 'html' || xmode === 'styleproperty') { - for (;;) { - c = source_row.charAt(0); - if ((c < '0' || c > '9') && - (c < 'a' || c > 'f') && - (c < 'A' || c > 'F')) { - break; - } - character += 1; - source_row = source_row.substr(1); - symbol += c; - } - if (symbol.length !== 4 && symbol.length !== 7) { - warn_at('bad_color_a', line, - from + length, symbol); - } - return it('(color)', symbol); - } - return it('(punctuator)', symbol); - - default: - if (xmode === 'outer' && c === '&') { - character += 1; - source_row = source_row.substr(1); - for (;;) { - c = source_row.charAt(0); - character += 1; - source_row = source_row.substr(1); - if (c === ';') { - break; - } - if (!((c >= '0' && c <= '9') || - (c >= 'a' && c <= 'z') || - c === '#')) { - stop_at('bad_entity', line, from + length, - character); - } - } - break; - } - return it('(punctuator)', symbol); - } - } - } - } - }; - }()); - - - function add_label(symbol, type) { - - if (option.safe && funct['(global)'] && - typeof predefined[symbol] !== 'boolean') { - warn('adsafe_a', token, symbol); - } else if (symbol === 'hasOwnProperty') { - warn('bad_name_a', token, symbol); - } - -// Define symbol in the current function in the current scope. - - if (Object.prototype.hasOwnProperty.call(funct, symbol) && !funct['(global)']) { - warn(funct[symbol] === true ? - bundle.used_before_a : - bundle.already_defined, - next_token, symbol); - } - funct[symbol] = type; - if (funct['(global)']) { - if (global[symbol] === false) { - warn('read_only'); - } - global[symbol] = true; - if (Object.prototype.hasOwnProperty.call(implied, symbol)) { - warn('used_before_a', next_token, symbol); - delete implied[symbol]; - } - } else { - scope[symbol] = funct; - } - } - - - function peek(distance) { - -// Peek ahead to a future token. The distance is how far ahead to look. The -// default is the next token. - - var found, slot = 0; - - distance = distance || 0; - while (slot <= distance) { - found = lookahead[slot]; - if (!found) { - found = lookahead[slot] = lex.token(); - } - slot += 1; - } - return found; - } - - - function discard(it) { - -// The token will not be included in the parse tree, so move the comments -// that are attached to the token to tokens that are in the tree. - - it = it || token; - if (it.comments) { - var prev = it.prev; - while (prev.comments === null) { - prev = prev.prev; - } - if (prev.comments) { - prev.comments = prev.comments.concat(it.comments); - } else { - prev.comments = it.comments; - } - } - it.comments = null; - } - - - function advance(id, match) { - -// Produce the next token, also looking for programming errors. - - if (indent) { - -// In indentation checking was requested, then inspect all of the line breakings. -// The var statement is tricky because the names might be aligned or not. We -// look at the first line break after the var to determine the programmer's -// intention. - - if (var_mode && next_token.line !== token.line) { - if ((var_mode !== indent || !next_token.edge) && - next_token.from === indent.at - - (next_token.edge ? option.indent : 0)) { - var dent = indent; - for (;;) { - dent.at -= option.indent; - if (dent === var_mode) { - break; - } - dent = dent.was; - } - dent.open = false; - } - var_mode = false; - } - if (indent.open) { - -// If the token is an edge. - - if (next_token.edge) { - if (next_token.edge === 'label') { - expected_at(1); - } else if (next_token.edge === 'case') { - expected_at(indent.at - option.indent); - } else if (indent.mode !== 'array' || next_token.line !== token.line) { - expected_at(indent.at); - } - -// If the token is not an edge, but is the first token on the line. - - } else if (next_token.line !== token.line) { - if (next_token.from < indent.at + (indent.mode === - 'expression' ? 0 : option.indent)) { - expected_at(indent.at + option.indent); - } - indent.wrap = true; - } - } else if (next_token.line !== token.line) { - if (next_token.edge) { - expected_at(indent.at); - } else { - indent.wrap = true; - if (indent.mode === 'statement' || indent.mode === 'var') { - expected_at(indent.at + option.indent); - } else if (next_token.from < indent.at + (indent.mode === - 'expression' ? 0 : option.indent)) { - expected_at(indent.at + option.indent); - } - } - } - } - - switch (token.id) { - case '(number)': - if (next_token.id === '.') { - warn('trailing_decimal_a'); - } - break; - case '-': - if (next_token.id === '-' || next_token.id === '--') { - warn('confusing_a'); - } - break; - case '+': - if (next_token.id === '+' || next_token.id === '++') { - warn('confusing_a'); - } - break; - } - if (token.arity === 'string' || token.identifier) { - anonname = token.value; - } - - if (id && next_token.id !== id) { - if (match) { - warn('expected_a_b_from_c_d', next_token, id, - match.id, match.line, next_token.value); - } else if (!next_token.identifier || next_token.value !== id) { - warn('expected_a_b', next_token, id, next_token.value); - } - } - prev_token = token; - token = next_token; - next_token = lookahead.shift() || lex.token(); - if (token.id === '(end)') { - discard(); - } - } - - - function directive() { - var command = this.id, - name, - old_comments_off = comments_off, - old_option_white = option.white, - value; - if (next_token.line === token.line && next_token.from === token.thru) { - warn('missing_space_a_b', next_token, token.value, next_token.value); - } - comments_off = true; - option.white = false; - if (lookahead.length > 0 || next_token.comments) { - warn('unexpected_a', this); - } - switch (command) { - case '/*properties': - case '/*property': - case '/*members': - case '/*member': - command = '/*properties'; - if (!properties) { - properties = {}; - } - break; - case '/*jslint': - if (option.safe) { - warn('adsafe_a', this); - } - break; - case '/*globals': - case '/*global': - command = '/*global'; - if (option.safe) { - warn('adsafe_a', this); - } - break; - default: - stop('unpexpected_a', this); - } -loop: for (;;) { - for (;;) { - if (next_token.id === '*/') { - break loop; - } - if (next_token.id !== ',') { - break; - } - advance(); - } - if (next_token.arity !== 'string' && !next_token.identifier) { - stop('unexpected_a', next_token); - } - name = next_token.value; - advance(); - switch (command) { - case '/*global': - if (next_token.id === ':') { - advance(':'); - switch (next_token.id) { - case 'true': - if (typeof scope[name] === 'object' || - global[name] === false) { - stop('unexpected_a'); - } - global[name] = true; - advance('true'); - break; - case 'false': - if (typeof scope[name] === 'object') { - stop('unexpected_a'); - } - global[name] = false; - advance('false'); - break; - default: - stop('unexpected_a'); - } - } else { - if (typeof scope[name] === 'object') { - stop('unexpected_a'); - } - global[name] = false; - } - break; - case '/*jslint': - if (next_token.id !== ':') { - stop('expected_a_b', next_token, ':', next_token.value); - } - advance(':'); - switch (name) { - case 'indent': - value = +next_token.value; - if (typeof value !== 'number' || - !isFinite(value) || value < 0 || - Math.floor(value) !== value) { - stop('expected_small_a'); - } - if (value > 0) { - old_option_white = true; - } - option.indent = value; - break; - case 'maxerr': - value = +next_token.value; - if (typeof value !== 'number' || - !isFinite(value) || - value <= 0 || - Math.floor(value) !== value) { - stop('expected_small_a', next_token); - } - option.maxerr = value; - break; - case 'maxlen': - value = +next_token.value; - if (typeof value !== 'number' || !isFinite(value) || value < 0 || - Math.floor(value) !== value) { - stop('expected_small_a'); - } - option.maxlen = value; - break; - case 'white': - if (next_token.id === 'true') { - old_option_white = true; - } else if (next_token.id === 'false') { - old_option_white = false; - } else { - stop('unexpected_a'); - } - break; - default: - if (next_token.id === 'true') { - option[name] = true; - } else if (next_token.id === 'false') { - option[name] = false; - } else { - stop('unexpected_a'); - } - } - advance(); - break; - case '/*properties': - properties[name] = true; - break; - default: - stop('unexpected_a'); - } - } - if (command === '/*jslint') { - assume(); - } - comments_off = old_comments_off; - advance('*/'); - option.white = old_option_white; - } - - -// Indentation intention - - function edge(mode) { - next_token.edge = !indent || (indent.open && (mode || true)); - } - - - function step_in(mode) { - var open, was; - if (typeof mode === 'number') { - indent = { - at: mode, - open: true, - was: was - }; - } else if (!indent) { - indent = { - at: 1, - mode: 'statement', - open: true - }; - } else { - was = indent; - open = mode === 'var' || - (next_token.line !== token.line && mode !== 'statement'); - indent = { - at: (open || mode === 'control' ? - was.at + option.indent : was.at) + - (was.wrap ? option.indent : 0), - mode: mode, - open: open, - was: was - }; - if (mode === 'var' && open) { - var_mode = indent; - } - } - } - - function step_out(id, symbol) { - if (id) { - if (indent && indent.open) { - indent.at -= option.indent; - edge(); - } - advance(id, symbol); - } - if (indent) { - indent = indent.was; - } - } - -// Functions for conformance of whitespace. - - function one_space(left, right) { - left = left || token; - right = right || next_token; - if (right.id !== '(end)' && option.white && - (token.line !== right.line || - token.thru + 1 !== right.from)) { - warn('expected_space_a_b', right, token.value, right.value); - } - } - - function one_space_only(left, right) { - left = left || token; - right = right || next_token; - if (right.id !== '(end)' && (left.line !== right.line || - (option.white && left.thru + 1 !== right.from))) { - warn('expected_space_a_b', right, left.value, right.value); - } - } - - function no_space(left, right) { - left = left || token; - right = right || next_token; - if ((option.white || xmode === 'styleproperty' || xmode === 'style') && - left.thru !== right.from && left.line === right.line) { - warn('unexpected_space_a_b', right, left.value, right.value); - } - } - - function no_space_only(left, right) { - left = left || token; - right = right || next_token; - if (right.id !== '(end)' && (left.line !== right.line || - (option.white && left.thru !== right.from))) { - warn('unexpected_space_a_b', right, left.value, right.value); - } - } - - function spaces(left, right) { - if (option.white) { - left = left || token; - right = right || next_token; - if (left.thru === right.from && left.line === right.line) { - warn('missing_space_a_b', right, left.value, right.value); - } - } - } - - function comma() { - if (next_token.id !== ',') { - warn_at('expected_a_b', token.line, token.thru, ',', next_token.value); - } else { - if (option.white) { - no_space_only(); - } - advance(','); - discard(); - spaces(); - } - } - - - function semicolon() { - if (next_token.id !== ';') { - warn_at('expected_a_b', token.line, token.thru, ';', next_token.value); - } else { - if (option.white) { - no_space_only(); - } - advance(';'); - discard(); - if (semicolon_coda[next_token.id] !== true) { - spaces(); - } - } - } - - function use_strict() { - if (next_token.value === 'use strict') { - if (strict_mode) { - warn('unnecessary_use'); - } - edge(); - advance(); - semicolon(); - strict_mode = true; - option.newcap = true; - option.undef = true; - return true; - } else { - return false; - } - } - - - function are_similar(a, b) { - if (a === b) { - return true; - } - if (Array.isArray(a)) { - if (Array.isArray(b) && a.length === b.length) { - var i; - for (i = 0; i < a.length; i += 1) { - if (!are_similar(a[i], b[i])) { - return false; - } - } - return true; - } - return false; - } - if (Array.isArray(b)) { - return false; - } - if (a.arity === b.arity && a.value === b.value) { - switch (a.arity) { - case 'prefix': - case 'suffix': - case undefined: - return are_similar(a.first, b.first); - case 'infix': - return are_similar(a.first, b.first) && - are_similar(a.second, b.second); - case 'ternary': - return are_similar(a.first, b.first) && - are_similar(a.second, b.second) && - are_similar(a.third, b.third); - case 'function': - case 'regexp': - return false; - default: - return true; - } - } else { - if (a.id === '.' && b.id === '[' && b.arity === 'infix') { - return a.second.value === b.second.value && b.second.arity === 'string'; - } else if (a.id === '[' && a.arity === 'infix' && b.id === '.') { - return a.second.value === b.second.value && a.second.arity === 'string'; - } - } - return false; - } - - -// This is the heart of JSLINT, the Pratt parser. In addition to parsing, it -// is looking for ad hoc lint patterns. We add .fud to Pratt's model, which is -// like .nud except that it is only used on the first token of a statement. -// Having .fud makes it much easier to define statement-oriented languages like -// JavaScript. I retained Pratt's nomenclature. - -// .nud Null denotation -// .fud First null denotation -// .led Left denotation -// lbp Left binding power -// rbp Right binding power - -// They are elements of the parsing method called Top Down Operator Precedence. - - function expression(rbp, initial) { - -// rbp is the right binding power. -// initial indicates that this is the first expression of a statement. - - var left; - if (next_token.id === '(end)') { - stop('unexpected_a', token, next_token.id); - } - advance(); - if (option.safe && typeof predefined[token.value] === 'boolean' && - (next_token.id !== '(' && next_token.id !== '.')) { - warn('adsafe', token); - } - if (initial) { - anonname = 'anonymous'; - funct['(verb)'] = token.value; - } - if (initial === true && token.fud) { - left = token.fud(); - } else { - if (token.nud) { - left = token.nud(); - } else { - if (next_token.arity === 'number' && token.id === '.') { - warn('leading_decimal_a', token, - next_token.value); - advance(); - return token; - } else { - stop('expected_identifier_a', token, token.id); - } - } - while (rbp < next_token.lbp) { - advance(); - if (token.led) { - left = token.led(left); - } else { - stop('expected_operator_a', token, token.id); - } - } - } - return left; - } - - -// Functional constructors for making the symbols that will be inherited by -// tokens. - - function symbol(s, p) { - var x = syntax[s]; - if (!x || typeof x !== 'object') { - syntax[s] = x = { - id: s, - lbp: p, - value: s - }; - } - return x; - } - - - function delim(s) { - return symbol(s, 0); - } - - - function postscript(x) { - x.postscript = true; - return x; - } - - function ultimate(s) { - var x = symbol(s, 0); - x.from = 1; - x.thru = 1; - x.line = 0; - x.edge = true; - s.value = s; - return postscript(x); - } - - - function stmt(s, f) { - var x = delim(s); - x.identifier = x.reserved = true; - x.fud = f; - return x; - } - - function labeled_stmt(s, f) { - var x = stmt(s, f); - x.labeled = true; - } - - function disrupt_stmt(s, f) { - var x = stmt(s, f); - x.disrupt = true; - } - - - function reserve_name(x) { - var c = x.id.charAt(0); - if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) { - x.identifier = x.reserved = true; - } - return x; - } - - - function prefix(s, f) { - var x = symbol(s, 150); - reserve_name(x); - x.nud = (typeof f === 'function') ? f : function () { - if (s === 'typeof') { - one_space(); - } else { - no_space_only(); - } - this.first = expression(150); - this.arity = 'prefix'; - if (this.id === '++' || this.id === '--') { - if (option.plusplus) { - warn('unexpected_a', this); - } else if ((!this.first.identifier || this.first.reserved) && - this.first.id !== '.' && this.first.id !== '[') { - warn('bad_operand', this); - } - } - return this; - }; - return x; - } - - - function type(s, arity, nud) { - var x = delim(s); - x.arity = arity; - if (nud) { - x.nud = nud; - } - return x; - } - - - function reserve(s, f) { - var x = delim(s); - x.identifier = x.reserved = true; - if (typeof f === 'function') { - x.nud = f; - } - return x; - } - - - function reservevar(s, v) { - return reserve(s, function () { - if (typeof v === 'function') { - v(this); - } - return this; - }); - } - - - function infix(s, p, f, w) { - var x = symbol(s, p); - reserve_name(x); - x.led = function (left) { - this.arity = 'infix'; - if (!w) { - spaces(prev_token, token); - spaces(); - } - if (typeof f === 'function') { - return f(left, this); - } else { - this.first = left; - this.second = expression(p); - return this; - } - }; - return x; - } - - function expected_relation(node, message) { - if (node.assign) { - warn(message || bundle.conditional_assignment, node); - } - return node; - } - - function expected_condition(node, message) { - switch (node.id) { - case '[': - case '-': - if (node.arity !== 'infix') { - warn(message || bundle.weird_condition, node); - } - break; - case 'false': - case 'function': - case 'Infinity': - case 'NaN': - case 'null': - case 'true': - case 'undefined': - case 'void': - case '(number)': - case '(regexp)': - case '(string)': - case '{': - warn(message || bundle.weird_condition, node); - break; - case '(': - if (node.first.id === '.' && numbery[node.first.second.value] === true) { - warn(message || bundle.weird_condition, node); - } - break; - } - return node; - } - - function check_relation(node) { - switch (node.arity) { - case 'prefix': - switch (node.id) { - case '{': - case '[': - warn('unexpected_a', node); - break; - case '!': - warn('confusing_a', node); - break; - } - break; - case 'function': - case 'regexp': - warn('unexpected_a', node); - break; - default: - if (node.id === 'NaN') { - warn('isnan', node); - } - } - return node; - } - - - function relation(s, eqeq) { - var x = infix(s, 100, function (left, that) { - check_relation(left); - if (eqeq) { - warn('expected_a_b', that, eqeq, that.id); - } - var right = expression(100); - if (are_similar(left, right) || - ((left.arity === 'string' || left.arity === 'number') && - (right.arity === 'string' || right.arity === 'number'))) { - warn('weird_relation', that); - } - that.first = left; - that.second = check_relation(right); - return that; - }); - return x; - } - - - function assignop(s, bit) { - var x = infix(s, 20, function (left, that) { - var l; - if (option.bitwise && bit) { - warn('unexpected_a', that); - } - that.first = left; - if (funct[left.value] === false) { - warn('read_only', left); - } else if (left['function']) { - warn('a_function', left); - } - if (option.safe) { - l = left; - do { - if (typeof predefined[l.value] === 'boolean') { - warn('adsafe', l); - } - l = l.first; - } while (l); - } - if (left) { - if (left === syntax['function']) { - warn('identifier_function', token); - } - if (left.id === '.' || left.id === '[') { - if (!left.first || left.first.value === 'arguments') { - warn('bad_assignment', that); - } - that.second = expression(19); - if (that.id === '=' && are_similar(that.first, that.second)) { - warn('weird_assignment', that); - } - return that; - } else if (left.identifier && !left.reserved) { - if (funct[left.value] === 'exception') { - warn('assign_exception', left); - } - that.second = expression(19); - if (that.id === '=' && are_similar(that.first, that.second)) { - warn('weird_assignment', that); - } - return that; - } - } - stop('bad_assignment', that); - }); - x.assign = true; - return x; - } - - - function bitwise(s, p) { - return infix(s, p, function (left, that) { - if (option.bitwise) { - warn('unexpected_a', that); - } - that.first = left; - that.second = expression(p); - return that; - }); - } - - - function suffix(s) { - var x = symbol(s, 150); - x.led = function (left) { - no_space_only(prev_token, token); - if (option.plusplus) { - warn('unexpected_a', this); - } else if ((!left.identifier || left.reserved) && - left.id !== '.' && left.id !== '[') { - warn('bad_operand', this); - } - this.first = left; - this.arity = 'suffix'; - return this; - }; - return x; - } - - - function optional_identifier() { - if (next_token.identifier) { - advance(); - if (option.safe && banned[token.value]) { - warn('adsafe_a', token); - } else if (token.reserved && !option.es5) { - warn('expected_identifier_a_reserved', token); - } - return token.value; - } - } - - - function identifier() { - var i = optional_identifier(); - if (i) { - return i; - } - if (token.id === 'function' && next_token.id === '(') { - warn('name_function'); - } else { - stop('expected_identifier_a'); - } - } - - - function statement() { - -// Usually a statement starts a line. Exceptions include the var statement in the -// initialization part of a for statement, and an if after an else. - - var label, old_scope = scope, the_statement; - -// We don't like the empty statement. - - if (next_token.id === ';') { - warn('unexpected_a'); - semicolon(); - return; - } - -// Is this a labeled statement? - - if (next_token.identifier && !next_token.reserved && peek().id === ':') { - edge('label'); - label = next_token; - advance(); - discard(); - advance(':'); - discard(); - scope = Object.create(old_scope); - add_label(label.value, 'label'); - if (next_token.labeled !== true) { - warn('label_a_b', next_token, label.value, next_token.value); - } - if (jx.test(label.value + ':')) { - warn('url', label); - } - next_token.label = label; - } - -// Parse the statement. - - edge(); - step_in('statement'); - the_statement = expression(0, true); - if (the_statement) { - -// Look for the final semicolon. - - if (the_statement.arity === 'statement') { - if (the_statement.id === 'switch' || - (the_statement.block && the_statement.id !== 'do')) { - spaces(); - } else { - semicolon(); - } - } else { - -// If this is an expression statement, determine if it is acceptble. -// We do not like -// new Blah(); -// statments. If it is to be used at all, new should only be used to make -// objects, not side effects. The expression statements we do like do -// assignment or invocation or delete. - - if (the_statement.id === '(') { - if (the_statement.first.id === 'new') { - warn('bad_new'); - } - } else if (!the_statement.assign && - the_statement.id !== 'delete' && - the_statement.id !== '++' && - the_statement.id !== '--') { - warn('assignment_function_expression', token); - } - semicolon(); - } - } - step_out(); - scope = old_scope; - return the_statement; - } - - - function statements() { - var array = [], disruptor, the_statement; - -// A disrupt statement may not be followed by any other statement. -// If the last statement is disrupt, then the sequence is disrupt. - - while (next_token.postscript !== true) { - if (next_token.id === ';') { - warn('unexpected_a', next_token); - semicolon(); - } else { - if (disruptor) { - warn('unreachable_a_b', next_token, next_token.value, - disruptor.value); - disruptor = null; - } - the_statement = statement(); - if (the_statement) { - array.push(the_statement); - if (the_statement.disrupt) { - disruptor = the_statement; - array.disrupt = true; - } - } - } - } - return array; - } - - - function block(ordinary) { - -// array block is array sequence of statements wrapped in braces. -// ordinary is false for function bodies and try blocks. -// ordinary is true for if statements, while, etc. - - var array, - curly = next_token, - old_inblock = in_block, - old_scope = scope, - old_strict_mode = strict_mode; - - in_block = ordinary; - scope = Object.create(scope); - spaces(); - if (next_token.id === '{') { - advance('{'); - step_in(); - if (!ordinary && !use_strict() && !old_strict_mode && - option.strict && funct['(context)']['(global)']) { - warn('missing_use_strict'); - } - array = statements(); - strict_mode = old_strict_mode; - step_out('}', curly); - discard(); - } else if (!ordinary) { - stop('expected_a_b', next_token, '{', next_token.value); - } else { - warn('expected_a_b', next_token, '{', next_token.value); - array = [statement()]; - array.disrupt = array[0].disrupt; - } - funct['(verb)'] = null; - scope = old_scope; - in_block = old_inblock; - if (ordinary && array.length === 0) { - warn('empty_block'); - } - return array; - } - - - function tally_property(name) { - if (properties && typeof properties[name] !== 'boolean') { - warn('unexpected_property_a', token, name); - } - if (typeof member[name] === 'number') { - member[name] += 1; - } else { - member[name] = 1; - } - } - - - function note_implied(token) { - var name = token.value, line = token.line, a = implied[name]; - if (typeof a === 'function') { - a = false; - } - if (!a) { - a = [line]; - implied[name] = a; - } else if (a[a.length - 1] !== line) { - a.push(line); - } - } - - -// ECMAScript parser - - syntax['(identifier)'] = { - type: '(identifier)', - lbp: 0, - identifier: true, - nud: function () { - var variable = this.value, - site = scope[variable]; - if (typeof site === 'function') { - site = undefined; - } - -// The name is in scope and defined in the current function. - - if (funct === site) { - -// Change 'unused' to 'var', and reject labels. - - switch (funct[variable]) { - case 'error': - warn('unexpected_a', token); - funct[variable] = 'var'; - break; - case 'unused': - funct[variable] = 'var'; - break; - case 'unparam': - funct[variable] = 'parameter'; - break; - case 'unction': - funct[variable] = 'function'; - this['function'] = true; - break; - case 'function': - this['function'] = true; - break; - case 'label': - warn('a_label', token, variable); - break; - } - -// The name is not defined in the function. If we are in the global scope, -// then we have an undefined variable. - - } else if (funct['(global)']) { - if (typeof global[variable] === 'boolean') { - funct[variable] = global[variable]; - } else { - if (option.undef) { - warn('not_a_defined', token, variable); - } else { - note_implied(token); - } - } - -// If the name is already defined in the current -// function, but not as outer, then there is a scope error. - - } else { - switch (funct[variable]) { - case 'closure': - case 'function': - case 'var': - case 'unused': - warn('a_scope', token, variable); - break; - case 'label': - warn('a_label', token, variable); - break; - case 'outer': - case true: - case false: - break; - default: - -// If the name is defined in an outer function, make an outer entry, and if -// it was unused, make it var. - - if (typeof site === 'boolean') { - funct[variable] = site; - functions[0][variable] = true; - } else if (site === null) { - warn('a_not_allowed', token, variable); - note_implied(token); - } else if (typeof site !== 'object') { - if (option.undef) { - warn('a_not_defined', token, variable); - } else { - funct[variable] = true; - } - note_implied(token); - } else { - switch (site[variable]) { - case 'function': - case 'unction': - this['function'] = true; - site[variable] = 'closure'; - funct[variable] = site['(global)'] ? false : 'outer'; - break; - case 'var': - case 'unused': - site[variable] = 'closure'; - funct[variable] = site['(global)'] ? true : 'outer'; - break; - case 'closure': - case 'parameter': - funct[variable] = site['(global)'] ? true : 'outer'; - break; - case 'unparam': - site[variable] = 'parameter'; - funct[variable] = site['(global)'] ? false : 'outer'; - break; - case 'error': - warn('not_a_defined', token); - break; - case 'label': - warn('a_label', token, variable); - break; - } - } - } - } - return this; - }, - led: function () { - stop('expected_operator_a'); - } - }; - -// Build the syntax table by declaring the syntactic elements. - - type('(color)', 'color'); - type('(number)', 'number', return_this); - type('(string)', 'string', return_this); - type('(range)', 'range'); - type('(regexp)', 'regexp', return_this); - - ultimate('(begin)'); - ultimate('(end)'); - ultimate('(error)'); - postscript(delim(''); - postscript(delim('}')); - delim(')'); - delim(']'); - postscript(delim('"')); - postscript(delim('\'')); - delim(';'); - delim(':'); - delim(','); - delim('#'); - delim('@'); - delim('*/'); - postscript(reserve('case')); - reserve('catch'); - postscript(reserve('default')); - reserve('else'); - reserve('finally'); - - reservevar('arguments', function (x) { - if (strict_mode && funct['(global)']) { - warn('strict', x); - } else if (option.safe) { - warn('adsafe', x); - } - }); - reservevar('eval', function (x) { - if (option.safe) { - warn('adsafe', x); - } - }); - reservevar('false'); - reservevar('Infinity'); - reservevar('NaN'); - reservevar('null'); - reservevar('this', function (x) { - if (strict_mode && ((funct['(statement)'] && - funct['(name)'].charAt(0) > 'Z') || funct['(global)'])) { - warn('strict', x); - } else if (option.safe) { - warn('adsafe', x); - } - }); - reservevar('true'); - reservevar('undefined'); - - assignop('='); - assignop('+='); - assignop('-='); - assignop('*='); - assignop('/=').nud = function () { - stop('slash_equal'); - }; - assignop('%='); - assignop('&=', true); - assignop('|=', true); - assignop('^=', true); - assignop('<<=', true); - assignop('>>=', true); - assignop('>>>=', true); - - infix('?', 30, function (left, that) { - that.first = expected_condition(expected_relation(left)); - that.second = expression(0); - spaces(); - advance(':'); - discard(); - spaces(); - that.third = expression(10); - that.arity = 'ternary'; - if (are_similar(that.second, that.third)) { - warn('weird_ternary', that); - } - return that; - }); - - infix('||', 40, function (left, that) { - function paren_check(that) { - if (that.id === '&&' && !that.paren) { - warn('and', that); - } - return that; - } - - that.first = paren_check(expected_condition(expected_relation(left))); - that.second = paren_check(expected_relation(expression(40))); - if (are_similar(that.first, that.second)) { - warn('weird_condition', that); - } - return that; - }); - - infix('&&', 50, function (left, that) { - that.first = expected_condition(expected_relation(left)); - that.second = expected_relation(expression(50)); - if (are_similar(that.first, that.second)) { - warn('weird_condition', that); - } - return that; - }); - - prefix('void', function () { - this.first = expression(0); - if (this.first.arity !== 'number' || this.first.value) { - warn('unexpected_a', this); - return this; - } - return this; - }); - - bitwise('|', 70); - bitwise('^', 80); - bitwise('&', 90); - - relation('==', '==='); - relation('==='); - relation('!=', '!=='); - relation('!=='); - relation('<'); - relation('>'); - relation('<='); - relation('>='); - - bitwise('<<', 120); - bitwise('>>', 120); - bitwise('>>>', 120); - - infix('in', 120, function (left, that) { - warn('infix_in', that); - that.left = left; - that.right = expression(130); - return that; - }); - infix('instanceof', 120); - infix('+', 130, function (left, that) { - if (!left.value) { - if (left.arity === 'number') { - warn('unexpected_a', left); - } else if (left.arity === 'string') { - warn('expected_a_b', left, 'String', '\'\''); - } - } - var right = expression(130); - if (!right.value) { - if (right.arity === 'number') { - warn('unexpected_a', right); - } else if (right.arity === 'string') { - warn('expected_a_b', right, 'String', '\'\''); - } - } - if (left.arity === right.arity && - (left.arity === 'string' || left.arity === 'number')) { - left.value += right.value; - left.thru = right.thru; - if (left.arity === 'string' && jx.test(left.value)) { - warn('url', left); - } - discard(right); - discard(that); - return left; - } - that.first = left; - that.second = right; - return that; - }); - prefix('+', 'num'); - prefix('+++', function () { - warn('confusing_a', token); - this.first = expression(150); - this.arity = 'prefix'; - return this; - }); - infix('+++', 130, function (left) { - warn('confusing_a', token); - this.first = left; - this.second = expression(130); - return this; - }); - infix('-', 130, function (left, that) { - if ((left.arity === 'number' && left.value === 0) || left.arity === 'string') { - warn('unexpected_a', left); - } - var right = expression(130); - if ((right.arity === 'number' && right.value === 0) || right.arity === 'string') { - warn('unexpected_a', left); - } - if (left.arity === right.arity && left.arity === 'number') { - left.value -= right.value; - left.thru = right.thru; - discard(right); - discard(that); - return left; - } - that.first = left; - that.second = right; - return that; - }); - prefix('-'); - prefix('---', function () { - warn('confusing_a', token); - this.first = expression(150); - this.arity = 'prefix'; - return this; - }); - infix('---', 130, function (left) { - warn('confusing_a', token); - this.first = left; - this.second = expression(130); - return this; - }); - infix('*', 140, function (left, that) { - if ((left.arity === 'number' && (left.value === 0 || left.value === 1)) || left.arity === 'string') { - warn('unexpected_a', left); - } - var right = expression(140); - if ((right.arity === 'number' && (right.value === 0 || right.value === 1)) || right.arity === 'string') { - warn('unexpected_a', right); - } - if (left.arity === right.arity && left.arity === 'number') { - left.value *= right.value; - left.thru = right.thru; - discard(right); - discard(that); - return left; - } - that.first = left; - that.second = right; - return that; - }); - infix('/', 140, function (left, that) { - if ((left.arity === 'number' && left.value === 0) || left.arity === 'string') { - warn('unexpected_a', left); - } - var right = expression(140); - if ((right.arity === 'number' && (right.value === 0 || right.value === 1)) || right.arity === 'string') { - warn('unexpected_a', right); - } - if (left.arity === right.arity && left.arity === 'number') { - left.value /= right.value; - left.thru = right.thru; - discard(right); - discard(that); - return left; - } - that.first = left; - that.second = right; - return that; - }); - infix('%', 140, function (left, that) { - if ((left.arity === 'number' && (left.value === 0 || left.value === 1)) || left.arity === 'string') { - warn('unexpected_a', left); - } - var right = expression(140); - if ((right.arity === 'number' && right.value === 0) || right.arity === 'string') { - warn('unexpected_a', right); - } - if (left.arity === right.arity && left.arity === 'number') { - left.value %= right.value; - left.thru = right.thru; - discard(right); - discard(that); - return left; - } - that.first = left; - that.second = right; - return that; - }); - - suffix('++'); - prefix('++'); - - suffix('--'); - prefix('--'); - prefix('delete', function () { - one_space(); - var p = expression(0); - if (!p || (p.id !== '.' && p.id !== '[')) { - warn('deleted'); - } - this.first = p; - return this; - }); - - - prefix('~', function () { - no_space_only(); - if (option.bitwise) { - warn('unexpected_a', this); - } - expression(150); - return this; - }); - prefix('!', function () { - no_space_only(); - this.first = expected_condition(expression(150)); - this.arity = 'prefix'; - if (bang[this.first.id] === true) { - warn('confusing_a', this); - } - return this; - }); - prefix('typeof'); - prefix('new', function () { - one_space(); - var c = expression(160), i, p; - this.first = c; - if (c.id !== 'function') { - if (c.identifier) { - switch (c.value) { - case 'Object': - warn('use_object', token); - break; - case 'Array': - if (next_token.id === '(') { - p = next_token; - p.first = this; - advance('('); - if (next_token.id !== ')') { - p.second = expression(0); - if (p.second.arity !== 'number' || !p.second.value) { - expected_condition(p.second, bundle.use_array); - i = false; - } else { - i = true; - } - while (next_token.id !== ')' && next_token.id !== '(end)') { - if (i) { - warn('use_array', p); - i = false; - } - advance(); - } - } else { - warn('use_array', token); - } - advance(')', p); - discard(); - return p; - } - warn('use_array', token); - break; - case 'Number': - case 'String': - case 'Boolean': - case 'Math': - case 'JSON': - warn('not_a_constructor', c); - break; - case 'Function': - if (!option.evil) { - warn('function_eval'); - } - break; - case 'Date': - case 'RegExp': - break; - default: - if (c.id !== 'function') { - i = c.value.substr(0, 1); - if (option.newcap && (i < 'A' || i > 'Z')) { - warn('constructor_name_a', token); - } - } - } - } else { - if (c.id !== '.' && c.id !== '[' && c.id !== '(') { - warn('bad_constructor', token); - } - } - } else { - warn('weird_new', this); - } - if (next_token.id !== '(') { - warn('missing_a', next_token, '()'); - } - return this; - }); - - infix('(', 160, function (left, that) { - if (indent && indent.mode === 'expression') { - no_space(prev_token, token); - } else { - no_space_only(prev_token, token); - } - if (!left.immed && left.id === 'function') { - warn('wrap_immediate'); - } - var p = []; - if (left) { - if (left.identifier) { - if (left.value.match(/^[A-Z]([A-Z0-9_$]*[a-z][A-Za-z0-9_$]*)?$/)) { - if (left.value !== 'Number' && left.value !== 'String' && - left.value !== 'Boolean' && left.value !== 'Date') { - if (left.value === 'Math' || left.value === 'JSON') { - warn('not_a_function', left); - } else if (left.value === 'Object') { - warn('use_object', token); - } else if (left.value === 'Array' || option.newcap) { - warn('missing_a', left, 'new'); - } - } - } - } else if (left.id === '.') { - if (option.safe && left.first.value === 'Math' && - left.second === 'random') { - warn('adsafe', left); - } else if (left.second.value === 'split' && - left.first.id === '(string)') { - warn('use_array', left.second); - } - } - } - step_in(); - if (next_token.id !== ')') { - no_space(); - for (;;) { - edge(); - p.push(expression(10)); - if (next_token.id !== ',') { - break; - } - comma(); - } - } - no_space(); - step_out(')', that); - if (typeof left === 'object') { - if (left.value === 'parseInt' && p.length === 1) { - warn('radix', left); - } - if (!option.evil) { - if (left.value === 'eval' || left.value === 'Function' || - left.value === 'execScript') { - warn('evil', left); - } else if (p[0] && p[0].arity === 'string' && - (left.value === 'setTimeout' || - left.value === 'setInterval')) { - warn('implied_evil', left); - } - } - if (!left.identifier && left.id !== '.' && left.id !== '[' && - left.id !== '(' && left.id !== '&&' && left.id !== '||' && - left.id !== '?') { - warn('bad_invocation', left); - } - } - that.first = left; - that.second = p; - return that; - }, true); - - prefix('(', function () { - step_in('expression'); - discard(); - no_space(); - edge(); - if (next_token.id === 'function') { - next_token.immed = true; - } - var value = expression(0); - value.paren = true; - no_space(); - step_out(')', this); - discard(); - if (value.id === 'function') { - if (next_token.id === '(') { - warn('move_invocation'); - } else { - warn('bad_wrap', this); - } - } - return value; - }); - - infix('.', 170, function (left, that) { - no_space(prev_token, token); - no_space(); - var name = identifier(); - if (typeof name === 'string') { - tally_property(name); - } - that.first = left; - that.second = token; - if (left && left.value === 'arguments' && - (name === 'callee' || name === 'caller')) { - warn('avoid_a', left, 'arguments.' + name); - } else if (!option.evil && left && left.value === 'document' && - (name === 'write' || name === 'writeln')) { - warn('write_is_wrong', left); - } else if (option.adsafe) { - if (!adsafe_top && left.value === 'ADSAFE') { - if (name === 'id' || name === 'lib') { - warn('adsafe', that); - } else if (name === 'go') { - if (xmode !== 'script') { - warn('adsafe', that); - } else if (adsafe_went || next_token.id !== '(' || - peek(0).arity !== 'string' || - peek(0).value !== adsafe_id || - peek(1).id !== ',') { - stop('adsafe_a', that, 'go'); - } - adsafe_went = true; - adsafe_may = false; - } - } - adsafe_top = false; - } - if (!option.evil && (name === 'eval' || name === 'execScript')) { - warn('evil'); - } else if (option.safe) { - for (;;) { - if (banned[name] === true) { - warn('adsafe_a', token, name); - } - if (typeof predefined[left.value] !== 'boolean' || - next_token.id === '(') { - break; - } - if (standard_property[name] === true) { - if (next_token.id === '.') { - warn('adsafe', that); - } - break; - } - if (next_token.id !== '.') { - warn('adsafe', that); - break; - } - advance('.'); - token.first = that; - token.second = name; - that = token; - name = identifier(); - if (typeof name === 'string') { - tally_property(name); - } - } - } - return that; - }, true); - - infix('[', 170, function (left, that) { - no_space_only(prev_token, token); - no_space(); - step_in(); - edge(); - var e = expression(0), s; - if (e.arity === 'number') { - if (left.id === 'arguments') { - warn('use_param', left); - } - } else if (e.arity === 'string') { - if (option.safe && (banned[e.value] || - e.value.charAt(0) === '_' || e.value.slice(-1) === '_')) { - warn('adsafe_subscript_a', e); - } else if (!option.evil && - (e.value === 'eval' || e.value === 'execScript')) { - warn('evil', e); - } - tally_property(e.value); - if (!option.sub && ix.test(e.value)) { - s = syntax[e.value]; - if (!s || !s.reserved) { - warn('subscript', e); - } - } - } else if (option.safe) { - if (!((e.arity === 'prefix' && adsafe_prefix[e.id] === true) || - (e.arity === 'infix' && adsafe_infix[e.id] === true))) { - warn('adsafe_subscript_a', e); - } - } - step_out(']', that); - discard(); - no_space(prev_token, token); - that.first = left; - that.second = e; - return that; - }, true); - - prefix('[', function () { - this.arity = 'prefix'; - this.first = []; - step_in('array'); - while (next_token.id !== '(end)') { - while (next_token.id === ',') { - warn('unexpected_a', next_token); - advance(','); - discard(); - } - if (next_token.id === ']') { - break; - } - indent.wrap = false; - edge(); - this.first.push(expression(10)); - if (next_token.id === ',') { - comma(); - if (next_token.id === ']' && !option.es5) { - warn('unexpected_a', token); - break; - } - } else { - break; - } - } - step_out(']', this); - discard(); - return this; - }, 170); - - - function property_name() { - var id = optional_identifier(true); - if (!id) { - if (next_token.arity === 'string') { - id = next_token.value; - if (option.safe) { - if (banned[id]) { - warn('adsafe_a'); - } else if (id.charAt(0) === '_' || - id.charAt(id.length - 1) === '_') { - warn('dangling_a'); - } - } - advance(); - } else if (next_token.arity === 'number') { - id = next_token.value.toString(); - advance(); - } - } - return id; - } - - - function function_params() { - var id, paren = next_token, params = []; - advance('('); - step_in(); - discard(); - no_space(); - if (next_token.id === ')') { - no_space(); - step_out(')', paren); - discard(); - return; - } - for (;;) { - edge(); - id = identifier(); - params.push(token); - add_label(id, 'unparam'); - if (next_token.id === ',') { - comma(); - } else { - no_space(); - step_out(')', paren); - discard(); - return params; - } - } - } - - - function do_function(func, name) { - var old_properties = properties, - old_option = option, - old_global = global, - old_scope = scope; - funct = { - '(name)' : name || '\'' + anonname + '\'', - '(line)' : next_token.line, - '(context)' : funct, - '(breakage)' : 0, - '(loopage)' : 0, - '(scope)' : scope, - '(token)' : func - }; - properties = old_properties && Object.create(old_properties); - option = Object.create(old_option); - global = Object.create(old_global); - scope = Object.create(old_scope); - token.funct = funct; - functions.push(funct); - if (name) { - add_label(name, 'function'); - } - func.name = name || ''; - func.first = funct['(params)'] = function_params(); - one_space(); - func.block = block(false); - funct = funct['(context)']; - properties = old_properties; - option = old_option; - global = old_global; - scope = old_scope; - } - - - prefix('{', function () { - var get, i, j, name, p, set, seen = {}; - this.arity = 'prefix'; - this.first = []; - step_in(); - while (next_token.id !== '}') { - indent.wrap = false; - -// JSLint recognizes the ES5 extension for get/set in object literals, -// but requires that they be used in pairs. - - edge(); - if (next_token.value === 'get' && peek().id !== ':') { - if (!option.es5) { - warn('get_set'); - } - get = next_token; - advance('get'); - one_space_only(); - name = next_token; - i = property_name(); - if (!i) { - stop('missing_property'); - } - do_function(get, ''); - if (funct['(loopage)']) { - warn('function_loop', get); - } - p = get.first; - if (p) { - warn('parameter_a_get_b', p[0], p[0].value, i); - } - comma(); - set = next_token; - spaces(); - edge(); - advance('set'); - one_space_only(); - j = property_name(); - if (i !== j) { - stop('expected_a_b', token, i, j || next_token.value); - } - do_function(set, ''); - p = set.first; - if (!p || p.length !== 1) { - stop('parameter_set_a', set, 'value'); - } else if (p[0].value !== 'value') { - stop('expected_a_b', p[0], 'value', p[0].value); - } - name.first = [get, set]; - } else { - name = next_token; - i = property_name(); - if (typeof i !== 'string') { - stop('missing_property'); - } - advance(':'); - discard(); - spaces(); - name.first = expression(10); - } - this.first.push(name); - if (seen[i] === true) { - warn('duplicate_a', next_token, i); - } - seen[i] = true; - tally_property(i); - if (next_token.id !== ',') { - break; - } - for (;;) { - comma(); - if (next_token.id !== ',') { - break; - } - warn('unexpected_a', next_token); - } - if (next_token.id === '}' && !option.es5) { - warn('unexpected_a', token); - } - } - step_out('}', this); - discard(); - return this; - }); - - stmt('{', function () { - discard(); - warn('statement_block'); - this.arity = 'statement'; - this.block = statements(); - this.disrupt = this.block.disrupt; - advance('}', this); - discard(); - return this; - }); - - stmt('/*global', directive); - stmt('/*globals', directive); - stmt('/*jslint', directive); - stmt('/*member', directive); - stmt('/*members', directive); - stmt('/*property', directive); - stmt('/*properties', directive); - - stmt('var', function () { - -// JavaScript does not have block scope. It only has function scope. So, -// declaring a variable in a block can have unexpected consequences. - -// var.first will contain an array, the array containing name tokens -// and assignment tokens. - - var assign, id, name; - - if (funct['(onevar)'] && option.onevar) { - warn('combine_var'); - } else if (!funct['(global)']) { - funct['(onevar)'] = true; - } - this.arity = 'statement'; - this.first = []; - step_in('var'); - for (;;) { - name = next_token; - id = identifier(); - if (funct['(global)'] && predefined[id] === false) { - warn('redefinition_a', token, id); - } - add_label(id, 'error'); - - if (next_token.id === '=') { - assign = next_token; - assign.first = name; - spaces(); - advance('='); - spaces(); - if (next_token.id === 'undefined') { - warn('unnecessary_initialize', token, id); - } - if (peek(0).id === '=' && next_token.identifier) { - stop('var_a_not'); - } - assign.second = expression(0); - assign.arity = 'infix'; - this.first.push(assign); - } else { - this.first.push(name); - } - funct[id] = 'unused'; - if (next_token.id !== ',') { - break; - } - comma(); - indent.wrap = false; - if (var_mode && next_token.line === token.line && - this.first.length === 1) { - var_mode = false; - indent.open = false; - indent.at -= option.indent; - } - spaces(); - edge(); - } - var_mode = false; - step_out(); - return this; - }); - - stmt('function', function () { - one_space(); - if (in_block) { - warn('function_block', token); - } - var i = identifier(); - if (i) { - add_label(i, 'unction'); - no_space(); - } - do_function(this, i, true); - if (next_token.id === '(' && next_token.line === token.line) { - stop('function_statement'); - } - this.arity = 'statement'; - return this; - }); - - prefix('function', function () { - one_space(); - var i = optional_identifier(); - if (i) { - no_space(); - } - do_function(this, i); - if (funct['(loopage)']) { - warn('function_loop'); - } - this.arity = 'function'; - return this; - }); - - stmt('if', function () { - var paren = next_token; - one_space(); - advance('('); - step_in('control'); - discard(); - no_space(); - edge(); - this.arity = 'statement'; - this.first = expected_condition(expected_relation(expression(0))); - no_space(); - step_out(')', paren); - discard(); - one_space(); - this.block = block(true); - if (next_token.id === 'else') { - one_space(); - advance('else'); - discard(); - one_space(); - this['else'] = next_token.id === 'if' || next_token.id === 'switch' ? - statement(true) : block(true); - if (this['else'].disrupt && this.block.disrupt) { - this.disrupt = true; - } - } - return this; - }); - - stmt('try', function () { - -// try.first The catch variable -// try.second The catch clause -// try.third The finally clause -// try.block The try block - - var exception_variable, old_scope, paren; - if (option.adsafe) { - warn('adsafe_a', this); - } - one_space(); - this.arity = 'statement'; - this.block = block(false); - if (next_token.id === 'catch') { - one_space(); - advance('catch'); - discard(); - one_space(); - paren = next_token; - advance('('); - step_in('control'); - discard(); - no_space(); - edge(); - old_scope = scope; - scope = Object.create(old_scope); - exception_variable = next_token.value; - this.first = exception_variable; - if (!next_token.identifier) { - warn('expected_identifier_a', next_token); - } else { - add_label(exception_variable, 'exception'); - } - advance(); - no_space(); - step_out(')', paren); - discard(); - one_space(); - this.second = block(false); - scope = old_scope; - } - if (next_token.id === 'finally') { - discard(); - one_space(); - advance('finally'); - discard(); - one_space(); - this.third = block(false); - } else if (!this.second) { - stop('expected_a_b', next_token, 'catch', next_token.value); - } - return this; - }); - - labeled_stmt('while', function () { - one_space(); - var paren = next_token; - funct['(breakage)'] += 1; - funct['(loopage)'] += 1; - advance('('); - step_in('control'); - discard(); - no_space(); - edge(); - this.arity = 'statement'; - this.first = expected_relation(expression(0)); - if (this.first.id !== 'true') { - expected_condition(this.first, bundle.unexpected_a); - } - no_space(); - step_out(')', paren); - discard(); - one_space(); - this.block = block(true); - if (this.block.disrupt) { - warn('strange_loop', prev_token); - } - funct['(breakage)'] -= 1; - funct['(loopage)'] -= 1; - return this; - }); - - reserve('with'); - - labeled_stmt('switch', function () { - -// switch.first the switch expression -// switch.second the array of cases. A case is 'case' or 'default' token: -// case.first the array of case expressions -// case.second the array of statements -// If all of the arrays of statements are disrupt, then the switch is disrupt. - - var particular, - the_case = next_token, - unbroken = true; - funct['(breakage)'] += 1; - one_space(); - advance('('); - discard(); - no_space(); - step_in(); - this.arity = 'statement'; - this.first = expected_condition(expected_relation(expression(0))); - no_space(); - step_out(')', the_case); - discard(); - one_space(); - advance('{'); - step_in(); - this.second = []; - while (next_token.id === 'case') { - the_case = next_token; - the_case.first = []; - spaces(); - edge('case'); - advance('case'); - for (;;) { - one_space(); - particular = expression(0); - the_case.first.push(particular); - if (particular.id === 'NaN') { - warn('unexpected_a', particular); - } - no_space_only(); - advance(':'); - discard(); - if (next_token.id !== 'case') { - break; - } - spaces(); - edge('case'); - advance('case'); - discard(); - } - spaces(); - the_case.second = statements(); - if (the_case.second && the_case.second.length > 0) { - particular = the_case.second[the_case.second.length - 1]; - if (particular.disrupt) { - if (particular.id === 'break') { - unbroken = false; - } - } else { - warn('missing_a_after_b', next_token, 'break', 'case'); - } - } else { - warn('empty_case'); - } - this.second.push(the_case); - } - if (this.second.length === 0) { - warn('missing_a', next_token, 'case'); - } - if (next_token.id === 'default') { - spaces(); - the_case = next_token; - edge('case'); - advance('default'); - discard(); - no_space_only(); - advance(':'); - discard(); - spaces(); - the_case.second = statements(); - if (the_case.second && the_case.second.length > 0) { - particular = the_case.second[the_case.second.length - 1]; - if (unbroken && particular.disrupt && particular.id !== 'break') { - this.disrupt = true; - } - } - this.second.push(the_case); - } - funct['(breakage)'] -= 1; - spaces(); - step_out('}', this); - return this; - }); - - stmt('debugger', function () { - if (!option.debug) { - warn('unexpected_a', this); - } - this.arity = 'statement'; - return this; - }); - - labeled_stmt('do', function () { - funct['(breakage)'] += 1; - funct['(loopage)'] += 1; - one_space(); - this.arity = 'statement'; - this.block = block(true); - if (this.block.disrupt) { - warn('strange_loop', prev_token); - } - one_space(); - advance('while'); - discard(); - var paren = next_token; - one_space(); - advance('('); - step_in(); - discard(); - no_space(); - edge(); - this.first = expected_condition(expected_relation(expression(0)), bundle.unexpected_a); - no_space(); - step_out(')', paren); - discard(); - funct['(breakage)'] -= 1; - funct['(loopage)'] -= 1; - return this; - }); - - labeled_stmt('for', function () { - var blok, filter, ok = false, paren = next_token, the_in, value; - this.arity = 'statement'; - funct['(breakage)'] += 1; - funct['(loopage)'] += 1; - advance('('); - step_in('control'); - discard(); - spaces(this, paren); - no_space(); - if (next_token.id === 'var') { - stop('move_var'); - } - edge(); - if (peek(0).id === 'in') { - value = next_token; - switch (funct[value.value]) { - case 'unused': - funct[value.value] = 'var'; - break; - case 'var': - break; - default: - warn('bad_in_a', value); - } - advance(); - the_in = next_token; - advance('in'); - the_in.first = value; - the_in.second = expression(20); - step_out(')', paren); - discard(); - this.first = the_in; - blok = block(true); - if (!option.forin) { - if (blok.length === 1 && typeof blok[0] === 'object' && - blok[0].value === 'if' && !blok[0]['else']) { - filter = blok[0].first; - while (filter.id === '&&') { - filter = filter.first; - } - switch (filter.id) { - case '===': - case '!==': - ok = filter.first.id === '[' ? ( - filter.first.first.value === the_in.second.value && - filter.first.second.value === the_in.first.value - ) : ( - filter.first.id === 'typeof' && - filter.first.first.id === '[' && - filter.first.first.first.value === the_in.second.value && - filter.first.first.second.value === the_in.first.value - ); - break; - case '(': - ok = filter.first.id === '.' && (( - filter.first.first.value === the_in.second.value && - filter.first.second.value === 'hasOwnProperty' && - filter.second[0].value === the_in.first.value - ) || ( - filter.first.first.value === 'ADSAFE' && - filter.first.second.value === 'has' && - filter.second[0].value === the_in.second.value && - filter.second[1].value === the_in.first.value - ) || ( - filter.first.first.id === '.' && - filter.first.first.first.id === '.' && - filter.first.first.first.first.value === 'Object' && - filter.first.first.first.second.value === 'prototype' && - filter.first.first.second.value === 'hasOwnProperty' && - filter.first.second.value === 'call' && - filter.second[0].value === the_in.second.value && - filter.second[1].value === the_in.first.value - )); - break; - } - } - if (!ok) { - warn('for_if', this); - } - } - } else { - if (next_token.id !== ';') { - edge(); - this.first = []; - for (;;) { - this.first.push(expression(0, 'for')); - if (next_token.id !== ',') { - break; - } - comma(); - } - } - semicolon(); - if (next_token.id !== ';') { - edge(); - this.second = expected_relation(expression(0)); - if (this.second.id !== 'true') { - expected_condition(this.second, bundle.unexpected_a); - } - } - semicolon(token); - if (next_token.id === ';') { - stop('expected_a_b', next_token, ')', ';'); - } - if (next_token.id !== ')') { - this.third = []; - edge(); - for (;;) { - this.third.push(expression(0, 'for')); - if (next_token.id !== ',') { - break; - } - comma(); - } - } - no_space(); - step_out(')', paren); - discard(); - one_space(); - blok = block(true); - } - if (blok.disrupt) { - warn('strange_loop', prev_token); - } - this.block = blok; - funct['(breakage)'] -= 1; - funct['(loopage)'] -= 1; - return this; - }); - - disrupt_stmt('break', function () { - var label = next_token.value; - this.arity = 'statement'; - if (funct['(breakage)'] === 0) { - warn('unexpected_a', this); - } - if (next_token.identifier && token.line === next_token.line) { - one_space_only(); - if (funct[label] !== 'label') { - warn('not_a_label', next_token); - } else if (scope[label] !== funct) { - warn('not_a_scope', next_token); - } - this.first = next_token; - advance(); - } - return this; - }); - - disrupt_stmt('continue', function () { - if (!option['continue']) { - warn('unexpected_a', this); - } - var label = next_token.value; - this.arity = 'statement'; - if (funct['(breakage)'] === 0) { - warn('unexpected_a', this); - } - if (next_token.identifier && token.line === next_token.line) { - one_space_only(); - if (funct[label] !== 'label') { - warn('not_a_label', next_token); - } else if (scope[label] !== funct) { - warn('not_a_scope', next_token); - } - this.first = next_token; - advance(); - } - return this; - }); - - disrupt_stmt('return', function () { - this.arity = 'statement'; - if (next_token.id !== ';' && next_token.line === token.line) { - one_space_only(); - if (next_token.id === '/' || next_token.id === '(regexp)') { - warn('wrap_regexp'); - } - this.first = expression(20); - } - return this; - }); - - disrupt_stmt('throw', function () { - this.arity = 'statement'; - one_space_only(); - this.first = expression(20); - return this; - }); - - -// Superfluous reserved words - - reserve('class'); - reserve('const'); - reserve('enum'); - reserve('export'); - reserve('extends'); - reserve('import'); - reserve('super'); - -// Harmony reserved words - - reserve('let'); - reserve('yield'); - reserve('implements'); - reserve('interface'); - reserve('package'); - reserve('private'); - reserve('protected'); - reserve('public'); - reserve('static'); - - -// Parse JSON - - function json_value() { - - function json_object() { - var brace = next_token, object = {}; - advance('{'); - if (next_token.id !== '}') { - while (next_token.id !== '(end)') { - while (next_token.id === ',') { - warn('unexpected_a', next_token); - comma(); - } - if (next_token.arity !== 'string') { - warn('expected_string_a'); - } - if (object[next_token.value] === true) { - warn('duplicate_a'); - } else if (next_token.value === '__proto__') { - warn('dangling_a'); - } else { - object[next_token.value] = true; - } - advance(); - advance(':'); - json_value(); - if (next_token.id !== ',') { - break; - } - comma(); - if (next_token.id === '}') { - warn('unexpected_a', token); - break; - } - } - } - advance('}', brace); - } - - function json_array() { - var bracket = next_token; - advance('['); - if (next_token.id !== ']') { - while (next_token.id !== '(end)') { - while (next_token.id === ',') { - warn('unexpected_a', next_token); - comma(); - } - json_value(); - if (next_token.id !== ',') { - break; - } - comma(); - if (next_token.id === ']') { - warn('unexpected_a', token); - break; - } - } - } - advance(']', bracket); - } - - switch (next_token.id) { - case '{': - json_object(); - break; - case '[': - json_array(); - break; - case 'true': - case 'false': - case 'null': - case '(number)': - case '(string)': - advance(); - break; - case '-': - advance('-'); - no_space_only(); - advance('(number)'); - break; - default: - stop('unexpected_a'); - } - } - - -// CSS parsing. - - function css_name() { - if (next_token.identifier) { - advance(); - return true; - } - } - - - function css_number() { - if (next_token.id === '-') { - advance('-'); - no_space_only(); - } - if (next_token.arity === 'number') { - advance('(number)'); - return true; - } - } - - - function css_string() { - if (next_token.arity === 'string') { - advance(); - return true; - } - } - - function css_color() { - var i, number, paren, value; - if (next_token.identifier) { - value = next_token.value; - if (value === 'rgb' || value === 'rgba') { - advance(); - paren = next_token; - advance('('); - for (i = 0; i < 3; i += 1) { - if (i) { - comma(); - } - number = next_token.value; - if (next_token.arity !== 'number' || number < 0) { - warn('expected_positive_a', next_token); - advance(); - } else { - advance(); - if (next_token.id === '%') { - advance('%'); - if (number > 100) { - warn('expected_percent_a', token, number); - } - } else { - if (number > 255) { - warn('expected_small_a', token, number); - } - } - } - } - if (value === 'rgba') { - comma(); - number = +next_token.value; - if (next_token.arity !== 'number' || number < 0 || number > 1) { - warn('expected_fraction_a', next_token); - } - advance(); - if (next_token.id === '%') { - warn('unexpected_a'); - advance('%'); - } - } - advance(')', paren); - return true; - } else if (css_colorData[next_token.value] === true) { - advance(); - return true; - } - } else if (next_token.id === '(color)') { - advance(); - return true; - } - return false; - } - - - function css_length() { - if (next_token.id === '-') { - advance('-'); - no_space_only(); - } - if (next_token.arity === 'number') { - advance(); - if (next_token.arity !== 'string' && - css_lengthData[next_token.value] === true) { - no_space_only(); - advance(); - } else if (+token.value !== 0) { - warn('expected_linear_a'); - } - return true; - } - return false; - } - - - function css_line_height() { - if (next_token.id === '-') { - advance('-'); - no_space_only(); - } - if (next_token.arity === 'number') { - advance(); - if (next_token.arity !== 'string' && - css_lengthData[next_token.value] === true) { - no_space_only(); - advance(); - } - return true; - } - return false; - } - - - function css_width() { - if (next_token.identifier) { - switch (next_token.value) { - case 'thin': - case 'medium': - case 'thick': - advance(); - return true; - } - } else { - return css_length(); - } - } - - - function css_margin() { - if (next_token.identifier) { - if (next_token.value === 'auto') { - advance(); - return true; - } - } else { - return css_length(); - } - } - - function css_attr() { - if (next_token.identifier && next_token.value === 'attr') { - advance(); - advance('('); - if (!next_token.identifier) { - warn('expected_name_a'); - } - advance(); - advance(')'); - return true; - } - return false; - } - - - function css_comma_list() { - while (next_token.id !== ';') { - if (!css_name() && !css_string()) { - warn('expected_name_a'); - } - if (next_token.id !== ',') { - return true; - } - comma(); - } - } - - - function css_counter() { - if (next_token.identifier && next_token.value === 'counter') { - advance(); - advance('('); - advance(); - if (next_token.id === ',') { - comma(); - if (next_token.arity !== 'string') { - warn('expected_string_a'); - } - advance(); - } - advance(')'); - return true; - } - if (next_token.identifier && next_token.value === 'counters') { - advance(); - advance('('); - if (!next_token.identifier) { - warn('expected_name_a'); - } - advance(); - if (next_token.id === ',') { - comma(); - if (next_token.arity !== 'string') { - warn('expected_string_a'); - } - advance(); - } - if (next_token.id === ',') { - comma(); - if (next_token.arity !== 'string') { - warn('expected_string_a'); - } - advance(); - } - advance(')'); - return true; - } - return false; - } - - - function css_shape() { - var i; - if (next_token.identifier && next_token.value === 'rect') { - advance(); - advance('('); - for (i = 0; i < 4; i += 1) { - if (!css_length()) { - warn('expected_number_a'); - break; - } - } - advance(')'); - return true; - } - return false; - } - - - function css_url() { - var c, url; - if (next_token.identifier && next_token.value === 'url') { - next_token = lex.range('(', ')'); - url = next_token.value; - c = url.charAt(0); - if (c === '"' || c === '\'') { - if (url.slice(-1) !== c) { - warn('bad_url'); - } else { - url = url.slice(1, -1); - if (url.indexOf(c) >= 0) { - warn('bad_url'); - } - } - } - if (!url) { - warn('missing_url'); - } - if (option.safe && ux.test(url)) { - stop('adsafe_a', next_token, url); - } - urls.push(url); - advance(); - return true; - } - return false; - } - - - css_any = [css_url, function () { - for (;;) { - if (next_token.identifier) { - switch (next_token.value.toLowerCase()) { - case 'url': - css_url(); - break; - case 'expression': - warn('unexpected_a'); - advance(); - break; - default: - advance(); - } - } else { - if (next_token.id === ';' || next_token.id === '!' || - next_token.id === '(end)' || next_token.id === '}') { - return true; - } - advance(); - } - } - }]; - - - css_border_style = [ - 'none', 'dashed', 'dotted', 'double', 'groove', - 'hidden', 'inset', 'outset', 'ridge', 'solid' - ]; - - css_break = [ - 'auto', 'always', 'avoid', 'left', 'right' - ]; - - css_media = { - 'all': true, - 'braille': true, - 'embossed': true, - 'handheld': true, - 'print': true, - 'projection': true, - 'screen': true, - 'speech': true, - 'tty': true, - 'tv': true - }; - - css_overflow = [ - 'auto', 'hidden', 'scroll', 'visible' - ]; - - css_attribute_data = { - background: [ - true, 'background-attachment', 'background-color', - 'background-image', 'background-position', 'background-repeat' - ], - 'background-attachment': ['scroll', 'fixed'], - 'background-color': ['transparent', css_color], - 'background-image': ['none', css_url], - 'background-position': [ - 2, [css_length, 'top', 'bottom', 'left', 'right', 'center'] - ], - 'background-repeat': [ - 'repeat', 'repeat-x', 'repeat-y', 'no-repeat' - ], - 'border': [true, 'border-color', 'border-style', 'border-width'], - 'border-bottom': [ - true, 'border-bottom-color', 'border-bottom-style', - 'border-bottom-width' - ], - 'border-bottom-color': css_color, - 'border-bottom-style': css_border_style, - 'border-bottom-width': css_width, - 'border-collapse': ['collapse', 'separate'], - 'border-color': ['transparent', 4, css_color], - 'border-left': [ - true, 'border-left-color', 'border-left-style', 'border-left-width' - ], - 'border-left-color': css_color, - 'border-left-style': css_border_style, - 'border-left-width': css_width, - 'border-right': [ - true, 'border-right-color', 'border-right-style', - 'border-right-width' - ], - 'border-right-color': css_color, - 'border-right-style': css_border_style, - 'border-right-width': css_width, - 'border-spacing': [2, css_length], - 'border-style': [4, css_border_style], - 'border-top': [ - true, 'border-top-color', 'border-top-style', 'border-top-width' - ], - 'border-top-color': css_color, - 'border-top-style': css_border_style, - 'border-top-width': css_width, - 'border-width': [4, css_width], - bottom: [css_length, 'auto'], - 'caption-side' : ['bottom', 'left', 'right', 'top'], - clear: ['both', 'left', 'none', 'right'], - clip: [css_shape, 'auto'], - color: css_color, - content: [ - 'open-quote', 'close-quote', 'no-open-quote', 'no-close-quote', - css_string, css_url, css_counter, css_attr - ], - 'counter-increment': [ - css_name, 'none' - ], - 'counter-reset': [ - css_name, 'none' - ], - cursor: [ - css_url, 'auto', 'crosshair', 'default', 'e-resize', 'help', 'move', - 'n-resize', 'ne-resize', 'nw-resize', 'pointer', 's-resize', - 'se-resize', 'sw-resize', 'w-resize', 'text', 'wait' - ], - direction: ['ltr', 'rtl'], - display: [ - 'block', 'compact', 'inline', 'inline-block', 'inline-table', - 'list-item', 'marker', 'none', 'run-in', 'table', 'table-caption', - 'table-cell', 'table-column', 'table-column-group', - 'table-footer-group', 'table-header-group', 'table-row', - 'table-row-group' - ], - 'empty-cells': ['show', 'hide'], - 'float': ['left', 'none', 'right'], - font: [ - 'caption', 'icon', 'menu', 'message-box', 'small-caption', - 'status-bar', true, 'font-size', 'font-style', 'font-weight', - 'font-family' - ], - 'font-family': css_comma_list, - 'font-size': [ - 'xx-small', 'x-small', 'small', 'medium', 'large', 'x-large', - 'xx-large', 'larger', 'smaller', css_length - ], - 'font-size-adjust': ['none', css_number], - 'font-stretch': [ - 'normal', 'wider', 'narrower', 'ultra-condensed', - 'extra-condensed', 'condensed', 'semi-condensed', - 'semi-expanded', 'expanded', 'extra-expanded' - ], - 'font-style': [ - 'normal', 'italic', 'oblique' - ], - 'font-variant': [ - 'normal', 'small-caps' - ], - 'font-weight': [ - 'normal', 'bold', 'bolder', 'lighter', css_number - ], - height: [css_length, 'auto'], - left: [css_length, 'auto'], - 'letter-spacing': ['normal', css_length], - 'line-height': ['normal', css_line_height], - 'list-style': [ - true, 'list-style-image', 'list-style-position', 'list-style-type' - ], - 'list-style-image': ['none', css_url], - 'list-style-position': ['inside', 'outside'], - 'list-style-type': [ - 'circle', 'disc', 'square', 'decimal', 'decimal-leading-zero', - 'lower-roman', 'upper-roman', 'lower-greek', 'lower-alpha', - 'lower-latin', 'upper-alpha', 'upper-latin', 'hebrew', 'katakana', - 'hiragana-iroha', 'katakana-oroha', 'none' - ], - margin: [4, css_margin], - 'margin-bottom': css_margin, - 'margin-left': css_margin, - 'margin-right': css_margin, - 'margin-top': css_margin, - 'marker-offset': [css_length, 'auto'], - 'max-height': [css_length, 'none'], - 'max-width': [css_length, 'none'], - 'min-height': css_length, - 'min-width': css_length, - opacity: css_number, - outline: [true, 'outline-color', 'outline-style', 'outline-width'], - 'outline-color': ['invert', css_color], - 'outline-style': [ - 'dashed', 'dotted', 'double', 'groove', 'inset', 'none', - 'outset', 'ridge', 'solid' - ], - 'outline-width': css_width, - overflow: css_overflow, - 'overflow-x': css_overflow, - 'overflow-y': css_overflow, - padding: [4, css_length], - 'padding-bottom': css_length, - 'padding-left': css_length, - 'padding-right': css_length, - 'padding-top': css_length, - 'page-break-after': css_break, - 'page-break-before': css_break, - position: ['absolute', 'fixed', 'relative', 'static'], - quotes: [8, css_string], - right: [css_length, 'auto'], - 'table-layout': ['auto', 'fixed'], - 'text-align': ['center', 'justify', 'left', 'right'], - 'text-decoration': [ - 'none', 'underline', 'overline', 'line-through', 'blink' - ], - 'text-indent': css_length, - 'text-shadow': ['none', 4, [css_color, css_length]], - 'text-transform': ['capitalize', 'uppercase', 'lowercase', 'none'], - top: [css_length, 'auto'], - 'unicode-bidi': ['normal', 'embed', 'bidi-override'], - 'vertical-align': [ - 'baseline', 'bottom', 'sub', 'super', 'top', 'text-top', 'middle', - 'text-bottom', css_length - ], - visibility: ['visible', 'hidden', 'collapse'], - 'white-space': [ - 'normal', 'nowrap', 'pre', 'pre-line', 'pre-wrap', 'inherit' - ], - width: [css_length, 'auto'], - 'word-spacing': ['normal', css_length], - 'word-wrap': ['break-word', 'normal'], - 'z-index': ['auto', css_number] - }; - - function style_attribute() { - var v; - while (next_token.id === '*' || next_token.id === '#' || - next_token.value === '_') { - if (!option.css) { - warn('unexpected_a'); - } - advance(); - } - if (next_token.id === '-') { - if (!option.css) { - warn('unexpected_a'); - } - advance('-'); - if (!next_token.identifier) { - warn('expected_nonstandard_style_attribute'); - } - advance(); - return css_any; - } else { - if (!next_token.identifier) { - warn('expected_style_attribute'); - } else { - if (Object.prototype.hasOwnProperty.call(css_attribute_data, next_token.value)) { - v = css_attribute_data[next_token.value]; - } else { - v = css_any; - if (!option.css) { - warn('unrecognized_style_attribute_a'); - } - } - } - advance(); - return v; - } - } - - - function style_value(v) { - var i = 0, - n, - once, - match, - round, - start = 0, - vi; - switch (typeof v) { - case 'function': - return v(); - case 'string': - if (next_token.identifier && next_token.value === v) { - advance(); - return true; - } - return false; - } - for (;;) { - if (i >= v.length) { - return false; - } - vi = v[i]; - i += 1; - if (vi === true) { - break; - } else if (typeof vi === 'number') { - n = vi; - vi = v[i]; - i += 1; - } else { - n = 1; - } - match = false; - while (n > 0) { - if (style_value(vi)) { - match = true; - n -= 1; - } else { - break; - } - } - if (match) { - return true; - } - } - start = i; - once = []; - for (;;) { - round = false; - for (i = start; i < v.length; i += 1) { - if (!once[i]) { - if (style_value(css_attribute_data[v[i]])) { - match = true; - round = true; - once[i] = true; - break; - } - } - } - if (!round) { - return match; - } - } - } - - function style_child() { - if (next_token.arity === 'number') { - advance(); - if (next_token.value === 'n' && next_token.identifier) { - no_space_only(); - advance(); - if (next_token.id === '+') { - no_space_only(); - advance('+'); - no_space_only(); - advance('(number)'); - } - } - return; - } else { - if (next_token.identifier && - (next_token.value === 'odd' || next_token.value === 'even')) { - advance(); - return; - } - } - warn('unexpected_a'); - } - - function substyle() { - var v; - for (;;) { - if (next_token.id === '}' || next_token.id === '(end)' || - (xquote && next_token.id === xquote)) { - return; - } - while (next_token.id === ';') { - warn('unexpected_a'); - semicolon(); - } - v = style_attribute(); - advance(':'); - if (next_token.identifier && next_token.value === 'inherit') { - advance(); - } else { - if (!style_value(v)) { - warn('unexpected_a'); - advance(); - } - } - if (next_token.id === '!') { - advance('!'); - no_space_only(); - if (next_token.identifier && next_token.value === 'important') { - advance(); - } else { - warn('expected_a_b', - next_token, 'important', next_token.value); - } - } - if (next_token.id === '}' || next_token.id === xquote) { - warn('expected_a_b', next_token, ';', next_token.value); - } else { - semicolon(); - } - } - } - - function style_selector() { - if (next_token.identifier) { - if (!Object.prototype.hasOwnProperty.call(html_tag, option.cap ? - next_token.value.toLowerCase() : next_token.value)) { - warn('expected_tagname_a'); - } - advance(); - } else { - switch (next_token.id) { - case '>': - case '+': - advance(); - style_selector(); - break; - case ':': - advance(':'); - switch (next_token.value) { - case 'active': - case 'after': - case 'before': - case 'checked': - case 'disabled': - case 'empty': - case 'enabled': - case 'first-child': - case 'first-letter': - case 'first-line': - case 'first-of-type': - case 'focus': - case 'hover': - case 'last-child': - case 'last-of-type': - case 'link': - case 'only-of-type': - case 'root': - case 'target': - case 'visited': - advance(); - break; - case 'lang': - advance(); - advance('('); - if (!next_token.identifier) { - warn('expected_lang_a'); - } - advance(')'); - break; - case 'nth-child': - case 'nth-last-child': - case 'nth-last-of-type': - case 'nth-of-type': - advance(); - advance('('); - style_child(); - advance(')'); - break; - case 'not': - advance(); - advance('('); - if (next_token.id === ':' && peek(0).value === 'not') { - warn('not'); - } - style_selector(); - advance(')'); - break; - default: - warn('expected_pseudo_a'); - } - break; - case '#': - advance('#'); - if (!next_token.identifier) { - warn('expected_id_a'); - } - advance(); - break; - case '*': - advance('*'); - break; - case '.': - advance('.'); - if (!next_token.identifier) { - warn('expected_class_a'); - } - advance(); - break; - case '[': - advance('['); - if (!next_token.identifier) { - warn('expected_attribute_a'); - } - advance(); - if (next_token.id === '=' || next_token.value === '~=' || - next_token.value === '$=' || - next_token.value === '|=' || - next_token.id === '*=' || - next_token.id === '^=') { - advance(); - if (next_token.arity !== 'string') { - warn('expected_string_a'); - } - advance(); - } - advance(']'); - break; - default: - stop('expected_selector_a'); - } - } - } - - function style_pattern() { - if (next_token.id === '{') { - warn('expected_style_pattern'); - } - for (;;) { - style_selector(); - if (next_token.id === '= 0) { - warn('unexpected_char_a_b', token, v.charAt(x), a); - } - ids[u] = true; - } else if (a === 'class' || a === 'type' || a === 'name') { - x = v.search(qx); - if (x >= 0) { - warn('unexpected_char_a_b', token, v.charAt(x), a); - } - ids[u] = true; - } else if (a === 'href' || a === 'background' || - a === 'content' || a === 'data' || - a.indexOf('src') >= 0 || a.indexOf('url') >= 0) { - if (option.safe && ux.test(v)) { - stop('bad_url', next_token, v); - } - urls.push(v); - } else if (a === 'for') { - if (option.adsafe) { - if (adsafe_id) { - if (v.slice(0, adsafe_id.length) !== adsafe_id) { - warn('adsafe_prefix_a', next_token, adsafe_id); - } else if (!/^[A-Z]+_[A-Z]+$/.test(v)) { - warn('adsafe_bad_id'); - } - } else { - warn('adsafe_bad_id'); - } - } - } else if (a === 'name') { - if (option.adsafe && v.indexOf('_') >= 0) { - warn('adsafe_name_a', next_token, v); - } - } - } - - function do_tag(name, attribute) { - var i, tag = html_tag[name], script, x; - src = false; - if (!tag) { - stop( - bundle.unrecognized_tag_a, - next_token, - name === name.toLowerCase() ? name : name + ' (capitalization error)' - ); - } - if (stack.length > 0) { - if (name === 'html') { - stop('unexpected_a', token, name); - } - x = tag.parent; - if (x) { - if (x.indexOf(' ' + stack[stack.length - 1].name + ' ') < 0) { - stop('tag_a_in_b', token, name, x); - } - } else if (!option.adsafe && !option.fragment) { - i = stack.length; - do { - if (i <= 0) { - stop('tag_a_in_b', token, name, 'body'); - } - i -= 1; - } while (stack[i].name !== 'body'); - } - } - switch (name) { - case 'div': - if (option.adsafe && stack.length === 1 && !adsafe_id) { - warn('adsafe_missing_id'); - } - break; - case 'script': - xmode = 'script'; - advance('>'); - if (attribute.lang) { - warn('lang', token); - } - if (option.adsafe && stack.length !== 1) { - warn('adsafe_placement', token); - } - if (attribute.src) { - if (option.adsafe && (!adsafe_may || !approved[attribute.src])) { - warn('adsafe_source', token); - } - if (attribute.type) { - warn('type', token); - } - } else { - step_in(next_token.from); - edge(); - use_strict(); - adsafe_top = true; - script = statements(); - -// JSLint is also the static analyzer for ADsafe. See www.ADsafe.org. - - if (option.adsafe) { - if (adsafe_went) { - stop('adsafe_script', token); - } - if (script.length !== 1 || - aint(script[0], 'id', '(') || - aint(script[0].first, 'id', '.') || - aint(script[0].first.first, 'value', 'ADSAFE') || - aint(script[0].second[0], 'value', adsafe_id)) { - stop('adsafe_id_go'); - } - switch (script[0].first.second.value) { - case 'id': - if (adsafe_may || adsafe_went || - script[0].second.length !== 1) { - stop('adsafe_id', next_token); - } - adsafe_may = true; - break; - case 'go': - if (adsafe_went) { - stop('adsafe_go'); - } - if (script[0].second.length !== 2 || - aint(script[0].second[1], 'id', 'function') || - !script[0].second[1].first || - script[0].second[1].first.length !== 2 || - aint(script[0].second[1].first[0], 'value', 'dom') || - aint(script[0].second[1].first[1], 'value', 'lib')) { - stop('adsafe_go', next_token); - } - adsafe_went = true; - break; - default: - stop('adsafe_id_go'); - } - } - indent = null; - } - xmode = 'html'; - advance(''); - styles(); - xmode = 'html'; - advance(''; - } - - function html() { - var attribute, attributes, is_empty, name, old_white = option.white, - quote, tag_name, tag, wmode; - xmode = 'html'; - xquote = ''; - stack = null; - for (;;) { - switch (next_token.value) { - case '<': - xmode = 'html'; - advance('<'); - attributes = {}; - tag_name = next_token; - if (!tag_name.identifier) { - warn('bad_name_a', tag_name); - } - name = tag_name.value; - if (option.cap) { - name = name.toLowerCase(); - } - tag_name.name = name; - advance(); - if (!stack) { - stack = []; - do_begin(name); - } - tag = html_tag[name]; - if (typeof tag !== 'object') { - stop('unrecognized_tag_a', tag_name, name); - } - is_empty = tag.empty; - tag_name.type = name; - for (;;) { - if (next_token.id === '/') { - advance('/'); - if (next_token.id !== '>') { - warn('expected_a_b', next_token, '>', next_token.value); - } - break; - } - if (next_token.id && next_token.id.substr(0, 1) === '>') { - break; - } - if (!next_token.identifier) { - if (next_token.id === '(end)' || next_token.id === '(error)') { - warn('expected_a_b', next_token, '>', next_token.value); - } - warn('bad_name_a'); - } - option.white = true; - spaces(); - attribute = next_token.value; - option.white = old_white; - advance(); - if (!option.cap && attribute !== attribute.toLowerCase()) { - warn('attribute_case_a', token); - } - attribute = attribute.toLowerCase(); - xquote = ''; - if (Object.prototype.hasOwnProperty.call(attributes, attribute)) { - warn('duplicate_a', token, attribute); - } - if (attribute.slice(0, 2) === 'on') { - if (!option.on) { - warn('html_handlers'); - } - xmode = 'scriptstring'; - advance('='); - quote = next_token.id; - if (quote !== '"' && quote !== '\'') { - stop('expected_a_b', next_token, '"', next_token.value); - } - xquote = quote; - wmode = option.white; - option.white = false; - advance(quote); - use_strict(); - statements(); - option.white = wmode; - if (next_token.id !== quote) { - stop('expected_a_b', next_token, quote, next_token.value); - } - xmode = 'html'; - xquote = ''; - advance(quote); - tag = false; - } else if (attribute === 'style') { - xmode = 'scriptstring'; - advance('='); - quote = next_token.id; - if (quote !== '"' && quote !== '\'') { - stop('expected_a_b', next_token, '"', next_token.value); - } - xmode = 'styleproperty'; - xquote = quote; - advance(quote); - substyle(); - xmode = 'html'; - xquote = ''; - advance(quote); - tag = false; - } else { - if (next_token.id === '=') { - advance('='); - tag = next_token.value; - if (!next_token.identifier && - next_token.id !== '"' && - next_token.id !== '\'' && - next_token.arity !== 'string' && - next_token.arity !== 'number' && - next_token.id !== '(color)') { - warn('expected_attribute_value_a', token, attribute); - } - advance(); - } else { - tag = true; - } - } - attributes[attribute] = tag; - do_attribute(attribute, tag); - } - do_tag(name, attributes); - if (!is_empty) { - stack.push(tag_name); - } - xmode = 'outer'; - advance('>'); - break; - case '') { - stop('expected_a_b', next_token, '>', next_token.value); - } - xmode = 'outer'; - advance('>'); - break; - case '' || next_token.id === '(end)') { - break; - } - if (next_token.value.indexOf('--') >= 0) { - stop('unexpected_a', next_token, '--'); - } - if (next_token.value.indexOf('<') >= 0) { - stop('unexpected_a', next_token, '<'); - } - if (next_token.value.indexOf('>') >= 0) { - stop('unexpected_a', next_token, '>'); - } - } - xmode = 'outer'; - advance('>'); - break; - case '(end)': - return; - default: - if (next_token.id === '(end)') { - stop('missing_a', next_token, - ''); - } else { - advance(); - } - } - if (stack && stack.length === 0 && (option.adsafe || - !option.fragment || next_token.id === '(end)')) { - break; - } - } - if (next_token.id !== '(end)') { - stop('unexpected_a'); - } - } - - -// The actual JSLINT function itself. - - var itself = function (the_source, the_option) { - var i, keys, predef, tree; - JSLINT.comments = []; - JSLINT.errors = []; - JSLINT.tree = ''; - begin = older_token = prev_token = token = next_token = - Object.create(syntax['(begin)']); - predefined = Object.create(standard); - if (the_option) { - option = Object.create(the_option); - predef = option.predef; - if (predef) { - if (Array.isArray(predef)) { - for (i = 0; i < predef.length; i += 1) { - predefined[predef[i]] = true; - } - } else if (typeof predef === 'object') { - keys = Object.keys(predef); - for (i = 0; i < keys.length; i += 1) { - predefined[keys[i]] = !!predef[keys]; - } - } - } - if (option.adsafe) { - option.safe = true; - } - if (option.safe) { - option.browser = - option['continue'] = - option.css = - option.debug = - option.devel = - option.evil = - option.forin = - option.on = - option.rhino = - option.sub = - option.widget = - option.windows = false; - - option.nomen = - option.strict = - option.undef = true; - - predefined.Date = - predefined['eval'] = - predefined.Function = - predefined.Object = null; - - predefined.ADSAFE = - predefined.lib = false; - } - } else { - option = {}; - } - option.indent = +option.indent || 0; - option.maxerr = option.maxerr || 50; - adsafe_id = ''; - adsafe_may = adsafe_top = adsafe_went = false; - approved = {}; - if (option.approved) { - for (i = 0; i < option.approved.length; i += 1) { - approved[option.approved[i]] = option.approved[i]; - } - } else { - approved.test = 'test'; - } - tab = ''; - for (i = 0; i < option.indent; i += 1) { - tab += ' '; - } - global = Object.create(predefined); - scope = global; - funct = { - '(global)': true, - '(name)': '(global)', - '(scope)': scope, - '(breakage)': 0, - '(loopage)': 0 - }; - functions = [funct]; - - comments_off = false; - ids = {}; - implied = {}; - in_block = false; - indent = false; - json_mode = false; - lookahead = []; - member = {}; - properties = null; - prereg = true; - src = false; - stack = null; - strict_mode = false; - urls = []; - var_mode = false; - warnings = 0; - xmode = false; - lex.init(the_source); - - assume(); - - try { - advance(); - if (next_token.arity === 'number') { - stop('unexpected_a'); - } else if (next_token.value.charAt(0) === '<') { - html(); - if (option.adsafe && !adsafe_went) { - warn('adsafe_go', this); - } - } else { - switch (next_token.id) { - case '{': - case '[': - json_mode = true; - json_value(); - break; - case '@': - case '*': - case '#': - case '.': - case ':': - xmode = 'style'; - advance(); - if (token.id !== '@' || !next_token.identifier || - next_token.value !== 'charset' || token.line !== 1 || - token.from !== 1) { - stop('css'); - } - advance(); - if (next_token.arity !== 'string' && - next_token.value !== 'UTF-8') { - stop('css'); - } - advance(); - semicolon(); - styles(); - break; - - default: - if (option.adsafe && option.fragment) { - stop('expected_a_b', - next_token, '
', next_token.value); - } - -// If the first token is predef semicolon, ignore it. This is sometimes used when -// files are intended to be appended to files that may be sloppy. predef sloppy -// file may be depending on semicolon insertion on its last line. - - step_in(1); - if (next_token.id === ';') { - semicolon(); - } - if (next_token.value === 'use strict') { - warn('function_strict'); - use_strict(); - } - adsafe_top = true; - tree = statements(); - begin.first = tree; - JSLINT.tree = begin; - if (option.adsafe && (tree.length !== 1 || - aint(tree[0], 'id', '(') || - aint(tree[0].first, 'id', '.') || - aint(tree[0].first.first, 'value', 'ADSAFE') || - aint(tree[0].first.second, 'value', 'lib') || - tree[0].second.length !== 2 || - tree[0].second[0].arity !== 'string' || - aint(tree[0].second[1], 'id', 'function'))) { - stop('adsafe_lib'); - } - if (tree.disrupt) { - warn('weird_program', prev_token); - } - } - } - indent = null; - advance('(end)'); - } catch (e) { - if (e) { // `~ - JSLINT.errors.push({ - reason : e.message, - line : e.line || next_token.line, - character : e.character || next_token.from - }, null); - } - } - return JSLINT.errors.length === 0; - }; - - -// Data summary. - - itself.data = function () { - var data = {functions: []}, - function_data, - globals, - i, - implieds = [], - j, - kind, - members = [], - name, - the_function, - unused = []; - if (itself.errors.length) { - data.errors = itself.errors; - } - - if (json_mode) { - data.json = true; - } - - for (name in implied) { - if (Object.prototype.hasOwnProperty.call(implied, name)) { - implieds.push({ - name: name, - line: implied[name] - }); - } - } - if (implieds.length > 0) { - data.implieds = implieds; - } - - if (urls.length > 0) { - data.urls = urls; - } - - globals = Object.keys(functions[0]).filter(function (value) { - return value.charAt(0) !== '(' ? value : undefined; - }); - if (globals.length > 0) { - data.globals = globals; - } - - for (i = 1; i < functions.length; i += 1) { - the_function = functions[i]; - function_data = {}; - for (j = 0; j < functionicity.length; j += 1) { - function_data[functionicity[j]] = []; - } - for (name in the_function) { - if (Object.prototype.hasOwnProperty.call(the_function, name)) { - if (name.charAt(0) !== '(') { - kind = the_function[name]; - if (kind === 'unction' || - (kind === 'unparam' && !option.unparam)) { - kind = 'unused'; - } else if (typeof kind === 'boolean') { - kind = 'global'; - } - if (Array.isArray(function_data[kind])) { - function_data[kind].push(name); - if (kind === 'unused') { - unused.push({ - name: name, - line: the_function['(line)'], - 'function': the_function['(name)'] - }); - } - } - } - } - } - for (j = 0; j < functionicity.length; j += 1) { - if (function_data[functionicity[j]].length === 0) { - delete function_data[functionicity[j]]; - } - } - function_data.name = the_function['(name)']; - function_data.param = the_function['(params)']; - function_data.line = the_function['(line)']; - data.functions.push(function_data); - } - - if (unused.length > 0) { - data.unused = unused; - } - - members = []; - for (name in member) { - if (typeof member[name] === 'number') { - data.member = member; - break; - } - } - - return data; - }; - - - itself.report = function (errors_only) { - var data = itself.data(); - - var err, evidence, i, j, key, keys, length, mem = '', name, names, - output = [], snippets, the_function, warning; - - function detail(h, array) { - var comma_needed, i, singularity; - if (array) { - output.push('
' + h + ' '); - array = array.sort(); - for (i = 0; i < array.length; i += 1) { - if (array[i] !== singularity) { - singularity = array[i]; - output.push((comma_needed ? ', ' : '') + singularity); - comma_needed = true; - } - } - output.push('
'); - } - } - - if (data.errors || data.implieds || data.unused) { - err = true; - output.push('
Error:'); - if (data.errors) { - for (i = 0; i < data.errors.length; i += 1) { - warning = data.errors[i]; - if (warning) { - evidence = warning.evidence || ''; - output.push('

Problem' + (isFinite(warning.line) ? ' at line ' + - warning.line + ' character ' + warning.character : '') + - ': ' + warning.reason.entityify() + - '

' + - (evidence && (evidence.length > 80 ? evidence.slice(0, 77) + '...' : - evidence).entityify()) + '

'); - } - } - } - - if (data.implieds) { - snippets = []; - for (i = 0; i < data.implieds.length; i += 1) { - snippets[i] = '' + data.implieds[i].name + ' ' + - data.implieds[i].line + ''; - } - output.push('

Implied global: ' + snippets.join(', ') + '

'); - } - - if (data.unused) { - snippets = []; - for (i = 0; i < data.unused.length; i += 1) { - snippets[i] = '' + data.unused[i].name + ' ' + - data.unused[i].line + ' ' + - data.unused[i]['function'] + ''; - } - output.push('

Unused variable: ' + snippets.join(', ') + '

'); - } - if (data.json) { - output.push('

JSON: bad.

'); - } - output.push('
'); - } - - if (!errors_only) { - - output.push('
'); - - if (data.urls) { - detail("URLs
", data.urls, '
'); - } - - if (xmode === 'style') { - output.push('

CSS.

'); - } else if (data.json && !err) { - output.push('

JSON: good.

'); - } else if (data.globals) { - output.push('
Global ' + - data.globals.sort().join(', ') + '
'); - } else { - output.push('
No new global variables introduced.
'); - } - - for (i = 0; i < data.functions.length; i += 1) { - the_function = data.functions[i]; - names = []; - if (the_function.param) { - for (j = 0; j < the_function.param.length; j += 1) { - names[j] = the_function.param[j].value; - } - } - output.push('
' + the_function.line + ' ' + - (the_function.name || '') + '(' + names.join(', ') + ')
'); - detail('Unused', the_function.unused); - detail('Closure', the_function.closure); - detail('Variable', the_function['var']); - detail('Exception', the_function.exception); - detail('Outer', the_function.outer); - detail('Global', the_function.global); - detail('Label', the_function.label); - } - - if (data.member) { - keys = Object.keys(data.member); - if (keys.length) { - keys = keys.sort(); - mem = '
/*properties ';
-                    length = 13;
-                    for (i = 0; i < keys.length; i += 1) {
-                        key = keys[i];
-                        name = ix.test(key) ? key :
-                            '"' + key.entityify().replace(nx, sanitize) + '"';
-                        if (length + name.length > 72) {
-                            output.push(mem + '
'); - mem = ' '; - length = 1; - } - length += name.length + 2; - if (data.member[key] === 1) { - name = '' + name + ''; - } - if (i < keys.length - 1) { - name += ', '; - } - mem += name; - } - output.push(mem + '
*/
'); - } - output.push('
'); - } - } - return output.join(''); - }; - itself.jslint = itself; - - itself.edition = '2011-05-01'; - - return itself; - -}());// rhino.js -// 2009-09-11: Based on Douglas Crockford's Rhino edition -// -// I've made a few changes, specifically the ability to parse -// one file, while displaying the name of another. -// - -/*global JSLINT */ -/*jslint rhino: true, strict: false, onevar: false, white: false, browser: true, - laxbreak: true, undef: true, nomen: false, eqeqeq: true, plusplus: false, bitwise: true,regexp: true, newcap: true, immed: true */ - -(function (a) { - var e, i, input, fileToParse, fileToDisplay, defaults; - if (!a[0]) { - print("Usage: jslint.js file.js [realfilename.js]"); - quit(1); - } - fileToParse = a[ 0 ]; - fileToDisplay = a[ 1 ] ? a[ 1 ] : a[ 0 ]; - input = readFile( fileToParse ); - if (!input) { - print("jslint: Couldn't open file '" + fileToParse + "'."); - quit(1); - } - defaults = { - bitwise: true, // Allow bitwise operators - browser: true, // Assume a browser ( http://www.JSLint.com/lint.html#browser ) - css: true, // Tolerate CSS workarounds ( http://www.JSLint.com/lint.html#css ) - eqeqeq: true, // Require `===` && `!==` - immed: true, // Immediate invocations must be wrapped in parens. - laxbreak: true, // Tolerate "sloppy" line breaks - newcap: true, // Require initial caps for constructors ( http://www.JSLint.com/lint.html#new ) - nomen: false, // Allow dangling `_` in identifiers - onevar: false, // Allow multiple `var` statements. - plusplus: false, // Allow `++` and `--` - regexp: true, // Disallow `.` and `[^...]` in regex - strict: false, // Don't require `use strict;` - undef: true, // Disallow undeclared variables ( http://www.JSLint.com/lint.html#undefined ) - white: false // Don't apply strict whitespace rules - }; - - if (!JSLINT(input, defaults)) { - for (i = 0; i < JSLINT.errors.length; i += 1) { - e = JSLINT.errors[i]; - if (e) { - print('[' + fileToDisplay + '] Lint at line ' + e.line + ' character ' + - e.character + ': ' + e.reason); - print((e.evidence || ''). - replace(/^\s*(\S*(\s+\S+)*)\s*$/, "$1")); - print(''); - } - } - quit(2); - } else { - print("jslint: No problems found in " + fileToDisplay); - quit(); - } -}(arguments)); diff --git a/lib/vendor/Makefile b/lib/vendor/Makefile index e39aef0..b2369d9 100644 --- a/lib/vendor/Makefile +++ b/lib/vendor/Makefile @@ -1,8 +1,6 @@ -.PHONY: all updatejslint ../rhinoed_jslint.js ../node_jslint.js +.PHONY: all updatejslint updateforkedjslint updatejshint updatecsslint ../rhinoed_lint.js -all: updatejslint ../rhinoed_jslint.js ../node_jslint.js - -fork: updateforkedjslint ../rhinoed_jslint.js ../node_jslint.js +all: updatejslint updatejshint updatecsslint ../rhinoed_lint.js updateforkedjslint: @curl -o jslint.js http://github.com/mikewest/jslint/raw/master/fulljslint.js @@ -10,8 +8,13 @@ updateforkedjslint: updatejslint: @curl -O http://www.JSLint.com/jslint.js -../rhinoed_jslint.js: jslint.js ../rhino.js - @cat ./jslint.js ../rhino.js > ../rhinoed_jslint.js +updatejshint: + @curl -O https://raw.github.com/jshint/jshint/master/jshint.js + @cp jshint.js ../jshint.js + +updatecsslint: + @curl -O https://raw.github.com/stubbornella/csslint/master/release/csslint.js -../node_jslint.js: jslint.js ../node.js - @cat ./jslint.js ../node.js > ../node_jslint.js +../rhinoed_lint.js: + @curl -o ../jshint-rhino.js https://raw.github.com/jshint/jshint/master/env/jshint-rhino.js + @curl -o ../csslint-rhino.js https://raw.github.com/stubbornella/csslint/master/release/csslint-rhino.js diff --git a/lib/vendor/csslint.js b/lib/vendor/csslint.js new file mode 100644 index 0000000..2447a8a --- /dev/null +++ b/lib/vendor/csslint.js @@ -0,0 +1,7746 @@ +/*! +CSSLint +Copyright (c) 2011 Nicole Sullivan and Nicholas C. Zakas. All rights reserved. + +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 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. + +*/ +/* Build time: 8-September-2011 08:50:44 */ +var CSSLint = (function(){ +/*! +Parser-Lib +Copyright (c) 2009-2011 Nicholas C. Zakas. All rights reserved. + +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 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. + +*/ +/* Build time: 19-July-2011 01:46:47 */ +var parserlib = {}; +(function(){ + +/** + * A generic base to inherit from for any object + * that needs event handling. + * @class EventTarget + * @constructor + */ +function EventTarget(){ + + /** + * The array of listeners for various events. + * @type Object + * @property _listeners + * @private + */ + this._listeners = {}; +} + +EventTarget.prototype = { + + //restore constructor + constructor: EventTarget, + + /** + * Adds a listener for a given event type. + * @param {String} type The type of event to add a listener for. + * @param {Function} listener The function to call when the event occurs. + * @return {void} + * @method addListener + */ + addListener: function(type, listener){ + if (!this._listeners[type]){ + this._listeners[type] = []; + } + + this._listeners[type].push(listener); + }, + + /** + * Fires an event based on the passed-in object. + * @param {Object|String} event An object with at least a 'type' attribute + * or a string indicating the event name. + * @return {void} + * @method fire + */ + fire: function(event){ + if (typeof event == "string"){ + event = { type: event }; + } + if (!event.target){ + event.target = this; + } + + if (!event.type){ + throw new Error("Event object missing 'type' property."); + } + + if (this._listeners[event.type]){ + + //create a copy of the array and use that so listeners can't chane + var listeners = this._listeners[event.type].concat(); + for (var i=0, len=listeners.length; i < len; i++){ + listeners[i].call(this, event); + } + } + }, + + /** + * Removes a listener for a given event type. + * @param {String} type The type of event to remove a listener from. + * @param {Function} listener The function to remove from the event. + * @return {void} + * @method removeListener + */ + removeListener: function(type, listener){ + if (this._listeners[type]){ + var listeners = this._listeners[type]; + for (var i=0, len=listeners.length; i < len; i++){ + if (listeners[i] === listener){ + listeners.splice(i, 1); + break; + } + } + + + } + } +}; +/** + * Convenient way to read through strings. + * @namespace parserlib.util + * @class StringReader + * @constructor + * @param {String} text The text to read. + */ +function StringReader(text){ + + /** + * The input text with line endings normalized. + * @property _input + * @type String + * @private + */ + this._input = text.replace(/\n\r?/g, "\n"); + + + /** + * The row for the character to be read next. + * @property _line + * @type int + * @private + */ + this._line = 1; + + + /** + * The column for the character to be read next. + * @property _col + * @type int + * @private + */ + this._col = 1; + + /** + * The index of the character in the input to be read next. + * @property _cursor + * @type int + * @private + */ + this._cursor = 0; +} + +StringReader.prototype = { + + //restore constructor + constructor: StringReader, + + //------------------------------------------------------------------------- + // Position info + //------------------------------------------------------------------------- + + /** + * Returns the column of the character to be read next. + * @return {int} The column of the character to be read next. + * @method getCol + */ + getCol: function(){ + return this._col; + }, + + /** + * Returns the row of the character to be read next. + * @return {int} The row of the character to be read next. + * @method getLine + */ + getLine: function(){ + return this._line ; + }, + + /** + * Determines if you're at the end of the input. + * @return {Boolean} True if there's no more input, false otherwise. + * @method eof + */ + eof: function(){ + return (this._cursor == this._input.length); + }, + + //------------------------------------------------------------------------- + // Basic reading + //------------------------------------------------------------------------- + + /** + * Reads the next character without advancing the cursor. + * @param {int} count How many characters to look ahead (default is 1). + * @return {String} The next character or null if there is no next character. + * @method peek + */ + peek: function(count){ + var c = null; + count = (typeof count == "undefined" ? 1 : count); + + //if we're not at the end of the input... + if (this._cursor < this._input.length){ + + //get character and increment cursor and column + c = this._input.charAt(this._cursor + count - 1); + } + + return c; + }, + + /** + * Reads the next character from the input and adjusts the row and column + * accordingly. + * @return {String} The next character or null if there is no next character. + * @method read + */ + read: function(){ + var c = null; + + //if we're not at the end of the input... + if (this._cursor < this._input.length){ + + //if the last character was a newline, increment row count + //and reset column count + if (this._input.charAt(this._cursor) == "\n"){ + this._line++; + this._col=1; + } else { + this._col++; + } + + //get character and increment cursor and column + c = this._input.charAt(this._cursor++); + } + + return c; + }, + + //------------------------------------------------------------------------- + // Misc + //------------------------------------------------------------------------- + + /** + * Saves the current location so it can be returned to later. + * @method mark + * @return {void} + */ + mark: function(){ + this._bookmark = { + cursor: this._cursor, + line: this._line, + col: this._col + }; + }, + + reset: function(){ + if (this._bookmark){ + this._cursor = this._bookmark.cursor; + this._line = this._bookmark.line; + this._col = this._bookmark.col; + delete this._bookmark; + } + }, + + //------------------------------------------------------------------------- + // Advanced reading + //------------------------------------------------------------------------- + + /** + * Reads up to and including the given string. Throws an error if that + * string is not found. + * @param {String} pattern The string to read. + * @return {String} The string when it is found. + * @throws Error when the string pattern is not found. + * @method readTo + */ + readTo: function(pattern){ + + var buffer = "", + c; + + /* + * First, buffer must be the same length as the pattern. + * Then, buffer must end with the pattern or else reach the + * end of the input. + */ + while (buffer.length < pattern.length || buffer.lastIndexOf(pattern) != buffer.length - pattern.length){ + c = this.read(); + if (c){ + buffer += c; + } else { + throw new Error("Expected \"" + pattern + "\" at line " + this._line + ", col " + this._col + "."); + } + } + + return buffer; + + }, + + /** + * Reads characters while each character causes the given + * filter function to return true. The function is passed + * in each character and either returns true to continue + * reading or false to stop. + * @param {Function} filter The function to read on each character. + * @return {String} The string made up of all characters that passed the + * filter check. + * @method readWhile + */ + readWhile: function(filter){ + + var buffer = "", + c = this.read(); + + while(c !== null && filter(c)){ + buffer += c; + c = this.read(); + } + + return buffer; + + }, + + /** + * Reads characters that match either text or a regular expression and + * returns those characters. If a match is found, the row and column + * are adjusted; if no match is found, the reader's state is unchanged. + * reading or false to stop. + * @param {String|RegExp} matchter If a string, then the literal string + * value is searched for. If a regular expression, then any string + * matching the pattern is search for. + * @return {String} The string made up of all characters that matched or + * null if there was no match. + * @method readMatch + */ + readMatch: function(matcher){ + + var source = this._input.substring(this._cursor), + value = null; + + //if it's a string, just do a straight match + if (typeof matcher == "string"){ + if (source.indexOf(matcher) === 0){ + value = this.readCount(matcher.length); + } + } else if (matcher instanceof RegExp){ + if (matcher.test(source)){ + value = this.readCount(RegExp.lastMatch.length); + } + } + + return value; + }, + + + /** + * Reads a given number of characters. If the end of the input is reached, + * it reads only the remaining characters and does not throw an error. + * @param {int} count The number of characters to read. + * @return {String} The string made up the read characters. + * @method readCount + */ + readCount: function(count){ + var buffer = ""; + + while(count--){ + buffer += this.read(); + } + + return buffer; + } + +}; +/** + * Type to use when a syntax error occurs. + * @class SyntaxError + * @namespace parserlib.util + * @constructor + * @param {String} message The error message. + * @param {int} line The line at which the error occurred. + * @param {int} col The column at which the error occurred. + */ +function SyntaxError(message, line, col){ + + /** + * The column at which the error occurred. + * @type int + * @property col + */ + this.col = col; + + /** + * The line at which the error occurred. + * @type int + * @property line + */ + this.line = line; + + /** + * The text representation of the unit. + * @type String + * @property text + */ + this.message = message; + +} + +//inherit from Error +SyntaxError.prototype = new Error(); +/** + * Base type to represent a single syntactic unit. + * @class SyntaxUnit + * @namespace parserlib.util + * @constructor + * @param {String} text The text of the unit. + * @param {int} line The line of text on which the unit resides. + * @param {int} col The column of text on which the unit resides. + */ +function SyntaxUnit(text, line, col){ + + + /** + * The column of text on which the unit resides. + * @type int + * @property col + */ + this.col = col; + + /** + * The line of text on which the unit resides. + * @type int + * @property line + */ + this.line = line; + + /** + * The text representation of the unit. + * @type String + * @property text + */ + this.text = text; + +} + +/** + * Create a new syntax unit based solely on the given token. + * Convenience method for creating a new syntax unit when + * it represents a single token instead of multiple. + * @param {Object} token The token object to represent. + * @return {parserlib.util.SyntaxUnit} The object representing the token. + * @static + * @method fromToken + */ +SyntaxUnit.fromToken = function(token){ + return new SyntaxUnit(token.value, token.startLine, token.startCol); +}; + +SyntaxUnit.prototype = { + + //restore constructor + constructor: SyntaxUnit, + + /** + * Returns the text representation of the unit. + * @return {String} The text representation of the unit. + * @method valueOf + */ + valueOf: function(){ + return this.toString(); + }, + + /** + * Returns the text representation of the unit. + * @return {String} The text representation of the unit. + * @method toString + */ + toString: function(){ + return this.text; + } + +}; +/** + * Generic TokenStream providing base functionality. + * @class TokenStreamBase + * @namespace parserlib.util + * @constructor + * @param {String|StringReader} input The text to tokenize or a reader from + * which to read the input. + */ +function TokenStreamBase(input, tokenData){ + + /** + * The string reader for easy access to the text. + * @type StringReader + * @property _reader + * @private + */ + //this._reader = (typeof input == "string") ? new StringReader(input) : input; + this._reader = input ? new StringReader(input.toString()) : null; + + /** + * Token object for the last consumed token. + * @type Token + * @property _token + * @private + */ + this._token = null; + + /** + * The array of token information. + * @type Array + * @property _tokenData + * @private + */ + this._tokenData = tokenData; + + /** + * Lookahead token buffer. + * @type Array + * @property _lt + * @private + */ + this._lt = []; + + /** + * Lookahead token buffer index. + * @type int + * @property _ltIndex + * @private + */ + this._ltIndex = 0; + + this._ltIndexCache = []; +} + +/** + * Accepts an array of token information and outputs + * an array of token data containing key-value mappings + * and matching functions that the TokenStream needs. + * @param {Array} tokens An array of token descriptors. + * @return {Array} An array of processed token data. + * @method createTokenData + * @static + */ +TokenStreamBase.createTokenData = function(tokens){ + + var nameMap = [], + typeMap = {}, + tokenData = tokens.concat([]), + i = 0, + len = tokenData.length+1; + + tokenData.UNKNOWN = -1; + tokenData.unshift({name:"EOF"}); + + for (; i < len; i++){ + nameMap.push(tokenData[i].name); + tokenData[tokenData[i].name] = i; + if (tokenData[i].text){ + typeMap[tokenData[i].text] = i; + } + } + + tokenData.name = function(tt){ + return nameMap[tt]; + }; + + tokenData.type = function(c){ + return typeMap[c]; + }; + + return tokenData; +}; + +TokenStreamBase.prototype = { + + //restore constructor + constructor: TokenStreamBase, + + //------------------------------------------------------------------------- + // Matching methods + //------------------------------------------------------------------------- + + /** + * Determines if the next token matches the given token type. + * If so, that token is consumed; if not, the token is placed + * back onto the token stream. You can pass in any number of + * token types and this will return true if any of the token + * types is found. + * @param {int|int[]} tokenTypes Either a single token type or an array of + * token types that the next token might be. If an array is passed, + * it's assumed that the token can be any of these. + * @param {variant} channel (Optional) The channel to read from. If not + * provided, reads from the default (unnamed) channel. + * @return {Boolean} True if the token type matches, false if not. + * @method match + */ + match: function(tokenTypes, channel){ + + //always convert to an array, makes things easier + if (!(tokenTypes instanceof Array)){ + tokenTypes = [tokenTypes]; + } + + var tt = this.get(channel), + i = 0, + len = tokenTypes.length; + + while(i < len){ + if (tt == tokenTypes[i++]){ + return true; + } + } + + //no match found, put the token back + this.unget(); + return false; + }, + + /** + * Determines if the next token matches the given token type. + * If so, that token is consumed; if not, an error is thrown. + * @param {int|int[]} tokenTypes Either a single token type or an array of + * token types that the next token should be. If an array is passed, + * it's assumed that the token must be one of these. + * @param {variant} channel (Optional) The channel to read from. If not + * provided, reads from the default (unnamed) channel. + * @return {void} + * @method mustMatch + */ + mustMatch: function(tokenTypes, channel){ + + //always convert to an array, makes things easier + if (!(tokenTypes instanceof Array)){ + tokenTypes = [tokenTypes]; + } + + if (!this.match.apply(this, arguments)){ + token = this.LT(1); + throw new SyntaxError("Expected " + this._tokenData[tokenTypes[0]].name + + " at line " + token.startLine + ", col " + token.startCol + ".", token.startLine, token.startCol); + } + }, + + //------------------------------------------------------------------------- + // Consuming methods + //------------------------------------------------------------------------- + + /** + * Keeps reading from the token stream until either one of the specified + * token types is found or until the end of the input is reached. + * @param {int|int[]} tokenTypes Either a single token type or an array of + * token types that the next token should be. If an array is passed, + * it's assumed that the token must be one of these. + * @param {variant} channel (Optional) The channel to read from. If not + * provided, reads from the default (unnamed) channel. + * @return {void} + * @method advance + */ + advance: function(tokenTypes, channel){ + + while(this.LA(0) != 0 && !this.match(tokenTypes, channel)){ + this.get(); + } + + return this.LA(0); + }, + + /** + * Consumes the next token from the token stream. + * @return {int} The token type of the token that was just consumed. + * @method get + */ + get: function(channel){ + + var tokenInfo = this._tokenData, + reader = this._reader, + value, + i =0, + len = tokenInfo.length, + found = false, + token, + info; + + //check the lookahead buffer first + if (this._lt.length && this._ltIndex >= 0 && this._ltIndex < this._lt.length){ + + i++; + this._token = this._lt[this._ltIndex++]; + info = tokenInfo[this._token.type]; + + //obey channels logic + while((info.channel !== undefined && channel !== info.channel) && + this._ltIndex < this._lt.length){ + this._token = this._lt[this._ltIndex++]; + info = tokenInfo[this._token.type]; + i++; + } + + //here be dragons + if ((info.channel === undefined || channel === info.channel) && + this._ltIndex <= this._lt.length){ + this._ltIndexCache.push(i); + return this._token.type; + } + } + + //call token retriever method + token = this._getToken(); + + //if it should be hidden, don't save a token + if (token.type > -1 && !tokenInfo[token.type].hide){ + + //apply token channel + token.channel = tokenInfo[token.type].channel; + + //save for later + this._token = token; + this._lt.push(token); + + //save space that will be moved (must be done before array is truncated) + this._ltIndexCache.push(this._lt.length - this._ltIndex + i); + + //keep the buffer under 5 items + if (this._lt.length > 5){ + this._lt.shift(); + } + + //also keep the shift buffer under 5 items + if (this._ltIndexCache.length > 5){ + this._ltIndexCache.shift(); + } + + //update lookahead index + this._ltIndex = this._lt.length; + } + + /* + * Skip to the next token if: + * 1. The token type is marked as hidden. + * 2. The token type has a channel specified and it isn't the current channel. + */ + info = tokenInfo[token.type]; + if (info && + (info.hide || + (info.channel !== undefined && channel !== info.channel))){ + return this.get(channel); + } else { + //return just the type + return token.type; + } + }, + + /** + * Looks ahead a certain number of tokens and returns the token type at + * that position. This will throw an error if you lookahead past the + * end of input, past the size of the lookahead buffer, or back past + * the first token in the lookahead buffer. + * @param {int} The index of the token type to retrieve. 0 for the + * current token, 1 for the next, -1 for the previous, etc. + * @return {int} The token type of the token in the given position. + * @method LA + */ + LA: function(index){ + var total = index, + tt; + if (index > 0){ + //TODO: Store 5 somewhere + if (index > 5){ + throw new Error("Too much lookahead."); + } + + //get all those tokens + while(total){ + tt = this.get(); + total--; + } + + //unget all those tokens + while(total < index){ + this.unget(); + total++; + } + } else if (index < 0){ + + if(this._lt[this._ltIndex+index]){ + tt = this._lt[this._ltIndex+index].type; + } else { + throw new Error("Too much lookbehind."); + } + + } else { + tt = this._token.type; + } + + return tt; + + }, + + /** + * Looks ahead a certain number of tokens and returns the token at + * that position. This will throw an error if you lookahead past the + * end of input, past the size of the lookahead buffer, or back past + * the first token in the lookahead buffer. + * @param {int} The index of the token type to retrieve. 0 for the + * current token, 1 for the next, -1 for the previous, etc. + * @return {Object} The token of the token in the given position. + * @method LA + */ + LT: function(index){ + + //lookahead first to prime the token buffer + this.LA(index); + + //now find the token, subtract one because _ltIndex is already at the next index + return this._lt[this._ltIndex+index-1]; + }, + + /** + * Returns the token type for the next token in the stream without + * consuming it. + * @return {int} The token type of the next token in the stream. + * @method peek + */ + peek: function(){ + return this.LA(1); + }, + + /** + * Returns the actual token object for the last consumed token. + * @return {Token} The token object for the last consumed token. + * @method token + */ + token: function(){ + return this._token; + }, + + /** + * Returns the name of the token for the given token type. + * @param {int} tokenType The type of token to get the name of. + * @return {String} The name of the token or "UNKNOWN_TOKEN" for any + * invalid token type. + * @method tokenName + */ + tokenName: function(tokenType){ + if (tokenType < 0 || tokenType > this._tokenData.length){ + return "UNKNOWN_TOKEN"; + } else { + return this._tokenData[tokenType].name; + } + }, + + /** + * Returns the token type value for the given token name. + * @param {String} tokenName The name of the token whose value should be returned. + * @return {int} The token type value for the given token name or -1 + * for an unknown token. + * @method tokenName + */ + tokenType: function(tokenName){ + return this._tokenData[tokenName] || -1; + }, + + /** + * Returns the last consumed token to the token stream. + * @method unget + */ + unget: function(){ + //if (this._ltIndex > -1){ + if (this._ltIndexCache.length){ + this._ltIndex -= this._ltIndexCache.pop();//--; + this._token = this._lt[this._ltIndex - 1]; + } else { + throw new Error("Too much lookahead."); + } + } + +}; + + +parserlib.util = { +StringReader: StringReader, +SyntaxError : SyntaxError, +SyntaxUnit : SyntaxUnit, +EventTarget : EventTarget, +TokenStreamBase : TokenStreamBase +}; +})(); +/* +Parser-Lib +Copyright (c) 2009-2011 Nicholas C. Zakas. All rights reserved. + +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 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. + +*/ +/* Build time: 19-July-2011 01:46:47 */ +(function(){ +var EventTarget = parserlib.util.EventTarget, +TokenStreamBase = parserlib.util.TokenStreamBase, +StringReader = parserlib.util.StringReader, +SyntaxError = parserlib.util.SyntaxError, +SyntaxUnit = parserlib.util.SyntaxUnit; + +var Colors = { + aliceblue :"#f0f8ff", + antiquewhite :"#faebd7", + aqua :"#00ffff", + aquamarine :"#7fffd4", + azure :"#f0ffff", + beige :"#f5f5dc", + bisque :"#ffe4c4", + black :"#000000", + blanchedalmond :"#ffebcd", + blue :"#0000ff", + blueviolet :"#8a2be2", + brown :"#a52a2a", + burlywood :"#deb887", + cadetblue :"#5f9ea0", + chartreuse :"#7fff00", + chocolate :"#d2691e", + coral :"#ff7f50", + cornflowerblue :"#6495ed", + cornsilk :"#fff8dc", + crimson :"#dc143c", + cyan :"#00ffff", + darkblue :"#00008b", + darkcyan :"#008b8b", + darkgoldenrod :"#b8860b", + darkgray :"#a9a9a9", + darkgreen :"#006400", + darkkhaki :"#bdb76b", + darkmagenta :"#8b008b", + darkolivegreen :"#556b2f", + darkorange :"#ff8c00", + darkorchid :"#9932cc", + darkred :"#8b0000", + darksalmon :"#e9967a", + darkseagreen :"#8fbc8f", + darkslateblue :"#483d8b", + darkslategray :"#2f4f4f", + darkturquoise :"#00ced1", + darkviolet :"#9400d3", + deeppink :"#ff1493", + deepskyblue :"#00bfff", + dimgray :"#696969", + dodgerblue :"#1e90ff", + firebrick :"#b22222", + floralwhite :"#fffaf0", + forestgreen :"#228b22", + fuchsia :"#ff00ff", + gainsboro :"#dcdcdc", + ghostwhite :"#f8f8ff", + gold :"#ffd700", + goldenrod :"#daa520", + gray :"#808080", + green :"#008000", + greenyellow :"#adff2f", + honeydew :"#f0fff0", + hotpink :"#ff69b4", + indianred :"#cd5c5c", + indigo :"#4b0082", + ivory :"#fffff0", + khaki :"#f0e68c", + lavender :"#e6e6fa", + lavenderblush :"#fff0f5", + lawngreen :"#7cfc00", + lemonchiffon :"#fffacd", + lightblue :"#add8e6", + lightcoral :"#f08080", + lightcyan :"#e0ffff", + lightgoldenrodyellow :"#fafad2", + lightgrey :"#d3d3d3", + lightgreen :"#90ee90", + lightpink :"#ffb6c1", + lightsalmon :"#ffa07a", + lightseagreen :"#20b2aa", + lightskyblue :"#87cefa", + lightslategray :"#778899", + lightsteelblue :"#b0c4de", + lightyellow :"#ffffe0", + lime :"#00ff00", + limegreen :"#32cd32", + linen :"#faf0e6", + magenta :"#ff00ff", + maroon :"#800000", + mediumaquamarine:"#66cdaa", + mediumblue :"#0000cd", + mediumorchid :"#ba55d3", + mediumpurple :"#9370d8", + mediumseagreen :"#3cb371", + mediumslateblue :"#7b68ee", + mediumspringgreen :"#00fa9a", + mediumturquoise :"#48d1cc", + mediumvioletred :"#c71585", + midnightblue :"#191970", + mintcream :"#f5fffa", + mistyrose :"#ffe4e1", + moccasin :"#ffe4b5", + navajowhite :"#ffdead", + navy :"#000080", + oldlace :"#fdf5e6", + olive :"#808000", + olivedrab :"#6b8e23", + orange :"#ffa500", + orangered :"#ff4500", + orchid :"#da70d6", + palegoldenrod :"#eee8aa", + palegreen :"#98fb98", + paleturquoise :"#afeeee", + palevioletred :"#d87093", + papayawhip :"#ffefd5", + peachpuff :"#ffdab9", + peru :"#cd853f", + pink :"#ffc0cb", + plum :"#dda0dd", + powderblue :"#b0e0e6", + purple :"#800080", + red :"#ff0000", + rosybrown :"#bc8f8f", + royalblue :"#4169e1", + saddlebrown :"#8b4513", + salmon :"#fa8072", + sandybrown :"#f4a460", + seagreen :"#2e8b57", + seashell :"#fff5ee", + sienna :"#a0522d", + silver :"#c0c0c0", + skyblue :"#87ceeb", + slateblue :"#6a5acd", + slategray :"#708090", + snow :"#fffafa", + springgreen :"#00ff7f", + steelblue :"#4682b4", + tan :"#d2b48c", + teal :"#008080", + thistle :"#d8bfd8", + tomato :"#ff6347", + turquoise :"#40e0d0", + violet :"#ee82ee", + wheat :"#f5deb3", + white :"#ffffff", + whitesmoke :"#f5f5f5", + yellow :"#ffff00", + yellowgreen :"#9acd32" +}; +/** + * Represents a selector combinator (whitespace, +, >). + * @namespace parserlib.css + * @class Combinator + * @extends parserlib.util.SyntaxUnit + * @constructor + * @param {String} text The text representation of the unit. + * @param {int} line The line of text on which the unit resides. + * @param {int} col The column of text on which the unit resides. + */ +function Combinator(text, line, col){ + + SyntaxUnit.call(this, text, line, col); + + /** + * The type of modifier. + * @type String + * @property type + */ + this.type = "unknown"; + + //pretty simple + if (/^\s+$/.test(text)){ + this.type = "descendant"; + } else if (text == ">"){ + this.type = "child"; + } else if (text == "+"){ + this.type = "adjacent-sibling"; + } else if (text == "~"){ + this.type = "sibling"; + } + +} + +Combinator.prototype = new SyntaxUnit(); +Combinator.prototype.constructor = Combinator; + + +var Level1Properties = { + + "background": 1, + "background-attachment": 1, + "background-color": 1, + "background-image": 1, + "background-position": 1, + "background-repeat": 1, + + "border": 1, + "border-bottom": 1, + "border-bottom-width": 1, + "border-color": 1, + "border-left": 1, + "border-left-width": 1, + "border-right": 1, + "border-right-width": 1, + "border-style": 1, + "border-top": 1, + "border-top-width": 1, + "border-width": 1, + + "clear": 1, + "color": 1, + "display": 1, + "float": 1, + + "font": 1, + "font-family": 1, + "font-size": 1, + "font-style": 1, + "font-variant": 1, + "font-weight": 1, + + "height": 1, + "letter-spacing": 1, + "line-height": 1, + + "list-style": 1, + "list-style-image": 1, + "list-style-position": 1, + "list-style-type": 1, + + "margin": 1, + "margin-bottom": 1, + "margin-left": 1, + "margin-right": 1, + "margin-top": 1, + + "padding": 1, + "padding-bottom": 1, + "padding-left": 1, + "padding-right": 1, + "padding-top": 1, + + "text-align": 1, + "text-decoration": 1, + "text-indent": 1, + "text-transform": 1, + + "vertical-align": 1, + "white-space": 1, + "width": 1, + "word-spacing": 1 + +}; + +var Level2Properties = { + + //Aural + "azimuth": 1, + "cue-after": 1, + "cue-before": 1, + "cue": 1, + "elevation": 1, + "pause-after": 1, + "pause-before": 1, + "pause": 1, + "pitch-range": 1, + "pitch": 1, + "play-during": 1, + "richness": 1, + "speak-header": 1, + "speak-numeral": 1, + "speak-punctuation": 1, + "speak": 1, + "speech-rate": 1, + "stress": 1, + "voice-family": 1, + "volume": 1, + + //Paged + "orphans": 1, + "page-break-after": 1, + "page-break-before": 1, + "page-break-inside": 1, + "widows": 1, + + //Interactive + "cursor": 1, + "outline-color": 1, + "outline-style": 1, + "outline-width": 1, + "outline": 1, + + //Visual + "background-attachment": 1, + "background-color": 1, + "background-image": 1, + "background-position": 1, + "background-repeat": 1, + "background": 1, + "border-collapse": 1, + "border-color": 1, + "border-spacing": 1, + "border-style": 1, + "border-top": 1, + "border-top-color": 1, + "border-top-style": 1, + "border-top-width": 1, + "border-width": 1, + "border": 1, + "bottom": 1, + "caption-side": 1, + "clear": 1, + "clip": 1, + "color": 1, + "content": 1, + "counter-increment": 1, + "counter-reset": 1, + "direction": 1, + "display": 1, + "empty-cells": 1, + "float": 1, + "font-family": 1, + "font-size": 1, + "font-style": 1, + "font-variant": 1, + "font-weight": 1, + "font": 1, + "height": 1, + "left": 1, + "letter-spacing": 1, + "line-height": 1, + "list-style-image": 1, + "list-style-position": 1, + "list-style-type": 1, + "list-style": 1, + "margin-right": 1, + "margin-top": 1, + "margin": 1, + "max-height": 1, + "max-width": 1, + "min-height": 1, + "min-width": 1, + "overflow": 1, + "padding-top": 1, + "padding": 1, + "position": 1, + "quotes": 1, + "right": 1, + "table-layout": 1, + "text-align": 1, + "text-decoration": 1, + "text-indent": 1, + "text-transform": 1, + "top": 1, + "unicode-bidi": 1, + "vertical-align": 1, + "visibility": 1, + "white-space": 1, + "width": 1, + "word-spacing": 1, + "z-index": 1 +}; +/** + * Represents a media feature, such as max-width:500. + * @namespace parserlib.css + * @class MediaFeature + * @extends parserlib.util.SyntaxUnit + * @constructor + * @param {SyntaxUnit} name The name of the feature. + * @param {SyntaxUnit} value The value of the feature or null if none. + */ +function MediaFeature(name, value){ + + SyntaxUnit.call(this, "(" + name + (value !== null ? ":" + value : "") + ")", name.startLine, name.startCol); + + /** + * The name of the media feature + * @type String + * @property name + */ + this.name = name; + + /** + * The value for the feature or null if there is none. + * @type SyntaxUnit + * @property value + */ + this.value = value; +} + +MediaFeature.prototype = new SyntaxUnit(); +MediaFeature.prototype.constructor = MediaFeature; + +/** + * Represents an individual media query. + * @namespace parserlib.css + * @class MediaQuery + * @extends parserlib.util.SyntaxUnit + * @constructor + * @param {String} modifier The modifier "not" or "only" (or null). + * @param {String} mediaType The type of media (i.e., "print"). + * @param {Array} parts Array of selectors parts making up this selector. + * @param {int} line The line of text on which the unit resides. + * @param {int} col The column of text on which the unit resides. + */ +function MediaQuery(modifier, mediaType, features, line, col){ + + SyntaxUnit.call(this, (modifier ? modifier + " ": "") + (mediaType ? mediaType + " " : "") + features.join(" and "), line, col); + + /** + * The media modifier ("not" or "only") + * @type String + * @property modifier + */ + this.modifier = modifier; + + /** + * The mediaType (i.e., "print") + * @type String + * @property mediaType + */ + this.mediaType = mediaType; + + /** + * The parts that make up the selector. + * @type Array + * @property features + */ + this.features = features; + +} + +MediaQuery.prototype = new SyntaxUnit(); +MediaQuery.prototype.constructor = MediaQuery; + +/** + * A CSS3 parser. + * @namespace parserlib.css + * @class Parser + * @constructor + * @param {Object} options (Optional) Various options for the parser: + * starHack (true|false) to allow IE6 star hack as valid, + * underscoreHack (true|false) to interpret leading underscores + * as IE6-7 targeting for known properties, ieFilters (true|false) + * to indicate that IE < 8 filters should be accepted and not throw + * syntax errors. + */ +function Parser(options){ + + //inherit event functionality + EventTarget.call(this); + + + this.options = options || {}; + + this._tokenStream = null; +} + +Parser.prototype = function(){ + + var proto = new EventTarget(), //new prototype + prop, + additions = { + + //restore constructor + constructor: Parser, + + //----------------------------------------------------------------- + // Grammar + //----------------------------------------------------------------- + + _stylesheet: function(){ + + /* + * stylesheet + * : [ CHARSET_SYM S* STRING S* ';' ]? + * [S|CDO|CDC]* [ import [S|CDO|CDC]* ]* + * [ namespace [S|CDO|CDC]* ]* + * [ [ ruleset | media | page | font_face | keyframes ] [S|CDO|CDC]* ]* + * ; + */ + + var tokenStream = this._tokenStream, + charset = null, + token, + tt; + + this.fire("startstylesheet"); + + //try to read character set + this._charset(); + + this._skipCruft(); + + //try to read imports - may be more than one + while (tokenStream.peek() == Tokens.IMPORT_SYM){ + this._import(); + this._skipCruft(); + } + + //try to read namespaces - may be more than one + while (tokenStream.peek() == Tokens.NAMESPACE_SYM){ + this._namespace(); + this._skipCruft(); + } + + //get the next token + tt = tokenStream.peek(); + + //try to read the rest + while(tt > Tokens.EOF){ + + try { + + switch(tt){ + case Tokens.MEDIA_SYM: + this._media(); + this._skipCruft(); + break; + case Tokens.PAGE_SYM: + this._page(); + this._skipCruft(); + break; + case Tokens.FONT_FACE_SYM: + this._font_face(); + this._skipCruft(); + break; + case Tokens.KEYFRAMES_SYM: + this._keyframes(); + this._skipCruft(); + break; + case Tokens.S: + this._readWhitespace(); + break; + default: + if(!this._ruleset()){ + + //error handling for known issues + switch(tt){ + case Tokens.CHARSET_SYM: + token = tokenStream.LT(1); + this._charset(false); + throw new SyntaxError("@charset not allowed here.", token.startLine, token.startCol); + case Tokens.IMPORT_SYM: + token = tokenStream.LT(1); + this._import(false); + throw new SyntaxError("@import not allowed here.", token.startLine, token.startCol); + case Tokens.NAMESPACE_SYM: + token = tokenStream.LT(1); + this._namespace(false); + throw new SyntaxError("@namespace not allowed here.", token.startLine, token.startCol); + default: + tokenStream.get(); //get the last token + this._unexpectedToken(tokenStream.token()); + } + + } + } + } catch(ex) { + if (ex instanceof SyntaxError && !this.options.strict){ + this.fire({ + type: "error", + error: ex, + message: ex.message, + line: ex.line, + col: ex.col + }); + } else { + throw ex; + } + } + + tt = tokenStream.peek(); + } + + if (tt != Tokens.EOF){ + this._unexpectedToken(tokenStream.token()); + } + + this.fire("endstylesheet"); + }, + + _charset: function(emit){ + var tokenStream = this._tokenStream, + charset, + token, + line, + col; + + if (tokenStream.match(Tokens.CHARSET_SYM)){ + line = tokenStream.token().startLine; + col = tokenStream.token().startCol; + + this._readWhitespace(); + tokenStream.mustMatch(Tokens.STRING); + + token = tokenStream.token(); + charset = token.value; + + this._readWhitespace(); + tokenStream.mustMatch(Tokens.SEMICOLON); + + if (emit !== false){ + this.fire({ + type: "charset", + charset:charset, + line: line, + col: col + }); + } + } + }, + + _import: function(emit){ + /* + * import + * : IMPORT_SYM S* + * [STRING|URI] S* media_query_list? ';' S* + */ + + var tokenStream = this._tokenStream, + tt, + uri, + importToken, + mediaList = []; + + //read import symbol + tokenStream.mustMatch(Tokens.IMPORT_SYM); + importToken = tokenStream.token(); + this._readWhitespace(); + + tokenStream.mustMatch([Tokens.STRING, Tokens.URI]); + + //grab the URI value + uri = tokenStream.token().value.replace(/(?:url\()?["']([^"']+)["']\)?/, "$1"); + + this._readWhitespace(); + + mediaList = this._media_query_list(); + + //must end with a semicolon + tokenStream.mustMatch(Tokens.SEMICOLON); + this._readWhitespace(); + + if (emit !== false){ + this.fire({ + type: "import", + uri: uri, + media: mediaList, + line: importToken.startLine, + col: importToken.startCol + }); + } + + }, + + _namespace: function(emit){ + /* + * namespace + * : NAMESPACE_SYM S* [namespace_prefix S*]? [STRING|URI] S* ';' S* + */ + + var tokenStream = this._tokenStream, + line, + col, + prefix, + uri; + + //read import symbol + tokenStream.mustMatch(Tokens.NAMESPACE_SYM); + line = tokenStream.token().startLine; + col = tokenStream.token().startCol; + this._readWhitespace(); + + //it's a namespace prefix - no _namespace_prefix() method because it's just an IDENT + if (tokenStream.match(Tokens.IDENT)){ + prefix = tokenStream.token().value; + this._readWhitespace(); + } + + tokenStream.mustMatch([Tokens.STRING, Tokens.URI]); + /*if (!tokenStream.match(Tokens.STRING)){ + tokenStream.mustMatch(Tokens.URI); + }*/ + + //grab the URI value + uri = tokenStream.token().value.replace(/(?:url\()?["']([^"']+)["']\)?/, "$1"); + + this._readWhitespace(); + + //must end with a semicolon + tokenStream.mustMatch(Tokens.SEMICOLON); + this._readWhitespace(); + + if (emit !== false){ + this.fire({ + type: "namespace", + prefix: prefix, + uri: uri, + line: line, + col: col + }); + } + + }, + + _media: function(){ + /* + * media + * : MEDIA_SYM S* media_query_list S* '{' S* ruleset* '}' S* + * ; + */ + var tokenStream = this._tokenStream, + line, + col, + mediaList;// = []; + + //look for @media + tokenStream.mustMatch(Tokens.MEDIA_SYM); + line = tokenStream.token().startLine; + col = tokenStream.token().startCol; + + this._readWhitespace(); + + mediaList = this._media_query_list(); + + tokenStream.mustMatch(Tokens.LBRACE); + this._readWhitespace(); + + this.fire({ + type: "startmedia", + media: mediaList, + line: line, + col: col + }); + + while(true) { + if (tokenStream.peek() == Tokens.PAGE_SYM){ + this._page(); + } else if (!this._ruleset()){ + break; + } + } + + tokenStream.mustMatch(Tokens.RBRACE); + this._readWhitespace(); + + this.fire({ + type: "endmedia", + media: mediaList, + line: line, + col: col + }); + }, + + + //CSS3 Media Queries + _media_query_list: function(){ + /* + * media_query_list + * : S* [media_query [ ',' S* media_query ]* ]? + * ; + */ + var tokenStream = this._tokenStream, + mediaList = []; + + + this._readWhitespace(); + + if (tokenStream.peek() == Tokens.IDENT || tokenStream.peek() == Tokens.LPAREN){ + mediaList.push(this._media_query()); + } + + while(tokenStream.match(Tokens.COMMA)){ + this._readWhitespace(); + mediaList.push(this._media_query()); + } + + return mediaList; + }, + + /* + * Note: "expression" in the grammar maps to the _media_expression + * method. + + */ + _media_query: function(){ + /* + * media_query + * : [ONLY | NOT]? S* media_type S* [ AND S* expression ]* + * | expression [ AND S* expression ]* + * ; + */ + var tokenStream = this._tokenStream, + type = null, + ident = null, + token = null, + expressions = []; + + if (tokenStream.match(Tokens.IDENT)){ + ident = tokenStream.token().value.toLowerCase(); + + //since there's no custom tokens for these, need to manually check + if (ident != "only" && ident != "not"){ + tokenStream.unget(); + ident = null; + } else { + token = tokenStream.token(); + } + } + + this._readWhitespace(); + + if (tokenStream.peek() == Tokens.IDENT){ + type = this._media_type(); + if (token === null){ + token = tokenStream.token(); + } + } else if (tokenStream.peek() == Tokens.LPAREN){ + if (token === null){ + token = tokenStream.LT(1); + } + expressions.push(this._media_expression()); + } + + if (type === null && expressions.length === 0){ + return null; + } else { + this._readWhitespace(); + while (tokenStream.match(Tokens.IDENT)){ + if (tokenStream.token().value.toLowerCase() != "and"){ + this._unexpectedToken(tokenStream.token()); + } + + this._readWhitespace(); + expressions.push(this._media_expression()); + } + } + + return new MediaQuery(ident, type, expressions, token.startLine, token.startCol); + }, + + //CSS3 Media Queries + _media_type: function(){ + /* + * media_type + * : IDENT + * ; + */ + return this._media_feature(); + }, + + /** + * Note: in CSS3 Media Queries, this is called "expression". + * Renamed here to avoid conflict with CSS3 Selectors + * definition of "expression". Also note that "expr" in the + * grammar now maps to "expression" from CSS3 selectors. + * @method _media_expression + * @private + */ + _media_expression: function(){ + /* + * expression + * : '(' S* media_feature S* [ ':' S* expr ]? ')' S* + * ; + */ + var tokenStream = this._tokenStream, + feature = null, + token, + expression = null; + + tokenStream.mustMatch(Tokens.LPAREN); + + feature = this._media_feature(); + this._readWhitespace(); + + if (tokenStream.match(Tokens.COLON)){ + this._readWhitespace(); + token = tokenStream.LT(1); + expression = this._expression(); + } + + tokenStream.mustMatch(Tokens.RPAREN); + this._readWhitespace(); + + return new MediaFeature(feature, (expression ? new SyntaxUnit(expression, token.startLine, token.startCol) : null)); + }, + + //CSS3 Media Queries + _media_feature: function(){ + /* + * media_feature + * : IDENT + * ; + */ + var tokenStream = this._tokenStream; + + tokenStream.mustMatch(Tokens.IDENT); + + return SyntaxUnit.fromToken(tokenStream.token()); + }, + + //CSS3 Paged Media + _page: function(){ + /* + * page: + * PAGE_SYM S* IDENT? pseudo_page? S* + * '{' S* [ declaration | margin ]? [ ';' S* [ declaration | margin ]? ]* '}' S* + * ; + */ + var tokenStream = this._tokenStream, + line, + col, + identifier = null, + pseudoPage = null; + + //look for @page + tokenStream.mustMatch(Tokens.PAGE_SYM); + line = tokenStream.token().startLine; + col = tokenStream.token().startCol; + + this._readWhitespace(); + + if (tokenStream.match(Tokens.IDENT)){ + identifier = tokenStream.token().value; + + //The value 'auto' may not be used as a page name and MUST be treated as a syntax error. + if (identifier.toLowerCase() === "auto"){ + this._unexpectedToken(tokenStream.token()); + } + } + + //see if there's a colon upcoming + if (tokenStream.peek() == Tokens.COLON){ + pseudoPage = this._pseudo_page(); + } + + this._readWhitespace(); + + this.fire({ + type: "startpage", + id: identifier, + pseudo: pseudoPage, + line: line, + col: col + }); + + this._readDeclarations(true, true); + + this.fire({ + type: "endpage", + id: identifier, + pseudo: pseudoPage, + line: line, + col: col + }); + + }, + + //CSS3 Paged Media + _margin: function(){ + /* + * margin : + * margin_sym S* '{' declaration [ ';' S* declaration? ]* '}' S* + * ; + */ + var tokenStream = this._tokenStream, + line, + col, + marginSym = this._margin_sym(); + + if (marginSym){ + line = tokenStream.token().startLine; + col = tokenStream.token().startCol; + + this.fire({ + type: "startpagemargin", + margin: marginSym, + line: line, + col: col + }); + + this._readDeclarations(true); + + this.fire({ + type: "endpagemargin", + margin: marginSym, + line: line, + col: col + }); + return true; + } else { + return false; + } + }, + + //CSS3 Paged Media + _margin_sym: function(){ + + /* + * margin_sym : + * TOPLEFTCORNER_SYM | + * TOPLEFT_SYM | + * TOPCENTER_SYM | + * TOPRIGHT_SYM | + * TOPRIGHTCORNER_SYM | + * BOTTOMLEFTCORNER_SYM | + * BOTTOMLEFT_SYM | + * BOTTOMCENTER_SYM | + * BOTTOMRIGHT_SYM | + * BOTTOMRIGHTCORNER_SYM | + * LEFTTOP_SYM | + * LEFTMIDDLE_SYM | + * LEFTBOTTOM_SYM | + * RIGHTTOP_SYM | + * RIGHTMIDDLE_SYM | + * RIGHTBOTTOM_SYM + * ; + */ + + var tokenStream = this._tokenStream; + + if(tokenStream.match([Tokens.TOPLEFTCORNER_SYM, Tokens.TOPLEFT_SYM, + Tokens.TOPCENTER_SYM, Tokens.TOPRIGHT_SYM, Tokens.TOPRIGHTCORNER_SYM, + Tokens.BOTTOMLEFTCORNER_SYM, Tokens.BOTTOMLEFT_SYM, + Tokens.BOTTOMCENTER_SYM, Tokens.BOTTOMRIGHT_SYM, + Tokens.BOTTOMRIGHTCORNER_SYM, Tokens.LEFTTOP_SYM, + Tokens.LEFTMIDDLE_SYM, Tokens.LEFTBOTTOM_SYM, Tokens.RIGHTTOP_SYM, + Tokens.RIGHTMIDDLE_SYM, Tokens.RIGHTBOTTOM_SYM])) + { + return SyntaxUnit.fromToken(tokenStream.token()); + } else { + return null; + } + + }, + + _pseudo_page: function(){ + /* + * pseudo_page + * : ':' IDENT + * ; + */ + + var tokenStream = this._tokenStream; + + tokenStream.mustMatch(Tokens.COLON); + tokenStream.mustMatch(Tokens.IDENT); + + //TODO: CSS3 Paged Media says only "left", "center", and "right" are allowed + + return tokenStream.token().value; + }, + + _font_face: function(){ + /* + * font_face + * : FONT_FACE_SYM S* + * '{' S* declaration [ ';' S* declaration ]* '}' S* + * ; + */ + var tokenStream = this._tokenStream, + line, + col; + + //look for @page + tokenStream.mustMatch(Tokens.FONT_FACE_SYM); + line = tokenStream.token().startLine; + col = tokenStream.token().startCol; + + this._readWhitespace(); + + this.fire({ + type: "startfontface", + line: line, + col: col + }); + + this._readDeclarations(true); + + this.fire({ + type: "endfontface", + line: line, + col: col + }); + }, + + _operator: function(){ + + /* + * operator + * : '/' S* | ',' S* | /( empty )/ + * ; + */ + + var tokenStream = this._tokenStream, + token = null; + + if (tokenStream.match([Tokens.SLASH, Tokens.COMMA])){ + token = tokenStream.token(); + this._readWhitespace(); + } + return token ? PropertyValuePart.fromToken(token) : null; + + }, + + _combinator: function(){ + + /* + * combinator + * : PLUS S* | GREATER S* | TILDE S* | S+ + * ; + */ + + var tokenStream = this._tokenStream, + value = null, + token; + + if(tokenStream.match([Tokens.PLUS, Tokens.GREATER, Tokens.TILDE])){ + token = tokenStream.token(); + value = new Combinator(token.value, token.startLine, token.startCol); + this._readWhitespace(); + } + + return value; + }, + + _unary_operator: function(){ + + /* + * unary_operator + * : '-' | '+' + * ; + */ + + var tokenStream = this._tokenStream; + + if (tokenStream.match([Tokens.MINUS, Tokens.PLUS])){ + return tokenStream.token().value; + } else { + return null; + } + }, + + _property: function(){ + + /* + * property + * : IDENT S* + * ; + */ + + var tokenStream = this._tokenStream, + value = null, + hack = null, + tokenValue, + token, + line, + col; + + //check for star hack - throws error if not allowed + if (tokenStream.peek() == Tokens.STAR && this.options.starHack){ + tokenStream.get(); + token = tokenStream.token(); + hack = token.value; + line = token.startLine; + col = token.startCol; + } + + if(tokenStream.match(Tokens.IDENT)){ + token = tokenStream.token(); + tokenValue = token.value; + + //check for underscore hack - no error if not allowed because it's valid CSS syntax + if (tokenValue.charAt(0) == "_" && this.options.underscoreHack){ + hack = "_"; + tokenValue = tokenValue.substring(1); + } + + value = new PropertyName(tokenValue, hack, (line||token.startLine), (col||token.startCol)); + this._readWhitespace(); + } + + return value; + }, + + //Augmented with CSS3 Selectors + _ruleset: function(){ + /* + * ruleset + * : selectors_group + * '{' S* declaration? [ ';' S* declaration? ]* '}' S* + * ; + */ + + var tokenStream = this._tokenStream, + tt, + selectors; + + + /* + * Error Recovery: If even a single selector fails to parse, + * then the entire ruleset should be thrown away. + */ + try { + selectors = this._selectors_group(); + } catch (ex){ + if (ex instanceof SyntaxError && !this.options.strict){ + + //fire error event + this.fire({ + type: "error", + error: ex, + message: ex.message, + line: ex.line, + col: ex.col + }); + + //skip over everything until closing brace + tt = tokenStream.advance([Tokens.RBRACE]); + if (tt == Tokens.RBRACE){ + //if there's a right brace, the rule is finished so don't do anything + } else { + //otherwise, rethrow the error because it wasn't handled properly + throw ex; + } + + } else { + //not a syntax error, rethrow it + throw ex; + } + + //trigger parser to continue + return true; + } + + //if it got here, all selectors parsed + if (selectors){ + + this.fire({ + type: "startrule", + selectors: selectors, + line: selectors[0].line, + col: selectors[0].col + }); + + this._readDeclarations(true); + + this.fire({ + type: "endrule", + selectors: selectors, + line: selectors[0].line, + col: selectors[0].col + }); + + } + + return selectors; + + }, + + //CSS3 Selectors + _selectors_group: function(){ + + /* + * selectors_group + * : selector [ COMMA S* selector ]* + * ; + */ + var tokenStream = this._tokenStream, + selectors = [], + selector; + + selector = this._selector(); + if (selector !== null){ + + selectors.push(selector); + while(tokenStream.match(Tokens.COMMA)){ + this._readWhitespace(); + selector = this._selector(); + if (selector !== null){ + selectors.push(selector); + } else { + this._unexpectedToken(tokenStream.LT(1)); + } + } + } + + return selectors.length ? selectors : null; + }, + + //CSS3 Selectors + _selector: function(){ + /* + * selector + * : simple_selector_sequence [ combinator simple_selector_sequence ]* + * ; + */ + + var tokenStream = this._tokenStream, + selector = [], + nextSelector = null, + combinator = null, + ws = null; + + //if there's no simple selector, then there's no selector + nextSelector = this._simple_selector_sequence(); + if (nextSelector === null){ + return null; + } + + selector.push(nextSelector); + + do { + + //look for a combinator + combinator = this._combinator(); + + if (combinator !== null){ + selector.push(combinator); + nextSelector = this._simple_selector_sequence(); + + //there must be a next selector + if (nextSelector === null){ + this._unexpectedToken(this.LT(1)); + } else { + + //nextSelector is an instance of SelectorPart + selector.push(nextSelector); + } + } else { + + //if there's not whitespace, we're done + if (this._readWhitespace()){ + + //add whitespace separator + ws = new Combinator(tokenStream.token().value, tokenStream.token().startLine, tokenStream.token().startCol); + + //combinator is not required + combinator = this._combinator(); + + //selector is required if there's a combinator + nextSelector = this._simple_selector_sequence(); + if (nextSelector === null){ + if (combinator !== null){ + this._unexpectedToken(tokenStream.LT(1)); + } + } else { + + if (combinator !== null){ + selector.push(combinator); + } else { + selector.push(ws); + } + + selector.push(nextSelector); + } + } else { + break; + } + + } + } while(true); + + return new Selector(selector, selector[0].line, selector[0].col); + }, + + //CSS3 Selectors + _simple_selector_sequence: function(){ + /* + * simple_selector_sequence + * : [ type_selector | universal ] + * [ HASH | class | attrib | pseudo | negation ]* + * | [ HASH | class | attrib | pseudo | negation ]+ + * ; + */ + + var tokenStream = this._tokenStream, + + //parts of a simple selector + elementName = null, + modifiers = [], + + //complete selector text + selectorText= "", + + //the different parts after the element name to search for + components = [ + //HASH + function(){ + return tokenStream.match(Tokens.HASH) ? + new SelectorSubPart(tokenStream.token().value, "id", tokenStream.token().startLine, tokenStream.token().startCol) : + null; + }, + this._class, + this._attrib, + this._pseudo, + this._negation + ], + i = 0, + len = components.length, + component = null, + found = false, + line, + col; + + + //get starting line and column for the selector + line = tokenStream.LT(1).startLine; + col = tokenStream.LT(1).startCol; + + elementName = this._type_selector(); + if (!elementName){ + elementName = this._universal(); + } + + if (elementName !== null){ + selectorText += elementName; + } + + while(true){ + + //whitespace means we're done + if (tokenStream.peek() === Tokens.S){ + break; + } + + //check for each component + while(i < len && component === null){ + component = components[i++].call(this); + } + + if (component === null){ + + //we don't have a selector + if (selectorText === ""){ + return null; + } else { + break; + } + } else { + i = 0; + modifiers.push(component); + selectorText += component.toString(); + component = null; + } + } + + + return selectorText !== "" ? + new SelectorPart(elementName, modifiers, selectorText, line, col) : + null; + }, + + //CSS3 Selectors + _type_selector: function(){ + /* + * type_selector + * : [ namespace_prefix ]? element_name + * ; + */ + + var tokenStream = this._tokenStream, + ns = this._namespace_prefix(), + elementName = this._element_name(); + + if (!elementName){ + /* + * Need to back out the namespace that was read due to both + * type_selector and universal reading namespace_prefix + * first. Kind of hacky, but only way I can figure out + * right now how to not change the grammar. + */ + if (ns){ + tokenStream.unget(); + if (ns.length > 1){ + tokenStream.unget(); + } + } + + return null; + } else { + if (ns){ + elementName.text = ns + elementName.text; + elementName.col -= ns.length; + } + return elementName; + } + }, + + //CSS3 Selectors + _class: function(){ + /* + * class + * : '.' IDENT + * ; + */ + + var tokenStream = this._tokenStream, + token; + + if (tokenStream.match(Tokens.DOT)){ + tokenStream.mustMatch(Tokens.IDENT); + token = tokenStream.token(); + return new SelectorSubPart("." + token.value, "class", token.startLine, token.startCol - 1); + } else { + return null; + } + + }, + + //CSS3 Selectors + _element_name: function(){ + /* + * element_name + * : IDENT + * ; + */ + + var tokenStream = this._tokenStream, + token; + + if (tokenStream.match(Tokens.IDENT)){ + token = tokenStream.token(); + return new SelectorSubPart(token.value, "elementName", token.startLine, token.startCol); + + } else { + return null; + } + }, + + //CSS3 Selectors + _namespace_prefix: function(){ + /* + * namespace_prefix + * : [ IDENT | '*' ]? '|' + * ; + */ + var tokenStream = this._tokenStream, + value = ""; + + //verify that this is a namespace prefix + if (tokenStream.LA(1) === Tokens.PIPE || tokenStream.LA(2) === Tokens.PIPE){ + + if(tokenStream.match([Tokens.IDENT, Tokens.STAR])){ + value += tokenStream.token().value; + } + + tokenStream.mustMatch(Tokens.PIPE); + value += "|"; + + } + + return value.length ? value : null; + }, + + //CSS3 Selectors + _universal: function(){ + /* + * universal + * : [ namespace_prefix ]? '*' + * ; + */ + var tokenStream = this._tokenStream, + value = "", + ns; + + ns = this._namespace_prefix(); + if(ns){ + value += ns; + } + + if(tokenStream.match(Tokens.STAR)){ + value += "*"; + } + + return value.length ? value : null; + + }, + + //CSS3 Selectors + _attrib: function(){ + /* + * attrib + * : '[' S* [ namespace_prefix ]? IDENT S* + * [ [ PREFIXMATCH | + * SUFFIXMATCH | + * SUBSTRINGMATCH | + * '=' | + * INCLUDES | + * DASHMATCH ] S* [ IDENT | STRING ] S* + * ]? ']' + * ; + */ + + var tokenStream = this._tokenStream, + value = null, + ns, + token; + + if (tokenStream.match(Tokens.LBRACKET)){ + token = tokenStream.token(); + value = token.value; + value += this._readWhitespace(); + + ns = this._namespace_prefix(); + + if (ns){ + value += ns; + } + + tokenStream.mustMatch(Tokens.IDENT); + value += tokenStream.token().value; + value += this._readWhitespace(); + + if(tokenStream.match([Tokens.PREFIXMATCH, Tokens.SUFFIXMATCH, Tokens.SUBSTRINGMATCH, + Tokens.EQUALS, Tokens.INCLUDES, Tokens.DASHMATCH])){ + + value += tokenStream.token().value; + value += this._readWhitespace(); + + tokenStream.mustMatch([Tokens.IDENT, Tokens.STRING]); + value += tokenStream.token().value; + value += this._readWhitespace(); + } + + tokenStream.mustMatch(Tokens.RBRACKET); + + return new SelectorSubPart(value + "]", "attribute", token.startLine, token.startCol); + } else { + return null; + } + }, + + //CSS3 Selectors + _pseudo: function(){ + + /* + * pseudo + * : ':' ':'? [ IDENT | functional_pseudo ] + * ; + */ + + var tokenStream = this._tokenStream, + pseudo = null, + colons = ":", + line, + col; + + if (tokenStream.match(Tokens.COLON)){ + + if (tokenStream.match(Tokens.COLON)){ + colons += ":"; + } + + if (tokenStream.match(Tokens.IDENT)){ + pseudo = tokenStream.token().value; + line = tokenStream.token().startLine; + col = tokenStream.token().startCol - colons.length; + } else if (tokenStream.peek() == Tokens.FUNCTION){ + line = tokenStream.LT(1).startLine; + col = tokenStream.LT(1).startCol - colons.length; + pseudo = this._functional_pseudo(); + } + + if (pseudo){ + pseudo = new SelectorSubPart(colons + pseudo, "pseudo", line, col); + } + } + + return pseudo; + }, + + //CSS3 Selectors + _functional_pseudo: function(){ + /* + * functional_pseudo + * : FUNCTION S* expression ')' + * ; + */ + + var tokenStream = this._tokenStream, + value = null; + + if(tokenStream.match(Tokens.FUNCTION)){ + value = tokenStream.token().value; + value += this._readWhitespace(); + value += this._expression(); + tokenStream.mustMatch(Tokens.RPAREN); + value += ")"; + } + + return value; + }, + + //CSS3 Selectors + _expression: function(){ + /* + * expression + * : [ [ PLUS | '-' | DIMENSION | NUMBER | STRING | IDENT ] S* ]+ + * ; + */ + + var tokenStream = this._tokenStream, + value = ""; + + while(tokenStream.match([Tokens.PLUS, Tokens.MINUS, Tokens.DIMENSION, + Tokens.NUMBER, Tokens.STRING, Tokens.IDENT, Tokens.LENGTH, + Tokens.FREQ, Tokens.ANGLE, Tokens.TIME, + Tokens.RESOLUTION])){ + + value += tokenStream.token().value; + value += this._readWhitespace(); + } + + return value.length ? value : null; + + }, + + //CSS3 Selectors + _negation: function(){ + /* + * negation + * : NOT S* negation_arg S* ')' + * ; + */ + + var tokenStream = this._tokenStream, + line, + col, + value = "", + arg, + subpart = null; + + if (tokenStream.match(Tokens.NOT)){ + value = tokenStream.token().value; + line = tokenStream.token().startLine; + col = tokenStream.token().startCol; + value += this._readWhitespace(); + arg = this._negation_arg(); + value += arg; + value += this._readWhitespace(); + tokenStream.match(Tokens.RPAREN); + value += tokenStream.token().value; + + subpart = new SelectorSubPart(value, "not", line, col); + subpart.args.push(arg); + } + + return subpart; + }, + + //CSS3 Selectors + _negation_arg: function(){ + /* + * negation_arg + * : type_selector | universal | HASH | class | attrib | pseudo + * ; + */ + + var tokenStream = this._tokenStream, + args = [ + this._type_selector, + this._universal, + function(){ + return tokenStream.match(Tokens.HASH) ? + new SelectorSubPart(tokenStream.token().value, "id", tokenStream.token().startLine, tokenStream.token().startCol) : + null; + }, + this._class, + this._attrib, + this._pseudo + ], + arg = null, + i = 0, + len = args.length, + elementName, + line, + col, + part; + + line = tokenStream.LT(1).startLine; + col = tokenStream.LT(1).startCol; + + while(i < len && arg === null){ + + arg = args[i].call(this); + i++; + } + + //must be a negation arg + if (arg === null){ + this._unexpectedToken(tokenStream.LT(1)); + } + + //it's an element name + if (arg.type == "elementName"){ + part = new SelectorPart(arg, [], arg.toString(), line, col); + } else { + part = new SelectorPart(null, [arg], arg.toString(), line, col); + } + + return part; + }, + + _declaration: function(){ + + /* + * declaration + * : property ':' S* expr prio? + * | /( empty )/ + * ; + */ + + var tokenStream = this._tokenStream, + property = null, + expr = null, + prio = null, + error = null, + valid = true; + + property = this._property(); + if (property !== null){ + + tokenStream.mustMatch(Tokens.COLON); + this._readWhitespace(); + + expr = this._expr(); + + //if there's no parts for the value, it's an error + if (!expr || expr.length === 0){ + this._unexpectedToken(tokenStream.LT(1)); + } + + prio = this._prio(); + + try { + this._validateProperty(property, expr); + } catch (ex) { + valid = false; + error = ex; + } + + this.fire({ + type: "property", + property: property, + value: expr, + important: prio, + line: property.line, + col: property.col, + valid: valid, + error: error + }); + + return true; + } else { + return false; + } + }, + + _prio: function(){ + /* + * prio + * : IMPORTANT_SYM S* + * ; + */ + + var tokenStream = this._tokenStream, + result = tokenStream.match(Tokens.IMPORTANT_SYM); + + this._readWhitespace(); + return result; + }, + + _expr: function(){ + /* + * expr + * : term [ operator term ]* + * ; + */ + + var tokenStream = this._tokenStream, + values = [], + //valueParts = [], + value = null, + operator = null; + + value = this._term(); + if (value !== null){ + + values.push(value); + + do { + operator = this._operator(); + + //if there's an operator, keep building up the value parts + if (operator){ + values.push(operator); + } /*else { + //if there's not an operator, you have a full value + values.push(new PropertyValue(valueParts, valueParts[0].line, valueParts[0].col)); + valueParts = []; + }*/ + + value = this._term(); + + if (value === null){ + break; + } else { + values.push(value); + } + } while(true); + } + + //cleanup + /*if (valueParts.length){ + values.push(new PropertyValue(valueParts, valueParts[0].line, valueParts[0].col)); + }*/ + + return values.length > 0 ? new PropertyValue(values, values[0].startLine, values[0].startCol) : null; + }, + + _term: function(){ + + /* + * term + * : unary_operator? + * [ NUMBER S* | PERCENTAGE S* | LENGTH S* | ANGLE S* | + * TIME S* | FREQ S* | function | ie_function ] + * | STRING S* | IDENT S* | URI S* | UNICODERANGE S* | hexcolor + * ; + */ + + var tokenStream = this._tokenStream, + unary = null, + value = null, + line, + col; + + //returns the operator or null + unary = this._unary_operator(); + if (unary !== null){ + line = tokenStream.token().startLine; + col = tokenStream.token().startCol; + } + + //exception for IE filters + if (tokenStream.peek() == Tokens.IE_FUNCTION && this.options.ieFilters){ + + value = this._ie_function(); + if (unary === null){ + line = tokenStream.token().startLine; + col = tokenStream.token().startCol; + } + + //see if there's a simple match + } else if (tokenStream.match([Tokens.NUMBER, Tokens.PERCENTAGE, Tokens.LENGTH, + Tokens.ANGLE, Tokens.TIME, + Tokens.FREQ, Tokens.STRING, Tokens.IDENT, Tokens.URI, Tokens.UNICODE_RANGE])){ + + value = tokenStream.token().value; + if (unary === null){ + line = tokenStream.token().startLine; + col = tokenStream.token().startCol; + } + this._readWhitespace(); + } else { + + //see if it's a color + value = this._hexcolor(); + if (value === null){ + + //if there's no unary, get the start of the next token for line/col info + if (unary === null){ + line = tokenStream.LT(1).startLine; + col = tokenStream.LT(1).startCol; + } + + //has to be a function + if (value === null){ + + /* + * This checks for alpha(opacity=0) style of IE + * functions. IE_FUNCTION only presents progid: style. + */ + if (tokenStream.LA(3) == Tokens.EQUALS && this.options.ieFilters){ + value = this._ie_function(); + } else { + value = this._function(); + } + } + + /*if (value === null){ + return null; + //throw new Error("Expected identifier at line " + tokenStream.token().startLine + ", character " + tokenStream.token().startCol + "."); + }*/ + + } else { + if (unary === null){ + line = tokenStream.token().startLine; + col = tokenStream.token().startCol; + } + } + + } + + return value !== null ? + new PropertyValuePart(unary !== null ? unary + value : value, line, col) : + null; + + }, + + _function: function(){ + + /* + * function + * : FUNCTION S* expr ')' S* + * ; + */ + + var tokenStream = this._tokenStream, + functionText = null, + expr = null; + + if (tokenStream.match(Tokens.FUNCTION)){ + functionText = tokenStream.token().value; + this._readWhitespace(); + expr = this._expr(); + + tokenStream.match(Tokens.RPAREN); + functionText += expr + ")"; + this._readWhitespace(); + } + + return functionText; + }, + + _ie_function: function(){ + + /* (My own extension) + * ie_function + * : IE_FUNCTION S* IDENT '=' term [S* ','? IDENT '=' term]+ ')' S* + * ; + */ + + var tokenStream = this._tokenStream, + functionText = null, + expr = null, + lt; + + //IE function can begin like a regular function, too + if (tokenStream.match([Tokens.IE_FUNCTION, Tokens.FUNCTION])){ + functionText = tokenStream.token().value; + + do { + + if (this._readWhitespace()){ + functionText += tokenStream.token().value; + } + + //might be second time in the loop + if (tokenStream.LA(0) == Tokens.COMMA){ + functionText += tokenStream.token().value; + } + + tokenStream.match(Tokens.IDENT); + functionText += tokenStream.token().value; + + tokenStream.match(Tokens.EQUALS); + functionText += tokenStream.token().value; + + //functionText += this._term(); + lt = tokenStream.peek(); + while(lt != Tokens.COMMA && lt != Tokens.S && lt != Tokens.RPAREN){ + tokenStream.get(); + functionText += tokenStream.token().value; + lt = tokenStream.peek(); + } + } while(tokenStream.match([Tokens.COMMA, Tokens.S])); + + tokenStream.match(Tokens.RPAREN); + functionText += ")"; + this._readWhitespace(); + } + + return functionText; + }, + + _hexcolor: function(){ + /* + * There is a constraint on the color that it must + * have either 3 or 6 hex-digits (i.e., [0-9a-fA-F]) + * after the "#"; e.g., "#000" is OK, but "#abcd" is not. + * + * hexcolor + * : HASH S* + * ; + */ + + var tokenStream = this._tokenStream, + token, + color = null; + + if(tokenStream.match(Tokens.HASH)){ + + //need to do some validation here + + token = tokenStream.token(); + color = token.value; + if (!/#[a-f0-9]{3,6}/i.test(color)){ + throw new SyntaxError("Expected a hex color but found '" + color + "' at line " + token.startLine + ", col " + token.startCol + ".", token.startLine, token.startCol); + } + this._readWhitespace(); + } + + return color; + }, + + //----------------------------------------------------------------- + // Animations methods + //----------------------------------------------------------------- + + _keyframes: function(){ + + /* + * keyframes: + * : KEYFRAMES_SYM S* keyframe_name S* '{' S* keyframe_rule* '}' { + * ; + */ + var tokenStream = this._tokenStream, + token, + tt, + name; + + tokenStream.mustMatch(Tokens.KEYFRAMES_SYM); + this._readWhitespace(); + name = this._keyframe_name(); + + this._readWhitespace(); + tokenStream.mustMatch(Tokens.LBRACE); + + this.fire({ + type: "startkeyframes", + name: name, + line: name.line, + col: name.col + }); + + this._readWhitespace(); + tt = tokenStream.peek(); + + //check for key + while(tt == Tokens.IDENT || tt == Tokens.PERCENTAGE) { + this._keyframe_rule(); + this._readWhitespace(); + tt = tokenStream.peek(); + } + + this.fire({ + type: "endkeyframes", + name: name, + line: name.line, + col: name.col + }); + + this._readWhitespace(); + tokenStream.mustMatch(Tokens.RBRACE); + + }, + + _keyframe_name: function(){ + + /* + * keyframe_name: + * : IDENT + * | STRING + * ; + */ + var tokenStream = this._tokenStream, + token; + + tokenStream.mustMatch([Tokens.IDENT, Tokens.STRING]); + return SyntaxUnit.fromToken(tokenStream.token()); + }, + + _keyframe_rule: function(){ + + /* + * keyframe_rule: + * : key_list S* + * '{' S* declaration [ ';' S* declaration ]* '}' S* + * ; + */ + var tokenStream = this._tokenStream, + token, + keyList = this._key_list(); + + this.fire({ + type: "startkeyframerule", + keys: keyList, + line: keyList[0].line, + col: keyList[0].col + }); + + this._readDeclarations(true); + + this.fire({ + type: "endkeyframerule", + keys: keyList, + line: keyList[0].line, + col: keyList[0].col + }); + + }, + + _key_list: function(){ + + /* + * key_list: + * : key [ S* ',' S* key]* + * ; + */ + var tokenStream = this._tokenStream, + token, + key, + keyList = []; + + //must be least one key + keyList.push(this._key()); + + this._readWhitespace(); + + while(tokenStream.match(Tokens.COMMA)){ + this._readWhitespace(); + keyList.push(this._key()); + this._readWhitespace(); + } + + return keyList; + }, + + _key: function(){ + /* + * There is a restriction that IDENT can be only "from" or "to". + * + * key + * : PERCENTAGE + * | IDENT + * ; + */ + + var tokenStream = this._tokenStream, + token; + + if (tokenStream.match(Tokens.PERCENTAGE)){ + return SyntaxUnit.fromToken(tokenStream.token()); + } else if (tokenStream.match(Tokens.IDENT)){ + token = tokenStream.token(); + + if (/from|to/i.test(token.value)){ + return SyntaxUnit.fromToken(token); + } + + tokenStream.unget(); + } + + //if it gets here, there wasn't a valid token, so time to explode + this._unexpectedToken(tokenStream.LT(1)); + }, + + //----------------------------------------------------------------- + // Helper methods + //----------------------------------------------------------------- + + /** + * Not part of CSS grammar, but useful for skipping over + * combination of white space and HTML-style comments. + * @return {void} + * @method _skipCruft + * @private + */ + _skipCruft: function(){ + while(this._tokenStream.match([Tokens.S, Tokens.CDO, Tokens.CDC])){ + //noop + } + }, + + /** + * Not part of CSS grammar, but this pattern occurs frequently + * in the official CSS grammar. Split out here to eliminate + * duplicate code. + * @param {Boolean} checkStart Indicates if the rule should check + * for the left brace at the beginning. + * @param {Boolean} readMargins Indicates if the rule should check + * for margin patterns. + * @return {void} + * @method _readDeclarations + * @private + */ + _readDeclarations: function(checkStart, readMargins){ + /* + * Reads the pattern + * S* '{' S* declaration [ ';' S* declaration ]* '}' S* + * or + * S* '{' S* [ declaration | margin ]? [ ';' S* [ declaration | margin ]? ]* '}' S* + * Note that this is how it is described in CSS3 Paged Media, but is actually incorrect. + * A semicolon is only necessary following a delcaration is there's another declaration + * or margin afterwards. + */ + var tokenStream = this._tokenStream, + tt; + + + this._readWhitespace(); + + if (checkStart){ + tokenStream.mustMatch(Tokens.LBRACE); + } + + this._readWhitespace(); + + try { + + while(true){ + + if (readMargins && this._margin()){ + //noop + } else if (this._declaration()){ + if (!tokenStream.match(Tokens.SEMICOLON)){ + break; + } + } else { + break; + } + + //if ((!this._margin() && !this._declaration()) || !tokenStream.match(Tokens.SEMICOLON)){ + // break; + //} + this._readWhitespace(); + } + + tokenStream.mustMatch(Tokens.RBRACE); + this._readWhitespace(); + + } catch (ex) { + if (ex instanceof SyntaxError && !this.options.strict){ + + //fire error event + this.fire({ + type: "error", + error: ex, + message: ex.message, + line: ex.line, + col: ex.col + }); + + //see if there's another declaration + tt = tokenStream.advance([Tokens.SEMICOLON, Tokens.RBRACE]); + if (tt == Tokens.SEMICOLON){ + //if there's a semicolon, then there might be another declaration + this._readDeclarations(false, readMargins); + } else if (tt == Tokens.RBRACE){ + //if there's a right brace, the rule is finished so don't do anything + } else { + //otherwise, rethrow the error because it wasn't handled properly + throw ex; + } + + } else { + //not a syntax error, rethrow it + throw ex; + } + } + + }, + + /** + * In some cases, you can end up with two white space tokens in a + * row. Instead of making a change in every function that looks for + * white space, this function is used to match as much white space + * as necessary. + * @method _readWhitespace + * @return {String} The white space if found, empty string if not. + * @private + */ + _readWhitespace: function(){ + + var tokenStream = this._tokenStream, + ws = ""; + + while(tokenStream.match(Tokens.S)){ + ws += tokenStream.token().value; + } + + return ws; + }, + + + /** + * Throws an error when an unexpected token is found. + * @param {Object} token The token that was found. + * @method _unexpectedToken + * @return {void} + * @private + */ + _unexpectedToken: function(token){ + throw new SyntaxError("Unexpected token '" + token.value + "' at line " + token.startLine + ", col " + token.startCol + ".", token.startLine, token.startCol); + }, + + /** + * Helper method used for parsing subparts of a style sheet. + * @return {void} + * @method _verifyEnd + * @private + */ + _verifyEnd: function(){ + if (this._tokenStream.LA(1) != Tokens.EOF){ + this._unexpectedToken(this._tokenStream.LT(1)); + } + }, + + //----------------------------------------------------------------- + // Validation methods + //----------------------------------------------------------------- + _validateProperty: function(property, value){ + var name = property.text.toLowerCase(), + validation, + i, len; + + if (Properties[name]){ + validation = Properties[name]; + if (typeof validation == "object"){ + for (i=0, len=validation.parts.length; i < len; i++){ + if (!validation.parts[i]){ + throw new ValidationError("Unexpected value. Expected only " + validation.parts.length + " values for property '" + property + "'.", + value.line, value.col); + } else if ((new RegExp("^("+validation.parts[i].types.join("|")+")$")).test(value.parts[i].type)){ + if (validation.parts[i][RegExp.$1]){ + if (!validation.parts[i][RegExp.$1].test(value.parts[i])){ + throw new ValidationError("Unexpected value '" + value.parts[i] + + "'.", value.parts[i].line, value.parts[i].col); + } + } + } else { + throw new ValidationError("Unexpected value type " + value.parts[i].type + + ". Expected " + validation.parts[i].types + ".", value.parts[i].line, value.parts[i].col); + } + } + } + + //otherwise, no validation available yet + } else if (name.indexOf("-") !== 0){ //vendor prefixed are ok + throw new ValidationError("Property '" + property + "' isn't recognized.", property.line, property.col); + } + }, + + //----------------------------------------------------------------- + // Parsing methods + //----------------------------------------------------------------- + + parse: function(input){ + this._tokenStream = new TokenStream(input, Tokens); + this._stylesheet(); + }, + + parseStyleSheet: function(input){ + //just passthrough + return this.parse(input); + }, + + parseMediaQuery: function(input){ + this._tokenStream = new TokenStream(input, Tokens); + var result = this._media_query(); + + //if there's anything more, then it's an invalid selector + this._verifyEnd(); + + //otherwise return result + return result; + }, + + /** + * Parses a property value (everything after the semicolon). + * @return {parserlib.css.PropertyValue} The property value. + * @throws parserlib.util.SyntaxError If an unexpected token is found. + * @method parserPropertyValue + */ + parsePropertyValue: function(input){ + + this._tokenStream = new TokenStream(input, Tokens); + this._readWhitespace(); + + var result = this._expr(); + + //okay to have a trailing white space + this._readWhitespace(); + + //if there's anything more, then it's an invalid selector + this._verifyEnd(); + + //otherwise return result + return result; + }, + + /** + * Parses a complete CSS rule, including selectors and + * properties. + * @param {String} input The text to parser. + * @return {Boolean} True if the parse completed successfully, false if not. + * @method parseRule + */ + parseRule: function(input){ + this._tokenStream = new TokenStream(input, Tokens); + + //skip any leading white space + this._readWhitespace(); + + var result = this._ruleset(); + + //skip any trailing white space + this._readWhitespace(); + + //if there's anything more, then it's an invalid selector + this._verifyEnd(); + + //otherwise return result + return result; + }, + + /** + * Parses a single CSS selector (no comma) + * @param {String} input The text to parse as a CSS selector. + * @return {Selector} An object representing the selector. + * @throws parserlib.util.SyntaxError If an unexpected token is found. + * @method parseSelector + */ + parseSelector: function(input){ + + this._tokenStream = new TokenStream(input, Tokens); + + //skip any leading white space + this._readWhitespace(); + + var result = this._selector(); + + //skip any trailing white space + this._readWhitespace(); + + //if there's anything more, then it's an invalid selector + this._verifyEnd(); + + //otherwise return result + return result; + } + + }; + + //copy over onto prototype + for (prop in additions){ + proto[prop] = additions[prop]; + } + + return proto; +}(); + + +/* +nth + : S* [ ['-'|'+']? INTEGER? {N} [ S* ['-'|'+'] S* INTEGER ]? | + ['-'|'+']? INTEGER | {O}{D}{D} | {E}{V}{E}{N} ] S* + ; +*/ +var Validation = { + measurement: { + parts: [ + { + types: ["length", "percentage", "integer", "identifier"], + identifier: /^(auto|inherit)$/i, + integer: /^0$/ + } + ] + }, + oneColor: { + maxParts: 1, + minParts: 1, + parts: [ + { + types: ["color", "identifier"], + identifier: /^(inherit|transparent)$/i + } + ] + } +}; + + + + + + + + + +var Properties = { + + "alignment-adjust": 1, + "alignment-baseline": 1, + "animation": 1, + "animation-delay": 1, + "animation-direction": 1, + "animation-duration": 1, + "animation-iteration-count": 1, + "animation-name": 1, + "animation-play-state": 1, + "animation-timing-function": 1, + "appearance": 1, + "azimuth": 1, + "backface-visibility": 1, + "background": 1, + "background-attachment": 1, + "background-break": 1, + "background-clip": 1, + "background-color": Validation.oneColor, + "background-image": 1, + "background-origin": 1, + "background-position": 1, + "background-repeat": 1, + "background-size": 1, + "baseline-shift": 1, + "binding": 1, + "bleed": 1, + "bookmark-label": 1, + "bookmark-level": 1, + "bookmark-state": 1, + "bookmark-target": 1, + "border": 1, + "border-bottom": 1, + "border-bottom-color": 1, + "border-bottom-left-radius": 1, + "border-bottom-right-radius": 1, + "border-bottom-style": 1, + "border-bottom-width": 1, + "border-collapse": 1, + "border-color": Validation.oneColor, + "border-image": 1, + "border-image-outset": 1, + "border-image-repeat": 1, + "border-image-slice": 1, + "border-image-source": 1, + "border-image-width": 1, + "border-left": 1, + "border-left-color": 1, + "border-left-style": 1, + "border-left-width": 1, + "border-radius": 1, + "border-right": 1, + "border-right-color": 1, + "border-right-style": 1, + "border-right-width": 1, + "border-spacing": 1, + "border-style": 1, + "border-top": 1, + "border-top-color": 1, + "border-top-left-radius": 1, + "border-top-right-radius": 1, + "border-top-style": 1, + "border-top-width": 1, + "border-width": 1, + "bottom": Validation.measurement, + "box-align": 1, + "box-decoration-break": 1, + "box-direction": 1, + "box-flex": 1, + "box-flex-group": 1, + "box-lines": 1, + "box-ordinal-group": 1, + "box-orient": 1, + "box-pack": 1, + "box-shadow": 1, + "box-sizing": 1, + "break-after": 1, + "break-before": 1, + "break-inside": 1, + "caption-side": 1, + "clear": 1, + "clip": 1, + "color": { + parts: [ + { + types: ["color", "identifier"], + identifier: /^inherit$/i + } + ] + }, + "color-profile": 1, + "column-count": 1, + "column-fill": 1, + "column-gap": 1, + "column-rule": 1, + "column-rule-color": 1, + "column-rule-style": 1, + "column-rule-width": 1, + "column-span": 1, + "column-width": 1, + "columns": 1, + "content": 1, + "counter-increment": 1, + "counter-reset": 1, + "crop": 1, + "cue": 1, + "cue-after": 1, + "cue-before": 1, + "cursor": 1, + "direction": 1, + "display": 1, + "dominant-baseline": 1, + "drop-initial-after-adjust": 1, + "drop-initial-after-align": 1, + "drop-initial-before-adjust": 1, + "drop-initial-before-align": 1, + "drop-initial-size": 1, + "drop-initial-value": 1, + "elevation": 1, + "empty-cells": 1, + "fit": 1, + "fit-position": 1, + "float": { + parts: [ + { + types: ["identifier"], + identifier: /^(left|right|none|inherit)$/i + } + ] + }, + + "float-offset": 1, + "font": 1, + "font-family": 1, + "font-size": 1, + "font-size-adjust": 1, + "font-stretch": 1, + "font-style": 1, + "font-variant": 1, + "font-weight": 1, + "grid-columns": 1, + "grid-rows": 1, + "hanging-punctuation": 1, + "height": Validation.measurement, + "hyphenate-after": 1, + "hyphenate-before": 1, + "hyphenate-character": 1, + "hyphenate-lines": 1, + "hyphenate-resource": 1, + "hyphens": 1, + "icon": 1, + "image-orientation": 1, + "image-rendering": 1, + "image-resolution": 1, + "inline-box-align": 1, + "left": Validation.measurement, + "letter-spacing": 1, + "line-height": 1, + "line-stacking": 1, + "line-stacking-ruby": 1, + "line-stacking-shift": 1, + "line-stacking-strategy": 1, + "list-style": 1, + "list-style-image": 1, + "list-style-position": 1, + "list-style-type": 1, + "margin": 1, + "margin-bottom": 1, + "margin-left": 1, + "margin-right": 1, + "margin-top": 1, + "mark": 1, + "mark-after": 1, + "mark-before": 1, + "marks": 1, + "marquee-direction": 1, + "marquee-play-count": 1, + "marquee-speed": 1, + "marquee-style": 1, + "max-height": 1, + "max-width": 1, + "min-height": 1, + "min-width": 1, + "move-to": 1, + "nav-down": 1, + "nav-index": 1, + "nav-left": 1, + "nav-right": 1, + "nav-up": 1, + "opacity": 1, + "orphans": 1, + "outline": 1, + "outline-color": 1, + "outline-offset": 1, + "outline-style": 1, + "outline-width": 1, + "overflow": 1, + "overflow-style": 1, + "overflow-x": 1, + "overflow-y": 1, + "padding": 1, + "padding-bottom": 1, + "padding-left": 1, + "padding-right": 1, + "padding-top": 1, + "page": 1, + "page-break-after": 1, + "page-break-before": 1, + "page-break-inside": 1, + "page-policy": 1, + "pause": 1, + "pause-after": 1, + "pause-before": 1, + "perspective": 1, + "perspective-origin": 1, + "phonemes": 1, + "pitch": 1, + "pitch-range": 1, + "play-during": 1, + "position": 1, + "presentation-level": 1, + "punctuation-trim": 1, + "quotes": 1, + "rendering-intent": 1, + "resize": 1, + "rest": 1, + "rest-after": 1, + "rest-before": 1, + "richness": 1, + "right": Validation.measurement, + "rotation": 1, + "rotation-point": 1, + "ruby-align": 1, + "ruby-overhang": 1, + "ruby-position": 1, + "ruby-span": 1, + "size": 1, + "speak": 1, + "speak-header": 1, + "speak-numeral": 1, + "speak-punctuation": 1, + "speech-rate": 1, + "stress": 1, + "string-set": 1, + "table-layout": 1, + "target": 1, + "target-name": 1, + "target-new": 1, + "target-position": 1, + "text-align": 1, + "text-align-last": 1, + "text-decoration": 1, + "text-emphasis": 1, + "text-height": 1, + "text-indent": 1, + "text-justify": 1, + "text-outline": 1, + "text-shadow": 1, + "text-transform": 1, + "text-wrap": 1, + "top": Validation.measurement, + "transform": 1, + "transform-origin": 1, + "transform-style": 1, + "transition": 1, + "transition-delay": 1, + "transition-duration": 1, + "transition-property": 1, + "transition-timing-function": 1, + "unicode-bidi": 1, + "vertical-align": 1, + "visibility": 1, + "voice-balance": 1, + "voice-duration": 1, + "voice-family": 1, + "voice-pitch": 1, + "voice-pitch-range": 1, + "voice-rate": 1, + "voice-stress": 1, + "voice-volume": 1, + "volume": 1, + "white-space": 1, + "white-space-collapse": 1, + "widows": 1, + "width": Validation.measurement, + "word-break": 1, + "word-spacing": { + minParts: 1, + maxParts: 1, + parts: [ + { + types: ["length", "number", "identifier"], + identifier: /^(normal|inherit)$/, + number: /^0$/ + } + ] + }, + "word-wrap": 1, + "z-index": { + minParts: 1, + maxParts: 1, + parts: [ + { + types: ["length", "identifier"], + identifier: /^(auto|inherit)$/ + } + ] + } + + +}; +/** + * Represents a selector combinator (whitespace, +, >). + * @namespace parserlib.css + * @class PropertyName + * @extends parserlib.util.SyntaxUnit + * @constructor + * @param {String} text The text representation of the unit. + * @param {String} hack The type of IE hack applied ("*", "_", or null). + * @param {int} line The line of text on which the unit resides. + * @param {int} col The column of text on which the unit resides. + */ +function PropertyName(text, hack, line, col){ + + SyntaxUnit.call(this, text, line, col); + + /** + * The type of IE hack applied ("*", "_", or null). + * @type String + * @property hack + */ + this.hack = hack; + +} + +PropertyName.prototype = new SyntaxUnit(); +PropertyName.prototype.constructor = PropertyName; +PropertyName.prototype.toString = function(){ + return (this.hack ? this.hack : "") + this.text; +}; +/** + * Represents a single part of a CSS property value, meaning that it represents + * just everything single part between ":" and ";". If there are multiple values + * separated by commas, this type represents just one of the values. + * @param {String[]} parts An array of value parts making up this value. + * @param {int} line The line of text on which the unit resides. + * @param {int} col The column of text on which the unit resides. + * @namespace parserlib.css + * @class PropertyValue + * @extends parserlib.util.SyntaxUnit + * @constructor + */ +function PropertyValue(parts, line, col){ + + SyntaxUnit.call(this, parts.join(" "), line, col); + + /** + * The parts that make up the selector. + * @type Array + * @property parts + */ + this.parts = parts; + +} + +PropertyValue.prototype = new SyntaxUnit(); +PropertyValue.prototype.constructor = PropertyValue; + +/** + * Represents a single part of a CSS property value, meaning that it represents + * just one part of the data between ":" and ";". + * @param {String} text The text representation of the unit. + * @param {int} line The line of text on which the unit resides. + * @param {int} col The column of text on which the unit resides. + * @namespace parserlib.css + * @class PropertyValuePart + * @extends parserlib.util.SyntaxUnit + * @constructor + */ +function PropertyValuePart(text, line, col){ + + SyntaxUnit.apply(this,arguments); + + /** + * Indicates the type of value unit. + * @type String + * @property type + */ + this.type = "unknown"; + + //figure out what type of data it is + + var temp; + + //it is a measurement? + if (/^([+\-]?[\d\.]+)([a-z]+)$/i.test(text)){ //dimension + this.type = "dimension"; + this.value = +RegExp.$1; + this.units = RegExp.$2; + + //try to narrow down + switch(this.units.toLowerCase()){ + + case "em": + case "rem": + case "ex": + case "px": + case "cm": + case "mm": + case "in": + case "pt": + case "pc": + this.type = "length"; + break; + + case "deg": + case "rad": + case "grad": + this.type = "angle"; + break; + + case "ms": + case "s": + this.type = "time"; + break; + + case "hz": + case "khz": + this.type = "frequency"; + break; + + case "dpi": + case "dpcm": + this.type = "resolution"; + break; + + //default + + } + + } else if (/^([+\-]?[\d\.]+)%$/i.test(text)){ //percentage + this.type = "percentage"; + this.value = +RegExp.$1; + } else if (/^([+\-]?[\d\.]+)%$/i.test(text)){ //percentage + this.type = "percentage"; + this.value = +RegExp.$1; + } else if (/^([+\-]?\d+)$/i.test(text)){ //integer + this.type = "integer"; + this.value = +RegExp.$1; + } else if (/^([+\-]?[\d\.]+)$/i.test(text)){ //number + this.type = "number"; + this.value = +RegExp.$1; + + } else if (/^#([a-f0-9]{3,6})/i.test(text)){ //hexcolor + this.type = "color"; + temp = RegExp.$1; + if (temp.length == 3){ + this.red = parseInt(temp.charAt(0)+temp.charAt(0),16); + this.green = parseInt(temp.charAt(1)+temp.charAt(1),16); + this.blue = parseInt(temp.charAt(2)+temp.charAt(2),16); + } else { + this.red = parseInt(temp.substring(0,2),16); + this.green = parseInt(temp.substring(2,4),16); + this.blue = parseInt(temp.substring(4,6),16); + } + } else if (/^rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/i.test(text)){ //rgb() color with absolute numbers + this.type = "color"; + this.red = +RegExp.$1; + this.green = +RegExp.$2; + this.blue = +RegExp.$3; + } else if (/^rgb\(\s*(\d+)%\s*,\s*(\d+)%\s*,\s*(\d+)%\s*\)/i.test(text)){ //rgb() color with percentages + this.type = "color"; + this.red = +RegExp.$1 * 255 / 100; + this.green = +RegExp.$2 * 255 / 100; + this.blue = +RegExp.$3 * 255 / 100; + } else if (/^url\(["']?([^\)"']+)["']?\)/i.test(text)){ //URI + this.type = "uri"; + this.uri = RegExp.$1; + } else if (/^["'][^"']*["']/.test(text)){ //string + this.type = "string"; + this.value = eval(text); + } else if (Colors[text.toLowerCase()]){ //named color + this.type = "color"; + temp = Colors[text.toLowerCase()].substring(1); + this.red = parseInt(temp.substring(0,2),16); + this.green = parseInt(temp.substring(2,4),16); + this.blue = parseInt(temp.substring(4,6),16); + } else if (/^[\,\/]$/.test(text)){ + this.type = "operator"; + this.value = text; + } else if (/^[a-z\-\u0080-\uFFFF][a-z0-9\-\u0080-\uFFFF]*$/i.test(text)){ + this.type = "identifier"; + this.value = text; + } + +} + +PropertyValuePart.prototype = new SyntaxUnit(); +PropertyValuePart.prototype.constructor = PropertyValue; + +/** + * Create a new syntax unit based solely on the given token. + * Convenience method for creating a new syntax unit when + * it represents a single token instead of multiple. + * @param {Object} token The token object to represent. + * @return {parserlib.css.PropertyValuePart} The object representing the token. + * @static + * @method fromToken + */ +PropertyValuePart.fromToken = function(token){ + return new PropertyValuePart(token.value, token.startLine, token.startCol); +}; +/** + * Represents an entire single selector, including all parts but not + * including multiple selectors (those separated by commas). + * @namespace parserlib.css + * @class Selector + * @extends parserlib.util.SyntaxUnit + * @constructor + * @param {Array} parts Array of selectors parts making up this selector. + * @param {int} line The line of text on which the unit resides. + * @param {int} col The column of text on which the unit resides. + */ +function Selector(parts, line, col){ + + SyntaxUnit.call(this, parts.join(" "), line, col); + + /** + * The parts that make up the selector. + * @type Array + * @property parts + */ + this.parts = parts; + +} + +Selector.prototype = new SyntaxUnit(); +Selector.prototype.constructor = Selector; + +/** + * Represents a single part of a selector string, meaning a single set of + * element name and modifiers. This does not include combinators such as + * spaces, +, >, etc. + * @namespace parserlib.css + * @class SelectorPart + * @extends parserlib.util.SyntaxUnit + * @constructor + * @param {String} elementName The element name in the selector or null + * if there is no element name. + * @param {Array} modifiers Array of individual modifiers for the element. + * May be empty if there are none. + * @param {String} text The text representation of the unit. + * @param {int} line The line of text on which the unit resides. + * @param {int} col The column of text on which the unit resides. + */ +function SelectorPart(elementName, modifiers, text, line, col){ + + SyntaxUnit.call(this, text, line, col); + + /** + * The tag name of the element to which this part + * of the selector affects. + * @type String + * @property elementName + */ + this.elementName = elementName; + + /** + * The parts that come after the element name, such as class names, IDs, + * pseudo classes/elements, etc. + * @type Array + * @property modifiers + */ + this.modifiers = modifiers; + +} + +SelectorPart.prototype = new SyntaxUnit(); +SelectorPart.prototype.constructor = SelectorPart; + +/** + * Represents a selector modifier string, meaning a class name, element name, + * element ID, pseudo rule, etc. + * @namespace parserlib.css + * @class SelectorSubPart + * @extends parserlib.util.SyntaxUnit + * @constructor + * @param {String} text The text representation of the unit. + * @param {String} type The type of selector modifier. + * @param {int} line The line of text on which the unit resides. + * @param {int} col The column of text on which the unit resides. + */ +function SelectorSubPart(text, type, line, col){ + + SyntaxUnit.call(this, text, line, col); + + /** + * The type of modifier. + * @type String + * @property type + */ + this.type = type; + + /** + * Some subparts have arguments, this represents them. + * @type Array + * @property args + */ + this.args = []; + +} + +SelectorSubPart.prototype = new SyntaxUnit(); +SelectorSubPart.prototype.constructor = SelectorSubPart; + + + +var h = /^[0-9a-fA-F]$/, + nonascii = /^[\u0080-\uFFFF]$/, + nl = /\n|\r\n|\r|\f/; + +//----------------------------------------------------------------------------- +// Helper functions +//----------------------------------------------------------------------------- + + +function isHexDigit(c){ + return c != null && h.test(c); +} + +function isDigit(c){ + return c != null && /\d/.test(c); +} + +function isWhitespace(c){ + return c != null && /\s/.test(c); +} + +function isNewLine(c){ + return c != null && nl.test(c); +} + +function isNameStart(c){ + return c != null && (/[a-z_\u0080-\uFFFF\\]/i.test(c)); +} + +function isNameChar(c){ + return c != null && (isNameStart(c) || /[0-9\-\\]/.test(c)); +} + +function isIdentStart(c){ + return c != null && (isNameStart(c) || /\-\\/.test(c)); +} + +function mix(receiver, supplier){ + for (var prop in supplier){ + if (supplier.hasOwnProperty(prop)){ + receiver[prop] = supplier[prop]; + } + } + return receiver; +} + +//----------------------------------------------------------------------------- +// CSS Token Stream +//----------------------------------------------------------------------------- + + +/** + * A token stream that produces CSS tokens. + * @param {String|Reader} input The source of text to tokenize. + * @constructor + * @class TokenStream + * @namespace parserlib.css + */ +function TokenStream(input){ + TokenStreamBase.call(this, input, Tokens); +} + +TokenStream.prototype = mix(new TokenStreamBase(), { + + /** + * Overrides the TokenStreamBase method of the same name + * to produce CSS tokens. + * @param {variant} channel The name of the channel to use + * for the next token. + * @return {Object} A token object representing the next token. + * @method _getToken + * @private + */ + _getToken: function(channel){ + + var c, + reader = this._reader, + token = null, + startLine = reader.getLine(), + startCol = reader.getCol(); + + c = reader.read(); + + + while(c){ + switch(c){ + + /* + * Potential tokens: + * - COMMENT + * - SLASH + * - CHAR + */ + case "/": + + if(reader.peek() == "*"){ + token = this.commentToken(c, startLine, startCol); + } else { + token = this.charToken(c, startLine, startCol); + } + break; + + /* + * Potential tokens: + * - DASHMATCH + * - INCLUDES + * - PREFIXMATCH + * - SUFFIXMATCH + * - SUBSTRINGMATCH + * - CHAR + */ + case "|": + case "~": + case "^": + case "$": + case "*": + if(reader.peek() == "="){ + token = this.comparisonToken(c, startLine, startCol); + } else { + token = this.charToken(c, startLine, startCol); + } + break; + + /* + * Potential tokens: + * - STRING + * - INVALID + */ + case "\"": + case "'": + token = this.stringToken(c, startLine, startCol); + break; + + /* + * Potential tokens: + * - HASH + * - CHAR + */ + case "#": + if (isNameChar(reader.peek())){ + token = this.hashToken(c, startLine, startCol); + } else { + token = this.charToken(c, startLine, startCol); + } + break; + + /* + * Potential tokens: + * - DOT + * - NUMBER + * - DIMENSION + * - PERCENTAGE + */ + case ".": + if (isDigit(reader.peek())){ + token = this.numberToken(c, startLine, startCol); + } else { + token = this.charToken(c, startLine, startCol); + } + break; + + /* + * Potential tokens: + * - CDC + * - MINUS + * - NUMBER + * - DIMENSION + * - PERCENTAGE + */ + case "-": + if (reader.peek() == "-"){ //could be closing HTML-style comment + token = this.htmlCommentEndToken(c, startLine, startCol); + } else if (isNameStart(reader.peek())){ + token = this.identOrFunctionToken(c, startLine, startCol); + } else { + token = this.charToken(c, startLine, startCol); + } + break; + + /* + * Potential tokens: + * - IMPORTANT_SYM + * - CHAR + */ + case "!": + token = this.importantToken(c, startLine, startCol); + break; + + /* + * Any at-keyword or CHAR + */ + case "@": + token = this.atRuleToken(c, startLine, startCol); + break; + + /* + * Potential tokens: + * - NOT + * - CHAR + */ + case ":": + token = this.notToken(c, startLine, startCol); + break; + + /* + * Potential tokens: + * - CDO + * - CHAR + */ + case "<": + token = this.htmlCommentStartToken(c, startLine, startCol); + break; + + /* + * Potential tokens: + * - UNICODE_RANGE + * - URL + * - CHAR + */ + case "U": + case "u": + if (reader.peek() == "+"){ + token = this.unicodeRangeToken(c, startLine, startCol); + break; + } + /*falls through*/ + + default: + + /* + * Potential tokens: + * - NUMBER + * - DIMENSION + * - LENGTH + * - FREQ + * - TIME + * - EMS + * - EXS + * - ANGLE + */ + if (isDigit(c)){ + token = this.numberToken(c, startLine, startCol); + } else + + /* + * Potential tokens: + * - S + */ + if (isWhitespace(c)){ + token = this.whitespaceToken(c, startLine, startCol); + } else + + /* + * Potential tokens: + * - IDENT + */ + if (isIdentStart(c)){ + token = this.identOrFunctionToken(c, startLine, startCol); + } else + + /* + * Potential tokens: + * - CHAR + * - PLUS + */ + { + token = this.charToken(c, startLine, startCol); + } + + + + + + + } + + //make sure this token is wanted + //TODO: check channel + break; + + c = reader.read(); + } + + if (!token && c == null){ + token = this.createToken(Tokens.EOF,null,startLine,startCol); + } + + return token; + }, + + //------------------------------------------------------------------------- + // Methods to create tokens + //------------------------------------------------------------------------- + + /** + * Produces a token based on available data and the current + * reader position information. This method is called by other + * private methods to create tokens and is never called directly. + * @param {int} tt The token type. + * @param {String} value The text value of the token. + * @param {int} startLine The beginning line for the character. + * @param {int} startCol The beginning column for the character. + * @param {Object} options (Optional) Specifies a channel property + * to indicate that a different channel should be scanned + * and/or a hide property indicating that the token should + * be hidden. + * @return {Object} A token object. + * @method createToken + */ + createToken: function(tt, value, startLine, startCol, options){ + var reader = this._reader; + options = options || {}; + + return { + value: value, + type: tt, + channel: options.channel, + hide: options.hide || false, + startLine: startLine, + startCol: startCol, + endLine: reader.getLine(), + endCol: reader.getCol() + }; + }, + + //------------------------------------------------------------------------- + // Methods to create specific tokens + //------------------------------------------------------------------------- + + /** + * Produces a token for any at-rule. If the at-rule is unknown, then + * the token is for a single "@" character. + * @param {String} first The first character for the token. + * @param {int} startLine The beginning line for the character. + * @param {int} startCol The beginning column for the character. + * @return {Object} A token object. + * @method atRuleToken + */ + atRuleToken: function(first, startLine, startCol){ + var rule = first, + reader = this._reader, + tt = Tokens.CHAR, + valid = false, + ident, + c; + + /* + * First, mark where we are. There are only four @ rules, + * so anything else is really just an invalid token. + * Basically, if this doesn't match one of the known @ + * rules, just return '@' as an unknown token and allow + * parsing to continue after that point. + */ + reader.mark(); + + //try to find the at-keyword + ident = this.readName(); + rule = first + ident; + tt = Tokens.type(rule.toLowerCase()); + + //if it's not valid, use the first character only and reset the reader + if (tt == Tokens.CHAR || tt == Tokens.UNKNOWN){ + tt = Tokens.CHAR; + rule = first; + reader.reset(); + } + + return this.createToken(tt, rule, startLine, startCol); + }, + + /** + * Produces a character token based on the given character + * and location in the stream. If there's a special (non-standard) + * token name, this is used; otherwise CHAR is used. + * @param {String} c The character for the token. + * @param {int} startLine The beginning line for the character. + * @param {int} startCol The beginning column for the character. + * @return {Object} A token object. + * @method charToken + */ + charToken: function(c, startLine, startCol){ + var tt = Tokens.type(c); + + if (tt == -1){ + tt = Tokens.CHAR; + } + + return this.createToken(tt, c, startLine, startCol); + }, + + /** + * Produces a character token based on the given character + * and location in the stream. If there's a special (non-standard) + * token name, this is used; otherwise CHAR is used. + * @param {String} first The first character for the token. + * @param {int} startLine The beginning line for the character. + * @param {int} startCol The beginning column for the character. + * @return {Object} A token object. + * @method commentToken + */ + commentToken: function(first, startLine, startCol){ + var reader = this._reader, + comment = this.readComment(first); + + return this.createToken(Tokens.COMMENT, comment, startLine, startCol); + }, + + /** + * Produces a comparison token based on the given character + * and location in the stream. The next character must be + * read and is already known to be an equals sign. + * @param {String} c The character for the token. + * @param {int} startLine The beginning line for the character. + * @param {int} startCol The beginning column for the character. + * @return {Object} A token object. + * @method comparisonToken + */ + comparisonToken: function(c, startLine, startCol){ + var reader = this._reader, + comparison = c + reader.read(), + tt = Tokens.type(comparison) || Tokens.CHAR; + + return this.createToken(tt, comparison, startLine, startCol); + }, + + /** + * Produces a hash token based on the specified information. The + * first character provided is the pound sign (#) and then this + * method reads a name afterward. + * @param {String} first The first character (#) in the hash name. + * @param {int} startLine The beginning line for the character. + * @param {int} startCol The beginning column for the character. + * @return {Object} A token object. + * @method hashToken + */ + hashToken: function(first, startLine, startCol){ + var reader = this._reader, + name = this.readName(first); + + return this.createToken(Tokens.HASH, name, startLine, startCol); + }, + + /** + * Produces a CDO or CHAR token based on the specified information. The + * first character is provided and the rest is read by the function to determine + * the correct token to create. + * @param {String} first The first character in the token. + * @param {int} startLine The beginning line for the character. + * @param {int} startCol The beginning column for the character. + * @return {Object} A token object. + * @method htmlCommentStartToken + */ + htmlCommentStartToken: function(first, startLine, startCol){ + var reader = this._reader, + text = first; + + reader.mark(); + text += reader.readCount(3); + + if (text == ""){ + return this.createToken(Tokens.CDC, text, startLine, startCol); + } else { + reader.reset(); + return this.charToken(first, startLine, startCol); + } + }, + + /** + * Produces an IDENT or FUNCTION token based on the specified information. The + * first character is provided and the rest is read by the function to determine + * the correct token to create. + * @param {String} first The first character in the identifier. + * @param {int} startLine The beginning line for the character. + * @param {int} startCol The beginning column for the character. + * @return {Object} A token object. + * @method identOrFunctionToken + */ + identOrFunctionToken: function(first, startLine, startCol){ + var reader = this._reader, + ident = this.readName(first), + tt = Tokens.IDENT; + + //if there's a left paren immediately after, it's a URI or function + if (reader.peek() == "("){ + ident += reader.read(); + if (ident.toLowerCase() == "url("){ + tt = Tokens.URI; + ident = this.readURI(ident); + + //didn't find a valid URL or there's no closing paren + if (ident.toLowerCase() == "url("){ + tt = Tokens.FUNCTION; + } + } else { + tt = Tokens.FUNCTION; + } + } else if (reader.peek() == ":"){ //might be an IE function + + //IE-specific functions always being with progid: + if (ident.toLowerCase() == "progid"){ + ident += reader.readTo("("); + tt = Tokens.IE_FUNCTION; + } + } + + return this.createToken(tt, ident, startLine, startCol); + }, + + /** + * Produces an IMPORTANT_SYM or CHAR token based on the specified information. The + * first character is provided and the rest is read by the function to determine + * the correct token to create. + * @param {String} first The first character in the token. + * @param {int} startLine The beginning line for the character. + * @param {int} startCol The beginning column for the character. + * @return {Object} A token object. + * @method importantToken + */ + importantToken: function(first, startLine, startCol){ + var reader = this._reader, + important = first, + tt = Tokens.CHAR, + temp, + c; + + reader.mark(); + c = reader.read(); + + while(c){ + + //there can be a comment in here + if (c == "/"){ + + //if the next character isn't a star, then this isn't a valid !important token + if (reader.peek() != "*"){ + break; + } else { + temp = this.readComment(c); + if (temp == ""){ //broken! + break; + } + } + } else if (isWhitespace(c)){ + important += c + this.readWhitespace(); + } else if (/i/i.test(c)){ + temp = reader.readCount(8); + if (/mportant/i.test(temp)){ + important += c + temp; + tt = Tokens.IMPORTANT_SYM; + + } + break; //we're done + } else { + break; + } + + c = reader.read(); + } + + if (tt == Tokens.CHAR){ + reader.reset(); + return this.charToken(first, startLine, startCol); + } else { + return this.createToken(tt, important, startLine, startCol); + } + + + }, + + /** + * Produces a NOT or CHAR token based on the specified information. The + * first character is provided and the rest is read by the function to determine + * the correct token to create. + * @param {String} first The first character in the token. + * @param {int} startLine The beginning line for the character. + * @param {int} startCol The beginning column for the character. + * @return {Object} A token object. + * @method notToken + */ + notToken: function(first, startLine, startCol){ + var reader = this._reader, + text = first; + + reader.mark(); + text += reader.readCount(4); + + if (text.toLowerCase() == ":not("){ + return this.createToken(Tokens.NOT, text, startLine, startCol); + } else { + reader.reset(); + return this.charToken(first, startLine, startCol); + } + }, + + /** + * Produces a number token based on the given character + * and location in the stream. This may return a token of + * NUMBER, EMS, EXS, LENGTH, ANGLE, TIME, FREQ, DIMENSION, + * or PERCENTAGE. + * @param {String} first The first character for the token. + * @param {int} startLine The beginning line for the character. + * @param {int} startCol The beginning column for the character. + * @return {Object} A token object. + * @method numberToken + */ + numberToken: function(first, startLine, startCol){ + var reader = this._reader, + value = this.readNumber(first), + ident, + tt = Tokens.NUMBER, + c = reader.peek(); + + if (isIdentStart(c)){ + ident = this.readName(reader.read()); + value += ident; + + if (/^em$|^ex$|^px$|^gd$|^rem$|^vw$|^vh$|^vm$|^ch$|^cm$|^mm$|^in$|^pt$|^pc$/i.test(ident)){ + tt = Tokens.LENGTH; + } else if (/^deg|^rad$|^grad$/i.test(ident)){ + tt = Tokens.ANGLE; + } else if (/^ms$|^s$/i.test(ident)){ + tt = Tokens.TIME; + } else if (/^hz$|^khz$/i.test(ident)){ + tt = Tokens.FREQ; + } else if (/^dpi$|^dpcm$/i.test(ident)){ + tt = Tokens.RESOLUTION; + } else { + tt = Tokens.DIMENSION; + } + + } else if (c == "%"){ + value += reader.read(); + tt = Tokens.PERCENTAGE; + } + + return this.createToken(tt, value, startLine, startCol); + }, + + /** + * Produces a string token based on the given character + * and location in the stream. Since strings may be indicated + * by single or double quotes, a failure to match starting + * and ending quotes results in an INVALID token being generated. + * The first character in the string is passed in and then + * the rest are read up to and including the final quotation mark. + * @param {String} first The first character in the string. + * @param {int} startLine The beginning line for the character. + * @param {int} startCol The beginning column for the character. + * @return {Object} A token object. + * @method stringToken + */ + stringToken: function(first, startLine, startCol){ + var delim = first, + string = first, + reader = this._reader, + prev = first, + tt = Tokens.STRING, + c = reader.read(); + + while(c){ + string += c; + + //if the delimiter is found with an escapement, we're done. + if (c == delim && prev != "\\"){ + break; + } + + //if there's a newline without an escapement, it's an invalid string + if (isNewLine(reader.peek()) && c != "\\"){ + tt = Tokens.INVALID; + break; + } + + //save previous and get next + prev = c; + c = reader.read(); + } + + //if c is null, that means we're out of input and the string was never closed + if (c == null){ + tt = Tokens.INVALID; + } + + return this.createToken(tt, string, startLine, startCol); + }, + + unicodeRangeToken: function(first, startLine, startCol){ + var reader = this._reader, + value = first, + temp, + tt = Tokens.CHAR; + + //then it should be a unicode range + if (reader.peek() == "+"){ + reader.mark(); + value += reader.read(); + value += this.readUnicodeRangePart(true); + + //ensure there's an actual unicode range here + if (value.length == 2){ + reader.reset(); + } else { + + tt = Tokens.UNICODE_RANGE; + + //if there's a ? in the first part, there can't be a second part + if (value.indexOf("?") == -1){ + + if (reader.peek() == "-"){ + reader.mark(); + temp = reader.read(); + temp += this.readUnicodeRangePart(false); + + //if there's not another value, back up and just take the first + if (temp.length == 1){ + reader.reset(); + } else { + value += temp; + } + } + + } + } + } + + return this.createToken(tt, value, startLine, startCol); + }, + + /** + * Produces a S token based on the specified information. Since whitespace + * may have multiple characters, this consumes all whitespace characters + * into a single token. + * @param {String} first The first character in the token. + * @param {int} startLine The beginning line for the character. + * @param {int} startCol The beginning column for the character. + * @return {Object} A token object. + * @method whitespaceToken + */ + whitespaceToken: function(first, startLine, startCol){ + var reader = this._reader, + value = first + this.readWhitespace(); + return this.createToken(Tokens.S, value, startLine, startCol); + }, + + + + + //------------------------------------------------------------------------- + // Methods to read values from the string stream + //------------------------------------------------------------------------- + + readUnicodeRangePart: function(allowQuestionMark){ + var reader = this._reader, + part = "", + c = reader.peek(); + + //first read hex digits + while(isHexDigit(c) && part.length < 6){ + reader.read(); + part += c; + c = reader.peek(); + } + + //then read question marks if allowed + if (allowQuestionMark){ + while(c == "?" && part.length < 6){ + reader.read(); + part += c; + c = reader.peek(); + } + } + + //there can't be any other characters after this point + + return part; + }, + + readWhitespace: function(){ + var reader = this._reader, + whitespace = "", + c = reader.peek(); + + while(isWhitespace(c)){ + reader.read(); + whitespace += c; + c = reader.peek(); + } + + return whitespace; + }, + readNumber: function(first){ + var reader = this._reader, + number = first, + hasDot = (first == "."), + c = reader.peek(); + + + while(c){ + if (isDigit(c)){ + number += reader.read(); + } else if (c == "."){ + if (hasDot){ + break; + } else { + hasDot = true; + number += reader.read(); + } + } else { + break; + } + + c = reader.peek(); + } + + return number; + }, + readString: function(){ + var reader = this._reader, + delim = reader.read(), + string = delim, + prev = delim, + c = reader.peek(); + + while(c){ + c = reader.read(); + string += c; + + //if the delimiter is found with an escapement, we're done. + if (c == delim && prev != "\\"){ + break; + } + + //if there's a newline without an escapement, it's an invalid string + if (isNewLine(reader.peek()) && c != "\\"){ + string = ""; + break; + } + + //save previous and get next + prev = c; + c = reader.peek(); + } + + //if c is null, that means we're out of input and the string was never closed + if (c == null){ + string = ""; + } + + return string; + }, + readURI: function(first){ + var reader = this._reader, + uri = first, + inner = "", + c = reader.peek(); + + reader.mark(); + + //skip whitespace before + while(c && isWhitespace(c)){ + reader.read(); + c = reader.peek(); + } + + //it's a string + if (c == "'" || c == "\""){ + inner = this.readString(); + } else { + inner = this.readURL(); + } + + c = reader.peek(); + + //skip whitespace after + while(c && isWhitespace(c)){ + reader.read(); + c = reader.peek(); + } + + //if there was no inner value or the next character isn't closing paren, it's not a URI + if (inner == "" || c != ")"){ + uri = first; + reader.reset(); + } else { + uri += inner + reader.read(); + } + + return uri; + }, + readURL: function(){ + var reader = this._reader, + url = "", + c = reader.peek(); + + //TODO: Check for escape and nonascii + while (/^[!#$%&\\*-~]$/.test(c)){ + url += reader.read(); + c = reader.peek(); + } + + return url; + + }, + readName: function(first){ + var reader = this._reader, + ident = first || "", + c = reader.peek(); + + while(true){ + if (c == "\\"){ + ident += this.readEscape(reader.read()); + c = reader.peek(); + } else if(c && isNameChar(c)){ + ident += reader.read(); + c = reader.peek(); + } else { + break; + } + } + + return ident; + }, + + readEscape: function(first){ + var reader = this._reader, + cssEscape = first || "", + i = 0, + c = reader.peek(); + + if (isHexDigit(c)){ + do { + cssEscape += reader.read(); + c = reader.peek(); + } while(c && isHexDigit(c) && ++i < 6); + } + + if (cssEscape.length == 3 && /\s/.test(c) || + cssEscape.length == 7 || cssEscape.length == 1){ + reader.read(); + } else { + c = ""; + } + + return cssEscape + c; + }, + + readComment: function(first){ + var reader = this._reader, + comment = first || "", + c = reader.read(); + + if (c == "*"){ + while(c){ + comment += c; + + //look for end of comment + if (c == "*" && reader.peek() == "/"){ + comment += reader.read(); + break; + } + + c = reader.read(); + } + + return comment; + } else { + return ""; + } + + } +}); + +var Tokens = [ + + /* + * The following token names are defined in CSS3 Grammar: http://www.w3.org/TR/css3-syntax/#lexical + */ + + //HTML-style comments + { name: "CDO"}, + { name: "CDC"}, + + //ignorables + { name: "S", whitespace: true/*, channel: "ws"*/}, + { name: "COMMENT", comment: true, hide: true, channel: "comment" }, + + //attribute equality + { name: "INCLUDES", text: "~="}, + { name: "DASHMATCH", text: "|="}, + { name: "PREFIXMATCH", text: "^="}, + { name: "SUFFIXMATCH", text: "$="}, + { name: "SUBSTRINGMATCH", text: "*="}, + + //identifier types + { name: "STRING"}, + { name: "IDENT"}, + { name: "HASH"}, + + //at-keywords + { name: "IMPORT_SYM", text: "@import"}, + { name: "PAGE_SYM", text: "@page"}, + { name: "MEDIA_SYM", text: "@media"}, + { name: "FONT_FACE_SYM", text: "@font-face"}, + { name: "CHARSET_SYM", text: "@charset"}, + { name: "NAMESPACE_SYM", text: "@namespace"}, + //{ name: "ATKEYWORD"}, + + //CSS3 animations + { name: "KEYFRAMES_SYM", text: [ "@keyframes", "@-webkit-keyframes", "@-moz-keyframes" ] }, + + //important symbol + { name: "IMPORTANT_SYM"}, + + //measurements + { name: "LENGTH"}, + { name: "ANGLE"}, + { name: "TIME"}, + { name: "FREQ"}, + { name: "DIMENSION"}, + { name: "PERCENTAGE"}, + { name: "NUMBER"}, + + //functions + { name: "URI"}, + { name: "FUNCTION"}, + + //Unicode ranges + { name: "UNICODE_RANGE"}, + + /* + * The following token names are defined in CSS3 Selectors: http://www.w3.org/TR/css3-selectors/#selector-syntax + */ + + //invalid string + { name: "INVALID"}, + + //combinators + { name: "PLUS", text: "+" }, + { name: "GREATER", text: ">"}, + { name: "COMMA", text: ","}, + { name: "TILDE", text: "~"}, + + //modifier + { name: "NOT"}, + + /* + * Defined in CSS3 Paged Media + */ + { name: "TOPLEFTCORNER_SYM", text: "@top-left-corner"}, + { name: "TOPLEFT_SYM", text: "@top-left"}, + { name: "TOPCENTER_SYM", text: "@top-center"}, + { name: "TOPRIGHT_SYM", text: "@top-right"}, + { name: "TOPRIGHTCORNER_SYM", text: "@top-right-corner"}, + { name: "BOTTOMLEFTCORNER_SYM", text: "@bottom-left-corner"}, + { name: "BOTTOMLEFT_SYM", text: "@bottom-left"}, + { name: "BOTTOMCENTER_SYM", text: "@bottom-center"}, + { name: "BOTTOMRIGHT_SYM", text: "@bottom-right"}, + { name: "BOTTOMRIGHTCORNER_SYM", text: "@bottom-right-corner"}, + { name: "LEFTTOP_SYM", text: "@left-top"}, + { name: "LEFTMIDDLE_SYM", text: "@left-middle"}, + { name: "LEFTBOTTOM_SYM", text: "@left-bottom"}, + { name: "RIGHTTOP_SYM", text: "@right-top"}, + { name: "RIGHTMIDDLE_SYM", text: "@right-middle"}, + { name: "RIGHTBOTTOM_SYM", text: "@right-bottom"}, + + /* + * The following token names are defined in CSS3 Media Queries: http://www.w3.org/TR/css3-mediaqueries/#syntax + */ + /*{ name: "MEDIA_ONLY", state: "media"}, + { name: "MEDIA_NOT", state: "media"}, + { name: "MEDIA_AND", state: "media"},*/ + { name: "RESOLUTION", state: "media"}, + + /* + * The following token names are not defined in any CSS specification but are used by the lexer. + */ + + //not a real token, but useful for stupid IE filters + { name: "IE_FUNCTION" }, + + //part of CSS3 grammar but not the Flex code + { name: "CHAR" }, + + //TODO: Needed? + //Not defined as tokens, but might as well be + { + name: "PIPE", + text: "|" + }, + { + name: "SLASH", + text: "/" + }, + { + name: "MINUS", + text: "-" + }, + { + name: "STAR", + text: "*" + }, + + { + name: "LBRACE", + text: "{" + }, + { + name: "RBRACE", + text: "}" + }, + { + name: "LBRACKET", + text: "[" + }, + { + name: "RBRACKET", + text: "]" + }, + { + name: "EQUALS", + text: "=" + }, + { + name: "COLON", + text: ":" + }, + { + name: "SEMICOLON", + text: ";" + }, + + { + name: "LPAREN", + text: "(" + }, + { + name: "RPAREN", + text: ")" + }, + { + name: "DOT", + text: "." + } +]; + +(function(){ + + var nameMap = [], + typeMap = {}; + + Tokens.UNKNOWN = -1; + Tokens.unshift({name:"EOF"}); + for (var i=0, len = Tokens.length; i < len; i++){ + nameMap.push(Tokens[i].name); + Tokens[Tokens[i].name] = i; + if (Tokens[i].text){ + if (Tokens[i].text instanceof Array){ + for (var j=0; j < Tokens[i].text.length; j++){ + typeMap[Tokens[i].text[j]] = i; + } + } else { + typeMap[Tokens[i].text] = i; + } + } + } + + Tokens.name = function(tt){ + return nameMap[tt]; + }; + + Tokens.type = function(c){ + return typeMap[c] || -1; + }; + +})(); + + + +/** + * Type to use when a validation error occurs. + * @class ValidationError + * @namespace parserlib.util + * @constructor + * @param {String} message The error message. + * @param {int} line The line at which the error occurred. + * @param {int} col The column at which the error occurred. + */ +function ValidationError(message, line, col){ + + /** + * The column at which the error occurred. + * @type int + * @property col + */ + this.col = col; + + /** + * The line at which the error occurred. + * @type int + * @property line + */ + this.line = line; + + /** + * The text representation of the unit. + * @type String + * @property text + */ + this.message = message; + +} + +//inherit from Error +ValidationError.prototype = new Error(); + +parserlib.css = { +Colors :Colors, +Combinator :Combinator, +Parser :Parser, +PropertyName :PropertyName, +PropertyValue :PropertyValue, +PropertyValuePart :PropertyValuePart, +MediaFeature :MediaFeature, +MediaQuery :MediaQuery, +Selector :Selector, +SelectorPart :SelectorPart, +SelectorSubPart :SelectorSubPart, +TokenStream :TokenStream, +Tokens :Tokens, +ValidationError :ValidationError +}; +})(); + +/** + * Main CSSLint object. + * @class CSSLint + * @static + * @extends parserlib.util.EventTarget + */ +var CSSLint = (function(){ + + var rules = [], + formatters = [], + api = new parserlib.util.EventTarget(); + + api.version = "0.6.1"; + + //------------------------------------------------------------------------- + // Rule Management + //------------------------------------------------------------------------- + + /** + * Adds a new rule to the engine. + * @param {Object} rule The rule to add. + * @method addRule + */ + api.addRule = function(rule){ + rules.push(rule); + rules[rule.id] = rule; + }; + + /** + * Clears all rule from the engine. + * @method clearRules + */ + api.clearRules = function(){ + rules = []; + }; + + /** + * Returns the rule objects. + * @return An array of rule objects. + * @method getRules + */ + api.getRules = function(){ + return [].concat(rules).sort(function(a,b){ + return a.id > b.id ? 1 : 0; + }); + }; + + //------------------------------------------------------------------------- + // Formatters + //------------------------------------------------------------------------- + + /** + * Adds a new formatter to the engine. + * @param {Object} formatter The formatter to add. + * @method addFormatter + */ + api.addFormatter = function(formatter) { + // formatters.push(formatter); + formatters[formatter.id] = formatter; + }; + + /** + * Retrieves a formatter for use. + * @param {String} formatId The name of the format to retrieve. + * @return {Object} The formatter or undefined. + * @method getFormatter + */ + api.getFormatter = function(formatId){ + return formatters[formatId]; + }; + + /** + * Formats the results in a particular format for a single file. + * @param {Object} result The results returned from CSSLint.verify(). + * @param {String} filename The filename for which the results apply. + * @param {String} formatId The name of the formatter to use. + * @return {String} A formatted string for the results. + * @method format + */ + api.format = function(results, filename, formatId) { + var formatter = this.getFormatter(formatId), + result = null; + + if (formatter){ + result = formatter.startFormat(); + result += formatter.formatResults(results, filename); + result += formatter.endFormat(); + } + + return result; + } + + /** + * Indicates if the given format is supported. + * @param {String} formatId The ID of the format to check. + * @return {Boolean} True if the format exists, false if not. + * @method hasFormat + */ + api.hasFormat = function(formatId){ + return formatters.hasOwnProperty(formatId); + }; + + //------------------------------------------------------------------------- + // Verification + //------------------------------------------------------------------------- + + /** + * Starts the verification process for the given CSS text. + * @param {String} text The CSS text to verify. + * @param {Object} ruleset (Optional) List of rules to apply. If null, then + * all rules are used. + * @return {Object} Results of the verification. + * @method verify + */ + api.verify = function(text, ruleset){ + + var i = 0, + len = rules.length, + reporter, + lines, + report, + parser = new parserlib.css.Parser({ starHack: true, ieFilters: true, + underscoreHack: true, strict: false }); + + lines = text.split(/\n\r?/g); + reporter = new Reporter(lines); + + if (!ruleset){ + while (i < len){ + rules[i++].init(parser, reporter); + } + } else { + ruleset.errors = 1; //always report parsing errors + for (i in ruleset){ + if(ruleset.hasOwnProperty(i)){ + if (rules[i]){ + rules[i].init(parser, reporter); + } + } + } + } + + //capture most horrible error type + try { + parser.parse(text); + } catch (ex) { + reporter.error("Fatal error, cannot continue: " + ex.message, ex.line, ex.col); + } + + report = { + messages : reporter.messages, + stats : reporter.stats + }; + + //sort by line numbers, rollups at the bottom + report.messages.sort(function (a, b){ + if (a.rollup && !b.rollup){ + return 1; + } else if (!a.rollup && b.rollup){ + return -1; + } else { + return a.line - b.line; + } + }); + + return report; + }; + + //------------------------------------------------------------------------- + // Publish the API + //------------------------------------------------------------------------- + + return api; + +})(); + +/** + * An instance of Report is used to report results of the + * verification back to the main API. + * @class Reporter + * @constructor + * @param {String[]} lines The text lines of the source. + */ +function Reporter(lines){ + + /** + * List of messages being reported. + * @property messages + * @type String[] + */ + this.messages = []; + + /** + * List of statistics being reported. + * @property stats + * @type String[] + */ + this.stats = []; + + /** + * Lines of code being reported on. Used to provide contextual information + * for messages. + * @property lines + * @type String[] + */ + this.lines = lines; +} + +Reporter.prototype = { + + //restore constructor + constructor: Reporter, + + /** + * Report an error. + * @param {String} message The message to store. + * @param {int} line The line number. + * @param {int} col The column number. + * @param {Object} rule The rule this message relates to. + * @method error + */ + error: function(message, line, col, rule){ + this.messages.push({ + type : "error", + line : line, + col : col, + message : message, + evidence: this.lines[line-1], + rule : rule + }); + }, + + /** + * Report an warning. + * @param {String} message The message to store. + * @param {int} line The line number. + * @param {int} col The column number. + * @param {Object} rule The rule this message relates to. + * @method warn + */ + warn: function(message, line, col, rule){ + this.messages.push({ + type : "warning", + line : line, + col : col, + message : message, + evidence: this.lines[line-1], + rule : rule + }); + }, + + /** + * Report some informational text. + * @param {String} message The message to store. + * @param {int} line The line number. + * @param {int} col The column number. + * @param {Object} rule The rule this message relates to. + * @method info + */ + info: function(message, line, col, rule){ + this.messages.push({ + type : "info", + line : line, + col : col, + message : message, + evidence: this.lines[line-1], + rule : rule + }); + }, + + /** + * Report some rollup error information. + * @param {String} message The message to store. + * @param {Object} rule The rule this message relates to. + * @method rollupError + */ + rollupError: function(message, rule){ + this.messages.push({ + type : "error", + rollup : true, + message : message, + rule : rule + }); + }, + + /** + * Report some rollup warning information. + * @param {String} message The message to store. + * @param {Object} rule The rule this message relates to. + * @method rollupWarn + */ + rollupWarn: function(message, rule){ + this.messages.push({ + type : "warning", + rollup : true, + message : message, + rule : rule + }); + }, + + /** + * Report a statistic. + * @param {String} name The name of the stat to store. + * @param {Variant} value The value of the stat. + * @method stat + */ + stat: function(name, value){ + this.stats[name] = value; + } +}; + +/* + * Utility functions that make life easier. + */ + +/* + * Adds all properties from supplier onto receiver, + * overwriting if the same name already exists on + * reciever. + * @param {Object} The object to receive the properties. + * @param {Object} The object to provide the properties. + * @return {Object} The receiver + */ +function mix(reciever, supplier){ + var prop; + + for (prop in supplier){ + if (supplier.hasOwnProperty(prop)){ + receiver[prop] = supplier[prop]; + } + } + + return prop; +} + +/* + * Polyfill for array indexOf() method. + * @param {Array} values The array to search. + * @param {Variant} value The value to search for. + * @return {int} The index of the value if found, -1 if not. + */ +function indexOf(values, value){ + if (values.indexOf){ + return values.indexOf(value); + } else { + for (var i=0, len=values.length; i < len; i++){ + if (values[i] === value){ + return i; + } + } + return -1; + } +} +/* + * Rule: Don't use adjoining classes (.foo.bar). + */ +CSSLint.addRule({ + + //rule information + id: "adjoining-classes", + name: "Adjoining Classes", + desc: "Don't use adjoining classes.", + browsers: "IE6", + + //initialization + init: function(parser, reporter){ + var rule = this; + parser.addListener("startrule", function(event){ + var selectors = event.selectors, + selector, + part, + modifier, + classCount, + i, j, k; + + for (i=0; i < selectors.length; i++){ + selector = selectors[i]; + for (j=0; j < selector.parts.length; j++){ + part = selector.parts[j]; + if (part instanceof parserlib.css.SelectorPart){ + classCount = 0; + for (k=0; k < part.modifiers.length; k++){ + modifier = part.modifiers[k]; + if (modifier.type == "class"){ + classCount++; + } + if (classCount > 1){ + reporter.warn("Don't use adjoining classes.", part.line, part.col, rule); + } + } + } + } + } + }); + } + +}); +/* + * Rule: Don't use width or height when using padding or border. + */ +CSSLint.addRule({ + + //rule information + id: "box-model", + name: "Box Model", + desc: "Don't use width or height when using padding or border.", + browsers: "All", + + //initialization + init: function(parser, reporter){ + var rule = this, + widthProperties = { + border: 1, + "border-left": 1, + "border-right": 1, + padding: 1, + "padding-left": 1, + "padding-right": 1 + }, + heightProperties = { + border: 1, + "border-bottom": 1, + "border-top": 1, + padding: 1, + "padding-bottom": 1, + "padding-top": 1 + }, + properties; + + parser.addListener("startrule", function(){ + properties = { + }; + }); + + parser.addListener("property", function(event){ + var name = event.property.text.toLowerCase(); + + if (heightProperties[name] || widthProperties[name]){ + if (!/^0\S*$/.test(event.value) && !(name == "border" && event.value == "none")){ + properties[name] = { line: event.property.line, col: event.property.col, value: event.value }; + } + } else { + if (name == "width" || name == "height"){ + properties[name] = 1; + } + } + + }); + + parser.addListener("endrule", function(){ + var prop; + if (properties["height"]){ + for (prop in heightProperties){ + if (heightProperties.hasOwnProperty(prop) && properties[prop]){ + + //special case for padding + if (prop == "padding" && properties[prop].value.parts.length == 2 && properties[prop].value.parts[0].value == 0){ + //noop + } else { + reporter.warn("Broken box model: using height with " + prop + ".", properties[prop].line, properties[prop].col, rule); + } + } + } + } + + if (properties["width"]){ + for (prop in widthProperties){ + if (widthProperties.hasOwnProperty(prop) && properties[prop]){ + + if (prop == "padding" && properties[prop].value.parts.length == 2 && properties[prop].value.parts[1].value == 0){ + //noop + } else { + reporter.warn("Broken box model: using width with " + prop + ".", properties[prop].line, properties[prop].col, rule); + } + } + } + } + + }); + } + +}); +/* + * Rule: Include all compatible vendor prefixes to reach a wider + * range of users. + */ +/*global CSSLint*/ +CSSLint.addRule({ + + //rule information + id: "compatible-vendor-prefixes", + name: "Compatible Vendor Prefixes", + desc: "Include all compatible vendor prefixes to reach a wider range of users.", + browsers: "All", + + //initialization + init: function (parser, reporter) { + var rule = this, + compatiblePrefixes, + properties, + prop, + variations, + prefixed, + i, + len, + arrayPush = Array.prototype.push, + applyTo = []; + + // See http://peter.sh/experiments/vendor-prefixed-css-property-overview/ for details + compatiblePrefixes = { + "animation" : "webkit moz", + "animation-delay" : "webkit moz", + "animation-direction" : "webkit moz", + "animation-duration" : "webkit moz", + "animation-fill-mode" : "webkit moz", + "animation-iteration-count" : "webkit moz", + "animation-name" : "webkit moz", + "animation-play-state" : "webkit moz", + "animation-timing-function" : "webkit moz", + "appearance" : "webkit moz", + "border-end" : "webkit moz", + "border-end-color" : "webkit moz", + "border-end-style" : "webkit moz", + "border-end-width" : "webkit moz", + "border-image" : "webkit moz o", + "border-radius" : "webkit moz", + "border-start" : "webkit moz", + "border-start-color" : "webkit moz", + "border-start-style" : "webkit moz", + "border-start-width" : "webkit moz", + "box-align" : "webkit moz ms", + "box-direction" : "webkit moz ms", + "box-flex" : "webkit moz ms", + "box-lines" : "webkit ms", + "box-ordinal-group" : "webkit moz ms", + "box-orient" : "webkit moz ms", + "box-pack" : "webkit moz ms", + "box-sizing" : "webkit moz", + "box-shadow" : "webkit moz", + "column-count" : "webkit moz", + "column-gap" : "webkit moz", + "column-rule" : "webkit moz", + "column-rule-color" : "webkit moz", + "column-rule-style" : "webkit moz", + "column-rule-width" : "webkit moz", + "column-width" : "webkit moz", + "hyphens" : "epub moz", + "line-break" : "webkit ms", + "margin-end" : "webkit moz", + "margin-start" : "webkit moz", + "marquee-speed" : "webkit wap", + "marquee-style" : "webkit wap", + "padding-end" : "webkit moz", + "padding-start" : "webkit moz", + "tab-size" : "moz o", + "text-size-adjust" : "webkit ms", + "transform" : "webkit moz ms o", + "transform-origin" : "webkit moz ms o", + "transition" : "webkit moz o", + "transition-delay" : "webkit moz o", + "transition-duration" : "webkit moz o", + "transition-property" : "webkit moz o", + "transition-timing-function" : "webkit moz o", + "user-modify" : "webkit moz", + "user-select" : "webkit moz", + "word-break" : "epub ms", + "writing-mode" : "epub ms" + }; + + for (prop in compatiblePrefixes) { + if (compatiblePrefixes.hasOwnProperty(prop)) { + variations = []; + prefixed = compatiblePrefixes[prop].split(' '); + for (i = 0, len = prefixed.length; i < len; i++) { + variations.push('-' + prefixed[i] + '-' + prop); + } + compatiblePrefixes[prop] = variations; + arrayPush.apply(applyTo, variations); + } + } + parser.addListener("startrule", function () { + properties = []; + }); + + parser.addListener("property", function (event) { + var name = event.property.text; + if (applyTo.indexOf(name) > -1) { + properties.push(name); + } + }); + + parser.addListener("endrule", function (event) { + if (!properties.length) { + return; + } + + var propertyGroups = {}, + i, + len, + name, + prop, + variations, + value, + full, + actual, + item, + propertiesSpecified; + + for (i = 0, len = properties.length; i < len; i++) { + name = properties[i]; + + for (prop in compatiblePrefixes) { + if (compatiblePrefixes.hasOwnProperty(prop)) { + variations = compatiblePrefixes[prop]; + if (variations.indexOf(name) > -1) { + if (propertyGroups[prop] === undefined) { + propertyGroups[prop] = { + full : variations.slice(0), + actual : [] + }; + } + if (propertyGroups[prop].actual.indexOf(name) === -1) { + propertyGroups[prop].actual.push(name); + } + } + } + } + } + + for (prop in propertyGroups) { + if (propertyGroups.hasOwnProperty(prop)) { + value = propertyGroups[prop]; + full = value.full; + actual = value.actual; + + if (full.length > actual.length) { + for (i = 0, len = full.length; i < len; i++) { + item = full[i]; + if (actual.indexOf(item) === -1) { + propertiesSpecified = (actual.length === 1) ? actual[0] : (actual.length == 2) ? actual.join(" and ") : actual.join(", "); + reporter.warn("The property " + item + " is compatible with " + propertiesSpecified + " and should be included as well.", event.selectors[0].line, event.selectors[0].col, rule); + } + } + + } + } + } + }); + } +}); +/* + * Rule: Certain properties don't play well with certain display values. + * - float should not be used with inline-block + * - height, width, margin-top, margin-bottom, float should not be used with inline + * - vertical-align should not be used with block + * - margin, float should not be used with table-* + */ +CSSLint.addRule({ + + //rule information + id: "display-property-grouping", + name: "Display Property Grouping", + desc: "Certain properties shouldn't be used with certain display property values.", + browsers: "All", + + //initialization + init: function(parser, reporter){ + var rule = this; + + var propertiesToCheck = { + display: 1, + "float": "none", + height: 1, + width: 1, + margin: 1, + "margin-left": 1, + "margin-right": 1, + "margin-bottom": 1, + "margin-top": 1, + padding: 1, + "padding-left": 1, + "padding-right": 1, + "padding-bottom": 1, + "padding-top": 1, + "vertical-align": 1 + }, + properties; + + parser.addListener("startrule", function(){ + properties = {}; + }); + + parser.addListener("property", function(event){ + var name = event.property.text.toLowerCase(); + + if (propertiesToCheck[name]){ + properties[name] = { value: event.value.text, line: event.property.line, col: event.property.col }; + } + }); + + parser.addListener("endrule", function(){ + + var display = properties.display ? properties.display.value : null; + if (display){ + switch(display){ + + case "inline": + //height, width, margin-top, margin-bottom, float should not be used with inline + reportProperty("height", display); + reportProperty("width", display); + reportProperty("margin", display); + reportProperty("margin-top", display); + reportProperty("margin-bottom", display); + reportProperty("float", display, "display:inline has no effect on floated elements (but may be used to fix the IE6 double-margin bug)."); + break; + + case "block": + //vertical-align should not be used with block + reportProperty("vertical-align", display); + break; + + case "inline-block": + //float should not be used with inline-block + reportProperty("float", display); + break; + + default: + //margin, float should not be used with table + if (display.indexOf("table-") == 0){ + reportProperty("margin", display); + reportProperty("margin-left", display); + reportProperty("margin-right", display); + reportProperty("margin-top", display); + reportProperty("margin-bottom", display); + reportProperty("float", display); + } + + //otherwise do nothing + } + } + + }); + + + function reportProperty(name, display, msg){ + if (properties[name]){ + if (!(typeof propertiesToCheck[name] == "string") || properties[name].value.toLowerCase() != propertiesToCheck[name]){ + reporter.warn(msg || name + " can't be used with display: " + display + ".", properties[name].line, properties[name].col, rule); + } + } + } + } + +}); +/* + * Rule: Duplicate properties must appear one after the other. If an already-defined + * property appears somewhere else in the rule, then it's likely an error. + */ +CSSLint.addRule({ + + //rule information + id: "duplicate-properties", + name: "Duplicate Properties", + desc: "Duplicate properties must appear one after the other.", + browsers: "All", + + //initialization + init: function(parser, reporter){ + var rule = this, + properties, + lastProperty; + + function startRule(event){ + properties = {}; + } + + parser.addListener("startrule", startRule); + parser.addListener("startfontface", startRule); + parser.addListener("startpage", startRule); + + parser.addListener("property", function(event){ + var property = event.property, + name = property.text.toLowerCase(); + + if (properties[name] && (lastProperty != name || properties[name] == event.value.text)){ + reporter.warn("Duplicate property '" + event.property + "' found.", event.line, event.col, rule); + } + + properties[name] = event.value.text; + lastProperty = name; + + }); + + + } + +}); +/* + * Rule: Style rules without any properties defined should be removed. + */ +CSSLint.addRule({ + + //rule information + id: "empty-rules", + name: "Empty Rules", + desc: "Rules without any properties specified should be removed.", + browsers: "All", + + //initialization + init: function(parser, reporter){ + var rule = this, + count = 0; + + parser.addListener("startrule", function(){ + count=0; + }); + + parser.addListener("property", function(){ + count++; + }); + + parser.addListener("endrule", function(event){ + var selectors = event.selectors; + if (count == 0){ + reporter.warn("Rule is empty.", selectors[0].line, selectors[0].col, rule); + } + }); + } + +}); +/* + * Rule: There should be no syntax errors. (Duh.) + */ +CSSLint.addRule({ + + //rule information + id: "errors", + name: "Parsing Errors", + desc: "This rule looks for recoverable syntax errors.", + browsers: "All", + + //initialization + init: function(parser, reporter){ + var rule = this; + + parser.addListener("error", function(event){ + reporter.error(event.message, event.line, event.col, rule); + }); + + } + +}); +/* + * Rule: You shouldn't use more than 10 floats. If you do, there's probably + * room for some abstraction. + */ +CSSLint.addRule({ + + //rule information + id: "floats", + name: "Floats", + desc: "This rule tests if the float property is used too many times", + browsers: "All", + + //initialization + init: function(parser, reporter){ + var rule = this; + var count = 0; + + //count how many times "float" is used + parser.addListener("property", function(event){ + if (event.property.text.toLowerCase() == "float" && + event.value.text.toLowerCase() != "none"){ + count++; + } + }); + + //report the results + parser.addListener("endstylesheet", function(){ + reporter.stat("floats", count); + if (count >= 10){ + reporter.rollupWarn("Too many floats (" + count + "), you're probably using them for layout. Consider using a grid system instead.", rule); + } + }); + } + +}); +/* + * Rule: Avoid too many @font-face declarations in the same stylesheet. + */ +CSSLint.addRule({ + + //rule information + id: "font-faces", + name: "Font Faces", + desc: "Too many different web fonts in the same stylesheet.", + browsers: "All", + + //initialization + init: function(parser, reporter){ + var rule = this, + count = 0; + + + parser.addListener("startfontface", function(){ + count++; + }); + + parser.addListener("endstylesheet", function(){ + if (count > 5){ + reporter.rollupWarn("Too many @font-face declarations (" + count + ").", rule); + } + }); + } + +}); +/* + * Rule: You shouldn't need more than 9 font-size declarations. + */ + +CSSLint.addRule({ + + //rule information + id: "font-sizes", + name: "Font Sizes", + desc: "Checks the number of font-size declarations.", + browsers: "All", + + //initialization + init: function(parser, reporter){ + var rule = this, + count = 0; + + //check for use of "font-size" + parser.addListener("property", function(event){ + if (event.property == "font-size"){ + count++; + } + }); + + //report the results + parser.addListener("endstylesheet", function(){ + reporter.stat("font-sizes", count); + if (count >= 10){ + reporter.rollupWarn("Too many font-size declarations (" + count + "), abstraction needed.", rule); + } + }); + } + +}); +/* + * Rule: When using a vendor-prefixed gradient, make sure to use them all. + */ +CSSLint.addRule({ + + //rule information + id: "gradients", + name: "Gradients", + desc: "When using a vendor-prefixed gradient, make sure to use them all.", + browsers: "All", + + //initialization + init: function(parser, reporter){ + var rule = this, + gradients; + + parser.addListener("startrule", function(){ + gradients = { + moz: 0, + webkit: 0, + ms: 0, + o: 0 + }; + }); + + parser.addListener("property", function(event){ + + if (/\-(moz|ms|o|webkit)(?:\-(?:linear|radial))\-gradient/.test(event.value)){ + gradients[RegExp.$1] = 1; + } + + }); + + parser.addListener("endrule", function(event){ + var missing = []; + + if (!gradients.moz){ + missing.push("Firefox 3.6+"); + } + + if (!gradients.webkit){ + missing.push("Webkit (Safari, Chrome)"); + } + + if (!gradients.ms){ + missing.push("Internet Explorer 10+"); + } + + if (!gradients.o){ + missing.push("Opera 11.1+"); + } + + if (missing.length && missing.length < 4){ + reporter.warn("Missing vendor-prefixed CSS gradients for " + missing.join(", ") + ".", event.selectors[0].line, event.selectors[0].col, rule); + } + + }); + + } + +}); +/* + * Rule: Don't use IDs for selectors. + */ +CSSLint.addRule({ + + //rule information + id: "ids", + name: "IDs", + desc: "Selectors should not contain IDs.", + browsers: "All", + + //initialization + init: function(parser, reporter){ + var rule = this; + parser.addListener("startrule", function(event){ + var selectors = event.selectors, + selector, + part, + modifier, + idCount, + i, j, k; + + for (i=0; i < selectors.length; i++){ + selector = selectors[i]; + idCount = 0; + + for (j=0; j < selector.parts.length; j++){ + part = selector.parts[j]; + if (part instanceof parserlib.css.SelectorPart){ + for (k=0; k < part.modifiers.length; k++){ + modifier = part.modifiers[k]; + if (modifier.type == "id"){ + idCount++; + } + } + } + } + + if (idCount == 1){ + reporter.warn("Don't use IDs in selectors.", selector.line, selector.col, rule); + } else if (idCount > 1){ + reporter.warn(idCount + " IDs in the selector, really?", selector.line, selector.col, rule); + } + } + + }); + } + +}); +/* + * Rule: Don't use @import, use instead. + */ +CSSLint.addRule({ + + //rule information + id: "import", + name: "@import", + desc: "Don't use @import, use instead.", + browsers: "All", + + //initialization + init: function(parser, reporter){ + var rule = this; + + parser.addListener("import", function(event){ + reporter.warn("@import prevents parallel downloads, use instead.", event.line, event.col, rule); + }); + + } + +}); +/* + * Rule: Make sure !important is not overused, this could lead to specificity + * war. Display a warning on !important declarations, an error if it's + * used more at least 10 times. + */ +CSSLint.addRule({ + + //rule information + id: "important", + name: "Important", + desc: "Be careful when using !important declaration", + browsers: "All", + + //initialization + init: function(parser, reporter){ + var rule = this, + count = 0; + + //warn that important is used and increment the declaration counter + parser.addListener("property", function(event){ + if (event.important === true){ + count++; + reporter.warn("Use of !important", event.line, event.col, rule); + } + }); + + //if there are more than 10, show an error + parser.addListener("endstylesheet", function(){ + reporter.stat("important", count); + if (count >= 10){ + reporter.rollupWarn("Too many !important declarations (" + count + "), try to use less than 10 to avoid specifity issues.", rule); + } + }); + } + +}); +/* + * Rule: Properties should be known (listed in CSS3 specification) or + * be a vendor-prefixed property. + */ +CSSLint.addRule({ + + //rule information + id: "known-properties", + name: "Known Properties", + desc: "Properties should be known (listed in CSS specification) or be a vendor-prefixed property.", + browsers: "All", + + //initialization + init: function(parser, reporter){ + var rule = this, + properties = { + + "alignment-adjust": 1, + "alignment-baseline": 1, + "animation": 1, + "animation-delay": 1, + "animation-direction": 1, + "animation-duration": 1, + "animation-fill-mode": 1, + "animation-iteration-count": 1, + "animation-name": 1, + "animation-play-state": 1, + "animation-timing-function": 1, + "appearance": 1, + "azimuth": 1, + "backface-visibility": 1, + "background": 1, + "background-attachment": 1, + "background-break": 1, + "background-clip": 1, + "background-color": 1, + "background-image": 1, + "background-origin": 1, + "background-position": 1, + "background-repeat": 1, + "background-size": 1, + "baseline-shift": 1, + "binding": 1, + "bleed": 1, + "bookmark-label": 1, + "bookmark-level": 1, + "bookmark-state": 1, + "bookmark-target": 1, + "border": 1, + "border-bottom": 1, + "border-bottom-color": 1, + "border-bottom-left-radius": 1, + "border-bottom-right-radius": 1, + "border-bottom-style": 1, + "border-bottom-width": 1, + "border-collapse": 1, + "border-color": 1, + "border-image": 1, + "border-image-outset": 1, + "border-image-repeat": 1, + "border-image-slice": 1, + "border-image-source": 1, + "border-image-width": 1, + "border-left": 1, + "border-left-color": 1, + "border-left-style": 1, + "border-left-width": 1, + "border-radius": 1, + "border-right": 1, + "border-right-color": 1, + "border-right-style": 1, + "border-right-width": 1, + "border-spacing": 1, + "border-style": 1, + "border-top": 1, + "border-top-color": 1, + "border-top-left-radius": 1, + "border-top-right-radius": 1, + "border-top-style": 1, + "border-top-width": 1, + "border-width": 1, + "bottom": 1, + "box-align": 1, + "box-decoration-break": 1, + "box-direction": 1, + "box-flex": 1, + "box-flex-group": 1, + "box-lines": 1, + "box-ordinal-group": 1, + "box-orient": 1, + "box-pack": 1, + "box-shadow": 1, + "box-sizing": 1, + "break-after": 1, + "break-before": 1, + "break-inside": 1, + "caption-side": 1, + "clear": 1, + "clip": 1, + "color": 1, + "color-profile": 1, + "column-count": 1, + "column-fill": 1, + "column-gap": 1, + "column-rule": 1, + "column-rule-color": 1, + "column-rule-style": 1, + "column-rule-width": 1, + "column-span": 1, + "column-width": 1, + "columns": 1, + "content": 1, + "counter-increment": 1, + "counter-reset": 1, + "crop": 1, + "cue": 1, + "cue-after": 1, + "cue-before": 1, + "cursor": 1, + "direction": 1, + "display": 1, + "dominant-baseline": 1, + "drop-initial-after-adjust": 1, + "drop-initial-after-align": 1, + "drop-initial-before-adjust": 1, + "drop-initial-before-align": 1, + "drop-initial-size": 1, + "drop-initial-value": 1, + "elevation": 1, + "empty-cells": 1, + "fit": 1, + "fit-position": 1, + "float": 1, + "float-offset": 1, + "font": 1, + "font-family": 1, + "font-size": 1, + "font-size-adjust": 1, + "font-stretch": 1, + "font-style": 1, + "font-variant": 1, + "font-weight": 1, + "grid-columns": 1, + "grid-rows": 1, + "hanging-punctuation": 1, + "height": 1, + "hyphenate-after": 1, + "hyphenate-before": 1, + "hyphenate-character": 1, + "hyphenate-lines": 1, + "hyphenate-resource": 1, + "hyphens": 1, + "icon": 1, + "image-orientation": 1, + "image-rendering": 1, + "image-resolution": 1, + "inline-box-align": 1, + "left": 1, + "letter-spacing": 1, + "line-height": 1, + "line-stacking": 1, + "line-stacking-ruby": 1, + "line-stacking-shift": 1, + "line-stacking-strategy": 1, + "list-style": 1, + "list-style-image": 1, + "list-style-position": 1, + "list-style-type": 1, + "margin": 1, + "margin-bottom": 1, + "margin-left": 1, + "margin-right": 1, + "margin-top": 1, + "mark": 1, + "mark-after": 1, + "mark-before": 1, + "marks": 1, + "marquee-direction": 1, + "marquee-play-count": 1, + "marquee-speed": 1, + "marquee-style": 1, + "max-height": 1, + "max-width": 1, + "min-height": 1, + "min-width": 1, + "move-to": 1, + "nav-down": 1, + "nav-index": 1, + "nav-left": 1, + "nav-right": 1, + "nav-up": 1, + "opacity": 1, + "orphans": 1, + "outline": 1, + "outline-color": 1, + "outline-offset": 1, + "outline-style": 1, + "outline-width": 1, + "overflow": 1, + "overflow-style": 1, + "overflow-x": 1, + "overflow-y": 1, + "padding": 1, + "padding-bottom": 1, + "padding-left": 1, + "padding-right": 1, + "padding-top": 1, + "page": 1, + "page-break-after": 1, + "page-break-before": 1, + "page-break-inside": 1, + "page-policy": 1, + "pause": 1, + "pause-after": 1, + "pause-before": 1, + "perspective": 1, + "perspective-origin": 1, + "phonemes": 1, + "pitch": 1, + "pitch-range": 1, + "play-during": 1, + "position": 1, + "presentation-level": 1, + "punctuation-trim": 1, + "quotes": 1, + "rendering-intent": 1, + "resize": 1, + "rest": 1, + "rest-after": 1, + "rest-before": 1, + "richness": 1, + "right": 1, + "rotation": 1, + "rotation-point": 1, + "ruby-align": 1, + "ruby-overhang": 1, + "ruby-position": 1, + "ruby-span": 1, + "size": 1, + "speak": 1, + "speak-header": 1, + "speak-numeral": 1, + "speak-punctuation": 1, + "speech-rate": 1, + "stress": 1, + "string-set": 1, + "table-layout": 1, + "target": 1, + "target-name": 1, + "target-new": 1, + "target-position": 1, + "text-align": 1, + "text-align-last": 1, + "text-decoration": 1, + "text-emphasis": 1, + "text-height": 1, + "text-indent": 1, + "text-justify": 1, + "text-outline": 1, + "text-shadow": 1, + "text-transform": 1, + "text-wrap": 1, + "top": 1, + "transform": 1, + "transform-origin": 1, + "transform-style": 1, + "transition": 1, + "transition-delay": 1, + "transition-duration": 1, + "transition-property": 1, + "transition-timing-function": 1, + "unicode-bidi": 1, + "user-modify": 1, + "user-select": 1, + "vertical-align": 1, + "visibility": 1, + "voice-balance": 1, + "voice-duration": 1, + "voice-family": 1, + "voice-pitch": 1, + "voice-pitch-range": 1, + "voice-rate": 1, + "voice-stress": 1, + "voice-volume": 1, + "volume": 1, + "white-space": 1, + "white-space-collapse": 1, + "widows": 1, + "width": 1, + "word-break": 1, + "word-spacing": 1, + "word-wrap": 1, + "z-index": 1, + + //IE + "filter": 1, + "zoom": 1 + }; + + parser.addListener("property", function(event){ + var name = event.property.text.toLowerCase(); + + if (!properties[name] && name.charAt(0) != "-"){ + reporter.error("Unknown property '" + event.property + "'.", event.line, event.col, rule); + } + + }); + } + +}); +/* + * Rule: Don't use classes or IDs with elements (a.foo or a#foo). + */ +CSSLint.addRule({ + + //rule information + id: "overqualified-elements", + name: "Overqualified Elements", + desc: "Don't use classes or IDs with elements (a.foo or a#foo).", + browsers: "All", + + //initialization + init: function(parser, reporter){ + var rule = this, + classes = {}; + + parser.addListener("startrule", function(event){ + var selectors = event.selectors, + selector, + part, + modifier, + i, j, k; + + for (i=0; i < selectors.length; i++){ + selector = selectors[i]; + + for (j=0; j < selector.parts.length; j++){ + part = selector.parts[j]; + if (part instanceof parserlib.css.SelectorPart){ + for (k=0; k < part.modifiers.length; k++){ + modifier = part.modifiers[k]; + if (part.elementName && modifier.type == "id"){ + reporter.warn("Element (" + part + ") is overqualified, just use " + modifier + " without element name.", part.line, part.col, rule); + } else if (modifier.type == "class"){ + + if (!classes[modifier]){ + classes[modifier] = []; + } + classes[modifier].push({ modifier: modifier, part: part }); + } + } + } + } + } + }); + + parser.addListener("endstylesheet", function(){ + + var prop; + for (prop in classes){ + if (classes.hasOwnProperty(prop)){ + + //one use means that this is overqualified + if (classes[prop].length == 1 && classes[prop][0].part.elementName){ + reporter.warn("Element (" + classes[prop][0].part + ") is overqualified, just use " + classes[prop][0].modifier + " without element name.", classes[prop][0].part.line, classes[prop][0].part.col, rule); + } + } + } + }); + } + +}); +/* + * Rule: Headings (h1-h6) should not be qualified (namespaced). + */ +CSSLint.addRule({ + + //rule information + id: "qualified-headings", + name: "Qualified Headings", + desc: "Headings should not be qualified (namespaced).", + browsers: "All", + + //initialization + init: function(parser, reporter){ + var rule = this; + + parser.addListener("startrule", function(event){ + var selectors = event.selectors, + selector, + part, + i, j; + + for (i=0; i < selectors.length; i++){ + selector = selectors[i]; + + for (j=0; j < selector.parts.length; j++){ + part = selector.parts[j]; + if (part instanceof parserlib.css.SelectorPart){ + if (part.elementName && /h[1-6]/.test(part.elementName.toString()) && j > 0){ + reporter.warn("Heading (" + part.elementName + ") should not be qualified.", part.line, part.col, rule); + } + } + } + } + }); + } + +}); +/* + * Rule: Selectors that look like regular expressions are slow and should be avoided. + */ +CSSLint.addRule({ + + //rule information + id: "regex-selectors", + name: "Regex Selectors", + desc: "Selectors that look like regular expressions are slow and should be avoided.", + browsers: "All", + + //initialization + init: function(parser, reporter){ + var rule = this; + + parser.addListener("startrule", function(event){ + var selectors = event.selectors, + selector, + part, + modifier, + i, j, k; + + for (i=0; i < selectors.length; i++){ + selector = selectors[i]; + for (j=0; j < selector.parts.length; j++){ + part = selector.parts[j]; + if (part instanceof parserlib.css.SelectorPart){ + for (k=0; k < part.modifiers.length; k++){ + modifier = part.modifiers[k]; + if (modifier.type == "attribute"){ + if (/([\~\|\^\$\*]=)/.test(modifier)){ + reporter.warn("Attribute selectors with " + RegExp.$1 + " are slow!", modifier.line, modifier.col, rule); + } + } + + } + } + } + } + }); + } + +}); +/* + * Rule: Total number of rules should not exceed x. + */ +CSSLint.addRule({ + + //rule information + id: "rules-count", + name: "Rules Count", + desc: "Track how many rules there are.", + browsers: "All", + + //initialization + init: function(parser, reporter){ + var rule = this, + count = 0; + + //count each rule + parser.addListener("startrule", function(){ + count++; + }); + + parser.addListener("endstylesheet", function(){ + reporter.stat("rule-count", count); + }); + } + +}); +/* + * Rule: Use shorthand properties where possible. + * + */ + +CSSLint.addRule({ + + //rule information + id: "shorthand", + name: "Shorthand Properties", + desc: "Use shorthand properties where possible.", + browsers: "All", + + //initialization + init: function(parser, reporter){ + var rule = this, + prop, i, len, + propertiesToCheck = {}, + properties, + mapping = { + "margin": [ + "margin-top", + "margin-bottom", + "margin-left", + "margin-right" + ], + "padding": [ + "padding-top", + "padding-bottom", + "padding-left", + "padding-right" + ] + }; + + //initialize propertiesToCheck + for (prop in mapping){ + if (mapping.hasOwnProperty(prop)){ + for (i=0, len=mapping[prop].length; i < len; i++){ + propertiesToCheck[mapping[prop][i]] = prop; + } + } + } + + function startRule(event){ + properties = {}; + } + + //event handler for end of rules + function endRule(event){ + + var prop, i, len, total; + + //check which properties this rule has + for (prop in mapping){ + if (mapping.hasOwnProperty(prop)){ + total=0; + + for (i=0, len=mapping[prop].length; i < len; i++){ + total += properties[mapping[prop][i]] ? 1 : 0; + } + + if (total == mapping[prop].length){ + reporter.warn("The properties " + mapping[prop].join(", ") + " can be replaced by " + prop + ".", event.line, event.col, rule); + } + } + } + } + + parser.addListener("startrule", startRule); + parser.addListener("startfontface", startRule); + + //check for use of "font-size" + parser.addListener("property", function(event){ + var name = event.property.toString().toLowerCase(), + value = event.value.parts[0].value; + + if (propertiesToCheck[name]){ + properties[name] = 1; + } + }); + + parser.addListener("endrule", endRule); + parser.addListener("endfontface", endRule); + + } + +}); +/* + * Rule: Don't use text-indent for image replacement if you need to support rtl. + * + */ +/* + * Should we be checking for rtl/ltr? + */ +//Commented out due to lack of tests +CSSLint.addRule({ + + //rule information + id: "text-indent", + name: "Text Indent", + desc: "Checks for text indent less than -99px", + browsers: "All", + + //initialization + init: function(parser, reporter){ + var rule = this, + textIndent = false; + + + function startRule(event){ + textIndent = false; + } + + //event handler for end of rules + function endRule(event){ + if (textIndent){ + reporter.warn("Negative text-indent doesn't work well with RTL. If you use text-indent for image replacement explicitly set text-direction for that item to ltr.", textIndent.line, textIndent.col, rule); + } + } + + parser.addListener("startrule", startRule); + parser.addListener("startfontface", startRule); + + //check for use of "font-size" + parser.addListener("property", function(event){ + var name = event.property.toString().toLowerCase(), + value = event.value; + + if (name == "text-indent" && value.parts[0].value < -99){ + textIndent = event.property; + } else if (name == "direction" && value == "ltr"){ + textIndent = false; + } + }); + + parser.addListener("endrule", endRule); + parser.addListener("endfontface", endRule); + + } + +}); +/* + * Rule: Headings (h1-h6) should be defined only once. + */ +CSSLint.addRule({ + + //rule information + id: "unique-headings", + name: "Unique Headings", + desc: "Headings should be defined only once.", + browsers: "All", + + //initialization + init: function(parser, reporter){ + var rule = this; + + var headings = { + h1: 0, + h2: 0, + h3: 0, + h4: 0, + h5: 0, + h6: 0 + }; + + parser.addListener("startrule", function(event){ + var selectors = event.selectors, + selector, + part, + pseudo, + i, j; + + for (i=0; i < selectors.length; i++){ + selector = selectors[i]; + part = selector.parts[selector.parts.length-1]; + + if (part.elementName && /(h[1-6])/i.test(part.elementName.toString())){ + + for (j=0; j < part.modifiers.length; j++){ + if (part.modifiers[j].type == "pseudo"){ + pseudo = true; + break; + } + } + + if (!pseudo){ + headings[RegExp.$1]++; + if (headings[RegExp.$1] > 1) { + reporter.warn("Heading (" + part.elementName + ") has already been defined.", part.line, part.col, rule); + } + } + } + } + }); + + parser.addListener("endstylesheet", function(event){ + var prop, + messages = []; + + for (var prop in headings){ + if (headings.hasOwnProperty(prop)){ + if (headings[prop] > 1){ + messages.push(headings[prop] + " " + prop + "s"); + } + } + } + + if (messages.length){ + reporter.rollupWarn("You have " + messages.join(", ") + " defined in this stylesheet.", rule); + } + }); + } + +}); +/* + * Rule: Don't use universal selector because it's slow. + */ +CSSLint.addRule({ + + //rule information + id: "universal-selector", + name: "Universal Selector", + desc: "The universal selector (*) is known to be slow.", + browsers: "All", + + //initialization + init: function(parser, reporter){ + var rule = this; + + parser.addListener("startrule", function(event){ + var selectors = event.selectors, + selector, + part, + modifier, + i, j, k; + + for (i=0; i < selectors.length; i++){ + selector = selectors[i]; + + part = selector.parts[selector.parts.length-1]; + if (part.elementName == "*"){ + reporter.warn(rule.desc, part.line, part.col, rule); + } + } + }); + } + +}); +/* + * Rule: When using a vendor-prefixed property, make sure to + * include the standard one. + */ +CSSLint.addRule({ + + //rule information + id: "vendor-prefix", + name: "Vendor Prefix", + desc: "When using a vendor-prefixed property, make sure to include the standard one.", + browsers: "All", + + //initialization + init: function(parser, reporter){ + var rule = this, + properties, + num, + propertiesToCheck = { + "-webkit-border-radius": "border-radius", + "-webkit-border-top-left-radius": "border-top-left-radius", + "-webkit-border-top-right-radius": "border-top-right-radius", + "-webkit-border-bottom-left-radius": "border-bottom-left-radius", + "-webkit-border-bottom-right-radius": "border-bottom-right-radius", + + "-o-border-radius": "border-radius", + "-o-border-top-left-radius": "border-top-left-radius", + "-o-border-top-right-radius": "border-top-right-radius", + "-o-border-bottom-left-radius": "border-bottom-left-radius", + "-o-border-bottom-right-radius": "border-bottom-right-radius", + + "-moz-border-radius": "border-radius", + "-moz-border-radius-topleft": "border-top-left-radius", + "-moz-border-radius-topright": "border-top-right-radius", + "-moz-border-radius-bottomleft": "border-bottom-left-radius", + "-moz-border-radius-bottomright": "border-bottom-right-radius", + + "-moz-column-count": "column-count", + "-webkit-column-count": "column-count", + + "-moz-column-gap": "column-gap", + "-webkit-column-gap": "column-gap", + + "-moz-column-rule": "column-rule", + "-webkit-column-rule": "column-rule", + + "-moz-column-rule-style": "column-rule-style", + "-webkit-column-rule-style": "column-rule-style", + + "-moz-column-rule-color": "column-rule-color", + "-webkit-column-rule-color": "column-rule-color", + + "-moz-column-rule-width": "column-rule-width", + "-webkit-column-rule-width": "column-rule-width", + + "-moz-column-width": "column-width", + "-webkit-column-width": "column-width", + + "-webkit-column-span": "column-span", + "-webkit-columns": "columns", + + "-moz-box-shadow": "box-shadow", + "-webkit-box-shadow": "box-shadow", + + "-moz-transform" : "transform", + "-webkit-transform" : "transform", + "-o-transform" : "transform", + "-ms-transform" : "transform", + + "-moz-transform-origin" : "transform-origin", + "-webkit-transform-origin" : "transform-origin", + "-o-transform-origin" : "transform-origin", + "-ms-transform-origin" : "transform-origin", + + "-moz-box-sizing" : "box-sizing", + "-webkit-box-sizing" : "box-sizing", + + "-moz-user-select" : "user-select", + "-khtml-user-select" : "user-select", + "-webkit-user-select" : "user-select" + }; + + //event handler for beginning of rules + function startRule(){ + properties = {}; + num=1; + } + + //event handler for end of rules + function endRule(event){ + var prop, + i, len, + standard, + needed, + actual, + needsStandard = []; + + for (prop in properties){ + if (propertiesToCheck[prop]){ + needsStandard.push({ actual: prop, needed: propertiesToCheck[prop]}); + } + } + + for (i=0, len=needsStandard.length; i < len; i++){ + needed = needsStandard[i].needed; + actual = needsStandard[i].actual; + + if (!properties[needed]){ + reporter.warn("Missing standard property '" + needed + "' to go along with '" + actual + "'.", event.line, event.col, rule); + } else { + //make sure standard property is last + if (properties[needed][0].pos < properties[actual][0].pos){ + reporter.warn("Standard property '" + needed + "' should come after vendor-prefixed property '" + actual + "'.", event.line, event.col, rule); + } + } + } + + } + + parser.addListener("startrule", startRule); + parser.addListener("startfontface", startRule); + + parser.addListener("property", function(event){ + var name = event.property.text.toLowerCase(); + + if (!properties[name]){ + properties[name] = []; + } + + properties[name].push({ name: event.property, value : event.value, pos:num++ }); + }); + + parser.addListener("endrule", endRule); + parser.addListener("endfontface", endRule); + } + +}); +/* + * Rule: If an element has a width of 100%, be careful when placing within + * an element that has padding. It may look strange. + */ +//Commented out pending further review. +/*CSSLint.addRule({ + + //rule information + id: "width-100", + name: "Width 100%", + desc: "Be careful when using width: 100% on elements.", + browsers: "All", + + //initialization + init: function(parser, reporter){ + var rule = this, + width100, + boxsizing; + + parser.addListener("startrule", function(){ + width100 = null; + boxsizing = false; + }); + + parser.addListener("property", function(event){ + var name = event.property.text.toLowerCase(), + value = event.value; + + if (name == "width" && value == "100%"){ + width100 = event.property; + } else if (name == "box-sizing" || /\-(?:webkit|ms|moz)\-box-sizing/.test(name)){ //means you know what you're doing + boxsizing = true; + } + }); + + parser.addListener("endrule", function(){ + if (width100 && !boxsizing){ + reporter.warn("Elements with a width of 100% may not appear as you expect inside of other elements.", width100.line, width100.col, rule); + } + }); + } + +});*/ +/* + * Rule: You don't need to specify units when a value is 0. + */ +CSSLint.addRule({ + + //rule information + id: "zero-units", + name: "Zero Units", + desc: "You don't need to specify units when a value is 0.", + browsers: "All", + + //initialization + init: function(parser, reporter){ + var rule = this; + + //count how many times "float" is used + parser.addListener("property", function(event){ + var parts = event.value.parts, + i = 0, + len = parts.length; + + while(i < len){ + if ((parts[i].units || parts[i].type == "percentage") && parts[i].value === 0){ + reporter.warn("Values of 0 shouldn't have units specified.", parts[i].line, parts[i].col, rule); + } + i++; + } + + }); + + } + +}); +CSSLint.addFormatter({ + //format information + id: "checkstyle-xml", + name: "Checkstyle XML format", + + startFormat: function(){ + return ""; + }, + + endFormat: function(){ + return ""; + }, + + formatResults: function(results, filename) { + var messages = results.messages, + output = []; + + /** + * Generate a source string for a rule. + * Checkstyle source strings usually resemble Java class names e.g + * net.csslint.SomeRuleName + * @param {Object} rule + * @return rule source as {String} + */ + var generateSource = function(rule) { + if (!rule || !('name' in rule)) { + return ""; + } + return 'net.csslint.' + rule.name.replace(/\s/g,''); + }; + + /** + * Replace special characters before write to output. + * + * Rules: + * - single quotes is the escape sequence for double-quotes + * - < is the escape sequence for < + * - > is the escape sequence for > + * + * @param {String} message to escape + * @return escaped message as {String} + */ + var escapeSpecialCharacters = function(str) { + if (!str || str.constructor !== String) { + return ""; + } + return str.replace(/\"/g, "'").replace(//g, ">"); + }; + + if (messages.length > 0) { + output.push(""); + messages.forEach(function (message, i) { + if (message.rollup) { + //ignore rollups for now + } else { + output.push(""); + } + }); + output.push(""); + } + + return output.join(""); + } +}); +CSSLint.addFormatter({ + //format information + id: "compact", + name: "Compact, 'porcelain' format", + + startFormat: function(){ + return ""; + }, + + endFormat: function(){ + return ""; + }, + + formatResults: function(results, filename) { + var messages = results.messages, + output = "", + pos = filename.lastIndexOf("/"), + shortFilename = filename; + + if (messages.length === 0) { + return shortFilename + ": Lint Free!"; + } + + if (pos == -1){ + pos = filename.lastIndexOf("\\"); + } + if (pos > -1){ + shortFilename = filename.substring(pos+1); + } + + messages.forEach(function (message, i) { + if (message.rollup) { + output += shortFilename + ": " + message.message + "\n"; + } else { + output += shortFilename + ": " + "line " + message.line + + ", col " + message.col + ", " + message.message + "\n"; + } + }); + + return output; + } +}); +CSSLint.addFormatter({ + //format information + id: "csslint-xml", + name: "CSSLint XML format", + + /** + * Return opening root XML tag. + * @return {String} to prepend before all results + */ + startFormat: function(){ + return ""; + }, + + /** + * Return closing root XML tag. + * @return {String} to append after all results + */ + endFormat: function(){ + return ""; + }, + + formatResults: function(results, filename) { + var messages = results.messages, + output = []; + + /** + * Replace special characters before write to output. + * + * Rules: + * - single quotes is the escape sequence for double-quotes + * - < is the escape sequence for < + * - > is the escape sequence for > + * + * @param {String} message to escape + * @return escaped message as {String} + */ + var escapeSpecialCharacters = function(str) { + if (!str || str.constructor !== String) { + return ""; + } + return str.replace(/\"/g, "'").replace(//g, ">"); + }; + + if (messages.length > 0) { + output.push(""); + messages.forEach(function (message, i) { + if (message.rollup) { + output.push(""); + } else { + output.push(""); + } + }); + output.push(""); + } + + return output.join(""); + } +}); +CSSLint.addFormatter({ + //format information + id: "lint-xml", + name: "Lint XML format", + + startFormat: function(){ + return ""; + }, + + endFormat: function(){ + return ""; + }, + + formatResults: function(results, filename) { + var messages = results.messages, + output = []; + + /** + * Replace special characters before write to output. + * + * Rules: + * - single quotes is the escape sequence for double-quotes + * - < is the escape sequence for < + * - > is the escape sequence for > + * + * @param {String} message to escape + * @return escaped message as {String} + */ + var escapeSpecialCharacters = function(str) { + if (!str || str.constructor !== String) { + return ""; + } + return str.replace(/\"/g, "'").replace(//g, ">"); + }; + + if (messages.length > 0) { + + output.push(""); + messages.forEach(function (message, i) { + if (message.rollup) { + output.push(""); + } else { + output.push(""); + } + }); + output.push(""); + } + + return output.join(""); + } +}); +CSSLint.addFormatter({ + //format information + id: "text", + name: "Plain Text", + + startFormat: function(){ + return ""; + }, + + endFormat: function(){ + return ""; + }, + + formatResults: function(results, filename) { + var messages = results.messages; + if (messages.length === 0) { + return "\n\ncsslint: No errors in " + filename + "."; + } + + output = "\n\ncsslint: There are " + messages.length + " problems in " + filename + "."; + var pos = filename.lastIndexOf("/"), + shortFilename = filename; + + if (pos == -1){ + pos = filename.lastIndexOf("\\"); + } + if (pos > -1){ + shortFilename = filename.substring(pos+1); + } + + messages.forEach(function (message, i) { + output = output + "\n\n" + shortFilename; + if (message.rollup) { + output += "\n" + (i+1) + ": " + message.type; + output += "\n" + message.message; + } else { + output += "\n" + (i+1) + ": " + message.type + " at line " + message.line + ", col " + message.col; + output += "\n" + message.message; + output += "\n" + message.evidence; + } + }); + + return output; + } +}); + +return CSSLint; +})(); diff --git a/lib/vendor/jshint.js b/lib/vendor/jshint.js new file mode 100644 index 0000000..0cd530a --- /dev/null +++ b/lib/vendor/jshint.js @@ -0,0 +1,4026 @@ +/*! + * JSHint, by JSHint Community. + * + * Licensed under the same slightly modified MIT license that JSLint is. + * It stops evil-doers everywhere. + * + * JSHint is a derivative work of JSLint: + * + * Copyright (c) 2002 Douglas Crockford (www.JSLint.com) + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom + * the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * The Software shall be used for Good, not Evil. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + * JSHint was forked from 2010-12-16 edition of JSLint. + * + */ + +/* + JSHINT is a global function. It takes two parameters. + + var myResult = JSHINT(source, option); + + The first parameter is either a string or an array of strings. If it is a + string, it will be split on '\n' or '\r'. If it is an array of strings, it + is assumed that each string represents one line. The source can be a + JavaScript text or a JSON text. + + The second parameter is an optional object of options which control the + operation of JSHINT. Most of the options are booleans: They are all + optional and have a default value of false. One of the options, predef, + can be an array of names, which will be used to declare global variables, + or an object whose keys are used as global names, with a boolean value + that determines if they are assignable. + + If it checks out, JSHINT returns true. Otherwise, it returns false. + + If false, you can inspect JSHINT.errors to find out the problems. + JSHINT.errors is an array of objects containing these members: + + { + line : The line (relative to 0) at which the lint was found + character : The character (relative to 0) at which the lint was found + reason : The problem + evidence : The text line in which the problem occurred + raw : The raw message before the details were inserted + a : The first detail + b : The second detail + c : The third detail + d : The fourth detail + } + + If a fatal error was found, a null will be the last element of the + JSHINT.errors array. + + You can request a Function Report, which shows all of the functions + and the parameters and vars that they use. This can be used to find + implied global variables and other problems. The report is in HTML and + can be inserted in an HTML . + + var myReport = JSHINT.report(limited); + + If limited is true, then the report will be limited to only errors. + + You can request a data structure which contains JSHint's results. + + var myData = JSHINT.data(); + + It returns a structure with this form: + + { + errors: [ + { + line: NUMBER, + character: NUMBER, + reason: STRING, + evidence: STRING + } + ], + functions: [ + name: STRING, + line: NUMBER, + last: NUMBER, + param: [ + STRING + ], + closure: [ + STRING + ], + var: [ + STRING + ], + exception: [ + STRING + ], + outer: [ + STRING + ], + unused: [ + STRING + ], + global: [ + STRING + ], + label: [ + STRING + ] + ], + globals: [ + STRING + ], + member: { + STRING: NUMBER + }, + unuseds: [ + { + name: STRING, + line: NUMBER + } + ], + implieds: [ + { + name: STRING, + line: NUMBER + } + ], + urls: [ + STRING + ], + json: BOOLEAN + } + + Empty arrays will not be included. + +*/ + +/*jshint + evil: true, nomen: false, onevar: false, regexp: false, strict: true, boss: true, + undef: true, maxlen: 100 +*/ + +/*members "\b", "\t", "\n", "\f", "\r", "!=", "!==", "\"", "%", "(begin)", + "(breakage)", "(context)", "(error)", "(global)", "(identifier)", "(last)", + "(line)", "(loopage)", "(name)", "(onevar)", "(params)", "(scope)", + "(statement)", "(verb)", "*", "+", "++", "-", "--", "\/", "<", "<=", "==", + "===", ">", ">=", $, $$, $A, $F, $H, $R, $break, $continue, $w, Abstract, Ajax, + __filename, __dirname, ActiveXObject, Array, ArrayBuffer, ArrayBufferView, Audio, + Autocompleter, Assets, Boolean, Builder, Buffer, Browser, COM, CScript, Canvas, + CustomAnimation, Class, Control, Chain, Color, Cookie, Core, DataView, Date, + Debug, Draggable, Draggables, Droppables, Document, DomReady, DOMReady, Drag, + E, Enumerator, Enumerable, Element, Elements, Error, Effect, EvalError, Event, + Events, FadeAnimation, Field, Flash, Float32Array, Float64Array, Form, + FormField, Frame, FormData, Function, Fx, GetObject, Group, Hash, HotKey, + HTMLElement, HTMLAnchorElement, HTMLBaseElement, HTMLBlockquoteElement, + HTMLBodyElement, HTMLBRElement, HTMLButtonElement, HTMLCanvasElement, HTMLDirectoryElement, + HTMLDivElement, HTMLDListElement, HTMLFieldSetElement, + HTMLFontElement, HTMLFormElement, HTMLFrameElement, HTMLFrameSetElement, + HTMLHeadElement, HTMLHeadingElement, HTMLHRElement, HTMLHtmlElement, + HTMLIFrameElement, HTMLImageElement, HTMLInputElement, HTMLIsIndexElement, + HTMLLabelElement, HTMLLayerElement, HTMLLegendElement, HTMLLIElement, + HTMLLinkElement, HTMLMapElement, HTMLMenuElement, HTMLMetaElement, + HTMLModElement, HTMLObjectElement, HTMLOListElement, HTMLOptGroupElement, + HTMLOptionElement, HTMLParagraphElement, HTMLParamElement, HTMLPreElement, + HTMLQuoteElement, HTMLScriptElement, HTMLSelectElement, HTMLStyleElement, + HtmlTable, HTMLTableCaptionElement, HTMLTableCellElement, HTMLTableColElement, + HTMLTableElement, HTMLTableRowElement, HTMLTableSectionElement, + HTMLTextAreaElement, HTMLTitleElement, HTMLUListElement, HTMLVideoElement + Iframe, IframeShim, Image, Int16Array, Int32Array, Int8Array, + Insertion, InputValidator, JSON, Keyboard, Locale, LN10, LN2, LOG10E, LOG2E, + MAX_VALUE, MIN_VALUE, Mask, Math, MenuItem, MoveAnimation, MooTools, Native, + NEGATIVE_INFINITY, Number, Object, ObjectRange, Option, Options, OverText, PI, + POSITIVE_INFINITY, PeriodicalExecuter, Point, Position, Prototype, RangeError, + Rectangle, ReferenceError, RegExp, ResizeAnimation, Request, RotateAnimation, + SQRT1_2, SQRT2, ScrollBar, ScriptEngine, ScriptEngineBuildVersion, + ScriptEngineMajorVersion, ScriptEngineMinorVersion, Scriptaculous, Scroller, + Slick, Slider, Selector, SharedWorker, String, Style, SyntaxError, Sortable, Sortables, + SortableObserver, Sound, Spinner, System, Swiff, Text, TextArea, Template, + Timer, Tips, Type, TypeError, Toggle, Try, unescape, URI, URIError, URL, VBArray, WSH, + WScript, Web, Window, XMLDOM, XMLHttpRequest, XPathEvaluator, XPathException, + XPathExpression, XPathNamespace, XPathNSResolver, XPathResult, "\\", a, + addEventListener, address, alert, apply, applicationCache, arguments, arity, + asi, b, bitwise, block, blur, boolOptions, boss, browser, c, call, callee, + caller, cases, charAt, charCodeAt, character, clearInterval, clearTimeout, + close, closed, closure, comment, condition, confirm, console, constructor, + content, couch, create, css, curly, d, data, datalist, dd, debug, decodeURI, + decodeURIComponent, defaultStatus, defineClass, deserialize, devel, document, + dojo, dijit, dojox, define, edition, else, emit, encodeURI, encodeURIComponent, + entityify, eqeqeq, eqnull, errors, es5, escape, eval, event, evidence, evil, + ex, exception, exec, exps, expr, exports, FileReader, first, floor, focus, + forin, fragment, frames, from, fromCharCode, fud, funct, function, functions, + g, gc, getComputedStyle, getRow, GLOBAL, global, globals, globalstrict, + hasOwnProperty, help, history, i, id, identifier, immed, implieds, include, + indent, indexOf, init, ins, instanceOf, isAlpha, isApplicationRunning, isArray, + isDigit, isFinite, isNaN, iterator, join, jshint, + JSHINT, json, jquery, jQuery, keys, label, labelled, last, lastsemic, laxbreak, + latedef, lbp, led, left, length, line, load, loadClass, localStorage, location, + log, loopfunc, m, match, maxerr, maxlen, member,message, meta, module, moveBy, + moveTo, mootools, name, navigator, new, newcap, noarg, node, noempty, nomen, + nonew, nonstandard, nud, onbeforeunload, onblur, onerror, onevar, onecase, onfocus, + onload, onresize, onunload, open, openDatabase, openURL, opener, opera, options, outer, param, + parent, parseFloat, parseInt, passfail, plusplus, predef, print, process, prompt, + proto, prototype, prototypejs, push, quit, range, raw, reach, reason, regexp, + readFile, readUrl, regexdash, removeEventListener, replace, report, require, + reserved, resizeBy, resizeTo, resolvePath, resumeUpdates, respond, rhino, right, + runCommand, scroll, screen, scripturl, scrollBy, scrollTo, scrollbar, search, seal, + send, serialize, sessionStorage, setInterval, setTimeout, shift, slice, sort,spawn, + split, stack, status, start, strict, sub, substr, supernew, shadow, supplant, sum, + sync, test, toLowerCase, toString, toUpperCase, toint32, token, top, trailing, type, + typeOf, Uint16Array, Uint32Array, Uint8Array, undef, unused, urls, validthis, value, valueOf, + var, version, WebSocket, white, window, Worker, wsh*/ + +/*global exports: false */ + +// We build the application inside a function so that we produce only a single +// global variable. That function will be invoked immediately, and its return +// value is the JSHINT function itself. + +var JSHINT = (function () { + "use strict"; + + var anonname, // The guessed name for anonymous functions. + +// These are operators that should not be used with the ! operator. + + bang = { + '<' : true, + '<=' : true, + '==' : true, + '===': true, + '!==': true, + '!=' : true, + '>' : true, + '>=' : true, + '+' : true, + '-' : true, + '*' : true, + '/' : true, + '%' : true + }, + + // These are the JSHint boolean options. + boolOptions = { + asi : true, // if automatic semicolon insertion should be tolerated + bitwise : true, // if bitwise operators should not be allowed + boss : true, // if advanced usage of assignments should be allowed + browser : true, // if the standard browser globals should be predefined + couch : true, // if CouchDB globals should be predefined + curly : true, // if curly braces around all blocks should be required + debug : true, // if debugger statements should be allowed + devel : true, // if logging globals should be predefined (console, + // alert, etc.) + dojo : true, // if Dojo Toolkit globals should be predefined + eqeqeq : true, // if === should be required + eqnull : true, // if == null comparisons should be tolerated + es5 : true, // if ES5 syntax should be allowed + evil : true, // if eval should be allowed + expr : true, // if ExpressionStatement should be allowed as Programs + forin : true, // if for in statements must filter + globalstrict: true, // if global "use strict"; should be allowed (also + // enables 'strict') + immed : true, // if immediate invocations must be wrapped in parens + iterator : true, // if the `__iterator__` property should be disallowed + jquery : true, // if jQuery globals should be predefined + lastsemic : true, // if semicolons may be ommitted for the trailing + // statements inside of a one-line blocks. + latedef : true, // if the use before definition should not be tolerated + laxbreak : true, // if line breaks should not be checked + loopfunc : true, // if functions should be allowed to be defined within + // loops + mootools : true, // if MooTools globals should be predefined + newcap : true, // if constructor names must be capitalized + noarg : true, // if arguments.caller and arguments.callee should be + // disallowed + node : true, // if the Node.js environment globals should be + // predefined + noempty : true, // if empty blocks should be disallowed + nonew : true, // if using `new` for side-effects should be disallowed + nonstandard : true, // if non-standard (but widely adopted) globals should + // be predefined + nomen : true, // if names should be checked + onevar : true, // if only one var statement per function should be + // allowed + onecase : true, // if one case switch statements should be allowed + passfail : true, // if the scan should stop on first error + plusplus : true, // if increment/decrement should not be allowed + proto : true, // if the `__proto__` property should be disallowed + prototypejs : true, // if Prototype and Scriptaculous globals should be + // predefined + regexdash : true, // if unescaped last dash (-) inside brackets should be + // tolerated + regexp : true, // if the . should not be allowed in regexp literals + rhino : true, // if the Rhino environment globals should be predefined + undef : true, // if variables should be declared before used + scripturl : true, // if script-targeted URLs should be tolerated + shadow : true, // if variable shadowing should be tolerated + strict : true, // require the "use strict"; pragma + sub : true, // if all forms of subscript notation are tolerated + supernew : true, // if `new function () { ... };` and `new Object;` + // should be tolerated + trailing : true, // if trailing whitespace rules apply + validthis : true, // if 'this' inside a non-constructor function is valid. + // This is a function scoped option only. + white : true, // if strict whitespace rules apply + wsh : true // if the Windows Scripting Host environment globals + // should be predefined + }, + + // browser contains a set of global names which are commonly provided by a + // web browser environment. + browser = { + ArrayBuffer : false, + ArrayBufferView : false, + Audio : false, + addEventListener : false, + applicationCache : false, + blur : false, + clearInterval : false, + clearTimeout : false, + close : false, + closed : false, + DataView : false, + defaultStatus : false, + document : false, + event : false, + FileReader : false, + Float32Array : false, + Float64Array : false, + FormData : false, + focus : false, + frames : false, + getComputedStyle : false, + HTMLElement : false, + HTMLAnchorElement : false, + HTMLBaseElement : false, + HTMLBlockquoteElement : false, + HTMLBodyElement : false, + HTMLBRElement : false, + HTMLButtonElement : false, + HTMLCanvasElement : false, + HTMLDirectoryElement : false, + HTMLDivElement : false, + HTMLDListElement : false, + HTMLFieldSetElement : false, + HTMLFontElement : false, + HTMLFormElement : false, + HTMLFrameElement : false, + HTMLFrameSetElement : false, + HTMLHeadElement : false, + HTMLHeadingElement : false, + HTMLHRElement : false, + HTMLHtmlElement : false, + HTMLIFrameElement : false, + HTMLImageElement : false, + HTMLInputElement : false, + HTMLIsIndexElement : false, + HTMLLabelElement : false, + HTMLLayerElement : false, + HTMLLegendElement : false, + HTMLLIElement : false, + HTMLLinkElement : false, + HTMLMapElement : false, + HTMLMenuElement : false, + HTMLMetaElement : false, + HTMLModElement : false, + HTMLObjectElement : false, + HTMLOListElement : false, + HTMLOptGroupElement : false, + HTMLOptionElement : false, + HTMLParagraphElement : false, + HTMLParamElement : false, + HTMLPreElement : false, + HTMLQuoteElement : false, + HTMLScriptElement : false, + HTMLSelectElement : false, + HTMLStyleElement : false, + HTMLTableCaptionElement : false, + HTMLTableCellElement : false, + HTMLTableColElement : false, + HTMLTableElement : false, + HTMLTableRowElement : false, + HTMLTableSectionElement : false, + HTMLTextAreaElement : false, + HTMLTitleElement : false, + HTMLUListElement : false, + HTMLVideoElement : false, + history : false, + Int16Array : false, + Int32Array : false, + Int8Array : false, + Image : false, + length : false, + localStorage : false, + location : false, + moveBy : false, + moveTo : false, + name : false, + navigator : false, + onbeforeunload : true, + onblur : true, + onerror : true, + onfocus : true, + onload : true, + onresize : true, + onunload : true, + open : false, + openDatabase : false, + opener : false, + Option : false, + parent : false, + print : false, + removeEventListener : false, + resizeBy : false, + resizeTo : false, + screen : false, + scroll : false, + scrollBy : false, + scrollTo : false, + sessionStorage : false, + setInterval : false, + setTimeout : false, + SharedWorker : false, + status : false, + top : false, + Uint16Array : false, + Uint32Array : false, + Uint8Array : false, + WebSocket : false, + window : false, + Worker : false, + XMLHttpRequest : false, + XPathEvaluator : false, + XPathException : false, + XPathExpression : false, + XPathNamespace : false, + XPathNSResolver : false, + XPathResult : false + }, + + couch = { + "require" : false, + respond : false, + getRow : false, + emit : false, + send : false, + start : false, + sum : false, + log : false, + exports : false, + module : false + }, + + devel = { + alert : false, + confirm : false, + console : false, + Debug : false, + opera : false, + prompt : false + }, + + dojo = { + dojo : false, + dijit : false, + dojox : false, + define : false, + "require" : false + }, + + escapes = { + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\f': '\\f', + '\r': '\\r', + '"' : '\\"', + '/' : '\\/', + '\\': '\\\\' + }, + + funct, // The current function + + functionicity = [ + 'closure', 'exception', 'global', 'label', + 'outer', 'unused', 'var' + ], + + functions, // All of the functions + + global, // The global scope + implied, // Implied globals + inblock, + indent, + jsonmode, + + jquery = { + '$' : false, + jQuery : false + }, + + lines, + lookahead, + member, + membersOnly, + + mootools = { + '$' : false, + '$$' : false, + Assets : false, + Browser : false, + Chain : false, + Class : false, + Color : false, + Cookie : false, + Core : false, + Document : false, + DomReady : false, + DOMReady : false, + Drag : false, + Element : false, + Elements : false, + Event : false, + Events : false, + Fx : false, + Group : false, + Hash : false, + HtmlTable : false, + Iframe : false, + IframeShim : false, + InputValidator : false, + instanceOf : false, + Keyboard : false, + Locale : false, + Mask : false, + MooTools : false, + Native : false, + Options : false, + OverText : false, + Request : false, + Scroller : false, + Slick : false, + Slider : false, + Sortables : false, + Spinner : false, + Swiff : false, + Tips : false, + Type : false, + typeOf : false, + URI : false, + Window : false + }, + + nexttoken, + + node = { + __filename : false, + __dirname : false, + Buffer : false, + console : false, + exports : false, + GLOBAL : false, + global : false, + module : false, + process : false, + require : false + }, + + noreach, + option, + predefined, // Global variables defined by option + prereg, + prevtoken, + + prototypejs = { + '$' : false, + '$$' : false, + '$A' : false, + '$F' : false, + '$H' : false, + '$R' : false, + '$break' : false, + '$continue' : false, + '$w' : false, + Abstract : false, + Ajax : false, + Class : false, + Enumerable : false, + Element : false, + Event : false, + Field : false, + Form : false, + Hash : false, + Insertion : false, + ObjectRange : false, + PeriodicalExecuter: false, + Position : false, + Prototype : false, + Selector : false, + Template : false, + Toggle : false, + Try : false, + Autocompleter : false, + Builder : false, + Control : false, + Draggable : false, + Draggables : false, + Droppables : false, + Effect : false, + Sortable : false, + SortableObserver : false, + Sound : false, + Scriptaculous : false + }, + + rhino = { + defineClass : false, + deserialize : false, + gc : false, + help : false, + load : false, + loadClass : false, + print : false, + quit : false, + readFile : false, + readUrl : false, + runCommand : false, + seal : false, + serialize : false, + spawn : false, + sync : false, + toint32 : false, + version : false + }, + + scope, // The current scope + src, + stack, + + // standard contains the global names that are provided by the + // ECMAScript standard. + standard = { + Array : false, + Boolean : false, + Date : false, + decodeURI : false, + decodeURIComponent : false, + encodeURI : false, + encodeURIComponent : false, + Error : false, + 'eval' : false, + EvalError : false, + Function : false, + hasOwnProperty : false, + isFinite : false, + isNaN : false, + JSON : false, + Math : false, + Number : false, + Object : false, + parseInt : false, + parseFloat : false, + RangeError : false, + ReferenceError : false, + RegExp : false, + String : false, + SyntaxError : false, + TypeError : false, + URIError : false + }, + + // widely adopted global names that are not part of ECMAScript standard + nonstandard = { + escape : false, + unescape : false + }, + + standard_member = { + E : true, + LN2 : true, + LN10 : true, + LOG2E : true, + LOG10E : true, + MAX_VALUE : true, + MIN_VALUE : true, + NEGATIVE_INFINITY : true, + PI : true, + POSITIVE_INFINITY : true, + SQRT1_2 : true, + SQRT2 : true + }, + + strict_mode, + syntax = {}, + tab, + token, + urls, + warnings, + + wsh = { + ActiveXObject : true, + Enumerator : true, + GetObject : true, + ScriptEngine : true, + ScriptEngineBuildVersion : true, + ScriptEngineMajorVersion : true, + ScriptEngineMinorVersion : true, + VBArray : true, + WSH : true, + WScript : true + }; + + // Regular expressions. Some of these are stupidly long. + var ax, cx, tx, nx, nxg, lx, ix, jx, ft; + (function () { + /*jshint maxlen:300 */ + + // unsafe comment or string + ax = /@cc|<\/?|script|\]\s*\]|<\s*!|</i; + + // unsafe characters that are silently deleted by one or more browsers + cx = /[\u0000-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/; + + // token + tx = /^\s*([(){}\[.,:;'"~\?\]#@]|==?=?|\/(\*(jshint|jslint|members?|global)?|=|\/)?|\*[\/=]?|\+(?:=|\++)?|-(?:=|-+)?|%=?|&[&=]?|\|[|=]?|>>?>?=?|<([\/=!]|\!(\[|--)?|<=?)?|\^=?|\!=?=?|[a-zA-Z_$][a-zA-Z0-9_$]*|[0-9]+([xX][0-9a-fA-F]+|\.[0-9]*)?([eE][+\-]?[0-9]+)?)/; + + // characters in strings that need escapement + nx = /[\u0000-\u001f&<"\/\\\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/; + nxg = /[\u0000-\u001f&<"\/\\\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; + + // star slash + lx = /\*\/|\/\*/; + + // identifier + ix = /^([a-zA-Z_$][a-zA-Z0-9_$]*)$/; + + // javascript url + jx = /^(?:javascript|jscript|ecmascript|vbscript|mocha|livescript)\s*:/i; + + // catches /* falls through */ comments + ft = /^\s*\/\*\s*falls\sthrough\s*\*\/\s*$/; + }()); + + function F() {} // Used by Object.create + + function is_own(object, name) { + +// The object.hasOwnProperty method fails when the property under consideration +// is named 'hasOwnProperty'. So we have to use this more convoluted form. + + return Object.prototype.hasOwnProperty.call(object, name); + } + +// Provide critical ES5 functions to ES3. + + if (typeof Array.isArray !== 'function') { + Array.isArray = function (o) { + return Object.prototype.toString.apply(o) === '[object Array]'; + }; + } + + if (typeof Object.create !== 'function') { + Object.create = function (o) { + F.prototype = o; + return new F(); + }; + } + + if (typeof Object.keys !== 'function') { + Object.keys = function (o) { + var a = [], k; + for (k in o) { + if (is_own(o, k)) { + a.push(k); + } + } + return a; + }; + } + +// Non standard methods + + if (typeof String.prototype.entityify !== 'function') { + String.prototype.entityify = function () { + return this + .replace(/&/g, '&') + .replace(//g, '>'); + }; + } + + if (typeof String.prototype.isAlpha !== 'function') { + String.prototype.isAlpha = function () { + return (this >= 'a' && this <= 'z\uffff') || + (this >= 'A' && this <= 'Z\uffff'); + }; + } + + if (typeof String.prototype.isDigit !== 'function') { + String.prototype.isDigit = function () { + return (this >= '0' && this <= '9'); + }; + } + + if (typeof String.prototype.supplant !== 'function') { + String.prototype.supplant = function (o) { + return this.replace(/\{([^{}]*)\}/g, function (a, b) { + var r = o[b]; + return typeof r === 'string' || typeof r === 'number' ? r : a; + }); + }; + } + + if (typeof String.prototype.name !== 'function') { + String.prototype.name = function () { + +// If the string looks like an identifier, then we can return it as is. +// If the string contains no control characters, no quote characters, and no +// backslash characters, then we can simply slap some quotes around it. +// Otherwise we must also replace the offending characters with safe +// sequences. + + if (ix.test(this)) { + return this; + } + if (nx.test(this)) { + return '"' + this.replace(nxg, function (a) { + var c = escapes[a]; + if (c) { + return c; + } + return '\\u' + ('0000' + a.charCodeAt().toString(16)).slice(-4); + }) + '"'; + } + return '"' + this + '"'; + }; + } + + + function combine(t, o) { + var n; + for (n in o) { + if (is_own(o, n)) { + t[n] = o[n]; + } + } + } + + function assume() { + if (option.couch) + combine(predefined, couch); + + if (option.rhino) + combine(predefined, rhino); + + if (option.prototypejs) + combine(predefined, prototypejs); + + if (option.node) + combine(predefined, node); + + if (option.devel) + combine(predefined, devel); + + if (option.dojo) + combine(predefined, dojo); + + if (option.browser) + combine(predefined, browser); + + if (option.nonstandard) + combine(predefined, nonstandard); + + if (option.jquery) + combine(predefined, jquery); + + if (option.mootools) + combine(predefined, mootools); + + if (option.wsh) + combine(predefined, wsh); + + if (option.globalstrict && option.strict !== false) + option.strict = true; + } + + + // Produce an error warning. + function quit(message, line, chr) { + var percentage = Math.floor((line / lines.length) * 100); + + throw { + name: 'JSHintError', + line: line, + character: chr, + message: message + " (" + percentage + "% scanned)." + }; + } + + function warning(m, t, a, b, c, d) { + var ch, l, w; + t = t || nexttoken; + if (t.id === '(end)') { // `~ + t = token; + } + l = t.line || 0; + ch = t.from || 0; + w = { + id: '(error)', + raw: m, + evidence: lines[l - 1] || '', + line: l, + character: ch, + a: a, + b: b, + c: c, + d: d + }; + w.reason = m.supplant(w); + JSHINT.errors.push(w); + if (option.passfail) { + quit('Stopping. ', l, ch); + } + warnings += 1; + if (warnings >= option.maxerr) { + quit("Too many errors.", l, ch); + } + return w; + } + + function warningAt(m, l, ch, a, b, c, d) { + return warning(m, { + line: l, + from: ch + }, a, b, c, d); + } + + function error(m, t, a, b, c, d) { + var w = warning(m, t, a, b, c, d); + } + + function errorAt(m, l, ch, a, b, c, d) { + return error(m, { + line: l, + from: ch + }, a, b, c, d); + } + + + +// lexical analysis and token construction + + var lex = (function lex() { + var character, from, line, s; + +// Private lex methods + + function nextLine() { + var at, + tw; // trailing whitespace check + + if (line >= lines.length) + return false; + + character = 1; + s = lines[line]; + line += 1; + at = s.search(/ \t/); + + if (at >= 0) + warningAt("Mixed spaces and tabs.", line, at + 1); + + s = s.replace(/\t/g, tab); + at = s.search(cx); + + if (at >= 0) + warningAt("Unsafe character.", line, at); + + if (option.maxlen && option.maxlen < s.length) + warningAt("Line too long.", line, s.length); + + // Check for trailing whitespaces + tw = s.search(/\s+$/); + if (option.trailing && ~tw && !~s.search(/^\s+$/)) + warningAt("Trailing whitespace.", line, tw); + + return true; + } + +// Produce a token object. The token inherits from a syntax symbol. + + function it(type, value) { + var i, t; + if (type === '(color)' || type === '(range)') { + t = {type: type}; + } else if (type === '(punctuator)' || + (type === '(identifier)' && is_own(syntax, value))) { + t = syntax[value] || syntax['(error)']; + } else { + t = syntax[type]; + } + t = Object.create(t); + if (type === '(string)' || type === '(range)') { + if (!option.scripturl && jx.test(value)) { + warningAt("Script URL.", line, from); + } + } + if (type === '(identifier)') { + t.identifier = true; + if (value === '__proto__' && !option.proto) { + warningAt("The '{a}' property is deprecated.", + line, from, value); + } else if (value === '__iterator__' && !option.iterator) { + warningAt("'{a}' is only available in JavaScript 1.7.", + line, from, value); + } else if (option.nomen && (value.charAt(0) === '_' || + value.charAt(value.length - 1) === '_')) { + if (!option.node || token.id == '.' || + (value != '__dirname' && value != '__filename')) { + warningAt("Unexpected {a} in '{b}'.", line, from, "dangling '_'", value); + } + } + } + t.value = value; + t.line = line; + t.character = character; + t.from = from; + i = t.id; + if (i !== '(endline)') { + prereg = i && + (('(,=:[!&|?{};'.indexOf(i.charAt(i.length - 1)) >= 0) || + i === 'return'); + } + return t; + } + + // Public lex methods + return { + init: function (source) { + if (typeof source === 'string') { + lines = source + .replace(/\r\n/g, '\n') + .replace(/\r/g, '\n') + .split('\n'); + } else { + lines = source; + } + + // If the first line is a shebang (#!), make it a blank and move on. + // Shebangs are used by Node scripts. + if (lines[0] && lines[0].substr(0, 2) == '#!') + lines[0] = ''; + + line = 0; + nextLine(); + from = 1; + }, + + range: function (begin, end) { + var c, value = ''; + from = character; + if (s.charAt(0) !== begin) { + errorAt("Expected '{a}' and instead saw '{b}'.", + line, character, begin, s.charAt(0)); + } + for (;;) { + s = s.slice(1); + character += 1; + c = s.charAt(0); + switch (c) { + case '': + errorAt("Missing '{a}'.", line, character, c); + break; + case end: + s = s.slice(1); + character += 1; + return it('(range)', value); + case '\\': + warningAt("Unexpected '{a}'.", line, character, c); + } + value += c; + } + + }, + + + // token -- this is called by advance to get the next token + token: function () { + var b, c, captures, d, depth, high, i, l, low, q, t; + + function match(x) { + var r = x.exec(s), r1; + if (r) { + l = r[0].length; + r1 = r[1]; + c = r1.charAt(0); + s = s.substr(l); + from = character + l - r1.length; + character += l; + return r1; + } + } + + function string(x) { + var c, j, r = ''; + + if (jsonmode && x !== '"') { + warningAt("Strings must use doublequote.", + line, character); + } + + function esc(n) { + var i = parseInt(s.substr(j + 1, n), 16); + j += n; + if (i >= 32 && i <= 126 && + i !== 34 && i !== 92 && i !== 39) { + warningAt("Unnecessary escapement.", line, character); + } + character += n; + c = String.fromCharCode(i); + } + j = 0; + for (;;) { + while (j >= s.length) { + j = 0; + if (!nextLine()) { + errorAt("Unclosed string.", line, from); + } + } + c = s.charAt(j); + if (c === x) { + character += 1; + s = s.substr(j + 1); + return it('(string)', r, x); + } + if (c < ' ') { + if (c === '\n' || c === '\r') { + break; + } + warningAt("Control character in string: {a}.", + line, character + j, s.slice(0, j)); + } else if (c === '\\') { + j += 1; + character += 1; + c = s.charAt(j); + switch (c) { + case '\\': + case '"': + case '/': + break; + case '\'': + if (jsonmode) { + warningAt("Avoid \\'.", line, character); + } + break; + case 'b': + c = '\b'; + break; + case 'f': + c = '\f'; + break; + case 'n': + c = '\n'; + break; + case 'r': + c = '\r'; + break; + case 't': + c = '\t'; + break; + case 'u': + esc(4); + break; + case 'v': + if (jsonmode) { + warningAt("Avoid \\v.", line, character); + } + c = '\v'; + break; + case 'x': + if (jsonmode) { + warningAt("Avoid \\x-.", line, character); + } + esc(2); + break; + default: + warningAt("Bad escapement.", line, character); + } + } + r += c; + character += 1; + j += 1; + } + } + + for (;;) { + if (!s) { + return it(nextLine() ? '(endline)' : '(end)', ''); + } + t = match(tx); + if (!t) { + t = ''; + c = ''; + while (s && s < '!') { + s = s.substr(1); + } + if (s) { + errorAt("Unexpected '{a}'.", line, character, s.substr(0, 1)); + } + } else { + + // identifier + + if (c.isAlpha() || c === '_' || c === '$') { + return it('(identifier)', t); + } + + // number + + if (c.isDigit()) { + if (!isFinite(Number(t))) { + warningAt("Bad number '{a}'.", + line, character, t); + } + if (s.substr(0, 1).isAlpha()) { + warningAt("Missing space after '{a}'.", + line, character, t); + } + if (c === '0') { + d = t.substr(1, 1); + if (d.isDigit()) { + if (token.id !== '.') { + warningAt("Don't use extra leading zeros '{a}'.", + line, character, t); + } + } else if (jsonmode && (d === 'x' || d === 'X')) { + warningAt("Avoid 0x-. '{a}'.", + line, character, t); + } + } + if (t.substr(t.length - 1) === '.') { + warningAt( +"A trailing decimal point can be confused with a dot '{a}'.", line, character, t); + } + return it('(number)', t); + } + switch (t) { + + // string + + case '"': + case "'": + return string(t); + + // // comment + + case '//': + if (src) { + warningAt("Unexpected comment.", line, character); + } + s = ''; + token.comment = true; + break; + + // /* comment + + case '/*': + if (src) { + warningAt("Unexpected comment.", line, character); + } + for (;;) { + i = s.search(lx); + if (i >= 0) { + break; + } + if (!nextLine()) { + errorAt("Unclosed comment.", line, character); + } + } + character += i + 2; + if (s.substr(i, 1) === '/') { + errorAt("Nested comment.", line, character); + } + s = s.substr(i + 2); + token.comment = true; + break; + + // /*members /*jshint /*global + + case '/*members': + case '/*member': + case '/*jshint': + case '/*jslint': + case '/*global': + case '*/': + return { + value: t, + type: 'special', + line: line, + character: character, + from: from + }; + + case '': + break; + // / + case '/': + if (token.id === '/=') { + errorAt( +"A regular expression literal can be confused with '/='.", line, from); + } + if (prereg) { + depth = 0; + captures = 0; + l = 0; + for (;;) { + b = true; + c = s.charAt(l); + l += 1; + switch (c) { + case '': + errorAt("Unclosed regular expression.", + line, from); + return; + case '/': + if (depth > 0) { + warningAt("Unescaped '{a}'.", + line, from + l, '/'); + } + c = s.substr(0, l - 1); + q = { + g: true, + i: true, + m: true + }; + while (q[s.charAt(l)] === true) { + q[s.charAt(l)] = false; + l += 1; + } + character += l; + s = s.substr(l); + q = s.charAt(0); + if (q === '/' || q === '*') { + errorAt("Confusing regular expression.", + line, from); + } + return it('(regexp)', c); + case '\\': + c = s.charAt(l); + if (c < ' ') { + warningAt( +"Unexpected control character in regular expression.", line, from + l); + } else if (c === '<') { + warningAt( +"Unexpected escaped character '{a}' in regular expression.", line, from + l, c); + } + l += 1; + break; + case '(': + depth += 1; + b = false; + if (s.charAt(l) === '?') { + l += 1; + switch (s.charAt(l)) { + case ':': + case '=': + case '!': + l += 1; + break; + default: + warningAt( +"Expected '{a}' and instead saw '{b}'.", line, from + l, ':', s.charAt(l)); + } + } else { + captures += 1; + } + break; + case '|': + b = false; + break; + case ')': + if (depth === 0) { + warningAt("Unescaped '{a}'.", + line, from + l, ')'); + } else { + depth -= 1; + } + break; + case ' ': + q = 1; + while (s.charAt(l) === ' ') { + l += 1; + q += 1; + } + if (q > 1) { + warningAt( +"Spaces are hard to count. Use {{a}}.", line, from + l, q); + } + break; + case '[': + c = s.charAt(l); + if (c === '^') { + l += 1; + if (option.regexp) { + warningAt("Insecure '{a}'.", + line, from + l, c); + } else if (s.charAt(l) === ']') { + errorAt("Unescaped '{a}'.", + line, from + l, '^'); + } + } + q = false; + if (c === ']') { + warningAt("Empty class.", line, + from + l - 1); + q = true; + } +klass: do { + c = s.charAt(l); + l += 1; + switch (c) { + case '[': + case '^': + warningAt("Unescaped '{a}'.", + line, from + l, c); + q = true; + break; + case '-': + if (q) { + q = false; + } else { + warningAt("Unescaped '{a}'.", + line, from + l, '-'); + q = true; + } + break; + case ']': + if (!q && !option.regexdash) { + warningAt("Unescaped '{a}'.", + line, from + l - 1, '-'); + } + break klass; + case '\\': + c = s.charAt(l); + if (c < ' ') { + warningAt( +"Unexpected control character in regular expression.", line, from + l); + } else if (c === '<') { + warningAt( +"Unexpected escaped character '{a}' in regular expression.", line, from + l, c); + } + l += 1; + q = true; + break; + case '/': + warningAt("Unescaped '{a}'.", + line, from + l - 1, '/'); + q = true; + break; + case '<': + q = true; + break; + default: + q = true; + } + } while (c); + break; + case '.': + if (option.regexp) { + warningAt("Insecure '{a}'.", line, + from + l, c); + } + break; + case ']': + case '?': + case '{': + case '}': + case '+': + case '*': + warningAt("Unescaped '{a}'.", line, + from + l, c); + } + if (b) { + switch (s.charAt(l)) { + case '?': + case '+': + case '*': + l += 1; + if (s.charAt(l) === '?') { + l += 1; + } + break; + case '{': + l += 1; + c = s.charAt(l); + if (c < '0' || c > '9') { + warningAt( +"Expected a number and instead saw '{a}'.", line, from + l, c); + } + l += 1; + low = +c; + for (;;) { + c = s.charAt(l); + if (c < '0' || c > '9') { + break; + } + l += 1; + low = +c + (low * 10); + } + high = low; + if (c === ',') { + l += 1; + high = Infinity; + c = s.charAt(l); + if (c >= '0' && c <= '9') { + l += 1; + high = +c; + for (;;) { + c = s.charAt(l); + if (c < '0' || c > '9') { + break; + } + l += 1; + high = +c + (high * 10); + } + } + } + if (s.charAt(l) !== '}') { + warningAt( +"Expected '{a}' and instead saw '{b}'.", line, from + l, '}', c); + } else { + l += 1; + } + if (s.charAt(l) === '?') { + l += 1; + } + if (low > high) { + warningAt( +"'{a}' should not be greater than '{b}'.", line, from + l, low, high); + } + } + } + } + c = s.substr(0, l - 1); + character += l; + s = s.substr(l); + return it('(regexp)', c); + } + return it('(punctuator)', t); + + // punctuator + + case '#': + return it('(punctuator)', t); + default: + return it('(punctuator)', t); + } + } + } + } + }; + }()); + + + function addlabel(t, type) { + + if (t === 'hasOwnProperty') { + warning("'hasOwnProperty' is a really bad name."); + } + +// Define t in the current function in the current scope. + + if (is_own(funct, t) && !funct['(global)']) { + if (funct[t] === true) { + if (option.latedef) + warning("'{a}' was used before it was defined.", nexttoken, t); + } else { + if (!option.shadow) + warning("'{a}' is already defined.", nexttoken, t); + } + } + + funct[t] = type; + if (funct['(global)']) { + global[t] = funct; + if (is_own(implied, t)) { + if (option.latedef) + warning("'{a}' was used before it was defined.", nexttoken, t); + delete implied[t]; + } + } else { + scope[t] = funct; + } + } + + + function doOption() { + var b, obj, filter, o = nexttoken.value, t, v; + switch (o) { + case '*/': + error("Unbegun comment."); + break; + case '/*members': + case '/*member': + o = '/*members'; + if (!membersOnly) { + membersOnly = {}; + } + obj = membersOnly; + break; + case '/*jshint': + case '/*jslint': + obj = option; + filter = boolOptions; + break; + case '/*global': + obj = predefined; + break; + default: + error("What?"); + } + t = lex.token(); +loop: for (;;) { + for (;;) { + if (t.type === 'special' && t.value === '*/') { + break loop; + } + if (t.id !== '(endline)' && t.id !== ',') { + break; + } + t = lex.token(); + } + if (t.type !== '(string)' && t.type !== '(identifier)' && + o !== '/*members') { + error("Bad option.", t); + } + v = lex.token(); + if (v.id === ':') { + v = lex.token(); + if (obj === membersOnly) { + error("Expected '{a}' and instead saw '{b}'.", + t, '*/', ':'); + } + if (t.value === 'indent' && (o === '/*jshint' || o === '/*jslint')) { + b = +v.value; + if (typeof b !== 'number' || !isFinite(b) || b <= 0 || + Math.floor(b) !== b) { + error("Expected a small integer and instead saw '{a}'.", + v, v.value); + } + obj.white = true; + obj.indent = b; + } else if (t.value === 'maxerr' && (o === '/*jshint' || o === '/*jslint')) { + b = +v.value; + if (typeof b !== 'number' || !isFinite(b) || b <= 0 || + Math.floor(b) !== b) { + error("Expected a small integer and instead saw '{a}'.", + v, v.value); + } + obj.maxerr = b; + } else if (t.value === 'maxlen' && (o === '/*jshint' || o === '/*jslint')) { + b = +v.value; + if (typeof b !== 'number' || !isFinite(b) || b <= 0 || + Math.floor(b) !== b) { + error("Expected a small integer and instead saw '{a}'.", + v, v.value); + } + obj.maxlen = b; + } else if (t.value == 'validthis') { + if (funct['(global)']) { + error("Option 'validthis' can't be used in a global scope."); + } else { + if (v.value === 'true' || v.value === 'false') + obj[t.value] = v.value === 'true'; + else + error("Bad option value.", v); + } + } else if (v.value === 'true') { + obj[t.value] = true; + } else if (v.value === 'false') { + obj[t.value] = false; + } else { + error("Bad option value.", v); + } + t = lex.token(); + } else { + if (o === '/*jshint' || o === '/*jslint') { + error("Missing option value.", t); + } + obj[t.value] = false; + t = v; + } + } + if (filter) { + assume(); + } + } + + +// We need a peek function. If it has an argument, it peeks that much farther +// ahead. It is used to distinguish +// for ( var i in ... +// from +// for ( var i = ... + + function peek(p) { + var i = p || 0, j = 0, t; + + while (j <= i) { + t = lookahead[j]; + if (!t) { + t = lookahead[j] = lex.token(); + } + j += 1; + } + return t; + } + + + +// Produce the next token. It looks for programming errors. + + function advance(id, t) { + switch (token.id) { + case '(number)': + if (nexttoken.id === '.') { + warning("A dot following a number can be confused with a decimal point.", token); + } + break; + case '-': + if (nexttoken.id === '-' || nexttoken.id === '--') { + warning("Confusing minusses."); + } + break; + case '+': + if (nexttoken.id === '+' || nexttoken.id === '++') { + warning("Confusing plusses."); + } + break; + } + + if (token.type === '(string)' || token.identifier) { + anonname = token.value; + } + + if (id && nexttoken.id !== id) { + if (t) { + if (nexttoken.id === '(end)') { + warning("Unmatched '{a}'.", t, t.id); + } else { + warning("Expected '{a}' to match '{b}' from line {c} and instead saw '{d}'.", + nexttoken, id, t.id, t.line, nexttoken.value); + } + } else if (nexttoken.type !== '(identifier)' || + nexttoken.value !== id) { + warning("Expected '{a}' and instead saw '{b}'.", + nexttoken, id, nexttoken.value); + } + } + + prevtoken = token; + token = nexttoken; + for (;;) { + nexttoken = lookahead.shift() || lex.token(); + if (nexttoken.id === '(end)' || nexttoken.id === '(error)') { + return; + } + if (nexttoken.type === 'special') { + doOption(); + } else { + if (nexttoken.id !== '(endline)') { + break; + } + } + } + } + + +// This is the heart of JSHINT, the Pratt parser. In addition to parsing, it +// is looking for ad hoc lint patterns. We add .fud to Pratt's model, which is +// like .nud except that it is only used on the first token of a statement. +// Having .fud makes it much easier to define statement-oriented languages like +// JavaScript. I retained Pratt's nomenclature. + +// .nud Null denotation +// .fud First null denotation +// .led Left denotation +// lbp Left binding power +// rbp Right binding power + +// They are elements of the parsing method called Top Down Operator Precedence. + + function expression(rbp, initial) { + var left, isArray = false; + + if (nexttoken.id === '(end)') + error("Unexpected early end of program.", token); + + advance(); + if (initial) { + anonname = 'anonymous'; + funct['(verb)'] = token.value; + } + if (initial === true && token.fud) { + left = token.fud(); + } else { + if (token.nud) { + left = token.nud(); + } else { + if (nexttoken.type === '(number)' && token.id === '.') { + warning("A leading decimal point can be confused with a dot: '.{a}'.", + token, nexttoken.value); + advance(); + return token; + } else { + error("Expected an identifier and instead saw '{a}'.", + token, token.id); + } + } + while (rbp < nexttoken.lbp) { + isArray = token.value == 'Array'; + advance(); + if (isArray && token.id == '(' && nexttoken.id == ')') + warning("Use the array literal notation [].", token); + if (token.led) { + left = token.led(left); + } else { + error("Expected an operator and instead saw '{a}'.", + token, token.id); + } + } + } + return left; + } + + +// Functions for conformance of style. + + function adjacent(left, right) { + left = left || token; + right = right || nexttoken; + if (option.white) { + if (left.character !== right.from && left.line === right.line) { + warning("Unexpected space after '{a}'.", right, left.value); + } + } + } + + function nobreak(left, right) { + left = left || token; + right = right || nexttoken; + if (option.white && (left.character !== right.from || left.line !== right.line)) { + warning("Unexpected space before '{a}'.", right, right.value); + } + } + + function nospace(left, right) { + left = left || token; + right = right || nexttoken; + if (option.white && !left.comment) { + if (left.line === right.line) { + adjacent(left, right); + } + } + } + + function nonadjacent(left, right) { + if (option.white) { + left = left || token; + right = right || nexttoken; + if (left.line === right.line && left.character === right.from) { + warning("Missing space after '{a}'.", + nexttoken, left.value); + } + } + } + + function nobreaknonadjacent(left, right) { + left = left || token; + right = right || nexttoken; + if (!option.laxbreak && left.line !== right.line) { + warning("Bad line breaking before '{a}'.", right, right.id); + } else if (option.white) { + left = left || token; + right = right || nexttoken; + if (left.character === right.from) { + warning("Missing space after '{a}'.", + nexttoken, left.value); + } + } + } + + function indentation(bias) { + var i; + if (option.white && nexttoken.id !== '(end)') { + i = indent + (bias || 0); + if (nexttoken.from !== i) { + warning( +"Expected '{a}' to have an indentation at {b} instead at {c}.", + nexttoken, nexttoken.value, i, nexttoken.from); + } + } + } + + function nolinebreak(t) { + t = t || token; + if (t.line !== nexttoken.line) { + warning("Line breaking error '{a}'.", t, t.value); + } + } + + + function comma() { + if (token.line !== nexttoken.line) { + if (!option.laxbreak) { + warning("Bad line breaking before '{a}'.", token, nexttoken.id); + } + } else if (token.character !== nexttoken.from && option.white) { + warning("Unexpected space after '{a}'.", nexttoken, token.value); + } + advance(','); + nonadjacent(token, nexttoken); + } + + +// Functional constructors for making the symbols that will be inherited by +// tokens. + + function symbol(s, p) { + var x = syntax[s]; + if (!x || typeof x !== 'object') { + syntax[s] = x = { + id: s, + lbp: p, + value: s + }; + } + return x; + } + + + function delim(s) { + return symbol(s, 0); + } + + + function stmt(s, f) { + var x = delim(s); + x.identifier = x.reserved = true; + x.fud = f; + return x; + } + + + function blockstmt(s, f) { + var x = stmt(s, f); + x.block = true; + return x; + } + + + function reserveName(x) { + var c = x.id.charAt(0); + if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) { + x.identifier = x.reserved = true; + } + return x; + } + + + function prefix(s, f) { + var x = symbol(s, 150); + reserveName(x); + x.nud = (typeof f === 'function') ? f : function () { + this.right = expression(150); + this.arity = 'unary'; + if (this.id === '++' || this.id === '--') { + if (option.plusplus) { + warning("Unexpected use of '{a}'.", this, this.id); + } else if ((!this.right.identifier || this.right.reserved) && + this.right.id !== '.' && this.right.id !== '[') { + warning("Bad operand.", this); + } + } + return this; + }; + return x; + } + + + function type(s, f) { + var x = delim(s); + x.type = s; + x.nud = f; + return x; + } + + + function reserve(s, f) { + var x = type(s, f); + x.identifier = x.reserved = true; + return x; + } + + + function reservevar(s, v) { + return reserve(s, function () { + if (typeof v === 'function') { + v(this); + } + return this; + }); + } + + + function infix(s, f, p, w) { + var x = symbol(s, p); + reserveName(x); + x.led = function (left) { + if (!w) { + nobreaknonadjacent(prevtoken, token); + nonadjacent(token, nexttoken); + } + if (typeof f === 'function') { + return f(left, this); + } else { + this.left = left; + this.right = expression(p); + return this; + } + }; + return x; + } + + + function relation(s, f) { + var x = symbol(s, 100); + x.led = function (left) { + nobreaknonadjacent(prevtoken, token); + nonadjacent(token, nexttoken); + var right = expression(100); + if ((left && left.id === 'NaN') || (right && right.id === 'NaN')) { + warning("Use the isNaN function to compare with NaN.", this); + } else if (f) { + f.apply(this, [left, right]); + } + if (left.id === '!') { + warning("Confusing use of '{a}'.", left, '!'); + } + if (right.id === '!') { + warning("Confusing use of '{a}'.", left, '!'); + } + this.left = left; + this.right = right; + return this; + }; + return x; + } + + + function isPoorRelation(node) { + return node && + ((node.type === '(number)' && +node.value === 0) || + (node.type === '(string)' && node.value === '') || + (node.type === 'null' && !option.eqnull) || + node.type === 'true' || + node.type === 'false' || + node.type === 'undefined'); + } + + + function assignop(s, f) { + symbol(s, 20).exps = true; + return infix(s, function (left, that) { + var l; + that.left = left; + if (predefined[left.value] === false && + scope[left.value]['(global)'] === true) { + warning("Read only.", left); + } else if (left['function']) { + warning("'{a}' is a function.", left, left.value); + } + if (left) { + if (left.id === '.' || left.id === '[') { + if (!left.left || left.left.value === 'arguments') { + warning('Bad assignment.', that); + } + that.right = expression(19); + return that; + } else if (left.identifier && !left.reserved) { + if (funct[left.value] === 'exception') { + warning("Do not assign to the exception parameter.", left); + } + that.right = expression(19); + return that; + } + if (left === syntax['function']) { + warning( +"Expected an identifier in an assignment and instead saw a function invocation.", + token); + } + } + error("Bad assignment.", that); + }, 20); + } + + + function bitwise(s, f, p) { + var x = symbol(s, p); + reserveName(x); + x.led = (typeof f === 'function') ? f : function (left) { + if (option.bitwise) { + warning("Unexpected use of '{a}'.", this, this.id); + } + this.left = left; + this.right = expression(p); + return this; + }; + return x; + } + + + function bitwiseassignop(s) { + symbol(s, 20).exps = true; + return infix(s, function (left, that) { + if (option.bitwise) { + warning("Unexpected use of '{a}'.", that, that.id); + } + nonadjacent(prevtoken, token); + nonadjacent(token, nexttoken); + if (left) { + if (left.id === '.' || left.id === '[' || + (left.identifier && !left.reserved)) { + expression(19); + return that; + } + if (left === syntax['function']) { + warning( +"Expected an identifier in an assignment, and instead saw a function invocation.", + token); + } + return that; + } + error("Bad assignment.", that); + }, 20); + } + + + function suffix(s, f) { + var x = symbol(s, 150); + x.led = function (left) { + if (option.plusplus) { + warning("Unexpected use of '{a}'.", this, this.id); + } else if ((!left.identifier || left.reserved) && + left.id !== '.' && left.id !== '[') { + warning("Bad operand.", this); + } + this.left = left; + return this; + }; + return x; + } + + + // fnparam means that this identifier is being defined as a function + // argument (see identifier()) + function optionalidentifier(fnparam) { + if (nexttoken.identifier) { + advance(); + if (token.reserved && !option.es5) { + // `undefined` as a function param is a common pattern to protect + // against the case when somebody does `undefined = true` and + // help with minification. More info: https://gist.github.com/315916 + if (!fnparam || token.value != 'undefined') { + warning("Expected an identifier and instead saw '{a}' (a reserved word).", + token, token.id); + } + } + return token.value; + } + } + + // fnparam means that this identifier is being defined as a function + // argument + function identifier(fnparam) { + var i = optionalidentifier(fnparam); + if (i) { + return i; + } + if (token.id === 'function' && nexttoken.id === '(') { + warning("Missing name in function declaration."); + } else { + error("Expected an identifier and instead saw '{a}'.", + nexttoken, nexttoken.value); + } + } + + + function reachable(s) { + var i = 0, t; + if (nexttoken.id !== ';' || noreach) { + return; + } + for (;;) { + t = peek(i); + if (t.reach) { + return; + } + if (t.id !== '(endline)') { + if (t.id === 'function') { + warning( +"Inner functions should be listed at the top of the outer function.", t); + break; + } + warning("Unreachable '{a}' after '{b}'.", t, t.value, s); + break; + } + i += 1; + } + } + + + function statement(noindent) { + var i = indent, r, s = scope, t = nexttoken; + +// We don't like the empty statement. + + if (t.id === ';') { + warning("Unnecessary semicolon.", t); + advance(';'); + return; + } + +// Is this a labelled statement? + + if (t.identifier && !t.reserved && peek().id === ':') { + advance(); + advance(':'); + scope = Object.create(s); + addlabel(t.value, 'label'); + if (!nexttoken.labelled) { + warning("Label '{a}' on {b} statement.", + nexttoken, t.value, nexttoken.value); + } + if (jx.test(t.value + ':')) { + warning("Label '{a}' looks like a javascript url.", + t, t.value); + } + nexttoken.label = t.value; + t = nexttoken; + } + +// Parse the statement. + + if (!noindent) { + indentation(); + } + r = expression(0, true); + + // Look for the final semicolon. + if (!t.block) { + if (!option.expr && (!r || !r.exps)) { + warning("Expected an assignment or function call and instead saw an expression.", + token); + } else if (option.nonew && r.id === '(' && r.left.id === 'new') { + warning("Do not use 'new' for side effects."); + } + + if (nexttoken.id !== ';') { + if (!option.asi) { + // If this is the last statement in a block that ends on + // the same line *and* option lastsemic is on, ignore the warning. + // Otherwise, complain about missing semicolon. + if (!option.lastsemic || nexttoken.id != '}' || + nexttoken.line != token.line) { + warningAt("Missing semicolon.", token.line, token.from + + token.value.length); + } + } + if (!option.asi && !(option.lastsemic && nexttoken.id == '}' && + nexttoken.line == token.line)) { + + } + } else { + adjacent(token, nexttoken); + advance(';'); + nonadjacent(token, nexttoken); + } + } + +// Restore the indentation. + + indent = i; + scope = s; + return r; + } + + + function use_strict() { + if (nexttoken.value === 'use strict') { + if (strict_mode) { + warning("Unnecessary \"use strict\"."); + } + advance(); + advance(';'); + strict_mode = true; + option.newcap = true; + option.undef = true; + return true; + } else { + return false; + } + } + + + function statements(begin) { + var a = [], f, p; + + while (!nexttoken.reach && nexttoken.id !== '(end)') { + if (nexttoken.id === ';') { + warning("Unnecessary semicolon."); + advance(';'); + } else { + a.push(statement()); + } + } + return a; + } + + + /* + * Parses a single block. A block is a sequence of statements wrapped in + * braces. + * + * ordinary - true for everything but function bodies and try blocks. + * stmt - true if block can be a single statement (e.g. in if/for/while). + */ + function block(ordinary, stmt) { + var a, + b = inblock, + old_indent = indent, + m = strict_mode, + s = scope, + t; + + inblock = ordinary; + scope = Object.create(scope); + nonadjacent(token, nexttoken); + t = nexttoken; + + if (nexttoken.id === '{') { + advance('{'); + if (nexttoken.id !== '}' || token.line !== nexttoken.line) { + indent += option.indent; + while (!ordinary && nexttoken.from > indent) { + indent += option.indent; + } + if (!ordinary && !use_strict() && !m && option.strict && + funct['(context)']['(global)']) { + warning("Missing \"use strict\" statement."); + } + a = statements(); + strict_mode = m; + indent -= option.indent; + indentation(); + } + advance('}', t); + indent = old_indent; + } else if (!ordinary) { + error("Expected '{a}' and instead saw '{b}'.", + nexttoken, '{', nexttoken.value); + } else { + if (!stmt || option.curly) + warning("Expected '{a}' and instead saw '{b}'.", + nexttoken, '{', nexttoken.value); + + noreach = true; + a = [statement()]; + noreach = false; + } + funct['(verb)'] = null; + scope = s; + inblock = b; + if (ordinary && option.noempty && (!a || a.length === 0)) { + warning("Empty block."); + } + return a; + } + + + function countMember(m) { + if (membersOnly && typeof membersOnly[m] !== 'boolean') { + warning("Unexpected /*member '{a}'.", token, m); + } + if (typeof member[m] === 'number') { + member[m] += 1; + } else { + member[m] = 1; + } + } + + + function note_implied(token) { + var name = token.value, line = token.line, a = implied[name]; + if (typeof a === 'function') { + a = false; + } + if (!a) { + a = [line]; + implied[name] = a; + } else if (a[a.length - 1] !== line) { + a.push(line); + } + } + + // Build the syntax table by declaring the syntactic elements of the language. + + type('(number)', function () { + return this; + }); + + type('(string)', function () { + return this; + }); + + syntax['(identifier)'] = { + type: '(identifier)', + lbp: 0, + identifier: true, + nud: function () { + var v = this.value, + s = scope[v], + f; + + if (typeof s === 'function') { + // Protection against accidental inheritance. + s = undefined; + } else if (typeof s === 'boolean') { + f = funct; + funct = functions[0]; + addlabel(v, 'var'); + s = funct; + funct = f; + } + + // The name is in scope and defined in the current function. + if (funct === s) { + // Change 'unused' to 'var', and reject labels. + switch (funct[v]) { + case 'unused': + funct[v] = 'var'; + break; + case 'unction': + funct[v] = 'function'; + this['function'] = true; + break; + case 'function': + this['function'] = true; + break; + case 'label': + warning("'{a}' is a statement label.", token, v); + break; + } + } else if (funct['(global)']) { + // The name is not defined in the function. If we are in the global + // scope, then we have an undefined variable. + // + // Operators typeof and delete do not raise runtime errors even if + // the base object of a reference is null so no need to display warning + // if we're inside of typeof or delete. + if (anonname != 'typeof' && anonname != 'delete' && + option.undef && typeof predefined[v] !== 'boolean') { + warning("'{a}' is not defined.", token, v); + } + note_implied(token); + } else { + // If the name is already defined in the current + // function, but not as outer, then there is a scope error. + + switch (funct[v]) { + case 'closure': + case 'function': + case 'var': + case 'unused': + warning("'{a}' used out of scope.", token, v); + break; + case 'label': + warning("'{a}' is a statement label.", token, v); + break; + case 'outer': + case 'global': + break; + default: + // If the name is defined in an outer function, make an outer entry, + // and if it was unused, make it var. + if (s === true) { + funct[v] = true; + } else if (s === null) { + warning("'{a}' is not allowed.", token, v); + note_implied(token); + } else if (typeof s !== 'object') { + // Operators typeof and delete do not raise runtime errors even + // if the base object of a reference is null so no need to + // display warning if we're inside of typeof or delete. + if (anonname != 'typeof' && anonname != 'delete' && option.undef) { + warning("'{a}' is not defined.", token, v); + } else { + funct[v] = true; + } + note_implied(token); + } else { + switch (s[v]) { + case 'function': + case 'unction': + this['function'] = true; + s[v] = 'closure'; + funct[v] = s['(global)'] ? 'global' : 'outer'; + break; + case 'var': + case 'unused': + s[v] = 'closure'; + funct[v] = s['(global)'] ? 'global' : 'outer'; + break; + case 'closure': + case 'parameter': + funct[v] = s['(global)'] ? 'global' : 'outer'; + break; + case 'label': + warning("'{a}' is a statement label.", token, v); + } + } + } + } + return this; + }, + led: function () { + error("Expected an operator and instead saw '{a}'.", + nexttoken, nexttoken.value); + } + }; + + type('(regexp)', function () { + return this; + }); + + +// ECMAScript parser + + delim('(endline)'); + delim('(begin)'); + delim('(end)').reach = true; + delim(''); + delim('(error)').reach = true; + delim('}').reach = true; + delim(')'); + delim(']'); + delim('"').reach = true; + delim("'").reach = true; + delim(';'); + delim(':').reach = true; + delim(','); + delim('#'); + delim('@'); + reserve('else'); + reserve('case').reach = true; + reserve('catch'); + reserve('default').reach = true; + reserve('finally'); + reservevar('arguments', function (x) { + if (strict_mode && funct['(global)']) { + warning("Strict violation.", x); + } + }); + reservevar('eval'); + reservevar('false'); + reservevar('Infinity'); + reservevar('NaN'); + reservevar('null'); + reservevar('this', function (x) { + if (strict_mode && !option.validthis && ((funct['(statement)'] && + funct['(name)'].charAt(0) > 'Z') || funct['(global)'])) { + warning("Possible strict violation.", x); + } + }); + reservevar('true'); + reservevar('undefined'); + assignop('=', 'assign', 20); + assignop('+=', 'assignadd', 20); + assignop('-=', 'assignsub', 20); + assignop('*=', 'assignmult', 20); + assignop('/=', 'assigndiv', 20).nud = function () { + error("A regular expression literal can be confused with '/='."); + }; + assignop('%=', 'assignmod', 20); + bitwiseassignop('&=', 'assignbitand', 20); + bitwiseassignop('|=', 'assignbitor', 20); + bitwiseassignop('^=', 'assignbitxor', 20); + bitwiseassignop('<<=', 'assignshiftleft', 20); + bitwiseassignop('>>=', 'assignshiftright', 20); + bitwiseassignop('>>>=', 'assignshiftrightunsigned', 20); + infix('?', function (left, that) { + that.left = left; + that.right = expression(10); + advance(':'); + that['else'] = expression(10); + return that; + }, 30); + + infix('||', 'or', 40); + infix('&&', 'and', 50); + bitwise('|', 'bitor', 70); + bitwise('^', 'bitxor', 80); + bitwise('&', 'bitand', 90); + relation('==', function (left, right) { + var eqnull = option.eqnull && (left.value == 'null' || right.value == 'null'); + + if (!eqnull && option.eqeqeq) + warning("Expected '{a}' and instead saw '{b}'.", this, '===', '=='); + else if (isPoorRelation(left)) + warning("Use '{a}' to compare with '{b}'.", this, '===', left.value); + else if (isPoorRelation(right)) + warning("Use '{a}' to compare with '{b}'.", this, '===', right.value); + + return this; + }); + relation('==='); + relation('!=', function (left, right) { + var eqnull = option.eqnull && + (left.value == 'null' || right.value == 'null'); + + if (!eqnull && option.eqeqeq) { + warning("Expected '{a}' and instead saw '{b}'.", + this, '!==', '!='); + } else if (isPoorRelation(left)) { + warning("Use '{a}' to compare with '{b}'.", + this, '!==', left.value); + } else if (isPoorRelation(right)) { + warning("Use '{a}' to compare with '{b}'.", + this, '!==', right.value); + } + return this; + }); + relation('!=='); + relation('<'); + relation('>'); + relation('<='); + relation('>='); + bitwise('<<', 'shiftleft', 120); + bitwise('>>', 'shiftright', 120); + bitwise('>>>', 'shiftrightunsigned', 120); + infix('in', 'in', 120); + infix('instanceof', 'instanceof', 120); + infix('+', function (left, that) { + var right = expression(130); + if (left && right && left.id === '(string)' && right.id === '(string)') { + left.value += right.value; + left.character = right.character; + if (!option.scripturl && jx.test(left.value)) { + warning("JavaScript URL.", left); + } + return left; + } + that.left = left; + that.right = right; + return that; + }, 130); + prefix('+', 'num'); + prefix('+++', function () { + warning("Confusing pluses."); + this.right = expression(150); + this.arity = 'unary'; + return this; + }); + infix('+++', function (left) { + warning("Confusing pluses."); + this.left = left; + this.right = expression(130); + return this; + }, 130); + infix('-', 'sub', 130); + prefix('-', 'neg'); + prefix('---', function () { + warning("Confusing minuses."); + this.right = expression(150); + this.arity = 'unary'; + return this; + }); + infix('---', function (left) { + warning("Confusing minuses."); + this.left = left; + this.right = expression(130); + return this; + }, 130); + infix('*', 'mult', 140); + infix('/', 'div', 140); + infix('%', 'mod', 140); + + suffix('++', 'postinc'); + prefix('++', 'preinc'); + syntax['++'].exps = true; + + suffix('--', 'postdec'); + prefix('--', 'predec'); + syntax['--'].exps = true; + prefix('delete', function () { + var p = expression(0); + if (!p || (p.id !== '.' && p.id !== '[')) { + warning("Variables should not be deleted."); + } + this.first = p; + return this; + }).exps = true; + + prefix('~', function () { + if (option.bitwise) { + warning("Unexpected '{a}'.", this, '~'); + } + expression(150); + return this; + }); + + prefix('!', function () { + this.right = expression(150); + this.arity = 'unary'; + if (bang[this.right.id] === true) { + warning("Confusing use of '{a}'.", this, '!'); + } + return this; + }); + prefix('typeof', 'typeof'); + prefix('new', function () { + var c = expression(155), i; + if (c && c.id !== 'function') { + if (c.identifier) { + c['new'] = true; + switch (c.value) { + case 'Object': + warning("Use the object literal notation {}.", token); + break; + case 'Number': + case 'String': + case 'Boolean': + case 'Math': + case 'JSON': + warning("Do not use {a} as a constructor.", token, c.value); + break; + case 'Function': + if (!option.evil) { + warning("The Function constructor is eval."); + } + break; + case 'Date': + case 'RegExp': + break; + default: + if (c.id !== 'function') { + i = c.value.substr(0, 1); + if (option.newcap && (i < 'A' || i > 'Z')) { + warning("A constructor name should start with "+ + "an uppercase letter.", token); + } + } + } + } else { + if (c.id !== '.' && c.id !== '[' && c.id !== '(') { + warning("Bad constructor.", token); + } + } + } else { + if (!option.supernew) + warning("Weird construction. Delete 'new'.", this); + } + adjacent(token, nexttoken); + if (nexttoken.id !== '(' && !option.supernew) { + warning("Missing '()' invoking a constructor."); + } + this.first = c; + return this; + }); + syntax['new'].exps = true; + + prefix('void').exps = true; + + infix('.', function (left, that) { + adjacent(prevtoken, token); + nobreak(); + var m = identifier(); + if (typeof m === 'string') { + countMember(m); + } + that.left = left; + that.right = m; + if (left && left.value === 'arguments' && (m === 'callee' || m === 'caller')) { + if (option.noarg) + warning("Avoid arguments.{a}.", left, m); + else if (strict_mode) + error('Strict violation.'); + } else if (!option.evil && left && left.value === 'document' && + (m === 'write' || m === 'writeln')) { + warning("document.write can be a form of eval.", left); + } + if (!option.evil && (m === 'eval' || m === 'execScript')) { + warning('eval is evil.'); + } + return that; + }, 160, true); + + infix('(', function (left, that) { + if (prevtoken.id !== '}' && prevtoken.id !== ')') { + nobreak(prevtoken, token); + } + nospace(); + if (option.immed && !left.immed && left.id === 'function') { + warning("Wrap an immediate function invocation in parentheses " + + "to assist the reader in understanding that the expression " + + "is the result of a function, and not the function itself."); + } + var n = 0, + p = []; + if (left) { + if (left.type === '(identifier)') { + if (left.value.match(/^[A-Z]([A-Z0-9_$]*[a-z][A-Za-z0-9_$]*)?$/)) { + if (left.value !== 'Number' && left.value !== 'String' && + left.value !== 'Boolean' && + left.value !== 'Date') { + if (left.value === 'Math') { + warning("Math is not a function.", left); + } else if (option.newcap) { + warning( +"Missing 'new' prefix when invoking a constructor.", left); + } + } + } + } + } + if (nexttoken.id !== ')') { + for (;;) { + p[p.length] = expression(10); + n += 1; + if (nexttoken.id !== ',') { + break; + } + comma(); + } + } + advance(')'); + nospace(prevtoken, token); + if (typeof left === 'object') { + if (left.value === 'parseInt' && n === 1) { + warning("Missing radix parameter.", left); + } + if (!option.evil) { + if (left.value === 'eval' || left.value === 'Function' || + left.value === 'execScript') { + warning("eval is evil.", left); + } else if (p[0] && p[0].id === '(string)' && + (left.value === 'setTimeout' || + left.value === 'setInterval')) { + warning( + "Implied eval is evil. Pass a function instead of a string.", left); + } + } + if (!left.identifier && left.id !== '.' && left.id !== '[' && + left.id !== '(' && left.id !== '&&' && left.id !== '||' && + left.id !== '?') { + warning("Bad invocation.", left); + } + } + that.left = left; + return that; + }, 155, true).exps = true; + + prefix('(', function () { + nospace(); + if (nexttoken.id === 'function') { + nexttoken.immed = true; + } + var v = expression(0); + advance(')', this); + nospace(prevtoken, token); + if (option.immed && v.id === 'function') { + if (nexttoken.id === '(') { + warning( +"Move the invocation into the parens that contain the function.", nexttoken); + } else { + warning( +"Do not wrap function literals in parens unless they are to be immediately invoked.", + this); + } + } + return v; + }); + + infix('[', function (left, that) { + nobreak(prevtoken, token); + nospace(); + var e = expression(0), s; + if (e && e.type === '(string)') { + if (!option.evil && (e.value === 'eval' || e.value === 'execScript')) { + warning("eval is evil.", that); + } + countMember(e.value); + if (!option.sub && ix.test(e.value)) { + s = syntax[e.value]; + if (!s || !s.reserved) { + warning("['{a}'] is better written in dot notation.", + e, e.value); + } + } + } + advance(']', that); + nospace(prevtoken, token); + that.left = left; + that.right = e; + return that; + }, 160, true); + + prefix('[', function () { + var b = token.line !== nexttoken.line; + this.first = []; + if (b) { + indent += option.indent; + if (nexttoken.from === indent + option.indent) { + indent += option.indent; + } + } + while (nexttoken.id !== '(end)') { + while (nexttoken.id === ',') { + warning("Extra comma."); + advance(','); + } + if (nexttoken.id === ']') { + break; + } + if (b && token.line !== nexttoken.line) { + indentation(); + } + this.first.push(expression(10)); + if (nexttoken.id === ',') { + comma(); + if (nexttoken.id === ']' && !option.es5) { + warning("Extra comma.", token); + break; + } + } else { + break; + } + } + if (b) { + indent -= option.indent; + indentation(); + } + advance(']', this); + return this; + }, 160); + + + function property_name() { + var id = optionalidentifier(true); + if (!id) { + if (nexttoken.id === '(string)') { + id = nexttoken.value; + advance(); + } else if (nexttoken.id === '(number)') { + id = nexttoken.value.toString(); + advance(); + } + } + return id; + } + + + function functionparams() { + var i, t = nexttoken, p = []; + advance('('); + nospace(); + if (nexttoken.id === ')') { + advance(')'); + nospace(prevtoken, token); + return; + } + for (;;) { + i = identifier(true); + p.push(i); + addlabel(i, 'parameter'); + if (nexttoken.id === ',') { + comma(); + } else { + advance(')', t); + nospace(prevtoken, token); + return p; + } + } + } + + + function doFunction(i, statement) { + var f, + oldOption = option, + oldScope = scope; + + option = Object.create(option); + scope = Object.create(scope); + + funct = { + '(name)' : i || '"' + anonname + '"', + '(line)' : nexttoken.line, + '(context)' : funct, + '(breakage)' : 0, + '(loopage)' : 0, + '(scope)' : scope, + '(statement)': statement + }; + f = funct; + token.funct = funct; + functions.push(funct); + if (i) { + addlabel(i, 'function'); + } + funct['(params)'] = functionparams(); + + block(false); + scope = oldScope; + option = oldOption; + funct['(last)'] = token.line; + funct = funct['(context)']; + return f; + } + + + (function (x) { + x.nud = function () { + var b, f, i, j, p, seen = {}, t; + + b = token.line !== nexttoken.line; + if (b) { + indent += option.indent; + if (nexttoken.from === indent + option.indent) { + indent += option.indent; + } + } + for (;;) { + if (nexttoken.id === '}') { + break; + } + if (b) { + indentation(); + } + if (nexttoken.value === 'get' && peek().id !== ':') { + advance('get'); + if (!option.es5) { + error("get/set are ES5 features."); + } + i = property_name(); + if (!i) { + error("Missing property name."); + } + t = nexttoken; + adjacent(token, nexttoken); + f = doFunction(); + if (!option.loopfunc && funct['(loopage)']) { + warning("Don't make functions within a loop.", t); + } + p = f['(params)']; + if (p) { + warning("Unexpected parameter '{a}' in get {b} function.", t, p[0], i); + } + adjacent(token, nexttoken); + advance(','); + indentation(); + advance('set'); + j = property_name(); + if (i !== j) { + error("Expected {a} and instead saw {b}.", token, i, j); + } + t = nexttoken; + adjacent(token, nexttoken); + f = doFunction(); + p = f['(params)']; + if (!p || p.length !== 1 || p[0] !== 'value') { + warning("Expected (value) in set {a} function.", t, i); + } + } else { + i = property_name(); + if (typeof i !== 'string') { + break; + } + advance(':'); + nonadjacent(token, nexttoken); + expression(10); + } + if (seen[i] === true) { + warning("Duplicate member '{a}'.", nexttoken, i); + } + seen[i] = true; + countMember(i); + if (nexttoken.id === ',') { + comma(); + if (nexttoken.id === ',') { + warning("Extra comma.", token); + } else if (nexttoken.id === '}' && !option.es5) { + warning("Extra comma.", token); + } + } else { + break; + } + } + if (b) { + indent -= option.indent; + indentation(); + } + advance('}', this); + return this; + }; + x.fud = function () { + error("Expected to see a statement and instead saw a block.", token); + }; + }(delim('{'))); + + var varstatement = stmt('var', function (prefix) { + // JavaScript does not have block scope. It only has function scope. So, + // declaring a variable in a block can have unexpected consequences. + var id, name, value; + + if (funct['(onevar)'] && option.onevar) { + warning("Too many var statements."); + } else if (!funct['(global)']) { + funct['(onevar)'] = true; + } + this.first = []; + for (;;) { + nonadjacent(token, nexttoken); + id = identifier(); + if (funct['(global)'] && predefined[id] === false) { + warning("Redefinition of '{a}'.", token, id); + } + addlabel(id, 'unused'); + if (prefix) { + break; + } + name = token; + this.first.push(token); + if (nexttoken.id === '=') { + nonadjacent(token, nexttoken); + advance('='); + nonadjacent(token, nexttoken); + if (nexttoken.id === 'undefined') { + warning("It is not necessary to initialize '{a}' to 'undefined'.", token, id); + } + if (peek(0).id === '=' && nexttoken.identifier) { + error("Variable {a} was not declared correctly.", + nexttoken, nexttoken.value); + } + value = expression(0); + name.first = value; + } + if (nexttoken.id !== ',') { + break; + } + comma(); + } + return this; + }); + varstatement.exps = true; + + blockstmt('function', function () { + if (inblock) { + warning("Function declarations should not be placed in blocks. " + + "Use a function expression or move the statement to the top of " + + "the outer function.", token); + + } + var i = identifier(); + adjacent(token, nexttoken); + addlabel(i, 'unction'); + doFunction(i, true); + if (nexttoken.id === '(' && nexttoken.line === token.line) { + error( +"Function declarations are not invocable. Wrap the whole function invocation in parens."); + } + return this; + }); + + prefix('function', function () { + var i = optionalidentifier(); + if (i) { + adjacent(token, nexttoken); + } else { + nonadjacent(token, nexttoken); + } + doFunction(i); + if (!option.loopfunc && funct['(loopage)']) { + warning("Don't make functions within a loop."); + } + return this; + }); + + blockstmt('if', function () { + var t = nexttoken; + advance('('); + nonadjacent(this, t); + nospace(); + expression(20); + if (nexttoken.id === '=') { + if (!option.boss) + warning("Expected a conditional expression and instead saw an assignment."); + advance('='); + expression(20); + } + advance(')', t); + nospace(prevtoken, token); + block(true, true); + if (nexttoken.id === 'else') { + nonadjacent(token, nexttoken); + advance('else'); + if (nexttoken.id === 'if' || nexttoken.id === 'switch') { + statement(true); + } else { + block(true, true); + } + } + return this; + }); + + blockstmt('try', function () { + var b, e, s; + + block(false); + if (nexttoken.id === 'catch') { + advance('catch'); + nonadjacent(token, nexttoken); + advance('('); + s = scope; + scope = Object.create(s); + e = nexttoken.value; + if (nexttoken.type !== '(identifier)') { + warning("Expected an identifier and instead saw '{a}'.", + nexttoken, e); + } else { + addlabel(e, 'exception'); + } + advance(); + advance(')'); + block(false); + b = true; + scope = s; + } + if (nexttoken.id === 'finally') { + advance('finally'); + block(false); + return; + } else if (!b) { + error("Expected '{a}' and instead saw '{b}'.", + nexttoken, 'catch', nexttoken.value); + } + return this; + }); + + blockstmt('while', function () { + var t = nexttoken; + funct['(breakage)'] += 1; + funct['(loopage)'] += 1; + advance('('); + nonadjacent(this, t); + nospace(); + expression(20); + if (nexttoken.id === '=') { + if (!option.boss) + warning("Expected a conditional expression and instead saw an assignment."); + advance('='); + expression(20); + } + advance(')', t); + nospace(prevtoken, token); + block(true, true); + funct['(breakage)'] -= 1; + funct['(loopage)'] -= 1; + return this; + }).labelled = true; + + reserve('with'); + + blockstmt('switch', function () { + var t = nexttoken, + g = false; + funct['(breakage)'] += 1; + advance('('); + nonadjacent(this, t); + nospace(); + this.condition = expression(20); + advance(')', t); + nospace(prevtoken, token); + nonadjacent(token, nexttoken); + t = nexttoken; + advance('{'); + nonadjacent(token, nexttoken); + indent += option.indent; + this.cases = []; + for (;;) { + switch (nexttoken.id) { + case 'case': + switch (funct['(verb)']) { + case 'break': + case 'case': + case 'continue': + case 'return': + case 'switch': + case 'throw': + break; + default: + // You can tell JSHint that you don't use break intentionally by + // adding a comment /* falls through */ on a line just before + // the next `case`. + if (!ft.test(lines[nexttoken.line - 2])) { + warning( + "Expected a 'break' statement before 'case'.", + token); + } + } + indentation(-option.indent); + advance('case'); + this.cases.push(expression(20)); + g = true; + advance(':'); + funct['(verb)'] = 'case'; + break; + case 'default': + switch (funct['(verb)']) { + case 'break': + case 'continue': + case 'return': + case 'throw': + break; + default: + if (!ft.test(lines[nexttoken.line - 2])) { + warning( + "Expected a 'break' statement before 'default'.", + token); + } + } + indentation(-option.indent); + advance('default'); + g = true; + advance(':'); + break; + case '}': + indent -= option.indent; + indentation(); + advance('}', t); + if (this.cases.length === 1 || this.condition.id === 'true' || + this.condition.id === 'false') { + if (!option.onecase) + warning("This 'switch' should be an 'if'.", this); + } + funct['(breakage)'] -= 1; + funct['(verb)'] = undefined; + return; + case '(end)': + error("Missing '{a}'.", nexttoken, '}'); + return; + default: + if (g) { + switch (token.id) { + case ',': + error("Each value should have its own case label."); + return; + case ':': + statements(); + break; + default: + error("Missing ':' on a case clause.", token); + } + } else { + error("Expected '{a}' and instead saw '{b}'.", + nexttoken, 'case', nexttoken.value); + } + } + } + }).labelled = true; + + stmt('debugger', function () { + if (!option.debug) { + warning("All 'debugger' statements should be removed."); + } + return this; + }).exps = true; + + (function () { + var x = stmt('do', function () { + funct['(breakage)'] += 1; + funct['(loopage)'] += 1; + this.first = block(true); + advance('while'); + var t = nexttoken; + nonadjacent(token, t); + advance('('); + nospace(); + expression(20); + if (nexttoken.id === '=') { + if (!option.boss) + warning("Expected a conditional expression and instead saw an assignment."); + advance('='); + expression(20); + } + advance(')', t); + nospace(prevtoken, token); + funct['(breakage)'] -= 1; + funct['(loopage)'] -= 1; + return this; + }); + x.labelled = true; + x.exps = true; + }()); + + blockstmt('for', function () { + var s, t = nexttoken; + funct['(breakage)'] += 1; + funct['(loopage)'] += 1; + advance('('); + nonadjacent(this, t); + nospace(); + if (peek(nexttoken.id === 'var' ? 1 : 0).id === 'in') { + if (nexttoken.id === 'var') { + advance('var'); + varstatement.fud.call(varstatement, true); + } else { + switch (funct[nexttoken.value]) { + case 'unused': + funct[nexttoken.value] = 'var'; + break; + case 'var': + break; + default: + warning("Bad for in variable '{a}'.", + nexttoken, nexttoken.value); + } + advance(); + } + advance('in'); + expression(20); + advance(')', t); + s = block(true, true); + if (option.forin && (s.length > 1 || typeof s[0] !== 'object' || + s[0].value !== 'if')) { + warning("The body of a for in should be wrapped in an if statement to filter " + + "unwanted properties from the prototype.", this); + } + funct['(breakage)'] -= 1; + funct['(loopage)'] -= 1; + return this; + } else { + if (nexttoken.id !== ';') { + if (nexttoken.id === 'var') { + advance('var'); + varstatement.fud.call(varstatement); + } else { + for (;;) { + expression(0, 'for'); + if (nexttoken.id !== ',') { + break; + } + comma(); + } + } + } + nolinebreak(token); + advance(';'); + if (nexttoken.id !== ';') { + expression(20); + if (nexttoken.id === '=') { + if (!option.boss) + warning("Expected a conditional expression and instead saw an assignment."); + advance('='); + expression(20); + } + } + nolinebreak(token); + advance(';'); + if (nexttoken.id === ';') { + error("Expected '{a}' and instead saw '{b}'.", + nexttoken, ')', ';'); + } + if (nexttoken.id !== ')') { + for (;;) { + expression(0, 'for'); + if (nexttoken.id !== ',') { + break; + } + comma(); + } + } + advance(')', t); + nospace(prevtoken, token); + block(true, true); + funct['(breakage)'] -= 1; + funct['(loopage)'] -= 1; + return this; + } + }).labelled = true; + + + stmt('break', function () { + var v = nexttoken.value; + + if (funct['(breakage)'] === 0) + warning("Unexpected '{a}'.", nexttoken, this.value); + + if (!option.asi) + nolinebreak(this); + + if (nexttoken.id !== ';') { + if (token.line === nexttoken.line) { + if (funct[v] !== 'label') { + warning("'{a}' is not a statement label.", nexttoken, v); + } else if (scope[v] !== funct) { + warning("'{a}' is out of scope.", nexttoken, v); + } + this.first = nexttoken; + advance(); + } + } + reachable('break'); + return this; + }).exps = true; + + + stmt('continue', function () { + var v = nexttoken.value; + + if (funct['(breakage)'] === 0) + warning("Unexpected '{a}'.", nexttoken, this.value); + + if (!option.asi) + nolinebreak(this); + + if (nexttoken.id !== ';') { + if (token.line === nexttoken.line) { + if (funct[v] !== 'label') { + warning("'{a}' is not a statement label.", nexttoken, v); + } else if (scope[v] !== funct) { + warning("'{a}' is out of scope.", nexttoken, v); + } + this.first = nexttoken; + advance(); + } + } else if (!funct['(loopage)']) { + warning("Unexpected '{a}'.", nexttoken, this.value); + } + reachable('continue'); + return this; + }).exps = true; + + + stmt('return', function () { + if (!option.asi) + nolinebreak(this); + + if (nexttoken.id === '(regexp)') + warning("Wrap the /regexp/ literal in parens to disambiguate the slash operator."); + + if (this.line === nexttoken.line || !option.asi) { + if (nexttoken.id !== ';' && !nexttoken.reach) { + nonadjacent(token, nexttoken); + this.first = expression(20); + } + } + + reachable('return'); + return this; + }).exps = true; + + + stmt('throw', function () { + nolinebreak(this); + nonadjacent(token, nexttoken); + this.first = expression(20); + reachable('throw'); + return this; + }).exps = true; + +// Superfluous reserved words + + reserve('class'); + reserve('const'); + reserve('enum'); + reserve('export'); + reserve('extends'); + reserve('import'); + reserve('super'); + + reserve('let'); + reserve('yield'); + reserve('implements'); + reserve('interface'); + reserve('package'); + reserve('private'); + reserve('protected'); + reserve('public'); + reserve('static'); + + +// Parse JSON + + function jsonValue() { + + function jsonObject() { + var o = {}, t = nexttoken; + advance('{'); + if (nexttoken.id !== '}') { + for (;;) { + if (nexttoken.id === '(end)') { + error("Missing '}' to match '{' from line {a}.", + nexttoken, t.line); + } else if (nexttoken.id === '}') { + warning("Unexpected comma.", token); + break; + } else if (nexttoken.id === ',') { + error("Unexpected comma.", nexttoken); + } else if (nexttoken.id !== '(string)') { + warning("Expected a string and instead saw {a}.", + nexttoken, nexttoken.value); + } + if (o[nexttoken.value] === true) { + warning("Duplicate key '{a}'.", + nexttoken, nexttoken.value); + } else if ((nexttoken.value === '__proto__' && + !option.proto) || (nexttoken.value === '__iterator__' && + !option.iterator)) { + warning("The '{a}' key may produce unexpected results.", + nexttoken, nexttoken.value); + } else { + o[nexttoken.value] = true; + } + advance(); + advance(':'); + jsonValue(); + if (nexttoken.id !== ',') { + break; + } + advance(','); + } + } + advance('}'); + } + + function jsonArray() { + var t = nexttoken; + advance('['); + if (nexttoken.id !== ']') { + for (;;) { + if (nexttoken.id === '(end)') { + error("Missing ']' to match '[' from line {a}.", + nexttoken, t.line); + } else if (nexttoken.id === ']') { + warning("Unexpected comma.", token); + break; + } else if (nexttoken.id === ',') { + error("Unexpected comma.", nexttoken); + } + jsonValue(); + if (nexttoken.id !== ',') { + break; + } + advance(','); + } + } + advance(']'); + } + + switch (nexttoken.id) { + case '{': + jsonObject(); + break; + case '[': + jsonArray(); + break; + case 'true': + case 'false': + case 'null': + case '(number)': + case '(string)': + advance(); + break; + case '-': + advance('-'); + if (token.character !== nexttoken.from) { + warning("Unexpected space after '-'.", token); + } + adjacent(token, nexttoken); + advance('(number)'); + break; + default: + error("Expected a JSON value.", nexttoken); + } + } + + +// The actual JSHINT function itself. + + var itself = function (s, o, g) { + var a, i, k; + JSHINT.errors = []; + predefined = Object.create(standard); + combine(predefined, g || {}); + if (o) { + a = o.predef; + if (a) { + if (Array.isArray(a)) { + for (i = 0; i < a.length; i += 1) { + predefined[a[i]] = true; + } + } else if (typeof a === 'object') { + k = Object.keys(a); + for (i = 0; i < k.length; i += 1) { + predefined[k[i]] = !!a[k[i]]; + } + } + } + option = o; + } else { + option = {}; + } + option.indent = option.indent || 4; + option.maxerr = option.maxerr || 50; + + tab = ''; + for (i = 0; i < option.indent; i += 1) { + tab += ' '; + } + indent = 1; + global = Object.create(predefined); + scope = global; + funct = { + '(global)': true, + '(name)': '(global)', + '(scope)': scope, + '(breakage)': 0, + '(loopage)': 0 + }; + functions = [funct]; + urls = []; + src = false; + stack = null; + member = {}; + membersOnly = null; + implied = {}; + inblock = false; + lookahead = []; + jsonmode = false; + warnings = 0; + lex.init(s); + prereg = true; + strict_mode = false; + + prevtoken = token = nexttoken = syntax['(begin)']; + assume(); + + try { + advance(); + switch (nexttoken.id) { + case '{': + case '[': + option.laxbreak = true; + jsonmode = true; + jsonValue(); + break; + default: + if (nexttoken.value === 'use strict') { + if (!option.globalstrict) + warning("Use the function form of \"use strict\"."); + use_strict(); + } + + statements('lib'); + } + advance('(end)'); + } catch (e) { + if (e) { + JSHINT.errors.push({ + reason : e.message, + line : e.line || nexttoken.line, + character : e.character || nexttoken.from + }, null); + } + } + return JSHINT.errors.length === 0; + }; + + // Data summary. + itself.data = function () { + + var data = { functions: [], options: option }, fu, globals, implieds = [], f, i, j, + members = [], n, unused = [], v; + if (itself.errors.length) { + data.errors = itself.errors; + } + + if (jsonmode) { + data.json = true; + } + + for (n in implied) { + if (is_own(implied, n)) { + implieds.push({ + name: n, + line: implied[n] + }); + } + } + if (implieds.length > 0) { + data.implieds = implieds; + } + + if (urls.length > 0) { + data.urls = urls; + } + + globals = Object.keys(scope); + if (globals.length > 0) { + data.globals = globals; + } + + for (i = 1; i < functions.length; i += 1) { + f = functions[i]; + fu = {}; + for (j = 0; j < functionicity.length; j += 1) { + fu[functionicity[j]] = []; + } + for (n in f) { + if (is_own(f, n) && n.charAt(0) !== '(') { + v = f[n]; + if (v === 'unction') { + v = 'unused'; + } + if (Array.isArray(fu[v])) { + fu[v].push(n); + if (v === 'unused') { + unused.push({ + name: n, + line: f['(line)'], + 'function': f['(name)'] + }); + } + } + } + } + for (j = 0; j < functionicity.length; j += 1) { + if (fu[functionicity[j]].length === 0) { + delete fu[functionicity[j]]; + } + } + fu.name = f['(name)']; + fu.param = f['(params)']; + fu.line = f['(line)']; + fu.last = f['(last)']; + data.functions.push(fu); + } + + if (unused.length > 0) { + data.unused = unused; + } + + members = []; + for (n in member) { + if (typeof member[n] === 'number') { + data.member = member; + break; + } + } + + return data; + }; + + itself.report = function (option) { + var data = itself.data(); + + var a = [], c, e, err, f, i, k, l, m = '', n, o = [], s; + + function detail(h, array) { + var b, i, singularity; + if (array) { + o.push('
' + h + ' '); + array = array.sort(); + for (i = 0; i < array.length; i += 1) { + if (array[i] !== singularity) { + singularity = array[i]; + o.push((b ? ', ' : '') + singularity); + b = true; + } + } + o.push('
'); + } + } + + + if (data.errors || data.implieds || data.unused) { + err = true; + o.push('
Error:'); + if (data.errors) { + for (i = 0; i < data.errors.length; i += 1) { + c = data.errors[i]; + if (c) { + e = c.evidence || ''; + o.push('

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

' + + (e && (e.length > 80 ? e.slice(0, 77) + '...' : + e).entityify()) + '

'); + } + } + } + + if (data.implieds) { + s = []; + for (i = 0; i < data.implieds.length; i += 1) { + s[i] = '' + data.implieds[i].name + ' ' + + data.implieds[i].line + ''; + } + o.push('

Implied global: ' + s.join(', ') + '

'); + } + + if (data.unused) { + s = []; + for (i = 0; i < data.unused.length; i += 1) { + s[i] = '' + data.unused[i].name + ' ' + + data.unused[i].line + ' ' + + data.unused[i]['function'] + ''; + } + o.push('

Unused variable: ' + s.join(', ') + '

'); + } + if (data.json) { + o.push('

JSON: bad.

'); + } + o.push('
'); + } + + if (!option) { + + o.push('
'); + + if (data.urls) { + detail("URLs
", data.urls, '
'); + } + + if (data.json && !err) { + o.push('

JSON: good.

'); + } else if (data.globals) { + o.push('
Global ' + + data.globals.sort().join(', ') + '
'); + } else { + o.push('
No new global variables introduced.
'); + } + + for (i = 0; i < data.functions.length; i += 1) { + f = data.functions[i]; + + o.push('
' + f.line + '-' + + f.last + ' ' + (f.name || '') + '(' + + (f.param ? f.param.join(', ') : '') + ')
'); + detail('Unused', f.unused); + detail('Closure', f.closure); + detail('Variable', f['var']); + detail('Exception', f.exception); + detail('Outer', f.outer); + detail('Global', f.global); + detail('Label', f.label); + } + + if (data.member) { + a = Object.keys(data.member); + if (a.length) { + a = a.sort(); + m = '
/*members ';
+                    l = 10;
+                    for (i = 0; i < a.length; i += 1) {
+                        k = a[i];
+                        n = k.name();
+                        if (l + n.length > 72) {
+                            o.push(m + '
'); + m = ' '; + l = 1; + } + l += n.length + 2; + if (data.member[k] === 1) { + n = '' + n + ''; + } + if (i < a.length - 1) { + n += ', '; + } + m += n; + } + o.push(m + '
*/
'); + } + o.push('
'); + } + } + return o.join(''); + }; + + itself.jshint = itself; + itself.edition = '2011-04-16'; + + return itself; +}()); + +// Make JSHINT a Node module, if possible. +if (typeof exports == 'object' && exports) + exports.JSHINT = JSHINT; diff --git a/lib/vendor/jslint.js b/lib/vendor/jslint.js index 33f5e93..3151047 100644 --- a/lib/vendor/jslint.js +++ b/lib/vendor/jslint.js @@ -1,5 +1,5 @@ // jslint.js -// 2011-05-01 +// 2011-08-15 // Copyright (c) 2002 Douglas Crockford (www.JSLint.com) @@ -23,6 +23,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +// WARNING: JSLint will hurt your feelings. // JSLINT is a global function. It takes two parameters. @@ -87,33 +88,40 @@ // } // ], // functions: [ -// name: STRING, -// line: NUMBER, -// last: NUMBER, -// param: [ -// TOKEN -// ], -// closure: [ -// STRING -// ], -// var: [ -// STRING -// ], -// exception: [ -// STRING -// ], -// outer: [ -// STRING -// ], -// unused: [ -// STRING -// ], -// global: [ -// STRING -// ], -// label: [ -// STRING -// ] +// { +// name: STRING, +// line: NUMBER, +// last: NUMBER, +// params: [ +// { +// string: STRING +// } +// ], +// closure: [ +// STRING +// ], +// var: [ +// STRING +// ], +// exception: [ +// STRING +// ], +// outer: [ +// STRING +// ], +// unused: [ +// STRING +// ], +// undef: [ +// STRING +// ], +// global: [ +// STRING +// ], +// label: [ +// STRING +// ] +// } // ], // globals: [ // STRING @@ -121,18 +129,6 @@ // member: { // STRING: NUMBER // }, -// unuseds: [ -// { -// name: STRING, -// line: NUMBER -// } -// ], -// implieds: [ -// { -// name: STRING, -// line: NUMBER -// } -// ], // urls: [ // STRING // ], @@ -146,7 +142,7 @@ // with // JSON.stringify(JSLINT.tree, [ -// 'value', 'arity', 'name', 'first', +// 'string', 'arity', 'name', 'first', // 'second', 'third', 'block', 'else' // ], 4)); @@ -160,13 +156,15 @@ // The current option set is // adsafe true, if ADsafe rules should be enforced -// bitwise true, if bitwise operators should not be allowed +// bitwise true, if bitwise operators should be allowed // browser true, if the standard browser globals should be predefined // cap true, if upper case HTML should be allowed +// confusion true, if types can be used inconsistently // 'continue' true, if the continuation statement should be tolerated // css true, if CSS workarounds should be tolerated // debug true, if debugger statements should be allowed // devel true, if logging should be allowed (console, alert, etc.) +// eqeq true, if == should be allowed // es5 true, if ES5 syntax should be allowed // evil true, if eval should be allowed // forin true, if for in statements need not filter @@ -174,28 +172,29 @@ // indent the indentation factor // maxerr the maximum number of errors to allow // maxlen the maximum length of a source line -// newcap true, if constructor names must be capitalized +// newcap true, if constructor names capitalization is ignored // node true, if Node.js globals should be predefined -// nomen true, if names should be checked +// nomen true, if names may have dangling _ // on true, if HTML event handlers should be allowed -// onevar true, if only one var statement per function should be allowed // passfail true, if the scan should stop on first error -// plusplus true, if increment/decrement should not be allowed -// regexp true, if the . should not be allowed in regexp literals +// plusplus true, if increment/decrement should be allowed +// properties true, if all property names must be declared with /*properties*/ +// regexp true, if the . should be allowed in regexp literals // rhino true, if the Rhino environment globals should be predefined -// undef true, if variables should be declared before used +// undef true, if variables can be declared out of order // unparam true, if unused parameters should be tolerated // safe true, if use of some browser features should be restricted -// windows true, if MS Windows-specific globals should be predefined -// strict true, require the 'use strict'; pragma +// sloppy true, if the 'use strict'; pragma is optional // sub true, if all forms of subscript notation are tolerated -// white true, if strict whitespace rules apply +// vars true, if multiple var statements per function should be allowed +// white true, if sloppy whitespace is tolerated // widget true if the Yahoo Widgets globals should be predefined +// windows true, if MS Windows-specific globals should be predefined // For example: /*jslint - evil: true, nomen: false, onevar: false, regexp: false, strict: true + evil: true, nomen: true, regexp: true */ // The properties directive declares an exclusive list of property names. @@ -204,158 +203,201 @@ // For example: -/*properties "\b", "\t", "\n", "\f", "\r", "!=", "!==", "\"", "%", - "&", "'", "(begin)", "(breakage)", "(context)", "(error)", - "(global)", "(identifier)", "(line)", "(loopage)", "(name)", "(onevar)", - "(params)", "(scope)", "(statement)", "(token)", "(verb)", ")", "*", - "+", "-", "/", ";", "<", "<<", "<=", "==", "===", ">", - ">=", ">>", ">>>", ADSAFE, ActiveXObject, Array, - Boolean, Buffer, COM, CScript, Canvas, CustomAnimation, Date, Debug, E, - Enumerator, Error, EvalError, FadeAnimation, Flash, FormField, Frame, - Function, HotKey, Image, JSON, LN10, LN2, LOG10E, LOG2E, MAX_VALUE, - MIN_VALUE, Math, MenuItem, MoveAnimation, NEGATIVE_INFINITY, Number, - Object, Option, PI, POSITIVE_INFINITY, Point, RangeError, Rectangle, - ReferenceError, RegExp, ResizeAnimation, RotateAnimation, SQRT1_2, - SQRT2, ScrollBar, Storage, String, Style, SyntaxError, System, Text, - TextArea, Timer, TypeError, URIError, URL, VBArray, WScript, Web, - Window, XMLDOM, XMLHttpRequest, "\\", "^", __dirname, __filename, a, - a_function, a_label, a_not_allowed, a_not_defined, a_scope, abbr, - acronym, activeborder, activecaption, address, adsafe, adsafe_a, - adsafe_autocomplete, adsafe_bad_id, adsafe_div, adsafe_fragment, - adsafe_go, adsafe_html, adsafe_id, adsafe_id_go, adsafe_lib, - adsafe_lib_second, adsafe_missing_id, adsafe_name_a, adsafe_placement, - adsafe_prefix_a, adsafe_script, adsafe_source, adsafe_subscript_a, - adsafe_tag, alert, aliceblue, all, already_defined, and, animator, - antiquewhite, appleScript, applet, apply, approved, appworkspace, aqua, - aquamarine, area, arguments, arity, article, aside, assign, - assign_exception, assignment_function_expression, at, attribute_case_a, - audio, autocomplete, avoid_a, azure, b, background, - "background-attachment", "background-color", "background-image", - "background-position", "background-repeat", bad_assignment, bad_color_a, - bad_constructor, bad_entity, bad_html, bad_id_a, bad_in_a, - bad_invocation, bad_name_a, bad_new, bad_number, bad_operand, bad_type, - bad_url, bad_wrap, base, bdo, beep, beige, big, bisque, bitwise, black, - blanchedalmond, block, blockquote, blue, blueviolet, body, border, - "border-bottom", "border-bottom-color", "border-bottom-style", - "border-bottom-width", "border-collapse", "border-color", "border-left", - "border-left-color", "border-left-style", "border-left-width", - "border-right", "border-right-color", "border-right-style", - "border-right-width", "border-spacing", "border-style", "border-top", - "border-top-color", "border-top-style", "border-top-width", - "border-width", bottom, br, braille, brown, browser, burlywood, button, - buttonface, buttonhighlight, buttonshadow, buttontext, bytesToUIString, - c, cadetblue, call, callee, caller, canvas, cap, caption, - "caption-side", captiontext, center, charAt, charCodeAt, character, - chartreuse, chocolate, chooseColor, chooseFile, chooseFolder, cite, - clear, clearInterval, clearTimeout, clip, closeWidget, closure, cm, - code, col, colgroup, color, combine_var, command, comment, comments, - concat, conditional_assignment, confirm, confusing_a, confusing_regexp, - console, constructor, constructor_name_a, content, continue, control_a, - convertPathToHFS, convertPathToPlatform, coral, cornflowerblue, - cornsilk, "counter-increment", "counter-reset", create, crimson, css, - cursor, cyan, d, dangerous_comment, dangling_a, darkblue, darkcyan, - darkgoldenrod, darkgray, darkgreen, darkkhaki, darkmagenta, - darkolivegreen, darkorange, darkorchid, darkred, darksalmon, - darkseagreen, darkslateblue, darkslategray, darkturquoise, darkviolet, - data, datalist, dd, debug, decodeURI, decodeURIComponent, deeppink, - deepskyblue, defineClass, del, deleted, deserialize, details, devel, - dfn, dialog, dimgray, dir, direction, display, disrupt, div, dl, - document, dodgerblue, dt, duplicate_a, edge, edition, else, em, embed, - embossed, empty, "empty-cells", empty_block, empty_case, empty_class, - encodeURI, encodeURIComponent, entityify, errors, es5, escape, eval, - event, evidence, evil, ex, exception, exec, expected_a, - expected_a_at_b_c, expected_a_b, expected_a_b_from_c_d, expected_at_a, - expected_attribute_a, expected_attribute_value_a, expected_class_a, - expected_fraction_a, expected_id_a, expected_identifier_a, - expected_identifier_a_reserved, expected_lang_a, expected_linear_a, - expected_media_a, expected_name_a, expected_nonstandard_style_attribute, - expected_number_a, expected_operator_a, expected_percent_a, - expected_positive_a, expected_pseudo_a, expected_selector_a, - expected_small_a, expected_space_a_b, expected_string_a, - expected_style_attribute, expected_style_pattern, expected_tagname_a, - fieldset, figure, filesystem, filter, firebrick, first, float, floor, - floralwhite, focusWidget, font, "font-family", "font-size", - "font-size-adjust", "font-stretch", "font-style", "font-variant", - "font-weight", footer, for_if, forestgreen, forin, form, fragment, - frame, frames, frameset, from, fromCharCode, fuchsia, fud, funct, - function, function_block, function_eval, function_loop, - function_statement, function_strict, functions, g, gainsboro, gc, - get_set, ghostwhite, global, globals, gold, goldenrod, gray, graytext, - green, greenyellow, h1, h2, h3, h4, h5, h6, handheld, hasOwnProperty, - head, header, height, help, hgroup, highlight, highlighttext, history, - honeydew, hotpink, hr, "hta:application", html, html_confusion_a, - html_handlers, i, iTunes, id, identifier, identifier_function, iframe, - img, immed, implied_evil, implieds, in, inactiveborder, inactivecaption, - inactivecaptiontext, include, indent, indexOf, indianred, indigo, - infix_in, infobackground, infotext, init, input, ins, insecure_a, - isAlpha, isApplicationRunning, isArray, isDigit, isFinite, isNaN, ivory, - join, jslint, json, kbd, keygen, keys, khaki, konfabulatorVersion, - label, label_a_b, labeled, lang, lastIndexOf, lavender, lavenderblush, - lawngreen, lbp, leading_decimal_a, led, left, legend, lemonchiffon, - length, "letter-spacing", li, lib, lightblue, lightcoral, lightcyan, - lightgoldenrodyellow, lightgreen, lightpink, lightsalmon, lightseagreen, - lightskyblue, lightslategray, lightsteelblue, lightyellow, lime, - limegreen, line, "line-height", linen, link, "list-style", - "list-style-image", "list-style-position", "list-style-type", load, - loadClass, localStorage, location, log, m, magenta, map, margin, - "margin-bottom", "margin-left", "margin-right", "margin-top", mark, - "marker-offset", maroon, match, "max-height", "max-width", maxerr, - maxlen, md5, mediumaquamarine, mediumblue, mediumorchid, mediumpurple, - mediumseagreen, mediumslateblue, mediumspringgreen, mediumturquoise, - mediumvioletred, member, menu, menutext, message, meta, meter, - midnightblue, "min-height", "min-width", mintcream, missing_a, - missing_a_after_b, missing_option, missing_property, missing_space_a_b, - missing_url, missing_use_strict, mistyrose, mixed, mm, moccasin, mode, - module, move_invocation, move_var, name, name_function, nav, - navajowhite, navigator, navy, nested_comment, newcap, next, node, - noframes, nomen, noscript, not, not_a_constructor, not_a_defined, - not_a_function, not_a_label, not_a_scope, not_greater, nud, object, ol, - oldlace, olive, olivedrab, on, onevar, opacity, open, openURL, opera, - optgroup, option, orange, orangered, orchid, outer, outline, - "outline-color", "outline-style", "outline-width", output, overflow, - "overflow-x", "overflow-y", p, padding, "padding-bottom", - "padding-left", "padding-right", "padding-top", "page-break-after", - "page-break-before", palegoldenrod, palegreen, paleturquoise, - palevioletred, papayawhip, param, parameter_a_get_b, parameter_set_a, - paren, parent, parseFloat, parseInt, passfail, pc, peachpuff, peru, - pink, play, plum, plusplus, pop, popupMenu, position, postscript, - powderblue, pre, predef, preferenceGroups, preferences, prev, print, - process, progress, projection, prompt, prototype, pt, purple, push, px, - q, querystring, quit, quote, quotes, radix, random, range, raw, - readFile, readUrl, read_only, reason, red, redefinition_a, regexp, - reloadWidget, replace, report, require, reserved, reserved_a, - resolvePath, resumeUpdates, rhino, right, rosybrown, royalblue, rp, rt, - ruby, runCommand, runCommandInBg, saddlebrown, safe, salmon, samp, - sandybrown, saveAs, savePreferences, scanned_a_b, screen, script, - scrollbar, seagreen, seal, search, seashell, second, section, select, - serialize, sessionStorage, setInterval, setTimeout, shift, - showWidgetPreferences, sienna, silver, skyblue, slash_equal, slateblue, - slategray, sleep, slice, small, snow, sort, source, span, spawn, speak, - speech, split, springgreen, src, stack, statement_block, steelblue, - stopping, strange_loop, strict, strong, style, styleproperty, sub, - subscript, substr, sup, supplant, suppressUpdates, sync, system, table, - "table-layout", tag_a_in_b, tan, tbody, td, teal, tellWidget, test, - "text-align", "text-decoration", "text-indent", "text-shadow", - "text-transform", textarea, tfoot, th, thead, third, thistle, - threeddarkshadow, threedface, threedhighlight, threedlightshadow, - threedshadow, thru, time, title, toLowerCase, toString, toUpperCase, - toint32, token, tomato, too_long, too_many, top, tr, trailing_decimal_a, - tree, tt, tty, turquoise, tv, type, typeof, u, ul, unclosed, - unclosed_comment, unclosed_regexp, undef, unescape, unescaped_a, - unexpected_a, unexpected_char_a_b, unexpected_comment, - unexpected_property_a, unexpected_space_a_b, "unicode-bidi", - unnecessary_initialize, unnecessary_use, unreachable_a_b, - unrecognized_style_attribute_a, unrecognized_tag_a, unparam, unsafe, unused, - unwatch, updateNow, url, urls, use_array, use_braces, use_object, use_param, - used_before_a, util, value, valueOf, var, var_a_not, version, - "vertical-align", video, violet, visibility, was, watch, - weird_assignment, weird_condition, weird_new, weird_program, - weird_relation, weird_ternary, wheat, white, "white-space", whitesmoke, - widget, width, window, windowframe, windows, windowtext, "word-spacing", - "word-wrap", wrap, wrap_immediate, wrap_regexp, write_is_wrong, - yahooCheckLogin, yahooLogin, yahooLogout, yellow, yellowgreen, - "z-index", "|", "~" - */ +/*properties + '\b': string, '\t': string, '\n': string, '\f': string, '\r': string, + '!=': boolean, '!==': boolean, '"': string, '%': boolean, '\'': string, + '(begin)', '(breakage)': number, '(complexity)', '(confusion)': boolean, + '(context)': object, '(error)', '(identifier)', '(line)': number, + '(loopage)': number, '(name)', '(old_property_type)', '(params)', + '(scope)': object, '(token)', '(vars)', '(verb)', '*': boolean, + '+': boolean, '-': boolean, '/': *, '<': boolean, '<=': boolean, + '==': boolean, '===': boolean, '>': boolean, '>=': boolean, + ADSAFE: boolean, Array, Date, E: string, Function, LN10: string, + LN2: string, LOG10E: string, LOG2E: string, MAX_VALUE: string, + MIN_VALUE: string, NEGATIVE_INFINITY: string, Object, PI: string, + POSITIVE_INFINITY: string, SQRT1_2: string, SQRT2: string, '\\': string, + a: object, a_label: string, a_not_allowed: string, a_not_defined: string, + a_scope: string, abbr: object, acronym: object, address: object, adsafe, + adsafe_a: string, adsafe_autocomplete: string, adsafe_bad_id: string, + adsafe_div: string, adsafe_fragment: string, adsafe_go: string, + adsafe_html: string, adsafe_id: string, adsafe_id_go: string, + adsafe_lib: string, adsafe_lib_second: string, adsafe_missing_id: string, + adsafe_name_a: string, adsafe_placement: string, adsafe_prefix_a: string, + adsafe_script: string, adsafe_source: string, adsafe_subscript_a: string, + adsafe_tag: string, all: boolean, already_defined: string, and: string, + applet: object, apply: string, approved: array, area: object, + arity: string, article: object, aside: object, assign: boolean, + assign_exception: string, assignment_function_expression: string, + at: number, attribute_case_a: string, audio: object, autocomplete: string, + avoid_a: string, b: *, background: array, 'background-attachment': array, + 'background-color': array, 'background-image': array, + 'background-position': array, 'background-repeat': array, + bad_assignment: string, bad_color_a: string, bad_constructor: string, + bad_entity: string, bad_html: string, bad_id_a: string, bad_in_a: string, + bad_invocation: string, bad_name_a: string, bad_new: string, + bad_number: string, bad_operand: string, bad_style: string, + bad_type: string, bad_url_a: string, bad_wrap: string, base: object, + bdo: object, big: object, bind: string, bitwise: boolean, block: array, + blockquote: object, body: object, border: array, 'border-bottom': array, + 'border-bottom-color', 'border-bottom-left-radius', + 'border-bottom-right-radius', 'border-bottom-style': array, + 'border-bottom-width', 'border-collapse': array, 'border-color': array, + 'border-left': array, 'border-left-color', 'border-left-style': array, + 'border-left-width', 'border-radius', 'border-right': array, + 'border-right-color', 'border-right-style': array, 'border-right-width', + 'border-spacing': array, 'border-style': array, 'border-top': array, + 'border-top-color', 'border-top-left-radius', 'border-top-right-radius', + 'border-top-style': array, 'border-top-width', 'border-width': array, + bottom: array, br: object, braille: boolean, browser: boolean, + button: object, c, call: string, canvas: object, cap, caption: object, + 'caption-side': array, ceil: string, center: object, charAt: *, + charCodeAt: *, character, cite: object, clear: array, clip: array, closure, + cm: boolean, code: object, col: object, colgroup: object, color, + combine_var: string, command: object, concat: string, + conditional_assignment: string, confusing_a: string, + confusing_regexp: string, confusion: boolean, constructor: string, + constructor_name_a: string, content: array, continue, control_a: string, + 'counter-increment': array, 'counter-reset': array, create: *, css: string, + cursor: array, d, dangerous_comment: string, dangling_a: string, + data: function object, datalist: object, dd: object, debug, + defineProperties: string, defineProperty: string, del: object, + deleted: string, details: object, devel: boolean, dfn: object, + dialog: object, dir: object, direction: array, display: array, + disrupt: boolean, div: object, dl: object, dt: object, duplicate_a: string, + edge: string, edition: string, else, em: *, embed: object, + embossed: boolean, empty: boolean, 'empty-cells': array, + empty_block: string, empty_case: string, empty_class: string, + entityify: function, eqeq, errors: array, es5: string, eval, every: string, + evidence, evil: string, ex: boolean, exception, exec: *, + expected_a: string, expected_a_at_b_c: string, expected_a_b: string, + expected_a_b_from_c_d: string, expected_at_a: string, + expected_attribute_a: string, expected_attribute_value_a: string, + expected_class_a: string, expected_fraction_a: string, + expected_id_a: string, expected_identifier_a: string, + expected_identifier_a_reserved: string, expected_lang_a: string, + expected_linear_a: string, expected_media_a: string, + expected_name_a: string, expected_nonstandard_style_attribute: string, + expected_number_a: string, expected_operator_a: string, + expected_percent_a: string, expected_positive_a: string, + expected_pseudo_a: string, expected_selector_a: string, + expected_small_a: string, expected_space_a_b: string, + expected_string_a: string, expected_style_attribute: string, + expected_style_pattern: string, expected_tagname_a: string, + expected_type_a: string, f: string, fieldset: object, figure: object, + filter: *, first: *, float: array, floor: *, font: *, 'font-family', + 'font-size': array, 'font-size-adjust': array, 'font-stretch': array, + 'font-style': array, 'font-variant': array, 'font-weight': array, + footer: object, for, forEach: *, for_if: string, forin, form: object, + fragment, frame: object, frameset: object, freeze: string, from: number, + fromCharCode: function, fud: function, funct: object, function, + function_block: string, function_eval: string, function_loop: string, + function_statement: string, function_strict: string, functions: array, + getDate: string, getDay: string, getFullYear: string, getHours: string, + getMilliseconds: string, getMinutes: string, getMonth: string, + getOwnPropertyDescriptor: string, getOwnPropertyNames: string, + getPrototypeOf: string, getSeconds: string, getTime: string, + getTimezoneOffset: string, getUTCDate: string, getUTCDay: string, + getUTCFullYear: string, getUTCHours: string, getUTCMilliseconds: string, + getUTCMinutes: string, getUTCMonth: string, getUTCSeconds: string, + getYear: string, global, globals, h1: object, h2: object, h3: object, + h4: object, h5: object, h6: object, handheld: boolean, hasOwnProperty: *, + head: object, header: object, height: array, hgroup: object, hr: object, + 'hta:application': object, html: *, html_confusion_a: string, + html_handlers: string, i: object, id: string, identifier: boolean, + identifier_function: string, iframe: object, img: object, immed: boolean, + implied_evil: string, in, indent: number, indexOf: *, infix_in: string, + init: function, input: object, ins: object, insecure_a: string, + isAlpha: function, isArray: function boolean, isDigit: function, + isExtensible: string, isFrozen: string, isNaN: string, + isPrototypeOf: string, isSealed: string, join: *, jslint: function boolean, + json: boolean, kbd: object, keygen: object, keys: *, label: object, + label_a_b: string, labeled: boolean, lang: string, lastIndex: string, + lastIndexOf: *, lbp: number, leading_decimal_a: string, led: function, + left: array, legend: object, length: *, 'letter-spacing': array, + li: object, lib: boolean, line: number, 'line-height': array, link: object, + 'list-style': array, 'list-style-image': array, + 'list-style-position': array, 'list-style-type': array, map: *, + margin: array, 'margin-bottom', 'margin-left', 'margin-right', + 'margin-top', mark: object, 'marker-offset': array, match: function, + 'max-height': array, 'max-width': array, maxerr: number, + maxlen: number, member: object, menu: object, message, meta: object, + meter: object, 'min-height': function, 'min-width': function, + missing_a: string, missing_a_after_b: string, missing_option: string, + missing_property: string, missing_space_a_b: string, missing_url: string, + missing_use_strict: string, mixed: string, mm: boolean, mode: string, + move_invocation: string, move_var: string, n: string, name: string, + name_function: string, nav: object, nested_comment: string, + newcap: boolean, node: boolean, noframes: object, nomen, noscript: object, + not: string, not_a_constructor: string, not_a_defined: string, + not_a_function: string, not_a_label: string, not_a_scope: string, + not_greater: string, now: string, nud: function, number: number, + object: object, ol: object, on, opacity, open: boolean, optgroup: object, + option: object, outer: regexp, outline: array, 'outline-color': array, + 'outline-style': array, 'outline-width', output: object, overflow: array, + 'overflow-x': array, 'overflow-y': array, p: object, padding: array, + 'padding-bottom': function, 'padding-left': function, + 'padding-right': function, 'padding-top': function, + 'page-break-after': array, 'page-break-before': array, param: object, + parameter_a_get_b: string, parameter_set_a: string, params: array, + paren: boolean, parent: string, parse: string, passfail, pc: boolean, + plusplus, pop: *, position: array, postscript: boolean, pre: object, + predef, preventExtensions: string, print: boolean, progress: object, + projection: boolean, properties: boolean, propertyIsEnumerable: string, + prototype: string, pt: boolean, push: *, px: boolean, q: object, quote, + quotes: array, r: string, radix: string, range: function, raw, + read_only: string, reason, redefinition_a: string, reduce: string, + reduceRight: string, regexp, replace: function, report: function, + reserved: boolean, reserved_a: string, reverse: string, rhino: boolean, + right: array, rp: object, rt: object, ruby: object, safe: boolean, + samp: object, scanned_a_b: string, screen: boolean, script: object, + seal: string, search: function, second: *, section: object, select: object, + setDate: string, setDay: string, setFullYear: string, setHours: string, + setMilliseconds: string, setMinutes: string, setMonth: string, + setSeconds: string, setTime: string, setTimezoneOffset: string, + setUTCDate: string, setUTCDay: string, setUTCFullYear: string, + setUTCHours: string, setUTCMilliseconds: string, setUTCMinutes: string, + setUTCMonth: string, setUTCSeconds: string, setYear: string, shift: *, + slash_equal: string, slice: string, sloppy, small: object, some: string, + sort: *, source: object, span: object, speech: boolean, splice: string, + split: function, src, statement_block: string, stopping: string, + strange_loop: string, strict: string, string: string, stringify: string, + strong: object, style: *, styleproperty: regexp, sub: object, + subscript: string, substr: *, substring: string, sup: object, + supplant: function, t: string, table: object, 'table-layout': array, + tag_a_in_b: string, tbody: object, td: object, test: *, + 'text-align': array, 'text-decoration': array, 'text-indent': function, + 'text-shadow': array, 'text-transform': array, textarea: object, + tfoot: object, th: object, thead: object, third: array, thru: number, + time: object, title: object, toDateString: string, toExponential: string, + toFixed: string, toISOString: string, toJSON: string, + toLocaleDateString: string, toLocaleLowerCase: string, + toLocaleString: string, toLocaleTimeString: string, + toLocaleUpperCase: string, toLowerCase: *, toPrecision: string, + toString: function, toTimeString: string, toUTCString: string, + toUpperCase: *, token: function, too_long: string, too_many: string, + top: array, tr: object, trailing_decimal_a: string, tree: string, + trim: string, tt: object, tty: boolean, tv: boolean, type: string, + type_confusion_a_b: string, u: object, ul: object, unclosed: string, + unclosed_comment: string, unclosed_regexp: string, undef: boolean, + undefined, unescaped_a: string, unexpected_a: string, + unexpected_char_a_b: string, unexpected_comment: string, + unexpected_property_a: string, unexpected_space_a_b: string, + 'unicode-bidi': array, unnecessary_initialize: string, + unnecessary_use: string, unparam, unreachable_a_b: string, + unrecognized_style_attribute_a: string, unrecognized_tag_a: string, + unsafe: string, unshift: string, unused: array, url: string, urls: array, + use_array: string, use_braces: string, use_charAt: string, + use_object: string, use_or: string, use_param: string, + used_before_a: string, valueOf: string, var: object, var_a_not: string, + vars, 'vertical-align': array, video: object, visibility: array, + warn: boolean, was: object, weird_assignment: string, + weird_condition: string, weird_new: string, weird_program: string, + weird_relation: string, weird_ternary: string, white: boolean, + 'white-space': array, widget: boolean, width: array, windows: boolean, + 'word-spacing': array, 'word-wrap': array, wrap: boolean, + wrap_immediate: string, wrap_regexp: string, write_is_wrong: string, + writeable: boolean, 'z-index': array +*/ // The global directive is used to declare global variables that can // be accessed by the program. If a declaration is true, then the variable @@ -369,25 +411,16 @@ var JSLINT = (function () { 'use strict'; + function array_to_object(array, value) { + var i, object = {}; + for (i = 0; i < array.length; i += 1) { + object[array[i]] = value; + } + return object; + } + + var adsafe_id, // The widget's ADsafe id. - adsafe_infix = { - '-': true, - '*': true, - '/': true, - '%': true, - '&': true, - '|': true, - '^': true, - '<<': true, - '>>': true, - '>>>': true - }, - adsafe_prefix = { - '-': true, - '+': true, - '~': true, - 'typeof': true - }, adsafe_may, // The widget may load approved scripts. adsafe_top, // At the top of the widget script. adsafe_went, // ADSAFE.go has been called. @@ -414,55 +447,29 @@ var JSLINT = (function () { // These are property names that should not be permitted in the safe subset. - banned = { - 'arguments' : true, - callee : true, - caller : true, - constructor : true, - 'eval' : true, - prototype : true, - stack : true, - unwatch : true, - valueOf : true, - watch : true - }, + banned = array_to_object([ + 'arguments', 'callee', 'caller', 'constructor', 'eval', 'prototype', + 'stack', 'unwatch', 'valueOf', 'watch' + ], true), begin, // The root token // browser contains a set of global names that are commonly provided by a // web browser environment. - browser = { - clearInterval : false, - clearTimeout : false, - document : false, - event : false, - frames : false, - history : false, - Image : false, - localStorage : false, - location : false, - name : false, - navigator : false, - Option : false, - parent : false, - screen : false, - sessionStorage : false, - setInterval : false, - setTimeout : false, - Storage : false, - window : false, - XMLHttpRequest : false - }, + browser = array_to_object([ + 'clearInterval', 'clearTimeout', 'document', 'event', 'frames', + 'history', 'Image', 'localStorage', 'location', 'name', 'navigator', + 'Option', 'parent', 'screen', 'sessionStorage', 'setInterval', + 'setTimeout', 'Storage', 'window', 'XMLHttpRequest' + ], false), // bundle contains the text messages. bundle = { - a_function: "'{a}' is a function.", a_label: "'{a}' is a statement label.", a_not_allowed: "'{a}' is not allowed.", a_not_defined: "'{a}' is not defined.", a_scope: "'{a}' used out of scope.", - adsafe: "ADsafe violation.", adsafe_a: "ADsafe violation: '{a}'.", adsafe_autocomplete: "ADsafe autocomplete violation.", adsafe_bad_id: "ADSAFE violation: bad id.", @@ -502,8 +509,9 @@ var JSLINT = (function () { bad_new: "Do not use 'new' for side effects.", bad_number: "Bad number '{a}'.", bad_operand: "Bad operand.", + bad_style: "Bad style.", bad_type: "Bad type.", - bad_url: "Bad url string.", + bad_url_a: "Bad url '{a}'.", bad_wrap: "Do not wrap function literals in parens unless they " + "are to be immediately invoked.", combine_var: "Combine this with the previous 'var' statement.", @@ -522,6 +530,7 @@ var JSLINT = (function () { empty_block: "Empty block.", empty_case: "Empty case.", empty_class: "Empty class.", + es5: "This is an ES5 feature.", evil: "eval is evil.", expected_a: "Expected '{a}'.", expected_a_b: "Expected '{a}' and instead saw '{b}'.", @@ -551,12 +560,13 @@ var JSLINT = (function () { expected_positive_a: "Expected a positive number and instead saw '{a}'", expected_pseudo_a: "Expected a pseudo, and instead saw :{a}.", expected_selector_a: "Expected a CSS selector, and instead saw {a}.", - expected_small_a: "Expected a small number and instead saw '{a}'", + expected_small_a: "Expected a small positive integer and instead saw '{a}'", expected_space_a_b: "Expected exactly one space between '{a}' and '{b}'.", expected_string_a: "Expected a string and instead saw {a}.", expected_style_attribute: "Excepted a style attribute, and instead saw '{a}'.", expected_style_pattern: "Expected a style pattern, and instead saw '{a}'.", expected_tagname_a: "Expected a tagName, and instead saw {a}.", + expected_type_a: "Expected a type, and instead saw {a}.", for_if: "The body of a for in should be wrapped in an if " + "statement to filter unwanted properties from the prototype.", function_block: "Function statements should not be placed in blocks. " + @@ -567,7 +577,6 @@ var JSLINT = (function () { function_statement: "Function statements are not invocable. " + "Wrap the whole function invocation in parens.", function_strict: "Use the function form of 'use strict'.", - get_set: "get/set are ES5 features.", html_confusion_a: "HTML confusion in regular expression '<{a}'.", html_handlers: "Avoid HTML event handlers.", identifier_function: "Expected an identifier in an assignment " + @@ -619,6 +628,7 @@ var JSLINT = (function () { trailing_decimal_a: "A trailing decimal point can be confused " + "with a dot: '.{a}'.", type: "type is unnecessary.", + type_confusion_a_b: "Type confusion: {a} and {b}.", unclosed: "Unclosed string.", unclosed_comment: "Unclosed comment.", unclosed_regexp: "Unclosed regular expression.", @@ -638,7 +648,9 @@ var JSLINT = (function () { url: "JavaScript URL.", use_array: "Use the array literal notation [].", use_braces: "Spaces are hard to count. Use {{a}}.", + use_charAt: "Use the charAt method.", use_object: "Use the object literal notation {}.", + use_or: "Use the || operator.", use_param: "Use a named parameter.", used_before_a: "'{a}' was used before it was defined.", var_a_not: "Variable {a} was not declared correctly.", @@ -659,176 +671,46 @@ var JSLINT = (function () { css_attribute_data, css_any, - css_colorData = { - "aliceblue" : true, - "antiquewhite" : true, - "aqua" : true, - "aquamarine" : true, - "azure" : true, - "beige" : true, - "bisque" : true, - "black" : true, - "blanchedalmond" : true, - "blue" : true, - "blueviolet" : true, - "brown" : true, - "burlywood" : true, - "cadetblue" : true, - "chartreuse" : true, - "chocolate" : true, - "coral" : true, - "cornflowerblue" : true, - "cornsilk" : true, - "crimson" : true, - "cyan" : true, - "darkblue" : true, - "darkcyan" : true, - "darkgoldenrod" : true, - "darkgray" : true, - "darkgreen" : true, - "darkkhaki" : true, - "darkmagenta" : true, - "darkolivegreen" : true, - "darkorange" : true, - "darkorchid" : true, - "darkred" : true, - "darksalmon" : true, - "darkseagreen" : true, - "darkslateblue" : true, - "darkslategray" : true, - "darkturquoise" : true, - "darkviolet" : true, - "deeppink" : true, - "deepskyblue" : true, - "dimgray" : true, - "dodgerblue" : true, - "firebrick" : true, - "floralwhite" : true, - "forestgreen" : true, - "fuchsia" : true, - "gainsboro" : true, - "ghostwhite" : true, - "gold" : true, - "goldenrod" : true, - "gray" : true, - "green" : true, - "greenyellow" : true, - "honeydew" : true, - "hotpink" : true, - "indianred" : true, - "indigo" : true, - "ivory" : true, - "khaki" : true, - "lavender" : true, - "lavenderblush" : true, - "lawngreen" : true, - "lemonchiffon" : true, - "lightblue" : true, - "lightcoral" : true, - "lightcyan" : true, - "lightgoldenrodyellow" : true, - "lightgreen" : true, - "lightpink" : true, - "lightsalmon" : true, - "lightseagreen" : true, - "lightskyblue" : true, - "lightslategray" : true, - "lightsteelblue" : true, - "lightyellow" : true, - "lime" : true, - "limegreen" : true, - "linen" : true, - "magenta" : true, - "maroon" : true, - "mediumaquamarine" : true, - "mediumblue" : true, - "mediumorchid" : true, - "mediumpurple" : true, - "mediumseagreen" : true, - "mediumslateblue" : true, - "mediumspringgreen" : true, - "mediumturquoise" : true, - "mediumvioletred" : true, - "midnightblue" : true, - "mintcream" : true, - "mistyrose" : true, - "moccasin" : true, - "navajowhite" : true, - "navy" : true, - "oldlace" : true, - "olive" : true, - "olivedrab" : true, - "orange" : true, - "orangered" : true, - "orchid" : true, - "palegoldenrod" : true, - "palegreen" : true, - "paleturquoise" : true, - "palevioletred" : true, - "papayawhip" : true, - "peachpuff" : true, - "peru" : true, - "pink" : true, - "plum" : true, - "powderblue" : true, - "purple" : true, - "red" : true, - "rosybrown" : true, - "royalblue" : true, - "saddlebrown" : true, - "salmon" : true, - "sandybrown" : true, - "seagreen" : true, - "seashell" : true, - "sienna" : true, - "silver" : true, - "skyblue" : true, - "slateblue" : true, - "slategray" : true, - "snow" : true, - "springgreen" : true, - "steelblue" : true, - "tan" : true, - "teal" : true, - "thistle" : true, - "tomato" : true, - "turquoise" : true, - "violet" : true, - "wheat" : true, - "white" : true, - "whitesmoke" : true, - "yellow" : true, - "yellowgreen" : true, - - "activeborder" : true, - "activecaption" : true, - "appworkspace" : true, - "background" : true, - "buttonface" : true, - "buttonhighlight" : true, - "buttonshadow" : true, - "buttontext" : true, - "captiontext" : true, - "graytext" : true, - "highlight" : true, - "highlighttext" : true, - "inactiveborder" : true, - "inactivecaption" : true, - "inactivecaptiontext" : true, - "infobackground" : true, - "infotext" : true, - "menu" : true, - "menutext" : true, - "scrollbar" : true, - "threeddarkshadow" : true, - "threedface" : true, - "threedhighlight" : true, - "threedlightshadow" : true, - "threedshadow" : true, - "window" : true, - "windowframe" : true, - "windowtext" : true - }, + css_colorData = array_to_object([ + "aliceblue", "antiquewhite", "aqua", "aquamarine", "azure", "beige", + "bisque", "black", "blanchedalmond", "blue", "blueviolet", "brown", + "burlywood", "cadetblue", "chartreuse", "chocolate", "coral", + "cornflowerblue", "cornsilk", "crimson", "cyan", "darkblue", + "darkcyan", "darkgoldenrod", "darkgray", "darkgreen", "darkkhaki", + "darkmagenta", "darkolivegreen", "darkorange", "darkorchid", + "darkred", "darksalmon", "darkseagreen", "darkslateblue", + "darkslategray", "darkturquoise", "darkviolet", "deeppink", + "deepskyblue", "dimgray", "dodgerblue", "firebrick", "floralwhite", + "forestgreen", "fuchsia", "gainsboro", "ghostwhite", "gold", + "goldenrod", "gray", "green", "greenyellow", "honeydew", "hotpink", + "indianred", "indigo", "ivory", "khaki", "lavender", + "lavenderblush", "lawngreen", "lemonchiffon", "lightblue", + "lightcoral", "lightcyan", "lightgoldenrodyellow", "lightgreen", + "lightpink", "lightsalmon", "lightseagreen", "lightskyblue", + "lightslategray", "lightsteelblue", "lightyellow", "lime", + "limegreen", "linen", "magenta", "maroon", "mediumaquamarine", + "mediumblue", "mediumorchid", "mediumpurple", "mediumseagreen", + "mediumslateblue", "mediumspringgreen", "mediumturquoise", + "mediumvioletred", "midnightblue", "mintcream", "mistyrose", + "moccasin", "navajowhite", "navy", "oldlace", "olive", "olivedrab", + "orange", "orangered", "orchid", "palegoldenrod", "palegreen", + "paleturquoise", "palevioletred", "papayawhip", "peachpuff", + "peru", "pink", "plum", "powderblue", "purple", "red", "rosybrown", + "royalblue", "saddlebrown", "salmon", "sandybrown", "seagreen", + "seashell", "sienna", "silver", "skyblue", "slateblue", "slategray", + "snow", "springgreen", "steelblue", "tan", "teal", "thistle", + "tomato", "turquoise", "violet", "wheat", "white", "whitesmoke", + "yellow", "yellowgreen", + + "activeborder", "activecaption", "appworkspace", "background", + "buttonface", "buttonhighlight", "buttonshadow", "buttontext", + "captiontext", "graytext", "highlight", "highlighttext", + "inactiveborder", "inactivecaption", "inactivecaptiontext", + "infobackground", "infotext", "menu", "menutext", "scrollbar", + "threeddarkshadow", "threedface", "threedhighlight", + "threedlightshadow", "threedshadow", "window", "windowframe", + "windowtext" + ], true), css_border_style, css_break, @@ -848,34 +730,46 @@ var JSLINT = (function () { css_media, css_overflow, - devel = { - alert : false, - confirm : false, - console : false, - Debug : false, - opera : false, - prompt : false + descapes = { + 'b': '\b', + 't': '\t', + 'n': '\n', + 'f': '\f', + 'r': '\r', + '"': '"', + '/': '/', + '\\': '\\' }, + devel = array_to_object([ + 'alert', 'confirm', 'console', 'Debug', 'opera', 'prompt', 'WSH' + ], false), + directive, escapes = { '\b': '\\b', '\t': '\\t', '\n': '\\n', '\f': '\\f', '\r': '\\r', + '\'': '\\\'', '"' : '\\"', '/' : '\\/', '\\': '\\\\' }, - funct, // The current function + funct, // The current function, including the labels used in + // the function, as well as (breakage), (complexity), + // (context), (loopage), (name), (params), (token), + // (vars), (verb) functionicity = [ - 'closure', 'exception', 'global', 'label', 'outer', 'unused', 'var' + 'closure', 'exception', 'global', 'label', 'outer', 'undef', + 'unused', 'var' ], functions, // All of the functions - global, // The global scope + global_funct, // The global body + global_scope, // The global scope html_tag = { a: {}, abbr: {}, @@ -995,128 +889,191 @@ var JSLINT = (function () { }, ids, // HTML ids - implied, // Implied globals in_block, indent, + infer_statement,// Inference rules for statements + is_type = array_to_object([ + '*', 'array', 'boolean', 'function', 'number', 'object', + 'regexp', 'string' + ], true), + itself, // JSLint itself + jslint_limit = { + indent: 10, + maxerr: 1000, + maxlen: 256 + }, json_mode, + lex, // the tokenizer lines, lookahead, member, - node = { - Buffer : false, - clearInterval: false, - clearTimeout : false, - console : false, - global : false, - module : false, - process : false, - querystring : false, - require : false, - setInterval : false, - setTimeout : false, - util : false, - __filename : false, - __dirname : false - }, - numbery = { - indexOf : true, - lastIndexOf : true, - search : true - }, - properties, + node = array_to_object([ + 'Buffer', 'clearInterval', 'clearTimeout', 'console', 'exports', + 'global', 'module', 'process', 'querystring', 'require', + 'setInterval', 'setTimeout', '__dirname', '__filename' + ], false), + node_js, + numbery = array_to_object(['indexOf', 'lastIndexOf', 'search'], true), next_token, - older_token, option, predefined, // Global variables defined by option prereg, prev_token, - regexp_flag = { - g: true, - i: true, - m: true - }, - rhino = { - defineClass : false, - deserialize : false, - gc : false, - help : false, - load : false, - loadClass : false, - print : false, - quit : false, - readFile : false, - readUrl : false, - runCommand : false, - seal : false, - serialize : false, - spawn : false, - sync : false, - toint32 : false, - version : false - }, - - scope, // The current scope - semicolon_coda = { - ';' : true, - '"' : true, - '\'': true, - ')' : true + property_type, + regexp_flag = array_to_object(['g', 'i', 'm'], true), + return_this = function return_this() { + return this; }, + rhino = array_to_object([ + 'defineClass', 'deserialize', 'gc', 'help', 'load', 'loadClass', + 'print', 'quit', 'readFile', 'readUrl', 'runCommand', 'seal', + 'serialize', 'spawn', 'sync', 'toint32', 'version' + ], false), + + scope, // An object containing an object for each variable in scope + semicolon_coda = array_to_object([';', '"', '\'', ')'], true), src, stack, // standard contains the global names that are provided by the // ECMAScript standard. - standard = { - Array : false, - Boolean : false, - Date : false, - decodeURI : false, - decodeURIComponent : false, - encodeURI : false, - encodeURIComponent : false, - Error : false, - 'eval' : false, - EvalError : false, - Function : false, - hasOwnProperty : false, - isFinite : false, - isNaN : false, - JSON : false, - Math : false, - Number : false, - Object : false, - parseInt : false, - parseFloat : false, - RangeError : false, - ReferenceError : false, - RegExp : false, - String : false, - SyntaxError : false, - TypeError : false, - URIError : false - }, - - standard_property = { - E : true, - LN2 : true, - LN10 : true, - LOG2E : true, - LOG10E : true, - MAX_VALUE : true, - MIN_VALUE : true, - NEGATIVE_INFINITY : true, - PI : true, - POSITIVE_INFINITY : true, - SQRT1_2 : true, - SQRT2 : true + standard = array_to_object([ + 'Array', 'Boolean', 'Date', 'decodeURI', 'decodeURIComponent', + 'encodeURI', 'encodeURIComponent', 'Error', 'eval', 'EvalError', + 'Function', 'isFinite', 'isNaN', 'JSON', 'Math', 'Number', 'Object', + 'parseInt', 'parseFloat', 'RangeError', 'ReferenceError', 'RegExp', + 'String', 'SyntaxError', 'TypeError', 'URIError' + ], false), + + standard_property_type = { + E : 'number', + LN2 : 'number', + LN10 : 'number', + LOG2E : 'number', + LOG10E : 'number', + MAX_VALUE : 'number', + MIN_VALUE : 'number', + NEGATIVE_INFINITY : 'number', + PI : 'number', + POSITIVE_INFINITY : 'number', + SQRT1_2 : 'number', + SQRT2 : 'number', + apply : 'function', + bind : 'function function', + call : 'function', + ceil : 'function number', + charAt : 'function string', + concat : 'function', + constructor : 'function object', + create : 'function object', + defineProperty : 'function object', + defineProperties : 'function object', + every : 'function boolean', + exec : 'function array', + filter : 'function array', + floor : 'function number', + forEach : 'function', + freeze : 'function object', + getDate : 'function number', + getDay : 'function number', + getFullYear : 'function number', + getHours : 'function number', + getMilliseconds : 'function number', + getMinutes : 'function number', + getMonth : 'function number', + getOwnPropertyDescriptor + : 'function object', + getOwnPropertyNames : 'function array', + getPrototypeOf : 'function object', + getSeconds : 'function number', + getTime : 'function number', + getTimezoneOffset : 'function number', + getUTCDate : 'function number', + getUTCDay : 'function number', + getUTCFullYear : 'function number', + getUTCHours : 'function number', + getUTCMilliseconds : 'function number', + getUTCMinutes : 'function number', + getUTCMonth : 'function number', + getUTCSeconds : 'function number', + getYear : 'function number', + hasOwnProperty : 'function boolean', + indexOf : 'function number', + isExtensible : 'function boolean', + isFrozen : 'function boolean', + isPrototypeOf : 'function boolean', + isSealed : 'function boolean', + join : 'function string', + keys : 'function array', + lastIndexOf : 'function number', + lastIndex : 'number', + length : 'number', + map : 'function array', + now : 'function number', + parse : 'function', + pop : 'function', + preventExtensions : 'function object', + propertyIsEnumerable: 'function boolean', + prototype : 'object', + push : 'function number', + reduce : 'function', + reduceRight : 'function', + reverse : 'function', + seal : 'function object', + setDate : 'function', + setDay : 'function', + setFullYear : 'function', + setHours : 'function', + setMilliseconds : 'function', + setMinutes : 'function', + setMonth : 'function', + setSeconds : 'function', + setTime : 'function', + setTimezoneOffset : 'function', + setUTCDate : 'function', + setUTCDay : 'function', + setUTCFullYear : 'function', + setUTCHours : 'function', + setUTCMilliseconds : 'function', + setUTCMinutes : 'function', + setUTCMonth : 'function', + setUTCSeconds : 'function', + setYear : 'function', + shift : 'function', + slice : 'function', + some : 'function boolean', + sort : 'function', + splice : 'function', + stringify : 'function string', + substr : 'function string', + substring : 'function string', + test : 'function boolean', + toDateString : 'function string', + toExponential : 'function string', + toFixed : 'function string', + toJSON : 'function', + toISOString : 'function string', + toLocaleDateString : 'function string', + toLocaleLowerCase : 'function string', + toLocaleUpperCase : 'function string', + toLocaleString : 'function string', + toLocaleTimeString : 'function string', + toLowerCase : 'function string', + toPrecision : 'function string', + toTimeString : 'function string', + toUpperCase : 'function string', + toUTCString : 'function string', + trim : 'function string', + unshift : 'function number', + valueOf : 'function' }, strict_mode, syntax = {}, tab, token, + type_state_change, urls, var_mode, warnings, @@ -1124,102 +1081,39 @@ var JSLINT = (function () { // widget contains the global names which are provided to a Yahoo // (fna Konfabulator) widget. - widget = { - alert : true, - animator : true, - appleScript : true, - beep : true, - bytesToUIString : true, - Canvas : true, - chooseColor : true, - chooseFile : true, - chooseFolder : true, - closeWidget : true, - COM : true, - convertPathToHFS : true, - convertPathToPlatform : true, - CustomAnimation : true, - escape : true, - FadeAnimation : true, - filesystem : true, - Flash : true, - focusWidget : true, - form : true, - FormField : true, - Frame : true, - HotKey : true, - Image : true, - include : true, - isApplicationRunning : true, - iTunes : true, - konfabulatorVersion : true, - log : true, - md5 : true, - MenuItem : true, - MoveAnimation : true, - openURL : true, - play : true, - Point : true, - popupMenu : true, - preferenceGroups : true, - preferences : true, - print : true, - prompt : true, - random : true, - Rectangle : true, - reloadWidget : true, - ResizeAnimation : true, - resolvePath : true, - resumeUpdates : true, - RotateAnimation : true, - runCommand : true, - runCommandInBg : true, - saveAs : true, - savePreferences : true, - screen : true, - ScrollBar : true, - showWidgetPreferences : true, - sleep : true, - speak : true, - Style : true, - suppressUpdates : true, - system : true, - tellWidget : true, - Text : true, - TextArea : true, - Timer : true, - unescape : true, - updateNow : true, - URL : true, - Web : true, - widget : true, - Window : true, - XMLDOM : true, - XMLHttpRequest : true, - yahooCheckLogin : true, - yahooLogin : true, - yahooLogout : true - }, - - windows = { - ActiveXObject: false, - CScript : false, - Debug : false, - Enumerator : false, - System : false, - VBArray : false, - WScript : false - }, + widget = array_to_object([ + 'alert', 'animator', 'appleScript', 'beep', 'bytesToUIString', + 'Canvas', 'chooseColor', 'chooseFile', 'chooseFolder', + 'closeWidget', 'COM', 'convertPathToHFS', 'convertPathToPlatform', + 'CustomAnimation', 'escape', 'FadeAnimation', 'filesystem', 'Flash', + 'focusWidget', 'form', 'FormField', 'Frame', 'HotKey', 'Image', + 'include', 'isApplicationRunning', 'iTunes', 'konfabulatorVersion', + 'log', 'md5', 'MenuItem', 'MoveAnimation', 'openURL', 'play', + 'Point', 'popupMenu', 'preferenceGroups', 'preferences', 'print', + 'prompt', 'random', 'Rectangle', 'reloadWidget', 'ResizeAnimation', + 'resolvePath', 'resumeUpdates', 'RotateAnimation', 'runCommand', + 'runCommandInBg', 'saveAs', 'savePreferences', 'screen', + 'ScrollBar', 'showWidgetPreferences', 'sleep', 'speak', 'Style', + 'suppressUpdates', 'system', 'tellWidget', 'Text', 'TextArea', + 'Timer', 'unescape', 'updateNow', 'URL', 'Web', 'widget', 'Window', + 'XMLDOM', 'XMLHttpRequest', 'yahooCheckLogin', 'yahooLogin', + 'yahooLogout' + ], true), + + windows = array_to_object([ + 'ActiveXObject', 'CScript', 'Debug', 'Enumerator', 'System', + 'VBArray', 'WScript', 'WSH' + ], false), // xmode is used to adapt to the exceptions in html parsing. // It can have these states: -// false .js script file -// html -// outer -// script -// style -// scriptstring -// styleproperty +// '' .js script file +// 'html' +// 'outer' +// 'script' +// 'style' +// 'scriptstring' +// 'styleproperty' xmode, xquote, @@ -1228,6 +1122,9 @@ var JSLINT = (function () { // unsafe comment or string ax = /@cc|<\/?|script|\]\s*\]|<\s*!|</i, +// carriage return, or carriage return linefeed + crx = /\r/g, + crlfx = /\r\n/g, // unsafe characters that are silently deleted by one or more browsers cx = /[\u0000-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/, // query characters for ids @@ -1241,18 +1138,18 @@ var JSLINT = (function () { // star slash lx = /\*\/|\/\*/, // characters in strings that need escapement - nx = /[\u0000-\u001f"\\\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, + nx = /[\u0000-\u001f'\\\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, // outer html token ox = /[>&]|<[\/!]?|--/, // attributes characters qx = /[^a-zA-Z0-9+\-_\/ ]/, // style - sx = /^\s*([{:#%.=,>+\[\]@()"';]|\*=?|\$=|\|=|\^=|~=|[a-zA-Z_][a-zA-Z0-9_\-]*|[0-9]+|<\/|\/\*)/, + sx = /^\s*([{}:#%.=,>+\[\]@()"';]|\*=?|\$=|\|=|\^=|~=|[a-zA-Z_][a-zA-Z0-9_\-]*|[0-9]+|<\/|\/\*)/, ssx = /^\s*([@#!"'};:\-%.=,+\[\]()*_]|[a-zA-Z][a-zA-Z0-9._\-]*|\/\*?|\d+(?:\.\d+)?|<\/)/, // token tx = /^\s*([(){}\[.,:;'"~\?\]#@]|==?=?|\/(\*(jslint|properties|property|members?|globals?)?|=|\/)?|\*[\/=]?|\+(?:=|\++)?|-(?:=|-+)?|%=?|&[&=]?|\|[|=]?|>>?>?=?|<([\/=!]|\!(\[|--)?|<=?)?|\^=?|\!=?=?|[a-zA-Z_$][a-zA-Z0-9_$]*|[0-9]+([xX][0-9a-fA-F]+|\.[0-9]*)?([eE][+\-]?[0-9]+)?)/, // url badness - ux = /&|\+|\u00AD|\.\.|\/\*|%[^;]|base64|url|expression|data|mailto/i, + ux = /&|\+|\u00AD|\.\.|\/\*|%[^;]|base64|url|expression|data|mailto|script/i, rx = { outer: hx, @@ -1262,20 +1159,19 @@ var JSLINT = (function () { }; - function return_this() { - return this; - } - function F() {} // Used by Object.create // Provide critical ES5 functions to ES3. if (typeof Array.prototype.filter !== 'function') { Array.prototype.filter = function (f) { - var i, length = this.length, result = []; + var i, length = this.length, result = [], value; for (i = 0; i < length; i += 1) { try { - result.push(f(this[i])); + value = this[i]; + if (f(value)) { + result.push(value); + } } catch (ignore) { } } @@ -1283,13 +1179,25 @@ var JSLINT = (function () { }; } + if (typeof Array.prototype.forEach !== 'function') { + Array.prototype.forEach = function (f) { + var i, length = this.length; + for (i = 0; i < length; i += 1) { + try { + f(this[i]); + } catch (ignore) { + } + } + }; + } + if (typeof Array.isArray !== 'function') { Array.isArray = function (o) { return Object.prototype.toString.apply(o) === '[object Array]'; }; } - if (!Object.hasOwnProperty('create')) { + if (!Object.prototype.hasOwnProperty.call(Object, 'create')) { Object.create = function (o) { F.prototype = o; return new F(); @@ -1308,8 +1216,6 @@ var JSLINT = (function () { }; } -// Substandard methods - if (typeof String.prototype.entityify !== 'function') { String.prototype.entityify = function () { return this @@ -1347,46 +1253,61 @@ var JSLINT = (function () { // Escapify a troublesome character. - return escapes[a] ? escapes[a] : + return escapes[a] || '\\u' + ('0000' + a.charCodeAt().toString(16)).slice(-4); } - function combine(a, b) { - var name; - for (name in b) { - if (Object.prototype.hasOwnProperty.call(b, name)) { - a[name] = b[name]; - } - } + function add_to_predefined(group) { + Object.keys(group).forEach(function (name) { + predefined[name] = group[name]; + }); } + function assume() { if (!option.safe) { if (option.rhino) { - combine(predefined, rhino); + add_to_predefined(rhino); + option.rhino = false; } if (option.devel) { - combine(predefined, devel); + add_to_predefined(devel); + option.devel = false; } if (option.browser) { - combine(predefined, browser); + add_to_predefined(browser); + option.browser = false; } if (option.windows) { - combine(predefined, windows); + add_to_predefined(windows); + option.windows = false; } if (option.node) { - combine(predefined, node); + add_to_predefined(node); + option.node = false; + node_js = true; } if (option.widget) { - combine(predefined, widget); + add_to_predefined(widget); + option.widget = false; } } + if (option.type) { + option.confusion = true; + } } // Produce an error warning. + function artifact(tok) { + if (!tok) { + tok = next_token; + } + return tok.number || tok.string; + } + function quit(message, line, character) { throw { name: 'JSLintError', @@ -1410,7 +1331,8 @@ var JSLINT = (function () { evidence: lines[line - 1] || '', line: line, character: character, - a: a || offender.value, + a: a || (offender.id === '(number)' ? + String(offender.number) : offender.string), b: b, c: c, d: d @@ -1447,8 +1369,8 @@ var JSLINT = (function () { } function expected_at(at) { - if (option.white && next_token.from !== at) { - warn('expected_a_at_b_c', next_token, next_token.value, at, + if (!option.white && next_token.from !== at) { + warn('expected_a_at_b_c', next_token, '', at, next_token.from); } } @@ -1465,34 +1387,11 @@ var JSLINT = (function () { // lexical analysis and token construction - var lex = (function lex() { - var character, from, line, source_row; + lex = (function lex() { + var character, c, from, length, line, pos, source_row; // Private lex methods - function collect_comment(comment, quote, line, at) { - var comment_object = { - comment: comment, - quote: quote, - at: at, - line: line - }; - if (comments_off || src || (xmode && xmode !== 'script' && - xmode !== 'style' && xmode !== 'styleproperty')) { - warn_at('unexpected_comment', line, character); - } else if (xmode === 'script' && /<\//i.test(source_row)) { - warn_at('unexpected_a', line, character, '<\/'); - } else if (option.safe && ax.test(comment)) { - warn_at('dangerous_comment', line, at); - } - if (older_token.comments) { - older_token.comments.push(comment_object); - } else { - older_token.comments = [comment_object]; - } - JSLINT.comments.push(comment_object); - } - function next_line() { var at; if (line >= lines.length) { @@ -1536,14 +1435,16 @@ var JSLINT = (function () { the_token.identifier = true; if (value === '__iterator__' || value === '__proto__') { stop_at('reserved_a', line, from, value); - } else if (option.nomen && + } else if (!option.nomen && (value.charAt(0) === '_' || value.charAt(value.length - 1) === '_')) { warn_at('dangling_a', line, from, value); } } - if (value !== undefined) { - the_token.value = value; + if (type === '(number)') { + the_token.number = +value; + } else if (value !== undefined) { + the_token.string = String(value); } if (quote) { the_token.quote = quote; @@ -1551,25 +1452,489 @@ var JSLINT = (function () { the_token.line = line; the_token.from = from; the_token.thru = character; - the_token.prev = older_token; id = the_token.id; prereg = id && ( ('(,=:[!&|?{};'.indexOf(id.charAt(id.length - 1)) >= 0) || id === 'return' ); - older_token.next = the_token; - older_token = the_token; return the_token; } + function match(x) { + var exec = x.exec(source_row), first; + if (exec) { + length = exec[0].length; + first = exec[1]; + c = first.charAt(0); + source_row = source_row.slice(length); + from = character + length - first.length; + character += length; + return first; + } + } + + function string(x) { + var c, pos = 0, r = ''; + + function hex(n) { + var i = parseInt(source_row.substr(pos + 1, n), 16); + pos += n; + if (i >= 32 && i <= 126 && + i !== 34 && i !== 92 && i !== 39) { + warn_at('unexpected_a', line, character, '\\'); + } + character += n; + c = String.fromCharCode(i); + } + + if (json_mode && x !== '"') { + warn_at('expected_a', line, character, '"'); + } + + if (xquote === x || (xmode === 'scriptstring' && !xquote)) { + return it('(punctuator)', x); + } + + for (;;) { + while (pos >= source_row.length) { + pos = 0; + if (xmode !== 'html' || !next_line()) { + stop_at('unclosed', line, from); + } + } + c = source_row.charAt(pos); + if (c === x) { + character += 1; + source_row = source_row.slice(pos + 1); + return it('(string)', r, x); + } + if (c < ' ') { + if (c === '\n' || c === '\r') { + break; + } + warn_at('control_a', + line, character + pos, source_row.slice(0, pos)); + } else if (c === xquote) { + warn_at('bad_html', line, character + pos); + } else if (c === '<') { + if (option.safe && xmode === 'html') { + warn_at('adsafe_a', line, character + pos, c); + } else if (source_row.charAt(pos + 1) === '/' && (xmode || option.safe)) { + warn_at('expected_a_b', line, character, + '<\\/', ' 0) { + warn_at('unescaped_a', + line, from + length, '/'); + } + c = source_row.slice(0, length - 1); + flag = Object.create(regexp_flag); + while (flag[source_row.charAt(length)] === true) { + flag[source_row.charAt(length)] = false; + length += 1; + } + if (source_row.charAt(length).isAlpha()) { + stop_at('unexpected_a', + line, from, source_row.charAt(length)); + } + character += length; + source_row = source_row.slice(length); + quote = source_row.charAt(0); + if (quote === '/' || quote === '*') { + stop_at('confusing_regexp', + line, from); + } + return it('(regexp)', c); + case '\\': + c = source_row.charAt(length); + if (c < ' ') { + warn_at('control_a', + line, from + length, String(c)); + } else if (c === '<') { + warn_at( + bundle.unexpected_a, + line, + from + length, + '\\' + ); + } + length += 1; + break; + case '(': + depth += 1; + b = false; + if (source_row.charAt(length) === '?') { + length += 1; + switch (source_row.charAt(length)) { + case ':': + case '=': + case '!': + length += 1; + break; + default: + warn_at( + bundle.expected_a_b, + line, + from + length, + ':', + source_row.charAt(length) + ); + } + } else { + captures += 1; + } + break; + case '|': + b = false; + break; + case ')': + if (depth === 0) { + warn_at('unescaped_a', + line, from + length, ')'); + } else { + depth -= 1; + } + break; + case ' ': + pos = 1; + while (source_row.charAt(length) === ' ') { + length += 1; + pos += 1; + } + if (pos > 1) { + warn_at('use_braces', + line, from + length, pos); + } + break; + case '[': + c = source_row.charAt(length); + if (c === '^') { + length += 1; + if (!option.regexp) { + warn_at('insecure_a', + line, from + length, c); + } else if (source_row.charAt(length) === ']') { + stop_at('unescaped_a', + line, from + length, '^'); + } + } + bit = false; + if (c === ']') { + warn_at('empty_class', line, + from + length - 1); + bit = true; + } +klass: do { + c = source_row.charAt(length); + length += 1; + switch (c) { + case '[': + case '^': + warn_at('unescaped_a', + line, from + length, c); + bit = true; + break; + case '-': + if (bit) { + bit = false; + } else { + warn_at('unescaped_a', + line, from + length, '-'); + bit = true; + } + break; + case ']': + if (!bit) { + warn_at('unescaped_a', + line, from + length - 1, '-'); + } + break klass; + case '\\': + c = source_row.charAt(length); + if (c < ' ') { + warn_at( + bundle.control_a, + line, + from + length, + String(c) + ); + } else if (c === '<') { + warn_at( + bundle.unexpected_a, + line, + from + length, + '\\' + ); + } + length += 1; + bit = true; + break; + case '/': + warn_at('unescaped_a', + line, from + length - 1, '/'); + bit = true; + break; + case '<': + if (xmode === 'script') { + c = source_row.charAt(length); + if (c === '!' || c === '/') { + warn_at( + bundle.html_confusion_a, + line, + from + length, + c + ); + } + } + bit = true; + break; + default: + bit = true; + } + } while (c); + break; + case '.': + if (!option.regexp) { + warn_at('insecure_a', line, + from + length, c); + } + break; + case ']': + case '?': + case '{': + case '}': + case '+': + case '*': + warn_at('unescaped_a', line, + from + length, c); + break; + case '<': + if (xmode === 'script') { + c = source_row.charAt(length); + if (c === '!' || c === '/') { + warn_at( + bundle.html_confusion_a, + line, + from + length, + c + ); + } + } + break; + } + if (b) { + switch (source_row.charAt(length)) { + case '?': + case '+': + case '*': + length += 1; + if (source_row.charAt(length) === '?') { + length += 1; + } + break; + case '{': + length += 1; + c = source_row.charAt(length); + if (c < '0' || c > '9') { + warn_at( + bundle.expected_number_a, + line, + from + length, + c + ); + } + length += 1; + low = +c; + for (;;) { + c = source_row.charAt(length); + if (c < '0' || c > '9') { + break; + } + length += 1; + low = +c + (low * 10); + } + high = low; + if (c === ',') { + length += 1; + high = Infinity; + c = source_row.charAt(length); + if (c >= '0' && c <= '9') { + length += 1; + high = +c; + for (;;) { + c = source_row.charAt(length); + if (c < '0' || c > '9') { + break; + } + length += 1; + high = +c + (high * 10); + } + } + } + if (source_row.charAt(length) !== '}') { + warn_at( + bundle.expected_a_b, + line, + from + length, + '}', + c + ); + } else { + length += 1; + } + if (source_row.charAt(length) === '?') { + length += 1; + } + if (low > high) { + warn_at( + bundle.not_greater, + line, + from + length, + low, + high + ); + } + break; + } + } + } + c = source_row.slice(0, length - 1); + character += length; + source_row = source_row.slice(length); + return it('(regexp)', c); + } + // Public lex methods return { init: function (source) { if (typeof source === 'string') { lines = source - .replace(/\r\n/g, '\n') - .replace(/\r/g, '\n') + .replace(crlfx, '\n') + .replace(crx, '\n') .split('\n'); } else { lines = source; @@ -1610,143 +1975,7 @@ var JSLINT = (function () { // token -- this is called by advance to get the next token. token: function () { - var b, c, captures, digit, depth, flag, high, i, j, length, low, quote, symbol; - - function match(x) { - var exec = x.exec(source_row), first; - if (exec) { - length = exec[0].length; - first = exec[1]; - c = first.charAt(0); - source_row = source_row.substr(length); - from = character + length - first.length; - character += length; - return first; - } - } - - function string(x) { - var c, j, r = ''; - - function hex(n) { - var i = parseInt(source_row.substr(j + 1, n), 16); - j += n; - if (i >= 32 && i <= 126 && - i !== 34 && i !== 92 && i !== 39) { - warn_at('unexpected_a', line, character, '\\'); - } - character += n; - c = String.fromCharCode(i); - } - - if (json_mode && x !== '"') { - warn_at('expected_a', line, character, '"'); - } - - if (xquote === x || (xmode === 'scriptstring' && !xquote)) { - return it('(punctuator)', x); - } - - j = 0; - for (;;) { - while (j >= source_row.length) { - j = 0; - if (xmode !== 'html' || !next_line()) { - stop_at('unclosed', line, from); - } - } - c = source_row.charAt(j); - if (c === x) { - character += 1; - source_row = source_row.substr(j + 1); - return it('(string)', r, x); - } - if (c < ' ') { - if (c === '\n' || c === '\r') { - break; - } - warn_at('control_a', - line, character + j, source_row.slice(0, j)); - } else if (c === xquote) { - warn_at('bad_html', line, character + j); - } else if (c === '<') { - if (option.safe && xmode === 'html') { - warn_at('adsafe_a', line, character + j, c); - } else if (source_row.charAt(j + 1) === '/' && (xmode || option.safe)) { - warn_at('expected_a_b', line, character, - '<\\/', '= 0) { break; } - collect_comment(source_row, quote, line, character); - quote = ''; + comment(source_row); if (!next_line()) { stop_at('unclosed_comment', line, character); } } - collect_comment(source_row.slice(0, i), quote, character, line); + comment(source_row.slice(0, i)); character += i + 2; - if (source_row.substr(i, 1) === '/') { + if (source_row.charAt(i) === '/') { stop_at('nested_comment', line, character); } - source_row = source_row.substr(i + 2); + source_row = source_row.slice(i + 2); break; case '': @@ -1873,306 +2073,13 @@ var JSLINT = (function () { from ); } - if (prereg) { - depth = 0; - captures = 0; - length = 0; - for (;;) { - b = true; - c = source_row.charAt(length); - length += 1; - switch (c) { - case '': - stop_at('unclosed_regexp', line, from); - return; - case '/': - if (depth > 0) { - warn_at('unescaped_a', - line, from + length, '/'); - } - c = source_row.substr(0, length - 1); - flag = Object.create(regexp_flag); - while (flag[source_row.charAt(length)] === true) { - flag[source_row.charAt(length)] = false; - length += 1; - } - if (source_row.charAt(length).isAlpha()) { - stop_at('unexpected_a', - line, from, source_row.charAt(length)); - } - character += length; - source_row = source_row.substr(length); - quote = source_row.charAt(0); - if (quote === '/' || quote === '*') { - stop_at('confusing_regexp', - line, from); - } - return it('(regexp)', c); - case '\\': - c = source_row.charAt(length); - if (c < ' ') { - warn_at('control_a', - line, from + length, String(c)); - } else if (c === '<') { - warn_at( - bundle.unexpected_a, - line, - from + length, - '\\' - ); - } - length += 1; - break; - case '(': - depth += 1; - b = false; - if (source_row.charAt(length) === '?') { - length += 1; - switch (source_row.charAt(length)) { - case ':': - case '=': - case '!': - length += 1; - break; - default: - warn_at( - bundle.expected_a_b, - line, - from + length, - ':', - source_row.charAt(length) - ); - } - } else { - captures += 1; - } - break; - case '|': - b = false; - break; - case ')': - if (depth === 0) { - warn_at('unescaped_a', - line, from + length, ')'); - } else { - depth -= 1; - } - break; - case ' ': - j = 1; - while (source_row.charAt(length) === ' ') { - length += 1; - j += 1; - } - if (j > 1) { - warn_at('use_braces', - line, from + length, j); - } - break; - case '[': - c = source_row.charAt(length); - if (c === '^') { - length += 1; - if (option.regexp) { - warn_at('insecure_a', - line, from + length, c); - } else if (source_row.charAt(length) === ']') { - stop_at('unescaped_a', - line, from + length, '^'); - } - } - quote = false; - if (c === ']') { - warn_at('empty_class', line, - from + length - 1); - quote = true; - } -klass: do { - c = source_row.charAt(length); - length += 1; - switch (c) { - case '[': - case '^': - warn_at('unescaped_a', - line, from + length, c); - quote = true; - break; - case '-': - if (quote) { - quote = false; - } else { - warn_at('unescaped_a', - line, from + length, '-'); - quote = true; - } - break; - case ']': - if (!quote) { - warn_at('unescaped_a', - line, from + length - 1, '-'); - } - break klass; - case '\\': - c = source_row.charAt(length); - if (c < ' ') { - warn_at( - bundle.control_a, - line, - from + length, - String(c) - ); - } else if (c === '<') { - warn_at( - bundle.unexpected_a, - line, - from + length, - '\\' - ); - } - length += 1; - quote = true; - break; - case '/': - warn_at('unescaped_a', - line, from + length - 1, '/'); - quote = true; - break; - case '<': - if (xmode === 'script') { - c = source_row.charAt(length); - if (c === '!' || c === '/') { - warn_at( - bundle.html_confusion_a, - line, - from + length, - c - ); - } - } - quote = true; - break; - default: - quote = true; - } - } while (c); - break; - case '.': - if (option.regexp) { - warn_at('insecure_a', line, - from + length, c); - } - break; - case ']': - case '?': - case '{': - case '}': - case '+': - case '*': - warn_at('unescaped_a', line, - from + length, c); - break; - case '<': - if (xmode === 'script') { - c = source_row.charAt(length); - if (c === '!' || c === '/') { - warn_at( - bundle.html_confusion_a, - line, - from + length, - c - ); - } - } - break; - } - if (b) { - switch (source_row.charAt(length)) { - case '?': - case '+': - case '*': - length += 1; - if (source_row.charAt(length) === '?') { - length += 1; - } - break; - case '{': - length += 1; - c = source_row.charAt(length); - if (c < '0' || c > '9') { - warn_at( - bundle.expected_number_a, - line, - from + length, - c - ); - } - length += 1; - low = +c; - for (;;) { - c = source_row.charAt(length); - if (c < '0' || c > '9') { - break; - } - length += 1; - low = +c + (low * 10); - } - high = low; - if (c === ',') { - length += 1; - high = Infinity; - c = source_row.charAt(length); - if (c >= '0' && c <= '9') { - length += 1; - high = +c; - for (;;) { - c = source_row.charAt(length); - if (c < '0' || c > '9') { - break; - } - length += 1; - high = +c + (high * 10); - } - } - } - if (source_row.charAt(length) !== '}') { - warn_at( - bundle.expected_a_b, - line, - from + length, - '}', - c - ); - } else { - length += 1; - } - if (source_row.charAt(length) === '?') { - length += 1; - } - if (low > high) { - warn_at( - bundle.not_greater, - line, - from + length, - low, - high - ); - } - break; - } - } - } - c = source_row.substr(0, length - 1); - character += length; - source_row = source_row.substr(length); - return it('(regexp)', c); - } - return it('(punctuator)', symbol); + return prereg ? regexp() : it('(punctuator)', snippet); // punctuator case ''); - postscript(delim('}')); - delim(')'); - delim(']'); - postscript(delim('"')); - postscript(delim('\'')); - delim(';'); - delim(':'); - delim(','); - delim('#'); - delim('@'); - delim('*/'); + postscript(symbol(''); + postscript(symbol('}')); + symbol(')'); + symbol(']'); + postscript(symbol('"')); + postscript(symbol('\'')); + symbol(';'); + symbol(':'); + symbol(','); + symbol('#'); + symbol('@'); + symbol('*/'); postscript(reserve('case')); reserve('catch'); postscript(reserve('default')); @@ -3509,58 +3443,45 @@ loop: for (;;) { reserve('finally'); reservevar('arguments', function (x) { - if (strict_mode && funct['(global)']) { + if (strict_mode && funct === global_funct) { warn('strict', x); } else if (option.safe) { - warn('adsafe', x); + warn('adsafe_a', x); } }); reservevar('eval', function (x) { if (option.safe) { - warn('adsafe', x); + warn('adsafe_a', x); } }); - reservevar('false'); - reservevar('Infinity'); - reservevar('NaN'); - reservevar('null'); + constant('false', 'boolean'); + constant('Infinity', 'number'); + constant('NaN', 'number'); + constant('null', ''); reservevar('this', function (x) { - if (strict_mode && ((funct['(statement)'] && - funct['(name)'].charAt(0) > 'Z') || funct['(global)'])) { + if (option.safe) { + warn('adsafe_a', x); + } else if (strict_mode && funct['(token)'].arity === 'statement' && + funct['(name)'].charAt(0) > 'Z') { warn('strict', x); - } else if (option.safe) { - warn('adsafe', x); } }); - reservevar('true'); - reservevar('undefined'); - - assignop('='); - assignop('+='); - assignop('-='); - assignop('*='); - assignop('/=').nud = function () { - stop('slash_equal'); - }; - assignop('%='); - assignop('&=', true); - assignop('|=', true); - assignop('^=', true); - assignop('<<=', true); - assignop('>>=', true); - assignop('>>>=', true); + constant('true', 'boolean'); + constant('undefined', ''); infix('?', 30, function (left, that) { that.first = expected_condition(expected_relation(left)); that.second = expression(0); spaces(); + var colon = next_token; advance(':'); - discard(); spaces(); that.third = expression(10); that.arity = 'ternary'; if (are_similar(that.second, that.third)) { - warn('weird_ternary', that); + warn('weird_ternary', colon); + } else if (are_similar(that.first, that.second)) { + warn('use_or', that); } return that; }); @@ -3592,10 +3513,13 @@ loop: for (;;) { prefix('void', function () { this.first = expression(0); - if (this.first.arity !== 'number' || this.first.value) { - warn('unexpected_a', this); - return this; + this.arity = 'prefix'; + if (option.es5) { + warn('expected_a_b', this, 'undefined', 'void'); + } else if (this.first.number !== 0) { + warn('expected_a_b', this.first, '0', artifact(this.first)); } + this.type = 'undefined'; return this; }); @@ -3621,34 +3545,41 @@ loop: for (;;) { that.left = left; that.right = expression(130); return that; - }); - infix('instanceof', 120); + }, 'boolean'); + infix('instanceof', 120, null, 'boolean'); infix('+', 130, function (left, that) { - if (!left.value) { - if (left.arity === 'number') { - warn('unexpected_a', left); - } else if (left.arity === 'string') { + if (left.id === '(number)') { + if (left.number === 0) { + warn('unexpected_a', left, '0'); + } + } else if (left.id === '(string)') { + if (left.string === '') { warn('expected_a_b', left, 'String', '\'\''); } } var right = expression(130); - if (!right.value) { - if (right.arity === 'number') { - warn('unexpected_a', right); - } else if (right.arity === 'string') { + if (right.id === '(number)') { + if (right.number === 0) { + warn('unexpected_a', right, '0'); + } + } else if (right.id === '(string)') { + if (right.string === '') { warn('expected_a_b', right, 'String', '\'\''); } } - if (left.arity === right.arity && - (left.arity === 'string' || left.arity === 'number')) { - left.value += right.value; - left.thru = right.thru; - if (left.arity === 'string' && jx.test(left.value)) { - warn('url', left); + if (left.id === right.id) { + if (left.id === '(string)' || left.id === '(number)') { + if (left.id === '(string)') { + left.string += right.string; + if (jx.test(left.string)) { + warn('url', left); + } + } else { + left.number += right.number; + } + left.thru = right.thru; + return left; } - discard(right); - discard(that); - return left; } that.first = left; that.second = right; @@ -3668,24 +3599,22 @@ loop: for (;;) { return this; }); infix('-', 130, function (left, that) { - if ((left.arity === 'number' && left.value === 0) || left.arity === 'string') { + if ((left.id === '(number)' && left.number === 0) || left.id === '(string)') { warn('unexpected_a', left); } var right = expression(130); - if ((right.arity === 'number' && right.value === 0) || right.arity === 'string') { + if ((right.id === '(number)' && right.number === 0) || right.id === '(string)') { warn('unexpected_a', left); } - if (left.arity === right.arity && left.arity === 'number') { - left.value -= right.value; + if (left.id === right.id && left.id === '(number)') { + left.number -= right.number; left.thru = right.thru; - discard(right); - discard(that); return left; } that.first = left; that.second = right; return that; - }); + }, 'number'); prefix('-'); prefix('---', function () { warn('confusing_a', token); @@ -3700,62 +3629,56 @@ loop: for (;;) { return this; }); infix('*', 140, function (left, that) { - if ((left.arity === 'number' && (left.value === 0 || left.value === 1)) || left.arity === 'string') { + if ((left.id === '(number)' && (left.number === 0 || left.number === 1)) || left.id === '(string)') { warn('unexpected_a', left); } var right = expression(140); - if ((right.arity === 'number' && (right.value === 0 || right.value === 1)) || right.arity === 'string') { + if ((right.id === '(number)' && (right.number === 0 || right.number === 1)) || right.id === '(string)') { warn('unexpected_a', right); } - if (left.arity === right.arity && left.arity === 'number') { - left.value *= right.value; + if (left.id === right.id && left.id === '(number)') { + left.number *= right.number; left.thru = right.thru; - discard(right); - discard(that); return left; } that.first = left; that.second = right; return that; - }); + }, 'number'); infix('/', 140, function (left, that) { - if ((left.arity === 'number' && left.value === 0) || left.arity === 'string') { + if ((left.id === '(number)' && left.number === 0) || left.id === '(string)') { warn('unexpected_a', left); } var right = expression(140); - if ((right.arity === 'number' && (right.value === 0 || right.value === 1)) || right.arity === 'string') { + if ((right.id === '(number)' && (right.number === 0 || right.number === 1)) || right.id === '(string)') { warn('unexpected_a', right); } - if (left.arity === right.arity && left.arity === 'number') { - left.value /= right.value; + if (left.id === right.id && left.id === '(number)') { + left.number /= right.number; left.thru = right.thru; - discard(right); - discard(that); return left; } that.first = left; that.second = right; return that; - }); + }, 'number'); infix('%', 140, function (left, that) { - if ((left.arity === 'number' && (left.value === 0 || left.value === 1)) || left.arity === 'string') { + if ((left.id === '(number)' && (left.number === 0 || left.number === 1)) || left.id === '(string)') { warn('unexpected_a', left); } var right = expression(140); - if ((right.arity === 'number' && right.value === 0) || right.arity === 'string') { + if ((right.id === '(number)' && right.number === 0) || right.id === '(string)') { warn('unexpected_a', right); } - if (left.arity === right.arity && left.arity === 'number') { - left.value %= right.value; + if (left.id === right.id && left.id === '(number)') { + left.number %= right.number; left.thru = right.thru; - discard(right); - discard(that); return left; } that.first = left; that.second = right; return that; - }); + }, 'number'); suffix('++'); prefix('++'); @@ -3775,12 +3698,12 @@ loop: for (;;) { prefix('~', function () { no_space_only(); - if (option.bitwise) { + if (!option.bitwise) { warn('unexpected_a', this); } expression(150); return this; - }); + }, 'number'); prefix('!', function () { no_space_only(); this.first = expected_condition(expression(150)); @@ -3789,15 +3712,15 @@ loop: for (;;) { warn('confusing_a', this); } return this; - }); - prefix('typeof'); + }, 'boolean'); + prefix('typeof', null, 'string'); prefix('new', function () { one_space(); - var c = expression(160), i, p; + var c = expression(160), n, p, v; this.first = c; if (c.id !== 'function') { if (c.identifier) { - switch (c.value) { + switch (c.string) { case 'Object': warn('use_object', token); break; @@ -3807,25 +3730,19 @@ loop: for (;;) { p.first = this; advance('('); if (next_token.id !== ')') { - p.second = expression(0); - if (p.second.arity !== 'number' || !p.second.value) { - expected_condition(p.second, bundle.use_array); - i = false; - } else { - i = true; + n = expression(0); + p.second = [n]; + if (n.type !== 'number' || next_token.id === ',') { + warn('use_array', p); } - while (next_token.id !== ')' && next_token.id !== '(end)') { - if (i) { - warn('use_array', p); - i = false; - } - advance(); + while (next_token.id === ',') { + advance(','); + p.second.push(expression(0)); } } else { warn('use_array', token); } advance(')', p); - discard(); return p; } warn('use_array', token); @@ -3847,8 +3764,8 @@ loop: for (;;) { break; default: if (c.id !== 'function') { - i = c.value.substr(0, 1); - if (option.newcap && (i < 'A' || i > 'Z')) { + v = c.string.charAt(0); + if (!option.newcap && (v < 'A' || v > 'Z')) { warn('constructor_name_a', token); } } @@ -3868,6 +3785,7 @@ loop: for (;;) { }); infix('(', 160, function (left, that) { + var p; if (indent && indent.mode === 'expression') { no_space(prev_token, token); } else { @@ -3876,29 +3794,27 @@ loop: for (;;) { if (!left.immed && left.id === 'function') { warn('wrap_immediate'); } - var p = []; - if (left) { - if (left.identifier) { - if (left.value.match(/^[A-Z]([A-Z0-9_$]*[a-z][A-Za-z0-9_$]*)?$/)) { - if (left.value !== 'Number' && left.value !== 'String' && - left.value !== 'Boolean' && left.value !== 'Date') { - if (left.value === 'Math' || left.value === 'JSON') { - warn('not_a_function', left); - } else if (left.value === 'Object') { - warn('use_object', token); - } else if (left.value === 'Array' || option.newcap) { - warn('missing_a', left, 'new'); - } + p = []; + if (left.identifier) { + if (left.string.match(/^[A-Z]([A-Z0-9_$]*[a-z][A-Za-z0-9_$]*)?$/)) { + if (left.string !== 'Number' && left.string !== 'String' && + left.string !== 'Boolean' && left.string !== 'Date') { + if (left.string === 'Math' || left.string === 'JSON') { + warn('not_a_function', left); + } else if (left.string === 'Object') { + warn('use_object', token); + } else if (left.string === 'Array' || !option.newcap) { + warn('missing_a', left, 'new'); } } - } else if (left.id === '.') { - if (option.safe && left.first.value === 'Math' && - left.second === 'random') { - warn('adsafe', left); - } else if (left.second.value === 'split' && - left.first.id === '(string)') { - warn('use_array', left.second); - } + } + } else if (left.id === '.') { + if (option.safe && left.first.string === 'Math' && + left.second === 'random') { + warn('adsafe_a', left); + } else if (left.second.string === 'split' && + left.first.id === '(string)') { + warn('use_array', left.second); } } step_in(); @@ -3916,16 +3832,16 @@ loop: for (;;) { no_space(); step_out(')', that); if (typeof left === 'object') { - if (left.value === 'parseInt' && p.length === 1) { + if (left.string === 'parseInt' && p.length === 1) { warn('radix', left); } if (!option.evil) { - if (left.value === 'eval' || left.value === 'Function' || - left.value === 'execScript') { + if (left.string === 'eval' || left.string === 'Function' || + left.string === 'execScript') { warn('evil', left); - } else if (p[0] && p[0].arity === 'string' && - (left.value === 'setTimeout' || - left.value === 'setInterval')) { + } else if (p[0] && p[0].id === '(string)' && + (left.string === 'setTimeout' || + left.string === 'setInterval')) { warn('implied_evil', left); } } @@ -3938,11 +3854,10 @@ loop: for (;;) { that.first = left; that.second = p; return that; - }, true); + }, '', true); prefix('(', function () { step_in('expression'); - discard(); no_space(); edge(); if (next_token.id === 'function') { @@ -3952,7 +3867,6 @@ loop: for (;;) { value.paren = true; no_space(); step_out(')', this); - discard(); if (value.id === 'function') { if (next_token.id === '(') { warn('move_invocation'); @@ -3966,28 +3880,28 @@ loop: for (;;) { infix('.', 170, function (left, that) { no_space(prev_token, token); no_space(); - var name = identifier(); + var name = identifier(), type; if (typeof name === 'string') { tally_property(name); } that.first = left; that.second = token; - if (left && left.value === 'arguments' && + if (left && left.string === 'arguments' && (name === 'callee' || name === 'caller')) { warn('avoid_a', left, 'arguments.' + name); - } else if (!option.evil && left && left.value === 'document' && + } else if (!option.evil && left && left.string === 'document' && (name === 'write' || name === 'writeln')) { warn('write_is_wrong', left); } else if (option.adsafe) { - if (!adsafe_top && left.value === 'ADSAFE') { + if (!adsafe_top && left.string === 'ADSAFE') { if (name === 'id' || name === 'lib') { - warn('adsafe', that); + warn('adsafe_a', that); } else if (name === 'go') { if (xmode !== 'script') { - warn('adsafe', that); + warn('adsafe_a', that); } else if (adsafe_went || next_token.id !== '(' || - peek(0).arity !== 'string' || - peek(0).value !== adsafe_id || + peek(0).id !== '(string)' || + peek(0).string !== adsafe_id || peek(1).id !== ',') { stop('adsafe_a', that, 'go'); } @@ -4004,18 +3918,12 @@ loop: for (;;) { if (banned[name] === true) { warn('adsafe_a', token, name); } - if (typeof predefined[left.value] !== 'boolean' || + if (typeof predefined[left.string] !== 'boolean' || //// check for writeable next_token.id === '(') { break; } - if (standard_property[name] === true) { - if (next_token.id === '.') { - warn('adsafe', that); - } - break; - } if (next_token.id !== '.') { - warn('adsafe', that); + warn('adsafe_a', that); break; } advance('.'); @@ -4028,47 +3936,61 @@ loop: for (;;) { } } } + type = property_type[name]; + if (type && typeof type === 'string' && type !== '*') { + that.type = type; + } return that; - }, true); + }, '', true); infix('[', 170, function (left, that) { + var e, s; no_space_only(prev_token, token); no_space(); step_in(); edge(); - var e = expression(0), s; - if (e.arity === 'number') { - if (left.id === 'arguments') { + e = expression(0); + switch (e.type) { + case 'number': + if (e.id === '(number)' && left.id === 'arguments') { warn('use_param', left); } - } else if (e.arity === 'string') { - if (option.safe && (banned[e.value] || - e.value.charAt(0) === '_' || e.value.slice(-1) === '_')) { + break; + case 'string': + if (e.id === '(string)') { + if (option.safe && (banned[e.string] || + e.string.charAt(0) === '_' || e.string.slice(-1) === '_')) { + warn('adsafe_subscript_a', e); + } else if (!option.evil && + (e.string === 'eval' || e.string === 'execScript')) { + warn('evil', e); + } else if (!option.sub && ix.test(e.string)) { + s = syntax[e.string]; + if (!s || !s.reserved) { + warn('subscript', e); + } + } + tally_property(e.string); + } else if (option.safe && e.id !== 'typeof') { warn('adsafe_subscript_a', e); - } else if (!option.evil && - (e.value === 'eval' || e.value === 'execScript')) { - warn('evil', e); } - tally_property(e.value); - if (!option.sub && ix.test(e.value)) { - s = syntax[e.value]; - if (!s || !s.reserved) { - warn('subscript', e); - } + break; + case undefined: + if (option.safe) { + warn('adsafe_subscript_a', e); } - } else if (option.safe) { - if (!((e.arity === 'prefix' && adsafe_prefix[e.id] === true) || - (e.arity === 'infix' && adsafe_infix[e.id] === true))) { + break; + default: + if (option.safe) { warn('adsafe_subscript_a', e); } } step_out(']', that); - discard(); no_space(prev_token, token); that.first = left; that.second = e; return that; - }, true); + }, '', true); prefix('[', function () { this.arity = 'prefix'; @@ -4078,7 +4000,6 @@ loop: for (;;) { while (next_token.id === ',') { warn('unexpected_a', next_token); advance(','); - discard(); } if (next_token.id === ']') { break; @@ -4097,7 +4018,6 @@ loop: for (;;) { } } step_out(']', this); - discard(); return this; }, 170); @@ -4105,8 +4025,8 @@ loop: for (;;) { function property_name() { var id = optional_identifier(true); if (!id) { - if (next_token.arity === 'string') { - id = next_token.value; + if (next_token.id === '(string)') { + id = next_token.string; if (option.safe) { if (banned[id]) { warn('adsafe_a'); @@ -4116,8 +4036,8 @@ loop: for (;;) { } } advance(); - } else if (next_token.arity === 'number') { - id = next_token.value.toString(); + } else if (next_token.id === '(number)') { + id = next_token.number.toString(); advance(); } } @@ -4129,66 +4049,152 @@ loop: for (;;) { var id, paren = next_token, params = []; advance('('); step_in(); - discard(); no_space(); if (next_token.id === ')') { no_space(); step_out(')', paren); - discard(); return; } for (;;) { edge(); id = identifier(); params.push(token); - add_label(id, 'unparam'); + add_label(token, option.unparam ? 'parameter' : 'unparam'); if (next_token.id === ',') { comma(); } else { - no_space(); - step_out(')', paren); - discard(); - return params; + no_space(); + step_out(')', paren); + return params; + } + } + } + + + function complexity(exp) { + var score = 0; + if (exp) { + if (Array.isArray(exp)) { + exp.forEach(function (tok) { + score += complexity(tok); + }); + } else { + switch (exp.arity) { + case 'statement': + switch (exp.id) { + case 'if': + score += complexity(exp.first) + complexity(exp.block) + + complexity(exp['else']) + 1; + break; + case 'while': + case 'do': + if (exp.first.id !== 'true' && exp.first.number !== 1) { + score += 1; + } + score += complexity(exp.first) + complexity(exp.block); + break; + case 'for': + if (exp.second !== undefined && + exp.second.id !== 'true' && + exp.second.number !== 1) { + score += 1; + } + score += complexity(exp.first) + complexity(exp.second) + + complexity(exp.third) + complexity(exp.block); + break; + case 'switch': + score += complexity(exp.first) + + complexity(exp.second) + exp.second.length; + if (exp.second[exp.second.length - 1].id === 'default') { + score -= 1; + } + break; + case 'try': + if (exp.second) { + score += 1; + } + if (exp.third) { + score += 1; + } + score += complexity(exp.first) + complexity(exp.second) + + complexity(exp.third) + complexity(exp.block); + break; + } + break; + case 'prefix': + score += complexity(exp.first); + break; + case 'case': + case 'infix': + score += complexity(exp.first) + complexity(exp.second); + if (exp.id === '&&' || exp.id === '||') { + score += 1; + } + break; + case 'ternary': + score += complexity(exp.first) + complexity(exp.second) + complexity(exp.third); + break; + } } } + return score; } function do_function(func, name) { - var old_properties = properties, + var old_funct = funct, old_option = option, - old_global = global, old_scope = scope; funct = { - '(name)' : name || '\'' + anonname + '\'', + '(name)' : name || '\'' + (anonname || '').replace(nx, sanitize) + '\'', '(line)' : next_token.line, - '(context)' : funct, + '(context)' : old_funct, '(breakage)' : 0, '(loopage)' : 0, '(scope)' : scope, '(token)' : func }; - properties = old_properties && Object.create(old_properties); - option = Object.create(old_option); - global = Object.create(old_global); - scope = Object.create(old_scope); - token.funct = funct; + option = Object.create(old_option); + scope = Object.create(old_scope); functions.push(funct); + func.name = name; if (name) { - add_label(name, 'function'); + add_label(func, 'function', name); } - func.name = name || ''; + func.writeable = false; func.first = funct['(params)'] = function_params(); one_space(); func.block = block(false); - funct = funct['(context)']; - properties = old_properties; + if (funct['(old_property_type)']) { + property_type = funct['(old_property_type)']; + delete funct['(old_property_type)']; + } + funct['(complexity)'] = complexity(func.block) + 1; + if (option.confusion) { + funct['(confusion)'] = true; + } + funct = old_funct; option = old_option; - global = old_global; scope = old_scope; } + assignop('='); + assignop('+=', '+'); + assignop('-=', '-'); + assignop('*=', '*'); + assignop('/=', '/').nud = function () { + stop('slash_equal'); + }; + assignop('%=', '%'); + assignop('&=', '&'); + assignop('|=', '|'); + assignop('^=', '^'); + assignop('<<=', '<<'); + assignop('>>=', '>>'); + assignop('>>>=', '>>>'); + + prefix('{', function () { var get, i, j, name, p, set, seen = {}; this.arity = 'prefix'; @@ -4201,9 +4207,9 @@ loop: for (;;) { // but requires that they be used in pairs. edge(); - if (next_token.value === 'get' && peek().id !== ':') { + if (next_token.string === 'get' && peek().id !== ':') { if (!option.es5) { - warn('get_set'); + warn('es5'); } get = next_token; advance('get'); @@ -4213,30 +4219,32 @@ loop: for (;;) { if (!i) { stop('missing_property'); } - do_function(get, ''); + get.string = ''; + do_function(get); if (funct['(loopage)']) { warn('function_loop', get); } p = get.first; if (p) { - warn('parameter_a_get_b', p[0], p[0].value, i); + warn('parameter_a_get_b', p[0], p[0].string, i); } comma(); set = next_token; + set.string = ''; spaces(); edge(); advance('set'); one_space_only(); j = property_name(); if (i !== j) { - stop('expected_a_b', token, i, j || next_token.value); + stop('expected_a_b', token, i, j || next_token.string); } - do_function(set, ''); + do_function(set); p = set.first; if (!p || p.length !== 1) { stop('parameter_set_a', set, 'value'); - } else if (p[0].value !== 'value') { - stop('expected_a_b', p[0], 'value', p[0].value); + } else if (p[0].string !== 'value') { + stop('expected_a_b', p[0], 'value', p[0].string); } name.first = [get, set]; } else { @@ -4246,7 +4254,6 @@ loop: for (;;) { stop('missing_property'); } advance(':'); - discard(); spaces(); name.first = expression(10); } @@ -4271,18 +4278,15 @@ loop: for (;;) { } } step_out('}', this); - discard(); return this; }); stmt('{', function () { - discard(); warn('statement_block'); this.arity = 'statement'; this.block = statements(); this.disrupt = this.block.disrupt; advance('}', this); - discard(); return this; }); @@ -4304,10 +4308,10 @@ loop: for (;;) { var assign, id, name; - if (funct['(onevar)'] && option.onevar) { + if (funct['(vars)'] && !option.vars) { warn('combine_var'); - } else if (!funct['(global)']) { - funct['(onevar)'] = true; + } else if (funct !== global_funct) { + funct['(vars)'] = true; } this.arity = 'statement'; this.first = []; @@ -4315,10 +4319,7 @@ loop: for (;;) { for (;;) { name = next_token; id = identifier(); - if (funct['(global)'] && predefined[id] === false) { - warn('redefinition_a', token, id); - } - add_label(id, 'error'); + add_label(name, 'becoming'); if (next_token.id === '=') { assign = next_token; @@ -4338,7 +4339,9 @@ loop: for (;;) { } else { this.first.push(name); } - funct[id] = 'unused'; + if (funct[id] === 'becoming') { + funct[id] = 'unused'; + } if (next_token.id !== ',') { break; } @@ -4346,14 +4349,14 @@ loop: for (;;) { indent.wrap = false; if (var_mode && next_token.line === token.line && this.first.length === 1) { - var_mode = false; + var_mode = null; indent.open = false; indent.at -= option.indent; } spaces(); edge(); } - var_mode = false; + var_mode = null; step_out(); return this; }); @@ -4363,26 +4366,26 @@ loop: for (;;) { if (in_block) { warn('function_block', token); } - var i = identifier(); - if (i) { - add_label(i, 'unction'); - no_space(); - } - do_function(this, i, true); + var name = next_token, id = identifier(); + add_label(name, 'unction'); + no_space(); + this.arity = 'statement'; + do_function(this, id); if (next_token.id === '(' && next_token.line === token.line) { stop('function_statement'); } - this.arity = 'statement'; return this; }); prefix('function', function () { one_space(); - var i = optional_identifier(); - if (i) { + var id = optional_identifier(); + if (id) { no_space(); + } else { + id = ''; } - do_function(this, i); + do_function(this, id); if (funct['(loopage)']) { warn('function_loop'); } @@ -4395,20 +4398,17 @@ loop: for (;;) { one_space(); advance('('); step_in('control'); - discard(); no_space(); edge(); this.arity = 'statement'; this.first = expected_condition(expected_relation(expression(0))); no_space(); step_out(')', paren); - discard(); one_space(); this.block = block(true); if (next_token.id === 'else') { one_space(); advance('else'); - discard(); one_space(); this['else'] = next_token.id === 'if' || next_token.id === 'switch' ? statement(true) : block(true); @@ -4436,40 +4436,35 @@ loop: for (;;) { if (next_token.id === 'catch') { one_space(); advance('catch'); - discard(); one_space(); paren = next_token; advance('('); step_in('control'); - discard(); no_space(); edge(); old_scope = scope; scope = Object.create(old_scope); - exception_variable = next_token.value; + exception_variable = next_token.string; this.first = exception_variable; if (!next_token.identifier) { warn('expected_identifier_a', next_token); } else { - add_label(exception_variable, 'exception'); + add_label(next_token, 'exception'); } advance(); no_space(); step_out(')', paren); - discard(); one_space(); this.second = block(false); scope = old_scope; } if (next_token.id === 'finally') { - discard(); one_space(); advance('finally'); - discard(); one_space(); this.third = block(false); } else if (!this.second) { - stop('expected_a_b', next_token, 'catch', next_token.value); + stop('expected_a_b', next_token, 'catch', artifact()); } return this; }); @@ -4481,7 +4476,6 @@ loop: for (;;) { funct['(loopage)'] += 1; advance('('); step_in('control'); - discard(); no_space(); edge(); this.arity = 'statement'; @@ -4491,7 +4485,6 @@ loop: for (;;) { } no_space(); step_out(')', paren); - discard(); one_space(); this.block = block(true); if (this.block.disrupt) { @@ -4506,53 +4499,63 @@ loop: for (;;) { labeled_stmt('switch', function () { -// switch.first the switch expression -// switch.second the array of cases. A case is 'case' or 'default' token: -// case.first the array of case expressions -// case.second the array of statements +// switch.first the switch expression +// switch.second the array of cases. A case is 'case' or 'default' token: +// case.first the array of case expressions +// case.second the array of statements // If all of the arrays of statements are disrupt, then the switch is disrupt. - var particular, + var cases = [], + old_in_block = in_block, + particular, the_case = next_token, unbroken = true; + + function find_duplicate_case(value) { + if (are_similar(particular, value)) { + warn('duplicate_a', value); + } + } + funct['(breakage)'] += 1; one_space(); advance('('); - discard(); no_space(); step_in(); this.arity = 'statement'; this.first = expected_condition(expected_relation(expression(0))); no_space(); step_out(')', the_case); - discard(); one_space(); advance('{'); step_in(); + in_block = true; this.second = []; while (next_token.id === 'case') { the_case = next_token; + cases.forEach(find_duplicate_case); the_case.first = []; + the_case.arity = 'case'; spaces(); edge('case'); advance('case'); for (;;) { one_space(); particular = expression(0); + cases.forEach(find_duplicate_case); + cases.push(particular); the_case.first.push(particular); if (particular.id === 'NaN') { warn('unexpected_a', particular); } no_space_only(); advance(':'); - discard(); if (next_token.id !== 'case') { break; } spaces(); edge('case'); advance('case'); - discard(); } spaces(); the_case.second = statements(); @@ -4576,12 +4579,11 @@ loop: for (;;) { if (next_token.id === 'default') { spaces(); the_case = next_token; + the_case.arity = 'case'; edge('case'); advance('default'); - discard(); no_space_only(); advance(':'); - discard(); spaces(); the_case.second = statements(); if (the_case.second && the_case.second.length > 0) { @@ -4595,6 +4597,7 @@ loop: for (;;) { funct['(breakage)'] -= 1; spaces(); step_out('}', this); + in_block = old_in_block; return this; }); @@ -4617,31 +4620,28 @@ loop: for (;;) { } one_space(); advance('while'); - discard(); var paren = next_token; one_space(); advance('('); step_in(); - discard(); no_space(); edge(); this.first = expected_condition(expected_relation(expression(0)), bundle.unexpected_a); no_space(); step_out(')', paren); - discard(); funct['(breakage)'] -= 1; funct['(loopage)'] -= 1; return this; }); labeled_stmt('for', function () { - var blok, filter, ok = false, paren = next_token, the_in, value; + + var blok, filter, ok = false, paren = next_token, value; this.arity = 'statement'; funct['(breakage)'] += 1; funct['(loopage)'] += 1; advance('('); step_in('control'); - discard(); spaces(this, paren); no_space(); if (next_token.id === 'var') { @@ -4649,28 +4649,27 @@ loop: for (;;) { } edge(); if (peek(0).id === 'in') { + this.forin = true; value = next_token; - switch (funct[value.value]) { + switch (funct[value.string]) { case 'unused': - funct[value.value] = 'var'; + funct[value.string] = 'var'; break; + case 'closure': case 'var': break; default: warn('bad_in_a', value); } advance(); - the_in = next_token; advance('in'); - the_in.first = value; - the_in.second = expression(20); + this.first = value; + this.second = expression(20); step_out(')', paren); - discard(); - this.first = the_in; blok = block(true); if (!option.forin) { if (blok.length === 1 && typeof blok[0] === 'object' && - blok[0].value === 'if' && !blok[0]['else']) { + blok[0].string === 'if' && !blok[0]['else']) { filter = blok[0].first; while (filter.id === '&&') { filter = filter.first; @@ -4679,34 +4678,34 @@ loop: for (;;) { case '===': case '!==': ok = filter.first.id === '[' ? ( - filter.first.first.value === the_in.second.value && - filter.first.second.value === the_in.first.value + filter.first.first.string === this.second.string && + filter.first.second.string === this.first.string ) : ( filter.first.id === 'typeof' && filter.first.first.id === '[' && - filter.first.first.first.value === the_in.second.value && - filter.first.first.second.value === the_in.first.value + filter.first.first.first.string === this.second.string && + filter.first.first.second.string === this.first.string ); break; case '(': ok = filter.first.id === '.' && (( - filter.first.first.value === the_in.second.value && - filter.first.second.value === 'hasOwnProperty' && - filter.second[0].value === the_in.first.value + filter.first.first.string === this.second.string && + filter.first.second.string === 'hasOwnProperty' && + filter.second[0].string === this.first.string ) || ( - filter.first.first.value === 'ADSAFE' && - filter.first.second.value === 'has' && - filter.second[0].value === the_in.second.value && - filter.second[1].value === the_in.first.value + filter.first.first.string === 'ADSAFE' && + filter.first.second.string === 'has' && + filter.second[0].string === this.second.string && + filter.second[1].string === this.first.string ) || ( filter.first.first.id === '.' && filter.first.first.first.id === '.' && - filter.first.first.first.first.value === 'Object' && - filter.first.first.first.second.value === 'prototype' && - filter.first.first.second.value === 'hasOwnProperty' && - filter.first.second.value === 'call' && - filter.second[0].value === the_in.second.value && - filter.second[1].value === the_in.first.value + filter.first.first.first.first.string === 'Object' && + filter.first.first.first.second.string === 'prototype' && + filter.first.first.second.string === 'hasOwnProperty' && + filter.first.second.string === 'call' && + filter.second[0].string === this.second.string && + filter.second[1].string === this.first.string )); break; } @@ -4752,7 +4751,6 @@ loop: for (;;) { } no_space(); step_out(')', paren); - discard(); one_space(); blok = block(true); } @@ -4766,7 +4764,7 @@ loop: for (;;) { }); disrupt_stmt('break', function () { - var label = next_token.value; + var label = next_token.string; this.arity = 'statement'; if (funct['(breakage)'] === 0) { warn('unexpected_a', this); @@ -4775,7 +4773,7 @@ loop: for (;;) { one_space_only(); if (funct[label] !== 'label') { warn('not_a_label', next_token); - } else if (scope[label] !== funct) { + } else if (scope[label].funct !== funct) { warn('not_a_scope', next_token); } this.first = next_token; @@ -4788,7 +4786,7 @@ loop: for (;;) { if (!option['continue']) { warn('unexpected_a', this); } - var label = next_token.value; + var label = next_token.string; this.arity = 'statement'; if (funct['(breakage)'] === 0) { warn('unexpected_a', this); @@ -4797,7 +4795,7 @@ loop: for (;;) { one_space_only(); if (funct[label] !== 'label') { warn('not_a_label', next_token); - } else if (scope[label] !== funct) { + } else if (scope[label].funct !== funct) { warn('not_a_scope', next_token); } this.first = next_token; @@ -4807,6 +4805,9 @@ loop: for (;;) { }); disrupt_stmt('return', function () { + if (funct === global_funct) { + warn('unexpected_a', this); + } this.arity = 'statement'; if (next_token.id !== ';' && next_token.line === token.line) { one_space_only(); @@ -4838,15 +4839,221 @@ loop: for (;;) { // Harmony reserved words - reserve('let'); - reserve('yield'); reserve('implements'); reserve('interface'); + reserve('let'); reserve('package'); reserve('private'); reserve('protected'); reserve('public'); reserve('static'); + reserve('yield'); + + +// Type inference + + function get_type(one) { + var type; + if (typeof one === 'string') { + return one; + } else if (one.type) { + return one.type; + } else if (one.id === '.') { + type = property_type[one.second.string]; + return typeof type === 'string' ? type : ''; + } else { + return ((one.identifier && scope[one.string]) || one).type; + } + } + + + function match_type(one_type, two_type, one, two) { + if (one_type === two_type) { + return true; + } else { + if (!funct.confusion && !two.warn) { + if (typeof one !== 'string') { + if (one.id === '.') { + one_type = '.' + one.second.string + ': ' + one_type; + } else { + one_type = one.string + ': ' + one_type; + } + } + if (two.id === '.') { + two_type = '.' + two.second.string + ': ' + one_type; + } else { + two_type = two.string + ': ' + one_type; + } + warn('type_confusion_a_b', two, one_type, two_type); + two.warn = true; + } + return false; + } + } + + + function conform(one, two) { + +// The conform function takes a type string and a token, or two tokens. + + var one_type = typeof one === 'string' ? one : one.type, + two_type = two.type, + two_thing; + +// If both tokens already have a type, and if they match, then we are done. +// Once a token has a type, it is locked. Neither token will change, but if +// they do not match, there will be a warning. + + if (one_type) { + if (two_type) { + match_type(one_type, two_type, one, two); + } else { + +// two does not have a type, so look deeper. If two is a variable or property, +// then use its type if it has one, and make the deep type one's type if it +// doesn't. If the type was *, or if there was a mismatch, don't change the +// deep type. + + two_thing = two.id === '(identifier)' ? scope[two.string] : + two.id === '.' ? property_type[two.second.string] : null; + if (two_thing) { + two_type = two_thing.type; + if (two_type) { + if (two_type !== '*') { + if (!match_type(one_type, two_type, one, two)) { + return ''; + } + } + } else { + two_thing.type = one_type; + } + } + +// In any case, we give two a type. + + two.type = one_type; + type_state_change = true; + return one_type; + } + +// one does not have a type, but two does, so do the old switcheroo. + + } else { + if (two_type) { + return conform(two, one); + +// Neither token has a type yet. So we have to look deeper to see if either +// is a variable or property. + + } else { + if (one.id === '(identifier)') { + one_type = scope[one.string].type; + if (one_type && one_type !== '*') { + one.type = one_type; + return conform(one, two); + } + } else if (one.id === '.') { + one_type = property_type[one.second.string]; + if (one_type && one_type !== '*') { + one.type = scope[one.string].type; + return conform(one, two); + } + } + if (two.id === '(identifier)') { + two_type = scope[two.string].type; + if (two_type && two_type !== '*') { + two.type = two_type; + return conform(two, one); + } + } else if (two.id === '.') { + two_type = property_type[two.second.string]; + if (two_type && two_type !== '*') { + two.type = scope[two.string].type; + return conform(two, one); + } + } + } + } + +// Return a falsy string if we were unable to determine the type of either token. + + return ''; + } + + function conform_array(type, array) { + array.forEach(function (item) { + return conform(type, item); + }, type); + } + + + function infer(node) { + if (Array.isArray(node)) { + node.forEach(infer); + } else { + switch (node.arity) { + case 'statement': + infer_statement[node.id](node); + break; + case 'infix': + infer(node.first); + infer(node.second); + switch (node.id) { + case '(': + conform('function', node.first); + break; + default: + stop('unfinished'); + } + break; + case 'number': + case 'string': + case 'boolean': + break; + default: + stop('unfinished'); + } + } + } + + + infer_statement = { + 'var': function (node) { + var i, item, list = node.first; + for (i = 0; i < list.length; i += 1) { + item = list[i]; + if (item.id === '=') { + infer(item.second); + conform(item.first, item.second); + conform(item.first, item); + } + } + }, + 'for': function (node) { + infer(node.first); + infer(node.second); + if (node.forin) { + conform('string', node.first); + conform('object', node.second); + } else { + infer(node.third); + conform_array('number', node.first); + conform('boolean', node.second); + conform_array('number', node.third); + } + infer(node.block); + } + }; + + + function infer_types(node) { + do { + funct = global_funct; + scope = global_scope; + type_state_change = false; + infer(node); + } while (type_state_change); + } // Parse JSON @@ -4860,17 +5067,17 @@ loop: for (;;) { while (next_token.id !== '(end)') { while (next_token.id === ',') { warn('unexpected_a', next_token); - comma(); + advance(','); } - if (next_token.arity !== 'string') { + if (next_token.id !== '(string)') { warn('expected_string_a'); } - if (object[next_token.value] === true) { + if (object[next_token.string] === true) { warn('duplicate_a'); - } else if (next_token.value === '__proto__') { + } else if (next_token.string === '__proto__') { warn('dangling_a'); } else { - object[next_token.value] = true; + object[next_token.string] = true; } advance(); advance(':'); @@ -4878,7 +5085,7 @@ loop: for (;;) { if (next_token.id !== ',') { break; } - comma(); + advance(','); if (next_token.id === '}') { warn('unexpected_a', token); break; @@ -4895,13 +5102,13 @@ loop: for (;;) { while (next_token.id !== '(end)') { while (next_token.id === ',') { warn('unexpected_a', next_token); - comma(); + advance(','); } json_value(); if (next_token.id !== ',') { break; } - comma(); + advance(','); if (next_token.id === ']') { warn('unexpected_a', token); break; @@ -4951,7 +5158,7 @@ loop: for (;;) { advance('-'); no_space_only(); } - if (next_token.arity === 'number') { + if (next_token.id === '(number)') { advance('(number)'); return true; } @@ -4959,7 +5166,7 @@ loop: for (;;) { function css_string() { - if (next_token.arity === 'string') { + if (next_token.id === '(string)') { advance(); return true; } @@ -4968,7 +5175,7 @@ loop: for (;;) { function css_color() { var i, number, paren, value; if (next_token.identifier) { - value = next_token.value; + value = next_token.string; if (value === 'rgb' || value === 'rgba') { advance(); paren = next_token; @@ -4977,8 +5184,8 @@ loop: for (;;) { if (i) { comma(); } - number = next_token.value; - if (next_token.arity !== 'number' || number < 0) { + number = next_token.number; + if (next_token.id !== '(string)' || number < 0) { warn('expected_positive_a', next_token); advance(); } else { @@ -4997,8 +5204,8 @@ loop: for (;;) { } if (value === 'rgba') { comma(); - number = +next_token.value; - if (next_token.arity !== 'number' || number < 0 || number > 1) { + number = next_token.number; + if (next_token.id !== '(string)' || number < 0 || number > 1) { warn('expected_fraction_a', next_token); } advance(); @@ -5009,7 +5216,7 @@ loop: for (;;) { } advance(')', paren); return true; - } else if (css_colorData[next_token.value] === true) { + } else if (css_colorData[next_token.string] === true) { advance(); return true; } @@ -5026,13 +5233,13 @@ loop: for (;;) { advance('-'); no_space_only(); } - if (next_token.arity === 'number') { + if (next_token.id === '(number)') { advance(); - if (next_token.arity !== 'string' && - css_lengthData[next_token.value] === true) { + if (next_token.id !== '(string)' && + css_lengthData[next_token.string] === true) { no_space_only(); advance(); - } else if (+token.value !== 0) { + } else if (+token.number !== 0) { warn('expected_linear_a'); } return true; @@ -5046,10 +5253,10 @@ loop: for (;;) { advance('-'); no_space_only(); } - if (next_token.arity === 'number') { + if (next_token.id === '(number)') { advance(); - if (next_token.arity !== 'string' && - css_lengthData[next_token.value] === true) { + if (next_token.id !== '(string)' && + css_lengthData[next_token.string] === true) { no_space_only(); advance(); } @@ -5061,7 +5268,7 @@ loop: for (;;) { function css_width() { if (next_token.identifier) { - switch (next_token.value) { + switch (next_token.string) { case 'thin': case 'medium': case 'thick': @@ -5076,7 +5283,7 @@ loop: for (;;) { function css_margin() { if (next_token.identifier) { - if (next_token.value === 'auto') { + if (next_token.string === 'auto') { advance(); return true; } @@ -5086,7 +5293,7 @@ loop: for (;;) { } function css_attr() { - if (next_token.identifier && next_token.value === 'attr') { + if (next_token.identifier && next_token.string === 'attr') { advance(); advance('('); if (!next_token.identifier) { @@ -5114,13 +5321,13 @@ loop: for (;;) { function css_counter() { - if (next_token.identifier && next_token.value === 'counter') { + if (next_token.identifier && next_token.string === 'counter') { advance(); advance('('); advance(); if (next_token.id === ',') { comma(); - if (next_token.arity !== 'string') { + if (next_token.id !== '(string)') { warn('expected_string_a'); } advance(); @@ -5128,7 +5335,7 @@ loop: for (;;) { advance(')'); return true; } - if (next_token.identifier && next_token.value === 'counters') { + if (next_token.identifier && next_token.string === 'counters') { advance(); advance('('); if (!next_token.identifier) { @@ -5137,14 +5344,14 @@ loop: for (;;) { advance(); if (next_token.id === ',') { comma(); - if (next_token.arity !== 'string') { + if (next_token.id !== '(string)') { warn('expected_string_a'); } advance(); } if (next_token.id === ',') { comma(); - if (next_token.arity !== 'string') { + if (next_token.id !== '(string)') { warn('expected_string_a'); } advance(); @@ -5156,9 +5363,14 @@ loop: for (;;) { } + function css_radius() { + return css_length() && (next_token.id !== '(number)' || css_length()); + } + + function css_shape() { var i; - if (next_token.identifier && next_token.value === 'rect') { + if (next_token.identifier && next_token.string === 'rect') { advance(); advance('('); for (i = 0; i < 4; i += 1) { @@ -5176,25 +5388,25 @@ loop: for (;;) { function css_url() { var c, url; - if (next_token.identifier && next_token.value === 'url') { + if (next_token.identifier && next_token.string === 'url') { next_token = lex.range('(', ')'); - url = next_token.value; + url = next_token.string; c = url.charAt(0); if (c === '"' || c === '\'') { if (url.slice(-1) !== c) { - warn('bad_url'); + warn('bad_url_a'); } else { url = url.slice(1, -1); if (url.indexOf(c) >= 0) { - warn('bad_url'); + warn('bad_url_a'); } } } if (!url) { warn('missing_url'); } - if (option.safe && ux.test(url)) { - stop('adsafe_a', next_token, url); + if (ux.test(url)) { + stop('bad_url_a'); } urls.push(url); advance(); @@ -5207,7 +5419,7 @@ loop: for (;;) { css_any = [css_url, function () { for (;;) { if (next_token.identifier) { - switch (next_token.value.toLowerCase()) { + switch (next_token.string.toLowerCase()) { case 'url': css_url(); break; @@ -5229,6 +5441,39 @@ loop: for (;;) { }]; + function font_face() { + advance_identifier('font-family'); + advance(':'); + if (!css_name() && !css_string()) { + stop('expected_name_a'); + } + semicolon(); + advance_identifier('src'); + advance(':'); + while (true) { + if (next_token.string === 'local') { + advance_identifier('local'); + advance('('); + if (ux.test(next_token.string)) { + stop('bad_url_a'); + } + + if (!css_name() && !css_string()) { + stop('expected_name_a'); + } + advance(')'); + } else if (!css_url()) { + stop('expected_a_b', next_token, 'url', artifact()); + } + if (next_token.id !== ',') { + break; + } + comma(); + } + semicolon(); + } + + css_border_style = [ 'none', 'dashed', 'dotted', 'double', 'groove', 'hidden', 'inset', 'outset', 'ridge', 'solid' @@ -5275,6 +5520,8 @@ loop: for (;;) { 'border-bottom-width' ], 'border-bottom-color': css_color, + 'border-bottom-left-radius': css_radius, + 'border-bottom-right-radius': css_radius, 'border-bottom-style': css_border_style, 'border-bottom-width': css_width, 'border-collapse': ['collapse', 'separate'], @@ -5285,6 +5532,29 @@ loop: for (;;) { 'border-left-color': css_color, 'border-left-style': css_border_style, 'border-left-width': css_width, + 'border-radius': function () { + function count(separator) { + var n = 1; + if (separator) { + advance(separator); + } + if (!css_length()) { + return false; + } + while (next_token.id === '(number)') { + if (!css_length()) { + return false; + } + n += 1; + } + if (n > 4) { + warn('bad_style'); + } + return true; + } + + return count() && (next_token.id !== '/' || count('/')); + }, 'border-right': [ true, 'border-right-color', 'border-right-style', 'border-right-width' @@ -5298,6 +5568,8 @@ loop: for (;;) { true, 'border-top-color', 'border-top-style', 'border-top-width' ], 'border-top-color': css_color, + 'border-top-left-radius': css_radius, + 'border-top-right-radius': css_radius, 'border-top-style': css_border_style, 'border-top-width': css_width, 'border-width': [4, css_width], @@ -5429,7 +5701,7 @@ loop: for (;;) { function style_attribute() { var v; while (next_token.id === '*' || next_token.id === '#' || - next_token.value === '_') { + next_token.string === '_') { if (!option.css) { warn('unexpected_a'); } @@ -5449,8 +5721,9 @@ loop: for (;;) { if (!next_token.identifier) { warn('expected_style_attribute'); } else { - if (Object.prototype.hasOwnProperty.call(css_attribute_data, next_token.value)) { - v = css_attribute_data[next_token.value]; + if (Object.prototype.hasOwnProperty.call(css_attribute_data, + next_token.string)) { + v = css_attribute_data[next_token.string]; } else { v = css_any; if (!option.css) { @@ -5465,6 +5738,9 @@ loop: for (;;) { function style_value(v) { + + /*jslint confusion: true */ + var i = 0, n, once, @@ -5476,7 +5752,7 @@ loop: for (;;) { case 'function': return v(); case 'string': - if (next_token.identifier && next_token.value === v) { + if (next_token.identifier && next_token.string === v) { advance(); return true; } @@ -5488,7 +5764,7 @@ loop: for (;;) { } vi = v[i]; i += 1; - if (vi === true) { + if (typeof vi === 'boolean') { break; } else if (typeof vi === 'number') { n = vi; @@ -5531,9 +5807,9 @@ loop: for (;;) { } function style_child() { - if (next_token.arity === 'number') { + if (next_token.id === '(number)') { advance(); - if (next_token.value === 'n' && next_token.identifier) { + if (next_token.string === 'n' && next_token.identifier) { no_space_only(); advance(); if (next_token.id === '+') { @@ -5546,7 +5822,7 @@ loop: for (;;) { return; } else { if (next_token.identifier && - (next_token.value === 'odd' || next_token.value === 'even')) { + (next_token.string === 'odd' || next_token.string === 'even')) { advance(); return; } @@ -5561,13 +5837,9 @@ loop: for (;;) { (xquote && next_token.id === xquote)) { return; } - while (next_token.id === ';') { - warn('unexpected_a'); - semicolon(); - } v = style_attribute(); advance(':'); - if (next_token.identifier && next_token.value === 'inherit') { + if (next_token.identifier && next_token.string === 'inherit') { advance(); } else { if (!style_value(v)) { @@ -5578,15 +5850,15 @@ loop: for (;;) { if (next_token.id === '!') { advance('!'); no_space_only(); - if (next_token.identifier && next_token.value === 'important') { + if (next_token.identifier && next_token.string === 'important') { advance(); } else { warn('expected_a_b', - next_token, 'important', next_token.value); + next_token, 'important', artifact()); } } if (next_token.id === '}' || next_token.id === xquote) { - warn('expected_a_b', next_token, ';', next_token.value); + warn('expected_a_b', next_token, ';', artifact()); } else { semicolon(); } @@ -5596,7 +5868,7 @@ loop: for (;;) { function style_selector() { if (next_token.identifier) { if (!Object.prototype.hasOwnProperty.call(html_tag, option.cap ? - next_token.value.toLowerCase() : next_token.value)) { + next_token.string.toLowerCase() : next_token.string)) { warn('expected_tagname_a'); } advance(); @@ -5609,7 +5881,7 @@ loop: for (;;) { break; case ':': advance(':'); - switch (next_token.value) { + switch (next_token.string) { case 'active': case 'after': case 'before': @@ -5630,10 +5902,10 @@ loop: for (;;) { case 'root': case 'target': case 'visited': - advance(); + advance_identifier(next_token.string); break; case 'lang': - advance(); + advance_identifier('lang'); advance('('); if (!next_token.identifier) { warn('expected_lang_a'); @@ -5644,15 +5916,15 @@ loop: for (;;) { case 'nth-last-child': case 'nth-last-of-type': case 'nth-of-type': - advance(); + advance_identifier(next_token.string); advance('('); style_child(); advance(')'); break; case 'not': - advance(); + advance_identifier('not'); advance('('); - if (next_token.id === ':' && peek(0).value === 'not') { + if (next_token.id === ':' && peek(0).string === 'not') { warn('not'); } style_selector(); @@ -5685,13 +5957,13 @@ loop: for (;;) { warn('expected_attribute_a'); } advance(); - if (next_token.id === '=' || next_token.value === '~=' || - next_token.value === '$=' || - next_token.value === '|=' || + if (next_token.id === '=' || next_token.string === '~=' || + next_token.string === '$=' || + next_token.string === '|=' || next_token.id === '*=' || next_token.id === '^=') { advance(); - if (next_token.arity !== 'string') { + if (next_token.id !== '(string)') { warn('expected_string_a'); } advance(); @@ -5711,7 +5983,7 @@ loop: for (;;) { for (;;) { style_selector(); if (next_token.id === '= 0 || a.indexOf('url') >= 0) { if (option.safe && ux.test(v)) { - stop('bad_url', next_token, v); + stop('bad_url_a', next_token, v); } urls.push(v); } else if (a === 'for') { @@ -5929,13 +6203,13 @@ loop: for (;;) { stop('adsafe_script', token); } if (script.length !== 1 || - aint(script[0], 'id', '(') || - aint(script[0].first, 'id', '.') || - aint(script[0].first.first, 'value', 'ADSAFE') || - aint(script[0].second[0], 'value', adsafe_id)) { + aint(script[0], 'id', '(') || + aint(script[0].first, 'id', '.') || + aint(script[0].first.first, 'string', 'ADSAFE') || + aint(script[0].second[0], 'string', adsafe_id)) { stop('adsafe_id_go'); } - switch (script[0].first.second.value) { + switch (script[0].first.second.string) { case 'id': if (adsafe_may || adsafe_went || script[0].second.length !== 1) { @@ -5951,8 +6225,8 @@ loop: for (;;) { aint(script[0].second[1], 'id', 'function') || !script[0].second[1].first || script[0].second[1].first.length !== 2 || - aint(script[0].second[1].first[0], 'value', 'dom') || - aint(script[0].second[1].first[1], 'value', 'lib')) { + aint(script[0].second[1].first[0], 'string', 'dom') || + aint(script[0].second[1].first[1], 'string', 'lib')) { stop('adsafe_go', next_token); } adsafe_went = true; @@ -5965,10 +6239,7 @@ loop: for (;;) { } xmode = 'html'; advance('') { - warn('expected_a_b', next_token, '>', next_token.value); + warn('expected_a_b', next_token, '>', artifact()); } break; } - if (next_token.id && next_token.id.substr(0, 1) === '>') { + if (next_token.id && next_token.id.charAt(0) === '>') { break; } if (!next_token.identifier) { if (next_token.id === '(end)' || next_token.id === '(error)') { - warn('expected_a_b', next_token, '>', next_token.value); + warn('expected_a_b', next_token, '>', artifact()); } warn('bad_name_a'); } - option.white = true; + option.white = false; spaces(); - attribute = next_token.value; + attribute = next_token.string; option.white = old_white; advance(); if (!option.cap && attribute !== attribute.toLowerCase()) { @@ -6098,17 +6364,17 @@ loop: for (;;) { advance('='); quote = next_token.id; if (quote !== '"' && quote !== '\'') { - stop('expected_a_b', next_token, '"', next_token.value); + stop('expected_a_b', next_token, '"', artifact()); } xquote = quote; wmode = option.white; - option.white = false; + option.white = true; advance(quote); use_strict(); statements(); option.white = wmode; if (next_token.id !== quote) { - stop('expected_a_b', next_token, quote, next_token.value); + stop('expected_a_b', next_token, quote, artifact()); } xmode = 'html'; xquote = ''; @@ -6119,7 +6385,7 @@ loop: for (;;) { advance('='); quote = next_token.id; if (quote !== '"' && quote !== '\'') { - stop('expected_a_b', next_token, '"', next_token.value); + stop('expected_a_b', next_token, '"', artifact()); } xmode = 'styleproperty'; xquote = quote; @@ -6132,12 +6398,12 @@ loop: for (;;) { } else { if (next_token.id === '=') { advance('='); - tag = next_token.value; + tag = next_token.string; if (!next_token.identifier && next_token.id !== '"' && next_token.id !== '\'' && - next_token.arity !== 'string' && - next_token.arity !== 'number' && + next_token.id !== '(string)' && + next_token.id !== '(string)' && next_token.id !== '(color)') { warn('expected_attribute_value_a', token, attribute); } @@ -6162,7 +6428,7 @@ loop: for (;;) { if (!next_token.identifier) { warn('bad_name_a'); } - name = next_token.value; + name = next_token.string; if (option.cap) { name = name.toLowerCase(); } @@ -6179,7 +6445,7 @@ loop: for (;;) { next_token, closetag(tag_name.name), closetag(name)); } if (next_token.id !== '>') { - stop('expected_a_b', next_token, '>', next_token.value); + stop('expected_a_b', next_token, '>', artifact()); } xmode = 'outer'; advance('>'); @@ -6194,13 +6460,13 @@ loop: for (;;) { if (next_token.id === '>' || next_token.id === '(end)') { break; } - if (next_token.value.indexOf('--') >= 0) { + if (next_token.string.indexOf('--') >= 0) { stop('unexpected_a', next_token, '--'); } - if (next_token.value.indexOf('<') >= 0) { + if (next_token.string.indexOf('<') >= 0) { stop('unexpected_a', next_token, '<'); } - if (next_token.value.indexOf('>') >= 0) { + if (next_token.string.indexOf('>') >= 0) { stop('unexpected_a', next_token, '>'); } } @@ -6212,7 +6478,7 @@ loop: for (;;) { default: if (next_token.id === '(end)') { stop('missing_a', next_token, - ''); + ''); } else { advance(); } @@ -6230,14 +6496,16 @@ loop: for (;;) { // The actual JSLINT function itself. - var itself = function (the_source, the_option) { - var i, keys, predef, tree; - JSLINT.comments = []; + itself = function JSLint(the_source, the_option) { + + var i, predef, tree; JSLINT.errors = []; JSLINT.tree = ''; - begin = older_token = prev_token = token = next_token = + begin = prev_token = token = next_token = Object.create(syntax['(begin)']); - predefined = Object.create(standard); + predefined = {}; + add_to_predefined(standard); + property_type = Object.create(standard_property_type); if (the_option) { option = Object.create(the_option); predef = option.predef; @@ -6247,46 +6515,15 @@ loop: for (;;) { predefined[predef[i]] = true; } } else if (typeof predef === 'object') { - keys = Object.keys(predef); - for (i = 0; i < keys.length; i += 1) { - predefined[keys[i]] = !!predef[keys]; - } + add_to_predefined(predef); } } - if (option.adsafe) { - option.safe = true; - } - if (option.safe) { - option.browser = - option['continue'] = - option.css = - option.debug = - option.devel = - option.evil = - option.forin = - option.on = - option.rhino = - option.sub = - option.widget = - option.windows = false; - - option.nomen = - option.strict = - option.undef = true; - - predefined.Date = - predefined['eval'] = - predefined.Function = - predefined.Object = null; - - predefined.ADSAFE = - predefined.lib = false; - } + do_safe(); } else { option = {}; } - option.indent = +option.indent || 0; - option.maxerr = option.maxerr || 50; + option.indent = +option.indent || 4; + option.maxerr = +option.maxerr || 50; adsafe_id = ''; adsafe_may = adsafe_top = adsafe_went = false; approved = {}; @@ -6301,11 +6538,8 @@ loop: for (;;) { for (i = 0; i < option.indent; i += 1) { tab += ' '; } - global = Object.create(predefined); - scope = global; - funct = { - '(global)': true, - '(name)': '(global)', + global_scope = scope = {}; + global_funct = funct = { '(scope)': scope, '(breakage)': 0, '(loopage)': 0 @@ -6314,30 +6548,29 @@ loop: for (;;) { comments_off = false; ids = {}; - implied = {}; in_block = false; - indent = false; + indent = null; json_mode = false; lookahead = []; member = {}; - properties = null; + node_js = false; prereg = true; src = false; stack = null; strict_mode = false; urls = []; - var_mode = false; + var_mode = null; warnings = 0; - xmode = false; + xmode = ''; lex.init(the_source); assume(); try { advance(); - if (next_token.arity === 'number') { + if (next_token.id === '(number)') { stop('unexpected_a'); - } else if (next_token.value.charAt(0) === '<') { + } else if (next_token.string.charAt(0) === '<') { html(); if (option.adsafe && !adsafe_went) { warn('adsafe_go', this); @@ -6357,13 +6590,13 @@ loop: for (;;) { xmode = 'style'; advance(); if (token.id !== '@' || !next_token.identifier || - next_token.value !== 'charset' || token.line !== 1 || + next_token.string !== 'charset' || token.line !== 1 || token.from !== 1) { stop('css'); } advance(); - if (next_token.arity !== 'string' && - next_token.value !== 'UTF-8') { + if (next_token.id !== '(string)' && + next_token.string !== 'UTF-8') { stop('css'); } advance(); @@ -6374,32 +6607,29 @@ loop: for (;;) { default: if (option.adsafe && option.fragment) { stop('expected_a_b', - next_token, '
', next_token.value); + next_token, '
', artifact()); } -// If the first token is predef semicolon, ignore it. This is sometimes used when -// files are intended to be appended to files that may be sloppy. predef sloppy +// If the first token is a semicolon, ignore it. This is sometimes used when +// files are intended to be appended to files that may be sloppy. A sloppy // file may be depending on semicolon insertion on its last line. step_in(1); - if (next_token.id === ';') { + if (next_token.id === ';' && !node_js) { semicolon(); } - if (next_token.value === 'use strict') { - warn('function_strict'); - use_strict(); - } adsafe_top = true; tree = statements(); begin.first = tree; JSLINT.tree = begin; + // infer_types(tree); if (option.adsafe && (tree.length !== 1 || aint(tree[0], 'id', '(') || aint(tree[0].first, 'id', '.') || - aint(tree[0].first.first, 'value', 'ADSAFE') || - aint(tree[0].first.second, 'value', 'lib') || + aint(tree[0].first.first, 'string', 'ADSAFE') || + aint(tree[0].first.second, 'string', 'lib') || tree[0].second.length !== 2 || - tree[0].second[0].arity !== 'string' || + tree[0].second[0].id !== '(string)' || aint(tree[0].second[1], 'id', 'function'))) { stop('adsafe_lib'); } @@ -6430,12 +6660,12 @@ loop: for (;;) { function_data, globals, i, - implieds = [], j, kind, members = [], name, the_function, + undef = [], unused = []; if (itself.errors.length) { data.errors = itself.errors; @@ -6445,24 +6675,12 @@ loop: for (;;) { data.json = true; } - for (name in implied) { - if (Object.prototype.hasOwnProperty.call(implied, name)) { - implieds.push({ - name: name, - line: implied[name] - }); - } - } - if (implieds.length > 0) { - data.implieds = implieds; - } - if (urls.length > 0) { data.urls = urls; } - globals = Object.keys(functions[0]).filter(function (value) { - return value.charAt(0) !== '(' ? value : undefined; + globals = Object.keys(global_scope).filter(function (value) { + return value.charAt(0) !== '(' && typeof standard[value] !== 'boolean'; }); if (globals.length > 0) { data.globals = globals; @@ -6478,11 +6696,8 @@ loop: for (;;) { if (Object.prototype.hasOwnProperty.call(the_function, name)) { if (name.charAt(0) !== '(') { kind = the_function[name]; - if (kind === 'unction' || - (kind === 'unparam' && !option.unparam)) { + if (kind === 'unction' || kind === 'unparam') { kind = 'unused'; - } else if (typeof kind === 'boolean') { - kind = 'global'; } if (Array.isArray(function_data[kind])) { function_data[kind].push(name); @@ -6492,6 +6707,12 @@ loop: for (;;) { line: the_function['(line)'], 'function': the_function['(name)'] }); + } else if (kind === 'undef') { + undef.push({ + name: name, + line: the_function['(line)'], + 'function': the_function['(name)'] + }); } } } @@ -6503,14 +6724,18 @@ loop: for (;;) { } } function_data.name = the_function['(name)']; - function_data.param = the_function['(params)']; + function_data.params = the_function['(params)']; function_data.line = the_function['(line)']; + function_data['(complexity)'] = the_function['(complexity)']; data.functions.push(function_data); } if (unused.length > 0) { data.unused = unused; } + if (undef.length > 0) { + data['undefined'] = undef; + } members = []; for (name in member) { @@ -6525,28 +6750,28 @@ loop: for (;;) { itself.report = function (errors_only) { - var data = itself.data(); - - var err, evidence, i, j, key, keys, length, mem = '', name, names, - output = [], snippets, the_function, warning; + var data = itself.data(), err, evidence, i, italics, j, key, keys, length, + mem = '', name, names, output = [], snippets, the_function, type, + warning; - function detail(h, array) { - var comma_needed, i, singularity; - if (array) { + function detail(h, value) { + var comma_needed, singularity; + if (Array.isArray(value)) { output.push('
' + h + ' '); - array = array.sort(); - for (i = 0; i < array.length; i += 1) { - if (array[i] !== singularity) { - singularity = array[i]; + value.sort().forEach(function (item) { + if (item !== singularity) { + singularity = item; output.push((comma_needed ? ', ' : '') + singularity); comma_needed = true; } - } + }); output.push('
'); + } else if (value) { + output.push('
' + h + ' ' + value + '
'); } } - if (data.errors || data.implieds || data.unused) { + if (data.errors || data.unused || data['undefined']) { err = true; output.push('
Error:'); if (data.errors) { @@ -6554,8 +6779,9 @@ loop: for (;;) { warning = data.errors[i]; if (warning) { evidence = warning.evidence || ''; - output.push('

Problem' + (isFinite(warning.line) ? ' at line ' + - warning.line + ' character ' + warning.character : '') + + output.push('

Problem' + (isFinite(warning.line) ? + ' at line ' + String(warning.line) + ' character ' + + String(warning.character) : '') + ': ' + warning.reason.entityify() + '

' + (evidence && (evidence.length > 80 ? evidence.slice(0, 77) + '...' : @@ -6564,20 +6790,20 @@ loop: for (;;) { } } - if (data.implieds) { + if (data['undefined']) { snippets = []; - for (i = 0; i < data.implieds.length; i += 1) { - snippets[i] = '' + data.implieds[i].name + ' ' + - data.implieds[i].line + ''; + for (i = 0; i < data['undefined'].length; i += 1) { + snippets[i] = '' + data['undefined'][i].name + ' ' + + String(data['undefined'][i].line) + ' ' + + data['undefined'][i]['function'] + ''; } - output.push('

Implied global: ' + snippets.join(', ') + '

'); + output.push('

Undefined variable: ' + snippets.join(', ') + '

'); } - if (data.unused) { snippets = []; for (i = 0; i < data.unused.length; i += 1) { snippets[i] = '' + data.unused[i].name + ' ' + - data.unused[i].line + ' ' + + String(data.unused[i].line) + ' ' + data.unused[i]['function'] + ''; } output.push('

Unused variable: ' + snippets.join(', ') + '

'); @@ -6610,13 +6836,16 @@ loop: for (;;) { for (i = 0; i < data.functions.length; i += 1) { the_function = data.functions[i]; names = []; - if (the_function.param) { - for (j = 0; j < the_function.param.length; j += 1) { - names[j] = the_function.param[j].value; + if (the_function.params) { + for (j = 0; j < the_function.params.length; j += 1) { + names[j] = the_function.params[j].string; } } - output.push('
' + the_function.line + ' ' + - (the_function.name || '') + '(' + names.join(', ') + ')
'); + output.push('
' + + String(the_function.line) + ' ' + + the_function.name.entityify() + + '(' + names.join(', ') + ')
'); + detail('Undefined', the_function['undefined']); detail('Unused', the_function.unused); detail('Closure', the_function.closure); detail('Variable', the_function['var']); @@ -6624,31 +6853,71 @@ loop: for (;;) { detail('Outer', the_function.outer); detail('Global', the_function.global); detail('Label', the_function.label); + detail('Complexity', the_function['(complexity)']); } if (data.member) { keys = Object.keys(data.member); if (keys.length) { keys = keys.sort(); - mem = '
/*properties ';
-                    length = 13;
-                    for (i = 0; i < keys.length; i += 1) {
-                        key = keys[i];
-                        name = ix.test(key) ? key :
-                            '"' + key.entityify().replace(nx, sanitize) + '"';
-                        if (length + name.length > 72) {
-                            output.push(mem + '
'); - mem = ' '; - length = 1; - } - length += name.length + 2; - if (data.member[key] === 1) { - name = '' + name + ''; + output.push('
/*properties
'); + mem = ' '; + italics = 0; + j = 0; + if (option.confusion) { + for (i = 0; i < keys.length; i += 1) { + key = keys[i]; + if (typeof standard_property_type[key] !== 'string') { + name = ix.test(key) ? key : + '\'' + key.entityify().replace(nx, sanitize) + '\''; + if (data.member[key] === 1) { + name = '' + name + ''; + italics += 1; + j = 1; + } + if (i < keys.length - 1) { + name += ', '; + } + if (mem.length + name.length - (italics * 7) > 80) { + output.push(mem + '
'); + mem = ' '; + italics = j; + } + mem += name; + j = 0; + } } - if (i < keys.length - 1) { - name += ', '; + } else { + for (i = 0; i < keys.length; i += 1) { + key = keys[i]; + type = property_type[key]; + if (typeof type !== 'string') { + type = ''; + } + if (standard_property_type[key] !== type) { + name = ix.test(key) ? key : + '\'' + key.entityify().replace(nx, sanitize) + '\''; + length += name.length + 2; + if (data.member[key] === 1) { + name = '' + name + ''; + italics += 1; + j = 1; + } + if (type) { + name += ': ' + type; + } + if (i < keys.length - 1) { + name += ', '; + } + if (mem.length + name.length - (italics * 7) > 80) { + output.push(mem + '
'); + mem = ' '; + italics = j; + } + mem += name; + j = 0; + } } - mem += name; } output.push(mem + '
*/
'); } @@ -6659,7 +6928,7 @@ loop: for (;;) { }; itself.jslint = itself; - itself.edition = '2011-05-01'; + itself.edition = '2011-08-15'; return itself; diff --git a/scripts/jslint-to-xml.sh b/scripts/jslint-to-xml.sh index 5ba682b..afda5e4 100755 --- a/scripts/jslint-to-xml.sh +++ b/scripts/jslint-to-xml.sh @@ -103,7 +103,7 @@ else # If the tempfile contains "No problems found in", then the test passed # without issue. If not, failure! Oh, the misery! # - WAS_FAILURE=`cat "${TEMP}" | grep -v 'No problems found in'` + WAS_FAILURE=`cat "${TEMP}" | grep -v 'No problems found in' | grep -v 'No errors in' | grep -v 'Lint Free!'` # # And now we're completely finished with the temp file. Kill it. diff --git a/scripts/run-jslint.sh b/scripts/run-jslint.sh index aad607f..123bece 100755 --- a/scripts/run-jslint.sh +++ b/scripts/run-jslint.sh @@ -15,12 +15,12 @@ # ------ # The path (relative to this script file) to the Rhino JAR: -RHINO=../lib/js.jar +RHINO=js.jar -# The path (relative to this script file) to the JSLint JS files for Rhino: -RHINO_JSLINT=../lib/rhinoed_jslint.js -# and Node: -NODE_JSLINT=../lib/node_jslint.js +# The path (relative to this script file) to the CSSLint files for Rhino: +RHINO_CSSLINT=../lib/csslint-rhino.js +# and JSLint: +RHINO_JSLINT=../lib/jshint-rhino.js # Script (No touchies) # -------------------- @@ -30,9 +30,7 @@ NODE_JSLINT=../lib/node_jslint.js # Rhino and the two JSLinting instances. # SCRIPTPATH=`dirname ${0}` -RHINO="${SCRIPTPATH}/${RHINO}" -RHINO_JSLINT="${SCRIPTPATH}/${RHINO_JSLINT}" -NODE_JSLINT="${SCRIPTPATH}/${NODE_JSLINT}" +RHINO_CSSLINT="${SCRIPTPATH}/${RHINO_CSSLINT}" # # Write out usage information if we don't have a file to work with. @@ -62,6 +60,20 @@ else } awk -v PREPEND='@charset "UTF-8";' 'BEGIN {print PREPEND}{print}' "${LINTEE}" > "${TOPARSE}" TODISPLAY=$LINTEE + # + # We've got all the information we need: Now we need to decide + # which engine we use. If `node` exists, use it. It's amazingly + # fast. Much, much, much faster than Rhino. + # + if [ -n "`which csslint`" ]; then + csslint "${TOPARSE}" "${TODISPLAY}" + # + # Fallback to Rhino if we have to. Even though it's ugly and + # slow and makes babies cry. + # + else + java -jar "${SCRIPTPATH}/../lib/${RHINO}" "${RHINO_CSSLINT}" "${TOPARSE}" "${TODISPLAY}" + fi # # For everything else (that is, JavaScript, because what else is # there?) run against the file itself. @@ -69,20 +81,21 @@ else else TOPARSE=$LINTEE TODISPLAY=$LINTEE - fi - - # - # We've got all the information we need: Now we need to decide - # which engine we use. If `node` exists, use it. It's amazingly - # fast. Much, much, much faster than Rhino. - # - if [ -n "`which node`" ]; then - node "${NODE_JSLINT}" "${TOPARSE}" "${TODISPLAY}" - # - # Fallback to Rhino if we have to. Even though it's ugly and - # slow and makes babies cry. - # - else - java -jar "${RHINO}" "${RHINO_JSLINT}" "${TOPARSE}" "${TODISPLAY}" + # + # We've got all the information we need: Now we need to decide + # which engine we use. If `node` exists, use it. It's amazingly + # fast. Much, much, much faster than Rhino. + # + if [ -n "`which jshint`" ]; then + jshint "${TOPARSE}" "${TODISPLAY}" + # + # Fallback to Rhino if we have to. Even though it's ugly and + # slow and makes babies cry. + # + else + FULLDIR=`pwd` + cd lib/ + java -jar "${RHINO}" "${RHINO_JSLINT}" "${FULLDIR}/${TOPARSE}" "${TODISPLAY}" + fi fi fi diff --git a/test/src/error.css b/test/src/error.css index 6242895..4b7d0eb 100644 --- a/test/src/error.css +++ b/test/src/error.css @@ -1,3 +1,7 @@ omg-is-not-a-tagname { color: red; } + +test { + /* Look at the missing tag! */ + color: blue; diff --git a/test/src/error.js b/test/src/error.js index 037e917..6fc7732 100644 --- a/test/src/error.js +++ b/test/src/error.js @@ -1 +1,2 @@ x = "is not defined"; +{ \ No newline at end of file