diff --git a/lib/ace/mode/css/csslint.js b/lib/ace/mode/css/csslint.js
index e7d512c9..2c80f22c 100644
--- a/lib/ace/mode/css/csslint.js
+++ b/lib/ace/mode/css/csslint.js
@@ -22,8 +22,9 @@ THE SOFTWARE.
*/
define(function(require, exports, module) {
-/*
-Copyright (c) 2009 Nicholas C. Zakas. All rights reserved.
+/*!
+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
@@ -44,10 +45,10 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
+/* Build time: 13-July-2011 04:35:28 */
var parserlib = {};
(function(){
-
/**
* A generic base to inherit from for any object
* that needs event handling.
@@ -143,7 +144,7 @@ EventTarget.prototype = {
* @param {String} text The text to read.
*/
function StringReader(text){
-
+
/**
* The input text with line endings normalized.
* @property _input
@@ -151,8 +152,8 @@ function StringReader(text){
* @private
*/
this._input = text.replace(/\n\r?/g, "\n");
-
-
+
+
/**
* The row for the character to be read next.
* @property _line
@@ -160,8 +161,8 @@ function StringReader(text){
* @private
*/
this._line = 1;
-
-
+
+
/**
* The column for the character to be read next.
* @property _col
@@ -169,13 +170,13 @@ function StringReader(text){
* @private
*/
this._col = 1;
-
+
/**
* The index of the character in the input to be read next.
* @property _cursor
* @type int
* @private
- */
+ */
this._cursor = 0;
}
@@ -183,11 +184,11 @@ 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.
@@ -196,29 +197,29 @@ StringReader.prototype = {
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)
+ 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).
@@ -228,17 +229,17 @@ StringReader.prototype = {
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){
-
+ 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.
@@ -247,10 +248,10 @@ StringReader.prototype = {
*/
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"){
@@ -259,18 +260,18 @@ StringReader.prototype = {
} 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
@@ -283,7 +284,7 @@ StringReader.prototype = {
col: this._col
};
},
-
+
reset: function(){
if (this._bookmark){
this._cursor = this._bookmark.cursor;
@@ -292,11 +293,11 @@ StringReader.prototype = {
delete this._bookmark;
}
},
-
+
//-------------------------------------------------------------------------
// Advanced reading
//-------------------------------------------------------------------------
-
+
/**
* Reads up to and including the given string. Throws an error if that
* string is not found.
@@ -304,9 +305,9 @@ StringReader.prototype = {
* @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;
@@ -323,11 +324,11 @@ StringReader.prototype = {
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
@@ -337,21 +338,21 @@ StringReader.prototype = {
* @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
@@ -363,41 +364,41 @@ StringReader.prototype = {
* @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);
+ value = this.readCount(matcher.length);
}
} else if (matcher instanceof RegExp){
if (matcher.test(source)){
value = this.readCount(RegExp.lastMatch.length);
}
}
-
- return value;
+
+ 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;
}
@@ -575,7 +576,7 @@ function TokenStreamBase(input, tokenData){
*/
TokenStreamBase.createTokenData = function(tokens){
- var nameMap = [],
+ var nameMap = [],
typeMap = {},
tokenData = tokens.concat([]),
i = 0,
@@ -669,7 +670,7 @@ TokenStreamBase.prototype = {
if (!this.match.apply(this, arguments)){
token = this.LT(1);
throw new SyntaxError("Expected " + this._tokenData[tokenTypes[0]].name +
- " at line " + token.startLine + ", character " + token.startCol + ".", token.startLine, token.startCol);
+ " at line " + token.startLine + ", col " + token.startCol + ".", token.startLine, token.startCol);
}
},
@@ -910,7 +911,6 @@ TokenStreamBase.prototype = {
-
parserlib.util = {
StringReader: StringReader,
SyntaxError : SyntaxError,
@@ -920,9 +920,9 @@ TokenStreamBase : TokenStreamBase
};
})();
-
/*
-Copyright (c) 2009 Nicholas C. Zakas. All rights reserved.
+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
@@ -943,6 +943,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
+/* Build time: 13-July-2011 04:35:28 */
(function(){
var EventTarget = parserlib.util.EventTarget,
TokenStreamBase = parserlib.util.TokenStreamBase,
@@ -950,7 +951,6 @@ StringReader = parserlib.util.StringReader,
SyntaxError = parserlib.util.SyntaxError,
SyntaxUnit = parserlib.util.SyntaxUnit;
-
var Colors = {
aliceblue :"#f0f8ff",
antiquewhite :"#faebd7",
@@ -1425,7 +1425,7 @@ Parser.prototype = function(){
* : [ CHARSET_SYM S* STRING S* ';' ]?
* [S|CDO|CDC]* [ import [S|CDO|CDC]* ]*
* [ namespace [S|CDO|CDC]* ]*
- * [ [ ruleset | media | page | font_face ] [S|CDO|CDC]* ]*
+ * [ [ ruleset | media | page | font_face | keyframes ] [S|CDO|CDC]* ]*
* ;
*/
@@ -1474,6 +1474,10 @@ Parser.prototype = function(){
this._font_face();
this._skipCruft();
break;
+ case Tokens.KEYFRAMES_SYM:
+ this._keyframes();
+ this._skipCruft();
+ break;
case Tokens.S:
this._readWhitespace();
break;
@@ -1526,8 +1530,16 @@ Parser.prototype = function(){
},
_charset: function(emit){
- var tokenStream = this._tokenStream;
+ 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);
@@ -1540,7 +1552,9 @@ Parser.prototype = function(){
if (emit !== false){
this.fire({
type: "charset",
- charset:charset
+ charset:charset,
+ line: line,
+ col: col
});
}
}
@@ -1556,10 +1570,12 @@ Parser.prototype = function(){
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]);
@@ -1579,7 +1595,9 @@ Parser.prototype = function(){
this.fire({
type: "import",
uri: uri,
- media: mediaList
+ media: mediaList,
+ line: importToken.startLine,
+ col: importToken.startCol
});
}
@@ -1592,11 +1610,15 @@ Parser.prototype = function(){
*/
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
@@ -1623,7 +1645,9 @@ Parser.prototype = function(){
this.fire({
type: "namespace",
prefix: prefix,
- uri: uri
+ uri: uri,
+ line: line,
+ col: col
});
}
@@ -1636,10 +1660,15 @@ Parser.prototype = function(){
* ;
*/
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();
@@ -1649,17 +1678,27 @@ Parser.prototype = function(){
this.fire({
type: "startmedia",
- media: mediaList
+ media: mediaList,
+ line: line,
+ col: col
});
- while(this._ruleset()){}
+ 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
+ media: mediaList,
+ line: line,
+ col: col
});
},
@@ -1677,8 +1716,8 @@ Parser.prototype = function(){
this._readWhitespace();
- if (tokenStream.peek() == Tokens.IDENT){
- mediaList.push(this._media_query())
+ if (tokenStream.peek() == Tokens.IDENT || tokenStream.peek() == Tokens.LPAREN){
+ mediaList.push(this._media_query());
}
while(tokenStream.match(Tokens.COMMA)){
@@ -1819,11 +1858,16 @@ Parser.prototype = function(){
* ;
*/
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)){
@@ -1845,7 +1889,9 @@ Parser.prototype = function(){
this.fire({
type: "startpage",
id: identifier,
- pseudo: pseudoPage
+ pseudo: pseudoPage,
+ line: line,
+ col: col
});
this._readDeclarations(true, true);
@@ -1853,7 +1899,9 @@ Parser.prototype = function(){
this.fire({
type: "endpage",
id: identifier,
- pseudo: pseudoPage
+ pseudo: pseudoPage,
+ line: line,
+ col: col
});
},
@@ -1866,19 +1914,28 @@ Parser.prototype = function(){
* ;
*/
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
+ margin: marginSym,
+ line: line,
+ col: col
});
this._readDeclarations(true);
this.fire({
type: "endpagemargin",
- margin: marginSym
+ margin: marginSym,
+ line: line,
+ col: col
});
return true;
} else {
@@ -1951,20 +2008,29 @@ Parser.prototype = function(){
* '{' S* declaration [ ';' S* declaration ]* '}' S*
* ;
*/
- var tokenStream = this._tokenStream;
+ 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"
+ type: "startfontface",
+ line: line,
+ col: col
});
this._readDeclarations(true);
this.fire({
- type: "endfontface"
+ type: "endfontface",
+ line: line,
+ col: col
});
},
@@ -2077,6 +2143,7 @@ Parser.prototype = function(){
*/
var tokenStream = this._tokenStream,
+ tt,
selectors;
@@ -2121,14 +2188,18 @@ Parser.prototype = function(){
this.fire({
type: "startrule",
- selectors: selectors
+ selectors: selectors,
+ line: selectors[0].line,
+ col: selectors[0].col
});
this._readDeclarations(true);
this.fire({
type: "endrule",
- selectors: selectors
+ selectors: selectors,
+ line: selectors[0].line,
+ col: selectors[0].col
});
}
@@ -2584,7 +2655,7 @@ Parser.prototype = function(){
while(tokenStream.match([Tokens.PLUS, Tokens.MINUS, Tokens.DIMENSION,
Tokens.NUMBER, Tokens.STRING, Tokens.IDENT, Tokens.LENGTH,
- Tokens.FREQ, Tokens.EMS, Tokens.EXS, Tokens.ANGLE, Tokens.TIME,
+ Tokens.FREQ, Tokens.ANGLE, Tokens.TIME,
Tokens.RESOLUTION])){
value += tokenStream.token().value;
@@ -2697,7 +2768,7 @@ Parser.prototype = function(){
property = this._property();
if (property !== null){
-
+
tokenStream.mustMatch(Tokens.COLON);
this._readWhitespace();
@@ -2714,7 +2785,9 @@ Parser.prototype = function(){
type: "property",
property: property,
value: expr,
- important: prio
+ important: prio,
+ line: property.line,
+ col: property.col
});
return true;
@@ -2790,7 +2863,7 @@ Parser.prototype = function(){
/*
* term
* : unary_operator?
- * [ NUMBER S* | PERCENTAGE S* | LENGTH S* | EMS S* | EXS S* | ANGLE S* |
+ * [ NUMBER S* | PERCENTAGE S* | LENGTH S* | ANGLE S* |
* TIME S* | FREQ S* | function | ie_function ]
* | STRING S* | IDENT S* | URI S* | UNICODERANGE S* | hexcolor
* ;
@@ -2820,7 +2893,7 @@ Parser.prototype = function(){
//see if there's a simple match
} else if (tokenStream.match([Tokens.NUMBER, Tokens.PERCENTAGE, Tokens.LENGTH,
- Tokens.EMS, Tokens.EXS, Tokens.ANGLE, Tokens.TIME,
+ Tokens.ANGLE, Tokens.TIME,
Tokens.FREQ, Tokens.STRING, Tokens.IDENT, Tokens.URI, Tokens.UNICODE_RANGE])){
value = tokenStream.token().value;
@@ -2893,7 +2966,7 @@ Parser.prototype = function(){
expr = this._expr();
tokenStream.match(Tokens.RPAREN);
- functionText += expr + ")"
+ functionText += expr + ")";
this._readWhitespace();
}
@@ -2944,7 +3017,7 @@ Parser.prototype = function(){
} while(tokenStream.match([Tokens.COMMA, Tokens.S]));
tokenStream.match(Tokens.RPAREN);
- functionText += ")"
+ functionText += ")";
this._readWhitespace();
}
@@ -2973,7 +3046,7 @@ Parser.prototype = function(){
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 + ", character " + token.startCol + ".", token.startLine, token.startCol);
+ throw new SyntaxError("Expected a hex color but found '" + color + "' at line " + token.startLine + ", col " + token.startCol + ".", token.startLine, token.startCol);
}
this._readWhitespace();
}
@@ -2981,6 +3054,158 @@ Parser.prototype = function(){
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
//-----------------------------------------------------------------
@@ -3117,7 +3342,7 @@ Parser.prototype = function(){
* @private
*/
_unexpectedToken: function(token){
- throw new SyntaxError("Unexpected token '" + token.value + "' at line " + token.startLine + ", char " + token.startCol + ".", token.startLine, token.startCol);
+ throw new SyntaxError("Unexpected token '" + token.value + "' at line " + token.startLine + ", col " + token.startCol + ".", token.startLine, token.startCol);
},
/**
@@ -3556,7 +3781,7 @@ SelectorSubPart.prototype.constructor = SelectorSubPart;
-
+
var h = /^[0-9a-fA-F]$/,
nonascii = /^[\u0080-\uFFFF]$/,
nl = /\n|\r\n|\r|\f/;
@@ -3564,8 +3789,8 @@ var h = /^[0-9a-fA-F]$/,
//-----------------------------------------------------------------------------
// Helper functions
//-----------------------------------------------------------------------------
-
-
+
+
function isHexDigit(c){
return c != null && h.test(c);
}
@@ -3587,11 +3812,11 @@ function isNameStart(c){
}
function isNameChar(c){
- return c != null && (isNameStart(c) || /[0-9\-]/.test(c));
+ return c != null && (isNameStart(c) || /[0-9\-\\]/.test(c));
}
function isIdentStart(c){
- return c != null && (isNameStart(c) || c == "-");
+ return c != null && (isNameStart(c) || /\-\\/.test(c));
}
function mix(receiver, supplier){
@@ -3631,19 +3856,19 @@ TokenStream.prototype = mix(new TokenStreamBase(), {
* @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
@@ -3657,8 +3882,8 @@ TokenStream.prototype = mix(new TokenStreamBase(), {
} else {
token = this.charToken(c, startLine, startCol);
}
- break;
-
+ break;
+
/*
* Potential tokens:
* - DASHMATCH
@@ -3678,8 +3903,8 @@ TokenStream.prototype = mix(new TokenStreamBase(), {
} else {
token = this.charToken(c, startLine, startCol);
}
- break;
-
+ break;
+
/*
* Potential tokens:
* - STRING
@@ -3687,9 +3912,9 @@ TokenStream.prototype = mix(new TokenStreamBase(), {
*/
case "\"":
case "'":
- token = this.stringToken(c, startLine, startCol);
+ token = this.stringToken(c, startLine, startCol);
break;
-
+
/*
* Potential tokens:
* - HASH
@@ -3697,12 +3922,12 @@ TokenStream.prototype = mix(new TokenStreamBase(), {
*/
case "#":
if (isNameChar(reader.peek())){
- token = this.hashToken(c, startLine, startCol);
+ token = this.hashToken(c, startLine, startCol);
} else {
token = this.charToken(c, startLine, startCol);
- }
+ }
break;
-
+
/*
* Potential tokens:
* - DOT
@@ -3712,12 +3937,12 @@ TokenStream.prototype = mix(new TokenStreamBase(), {
*/
case ".":
if (isDigit(reader.peek())){
- token = this.numberToken(c, startLine, startCol);
+ token = this.numberToken(c, startLine, startCol);
} else {
token = this.charToken(c, startLine, startCol);
}
- break;
-
+ break;
+
/*
* Potential tokens:
* - CDC
@@ -3735,7 +3960,7 @@ TokenStream.prototype = mix(new TokenStreamBase(), {
token = this.charToken(c, startLine, startCol);
}
break;
-
+
/*
* Potential tokens:
* - IMPORTANT_SYM
@@ -3744,14 +3969,14 @@ TokenStream.prototype = mix(new TokenStreamBase(), {
case "!":
token = this.importantToken(c, startLine, startCol);
break;
-
+
/*
* Any at-keyword or CHAR
*/
case "@":
token = this.atRuleToken(c, startLine, startCol);
break;
-
+
/*
* Potential tokens:
* - NOT
@@ -3759,8 +3984,8 @@ TokenStream.prototype = mix(new TokenStreamBase(), {
*/
case ":":
token = this.notToken(c, startLine, startCol);
- break;
-
+ break;
+
/*
* Potential tokens:
* - CDO
@@ -3768,7 +3993,7 @@ TokenStream.prototype = mix(new TokenStreamBase(), {
*/
case "<":
token = this.htmlCommentStartToken(c, startLine, startCol);
- break;
+ break;
/*
* Potential tokens:
@@ -3781,11 +4006,11 @@ TokenStream.prototype = mix(new TokenStreamBase(), {
if (reader.peek() == "+"){
token = this.unicodeRangeToken(c, startLine, startCol);
break;
- }
+ }
/*falls through*/
-
+
default:
-
+
/*
* Potential tokens:
* - NUMBER
@@ -3799,58 +4024,58 @@ TokenStream.prototype = mix(new TokenStreamBase(), {
*/
if (isDigit(c)){
token = this.numberToken(c, startLine, startCol);
- } else
-
+ } else
+
/*
* Potential tokens:
* - S
*/
if (isWhitespace(c)){
token = this.whitespaceToken(c, startLine, startCol);
- } else
-
+ } else
+
/*
* Potential tokens:
* - IDENT
- */
+ */
if (isIdentStart(c)){
token = this.identOrFunctionToken(c, startLine, startCol);
- } else
-
+ } else
+
/*
* Potential tokens:
* - CHAR
* - PLUS
*/
{
- token = this.charToken(c, startLine, startCol);
+ 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
@@ -3865,11 +4090,11 @@ TokenStream.prototype = mix(new TokenStreamBase(), {
* 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,
@@ -3878,14 +4103,14 @@ TokenStream.prototype = mix(new TokenStreamBase(), {
startLine: startLine,
startCol: startCol,
endLine: reader.getLine(),
- endCol: reader.getCol()
- };
- },
-
+ 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.
@@ -3894,15 +4119,15 @@ TokenStream.prototype = mix(new TokenStreamBase(), {
* @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;
-
+ c;
+
/*
* First, mark where we are. There are only four @ rules,
* so anything else is really just an invalid token.
@@ -3911,22 +4136,22 @@ TokenStream.prototype = mix(new TokenStreamBase(), {
* parsing to continue after that point.
*/
reader.mark();
-
- //try to find the at-keyword
+
+ //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);
- },
-
+ }
+
+ 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)
@@ -3943,10 +4168,10 @@ TokenStream.prototype = mix(new TokenStreamBase(), {
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)
@@ -3956,14 +4181,14 @@ TokenStream.prototype = mix(new TokenStreamBase(), {
* @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);
- },
-
+ 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
@@ -3978,10 +4203,10 @@ TokenStream.prototype = mix(new TokenStreamBase(), {
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
@@ -3996,9 +4221,9 @@ TokenStream.prototype = mix(new TokenStreamBase(), {
var reader = this._reader,
name = this.readName(first);
- return this.createToken(Tokens.HASH, name, startLine, startCol);
+ 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
@@ -4008,22 +4233,22 @@ TokenStream.prototype = mix(new TokenStreamBase(), {
* @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();
+ 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
@@ -4058,7 +4283,7 @@ TokenStream.prototype = mix(new TokenStreamBase(), {
* @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),
@@ -4070,7 +4295,7 @@ TokenStream.prototype = mix(new TokenStreamBase(), {
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;
@@ -4079,7 +4304,7 @@ TokenStream.prototype = mix(new TokenStreamBase(), {
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("(");
@@ -4087,9 +4312,9 @@ TokenStream.prototype = mix(new TokenStreamBase(), {
}
}
- return this.createToken(tt, ident, startLine, startCol);
+ 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
@@ -4099,7 +4324,7 @@ TokenStream.prototype = mix(new TokenStreamBase(), {
* @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,
@@ -4109,12 +4334,12 @@ TokenStream.prototype = mix(new TokenStreamBase(), {
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;
@@ -4131,24 +4356,24 @@ TokenStream.prototype = mix(new TokenStreamBase(), {
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);
}
-
-
+
+
},
/**
@@ -4160,14 +4385,14 @@ TokenStream.prototype = mix(new TokenStreamBase(), {
* @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();
+ reader.mark();
text += reader.readCount(4);
-
+
if (text.toLowerCase() == ":not("){
return this.createToken(Tokens.NOT, text, startLine, startCol);
} else {
@@ -4186,31 +4411,27 @@ TokenStream.prototype = mix(new TokenStreamBase(), {
* @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;
+ value += ident;
- if (/em/i.test(ident)){
- tt = Tokens.EMS;
- } else if (/ex/i.test(ident)){
- tt = Tokens.EXS;
- } else if (/px|cm|mm|in|pt|pc/i.test(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)){
+ } else if (/^deg|^rad$|^grad$/i.test(ident)){
tt = Tokens.ANGLE;
- } else if (/ms|s/i.test(ident)){
+ } else if (/^ms$|^s$/i.test(ident)){
tt = Tokens.TIME;
- } else if (/hz|khz/i.test(ident)){
+ } else if (/^hz$|^khz$/i.test(ident)){
tt = Tokens.FREQ;
- } else if (/dpi|dpcm/i.test(ident)){
+ } else if (/^dpi$|^dpcm$/i.test(ident)){
tt = Tokens.RESOLUTION;
} else {
tt = Tokens.DIMENSION;
@@ -4220,10 +4441,10 @@ TokenStream.prototype = mix(new TokenStreamBase(), {
value += reader.read();
tt = Tokens.PERCENTAGE;
}
-
- return this.createToken(tt, value, startLine, startCol);
- },
-
+
+ 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
@@ -4236,7 +4457,7 @@ TokenStream.prototype = mix(new TokenStreamBase(), {
* @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,
@@ -4244,10 +4465,10 @@ TokenStream.prototype = mix(new TokenStreamBase(), {
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;
@@ -4258,47 +4479,47 @@ TokenStream.prototype = mix(new TokenStreamBase(), {
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);
- },
-
+
+ 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();
@@ -4310,10 +4531,10 @@ TokenStream.prototype = mix(new TokenStreamBase(), {
}
}
}
-
+
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
@@ -4323,57 +4544,57 @@ TokenStream.prototype = mix(new TokenStreamBase(), {
* @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);
- },
-
+ return this.createToken(Tokens.S, value, startLine, startCol);
+ },
+
//-------------------------------------------------------------------------
// Methods to read values from the string stream
//-------------------------------------------------------------------------
-
+
readUnicodeRangePart: function(allowQuestionMark){
var reader = this._reader,
- part = "",
+ part = "",
c = reader.peek();
-
+
//first read hex digits
while(isHexDigit(c) && part.length < 6){
reader.read();
part += c;
- c = reader.peek();
+ c = reader.peek();
}
-
+
//then read question marks if allowed
if (allowQuestionMark){
while(c == "?" && part.length < 6){
reader.read();
part += c;
- c = reader.peek();
+ c = reader.peek();
}
}
//there can't be any other characters after this point
-
- return part;
+
+ return part;
},
-
+
readWhitespace: function(){
var reader = this._reader,
whitespace = "",
c = reader.peek();
-
+
while(isWhitespace(c)){
reader.read();
whitespace += c;
- c = reader.peek();
+ c = reader.peek();
}
-
+
return whitespace;
},
readNumber: function(first){
@@ -4381,7 +4602,7 @@ TokenStream.prototype = mix(new TokenStreamBase(), {
number = first,
hasDot = (first == "."),
c = reader.peek();
-
+
while(c){
if (isDigit(c)){
@@ -4396,23 +4617,23 @@ TokenStream.prototype = mix(new TokenStreamBase(), {
} else {
break;
}
-
+
c = reader.peek();
- }
-
+ }
+
return number;
},
readString: function(){
var reader = this._reader,
delim = reader.read(),
- string = delim,
+ 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;
@@ -4423,17 +4644,17 @@ TokenStream.prototype = mix(new TokenStreamBase(), {
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){
@@ -4441,18 +4662,30 @@ TokenStream.prototype = mix(new TokenStreamBase(), {
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;
@@ -4460,64 +4693,90 @@ TokenStream.prototype = mix(new TokenStreamBase(), {
} 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(c && isNameChar(c)){
- ident += reader.read();
- 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);
}
- return ident;
- },
+ 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 "";
}
-
- },
-
-
-
+ }
});
@@ -4555,13 +4814,14 @@ var Tokens = [
{ 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: "EMS"},
- { name: "EXS"},
{ name: "LENGTH"},
{ name: "ANGLE"},
{ name: "TIME"},
@@ -4704,7 +4964,13 @@ var Tokens = [
nameMap.push(Tokens[i].name);
Tokens[Tokens[i].name] = i;
if (Tokens[i].text){
- typeMap[Tokens[i].text] = i;
+ 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;
+ }
}
}
@@ -4722,7 +4988,6 @@ var Tokens = [
-
parserlib.css = {
Colors :Colors,
Combinator :Combinator,
@@ -4740,9 +5005,6 @@ Tokens :Tokens
};
})();
-
-
-
/**
* Main CSSLint object.
* @class CSSLint
@@ -4751,13 +5013,16 @@ Tokens :Tokens
*/
var CSSLint = (function(){
- var rules = [],
- api = new parserlib.util.EventTarget();
+ var rules = [],
+ formatters = [],
+ api = new parserlib.util.EventTarget();
+
+ api.version = "@VERSION@";
//-------------------------------------------------------------------------
// Rule Management
//-------------------------------------------------------------------------
-
+
/**
* Adds a new rule to the engine.
* @param {Object} rule The rule to add.
@@ -4767,7 +5032,7 @@ var CSSLint = (function(){
rules.push(rule);
rules[rule.id] = rule;
};
-
+
/**
* Clears all rule from the engine.
* @method clearRules
@@ -4776,67 +5041,120 @@ var CSSLint = (function(){
rules = [];
};
+ //-------------------------------------------------------------------------
+ // 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} options (Optional) List of rules to apply. If null, then
+ * @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, options){
-
+ api.verify = function(text, ruleset){
+
var i = 0,
len = rules.length,
reporter,
lines,
- parser = new parserlib.css.Parser({ starHack: true, ieFilters: true,
+ parser = new parserlib.css.Parser({ starHack: true, ieFilters: true,
underscoreHack: true, strict: false });
lines = text.split(/\n\r?/g);
reporter = new Reporter(lines);
-
- if (!options){
+
+ if (!ruleset){
while (i < len){
rules[i++].init(parser, reporter);
}
} else {
- for (i in options){
- if(options.hasOwnProperty(i)){
+ 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);
}
-
+
return {
messages : reporter.messages,
stats : reporter.stats
};
};
-
//-------------------------------------------------------------------------
// Publish the API
//-------------------------------------------------------------------------
-
+
return api;
})();
-
-
/**
* An instance of Report is used to report results of the
* verification back to the main API.
@@ -4852,14 +5170,14 @@ function Reporter(lines){
* @type String[]
*/
this.messages = [];
-
+
/**
* List of statistics being reported.
* @property stats
* @type String[]
*/
- this.stats = [];
-
+ this.stats = [];
+
/**
* Lines of code being reported on. Used to provide contextual information
* for messages.
@@ -4873,7 +5191,7 @@ Reporter.prototype = {
//restore constructor
constructor: Reporter,
-
+
/**
* Report an error.
* @param {String} message The message to store.
@@ -4892,7 +5210,7 @@ Reporter.prototype = {
rule : rule
});
},
-
+
/**
* Report an warning.
* @param {String} message The message to store.
@@ -4911,7 +5229,7 @@ Reporter.prototype = {
rule : rule
});
},
-
+
/**
* Report some informational text.
* @param {String} message The message to store.
@@ -4930,7 +5248,7 @@ Reporter.prototype = {
rule : rule
});
},
-
+
/**
* Report some rollup error information.
* @param {String} message The message to store.
@@ -4945,7 +5263,7 @@ Reporter.prototype = {
rule : rule
});
},
-
+
/**
* Report some rollup warning information.
* @param {String} message The message to store.
@@ -4960,7 +5278,7 @@ Reporter.prototype = {
rule : rule
});
},
-
+
/**
* Report a statistic.
* @param {String} name The name of the stat to store.
@@ -4985,13 +5303,13 @@ Reporter.prototype = {
*/
function mix(reciever, supplier){
var prop;
-
+
for (prop in supplier){
if (supplier.hasOwnProperty(prop)){
receiver[prop] = supplier[prop];
}
}
-
+
return prop;
}
@@ -5022,8 +5340,8 @@ CSSLint.addRule({
id: "adjoining-classes",
name: "Adjoining Classes",
desc: "Don't use adjoining classes.",
- browsers: "IE6, IE7",
-
+ browsers: "IE6",
+
//initialization
init: function(parser, reporter){
var rule = this;
@@ -5034,26 +5352,26 @@ CSSLint.addRule({
modifier,
classCount,
i, j, k;
-
+
for (i=0; i < selectors.length; i++){
selector = selectors[i];
- for (j=0; j < selector.parts.length; j++){
+ 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++;
+ classCount++;
}
if (classCount > 1){
reporter.warn("Don't use adjoining classes.", part.line, part.col, rule);
}
}
- }
+ }
}
}
- });
+ });
}
});
@@ -5067,59 +5385,253 @@ CSSLint.addRule({
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,
- propertiesToCheck = {
+ widthProperties = {
border: 1,
"border-left": 1,
"border-right": 1,
- "border-bottom": 1,
- "border-top": 1,
padding: 1,
"padding-left": 1,
- "padding-right": 1,
+ "padding-right": 1
+ },
+ heightProperties = {
+ border: 1,
+ "border-bottom": 1,
+ "border-top": 1,
+ padding: 1,
"padding-bottom": 1,
- "padding-top": 1
+ "padding-top": 1
},
properties;
-
- parser.addListener("startrule", function(event){
- properties = {
+
+ parser.addListener("startrule", function(){
+ properties = {
};
});
-
+
parser.addListener("property", function(event){
- var name = event.property;
+ var name = event.property.text.toLowerCase();
- if (propertiesToCheck[name]){
- properties[name] = { line: name.line, col: name.col };
+ 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._flagProperty = name.text;
+ properties[name] = 1;
}
}
});
-
- parser.addListener("endrule", function(event){
+
+ parser.addListener("endrule", function(){
var prop;
- if (properties._flagProperty){
- for (prop in propertiesToCheck){
- if (propertiesToCheck.hasOwnProperty(prop) && properties[prop]){
- reporter.warn("Broken box model: using " + properties._flagProperty + " with " + prop + ".", properties[prop].line, properties[prop].col, rule);
+ 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, padding, float should not be used with inline
+ * - 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-*
*/
@@ -5130,75 +5642,68 @@ CSSLint.addRule({
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": 1,
+ "float": "none",
height: 1,
width: 1,
margin: 1,
"margin-left": 1,
"margin-right": 1,
"margin-bottom": 1,
- "margin-top": 1,
+ "margin-top": 1,
padding: 1,
"padding-left": 1,
"padding-right": 1,
"padding-bottom": 1,
- "padding-top": 1,
+ "padding-top": 1,
"vertical-align": 1
},
properties;
-
- parser.addListener("startrule", function(event){
- properties = {};
- });
+
+ parser.addListener("startrule", function(){
+ properties = {};
+ });
parser.addListener("property", function(event){
- var name = event.property;
-
+ var name = event.property.text.toLowerCase();
+
if (propertiesToCheck[name]){
- properties[name] = { value: event.value.text, line: name.line, col: name.col };
- }
- });
-
- parser.addListener("endrule", function(event){
-
+ 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, padding, float should not be used with 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-left", display);
- reportProperty("margin-right", display);
reportProperty("margin-top", display);
- reportProperty("margin-bottom", display);
- reportProperty("padding", display);
- reportProperty("padding-left", display);
- reportProperty("padding-right", display);
- reportProperty("padding-top", display);
- reportProperty("padding-bottom", display);
- reportProperty("float", 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){
@@ -5207,23 +5712,68 @@ CSSLint.addRule({
reportProperty("margin-right", display);
reportProperty("margin-top", display);
reportProperty("margin-bottom", display);
- reportProperty("float", display);
+ reportProperty("float", display);
}
-
- //otherwise do nothing
+
+ //otherwise do nothing
}
}
- });
-
-
- function reportProperty(name, display){
+ });
+
+
+ function reportProperty(name, display, msg){
if (properties[name]){
- reporter.warn(name + " can't be used with display: " + display + ".", properties[name].line, properties[name].col, rule);
- }
+ 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.
@@ -5235,26 +5785,26 @@ CSSLint.addRule({
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(event){
+ count = 0;
+
+ parser.addListener("startrule", function(){
count=0;
});
-
- parser.addListener("property", function(event){
+
+ 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);
}
- });
+ });
}
});
@@ -5268,11 +5818,11 @@ CSSLint.addRule({
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);
});
@@ -5291,26 +5841,27 @@ CSSLint.addRule({
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 == "float"){
+ if (event.property.text.toLowerCase() == "float" &&
+ event.value.text.toLowerCase() != "none"){
count++;
}
});
-
+
//report the results
- parser.addListener("endstylesheet", function(event){
+ parser.addListener("endstylesheet", function(){
reporter.stat("floats", count);
if (count >= 10){
- reporter.rollupWarn("Too many floats (" + count + "), abstraction needed.", rule);
+ reporter.rollupWarn("Too many floats (" + count + "), you're probably using them for layout. Consider using a grid system instead.", rule);
}
- });
+ });
}
});
@@ -5324,22 +5875,22 @@ CSSLint.addRule({
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(event){
+
+
+ parser.addListener("startfontface", function(){
count++;
});
- parser.addListener("endstylesheet", function(event){
+ parser.addListener("endstylesheet", function(){
if (count > 5){
reporter.rollupWarn("Too many @font-face declarations (" + count + ").", rule);
}
- });
+ });
}
});
@@ -5354,27 +5905,26 @@ CSSLint.addRule({
name: "Font Sizes",
desc: "Checks the number of font-size declarations.",
browsers: "All",
-
+
//initialization
init: function(parser, reporter){
- var rule = this,
+ var rule = this,
count = 0;
-
+
//check for use of "font-size"
parser.addListener("property", function(event){
- var part = event.value.parts[0];
if (event.property == "font-size"){
- count++;
+ count++;
}
});
-
+
//report the results
- parser.addListener("endstylesheet", function(event){
+ parser.addListener("endstylesheet", function(){
reporter.stat("font-sizes", count);
if (count >= 10){
reporter.rollupWarn("Too many font-size declarations (" + count + "), abstraction needed.", rule);
}
- });
+ });
}
});
@@ -5388,13 +5938,13 @@ CSSLint.addRule({
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(event){
+
+ parser.addListener("startrule", function(){
gradients = {
moz: 0,
webkit: 0,
@@ -5402,37 +5952,37 @@ CSSLint.addRule({
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);
- }
+ }
});
@@ -5449,7 +5999,7 @@ CSSLint.addRule({
name: "IDs",
desc: "Selectors should not contain IDs.",
browsers: "All",
-
+
//initialization
init: function(parser, reporter){
var rule = this;
@@ -5460,12 +6010,12 @@ CSSLint.addRule({
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++){
+ 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++){
@@ -5474,17 +6024,382 @@ CSSLint.addRule({
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);
- }
- }
- });
+ 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-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,
+ "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);
+ }
+
+ });
}
});
@@ -5498,36 +6413,55 @@ CSSLint.addRule({
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;
+ 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++){
+ for (j=0; j < selector.parts.length; j++){
part = selector.parts[j];
if (part instanceof parserlib.css.SelectorPart){
- if (part.elementName){
- for (k=0; k < part.modifiers.length; k++){
- modifier = part.modifiers[k];
- if (modifier.type == "class" || modifier.type == "id"){
- reporter.warn("Element (" + part + ") is overqualified, just use " + modifier + " without element name.", part.line, part.col, rule);
+ 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);
+ }
+ }
+ }
+ });
}
});
@@ -5541,31 +6475,30 @@ CSSLint.addRule({
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,
- modifier,
- i, j, k;
-
+ i, j;
+
for (i=0; i < selectors.length; i++){
selector = selectors[i];
- for (j=0; j < selector.parts.length; j++){
+ 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);
}
- }
+ }
}
}
- });
+ });
}
});
@@ -5579,21 +6512,21 @@ CSSLint.addRule({
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++){
+ 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++){
@@ -5601,14 +6534,14 @@ CSSLint.addRule({
if (modifier.type == "attribute"){
if (/([\~\|\^\$\*]=)/.test(modifier)){
reporter.warn("Attribute selectors with " + RegExp.$1 + " are slow!", modifier.line, modifier.col, rule);
- }
+ }
}
}
- }
+ }
}
}
- });
+ });
}
});
@@ -5622,20 +6555,52 @@ CSSLint.addRule({
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(event){
+ parser.addListener("startrule", function(){
count++;
});
-
- parser.addListener("endstylesheet", function(event){
+
+ parser.addListener("endstylesheet", function(){
reporter.stat("rule-count", count);
- });
+ });
+ }
+
+});
+/*
+ * 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;
+
+ //check for use of "font-size"
+ parser.addListener("property", function(event){
+ var name = event.property,
+ value = event.value.parts[0].value;
+
+ if (name == "text-indent" && value < -99){
+ 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.", name.line, name.col, rule);
+ }
+ });
}
});
@@ -5649,11 +6614,11 @@ CSSLint.addRule({
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,
@@ -5662,30 +6627,59 @@ CSSLint.addRule({
h5: 0,
h6: 0
};
-
+
+ parser.addListener("startrule", function(event){
+ var selectors = event.selectors,
+ selector,
+ part,
+ i;
+
+ for (i=0; i < selectors.length; i++){
+ selector = selectors[i];
+ part = selector.parts[selector.parts.length-1];
+
+ if (part.elementName && /(h[1-6])/.test(part.elementName.toString())){
+ headings[RegExp.$1]++;
+ if (headings[RegExp.$1] > 1) {
+ reporter.warn("Heading (" + part.elementName + ") has already been defined.", part.line, part.col, 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];
-
- 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())){
- headings[RegExp.$1]++;
- if (headings[RegExp.$1] > 1) {
- reporter.warn("Heading (" + part.elementName + ") has already been defined.", part.line, part.col, rule);
- }
- }
- }
+
+ part = selector.parts[selector.parts.length-1];
+ if (part.elementName == "*"){
+ reporter.warn(rule.desc, part.line, part.col, rule);
}
}
- });
+ });
}
});
@@ -5700,69 +6694,88 @@ CSSLint.addRule({
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;
-
- parser.addListener("startrule", function(event){
+ num,
+ propertiesToCheck = {
+ "-moz-border-radius": "border-radius",
+ "-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",
+ "-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-box-shadow": "box-shadow",
+ "-webkit-box-shadow": "box-shadow",
+ "-moz-transform" : "transform",
+ "-webkit-transform" : "transform",
+ "-o-transform" : "transform",
+ "-ms-transform" : "transform",
+ "-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;
- });
-
- parser.addListener("property", function(event){
- var name = event.property,
- parts = event.value.parts,
- i = 0,
- len = parts.length,
- j;
-
- if (!properties[name]){
- properties[name] = [];
- }
-
- properties[name].push({ name: event.property, value : event.value, pos:num++ });
- });
+ num=1;
+ }
- parser.addListener("endrule", function(event){
+ //event handler for end of rules
+ function endRule(event){
var prop,
i, len,
standard,
needed,
actual,
needsStandard = [];
-
+
for (prop in properties){
- if (/(\-(?:ms|moz|webkit|o)\-)/.test(prop)){
- needsStandard.push({ actual: prop, needed: prop.substring(RegExp.$1.length)});
+ 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;
- //special case for Mozilla's border radius
- if (/\-moz\-border\-radius\-(.+)/.test(actual)){
- standard = "border-" + RegExp.$1.replace(/(left|right)/, "-$1") + "-radius";
- } else {
- standard = needed;
- }
-
if (!properties[needed]){
- reporter.warn("Missing standard property '" + standard + "' to go along with '" + actual + "'.", event.selectors[0].line, event.selectors[0].col, rule);
+ 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 '" + standard + "' should come after vendor-prefixed property '" + actual + "'.", event.selectors[0].line, event.selectors[0].col, rule);
+ 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);
}
});
@@ -5770,29 +6783,45 @@ CSSLint.addRule({
* Rule: If an element has a width of 100%, be careful when placing within
* an element that has padding. It may look strange.
*/
-CSSLint.addRule({
+//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;
+ var rule = this,
+ width100,
+ boxsizing;
+
+ parser.addListener("startrule", function(){
+ width100 = null;
+ boxsizing = false;
+ });
parser.addListener("property", function(event){
- var name = event.property,
+ var name = event.property.text.toLowerCase(),
value = event.value;
-
+
if (name == "width" && value == "100%"){
- reporter.warn("Elements with a width of 100% may not appear as you expect inside of other elements.", name.line, name.col, rule);
+ 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.
*/
@@ -5803,25 +6832,24 @@ CSSLint.addRule({
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,
- j;
-
+ 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++;
}
-
+
});
}
@@ -5829,4 +6857,4 @@ CSSLint.addRule({
});
exports.CSSLint = CSSLint;
-});
\ No newline at end of file
+});