Make WordPress Core

Ticket #42424: 42424.diff

File 42424.diff, 240.6 KB (added by obenland, 7 years ago)
  • src/wp-includes/js/codemirror/csslint.js

     
    1 /*!
    2 CSSLint v1.0.4
    3 Copyright (c) 2016 Nicole Sullivan and Nicholas C. Zakas. All rights reserved.
    4 
    5 Permission is hereby granted, free of charge, to any person obtaining a copy
    6 of this software and associated documentation files (the 'Software'), to deal
    7 in the Software without restriction, including without limitation the rights
    8 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    9 copies of the Software, and to permit persons to whom the Software is
    10 furnished to do so, subject to the following conditions:
    11 
    12 The above copyright notice and this permission notice shall be included in
    13 all copies or substantial portions of the Software.
    14 
    15 THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    16 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    17 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    18 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    19 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    20 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    21 THE SOFTWARE.
    22 
    23 */
    24 
    25 var CSSLint = (function(){
    26   var module = module || {},
    27       exports = exports || {};
    28 
    29 /*!
     1/*!
     2CSSLint v1.0.4
     3Copyright (c) 2016 Nicole Sullivan and Nicholas C. Zakas. All rights reserved.
     4
     5Permission is hereby granted, free of charge, to any person obtaining a copy
     6of this software and associated documentation files (the 'Software'), to deal
     7in the Software without restriction, including without limitation the rights
     8to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     9copies of the Software, and to permit persons to whom the Software is
     10furnished to do so, subject to the following conditions:
     11
     12The above copyright notice and this permission notice shall be included in
     13all copies or substantial portions of the Software.
     14
     15THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
     16IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
     17FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
     18AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
     19LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
     20OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
     21THE SOFTWARE.
     22
     23*/
     24
     25var CSSLint = (function(){
     26  var module = module || {},
     27      exports = exports || {};
     28
     29/*!
    3030Parser-Lib
    3131Copyright (c) 2009-2016 Nicholas C. Zakas. All rights reserved.
    3232
     
    4646AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    4747LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    4848OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    49 THE SOFTWARE.
    50 */
    51 /* Version v1.1.0, Build time: 6-December-2016 10:31:29 */
    52 var parserlib = (function () {
    53 var require;
    54 require=(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
     49THE SOFTWARE.
     50*/
     51/* Version v1.1.0, Build time: 6-December-2016 10:31:29 */
     52var parserlib = (function () {
     53var require;
     54require=(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
    5555"use strict";
    5656
    5757/* exported Colors */
     
    238238    windowframe         :"Window frame.",
    239239    windowtext          :"Text in windows."
    240240};
    241 
    242 },{}],2:[function(require,module,exports){
     241
     242},{}],2:[function(require,module,exports){
    243243"use strict";
    244244
    245245module.exports = Combinator;
     
    285285Combinator.prototype = new SyntaxUnit();
    286286Combinator.prototype.constructor = Combinator;
    287287
    288 
    289 },{"../util/SyntaxUnit":26,"./Parser":6}],3:[function(require,module,exports){
     288
     289},{"../util/SyntaxUnit":26,"./Parser":6}],3:[function(require,module,exports){
    290290"use strict";
    291291
    292292module.exports = Matcher;
     
    642642        });
    643643    }
    644644};
    645 
    646 },{"../util/StringReader":24,"../util/SyntaxError":25,"./ValidationTypes":21}],4:[function(require,module,exports){
     645
     646},{"../util/StringReader":24,"../util/SyntaxError":25,"./ValidationTypes":21}],4:[function(require,module,exports){
    647647"use strict";
    648648
    649649module.exports = MediaFeature;
     
    683683MediaFeature.prototype = new SyntaxUnit();
    684684MediaFeature.prototype.constructor = MediaFeature;
    685685
    686 
    687 },{"../util/SyntaxUnit":26,"./Parser":6}],5:[function(require,module,exports){
     686
     687},{"../util/SyntaxUnit":26,"./Parser":6}],5:[function(require,module,exports){
    688688"use strict";
    689689
    690690module.exports = MediaQuery;
     
    735735MediaQuery.prototype = new SyntaxUnit();
    736736MediaQuery.prototype.constructor = MediaQuery;
    737737
    738 
    739 },{"../util/SyntaxUnit":26,"./Parser":6}],6:[function(require,module,exports){
     738
     739},{"../util/SyntaxUnit":26,"./Parser":6}],6:[function(require,module,exports){
    740740"use strict";
    741741
    742742module.exports = Parser;
     
    32893289         ['-'|'+']? INTEGER | {O}{D}{D} | {E}{V}{E}{N} ] S*
    32903290  ;
    32913291*/
    3292 
    3293 },{"../util/EventTarget":23,"../util/SyntaxError":25,"../util/SyntaxUnit":26,"./Combinator":2,"./MediaFeature":4,"./MediaQuery":5,"./PropertyName":8,"./PropertyValue":9,"./PropertyValuePart":11,"./Selector":13,"./SelectorPart":14,"./SelectorSubPart":15,"./TokenStream":17,"./Tokens":18,"./Validation":19}],7:[function(require,module,exports){
     3292
     3293},{"../util/EventTarget":23,"../util/SyntaxError":25,"../util/SyntaxUnit":26,"./Combinator":2,"./MediaFeature":4,"./MediaQuery":5,"./PropertyName":8,"./PropertyValue":9,"./PropertyValuePart":11,"./Selector":13,"./SelectorPart":14,"./SelectorSubPart":15,"./TokenStream":17,"./Tokens":18,"./Validation":19}],7:[function(require,module,exports){
    32943294"use strict";
    32953295
    32963296/* exported Properties */
     
    37833783    "z-index"                       : "<integer> | auto",
    37843784    "zoom"                          : "<number> | <percentage> | normal"
    37853785};
    3786 
    3787 },{}],8:[function(require,module,exports){
     3786
     3787},{}],8:[function(require,module,exports){
    37883788"use strict";
    37893789
    37903790module.exports = PropertyName;
     
    38223822PropertyName.prototype.toString = function() {
    38233823    return (this.hack ? this.hack : "") + this.text;
    38243824};
    3825 
    3826 },{"../util/SyntaxUnit":26,"./Parser":6}],9:[function(require,module,exports){
     3825
     3826},{"../util/SyntaxUnit":26,"./Parser":6}],9:[function(require,module,exports){
    38273827"use strict";
    38283828
    38293829module.exports = PropertyValue;
     
    38603860PropertyValue.prototype = new SyntaxUnit();
    38613861PropertyValue.prototype.constructor = PropertyValue;
    38623862
    3863 
    3864 },{"../util/SyntaxUnit":26,"./Parser":6}],10:[function(require,module,exports){
     3863
     3864},{"../util/SyntaxUnit":26,"./Parser":6}],10:[function(require,module,exports){
    38653865"use strict";
    38663866
    38673867module.exports = PropertyValueIterator;
     
    39983998PropertyValueIterator.prototype.drop = function() {
    39993999    this._marks.pop();
    40004000};
    4001 
    4002 },{}],11:[function(require,module,exports){
     4001
     4002},{}],11:[function(require,module,exports){
    40034003"use strict";
    40044004
    40054005module.exports = PropertyValuePart;
     
    42474247    });
    42484248    return part;
    42494249};
    4250 
    4251 },{"../util/SyntaxUnit":26,"./Colors":1,"./Parser":6,"./Tokens":18}],12:[function(require,module,exports){
     4250
     4251},{"../util/SyntaxUnit":26,"./Colors":1,"./Parser":6,"./Tokens":18}],12:[function(require,module,exports){
    42524252"use strict";
    42534253
    42544254var Pseudos = module.exports = {
     
    42654265Pseudos.isElement = function(pseudo) {
    42664266    return pseudo.indexOf("::") === 0 || Pseudos[pseudo.toLowerCase()] === Pseudos.ELEMENT;
    42674267};
    4268 
    4269 },{}],13:[function(require,module,exports){
     4268
     4269},{}],13:[function(require,module,exports){
    42704270"use strict";
    42714271
    42724272module.exports = Selector;
     
    43104310Selector.prototype = new SyntaxUnit();
    43114311Selector.prototype.constructor = Selector;
    43124312
    4313 
    4314 },{"../util/SyntaxUnit":26,"./Parser":6,"./Specificity":16}],14:[function(require,module,exports){
     4313
     4314},{"../util/SyntaxUnit":26,"./Parser":6,"./Specificity":16}],14:[function(require,module,exports){
    43154315"use strict";
    43164316
    43174317module.exports = SelectorPart;
     
    43614361SelectorPart.prototype = new SyntaxUnit();
    43624362SelectorPart.prototype.constructor = SelectorPart;
    43634363
    4364 
    4365 },{"../util/SyntaxUnit":26,"./Parser":6}],15:[function(require,module,exports){
     4364
     4365},{"../util/SyntaxUnit":26,"./Parser":6}],15:[function(require,module,exports){
    43664366"use strict";
    43674367
    43684368module.exports = SelectorSubPart;
     
    44064406SelectorSubPart.prototype = new SyntaxUnit();
    44074407SelectorSubPart.prototype.constructor = SelectorSubPart;
    44084408
    4409 
    4410 },{"../util/SyntaxUnit":26,"./Parser":6}],16:[function(require,module,exports){
     4409
     4410},{"../util/SyntaxUnit":26,"./Parser":6}],16:[function(require,module,exports){
    44114411"use strict";
    44124412
    44134413module.exports = Specificity;
     
    45374537
    45384538    return new Specificity(0, b, c, d);
    45394539};
    4540 
    4541 },{"./Pseudos":12,"./SelectorPart":14}],17:[function(require,module,exports){
     4540
     4541},{"./Pseudos":12,"./SelectorPart":14}],17:[function(require,module,exports){
    45424542"use strict";
    45434543
    45444544module.exports = TokenStream;
     
    55865586
    55875587    }
    55885588});
    5589 
    5590 },{"../util/TokenStreamBase":27,"./PropertyValuePart":11,"./Tokens":18}],18:[function(require,module,exports){
     5589
     5590},{"../util/TokenStreamBase":27,"./PropertyValuePart":11,"./Tokens":18}],18:[function(require,module,exports){
    55915591"use strict";
    55925592
    55935593var Tokens = module.exports = [
     
    57975797        return typeMap[c] || -1;
    57985798    };
    57995799})();
    5800 
    5801 },{}],19:[function(require,module,exports){
     5800
     5801},{}],19:[function(require,module,exports){
    58025802"use strict";
    58035803
    58045804/* exported Validation */
     
    58655865    }
    58665866
    58675867};
    5868 
    5869 },{"./Matcher":3,"./Properties":7,"./PropertyValueIterator":10,"./ValidationError":20,"./ValidationTypes":21}],20:[function(require,module,exports){
     5868
     5869},{"./Matcher":3,"./Properties":7,"./PropertyValueIterator":10,"./ValidationError":20,"./ValidationTypes":21}],20:[function(require,module,exports){
    58705870"use strict";
    58715871
    58725872module.exports = ValidationError;
     
    59075907
    59085908//inherit from Error
    59095909ValidationError.prototype = new Error();
    5910 
    5911 },{}],21:[function(require,module,exports){
     5910
     5911},{}],21:[function(require,module,exports){
    59125912"use strict";
    59135913
    59145914var ValidationTypes = module.exports;
     
    63866386                 "<font-variant-caps>",
    63876387                 { expand: "<font-variant-numeric>" },
    63886388                 { expand: "<font-variant-east-asian>" });
    6389 
    6390 },{"./Matcher":3}],22:[function(require,module,exports){
     6389
     6390},{"./Matcher":3}],22:[function(require,module,exports){
    63916391"use strict";
    63926392
    63936393module.exports = {
     
    64086408    Tokens            : require("./Tokens"),
    64096409    ValidationError   : require("./ValidationError")
    64106410};
    6411 
    6412 },{"./Colors":1,"./Combinator":2,"./Matcher":3,"./MediaFeature":4,"./MediaQuery":5,"./Parser":6,"./PropertyName":8,"./PropertyValue":9,"./PropertyValuePart":11,"./Selector":13,"./SelectorPart":14,"./SelectorSubPart":15,"./Specificity":16,"./TokenStream":17,"./Tokens":18,"./ValidationError":20}],23:[function(require,module,exports){
     6411
     6412},{"./Colors":1,"./Combinator":2,"./Matcher":3,"./MediaFeature":4,"./MediaQuery":5,"./Parser":6,"./PropertyName":8,"./PropertyValue":9,"./PropertyValuePart":11,"./Selector":13,"./SelectorPart":14,"./SelectorSubPart":15,"./Specificity":16,"./TokenStream":17,"./Tokens":18,"./ValidationError":20}],23:[function(require,module,exports){
    64136413"use strict";
    64146414
    64156415module.exports = EventTarget;
     
    65016501        }
    65026502    }
    65036503};
    6504 
    6505 },{}],24:[function(require,module,exports){
     6504
     6505},{}],24:[function(require,module,exports){
    65066506"use strict";
    65076507
    65086508module.exports = StringReader;
     
    67746774    }
    67756775
    67766776};
    6777 
    6778 },{}],25:[function(require,module,exports){
     6777
     6778},{}],25:[function(require,module,exports){
    67796779"use strict";
    67806780
    67816781module.exports = SyntaxError;
     
    68196819//inherit from Error
    68206820SyntaxError.prototype = Object.create(Error.prototype); // jshint ignore:line
    68216821SyntaxError.prototype.constructor = SyntaxError; // jshint ignore:line
    6822 
    6823 },{}],26:[function(require,module,exports){
     6822
     6823},{}],26:[function(require,module,exports){
    68246824"use strict";
    68256825
    68266826module.exports = SyntaxUnit;
     
    69036903    }
    69046904
    69056905};
    6906 
    6907 },{}],27:[function(require,module,exports){
     6906
     6907},{}],27:[function(require,module,exports){
    69086908"use strict";
    69096909
    69106910module.exports = TokenStreamBase;
     
    73057305
    73067306};
    73077307
    7308 
    7309 },{"./StringReader":24,"./SyntaxError":25}],28:[function(require,module,exports){
     7308
     7309},{"./StringReader":24,"./SyntaxError":25}],28:[function(require,module,exports){
    73107310"use strict";
    73117311
    73127312module.exports = {
     
    73167316    EventTarget     : require("./EventTarget"),
    73177317    TokenStreamBase : require("./TokenStreamBase")
    73187318};
    7319 
    7320 },{"./EventTarget":23,"./StringReader":24,"./SyntaxError":25,"./SyntaxUnit":26,"./TokenStreamBase":27}],"parserlib":[function(require,module,exports){
     7319
     7320},{"./EventTarget":23,"./StringReader":24,"./SyntaxError":25,"./SyntaxUnit":26,"./TokenStreamBase":27}],"parserlib":[function(require,module,exports){
    73217321"use strict";
    73227322
    73237323module.exports = {
    73247324    css  : require("./css"),
    73257325    util : require("./util")
    73267326};
    7327 
    7328 },{"./css":22,"./util":28}]},{},[]);
    7329 
    7330 return require('parserlib');
    7331 })();
    7332 var clone = (function() {
    7333 'use strict';
    7334 
    7335 var nativeMap;
    7336 try {
    7337   nativeMap = Map;
    7338 } catch(_) {
    7339   // maybe a reference error because no `Map`. Give it a dummy value that no
    7340   // value will ever be an instanceof.
    7341   nativeMap = function() {};
    7342 }
    7343 
    7344 var nativeSet;
    7345 try {
    7346   nativeSet = Set;
    7347 } catch(_) {
    7348   nativeSet = function() {};
    7349 }
    7350 
    7351 var nativePromise;
    7352 try {
    7353   nativePromise = Promise;
    7354 } catch(_) {
    7355   nativePromise = function() {};
    7356 }
    7357 
    7358 /**
    7359  * Clones (copies) an Object using deep copying.
    7360  *
    7361  * This function supports circular references by default, but if you are certain
    7362  * there are no circular references in your object, you can save some CPU time
    7363  * by calling clone(obj, false).
    7364  *
    7365  * Caution: if `circular` is false and `parent` contains circular references,
    7366  * your program may enter an infinite loop and crash.
    7367  *
    7368  * @param `parent` - the object to be cloned
    7369  * @param `circular` - set to true if the object to be cloned may contain
    7370  *    circular references. (optional - true by default)
    7371  * @param `depth` - set to a number if the object is only to be cloned to
    7372  *    a particular depth. (optional - defaults to Infinity)
    7373  * @param `prototype` - sets the prototype to be used when cloning an object.
    7374  *    (optional - defaults to parent prototype).
    7375  * @param `includeNonEnumerable` - set to true if the non-enumerable properties
    7376  *    should be cloned as well. Non-enumerable properties on the prototype
    7377  *    chain will be ignored. (optional - false by default)
    7378 */
    7379 function clone(parent, circular, depth, prototype, includeNonEnumerable) {
    7380   if (typeof circular === 'object') {
    7381     depth = circular.depth;
    7382     prototype = circular.prototype;
    7383     includeNonEnumerable = circular.includeNonEnumerable;
    7384     circular = circular.circular;
    7385   }
    7386   // maintain two arrays for circular references, where corresponding parents
    7387   // and children have the same index
    7388   var allParents = [];
    7389   var allChildren = [];
    7390 
    7391   var useBuffer = typeof Buffer != 'undefined';
    7392 
    7393   if (typeof circular == 'undefined')
    7394     circular = true;
    7395 
    7396   if (typeof depth == 'undefined')
    7397     depth = Infinity;
    7398 
    7399   // recurse this function so we don't reset allParents and allChildren
    7400   function _clone(parent, depth) {
    7401     // cloning null always returns null
    7402     if (parent === null)
    7403       return null;
    7404 
    7405     if (depth === 0)
    7406       return parent;
    7407 
    7408     var child;
    7409     var proto;
    7410     if (typeof parent != 'object') {
    7411       return parent;
    7412     }
    7413 
    7414     if (parent instanceof nativeMap) {
    7415       child = new nativeMap();
    7416     } else if (parent instanceof nativeSet) {
    7417       child = new nativeSet();
    7418     } else if (parent instanceof nativePromise) {
    7419       child = new nativePromise(function (resolve, reject) {
    7420         parent.then(function(value) {
    7421           resolve(_clone(value, depth - 1));
    7422         }, function(err) {
    7423           reject(_clone(err, depth - 1));
    7424         });
    7425       });
    7426     } else if (clone.__isArray(parent)) {
    7427       child = [];
    7428     } else if (clone.__isRegExp(parent)) {
    7429       child = new RegExp(parent.source, __getRegExpFlags(parent));
    7430       if (parent.lastIndex) child.lastIndex = parent.lastIndex;
    7431     } else if (clone.__isDate(parent)) {
    7432       child = new Date(parent.getTime());
    7433     } else if (useBuffer && Buffer.isBuffer(parent)) {
    7434       child = new Buffer(parent.length);
    7435       parent.copy(child);
    7436       return child;
    7437     } else if (parent instanceof Error) {
    7438       child = Object.create(parent);
    7439     } else {
    7440       if (typeof prototype == 'undefined') {
    7441         proto = Object.getPrototypeOf(parent);
    7442         child = Object.create(proto);
    7443       }
    7444       else {
    7445         child = Object.create(prototype);
    7446         proto = prototype;
    7447       }
    7448     }
    7449 
    7450     if (circular) {
    7451       var index = allParents.indexOf(parent);
    7452 
    7453       if (index != -1) {
    7454         return allChildren[index];
    7455       }
    7456       allParents.push(parent);
    7457       allChildren.push(child);
    7458     }
    7459 
    7460     if (parent instanceof nativeMap) {
    7461       var keyIterator = parent.keys();
    7462       while(true) {
    7463         var next = keyIterator.next();
    7464         if (next.done) {
    7465           break;
    7466         }
    7467         var keyChild = _clone(next.value, depth - 1);
    7468         var valueChild = _clone(parent.get(next.value), depth - 1);
    7469         child.set(keyChild, valueChild);
    7470       }
    7471     }
    7472     if (parent instanceof nativeSet) {
    7473       var iterator = parent.keys();
    7474       while(true) {
    7475         var next = iterator.next();
    7476         if (next.done) {
    7477           break;
    7478         }
    7479         var entryChild = _clone(next.value, depth - 1);
    7480         child.add(entryChild);
    7481       }
    7482     }
    7483 
    7484     for (var i in parent) {
    7485       var attrs;
    7486       if (proto) {
    7487         attrs = Object.getOwnPropertyDescriptor(proto, i);
    7488       }
    7489 
    7490       if (attrs && attrs.set == null) {
    7491         continue;
    7492       }
    7493       child[i] = _clone(parent[i], depth - 1);
    7494     }
    7495 
    7496     if (Object.getOwnPropertySymbols) {
    7497       var symbols = Object.getOwnPropertySymbols(parent);
    7498       for (var i = 0; i < symbols.length; i++) {
    7499         // Don't need to worry about cloning a symbol because it is a primitive,
    7500         // like a number or string.
    7501         var symbol = symbols[i];
    7502         var descriptor = Object.getOwnPropertyDescriptor(parent, symbol);
    7503         if (descriptor && !descriptor.enumerable && !includeNonEnumerable) {
    7504           continue;
    7505         }
    7506         child[symbol] = _clone(parent[symbol], depth - 1);
    7507         if (!descriptor.enumerable) {
    7508           Object.defineProperty(child, symbol, {
    7509             enumerable: false
    7510           });
    7511         }
    7512       }
    7513     }
    7514 
    7515     if (includeNonEnumerable) {
    7516       var allPropertyNames = Object.getOwnPropertyNames(parent);
    7517       for (var i = 0; i < allPropertyNames.length; i++) {
    7518         var propertyName = allPropertyNames[i];
    7519         var descriptor = Object.getOwnPropertyDescriptor(parent, propertyName);
    7520         if (descriptor && descriptor.enumerable) {
    7521           continue;
    7522         }
    7523         child[propertyName] = _clone(parent[propertyName], depth - 1);
    7524         Object.defineProperty(child, propertyName, {
    7525           enumerable: false
    7526         });
    7527       }
    7528     }
    7529 
    7530     return child;
    7531   }
    7532 
    7533   return _clone(parent, depth);
    7534 }
    7535 
    7536 /**
    7537  * Simple flat clone using prototype, accepts only objects, usefull for property
    7538  * override on FLAT configuration object (no nested props).
    7539  *
    7540  * USE WITH CAUTION! This may not behave as you wish if you do not know how this
    7541  * works.
    7542  */
    7543 clone.clonePrototype = function clonePrototype(parent) {
    7544   if (parent === null)
    7545     return null;
    7546 
    7547   var c = function () {};
    7548   c.prototype = parent;
    7549   return new c();
    7550 };
    7551 
    7552 // private utility functions
    7553 
    7554 function __objToStr(o) {
    7555   return Object.prototype.toString.call(o);
    7556 }
    7557 clone.__objToStr = __objToStr;
    7558 
    7559 function __isDate(o) {
    7560   return typeof o === 'object' && __objToStr(o) === '[object Date]';
    7561 }
    7562 clone.__isDate = __isDate;
    7563 
    7564 function __isArray(o) {
    7565   return typeof o === 'object' && __objToStr(o) === '[object Array]';
    7566 }
    7567 clone.__isArray = __isArray;
    7568 
    7569 function __isRegExp(o) {
    7570   return typeof o === 'object' && __objToStr(o) === '[object RegExp]';
    7571 }
    7572 clone.__isRegExp = __isRegExp;
    7573 
    7574 function __getRegExpFlags(re) {
    7575   var flags = '';
    7576   if (re.global) flags += 'g';
    7577   if (re.ignoreCase) flags += 'i';
    7578   if (re.multiline) flags += 'm';
    7579   return flags;
    7580 }
    7581 clone.__getRegExpFlags = __getRegExpFlags;
    7582 
    7583 return clone;
    7584 })();
    7585 
    7586 if (typeof module === 'object' && module.exports) {
    7587   module.exports = clone;
    7588 }
    7589 
    7590 /**
    7591  * Main CSSLint object.
    7592  * @class CSSLint
    7593  * @static
    7594  * @extends parserlib.util.EventTarget
    7595  */
    7596 
    7597 /* global parserlib, clone, Reporter */
    7598 /* exported CSSLint */
    7599 
    7600 var CSSLint = (function() {
    7601     "use strict";
    7602 
    7603     var rules           = [],
    7604         formatters      = [],
    7605         embeddedRuleset = /\/\*\s*csslint([^\*]*)\*\//,
    7606         api             = new parserlib.util.EventTarget();
    7607 
    7608     api.version = "1.0.4";
    7609 
    7610     //-------------------------------------------------------------------------
    7611     // Rule Management
    7612     //-------------------------------------------------------------------------
    7613 
    7614     /**
    7615      * Adds a new rule to the engine.
    7616      * @param {Object} rule The rule to add.
    7617      * @method addRule
    7618      */
    7619     api.addRule = function(rule) {
    7620         rules.push(rule);
    7621         rules[rule.id] = rule;
    7622     };
    7623 
    7624     /**
    7625      * Clears all rule from the engine.
    7626      * @method clearRules
    7627      */
    7628     api.clearRules = function() {
    7629         rules = [];
    7630     };
    7631 
    7632     /**
    7633      * Returns the rule objects.
    7634      * @return An array of rule objects.
    7635      * @method getRules
    7636      */
    7637     api.getRules = function() {
    7638         return [].concat(rules).sort(function(a, b) {
    7639             return a.id > b.id ? 1 : 0;
    7640         });
    7641     };
    7642 
    7643     /**
    7644      * Returns a ruleset configuration object with all current rules.
    7645      * @return A ruleset object.
    7646      * @method getRuleset
    7647      */
    7648     api.getRuleset = function() {
    7649         var ruleset = {},
    7650             i = 0,
    7651             len = rules.length;
    7652 
    7653         while (i < len) {
    7654             ruleset[rules[i++].id] = 1;    // by default, everything is a warning
    7655         }
    7656 
    7657         return ruleset;
    7658     };
    7659 
    7660     /**
    7661      * Returns a ruleset object based on embedded rules.
    7662      * @param {String} text A string of css containing embedded rules.
    7663      * @param {Object} ruleset A ruleset object to modify.
    7664      * @return {Object} A ruleset object.
    7665      * @method getEmbeddedRuleset
    7666      */
    7667     function applyEmbeddedRuleset(text, ruleset) {
    7668         var valueMap,
    7669             embedded = text && text.match(embeddedRuleset),
    7670             rules = embedded && embedded[1];
    7671 
    7672         if (rules) {
    7673             valueMap = {
    7674                 "true": 2,  // true is error
    7675                 "": 1,      // blank is warning
    7676                 "false": 0, // false is ignore
    7677 
    7678                 "2": 2,     // explicit error
    7679                 "1": 1,     // explicit warning
    7680                 "0": 0      // explicit ignore
    7681             };
    7682 
    7683             rules.toLowerCase().split(",").forEach(function(rule) {
    7684                 var pair = rule.split(":"),
    7685                     property = pair[0] || "",
    7686                     value = pair[1] || "";
    7687 
    7688                 ruleset[property.trim()] = valueMap[value.trim()];
    7689             });
    7690         }
    7691 
    7692         return ruleset;
    7693     }
    7694 
    7695     //-------------------------------------------------------------------------
    7696     // Formatters
    7697     //-------------------------------------------------------------------------
    7698 
    7699     /**
    7700      * Adds a new formatter to the engine.
    7701      * @param {Object} formatter The formatter to add.
    7702      * @method addFormatter
    7703      */
    7704     api.addFormatter = function(formatter) {
    7705         // formatters.push(formatter);
    7706         formatters[formatter.id] = formatter;
    7707     };
    7708 
    7709     /**
    7710      * Retrieves a formatter for use.
    7711      * @param {String} formatId The name of the format to retrieve.
    7712      * @return {Object} The formatter or undefined.
    7713      * @method getFormatter
    7714      */
    7715     api.getFormatter = function(formatId) {
    7716         return formatters[formatId];
    7717     };
    7718 
    7719     /**
    7720      * Formats the results in a particular format for a single file.
    7721      * @param {Object} result The results returned from CSSLint.verify().
    7722      * @param {String} filename The filename for which the results apply.
    7723      * @param {String} formatId The name of the formatter to use.
    7724      * @param {Object} options (Optional) for special output handling.
    7725      * @return {String} A formatted string for the results.
    7726      * @method format
    7727      */
    7728     api.format = function(results, filename, formatId, options) {
    7729         var formatter = this.getFormatter(formatId),
    7730             result = null;
    7731 
    7732         if (formatter) {
    7733             result = formatter.startFormat();
    7734             result += formatter.formatResults(results, filename, options || {});
    7735             result += formatter.endFormat();
    7736         }
    7737 
    7738         return result;
    7739     };
    7740 
    7741     /**
    7742      * Indicates if the given format is supported.
    7743      * @param {String} formatId The ID of the format to check.
    7744      * @return {Boolean} True if the format exists, false if not.
    7745      * @method hasFormat
    7746      */
    7747     api.hasFormat = function(formatId) {
    7748         return formatters.hasOwnProperty(formatId);
    7749     };
    7750 
    7751     //-------------------------------------------------------------------------
    7752     // Verification
    7753     //-------------------------------------------------------------------------
    7754 
    7755     /**
    7756      * Starts the verification process for the given CSS text.
    7757      * @param {String} text The CSS text to verify.
    7758      * @param {Object} ruleset (Optional) List of rules to apply. If null, then
    7759      *      all rules are used. If a rule has a value of 1 then it's a warning,
    7760      *      a value of 2 means it's an error.
    7761      * @return {Object} Results of the verification.
    7762      * @method verify
    7763      */
    7764     api.verify = function(text, ruleset) {
    7765 
    7766         var i = 0,
    7767             reporter,
    7768             lines,
    7769             allow = {},
    7770             ignore = [],
    7771             report,
    7772             parser = new parserlib.css.Parser({
    7773                 starHack: true,
    7774                 ieFilters: true,
    7775                 underscoreHack: true,
    7776                 strict: false
    7777             });
    7778 
    7779         // normalize line endings
    7780         lines = text.replace(/\n\r?/g, "$split$").split("$split$");
    7781 
    7782         // find 'allow' comments
    7783         CSSLint.Util.forEach(lines, function (line, lineno) {
    7784             var allowLine = line && line.match(/\/\*[ \t]*csslint[ \t]+allow:[ \t]*([^\*]*)\*\//i),
    7785                 allowRules = allowLine && allowLine[1],
    7786                 allowRuleset = {};
    7787 
    7788             if (allowRules) {
    7789                 allowRules.toLowerCase().split(",").forEach(function(allowRule) {
    7790                     allowRuleset[allowRule.trim()] = true;
    7791                 });
    7792                 if (Object.keys(allowRuleset).length > 0) {
    7793                     allow[lineno + 1] = allowRuleset;
    7794                 }
    7795             }
    7796         });
    7797 
    7798         var ignoreStart = null,
    7799             ignoreEnd = null;
    7800         CSSLint.Util.forEach(lines, function (line, lineno) {
    7801             // Keep oldest, "unclosest" ignore:start
    7802             if (ignoreStart === null && line.match(/\/\*[ \t]*csslint[ \t]+ignore:start[ \t]*\*\//i)) {
    7803                 ignoreStart = lineno;
    7804             }
    7805 
    7806             if (line.match(/\/\*[ \t]*csslint[ \t]+ignore:end[ \t]*\*\//i)) {
    7807                 ignoreEnd = lineno;
    7808             }
    7809 
    7810             if (ignoreStart !== null && ignoreEnd !== null) {
    7811                 ignore.push([ignoreStart, ignoreEnd]);
    7812                 ignoreStart = ignoreEnd = null;
    7813             }
    7814         });
    7815 
    7816         // Close remaining ignore block, if any
    7817         if (ignoreStart !== null) {
    7818             ignore.push([ignoreStart, lines.length]);
    7819         }
    7820 
    7821         if (!ruleset) {
    7822             ruleset = this.getRuleset();
    7823         }
    7824 
    7825         if (embeddedRuleset.test(text)) {
    7826             // defensively copy so that caller's version does not get modified
    7827             ruleset = clone(ruleset);
    7828             ruleset = applyEmbeddedRuleset(text, ruleset);
    7829         }
    7830 
    7831         reporter = new Reporter(lines, ruleset, allow, ignore);
    7832 
    7833         ruleset.errors = 2;       // always report parsing errors as errors
    7834         for (i in ruleset) {
    7835             if (ruleset.hasOwnProperty(i) && ruleset[i]) {
    7836                 if (rules[i]) {
    7837                     rules[i].init(parser, reporter);
    7838                 }
    7839             }
    7840         }
    7841 
    7842 
    7843         // capture most horrible error type
    7844         try {
    7845             parser.parse(text);
    7846         } catch (ex) {
    7847             reporter.error("Fatal error, cannot continue: " + ex.message, ex.line, ex.col, {});
    7848         }
    7849 
    7850         report = {
    7851             messages    : reporter.messages,
    7852             stats       : reporter.stats,
    7853             ruleset     : reporter.ruleset,
    7854             allow       : reporter.allow,
    7855             ignore      : reporter.ignore
    7856         };
    7857 
    7858         // sort by line numbers, rollups at the bottom
    7859         report.messages.sort(function (a, b) {
    7860             if (a.rollup && !b.rollup) {
    7861                 return 1;
    7862             } else if (!a.rollup && b.rollup) {
    7863                 return -1;
    7864             } else {
    7865                 return a.line - b.line;
    7866             }
    7867         });
    7868 
    7869         return report;
    7870     };
    7871 
    7872     //-------------------------------------------------------------------------
    7873     // Publish the API
    7874     //-------------------------------------------------------------------------
    7875 
    7876     return api;
    7877 
    7878 })();
    7879 
    7880 /**
    7881  * An instance of Report is used to report results of the
    7882  * verification back to the main API.
    7883  * @class Reporter
    7884  * @constructor
    7885  * @param {String[]} lines The text lines of the source.
    7886  * @param {Object} ruleset The set of rules to work with, including if
    7887  *      they are errors or warnings.
    7888  * @param {Object} explicitly allowed lines
    7889  * @param {[][]} ingore list of line ranges to be ignored
    7890  */
    7891 function Reporter(lines, ruleset, allow, ignore) {
    7892     "use strict";
    7893 
    7894     /**
    7895      * List of messages being reported.
    7896      * @property messages
    7897      * @type String[]
    7898      */
    7899     this.messages = [];
    7900 
    7901     /**
    7902      * List of statistics being reported.
    7903      * @property stats
    7904      * @type String[]
    7905      */
    7906     this.stats = [];
    7907 
    7908     /**
    7909      * Lines of code being reported on. Used to provide contextual information
    7910      * for messages.
    7911      * @property lines
    7912      * @type String[]
    7913      */
    7914     this.lines = lines;
    7915 
    7916     /**
    7917      * Information about the rules. Used to determine whether an issue is an
    7918      * error or warning.
    7919      * @property ruleset
    7920      * @type Object
    7921      */
    7922     this.ruleset = ruleset;
    7923 
    7924     /**
    7925      * Lines with specific rule messages to leave out of the report.
    7926      * @property allow
    7927      * @type Object
    7928      */
    7929     this.allow = allow;
    7930     if (!this.allow) {
    7931         this.allow = {};
    7932     }
    7933 
    7934     /**
    7935      * Linesets not to include in the report.
    7936      * @property ignore
    7937      * @type [][]
    7938      */
    7939     this.ignore = ignore;
    7940     if (!this.ignore) {
    7941         this.ignore = [];
    7942     }
    7943 }
    7944 
    7945 Reporter.prototype = {
    7946 
    7947     // restore constructor
    7948     constructor: Reporter,
    7949 
    7950     /**
    7951      * Report an error.
    7952      * @param {String} message The message to store.
    7953      * @param {int} line The line number.
    7954      * @param {int} col The column number.
    7955      * @param {Object} rule The rule this message relates to.
    7956      * @method error
    7957      */
    7958     error: function(message, line, col, rule) {
    7959         "use strict";
    7960         this.messages.push({
    7961             type    : "error",
    7962             line    : line,
    7963             col     : col,
    7964             message : message,
    7965             evidence: this.lines[line-1],
    7966             rule    : rule || {}
    7967         });
    7968     },
    7969 
    7970     /**
    7971      * Report an warning.
    7972      * @param {String} message The message to store.
    7973      * @param {int} line The line number.
    7974      * @param {int} col The column number.
    7975      * @param {Object} rule The rule this message relates to.
    7976      * @method warn
    7977      * @deprecated Use report instead.
    7978      */
    7979     warn: function(message, line, col, rule) {
    7980         "use strict";
    7981         this.report(message, line, col, rule);
    7982     },
    7983 
    7984     /**
    7985      * Report an issue.
    7986      * @param {String} message The message to store.
    7987      * @param {int} line The line number.
    7988      * @param {int} col The column number.
    7989      * @param {Object} rule The rule this message relates to.
    7990      * @method report
    7991      */
    7992     report: function(message, line, col, rule) {
    7993         "use strict";
    7994 
    7995         // Check if rule violation should be allowed
    7996         if (this.allow.hasOwnProperty(line) && this.allow[line].hasOwnProperty(rule.id)) {
    7997             return;
    7998         }
    7999 
    8000         var ignore = false;
    8001         CSSLint.Util.forEach(this.ignore, function (range) {
    8002             if (range[0] <= line && line <= range[1]) {
    8003                 ignore = true;
    8004             }
    8005         });
    8006         if (ignore) {
    8007             return;
    8008         }
    8009 
    8010         this.messages.push({
    8011             type    : this.ruleset[rule.id] === 2 ? "error" : "warning",
    8012             line    : line,
    8013             col     : col,
    8014             message : message,
    8015             evidence: this.lines[line-1],
    8016             rule    : rule
    8017         });
    8018     },
    8019 
    8020     /**
    8021      * Report some informational text.
    8022      * @param {String} message The message to store.
    8023      * @param {int} line The line number.
    8024      * @param {int} col The column number.
    8025      * @param {Object} rule The rule this message relates to.
    8026      * @method info
    8027      */
    8028     info: function(message, line, col, rule) {
    8029         "use strict";
    8030         this.messages.push({
    8031             type    : "info",
    8032             line    : line,
    8033             col     : col,
    8034             message : message,
    8035             evidence: this.lines[line-1],
    8036             rule    : rule
    8037         });
    8038     },
    8039 
    8040     /**
    8041      * Report some rollup error information.
    8042      * @param {String} message The message to store.
    8043      * @param {Object} rule The rule this message relates to.
    8044      * @method rollupError
    8045      */
    8046     rollupError: function(message, rule) {
    8047         "use strict";
    8048         this.messages.push({
    8049             type    : "error",
    8050             rollup  : true,
    8051             message : message,
    8052             rule    : rule
    8053         });
    8054     },
    8055 
    8056     /**
    8057      * Report some rollup warning information.
    8058      * @param {String} message The message to store.
    8059      * @param {Object} rule The rule this message relates to.
    8060      * @method rollupWarn
    8061      */
    8062     rollupWarn: function(message, rule) {
    8063         "use strict";
    8064         this.messages.push({
    8065             type    : "warning",
    8066             rollup  : true,
    8067             message : message,
    8068             rule    : rule
    8069         });
    8070     },
    8071 
    8072     /**
    8073      * Report a statistic.
    8074      * @param {String} name The name of the stat to store.
    8075      * @param {Variant} value The value of the stat.
    8076      * @method stat
    8077      */
    8078     stat: function(name, value) {
    8079         "use strict";
    8080         this.stats[name] = value;
    8081     }
    8082 };
    8083 
    8084 // expose for testing purposes
    8085 CSSLint._Reporter = Reporter;
    8086 
    8087 /*
    8088  * Utility functions that make life easier.
    8089  */
    8090 CSSLint.Util = {
    8091     /*
    8092      * Adds all properties from supplier onto receiver,
    8093      * overwriting if the same name already exists on
    8094      * receiver.
    8095      * @param {Object} The object to receive the properties.
    8096      * @param {Object} The object to provide the properties.
    8097      * @return {Object} The receiver
    8098      */
    8099     mix: function(receiver, supplier) {
    8100         "use strict";
    8101         var prop;
    8102 
    8103         for (prop in supplier) {
    8104             if (supplier.hasOwnProperty(prop)) {
    8105                 receiver[prop] = supplier[prop];
    8106             }
    8107         }
    8108 
    8109         return prop;
    8110     },
    8111 
    8112     /*
    8113      * Polyfill for array indexOf() method.
    8114      * @param {Array} values The array to search.
    8115      * @param {Variant} value The value to search for.
    8116      * @return {int} The index of the value if found, -1 if not.
    8117      */
    8118     indexOf: function(values, value) {
    8119         "use strict";
    8120         if (values.indexOf) {
    8121             return values.indexOf(value);
    8122         } else {
    8123             for (var i=0, len=values.length; i < len; i++) {
    8124                 if (values[i] === value) {
    8125                     return i;
    8126                 }
    8127             }
    8128             return -1;
    8129         }
    8130     },
    8131 
    8132     /*
    8133      * Polyfill for array forEach() method.
    8134      * @param {Array} values The array to operate on.
    8135      * @param {Function} func The function to call on each item.
    8136      * @return {void}
    8137      */
    8138     forEach: function(values, func) {
    8139         "use strict";
    8140         if (values.forEach) {
    8141             return values.forEach(func);
    8142         } else {
    8143             for (var i=0, len=values.length; i < len; i++) {
    8144                 func(values[i], i, values);
    8145             }
    8146         }
    8147     }
    8148 };
    8149 
    8150 /*
    8151  * Rule: Don't use adjoining classes (.foo.bar).
    8152  */
    8153 
    8154 CSSLint.addRule({
    8155 
    8156     // rule information
    8157     id: "adjoining-classes",
    8158     name: "Disallow adjoining classes",
    8159     desc: "Don't use adjoining classes.",
    8160     url: "https://github.com/CSSLint/csslint/wiki/Disallow-adjoining-classes",
    8161     browsers: "IE6",
    8162 
    8163     // initialization
    8164     init: function(parser, reporter) {
    8165         "use strict";
    8166         var rule = this;
    8167         parser.addListener("startrule", function(event) {
    8168             var selectors = event.selectors,
    8169                 selector,
    8170                 part,
    8171                 modifier,
    8172                 classCount,
    8173                 i, j, k;
    8174 
    8175             for (i=0; i < selectors.length; i++) {
    8176                 selector = selectors[i];
    8177                 for (j=0; j < selector.parts.length; j++) {
    8178                     part = selector.parts[j];
    8179                     if (part.type === parser.SELECTOR_PART_TYPE) {
    8180                         classCount = 0;
    8181                         for (k=0; k < part.modifiers.length; k++) {
    8182                             modifier = part.modifiers[k];
    8183                             if (modifier.type === "class") {
    8184                                 classCount++;
    8185                             }
    8186                             if (classCount > 1){
    8187                                 reporter.report("Adjoining classes: "+selectors[i].text, part.line, part.col, rule);
    8188                             }
    8189                         }
    8190                     }
    8191                 }
    8192             }
    8193         });
    8194     }
    8195 
    8196 });
    8197 
    8198 /*
    8199  * Rule: Don't use width or height when using padding or border.
    8200  */
    8201 CSSLint.addRule({
    8202 
    8203     // rule information
    8204     id: "box-model",
    8205     name: "Beware of broken box size",
    8206     desc: "Don't use width or height when using padding or border.",
    8207     url: "https://github.com/CSSLint/csslint/wiki/Beware-of-box-model-size",
    8208     browsers: "All",
    8209 
    8210     // initialization
    8211     init: function(parser, reporter) {
    8212         "use strict";
    8213         var rule = this,
    8214             widthProperties = {
    8215                 border: 1,
    8216                 "border-left": 1,
    8217                 "border-right": 1,
    8218                 padding: 1,
    8219                 "padding-left": 1,
    8220                 "padding-right": 1
    8221             },
    8222             heightProperties = {
    8223                 border: 1,
    8224                 "border-bottom": 1,
    8225                 "border-top": 1,
    8226                 padding: 1,
    8227                 "padding-bottom": 1,
    8228                 "padding-top": 1
    8229             },
    8230             properties,
    8231             boxSizing = false;
    8232 
    8233         function startRule() {
    8234             properties = {};
    8235             boxSizing = false;
    8236         }
    8237 
    8238         function endRule() {
    8239             var prop, value;
    8240 
    8241             if (!boxSizing) {
    8242                 if (properties.height) {
    8243                     for (prop in heightProperties) {
    8244                         if (heightProperties.hasOwnProperty(prop) && properties[prop]) {
    8245                             value = properties[prop].value;
    8246                             // special case for padding
    8247                             if (!(prop === "padding" && value.parts.length === 2 && value.parts[0].value === 0)) {
    8248                                 reporter.report("Using height with " + prop + " can sometimes make elements larger than you expect.", properties[prop].line, properties[prop].col, rule);
    8249                             }
    8250                         }
    8251                     }
    8252                 }
    8253 
    8254                 if (properties.width) {
    8255                     for (prop in widthProperties) {
    8256                         if (widthProperties.hasOwnProperty(prop) && properties[prop]) {
    8257                             value = properties[prop].value;
    8258 
    8259                             if (!(prop === "padding" && value.parts.length === 2 && value.parts[1].value === 0)) {
    8260                                 reporter.report("Using width with " + prop + " can sometimes make elements larger than you expect.", properties[prop].line, properties[prop].col, rule);
    8261                             }
    8262                         }
    8263                     }
    8264                 }
    8265             }
    8266         }
    8267 
    8268         parser.addListener("startrule", startRule);
    8269         parser.addListener("startfontface", startRule);
    8270         parser.addListener("startpage", startRule);
    8271         parser.addListener("startpagemargin", startRule);
    8272         parser.addListener("startkeyframerule", startRule);
    8273         parser.addListener("startviewport", startRule);
    8274 
    8275         parser.addListener("property", function(event) {
    8276             var name = event.property.text.toLowerCase();
    8277 
    8278             if (heightProperties[name] || widthProperties[name]) {
    8279                 if (!/^0\S*$/.test(event.value) && !(name === "border" && event.value.toString() === "none")) {
    8280                     properties[name] = {
    8281                         line: event.property.line,
    8282                         col: event.property.col,
    8283                         value: event.value
    8284                     };
    8285                 }
    8286             } else {
    8287                 if (/^(width|height)/i.test(name) && /^(length|percentage)/.test(event.value.parts[0].type)) {
    8288                     properties[name] = 1;
    8289                 } else if (name === "box-sizing") {
    8290                     boxSizing = true;
    8291                 }
    8292             }
    8293 
    8294         });
    8295 
    8296         parser.addListener("endrule", endRule);
    8297         parser.addListener("endfontface", endRule);
    8298         parser.addListener("endpage", endRule);
    8299         parser.addListener("endpagemargin", endRule);
    8300         parser.addListener("endkeyframerule", endRule);
    8301         parser.addListener("endviewport", endRule);
    8302     }
    8303 
    8304 });
    8305 
    8306 /*
    8307  * Rule: box-sizing doesn't work in IE6 and IE7.
    8308  */
    8309 
    8310 CSSLint.addRule({
    8311 
    8312     // rule information
    8313     id: "box-sizing",
    8314     name: "Disallow use of box-sizing",
    8315     desc: "The box-sizing properties isn't supported in IE6 and IE7.",
    8316     url: "https://github.com/CSSLint/csslint/wiki/Disallow-box-sizing",
    8317     browsers: "IE6, IE7",
    8318     tags: ["Compatibility"],
    8319 
    8320     // initialization
    8321     init: function(parser, reporter) {
    8322         "use strict";
    8323         var rule = this;
    8324 
    8325         parser.addListener("property", function(event) {
    8326             var name = event.property.text.toLowerCase();
    8327 
    8328             if (name === "box-sizing") {
    8329                 reporter.report("The box-sizing property isn't supported in IE6 and IE7.", event.line, event.col, rule);
    8330             }
    8331         });
    8332     }
    8333 
    8334 });
    8335 
    8336 /*
    8337  * Rule: Use the bulletproof @font-face syntax to avoid 404's in old IE
    8338  * (http://www.fontspring.com/blog/the-new-bulletproof-font-face-syntax)
    8339  */
    8340 
    8341 CSSLint.addRule({
    8342 
    8343     // rule information
    8344     id: "bulletproof-font-face",
    8345     name: "Use the bulletproof @font-face syntax",
    8346     desc: "Use the bulletproof @font-face syntax to avoid 404's in old IE (http://www.fontspring.com/blog/the-new-bulletproof-font-face-syntax).",
    8347     url: "https://github.com/CSSLint/csslint/wiki/Bulletproof-font-face",
    8348     browsers: "All",
    8349 
    8350     // initialization
    8351     init: function(parser, reporter) {
    8352         "use strict";
    8353         var rule = this,
    8354             fontFaceRule = false,
    8355             firstSrc = true,
    8356             ruleFailed = false,
    8357             line, col;
    8358 
    8359         // Mark the start of a @font-face declaration so we only test properties inside it
    8360         parser.addListener("startfontface", function() {
    8361             fontFaceRule = true;
    8362         });
    8363 
    8364         parser.addListener("property", function(event) {
    8365             // If we aren't inside an @font-face declaration then just return
    8366             if (!fontFaceRule) {
    8367                 return;
    8368             }
    8369 
    8370             var propertyName = event.property.toString().toLowerCase(),
    8371                 value = event.value.toString();
    8372 
    8373             // Set the line and col numbers for use in the endfontface listener
    8374             line = event.line;
    8375             col = event.col;
    8376 
    8377             // This is the property that we care about, we can ignore the rest
    8378             if (propertyName === "src") {
    8379                 var regex = /^\s?url\(['"].+\.eot\?.*['"]\)\s*format\(['"]embedded-opentype['"]\).*$/i;
    8380 
    8381                 // We need to handle the advanced syntax with two src properties
    8382                 if (!value.match(regex) && firstSrc) {
    8383                     ruleFailed = true;
    8384                     firstSrc = false;
    8385                 } else if (value.match(regex) && !firstSrc) {
    8386                     ruleFailed = false;
    8387                 }
    8388             }
    8389 
    8390 
    8391         });
    8392 
    8393         // Back to normal rules that we don't need to test
    8394         parser.addListener("endfontface", function() {
    8395             fontFaceRule = false;
    8396 
    8397             if (ruleFailed) {
    8398                 reporter.report("@font-face declaration doesn't follow the fontspring bulletproof syntax.", line, col, rule);
    8399             }
    8400         });
    8401     }
    8402 });
    8403 
    8404 /*
    8405  * Rule: Include all compatible vendor prefixes to reach a wider
    8406  * range of users.
    8407  */
    8408 
    8409 CSSLint.addRule({
    8410 
    8411     // rule information
    8412     id: "compatible-vendor-prefixes",
    8413     name: "Require compatible vendor prefixes",
    8414     desc: "Include all compatible vendor prefixes to reach a wider range of users.",
    8415     url: "https://github.com/CSSLint/csslint/wiki/Require-compatible-vendor-prefixes",
    8416     browsers: "All",
    8417 
    8418     // initialization
    8419     init: function (parser, reporter) {
    8420         "use strict";
    8421         var rule = this,
    8422             compatiblePrefixes,
    8423             properties,
    8424             prop,
    8425             variations,
    8426             prefixed,
    8427             i,
    8428             len,
    8429             inKeyFrame = false,
    8430             arrayPush = Array.prototype.push,
    8431             applyTo = [];
    8432 
    8433         // See http://peter.sh/experiments/vendor-prefixed-css-property-overview/ for details
    8434         compatiblePrefixes = {
    8435             "animation"                  : "webkit",
    8436             "animation-delay"            : "webkit",
    8437             "animation-direction"        : "webkit",
    8438             "animation-duration"         : "webkit",
    8439             "animation-fill-mode"        : "webkit",
    8440             "animation-iteration-count"  : "webkit",
    8441             "animation-name"             : "webkit",
    8442             "animation-play-state"       : "webkit",
    8443             "animation-timing-function"  : "webkit",
    8444             "appearance"                 : "webkit moz",
    8445             "border-end"                 : "webkit moz",
    8446             "border-end-color"           : "webkit moz",
    8447             "border-end-style"           : "webkit moz",
    8448             "border-end-width"           : "webkit moz",
    8449             "border-image"               : "webkit moz o",
    8450             "border-radius"              : "webkit",
    8451             "border-start"               : "webkit moz",
    8452             "border-start-color"         : "webkit moz",
    8453             "border-start-style"         : "webkit moz",
    8454             "border-start-width"         : "webkit moz",
    8455             "box-align"                  : "webkit moz ms",
    8456             "box-direction"              : "webkit moz ms",
    8457             "box-flex"                   : "webkit moz ms",
    8458             "box-lines"                  : "webkit ms",
    8459             "box-ordinal-group"          : "webkit moz ms",
    8460             "box-orient"                 : "webkit moz ms",
    8461             "box-pack"                   : "webkit moz ms",
    8462             "box-sizing"                 : "",
    8463             "box-shadow"                 : "",
    8464             "column-count"               : "webkit moz ms",
    8465             "column-gap"                 : "webkit moz ms",
    8466             "column-rule"                : "webkit moz ms",
    8467             "column-rule-color"          : "webkit moz ms",
    8468             "column-rule-style"          : "webkit moz ms",
    8469             "column-rule-width"          : "webkit moz ms",
    8470             "column-width"               : "webkit moz ms",
    8471             "hyphens"                    : "epub moz",
    8472             "line-break"                 : "webkit ms",
    8473             "margin-end"                 : "webkit moz",
    8474             "margin-start"               : "webkit moz",
    8475             "marquee-speed"              : "webkit wap",
    8476             "marquee-style"              : "webkit wap",
    8477             "padding-end"                : "webkit moz",
    8478             "padding-start"              : "webkit moz",
    8479             "tab-size"                   : "moz o",
    8480             "text-size-adjust"           : "webkit ms",
    8481             "transform"                  : "webkit ms",
    8482             "transform-origin"           : "webkit ms",
    8483             "transition"                 : "",
    8484             "transition-delay"           : "",
    8485             "transition-duration"        : "",
    8486             "transition-property"        : "",
    8487             "transition-timing-function" : "",
    8488             "user-modify"                : "webkit moz",
    8489             "user-select"                : "webkit moz ms",
    8490             "word-break"                 : "epub ms",
    8491             "writing-mode"               : "epub ms"
    8492         };
    8493 
    8494 
    8495         for (prop in compatiblePrefixes) {
    8496             if (compatiblePrefixes.hasOwnProperty(prop)) {
    8497                 variations = [];
    8498                 prefixed = compatiblePrefixes[prop].split(" ");
    8499                 for (i = 0, len = prefixed.length; i < len; i++) {
    8500                     variations.push("-" + prefixed[i] + "-" + prop);
    8501                 }
    8502                 compatiblePrefixes[prop] = variations;
    8503                 arrayPush.apply(applyTo, variations);
    8504             }
    8505         }
    8506 
    8507         parser.addListener("startrule", function () {
    8508             properties = [];
    8509         });
    8510 
    8511         parser.addListener("startkeyframes", function (event) {
    8512             inKeyFrame = event.prefix || true;
    8513         });
    8514 
    8515         parser.addListener("endkeyframes", function () {
    8516             inKeyFrame = false;
    8517         });
    8518 
    8519         parser.addListener("property", function (event) {
    8520             var name = event.property;
    8521             if (CSSLint.Util.indexOf(applyTo, name.text) > -1) {
    8522 
    8523                 // e.g., -moz-transform is okay to be alone in @-moz-keyframes
    8524                 if (!inKeyFrame || typeof inKeyFrame !== "string" ||
    8525                         name.text.indexOf("-" + inKeyFrame + "-") !== 0) {
    8526                     properties.push(name);
    8527                 }
    8528             }
    8529         });
    8530 
    8531         parser.addListener("endrule", function () {
    8532             if (!properties.length) {
    8533                 return;
    8534             }
    8535 
    8536             var propertyGroups = {},
    8537                 i,
    8538                 len,
    8539                 name,
    8540                 prop,
    8541                 variations,
    8542                 value,
    8543                 full,
    8544                 actual,
    8545                 item,
    8546                 propertiesSpecified;
    8547 
    8548             for (i = 0, len = properties.length; i < len; i++) {
    8549                 name = properties[i];
    8550 
    8551                 for (prop in compatiblePrefixes) {
    8552                     if (compatiblePrefixes.hasOwnProperty(prop)) {
    8553                         variations = compatiblePrefixes[prop];
    8554                         if (CSSLint.Util.indexOf(variations, name.text) > -1) {
    8555                             if (!propertyGroups[prop]) {
    8556                                 propertyGroups[prop] = {
    8557                                     full: variations.slice(0),
    8558                                     actual: [],
    8559                                     actualNodes: []
    8560                                 };
    8561                             }
    8562                             if (CSSLint.Util.indexOf(propertyGroups[prop].actual, name.text) === -1) {
    8563                                 propertyGroups[prop].actual.push(name.text);
    8564                                 propertyGroups[prop].actualNodes.push(name);
    8565                             }
    8566                         }
    8567                     }
    8568                 }
    8569             }
    8570 
    8571             for (prop in propertyGroups) {
    8572                 if (propertyGroups.hasOwnProperty(prop)) {
    8573                     value = propertyGroups[prop];
    8574                     full = value.full;
    8575                     actual = value.actual;
    8576 
    8577                     if (full.length > actual.length) {
    8578                         for (i = 0, len = full.length; i < len; i++) {
    8579                             item = full[i];
    8580                             if (CSSLint.Util.indexOf(actual, item) === -1) {
    8581                                 propertiesSpecified = (actual.length === 1) ? actual[0] : (actual.length === 2) ? actual.join(" and ") : actual.join(", ");
    8582                                 reporter.report("The property " + item + " is compatible with " + propertiesSpecified + " and should be included as well.", value.actualNodes[0].line, value.actualNodes[0].col, rule);
    8583                             }
    8584                         }
    8585 
    8586                     }
    8587                 }
    8588             }
    8589         });
    8590     }
    8591 });
    8592 
    8593 /*
    8594  * Rule: Certain properties don't play well with certain display values.
    8595  * - float should not be used with inline-block
    8596  * - height, width, margin-top, margin-bottom, float should not be used with inline
    8597  * - vertical-align should not be used with block
    8598  * - margin, float should not be used with table-*
    8599  */
    8600 
    8601 CSSLint.addRule({
    8602 
    8603     // rule information
    8604     id: "display-property-grouping",
    8605     name: "Require properties appropriate for display",
    8606     desc: "Certain properties shouldn't be used with certain display property values.",
    8607     url: "https://github.com/CSSLint/csslint/wiki/Require-properties-appropriate-for-display",
    8608     browsers: "All",
    8609 
    8610     // initialization
    8611     init: function(parser, reporter) {
    8612         "use strict";
    8613         var rule = this;
    8614 
    8615         var propertiesToCheck = {
    8616                 display: 1,
    8617                 "float": "none",
    8618                 height: 1,
    8619                 width: 1,
    8620                 margin: 1,
    8621                 "margin-left": 1,
    8622                 "margin-right": 1,
    8623                 "margin-bottom": 1,
    8624                 "margin-top": 1,
    8625                 padding: 1,
    8626                 "padding-left": 1,
    8627                 "padding-right": 1,
    8628                 "padding-bottom": 1,
    8629                 "padding-top": 1,
    8630                 "vertical-align": 1
    8631             },
    8632             properties;
    8633 
    8634         function reportProperty(name, display, msg) {
    8635             if (properties[name]) {
    8636                 if (typeof propertiesToCheck[name] !== "string" || properties[name].value.toLowerCase() !== propertiesToCheck[name]) {
    8637                     reporter.report(msg || name + " can't be used with display: " + display + ".", properties[name].line, properties[name].col, rule);
    8638                 }
    8639             }
    8640         }
    8641 
    8642         function startRule() {
    8643             properties = {};
    8644         }
    8645 
    8646         function endRule() {
    8647 
    8648             var display = properties.display ? properties.display.value : null;
    8649             if (display) {
    8650                 switch (display) {
    8651 
    8652                     case "inline":
    8653                         // height, width, margin-top, margin-bottom, float should not be used with inline
    8654                         reportProperty("height", display);
    8655                         reportProperty("width", display);
    8656                         reportProperty("margin", display);
    8657                         reportProperty("margin-top", display);
    8658                         reportProperty("margin-bottom", display);
    8659                         reportProperty("float", display, "display:inline has no effect on floated elements (but may be used to fix the IE6 double-margin bug).");
    8660                         break;
    8661 
    8662                     case "block":
    8663                         // vertical-align should not be used with block
    8664                         reportProperty("vertical-align", display);
    8665                         break;
    8666 
    8667                     case "inline-block":
    8668                         // float should not be used with inline-block
    8669                         reportProperty("float", display);
    8670                         break;
    8671 
    8672                     default:
    8673                         // margin, float should not be used with table
    8674                         if (display.indexOf("table-") === 0) {
    8675                             reportProperty("margin", display);
    8676                             reportProperty("margin-left", display);
    8677                             reportProperty("margin-right", display);
    8678                             reportProperty("margin-top", display);
    8679                             reportProperty("margin-bottom", display);
    8680                             reportProperty("float", display);
    8681                         }
    8682 
    8683                         // otherwise do nothing
    8684                 }
    8685             }
    8686 
    8687         }
    8688 
    8689         parser.addListener("startrule", startRule);
    8690         parser.addListener("startfontface", startRule);
    8691         parser.addListener("startkeyframerule", startRule);
    8692         parser.addListener("startpagemargin", startRule);
    8693         parser.addListener("startpage", startRule);
    8694         parser.addListener("startviewport", startRule);
    8695 
    8696         parser.addListener("property", function(event) {
    8697             var name = event.property.text.toLowerCase();
    8698 
    8699             if (propertiesToCheck[name]) {
    8700                 properties[name] = {
    8701                     value: event.value.text,
    8702                     line: event.property.line,
    8703                     col: event.property.col
    8704                 };
    8705             }
    8706         });
    8707 
    8708         parser.addListener("endrule", endRule);
    8709         parser.addListener("endfontface", endRule);
    8710         parser.addListener("endkeyframerule", endRule);
    8711         parser.addListener("endpagemargin", endRule);
    8712         parser.addListener("endpage", endRule);
    8713         parser.addListener("endviewport", endRule);
    8714 
    8715     }
    8716 
    8717 });
    8718 
    8719 /*
    8720  * Rule: Disallow duplicate background-images (using url).
    8721  */
    8722 
    8723 CSSLint.addRule({
    8724 
    8725     // rule information
    8726     id: "duplicate-background-images",
    8727     name: "Disallow duplicate background images",
    8728     desc: "Every background-image should be unique. Use a common class for e.g. sprites.",
    8729     url: "https://github.com/CSSLint/csslint/wiki/Disallow-duplicate-background-images",
    8730     browsers: "All",
    8731 
    8732     // initialization
    8733     init: function(parser, reporter) {
    8734         "use strict";
    8735         var rule = this,
    8736             stack = {};
    8737 
    8738         parser.addListener("property", function(event) {
    8739             var name = event.property.text,
    8740                 value = event.value,
    8741                 i, len;
    8742 
    8743             if (name.match(/background/i)) {
    8744                 for (i=0, len=value.parts.length; i < len; i++) {
    8745                     if (value.parts[i].type === "uri") {
    8746                         if (typeof stack[value.parts[i].uri] === "undefined") {
    8747                             stack[value.parts[i].uri] = event;
    8748                         } else {
    8749                             reporter.report("Background image '" + value.parts[i].uri + "' was used multiple times, first declared at line " + stack[value.parts[i].uri].line + ", col " + stack[value.parts[i].uri].col + ".", event.line, event.col, rule);
    8750                         }
    8751                     }
    8752                 }
    8753             }
    8754         });
    8755     }
    8756 });
    8757 
    8758 /*
    8759  * Rule: Duplicate properties must appear one after the other. If an already-defined
    8760  * property appears somewhere else in the rule, then it's likely an error.
    8761  */
    8762 
    8763 CSSLint.addRule({
    8764 
    8765     // rule information
    8766     id: "duplicate-properties",
    8767     name: "Disallow duplicate properties",
    8768     desc: "Duplicate properties must appear one after the other.",
    8769     url: "https://github.com/CSSLint/csslint/wiki/Disallow-duplicate-properties",
    8770     browsers: "All",
    8771 
    8772     // initialization
    8773     init: function(parser, reporter) {
    8774         "use strict";
    8775         var rule = this,
    8776             properties,
    8777             lastProperty;
    8778 
    8779         function startRule() {
    8780             properties = {};
    8781         }
    8782 
    8783         parser.addListener("startrule", startRule);
    8784         parser.addListener("startfontface", startRule);
    8785         parser.addListener("startpage", startRule);
    8786         parser.addListener("startpagemargin", startRule);
    8787         parser.addListener("startkeyframerule", startRule);
    8788         parser.addListener("startviewport", startRule);
    8789 
    8790         parser.addListener("property", function(event) {
    8791             var property = event.property,
    8792                 name = property.text.toLowerCase();
    8793 
    8794             if (properties[name] && (lastProperty !== name || properties[name] === event.value.text)) {
    8795                 reporter.report("Duplicate property '" + event.property + "' found.", event.line, event.col, rule);
    8796             }
    8797 
    8798             properties[name] = event.value.text;
    8799             lastProperty = name;
    8800 
    8801         });
    8802 
    8803 
    8804     }
    8805 
    8806 });
    8807 
    8808 /*
    8809  * Rule: Style rules without any properties defined should be removed.
    8810  */
    8811 
    8812 CSSLint.addRule({
    8813 
    8814     // rule information
    8815     id: "empty-rules",
    8816     name: "Disallow empty rules",
    8817     desc: "Rules without any properties specified should be removed.",
    8818     url: "https://github.com/CSSLint/csslint/wiki/Disallow-empty-rules",
    8819     browsers: "All",
    8820 
    8821     // initialization
    8822     init: function(parser, reporter) {
    8823         "use strict";
    8824         var rule = this,
    8825             count = 0;
    8826 
    8827         parser.addListener("startrule", function() {
    8828             count=0;
    8829         });
    8830 
    8831         parser.addListener("property", function() {
    8832             count++;
    8833         });
    8834 
    8835         parser.addListener("endrule", function(event) {
    8836             var selectors = event.selectors;
    8837             if (count === 0) {
    8838                 reporter.report("Rule is empty.", selectors[0].line, selectors[0].col, rule);
    8839             }
    8840         });
    8841     }
    8842 
    8843 });
    8844 
    8845 /*
    8846  * Rule: There should be no syntax errors. (Duh.)
    8847  */
    8848 
    8849 CSSLint.addRule({
    8850 
    8851     // rule information
    8852     id: "errors",
    8853     name: "Parsing Errors",
    8854     desc: "This rule looks for recoverable syntax errors.",
    8855     browsers: "All",
    8856 
    8857     // initialization
    8858     init: function(parser, reporter) {
    8859         "use strict";
    8860         var rule = this;
    8861 
    8862         parser.addListener("error", function(event) {
    8863             reporter.error(event.message, event.line, event.col, rule);
    8864         });
    8865 
    8866     }
    8867 
    8868 });
    8869 
    8870 CSSLint.addRule({
    8871 
    8872     // rule information
    8873     id: "fallback-colors",
    8874     name: "Require fallback colors",
    8875     desc: "For older browsers that don't support RGBA, HSL, or HSLA, provide a fallback color.",
    8876     url: "https://github.com/CSSLint/csslint/wiki/Require-fallback-colors",
    8877     browsers: "IE6,IE7,IE8",
    8878 
    8879     // initialization
    8880     init: function(parser, reporter) {
    8881         "use strict";
    8882         var rule = this,
    8883             lastProperty,
    8884             propertiesToCheck = {
    8885                 color: 1,
    8886                 background: 1,
    8887                 "border-color": 1,
    8888                 "border-top-color": 1,
    8889                 "border-right-color": 1,
    8890                 "border-bottom-color": 1,
    8891                 "border-left-color": 1,
    8892                 border: 1,
    8893                 "border-top": 1,
    8894                 "border-right": 1,
    8895                 "border-bottom": 1,
    8896                 "border-left": 1,
    8897                 "background-color": 1
    8898             };
    8899 
    8900         function startRule() {
    8901             lastProperty = null;
    8902         }
    8903 
    8904         parser.addListener("startrule", startRule);
    8905         parser.addListener("startfontface", startRule);
    8906         parser.addListener("startpage", startRule);
    8907         parser.addListener("startpagemargin", startRule);
    8908         parser.addListener("startkeyframerule", startRule);
    8909         parser.addListener("startviewport", startRule);
    8910 
    8911         parser.addListener("property", function(event) {
    8912             var property = event.property,
    8913                 name = property.text.toLowerCase(),
    8914                 parts = event.value.parts,
    8915                 i = 0,
    8916                 colorType = "",
    8917                 len = parts.length;
    8918 
    8919             if (propertiesToCheck[name]) {
    8920                 while (i < len) {
    8921                     if (parts[i].type === "color") {
    8922                         if ("alpha" in parts[i] || "hue" in parts[i]) {
    8923 
    8924                             if (/([^\)]+)\(/.test(parts[i])) {
    8925                                 colorType = RegExp.$1.toUpperCase();
    8926                             }
    8927 
    8928                             if (!lastProperty || (lastProperty.property.text.toLowerCase() !== name || lastProperty.colorType !== "compat")) {
    8929                                 reporter.report("Fallback " + name + " (hex or RGB) should precede " + colorType + " " + name + ".", event.line, event.col, rule);
    8930                             }
    8931                         } else {
    8932                             event.colorType = "compat";
    8933                         }
    8934                     }
    8935 
    8936                     i++;
    8937                 }
    8938             }
    8939 
    8940             lastProperty = event;
    8941         });
    8942 
    8943     }
    8944 
    8945 });
    8946 
    8947 /*
    8948  * Rule: You shouldn't use more than 10 floats. If you do, there's probably
    8949  * room for some abstraction.
    8950  */
    8951 
    8952 CSSLint.addRule({
    8953 
    8954     // rule information
    8955     id: "floats",
    8956     name: "Disallow too many floats",
    8957     desc: "This rule tests if the float property is used too many times",
    8958     url: "https://github.com/CSSLint/csslint/wiki/Disallow-too-many-floats",
    8959     browsers: "All",
    8960 
    8961     // initialization
    8962     init: function(parser, reporter) {
    8963         "use strict";
    8964         var rule = this;
    8965         var count = 0;
    8966 
    8967         // count how many times "float" is used
    8968         parser.addListener("property", function(event) {
    8969             if (event.property.text.toLowerCase() === "float" &&
    8970                     event.value.text.toLowerCase() !== "none") {
    8971                 count++;
    8972             }
    8973         });
    8974 
    8975         // report the results
    8976         parser.addListener("endstylesheet", function() {
    8977             reporter.stat("floats", count);
    8978             if (count >= 10) {
    8979                 reporter.rollupWarn("Too many floats (" + count + "), you're probably using them for layout. Consider using a grid system instead.", rule);
    8980             }
    8981         });
    8982     }
    8983 
    8984 });
    8985 
    8986 /*
    8987  * Rule: Avoid too many @font-face declarations in the same stylesheet.
    8988  */
    8989 
    8990 CSSLint.addRule({
    8991 
    8992     // rule information
    8993     id: "font-faces",
    8994     name: "Don't use too many web fonts",
    8995     desc: "Too many different web fonts in the same stylesheet.",
    8996     url: "https://github.com/CSSLint/csslint/wiki/Don%27t-use-too-many-web-fonts",
    8997     browsers: "All",
    8998 
    8999     // initialization
    9000     init: function(parser, reporter) {
    9001         "use strict";
    9002         var rule = this,
    9003             count = 0;
    9004 
    9005 
    9006         parser.addListener("startfontface", function() {
    9007             count++;
    9008         });
    9009 
    9010         parser.addListener("endstylesheet", function() {
    9011             if (count > 5) {
    9012                 reporter.rollupWarn("Too many @font-face declarations (" + count + ").", rule);
    9013             }
    9014         });
    9015     }
    9016 
    9017 });
    9018 
    9019 /*
    9020  * Rule: You shouldn't need more than 9 font-size declarations.
    9021  */
    9022 
    9023 CSSLint.addRule({
    9024 
    9025     // rule information
    9026     id: "font-sizes",
    9027     name: "Disallow too many font sizes",
    9028     desc: "Checks the number of font-size declarations.",
    9029     url: "https://github.com/CSSLint/csslint/wiki/Don%27t-use-too-many-font-size-declarations",
    9030     browsers: "All",
    9031 
    9032     // initialization
    9033     init: function(parser, reporter) {
    9034         "use strict";
    9035         var rule = this,
    9036             count = 0;
    9037 
    9038         // check for use of "font-size"
    9039         parser.addListener("property", function(event) {
    9040             if (event.property.toString() === "font-size") {
    9041                 count++;
    9042             }
    9043         });
    9044 
    9045         // report the results
    9046         parser.addListener("endstylesheet", function() {
    9047             reporter.stat("font-sizes", count);
    9048             if (count >= 10) {
    9049                 reporter.rollupWarn("Too many font-size declarations (" + count + "), abstraction needed.", rule);
    9050             }
    9051         });
    9052     }
    9053 
    9054 });
    9055 
    9056 /*
    9057  * Rule: When using a vendor-prefixed gradient, make sure to use them all.
    9058  */
    9059 
    9060 CSSLint.addRule({
    9061 
    9062     // rule information
    9063     id: "gradients",
    9064     name: "Require all gradient definitions",
    9065     desc: "When using a vendor-prefixed gradient, make sure to use them all.",
    9066     url: "https://github.com/CSSLint/csslint/wiki/Require-all-gradient-definitions",
    9067     browsers: "All",
    9068 
    9069     // initialization
    9070     init: function(parser, reporter) {
    9071         "use strict";
    9072         var rule = this,
    9073             gradients;
    9074 
    9075         parser.addListener("startrule", function() {
    9076             gradients = {
    9077                 moz: 0,
    9078                 webkit: 0,
    9079                 oldWebkit: 0,
    9080                 o: 0
    9081             };
    9082         });
    9083 
    9084         parser.addListener("property", function(event) {
    9085 
    9086             if (/\-(moz|o|webkit)(?:\-(?:linear|radial))\-gradient/i.test(event.value)) {
    9087                 gradients[RegExp.$1] = 1;
    9088             } else if (/\-webkit\-gradient/i.test(event.value)) {
    9089                 gradients.oldWebkit = 1;
    9090             }
    9091 
    9092         });
    9093 
    9094         parser.addListener("endrule", function(event) {
    9095             var missing = [];
    9096 
    9097             if (!gradients.moz) {
    9098                 missing.push("Firefox 3.6+");
    9099             }
    9100 
    9101             if (!gradients.webkit) {
    9102                 missing.push("Webkit (Safari 5+, Chrome)");
    9103             }
    9104 
    9105             if (!gradients.oldWebkit) {
    9106                 missing.push("Old Webkit (Safari 4+, Chrome)");
    9107             }
    9108 
    9109             if (!gradients.o) {
    9110                 missing.push("Opera 11.1+");
    9111             }
    9112 
    9113             if (missing.length && missing.length < 4) {
    9114                 reporter.report("Missing vendor-prefixed CSS gradients for " + missing.join(", ") + ".", event.selectors[0].line, event.selectors[0].col, rule);
    9115             }
    9116 
    9117         });
    9118 
    9119     }
    9120 
    9121 });
    9122 
    9123 /*
    9124  * Rule: Don't use IDs for selectors.
    9125  */
    9126 
    9127 CSSLint.addRule({
    9128 
    9129     // rule information
    9130     id: "ids",
    9131     name: "Disallow IDs in selectors",
    9132     desc: "Selectors should not contain IDs.",
    9133     url: "https://github.com/CSSLint/csslint/wiki/Disallow-IDs-in-selectors",
    9134     browsers: "All",
    9135 
    9136     // initialization
    9137     init: function(parser, reporter) {
    9138         "use strict";
    9139         var rule = this;
    9140         parser.addListener("startrule", function(event) {
    9141             var selectors = event.selectors,
    9142                 selector,
    9143                 part,
    9144                 modifier,
    9145                 idCount,
    9146                 i, j, k;
    9147 
    9148             for (i=0; i < selectors.length; i++) {
    9149                 selector = selectors[i];
    9150                 idCount = 0;
    9151 
    9152                 for (j=0; j < selector.parts.length; j++) {
    9153                     part = selector.parts[j];
    9154                     if (part.type === parser.SELECTOR_PART_TYPE) {
    9155                         for (k=0; k < part.modifiers.length; k++) {
    9156                             modifier = part.modifiers[k];
    9157                             if (modifier.type === "id") {
    9158                                 idCount++;
    9159                             }
    9160                         }
    9161                     }
    9162                 }
    9163 
    9164                 if (idCount === 1) {
    9165                     reporter.report("Don't use IDs in selectors.", selector.line, selector.col, rule);
    9166                 } else if (idCount > 1) {
    9167                     reporter.report(idCount + " IDs in the selector, really?", selector.line, selector.col, rule);
    9168                 }
    9169             }
    9170 
    9171         });
    9172     }
    9173 
    9174 });
    9175 
    9176 /*
    9177  * Rule: IE6-9 supports up to 31 stylesheet import.
    9178  * Reference:
    9179  * http://blogs.msdn.com/b/ieinternals/archive/2011/05/14/internet-explorer-stylesheet-rule-selector-import-sheet-limit-maximum.aspx
    9180  */
    9181 
    9182 CSSLint.addRule({
    9183 
    9184     // rule information
    9185     id: "import-ie-limit",
    9186     name: "@import limit on IE6-IE9",
    9187     desc: "IE6-9 supports up to 31 @import per stylesheet",
    9188     browsers: "IE6, IE7, IE8, IE9",
    9189 
    9190     // initialization
    9191     init: function(parser, reporter) {
    9192         "use strict";
    9193         var rule = this,
    9194             MAX_IMPORT_COUNT = 31,
    9195             count = 0;
    9196 
    9197         function startPage() {
    9198             count = 0;
    9199         }
    9200 
    9201         parser.addListener("startpage", startPage);
    9202 
    9203         parser.addListener("import", function() {
    9204             count++;
    9205         });
    9206 
    9207         parser.addListener("endstylesheet", function() {
    9208             if (count > MAX_IMPORT_COUNT) {
    9209                 reporter.rollupError(
    9210                     "Too many @import rules (" + count + "). IE6-9 supports up to 31 import per stylesheet.",
    9211                     rule
    9212                 );
    9213             }
    9214         });
    9215     }
    9216 
    9217 });
    9218 
    9219 /*
    9220  * Rule: Don't use @import, use <link> instead.
    9221  */
    9222 
    9223 CSSLint.addRule({
    9224 
    9225     // rule information
    9226     id: "import",
    9227     name: "Disallow @import",
    9228     desc: "Don't use @import, use <link> instead.",
    9229     url: "https://github.com/CSSLint/csslint/wiki/Disallow-%40import",
    9230     browsers: "All",
    9231 
    9232     // initialization
    9233     init: function(parser, reporter) {
    9234         "use strict";
    9235         var rule = this;
    9236 
    9237         parser.addListener("import", function(event) {
    9238             reporter.report("@import prevents parallel downloads, use <link> instead.", event.line, event.col, rule);
    9239         });
    9240 
    9241     }
    9242 
    9243 });
    9244 
    9245 /*
    9246  * Rule: Make sure !important is not overused, this could lead to specificity
    9247  * war. Display a warning on !important declarations, an error if it's
    9248  * used more at least 10 times.
    9249  */
    9250 
    9251 CSSLint.addRule({
    9252 
    9253     // rule information
    9254     id: "important",
    9255     name: "Disallow !important",
    9256     desc: "Be careful when using !important declaration",
    9257     url: "https://github.com/CSSLint/csslint/wiki/Disallow-%21important",
    9258     browsers: "All",
    9259 
    9260     // initialization
    9261     init: function(parser, reporter) {
    9262         "use strict";
    9263         var rule = this,
    9264             count = 0;
    9265 
    9266         // warn that important is used and increment the declaration counter
    9267         parser.addListener("property", function(event) {
    9268             if (event.important === true) {
    9269                 count++;
    9270                 reporter.report("Use of !important", event.line, event.col, rule);
    9271             }
    9272         });
    9273 
    9274         // if there are more than 10, show an error
    9275         parser.addListener("endstylesheet", function() {
    9276             reporter.stat("important", count);
    9277             if (count >= 10) {
    9278                 reporter.rollupWarn("Too many !important declarations (" + count + "), try to use less than 10 to avoid specificity issues.", rule);
    9279             }
    9280         });
    9281     }
    9282 
    9283 });
    9284 
    9285 /*
    9286  * Rule: Properties should be known (listed in CSS3 specification) or
    9287  * be a vendor-prefixed property.
    9288  */
    9289 
    9290 CSSLint.addRule({
    9291 
    9292     // rule information
    9293     id: "known-properties",
    9294     name: "Require use of known properties",
    9295     desc: "Properties should be known (listed in CSS3 specification) or be a vendor-prefixed property.",
    9296     url: "https://github.com/CSSLint/csslint/wiki/Require-use-of-known-properties",
    9297     browsers: "All",
    9298 
    9299     // initialization
    9300     init: function(parser, reporter) {
    9301         "use strict";
    9302         var rule = this;
    9303 
    9304         parser.addListener("property", function(event) {
    9305 
    9306             // the check is handled entirely by the parser-lib (https://github.com/nzakas/parser-lib)
    9307             if (event.invalid) {
    9308                 reporter.report(event.invalid.message, event.line, event.col, rule);
    9309             }
    9310 
    9311         });
    9312     }
    9313 
    9314 });
    9315 
    9316 /*
    9317  * Rule: All properties should be in alphabetical order.
    9318  */
    9319 
    9320 CSSLint.addRule({
    9321 
    9322     // rule information
    9323     id: "order-alphabetical",
    9324     name: "Alphabetical order",
    9325     desc: "Assure properties are in alphabetical order",
    9326     browsers: "All",
    9327 
    9328     // initialization
    9329     init: function(parser, reporter) {
    9330         "use strict";
    9331         var rule = this,
    9332             properties;
    9333 
    9334         var startRule = function () {
    9335             properties = [];
    9336         };
    9337 
    9338         var endRule = function(event) {
    9339             var currentProperties = properties.join(","),
    9340                 expectedProperties = properties.sort().join(",");
    9341 
    9342             if (currentProperties !== expectedProperties) {
    9343                 reporter.report("Rule doesn't have all its properties in alphabetical order.", event.line, event.col, rule);
    9344             }
    9345         };
    9346 
    9347         parser.addListener("startrule", startRule);
    9348         parser.addListener("startfontface", startRule);
    9349         parser.addListener("startpage", startRule);
    9350         parser.addListener("startpagemargin", startRule);
    9351         parser.addListener("startkeyframerule", startRule);
    9352         parser.addListener("startviewport", startRule);
    9353 
    9354         parser.addListener("property", function(event) {
    9355             var name = event.property.text,
    9356                 lowerCasePrefixLessName = name.toLowerCase().replace(/^-.*?-/, "");
    9357 
    9358             properties.push(lowerCasePrefixLessName);
    9359         });
    9360 
    9361         parser.addListener("endrule", endRule);
    9362         parser.addListener("endfontface", endRule);
    9363         parser.addListener("endpage", endRule);
    9364         parser.addListener("endpagemargin", endRule);
    9365         parser.addListener("endkeyframerule", endRule);
    9366         parser.addListener("endviewport", endRule);
    9367     }
    9368 
    9369 });
    9370 
    9371 /*
    9372  * Rule: outline: none or outline: 0 should only be used in a :focus rule
    9373  *       and only if there are other properties in the same rule.
    9374  */
    9375 
    9376 CSSLint.addRule({
    9377 
    9378     // rule information
    9379     id: "outline-none",
    9380     name: "Disallow outline: none",
    9381     desc: "Use of outline: none or outline: 0 should be limited to :focus rules.",
    9382     url: "https://github.com/CSSLint/csslint/wiki/Disallow-outline%3Anone",
    9383     browsers: "All",
    9384     tags: ["Accessibility"],
    9385 
    9386     // initialization
    9387     init: function(parser, reporter) {
    9388         "use strict";
    9389         var rule = this,
    9390             lastRule;
    9391 
    9392         function startRule(event) {
    9393             if (event.selectors) {
    9394                 lastRule = {
    9395                     line: event.line,
    9396                     col: event.col,
    9397                     selectors: event.selectors,
    9398                     propCount: 0,
    9399                     outline: false
    9400                 };
    9401             } else {
    9402                 lastRule = null;
    9403             }
    9404         }
    9405 
    9406         function endRule() {
    9407             if (lastRule) {
    9408                 if (lastRule.outline) {
    9409                     if (lastRule.selectors.toString().toLowerCase().indexOf(":focus") === -1) {
    9410                         reporter.report("Outlines should only be modified using :focus.", lastRule.line, lastRule.col, rule);
    9411                     } else if (lastRule.propCount === 1) {
    9412                         reporter.report("Outlines shouldn't be hidden unless other visual changes are made.", lastRule.line, lastRule.col, rule);
    9413                     }
    9414                 }
    9415             }
    9416         }
    9417 
    9418         parser.addListener("startrule", startRule);
    9419         parser.addListener("startfontface", startRule);
    9420         parser.addListener("startpage", startRule);
    9421         parser.addListener("startpagemargin", startRule);
    9422         parser.addListener("startkeyframerule", startRule);
    9423         parser.addListener("startviewport", startRule);
    9424 
    9425         parser.addListener("property", function(event) {
    9426             var name = event.property.text.toLowerCase(),
    9427                 value = event.value;
    9428 
    9429             if (lastRule) {
    9430                 lastRule.propCount++;
    9431                 if (name === "outline" && (value.toString() === "none" || value.toString() === "0")) {
    9432                     lastRule.outline = true;
    9433                 }
    9434             }
    9435 
    9436         });
    9437 
    9438         parser.addListener("endrule", endRule);
    9439         parser.addListener("endfontface", endRule);
    9440         parser.addListener("endpage", endRule);
    9441         parser.addListener("endpagemargin", endRule);
    9442         parser.addListener("endkeyframerule", endRule);
    9443         parser.addListener("endviewport", endRule);
    9444 
    9445     }
    9446 
    9447 });
    9448 
    9449 /*
    9450  * Rule: Don't use classes or IDs with elements (a.foo or a#foo).
    9451  */
    9452 
    9453 CSSLint.addRule({
    9454 
    9455     // rule information
    9456     id: "overqualified-elements",
    9457     name: "Disallow overqualified elements",
    9458     desc: "Don't use classes or IDs with elements (a.foo or a#foo).",
    9459     url: "https://github.com/CSSLint/csslint/wiki/Disallow-overqualified-elements",
    9460     browsers: "All",
    9461 
    9462     // initialization
    9463     init: function(parser, reporter) {
    9464         "use strict";
    9465         var rule = this,
    9466             classes = {};
    9467 
    9468         parser.addListener("startrule", function(event) {
    9469             var selectors = event.selectors,
    9470                 selector,
    9471                 part,
    9472                 modifier,
    9473                 i, j, k;
    9474 
    9475             for (i=0; i < selectors.length; i++) {
    9476                 selector = selectors[i];
    9477 
    9478                 for (j=0; j < selector.parts.length; j++) {
    9479                     part = selector.parts[j];
    9480                     if (part.type === parser.SELECTOR_PART_TYPE) {
    9481                         for (k=0; k < part.modifiers.length; k++) {
    9482                             modifier = part.modifiers[k];
    9483                             if (part.elementName && modifier.type === "id") {
    9484                                 reporter.report("Element (" + part + ") is overqualified, just use " + modifier + " without element name.", part.line, part.col, rule);
    9485                             } else if (modifier.type === "class") {
    9486 
    9487                                 if (!classes[modifier]) {
    9488                                     classes[modifier] = [];
    9489                                 }
    9490                                 classes[modifier].push({
    9491                                     modifier: modifier,
    9492                                     part: part
    9493                                 });
    9494                             }
    9495                         }
    9496                     }
    9497                 }
    9498             }
    9499         });
    9500 
    9501         parser.addListener("endstylesheet", function() {
    9502 
    9503             var prop;
    9504             for (prop in classes) {
    9505                 if (classes.hasOwnProperty(prop)) {
    9506 
    9507                     // one use means that this is overqualified
    9508                     if (classes[prop].length === 1 && classes[prop][0].part.elementName) {
    9509                         reporter.report("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);
    9510                     }
    9511                 }
    9512             }
    9513         });
    9514     }
    9515 
    9516 });
    9517 
    9518 /*
    9519  * Rule: Headings (h1-h6) should not be qualified (namespaced).
    9520  */
    9521 
    9522 CSSLint.addRule({
    9523 
    9524     // rule information
    9525     id: "qualified-headings",
    9526     name: "Disallow qualified headings",
    9527     desc: "Headings should not be qualified (namespaced).",
    9528     url: "https://github.com/CSSLint/csslint/wiki/Disallow-qualified-headings",
    9529     browsers: "All",
    9530 
    9531     // initialization
    9532     init: function(parser, reporter) {
    9533         "use strict";
    9534         var rule = this;
    9535 
    9536         parser.addListener("startrule", function(event) {
    9537             var selectors = event.selectors,
    9538                 selector,
    9539                 part,
    9540                 i, j;
    9541 
    9542             for (i=0; i < selectors.length; i++) {
    9543                 selector = selectors[i];
    9544 
    9545                 for (j=0; j < selector.parts.length; j++) {
    9546                     part = selector.parts[j];
    9547                     if (part.type === parser.SELECTOR_PART_TYPE) {
    9548                         if (part.elementName && /h[1-6]/.test(part.elementName.toString()) && j > 0) {
    9549                             reporter.report("Heading (" + part.elementName + ") should not be qualified.", part.line, part.col, rule);
    9550                         }
    9551                     }
    9552                 }
    9553             }
    9554         });
    9555     }
    9556 
    9557 });
    9558 
    9559 /*
    9560  * Rule: Selectors that look like regular expressions are slow and should be avoided.
    9561  */
    9562 
    9563 CSSLint.addRule({
    9564 
    9565     // rule information
    9566     id: "regex-selectors",
    9567     name: "Disallow selectors that look like regexs",
    9568     desc: "Selectors that look like regular expressions are slow and should be avoided.",
    9569     url: "https://github.com/CSSLint/csslint/wiki/Disallow-selectors-that-look-like-regular-expressions",
    9570     browsers: "All",
    9571 
    9572     // initialization
    9573     init: function(parser, reporter) {
    9574         "use strict";
    9575         var rule = this;
    9576 
    9577         parser.addListener("startrule", function(event) {
    9578             var selectors = event.selectors,
    9579                 selector,
    9580                 part,
    9581                 modifier,
    9582                 i, j, k;
    9583 
    9584             for (i=0; i < selectors.length; i++) {
    9585                 selector = selectors[i];
    9586                 for (j=0; j < selector.parts.length; j++) {
    9587                     part = selector.parts[j];
    9588                     if (part.type === parser.SELECTOR_PART_TYPE) {
    9589                         for (k=0; k < part.modifiers.length; k++) {
    9590                             modifier = part.modifiers[k];
    9591                             if (modifier.type === "attribute") {
    9592                                 if (/([~\|\^\$\*]=)/.test(modifier)) {
    9593                                     reporter.report("Attribute selectors with " + RegExp.$1 + " are slow!", modifier.line, modifier.col, rule);
    9594                                 }
    9595                             }
    9596 
    9597                         }
    9598                     }
    9599                 }
    9600             }
    9601         });
    9602     }
    9603 
    9604 });
    9605 
    9606 /*
    9607  * Rule: Total number of rules should not exceed x.
    9608  */
    9609 
    9610 CSSLint.addRule({
    9611 
    9612     // rule information
    9613     id: "rules-count",
    9614     name: "Rules Count",
    9615     desc: "Track how many rules there are.",
    9616     browsers: "All",
    9617 
    9618     // initialization
    9619     init: function(parser, reporter) {
    9620         "use strict";
    9621         var count = 0;
    9622 
    9623         // count each rule
    9624         parser.addListener("startrule", function() {
    9625             count++;
    9626         });
    9627 
    9628         parser.addListener("endstylesheet", function() {
    9629             reporter.stat("rule-count", count);
    9630         });
    9631     }
    9632 
    9633 });
    9634 
    9635 /*
    9636  * Rule: Warn people with approaching the IE 4095 limit
    9637  */
    9638 
    9639 CSSLint.addRule({
    9640 
    9641     // rule information
    9642     id: "selector-max-approaching",
    9643     name: "Warn when approaching the 4095 selector limit for IE",
    9644     desc: "Will warn when selector count is >= 3800 selectors.",
    9645     browsers: "IE",
    9646 
    9647     // initialization
    9648     init: function(parser, reporter) {
    9649         "use strict";
    9650         var rule = this, count = 0;
    9651 
    9652         parser.addListener("startrule", function(event) {
    9653             count += event.selectors.length;
    9654         });
    9655 
    9656         parser.addListener("endstylesheet", function() {
    9657             if (count >= 3800) {
    9658                 reporter.report("You have " + count + " selectors. Internet Explorer supports a maximum of 4095 selectors per stylesheet. Consider refactoring.", 0, 0, rule);
    9659             }
    9660         });
    9661     }
    9662 
    9663 });
    9664 
    9665 /*
    9666  * Rule: Warn people past the IE 4095 limit
    9667  */
    9668 
    9669 CSSLint.addRule({
    9670 
    9671     // rule information
    9672     id: "selector-max",
    9673     name: "Error when past the 4095 selector limit for IE",
    9674     desc: "Will error when selector count is > 4095.",
    9675     browsers: "IE",
    9676 
    9677     // initialization
    9678     init: function(parser, reporter) {
    9679         "use strict";
    9680         var rule = this, count = 0;
    9681 
    9682         parser.addListener("startrule", function(event) {
    9683             count += event.selectors.length;
    9684         });
    9685 
    9686         parser.addListener("endstylesheet", function() {
    9687             if (count > 4095) {
    9688                 reporter.report("You have " + count + " selectors. Internet Explorer supports a maximum of 4095 selectors per stylesheet. Consider refactoring.", 0, 0, rule);
    9689             }
    9690         });
    9691     }
    9692 
    9693 });
    9694 
    9695 /*
    9696  * Rule: Avoid new-line characters in selectors.
    9697  */
    9698 
    9699 CSSLint.addRule({
    9700 
    9701     // rule information
    9702     id: "selector-newline",
    9703     name: "Disallow new-line characters in selectors",
    9704     desc: "New-line characters in selectors are usually a forgotten comma and not a descendant combinator.",
    9705     browsers: "All",
    9706 
    9707     // initialization
    9708     init: function(parser, reporter) {
    9709         "use strict";
    9710         var rule = this;
    9711 
    9712         function startRule(event) {
    9713             var i, len, selector, p, n, pLen, part, part2, type, currentLine, nextLine,
    9714                 selectors = event.selectors;
    9715 
    9716             for (i = 0, len = selectors.length; i < len; i++) {
    9717                 selector = selectors[i];
    9718                 for (p = 0, pLen = selector.parts.length; p < pLen; p++) {
    9719                     for (n = p + 1; n < pLen; n++) {
    9720                         part = selector.parts[p];
    9721                         part2 = selector.parts[n];
    9722                         type = part.type;
    9723                         currentLine = part.line;
    9724                         nextLine = part2.line;
    9725 
    9726                         if (type === "descendant" && nextLine > currentLine) {
    9727                             reporter.report("newline character found in selector (forgot a comma?)", currentLine, selectors[i].parts[0].col, rule);
    9728                         }
    9729                     }
    9730                 }
    9731 
    9732             }
    9733         }
    9734 
    9735         parser.addListener("startrule", startRule);
    9736 
    9737     }
    9738 });
    9739 
    9740 /*
    9741  * Rule: Use shorthand properties where possible.
    9742  *
    9743  */
    9744 
    9745 CSSLint.addRule({
    9746 
    9747     // rule information
    9748     id: "shorthand",
    9749     name: "Require shorthand properties",
    9750     desc: "Use shorthand properties where possible.",
    9751     url: "https://github.com/CSSLint/csslint/wiki/Require-shorthand-properties",
    9752     browsers: "All",
    9753 
    9754     // initialization
    9755     init: function(parser, reporter) {
    9756         "use strict";
    9757         var rule = this,
    9758             prop, i, len,
    9759             propertiesToCheck = {},
    9760             properties,
    9761             mapping = {
    9762                 "margin": [
    9763                     "margin-top",
    9764                     "margin-bottom",
    9765                     "margin-left",
    9766                     "margin-right"
    9767                 ],
    9768                 "padding": [
    9769                     "padding-top",
    9770                     "padding-bottom",
    9771                     "padding-left",
    9772                     "padding-right"
    9773                 ]
    9774             };
    9775 
    9776         // initialize propertiesToCheck
    9777         for (prop in mapping) {
    9778             if (mapping.hasOwnProperty(prop)) {
    9779                 for (i=0, len=mapping[prop].length; i < len; i++) {
    9780                     propertiesToCheck[mapping[prop][i]] = prop;
    9781                 }
    9782             }
    9783         }
    9784 
    9785         function startRule() {
    9786             properties = {};
    9787         }
    9788 
    9789         // event handler for end of rules
    9790         function endRule(event) {
    9791 
    9792             var prop, i, len, total;
    9793 
    9794             // check which properties this rule has
    9795             for (prop in mapping) {
    9796                 if (mapping.hasOwnProperty(prop)) {
    9797                     total=0;
    9798 
    9799                     for (i=0, len=mapping[prop].length; i < len; i++) {
    9800                         total += properties[mapping[prop][i]] ? 1 : 0;
    9801                     }
    9802 
    9803                     if (total === mapping[prop].length) {
    9804                         reporter.report("The properties " + mapping[prop].join(", ") + " can be replaced by " + prop + ".", event.line, event.col, rule);
    9805                     }
    9806                 }
    9807             }
    9808         }
    9809 
    9810         parser.addListener("startrule", startRule);
    9811         parser.addListener("startfontface", startRule);
    9812 
    9813         // check for use of "font-size"
    9814         parser.addListener("property", function(event) {
    9815             var name = event.property.toString().toLowerCase();
    9816 
    9817             if (propertiesToCheck[name]) {
    9818                 properties[name] = 1;
    9819             }
    9820         });
    9821 
    9822         parser.addListener("endrule", endRule);
    9823         parser.addListener("endfontface", endRule);
    9824 
    9825     }
    9826 
    9827 });
    9828 
    9829 /*
    9830  * Rule: Don't use properties with a star prefix.
    9831  *
    9832  */
    9833 
    9834 CSSLint.addRule({
    9835 
    9836     // rule information
    9837     id: "star-property-hack",
    9838     name: "Disallow properties with a star prefix",
    9839     desc: "Checks for the star property hack (targets IE6/7)",
    9840     url: "https://github.com/CSSLint/csslint/wiki/Disallow-star-hack",
    9841     browsers: "All",
    9842 
    9843     // initialization
    9844     init: function(parser, reporter) {
    9845         "use strict";
    9846         var rule = this;
    9847 
    9848         // check if property name starts with "*"
    9849         parser.addListener("property", function(event) {
    9850             var property = event.property;
    9851 
    9852             if (property.hack === "*") {
    9853                 reporter.report("Property with star prefix found.", event.property.line, event.property.col, rule);
    9854             }
    9855         });
    9856     }
    9857 });
    9858 
    9859 /*
    9860  * Rule: Don't use text-indent for image replacement if you need to support rtl.
    9861  *
    9862  */
    9863 
    9864 CSSLint.addRule({
    9865 
    9866     // rule information
    9867     id: "text-indent",
    9868     name: "Disallow negative text-indent",
    9869     desc: "Checks for text indent less than -99px",
    9870     url: "https://github.com/CSSLint/csslint/wiki/Disallow-negative-text-indent",
    9871     browsers: "All",
    9872 
    9873     // initialization
    9874     init: function(parser, reporter) {
    9875         "use strict";
    9876         var rule = this,
    9877             textIndent,
    9878             direction;
    9879 
    9880 
    9881         function startRule() {
    9882             textIndent = false;
    9883             direction = "inherit";
    9884         }
    9885 
    9886         // event handler for end of rules
    9887         function endRule() {
    9888             if (textIndent && direction !== "ltr") {
    9889                 reporter.report("Negative text-indent doesn't work well with RTL. If you use text-indent for image replacement explicitly set direction for that item to ltr.", textIndent.line, textIndent.col, rule);
    9890             }
    9891         }
    9892 
    9893         parser.addListener("startrule", startRule);
    9894         parser.addListener("startfontface", startRule);
    9895 
    9896         // check for use of "font-size"
    9897         parser.addListener("property", function(event) {
    9898             var name = event.property.toString().toLowerCase(),
    9899                 value = event.value;
    9900 
    9901             if (name === "text-indent" && value.parts[0].value < -99) {
    9902                 textIndent = event.property;
    9903             } else if (name === "direction" && value.toString() === "ltr") {
    9904                 direction = "ltr";
    9905             }
    9906         });
    9907 
    9908         parser.addListener("endrule", endRule);
    9909         parser.addListener("endfontface", endRule);
    9910 
    9911     }
    9912 
    9913 });
    9914 
    9915 /*
    9916  * Rule: Don't use properties with a underscore prefix.
    9917  *
    9918  */
    9919 
    9920 CSSLint.addRule({
    9921 
    9922     // rule information
    9923     id: "underscore-property-hack",
    9924     name: "Disallow properties with an underscore prefix",
    9925     desc: "Checks for the underscore property hack (targets IE6)",
    9926     url: "https://github.com/CSSLint/csslint/wiki/Disallow-underscore-hack",
    9927     browsers: "All",
    9928 
    9929     // initialization
    9930     init: function(parser, reporter) {
    9931         "use strict";
    9932         var rule = this;
    9933 
    9934         // check if property name starts with "_"
    9935         parser.addListener("property", function(event) {
    9936             var property = event.property;
    9937 
    9938             if (property.hack === "_") {
    9939                 reporter.report("Property with underscore prefix found.", event.property.line, event.property.col, rule);
    9940             }
    9941         });
    9942     }
    9943 });
    9944 
    9945 /*
    9946  * Rule: Headings (h1-h6) should be defined only once.
    9947  */
    9948 
    9949 CSSLint.addRule({
    9950 
    9951     // rule information
    9952     id: "unique-headings",
    9953     name: "Headings should only be defined once",
    9954     desc: "Headings should be defined only once.",
    9955     url: "https://github.com/CSSLint/csslint/wiki/Headings-should-only-be-defined-once",
    9956     browsers: "All",
    9957 
    9958     // initialization
    9959     init: function(parser, reporter) {
    9960         "use strict";
    9961         var rule = this;
    9962 
    9963         var headings = {
    9964             h1: 0,
    9965             h2: 0,
    9966             h3: 0,
    9967             h4: 0,
    9968             h5: 0,
    9969             h6: 0
    9970         };
    9971 
    9972         parser.addListener("startrule", function(event) {
    9973             var selectors = event.selectors,
    9974                 selector,
    9975                 part,
    9976                 pseudo,
    9977                 i, j;
    9978 
    9979             for (i=0; i < selectors.length; i++) {
    9980                 selector = selectors[i];
    9981                 part = selector.parts[selector.parts.length-1];
    9982 
    9983                 if (part.elementName && /(h[1-6])/i.test(part.elementName.toString())) {
    9984 
    9985                     for (j=0; j < part.modifiers.length; j++) {
    9986                         if (part.modifiers[j].type === "pseudo") {
    9987                             pseudo = true;
    9988                             break;
    9989                         }
    9990                     }
    9991 
    9992                     if (!pseudo) {
    9993                         headings[RegExp.$1]++;
    9994                         if (headings[RegExp.$1] > 1) {
    9995                             reporter.report("Heading (" + part.elementName + ") has already been defined.", part.line, part.col, rule);
    9996                         }
    9997                     }
    9998                 }
    9999             }
    10000         });
    10001 
    10002         parser.addListener("endstylesheet", function() {
    10003             var prop,
    10004                 messages = [];
    10005 
    10006             for (prop in headings) {
    10007                 if (headings.hasOwnProperty(prop)) {
    10008                     if (headings[prop] > 1) {
    10009                         messages.push(headings[prop] + " " + prop + "s");
    10010                     }
    10011                 }
    10012             }
    10013 
    10014             if (messages.length) {
    10015                 reporter.rollupWarn("You have " + messages.join(", ") + " defined in this stylesheet.", rule);
    10016             }
    10017         });
    10018     }
    10019 
    10020 });
    10021 
    10022 /*
    10023  * Rule: Don't use universal selector because it's slow.
    10024  */
    10025 
    10026 CSSLint.addRule({
    10027 
    10028     // rule information
    10029     id: "universal-selector",
    10030     name: "Disallow universal selector",
    10031     desc: "The universal selector (*) is known to be slow.",
    10032     url: "https://github.com/CSSLint/csslint/wiki/Disallow-universal-selector",
    10033     browsers: "All",
    10034 
    10035     // initialization
    10036     init: function(parser, reporter) {
    10037         "use strict";
    10038         var rule = this;
    10039 
    10040         parser.addListener("startrule", function(event) {
    10041             var selectors = event.selectors,
    10042                 selector,
    10043                 part,
    10044                 i;
    10045 
    10046             for (i=0; i < selectors.length; i++) {
    10047                 selector = selectors[i];
    10048 
    10049                 part = selector.parts[selector.parts.length-1];
    10050                 if (part.elementName === "*") {
    10051                     reporter.report(rule.desc, part.line, part.col, rule);
    10052                 }
    10053             }
    10054         });
    10055     }
    10056 
    10057 });
    10058 
    10059 /*
    10060  * Rule: Don't use unqualified attribute selectors because they're just like universal selectors.
    10061  */
    10062 
    10063 CSSLint.addRule({
    10064 
    10065     // rule information
    10066     id: "unqualified-attributes",
    10067     name: "Disallow unqualified attribute selectors",
    10068     desc: "Unqualified attribute selectors are known to be slow.",
    10069     url: "https://github.com/CSSLint/csslint/wiki/Disallow-unqualified-attribute-selectors",
    10070     browsers: "All",
    10071 
    10072     // initialization
    10073     init: function(parser, reporter) {
    10074         "use strict";
    10075 
    10076         var rule = this;
    10077 
    10078         parser.addListener("startrule", function(event) {
    10079 
    10080             var selectors = event.selectors,
    10081                 selectorContainsClassOrId = false,
    10082                 selector,
    10083                 part,
    10084                 modifier,
    10085                 i, k;
    10086 
    10087             for (i=0; i < selectors.length; i++) {
    10088                 selector = selectors[i];
    10089 
    10090                 part = selector.parts[selector.parts.length-1];
    10091                 if (part.type === parser.SELECTOR_PART_TYPE) {
    10092                     for (k=0; k < part.modifiers.length; k++) {
    10093                         modifier = part.modifiers[k];
    10094 
    10095                         if (modifier.type === "class" || modifier.type === "id") {
    10096                             selectorContainsClassOrId = true;
    10097                             break;
    10098                         }
    10099                     }
    10100 
    10101                     if (!selectorContainsClassOrId) {
    10102                         for (k=0; k < part.modifiers.length; k++) {
    10103                             modifier = part.modifiers[k];
    10104                             if (modifier.type === "attribute" && (!part.elementName || part.elementName === "*")) {
    10105                                 reporter.report(rule.desc, part.line, part.col, rule);
    10106                             }
    10107                         }
    10108                     }
    10109                 }
    10110 
    10111             }
    10112         });
    10113     }
    10114 
    10115 });
    10116 
    10117 /*
    10118  * Rule: When using a vendor-prefixed property, make sure to
    10119  * include the standard one.
    10120  */
    10121 
    10122 CSSLint.addRule({
    10123 
    10124     // rule information
    10125     id: "vendor-prefix",
    10126     name: "Require standard property with vendor prefix",
    10127     desc: "When using a vendor-prefixed property, make sure to include the standard one.",
    10128     url: "https://github.com/CSSLint/csslint/wiki/Require-standard-property-with-vendor-prefix",
    10129     browsers: "All",
    10130 
    10131     // initialization
    10132     init: function(parser, reporter) {
    10133         "use strict";
    10134         var rule = this,
    10135             properties,
    10136             num,
    10137             propertiesToCheck = {
    10138                 "-webkit-border-radius": "border-radius",
    10139                 "-webkit-border-top-left-radius": "border-top-left-radius",
    10140                 "-webkit-border-top-right-radius": "border-top-right-radius",
    10141                 "-webkit-border-bottom-left-radius": "border-bottom-left-radius",
    10142                 "-webkit-border-bottom-right-radius": "border-bottom-right-radius",
    10143 
    10144                 "-o-border-radius": "border-radius",
    10145                 "-o-border-top-left-radius": "border-top-left-radius",
    10146                 "-o-border-top-right-radius": "border-top-right-radius",
    10147                 "-o-border-bottom-left-radius": "border-bottom-left-radius",
    10148                 "-o-border-bottom-right-radius": "border-bottom-right-radius",
    10149 
    10150                 "-moz-border-radius": "border-radius",
    10151                 "-moz-border-radius-topleft": "border-top-left-radius",
    10152                 "-moz-border-radius-topright": "border-top-right-radius",
    10153                 "-moz-border-radius-bottomleft": "border-bottom-left-radius",
    10154                 "-moz-border-radius-bottomright": "border-bottom-right-radius",
    10155 
    10156                 "-moz-column-count": "column-count",
    10157                 "-webkit-column-count": "column-count",
    10158 
    10159                 "-moz-column-gap": "column-gap",
    10160                 "-webkit-column-gap": "column-gap",
    10161 
    10162                 "-moz-column-rule": "column-rule",
    10163                 "-webkit-column-rule": "column-rule",
    10164 
    10165                 "-moz-column-rule-style": "column-rule-style",
    10166                 "-webkit-column-rule-style": "column-rule-style",
    10167 
    10168                 "-moz-column-rule-color": "column-rule-color",
    10169                 "-webkit-column-rule-color": "column-rule-color",
    10170 
    10171                 "-moz-column-rule-width": "column-rule-width",
    10172                 "-webkit-column-rule-width": "column-rule-width",
    10173 
    10174                 "-moz-column-width": "column-width",
    10175                 "-webkit-column-width": "column-width",
    10176 
    10177                 "-webkit-column-span": "column-span",
    10178                 "-webkit-columns": "columns",
    10179 
    10180                 "-moz-box-shadow": "box-shadow",
    10181                 "-webkit-box-shadow": "box-shadow",
    10182 
    10183                 "-moz-transform": "transform",
    10184                 "-webkit-transform": "transform",
    10185                 "-o-transform": "transform",
    10186                 "-ms-transform": "transform",
    10187 
    10188                 "-moz-transform-origin": "transform-origin",
    10189                 "-webkit-transform-origin": "transform-origin",
    10190                 "-o-transform-origin": "transform-origin",
    10191                 "-ms-transform-origin": "transform-origin",
    10192 
    10193                 "-moz-box-sizing": "box-sizing",
    10194                 "-webkit-box-sizing": "box-sizing"
    10195             };
    10196 
    10197         // event handler for beginning of rules
    10198         function startRule() {
    10199             properties = {};
    10200             num = 1;
    10201         }
    10202 
    10203         // event handler for end of rules
    10204         function endRule() {
    10205             var prop,
    10206                 i,
    10207                 len,
    10208                 needed,
    10209                 actual,
    10210                 needsStandard = [];
    10211 
    10212             for (prop in properties) {
    10213                 if (propertiesToCheck[prop]) {
    10214                     needsStandard.push({
    10215                         actual: prop,
    10216                         needed: propertiesToCheck[prop]
    10217                     });
    10218                 }
    10219             }
    10220 
    10221             for (i=0, len=needsStandard.length; i < len; i++) {
    10222                 needed = needsStandard[i].needed;
    10223                 actual = needsStandard[i].actual;
    10224 
    10225                 if (!properties[needed]) {
    10226                     reporter.report("Missing standard property '" + needed + "' to go along with '" + actual + "'.", properties[actual][0].name.line, properties[actual][0].name.col, rule);
    10227                 } else {
    10228                     // make sure standard property is last
    10229                     if (properties[needed][0].pos < properties[actual][0].pos) {
    10230                         reporter.report("Standard property '" + needed + "' should come after vendor-prefixed property '" + actual + "'.", properties[actual][0].name.line, properties[actual][0].name.col, rule);
    10231                     }
    10232                 }
    10233             }
    10234 
    10235         }
    10236 
    10237         parser.addListener("startrule", startRule);
    10238         parser.addListener("startfontface", startRule);
    10239         parser.addListener("startpage", startRule);
    10240         parser.addListener("startpagemargin", startRule);
    10241         parser.addListener("startkeyframerule", startRule);
    10242         parser.addListener("startviewport", startRule);
    10243 
    10244         parser.addListener("property", function(event) {
    10245             var name = event.property.text.toLowerCase();
    10246 
    10247             if (!properties[name]) {
    10248                 properties[name] = [];
    10249             }
    10250 
    10251             properties[name].push({
    10252                 name: event.property,
    10253                 value: event.value,
    10254                 pos: num++
    10255             });
    10256         });
    10257 
    10258         parser.addListener("endrule", endRule);
    10259         parser.addListener("endfontface", endRule);
    10260         parser.addListener("endpage", endRule);
    10261         parser.addListener("endpagemargin", endRule);
    10262         parser.addListener("endkeyframerule", endRule);
    10263         parser.addListener("endviewport", endRule);
    10264     }
    10265 
    10266 });
    10267 
    10268 /*
    10269  * Rule: You don't need to specify units when a value is 0.
    10270  */
    10271 
    10272 CSSLint.addRule({
    10273 
    10274     // rule information
    10275     id: "zero-units",
    10276     name: "Disallow units for 0 values",
    10277     desc: "You don't need to specify units when a value is 0.",
    10278     url: "https://github.com/CSSLint/csslint/wiki/Disallow-units-for-zero-values",
    10279     browsers: "All",
    10280 
    10281     // initialization
    10282     init: function(parser, reporter) {
    10283         "use strict";
    10284         var rule = this;
    10285 
    10286         // count how many times "float" is used
    10287         parser.addListener("property", function(event) {
    10288             var parts = event.value.parts,
    10289                 i = 0,
    10290                 len = parts.length;
    10291 
    10292             while (i < len) {
    10293                 if ((parts[i].units || parts[i].type === "percentage") && parts[i].value === 0 && parts[i].type !== "time") {
    10294                     reporter.report("Values of 0 shouldn't have units specified.", parts[i].line, parts[i].col, rule);
    10295                 }
    10296                 i++;
    10297             }
    10298 
    10299         });
    10300 
    10301     }
    10302 
    10303 });
    10304 
    10305 (function() {
    10306     "use strict";
    10307 
    10308     /**
    10309      * Replace special characters before write to output.
    10310      *
    10311      * Rules:
    10312      *  - single quotes is the escape sequence for double-quotes
    10313      *  - &amp; is the escape sequence for &
    10314      *  - &lt; is the escape sequence for <
    10315      *  - &gt; is the escape sequence for >
    10316      *
    10317      * @param {String} message to escape
    10318      * @return escaped message as {String}
    10319      */
    10320     var xmlEscape = function(str) {
    10321         if (!str || str.constructor !== String) {
    10322             return "";
    10323         }
    10324 
    10325         return str.replace(/["&><]/g, function(match) {
    10326             switch (match) {
    10327                 case "\"":
    10328                     return "&quot;";
    10329                 case "&":
    10330                     return "&amp;";
    10331                 case "<":
    10332                     return "&lt;";
    10333                 case ">":
    10334                     return "&gt;";
    10335             }
    10336         });
    10337     };
    10338 
    10339     CSSLint.addFormatter({
    10340         // format information
    10341         id: "checkstyle-xml",
    10342         name: "Checkstyle XML format",
    10343 
    10344         /**
    10345          * Return opening root XML tag.
    10346          * @return {String} to prepend before all results
    10347          */
    10348         startFormat: function() {
    10349             return "<?xml version=\"1.0\" encoding=\"utf-8\"?><checkstyle>";
    10350         },
    10351 
    10352         /**
    10353          * Return closing root XML tag.
    10354          * @return {String} to append after all results
    10355          */
    10356         endFormat: function() {
    10357             return "</checkstyle>";
    10358         },
    10359 
    10360         /**
    10361          * Returns message when there is a file read error.
    10362          * @param {String} filename The name of the file that caused the error.
    10363          * @param {String} message The error message
    10364          * @return {String} The error message.
    10365          */
    10366         readError: function(filename, message) {
    10367             return "<file name=\"" + xmlEscape(filename) + "\"><error line=\"0\" column=\"0\" severty=\"error\" message=\"" + xmlEscape(message) + "\"></error></file>";
    10368         },
    10369 
    10370         /**
    10371          * Given CSS Lint results for a file, return output for this format.
    10372          * @param results {Object} with error and warning messages
    10373          * @param filename {String} relative file path
    10374          * @param options {Object} (UNUSED for now) specifies special handling of output
    10375          * @return {String} output for results
    10376          */
    10377         formatResults: function(results, filename/*, options*/) {
    10378             var messages = results.messages,
    10379                 output = [];
    10380 
    10381             /**
    10382              * Generate a source string for a rule.
    10383              * Checkstyle source strings usually resemble Java class names e.g
    10384              * net.csslint.SomeRuleName
    10385              * @param {Object} rule
    10386              * @return rule source as {String}
    10387              */
    10388             var generateSource = function(rule) {
    10389                 if (!rule || !("name" in rule)) {
    10390                     return "";
    10391                 }
    10392                 return "net.csslint." + rule.name.replace(/\s/g, "");
    10393             };
    10394 
    10395 
    10396             if (messages.length > 0) {
    10397                 output.push("<file name=\""+filename+"\">");
    10398                 CSSLint.Util.forEach(messages, function (message) {
    10399                     // ignore rollups for now
    10400                     if (!message.rollup) {
    10401                         output.push("<error line=\"" + message.line + "\" column=\"" + message.col + "\" severity=\"" + message.type + "\"" +
    10402                           " message=\"" + xmlEscape(message.message) + "\" source=\"" + generateSource(message.rule) +"\"/>");
    10403                     }
    10404                 });
    10405                 output.push("</file>");
    10406             }
    10407 
    10408             return output.join("");
    10409         }
    10410     });
    10411 
    10412 }());
    10413 
    10414 CSSLint.addFormatter({
    10415     // format information
    10416     id: "compact",
    10417     name: "Compact, 'porcelain' format",
    10418 
    10419     /**
    10420      * Return content to be printed before all file results.
    10421      * @return {String} to prepend before all results
    10422      */
    10423     startFormat: function() {
    10424         "use strict";
    10425         return "";
    10426     },
    10427 
    10428     /**
    10429      * Return content to be printed after all file results.
    10430      * @return {String} to append after all results
    10431      */
    10432     endFormat: function() {
    10433         "use strict";
    10434         return "";
    10435     },
    10436 
    10437     /**
    10438      * Given CSS Lint results for a file, return output for this format.
    10439      * @param results {Object} with error and warning messages
    10440      * @param filename {String} relative file path
    10441      * @param options {Object} (Optional) specifies special handling of output
    10442      * @return {String} output for results
    10443      */
    10444     formatResults: function(results, filename, options) {
    10445         "use strict";
    10446         var messages = results.messages,
    10447             output = "";
    10448         options = options || {};
    10449 
    10450         /**
    10451          * Capitalize and return given string.
    10452          * @param str {String} to capitalize
    10453          * @return {String} capitalized
    10454          */
    10455         var capitalize = function(str) {
    10456             return str.charAt(0).toUpperCase() + str.slice(1);
    10457         };
    10458 
    10459         if (messages.length === 0) {
    10460             return options.quiet ? "" : filename + ": Lint Free!";
    10461         }
    10462 
    10463         CSSLint.Util.forEach(messages, function(message) {
    10464             if (message.rollup) {
    10465                 output += filename + ": " + capitalize(message.type) + " - " + message.message + " (" + message.rule.id + ")\n";
    10466             } else {
    10467                 output += filename + ": line " + message.line +
    10468                     ", col " + message.col + ", " + capitalize(message.type) + " - " + message.message + " (" + message.rule.id + ")\n";
    10469             }
    10470         });
    10471 
    10472         return output;
    10473     }
    10474 });
    10475 
    10476 CSSLint.addFormatter({
    10477     // format information
    10478     id: "csslint-xml",
    10479     name: "CSSLint XML format",
    10480 
    10481     /**
    10482      * Return opening root XML tag.
    10483      * @return {String} to prepend before all results
    10484      */
    10485     startFormat: function() {
    10486         "use strict";
    10487         return "<?xml version=\"1.0\" encoding=\"utf-8\"?><csslint>";
    10488     },
    10489 
    10490     /**
    10491      * Return closing root XML tag.
    10492      * @return {String} to append after all results
    10493      */
    10494     endFormat: function() {
    10495         "use strict";
    10496         return "</csslint>";
    10497     },
    10498 
    10499     /**
    10500      * Given CSS Lint results for a file, return output for this format.
    10501      * @param results {Object} with error and warning messages
    10502      * @param filename {String} relative file path
    10503      * @param options {Object} (UNUSED for now) specifies special handling of output
    10504      * @return {String} output for results
    10505      */
    10506     formatResults: function(results, filename/*, options*/) {
    10507         "use strict";
    10508         var messages = results.messages,
    10509             output = [];
    10510 
    10511         /**
    10512          * Replace special characters before write to output.
    10513          *
    10514          * Rules:
    10515          *  - single quotes is the escape sequence for double-quotes
    10516          *  - &amp; is the escape sequence for &
    10517          *  - &lt; is the escape sequence for <
    10518          *  - &gt; is the escape sequence for >
    10519          *
    10520          * @param {String} message to escape
    10521          * @return escaped message as {String}
    10522          */
    10523         var escapeSpecialCharacters = function(str) {
    10524             if (!str || str.constructor !== String) {
    10525                 return "";
    10526             }
    10527             return str.replace(/"/g, "'").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
    10528         };
    10529 
    10530         if (messages.length > 0) {
    10531             output.push("<file name=\""+filename+"\">");
    10532             CSSLint.Util.forEach(messages, function (message) {
    10533                 if (message.rollup) {
    10534                     output.push("<issue severity=\"" + message.type + "\" reason=\"" + escapeSpecialCharacters(message.message) + "\" evidence=\"" + escapeSpecialCharacters(message.evidence) + "\"/>");
    10535                 } else {
    10536                     output.push("<issue line=\"" + message.line + "\" char=\"" + message.col + "\" severity=\"" + message.type + "\"" +
    10537                         " reason=\"" + escapeSpecialCharacters(message.message) + "\" evidence=\"" + escapeSpecialCharacters(message.evidence) + "\"/>");
    10538                 }
    10539             });
    10540             output.push("</file>");
    10541         }
    10542 
    10543         return output.join("");
    10544     }
    10545 });
    10546 
    10547 /* globals JSON: true */
    10548 
    10549 CSSLint.addFormatter({
    10550     // format information
    10551     id: "json",
    10552     name: "JSON",
    10553 
    10554     /**
    10555      * Return content to be printed before all file results.
    10556      * @return {String} to prepend before all results
    10557      */
    10558     startFormat: function() {
    10559         "use strict";
    10560         this.json = [];
    10561         return "";
    10562     },
    10563 
    10564     /**
    10565      * Return content to be printed after all file results.
    10566      * @return {String} to append after all results
    10567      */
    10568     endFormat: function() {
    10569         "use strict";
    10570         var ret = "";
    10571         if (this.json.length > 0) {
    10572             if (this.json.length === 1) {
    10573                 ret = JSON.stringify(this.json[0]);
    10574             } else {
    10575                 ret = JSON.stringify(this.json);
    10576             }
    10577         }
    10578         return ret;
    10579     },
    10580 
    10581     /**
    10582      * Given CSS Lint results for a file, return output for this format.
    10583      * @param results {Object} with error and warning messages
    10584      * @param filename {String} relative file path (Unused)
    10585      * @return {String} output for results
    10586      */
    10587     formatResults: function(results, filename, options) {
    10588         "use strict";
    10589         if (results.messages.length > 0 || !options.quiet) {
    10590             this.json.push({
    10591                 filename: filename,
    10592                 messages: results.messages,
    10593                 stats: results.stats
    10594             });
    10595         }
    10596         return "";
    10597     }
    10598 });
    10599 
    10600 CSSLint.addFormatter({
    10601     // format information
    10602     id: "junit-xml",
    10603     name: "JUNIT XML format",
    10604 
    10605     /**
    10606      * Return opening root XML tag.
    10607      * @return {String} to prepend before all results
    10608      */
    10609     startFormat: function() {
    10610         "use strict";
    10611         return "<?xml version=\"1.0\" encoding=\"utf-8\"?><testsuites>";
    10612     },
    10613 
    10614     /**
    10615      * Return closing root XML tag.
    10616      * @return {String} to append after all results
    10617      */
    10618     endFormat: function() {
    10619         "use strict";
    10620         return "</testsuites>";
    10621     },
    10622 
    10623     /**
    10624      * Given CSS Lint results for a file, return output for this format.
    10625      * @param results {Object} with error and warning messages
    10626      * @param filename {String} relative file path
    10627      * @param options {Object} (UNUSED for now) specifies special handling of output
    10628      * @return {String} output for results
    10629      */
    10630     formatResults: function(results, filename/*, options*/) {
    10631         "use strict";
    10632 
    10633         var messages = results.messages,
    10634             output = [],
    10635             tests = {
    10636                 "error": 0,
    10637                 "failure": 0
    10638             };
    10639 
    10640         /**
    10641          * Generate a source string for a rule.
    10642          * JUNIT source strings usually resemble Java class names e.g
    10643          * net.csslint.SomeRuleName
    10644          * @param {Object} rule
    10645          * @return rule source as {String}
    10646          */
    10647         var generateSource = function(rule) {
    10648             if (!rule || !("name" in rule)) {
    10649                 return "";
    10650             }
    10651             return "net.csslint." + rule.name.replace(/\s/g, "");
    10652         };
    10653 
    10654         /**
    10655          * Replace special characters before write to output.
    10656          *
    10657          * Rules:
    10658          *  - single quotes is the escape sequence for double-quotes
    10659          *  - &lt; is the escape sequence for <
    10660          *  - &gt; is the escape sequence for >
    10661          *
    10662          * @param {String} message to escape
    10663          * @return escaped message as {String}
    10664          */
    10665         var escapeSpecialCharacters = function(str) {
    10666 
    10667             if (!str || str.constructor !== String) {
    10668                 return "";
    10669             }
    10670 
    10671             return str.replace(/"/g, "'").replace(/</g, "&lt;").replace(/>/g, "&gt;");
    10672 
    10673         };
    10674 
    10675         if (messages.length > 0) {
    10676 
    10677             messages.forEach(function (message) {
    10678 
    10679                 // since junit has no warning class
    10680                 // all issues as errors
    10681                 var type = message.type === "warning" ? "error" : message.type;
    10682 
    10683                 // ignore rollups for now
    10684                 if (!message.rollup) {
    10685 
    10686                     // build the test case separately, once joined
    10687                     // we'll add it to a custom array filtered by type
    10688                     output.push("<testcase time=\"0\" name=\"" + generateSource(message.rule) + "\">");
    10689                     output.push("<" + type + " message=\"" + escapeSpecialCharacters(message.message) + "\"><![CDATA[" + message.line + ":" + message.col + ":" + escapeSpecialCharacters(message.evidence) + "]]></" + type + ">");
    10690                     output.push("</testcase>");
    10691 
    10692                     tests[type] += 1;
    10693 
    10694                 }
    10695 
    10696             });
    10697 
    10698             output.unshift("<testsuite time=\"0\" tests=\"" + messages.length + "\" skipped=\"0\" errors=\"" + tests.error + "\" failures=\"" + tests.failure + "\" package=\"net.csslint\" name=\"" + filename + "\">");
    10699             output.push("</testsuite>");
    10700 
    10701         }
    10702 
    10703         return output.join("");
    10704 
    10705     }
    10706 });
    10707 
    10708 CSSLint.addFormatter({
    10709     // format information
    10710     id: "lint-xml",
    10711     name: "Lint XML format",
    10712 
    10713     /**
    10714      * Return opening root XML tag.
    10715      * @return {String} to prepend before all results
    10716      */
    10717     startFormat: function() {
    10718         "use strict";
    10719         return "<?xml version=\"1.0\" encoding=\"utf-8\"?><lint>";
    10720     },
    10721 
    10722     /**
    10723      * Return closing root XML tag.
    10724      * @return {String} to append after all results
    10725      */
    10726     endFormat: function() {
    10727         "use strict";
    10728         return "</lint>";
    10729     },
    10730 
    10731     /**
    10732      * Given CSS Lint results for a file, return output for this format.
    10733      * @param results {Object} with error and warning messages
    10734      * @param filename {String} relative file path
    10735      * @param options {Object} (UNUSED for now) specifies special handling of output
    10736      * @return {String} output for results
    10737      */
    10738     formatResults: function(results, filename/*, options*/) {
    10739         "use strict";
    10740         var messages = results.messages,
    10741             output = [];
    10742 
    10743         /**
    10744          * Replace special characters before write to output.
    10745          *
    10746          * Rules:
    10747          *  - single quotes is the escape sequence for double-quotes
    10748          *  - &amp; is the escape sequence for &
    10749          *  - &lt; is the escape sequence for <
    10750          *  - &gt; is the escape sequence for >
    10751          *
    10752          * @param {String} message to escape
    10753          * @return escaped message as {String}
    10754          */
    10755         var escapeSpecialCharacters = function(str) {
    10756             if (!str || str.constructor !== String) {
    10757                 return "";
    10758             }
    10759             return str.replace(/"/g, "'").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
    10760         };
    10761 
    10762         if (messages.length > 0) {
    10763 
    10764             output.push("<file name=\""+filename+"\">");
    10765             CSSLint.Util.forEach(messages, function (message) {
    10766                 if (message.rollup) {
    10767                     output.push("<issue severity=\"" + message.type + "\" reason=\"" + escapeSpecialCharacters(message.message) + "\" evidence=\"" + escapeSpecialCharacters(message.evidence) + "\"/>");
    10768                 } else {
    10769                     var rule = "";
    10770                     if (message.rule && message.rule.id) {
    10771                         rule = "rule=\"" + escapeSpecialCharacters(message.rule.id) + "\" ";
    10772                     }
    10773                     output.push("<issue " + rule + "line=\"" + message.line + "\" char=\"" + message.col + "\" severity=\"" + message.type + "\"" +
    10774                         " reason=\"" + escapeSpecialCharacters(message.message) + "\" evidence=\"" + escapeSpecialCharacters(message.evidence) + "\"/>");
    10775                 }
    10776             });
    10777             output.push("</file>");
    10778         }
    10779 
    10780         return output.join("");
    10781     }
    10782 });
    10783 
    10784 CSSLint.addFormatter({
    10785     // format information
    10786     id: "text",
    10787     name: "Plain Text",
    10788 
    10789     /**
    10790      * Return content to be printed before all file results.
    10791      * @return {String} to prepend before all results
    10792      */
    10793     startFormat: function() {
    10794         "use strict";
    10795         return "";
    10796     },
    10797 
    10798     /**
    10799      * Return content to be printed after all file results.
    10800      * @return {String} to append after all results
    10801      */
    10802     endFormat: function() {
    10803         "use strict";
    10804         return "";
    10805     },
    10806 
    10807     /**
    10808      * Given CSS Lint results for a file, return output for this format.
    10809      * @param results {Object} with error and warning messages
    10810      * @param filename {String} relative file path
    10811      * @param options {Object} (Optional) specifies special handling of output
    10812      * @return {String} output for results
    10813      */
    10814     formatResults: function(results, filename, options) {
    10815         "use strict";
    10816         var messages = results.messages,
    10817             output = "";
    10818         options = options || {};
    10819 
    10820         if (messages.length === 0) {
    10821             return options.quiet ? "" : "\n\ncsslint: No errors in " + filename + ".";
    10822         }
    10823 
    10824         output = "\n\ncsslint: There ";
    10825         if (messages.length === 1) {
    10826             output += "is 1 problem";
    10827         } else {
    10828             output += "are " + messages.length + " problems";
    10829         }
    10830         output += " in " + filename + ".";
    10831 
    10832         var pos = filename.lastIndexOf("/"),
    10833             shortFilename = filename;
    10834 
    10835         if (pos === -1) {
    10836             pos = filename.lastIndexOf("\\");
    10837         }
    10838         if (pos > -1) {
    10839             shortFilename = filename.substring(pos+1);
    10840         }
    10841 
    10842         CSSLint.Util.forEach(messages, function (message, i) {
    10843             output = output + "\n\n" + shortFilename;
    10844             if (message.rollup) {
    10845                 output += "\n" + (i+1) + ": " + message.type;
    10846                 output += "\n" + message.message;
    10847             } else {
    10848                 output += "\n" + (i+1) + ": " + message.type + " at line " + message.line + ", col " + message.col;
    10849                 output += "\n" + message.message;
    10850                 output += "\n" + message.evidence;
    10851             }
    10852         });
    10853 
    10854         return output;
    10855     }
    10856 });
    10857 
    10858 return CSSLint;
     7327
     7328},{"./css":22,"./util":28}]},{},[]);
     7329
     7330return require('parserlib');
     7331})();
     7332var clone = (function() {
     7333'use strict';
     7334
     7335var nativeMap;
     7336try {
     7337  nativeMap = Map;
     7338} catch(_) {
     7339  // maybe a reference error because no `Map`. Give it a dummy value that no
     7340  // value will ever be an instanceof.
     7341  nativeMap = function() {};
     7342}
     7343
     7344var nativeSet;
     7345try {
     7346  nativeSet = Set;
     7347} catch(_) {
     7348  nativeSet = function() {};
     7349}
     7350
     7351var nativePromise;
     7352try {
     7353  nativePromise = Promise;
     7354} catch(_) {
     7355  nativePromise = function() {};
     7356}
     7357
     7358/**
     7359 * Clones (copies) an Object using deep copying.
     7360 *
     7361 * This function supports circular references by default, but if you are certain
     7362 * there are no circular references in your object, you can save some CPU time
     7363 * by calling clone(obj, false).
     7364 *
     7365 * Caution: if `circular` is false and `parent` contains circular references,
     7366 * your program may enter an infinite loop and crash.
     7367 *
     7368 * @param `parent` - the object to be cloned
     7369 * @param `circular` - set to true if the object to be cloned may contain
     7370 *    circular references. (optional - true by default)
     7371 * @param `depth` - set to a number if the object is only to be cloned to
     7372 *    a particular depth. (optional - defaults to Infinity)
     7373 * @param `prototype` - sets the prototype to be used when cloning an object.
     7374 *    (optional - defaults to parent prototype).
     7375 * @param `includeNonEnumerable` - set to true if the non-enumerable properties
     7376 *    should be cloned as well. Non-enumerable properties on the prototype
     7377 *    chain will be ignored. (optional - false by default)
     7378*/
     7379function clone(parent, circular, depth, prototype, includeNonEnumerable) {
     7380  if (typeof circular === 'object') {
     7381    depth = circular.depth;
     7382    prototype = circular.prototype;
     7383    includeNonEnumerable = circular.includeNonEnumerable;
     7384    circular = circular.circular;
     7385  }
     7386  // maintain two arrays for circular references, where corresponding parents
     7387  // and children have the same index
     7388  var allParents = [];
     7389  var allChildren = [];
     7390
     7391  var useBuffer = typeof Buffer != 'undefined';
     7392
     7393  if (typeof circular == 'undefined')
     7394    circular = true;
     7395
     7396  if (typeof depth == 'undefined')
     7397    depth = Infinity;
     7398
     7399  // recurse this function so we don't reset allParents and allChildren
     7400  function _clone(parent, depth) {
     7401    // cloning null always returns null
     7402    if (parent === null)
     7403      return null;
     7404
     7405    if (depth === 0)
     7406      return parent;
     7407
     7408    var child;
     7409    var proto;
     7410    if (typeof parent != 'object') {
     7411      return parent;
     7412    }
     7413
     7414    if (parent instanceof nativeMap) {
     7415      child = new nativeMap();
     7416    } else if (parent instanceof nativeSet) {
     7417      child = new nativeSet();
     7418    } else if (parent instanceof nativePromise) {
     7419      child = new nativePromise(function (resolve, reject) {
     7420        parent.then(function(value) {
     7421          resolve(_clone(value, depth - 1));
     7422        }, function(err) {
     7423          reject(_clone(err, depth - 1));
     7424        });
     7425      });
     7426    } else if (clone.__isArray(parent)) {
     7427      child = [];
     7428    } else if (clone.__isRegExp(parent)) {
     7429      child = new RegExp(parent.source, __getRegExpFlags(parent));
     7430      if (parent.lastIndex) child.lastIndex = parent.lastIndex;
     7431    } else if (clone.__isDate(parent)) {
     7432      child = new Date(parent.getTime());
     7433    } else if (useBuffer && Buffer.isBuffer(parent)) {
     7434      child = new Buffer(parent.length);
     7435      parent.copy(child);
     7436      return child;
     7437    } else if (parent instanceof Error) {
     7438      child = Object.create(parent);
     7439    } else {
     7440      if (typeof prototype == 'undefined') {
     7441        proto = Object.getPrototypeOf(parent);
     7442        child = Object.create(proto);
     7443      }
     7444      else {
     7445        child = Object.create(prototype);
     7446        proto = prototype;
     7447      }
     7448    }
     7449
     7450    if (circular) {
     7451      var index = allParents.indexOf(parent);
     7452
     7453      if (index != -1) {
     7454        return allChildren[index];
     7455      }
     7456      allParents.push(parent);
     7457      allChildren.push(child);
     7458    }
     7459
     7460    if (parent instanceof nativeMap) {
     7461      var keyIterator = parent.keys();
     7462      while(true) {
     7463        var next = keyIterator.next();
     7464        if (next.done) {
     7465          break;
     7466        }
     7467        var keyChild = _clone(next.value, depth - 1);
     7468        var valueChild = _clone(parent.get(next.value), depth - 1);
     7469        child.set(keyChild, valueChild);
     7470      }
     7471    }
     7472    if (parent instanceof nativeSet) {
     7473      var iterator = parent.keys();
     7474      while(true) {
     7475        var next = iterator.next();
     7476        if (next.done) {
     7477          break;
     7478        }
     7479        var entryChild = _clone(next.value, depth - 1);
     7480        child.add(entryChild);
     7481      }
     7482    }
     7483
     7484    for (var i in parent) {
     7485      var attrs;
     7486      if (proto) {
     7487        attrs = Object.getOwnPropertyDescriptor(proto, i);
     7488      }
     7489
     7490      if (attrs && attrs.set == null) {
     7491        continue;
     7492      }
     7493      child[i] = _clone(parent[i], depth - 1);
     7494    }
     7495
     7496    if (Object.getOwnPropertySymbols) {
     7497      var symbols = Object.getOwnPropertySymbols(parent);
     7498      for (var i = 0; i < symbols.length; i++) {
     7499        // Don't need to worry about cloning a symbol because it is a primitive,
     7500        // like a number or string.
     7501        var symbol = symbols[i];
     7502        var descriptor = Object.getOwnPropertyDescriptor(parent, symbol);
     7503        if (descriptor && !descriptor.enumerable && !includeNonEnumerable) {
     7504          continue;
     7505        }
     7506        child[symbol] = _clone(parent[symbol], depth - 1);
     7507        if (!descriptor.enumerable) {
     7508          Object.defineProperty(child, symbol, {
     7509            enumerable: false
     7510          });
     7511        }
     7512      }
     7513    }
     7514
     7515    if (includeNonEnumerable) {
     7516      var allPropertyNames = Object.getOwnPropertyNames(parent);
     7517      for (var i = 0; i < allPropertyNames.length; i++) {
     7518        var propertyName = allPropertyNames[i];
     7519        var descriptor = Object.getOwnPropertyDescriptor(parent, propertyName);
     7520        if (descriptor && descriptor.enumerable) {
     7521          continue;
     7522        }
     7523        child[propertyName] = _clone(parent[propertyName], depth - 1);
     7524        Object.defineProperty(child, propertyName, {
     7525          enumerable: false
     7526        });
     7527      }
     7528    }
     7529
     7530    return child;
     7531  }
     7532
     7533  return _clone(parent, depth);
     7534}
     7535
     7536/**
     7537 * Simple flat clone using prototype, accepts only objects, usefull for property
     7538 * override on FLAT configuration object (no nested props).
     7539 *
     7540 * USE WITH CAUTION! This may not behave as you wish if you do not know how this
     7541 * works.
     7542 */
     7543clone.clonePrototype = function clonePrototype(parent) {
     7544  if (parent === null)
     7545    return null;
     7546
     7547  var c = function () {};
     7548  c.prototype = parent;
     7549  return new c();
     7550};
     7551
     7552// private utility functions
     7553
     7554function __objToStr(o) {
     7555  return Object.prototype.toString.call(o);
     7556}
     7557clone.__objToStr = __objToStr;
     7558
     7559function __isDate(o) {
     7560  return typeof o === 'object' && __objToStr(o) === '[object Date]';
     7561}
     7562clone.__isDate = __isDate;
     7563
     7564function __isArray(o) {
     7565  return typeof o === 'object' && __objToStr(o) === '[object Array]';
     7566}
     7567clone.__isArray = __isArray;
     7568
     7569function __isRegExp(o) {
     7570  return typeof o === 'object' && __objToStr(o) === '[object RegExp]';
     7571}
     7572clone.__isRegExp = __isRegExp;
     7573
     7574function __getRegExpFlags(re) {
     7575  var flags = '';
     7576  if (re.global) flags += 'g';
     7577  if (re.ignoreCase) flags += 'i';
     7578  if (re.multiline) flags += 'm';
     7579  return flags;
     7580}
     7581clone.__getRegExpFlags = __getRegExpFlags;
     7582
     7583return clone;
     7584})();
     7585
     7586if (typeof module === 'object' && module.exports) {
     7587  module.exports = clone;
     7588}
     7589
     7590/**
     7591 * Main CSSLint object.
     7592 * @class CSSLint
     7593 * @static
     7594 * @extends parserlib.util.EventTarget
     7595 */
     7596
     7597/* global parserlib, clone, Reporter */
     7598/* exported CSSLint */
     7599
     7600var CSSLint = (function() {
     7601    "use strict";
     7602
     7603    var rules           = [],
     7604        formatters      = [],
     7605        embeddedRuleset = /\/\*\s*csslint([^\*]*)\*\//,
     7606        api             = new parserlib.util.EventTarget();
     7607
     7608    api.version = "1.0.4";
     7609
     7610    //-------------------------------------------------------------------------
     7611    // Rule Management
     7612    //-------------------------------------------------------------------------
     7613
     7614    /**
     7615     * Adds a new rule to the engine.
     7616     * @param {Object} rule The rule to add.
     7617     * @method addRule
     7618     */
     7619    api.addRule = function(rule) {
     7620        rules.push(rule);
     7621        rules[rule.id] = rule;
     7622    };
     7623
     7624    /**
     7625     * Clears all rule from the engine.
     7626     * @method clearRules
     7627     */
     7628    api.clearRules = function() {
     7629        rules = [];
     7630    };
     7631
     7632    /**
     7633     * Returns the rule objects.
     7634     * @return An array of rule objects.
     7635     * @method getRules
     7636     */
     7637    api.getRules = function() {
     7638        return [].concat(rules).sort(function(a, b) {
     7639            return a.id > b.id ? 1 : 0;
     7640        });
     7641    };
     7642
     7643    /**
     7644     * Returns a ruleset configuration object with all current rules.
     7645     * @return A ruleset object.
     7646     * @method getRuleset
     7647     */
     7648    api.getRuleset = function() {
     7649        var ruleset = {},
     7650            i = 0,
     7651            len = rules.length;
     7652
     7653        while (i < len) {
     7654            ruleset[rules[i++].id] = 1;    // by default, everything is a warning
     7655        }
     7656
     7657        return ruleset;
     7658    };
     7659
     7660    /**
     7661     * Returns a ruleset object based on embedded rules.
     7662     * @param {String} text A string of css containing embedded rules.
     7663     * @param {Object} ruleset A ruleset object to modify.
     7664     * @return {Object} A ruleset object.
     7665     * @method getEmbeddedRuleset
     7666     */
     7667    function applyEmbeddedRuleset(text, ruleset) {
     7668        var valueMap,
     7669            embedded = text && text.match(embeddedRuleset),
     7670            rules = embedded && embedded[1];
     7671
     7672        if (rules) {
     7673            valueMap = {
     7674                "true": 2,  // true is error
     7675                "": 1,      // blank is warning
     7676                "false": 0, // false is ignore
     7677
     7678                "2": 2,     // explicit error
     7679                "1": 1,     // explicit warning
     7680                "0": 0      // explicit ignore
     7681            };
     7682
     7683            rules.toLowerCase().split(",").forEach(function(rule) {
     7684                var pair = rule.split(":"),
     7685                    property = pair[0] || "",
     7686                    value = pair[1] || "";
     7687
     7688                ruleset[property.trim()] = valueMap[value.trim()];
     7689            });
     7690        }
     7691
     7692        return ruleset;
     7693    }
     7694
     7695    //-------------------------------------------------------------------------
     7696    // Formatters
     7697    //-------------------------------------------------------------------------
     7698
     7699    /**
     7700     * Adds a new formatter to the engine.
     7701     * @param {Object} formatter The formatter to add.
     7702     * @method addFormatter
     7703     */
     7704    api.addFormatter = function(formatter) {
     7705        // formatters.push(formatter);
     7706        formatters[formatter.id] = formatter;
     7707    };
     7708
     7709    /**
     7710     * Retrieves a formatter for use.
     7711     * @param {String} formatId The name of the format to retrieve.
     7712     * @return {Object} The formatter or undefined.
     7713     * @method getFormatter
     7714     */
     7715    api.getFormatter = function(formatId) {
     7716        return formatters[formatId];
     7717    };
     7718
     7719    /**
     7720     * Formats the results in a particular format for a single file.
     7721     * @param {Object} result The results returned from CSSLint.verify().
     7722     * @param {String} filename The filename for which the results apply.
     7723     * @param {String} formatId The name of the formatter to use.
     7724     * @param {Object} options (Optional) for special output handling.
     7725     * @return {String} A formatted string for the results.
     7726     * @method format
     7727     */
     7728    api.format = function(results, filename, formatId, options) {
     7729        var formatter = this.getFormatter(formatId),
     7730            result = null;
     7731
     7732        if (formatter) {
     7733            result = formatter.startFormat();
     7734            result += formatter.formatResults(results, filename, options || {});
     7735            result += formatter.endFormat();
     7736        }
     7737
     7738        return result;
     7739    };
     7740
     7741    /**
     7742     * Indicates if the given format is supported.
     7743     * @param {String} formatId The ID of the format to check.
     7744     * @return {Boolean} True if the format exists, false if not.
     7745     * @method hasFormat
     7746     */
     7747    api.hasFormat = function(formatId) {
     7748        return formatters.hasOwnProperty(formatId);
     7749    };
     7750
     7751    //-------------------------------------------------------------------------
     7752    // Verification
     7753    //-------------------------------------------------------------------------
     7754
     7755    /**
     7756     * Starts the verification process for the given CSS text.
     7757     * @param {String} text The CSS text to verify.
     7758     * @param {Object} ruleset (Optional) List of rules to apply. If null, then
     7759     *      all rules are used. If a rule has a value of 1 then it's a warning,
     7760     *      a value of 2 means it's an error.
     7761     * @return {Object} Results of the verification.
     7762     * @method verify
     7763     */
     7764    api.verify = function(text, ruleset) {
     7765
     7766        var i = 0,
     7767            reporter,
     7768            lines,
     7769            allow = {},
     7770            ignore = [],
     7771            report,
     7772            parser = new parserlib.css.Parser({
     7773                starHack: true,
     7774                ieFilters: true,
     7775                underscoreHack: true,
     7776                strict: false
     7777            });
     7778
     7779        // normalize line endings
     7780        lines = text.replace(/\n\r?/g, "$split$").split("$split$");
     7781
     7782        // find 'allow' comments
     7783        CSSLint.Util.forEach(lines, function (line, lineno) {
     7784            var allowLine = line && line.match(/\/\*[ \t]*csslint[ \t]+allow:[ \t]*([^\*]*)\*\//i),
     7785                allowRules = allowLine && allowLine[1],
     7786                allowRuleset = {};
     7787
     7788            if (allowRules) {
     7789                allowRules.toLowerCase().split(",").forEach(function(allowRule) {
     7790                    allowRuleset[allowRule.trim()] = true;
     7791                });
     7792                if (Object.keys(allowRuleset).length > 0) {
     7793                    allow[lineno + 1] = allowRuleset;
     7794                }
     7795            }
     7796        });
     7797
     7798        var ignoreStart = null,
     7799            ignoreEnd = null;
     7800        CSSLint.Util.forEach(lines, function (line, lineno) {
     7801            // Keep oldest, "unclosest" ignore:start
     7802            if (ignoreStart === null && line.match(/\/\*[ \t]*csslint[ \t]+ignore:start[ \t]*\*\//i)) {
     7803                ignoreStart = lineno;
     7804            }
     7805
     7806            if (line.match(/\/\*[ \t]*csslint[ \t]+ignore:end[ \t]*\*\//i)) {
     7807                ignoreEnd = lineno;
     7808            }
     7809
     7810            if (ignoreStart !== null && ignoreEnd !== null) {
     7811                ignore.push([ignoreStart, ignoreEnd]);
     7812                ignoreStart = ignoreEnd = null;
     7813            }
     7814        });
     7815
     7816        // Close remaining ignore block, if any
     7817        if (ignoreStart !== null) {
     7818            ignore.push([ignoreStart, lines.length]);
     7819        }
     7820
     7821        if (!ruleset) {
     7822            ruleset = this.getRuleset();
     7823        }
     7824
     7825        if (embeddedRuleset.test(text)) {
     7826            // defensively copy so that caller's version does not get modified
     7827            ruleset = clone(ruleset);
     7828            ruleset = applyEmbeddedRuleset(text, ruleset);
     7829        }
     7830
     7831        reporter = new Reporter(lines, ruleset, allow, ignore);
     7832
     7833        ruleset.errors = 2;       // always report parsing errors as errors
     7834        for (i in ruleset) {
     7835            if (ruleset.hasOwnProperty(i) && ruleset[i]) {
     7836                if (rules[i]) {
     7837                    rules[i].init(parser, reporter);
     7838                }
     7839            }
     7840        }
     7841
     7842
     7843        // capture most horrible error type
     7844        try {
     7845            parser.parse(text);
     7846        } catch (ex) {
     7847            reporter.error("Fatal error, cannot continue: " + ex.message, ex.line, ex.col, {});
     7848        }
     7849
     7850        report = {
     7851            messages    : reporter.messages,
     7852            stats       : reporter.stats,
     7853            ruleset     : reporter.ruleset,
     7854            allow       : reporter.allow,
     7855            ignore      : reporter.ignore
     7856        };
     7857
     7858        // sort by line numbers, rollups at the bottom
     7859        report.messages.sort(function (a, b) {
     7860            if (a.rollup && !b.rollup) {
     7861                return 1;
     7862            } else if (!a.rollup && b.rollup) {
     7863                return -1;
     7864            } else {
     7865                return a.line - b.line;
     7866            }
     7867        });
     7868
     7869        return report;
     7870    };
     7871
     7872    //-------------------------------------------------------------------------
     7873    // Publish the API
     7874    //-------------------------------------------------------------------------
     7875
     7876    return api;
     7877
     7878})();
     7879
     7880/**
     7881 * An instance of Report is used to report results of the
     7882 * verification back to the main API.
     7883 * @class Reporter
     7884 * @constructor
     7885 * @param {String[]} lines The text lines of the source.
     7886 * @param {Object} ruleset The set of rules to work with, including if
     7887 *      they are errors or warnings.
     7888 * @param {Object} explicitly allowed lines
     7889 * @param {[][]} ingore list of line ranges to be ignored
     7890 */
     7891function Reporter(lines, ruleset, allow, ignore) {
     7892    "use strict";
     7893
     7894    /**
     7895     * List of messages being reported.
     7896     * @property messages
     7897     * @type String[]
     7898     */
     7899    this.messages = [];
     7900
     7901    /**
     7902     * List of statistics being reported.
     7903     * @property stats
     7904     * @type String[]
     7905     */
     7906    this.stats = [];
     7907
     7908    /**
     7909     * Lines of code being reported on. Used to provide contextual information
     7910     * for messages.
     7911     * @property lines
     7912     * @type String[]
     7913     */
     7914    this.lines = lines;
     7915
     7916    /**
     7917     * Information about the rules. Used to determine whether an issue is an
     7918     * error or warning.
     7919     * @property ruleset
     7920     * @type Object
     7921     */
     7922    this.ruleset = ruleset;
     7923
     7924    /**
     7925     * Lines with specific rule messages to leave out of the report.
     7926     * @property allow
     7927     * @type Object
     7928     */
     7929    this.allow = allow;
     7930    if (!this.allow) {
     7931        this.allow = {};
     7932    }
     7933
     7934    /**
     7935     * Linesets not to include in the report.
     7936     * @property ignore
     7937     * @type [][]
     7938     */
     7939    this.ignore = ignore;
     7940    if (!this.ignore) {
     7941        this.ignore = [];
     7942    }
     7943}
     7944
     7945Reporter.prototype = {
     7946
     7947    // restore constructor
     7948    constructor: Reporter,
     7949
     7950    /**
     7951     * Report an error.
     7952     * @param {String} message The message to store.
     7953     * @param {int} line The line number.
     7954     * @param {int} col The column number.
     7955     * @param {Object} rule The rule this message relates to.
     7956     * @method error
     7957     */
     7958    error: function(message, line, col, rule) {
     7959        "use strict";
     7960        this.messages.push({
     7961            type    : "error",
     7962            line    : line,
     7963            col     : col,
     7964            message : message,
     7965            evidence: this.lines[line-1],
     7966            rule    : rule || {}
     7967        });
     7968    },
     7969
     7970    /**
     7971     * Report an warning.
     7972     * @param {String} message The message to store.
     7973     * @param {int} line The line number.
     7974     * @param {int} col The column number.
     7975     * @param {Object} rule The rule this message relates to.
     7976     * @method warn
     7977     * @deprecated Use report instead.
     7978     */
     7979    warn: function(message, line, col, rule) {
     7980        "use strict";
     7981        this.report(message, line, col, rule);
     7982    },
     7983
     7984    /**
     7985     * Report an issue.
     7986     * @param {String} message The message to store.
     7987     * @param {int} line The line number.
     7988     * @param {int} col The column number.
     7989     * @param {Object} rule The rule this message relates to.
     7990     * @method report
     7991     */
     7992    report: function(message, line, col, rule) {
     7993        "use strict";
     7994
     7995        // Check if rule violation should be allowed
     7996        if (this.allow.hasOwnProperty(line) && this.allow[line].hasOwnProperty(rule.id)) {
     7997            return;
     7998        }
     7999
     8000        var ignore = false;
     8001        CSSLint.Util.forEach(this.ignore, function (range) {
     8002            if (range[0] <= line && line <= range[1]) {
     8003                ignore = true;
     8004            }
     8005        });
     8006        if (ignore) {
     8007            return;
     8008        }
     8009
     8010        this.messages.push({
     8011            type    : this.ruleset[rule.id] === 2 ? "error" : "warning",
     8012            line    : line,
     8013            col     : col,
     8014            message : message,
     8015            evidence: this.lines[line-1],
     8016            rule    : rule
     8017        });
     8018    },
     8019
     8020    /**
     8021     * Report some informational text.
     8022     * @param {String} message The message to store.
     8023     * @param {int} line The line number.
     8024     * @param {int} col The column number.
     8025     * @param {Object} rule The rule this message relates to.
     8026     * @method info
     8027     */
     8028    info: function(message, line, col, rule) {
     8029        "use strict";
     8030        this.messages.push({
     8031            type    : "info",
     8032            line    : line,
     8033            col     : col,
     8034            message : message,
     8035            evidence: this.lines[line-1],
     8036            rule    : rule
     8037        });
     8038    },
     8039
     8040    /**
     8041     * Report some rollup error information.
     8042     * @param {String} message The message to store.
     8043     * @param {Object} rule The rule this message relates to.
     8044     * @method rollupError
     8045     */
     8046    rollupError: function(message, rule) {
     8047        "use strict";
     8048        this.messages.push({
     8049            type    : "error",
     8050            rollup  : true,
     8051            message : message,
     8052            rule    : rule
     8053        });
     8054    },
     8055
     8056    /**
     8057     * Report some rollup warning information.
     8058     * @param {String} message The message to store.
     8059     * @param {Object} rule The rule this message relates to.
     8060     * @method rollupWarn
     8061     */
     8062    rollupWarn: function(message, rule) {
     8063        "use strict";
     8064        this.messages.push({
     8065            type    : "warning",
     8066            rollup  : true,
     8067            message : message,
     8068            rule    : rule
     8069        });
     8070    },
     8071
     8072    /**
     8073     * Report a statistic.
     8074     * @param {String} name The name of the stat to store.
     8075     * @param {Variant} value The value of the stat.
     8076     * @method stat
     8077     */
     8078    stat: function(name, value) {
     8079        "use strict";
     8080        this.stats[name] = value;
     8081    }
     8082};
     8083
     8084// expose for testing purposes
     8085CSSLint._Reporter = Reporter;
     8086
     8087/*
     8088 * Utility functions that make life easier.
     8089 */
     8090CSSLint.Util = {
     8091    /*
     8092     * Adds all properties from supplier onto receiver,
     8093     * overwriting if the same name already exists on
     8094     * receiver.
     8095     * @param {Object} The object to receive the properties.
     8096     * @param {Object} The object to provide the properties.
     8097     * @return {Object} The receiver
     8098     */
     8099    mix: function(receiver, supplier) {
     8100        "use strict";
     8101        var prop;
     8102
     8103        for (prop in supplier) {
     8104            if (supplier.hasOwnProperty(prop)) {
     8105                receiver[prop] = supplier[prop];
     8106            }
     8107        }
     8108
     8109        return prop;
     8110    },
     8111
     8112    /*
     8113     * Polyfill for array indexOf() method.
     8114     * @param {Array} values The array to search.
     8115     * @param {Variant} value The value to search for.
     8116     * @return {int} The index of the value if found, -1 if not.
     8117     */
     8118    indexOf: function(values, value) {
     8119        "use strict";
     8120        if (values.indexOf) {
     8121            return values.indexOf(value);
     8122        } else {
     8123            for (var i=0, len=values.length; i < len; i++) {
     8124                if (values[i] === value) {
     8125                    return i;
     8126                }
     8127            }
     8128            return -1;
     8129        }
     8130    },
     8131
     8132    /*
     8133     * Polyfill for array forEach() method.
     8134     * @param {Array} values The array to operate on.
     8135     * @param {Function} func The function to call on each item.
     8136     * @return {void}
     8137     */
     8138    forEach: function(values, func) {
     8139        "use strict";
     8140        if (values.forEach) {
     8141            return values.forEach(func);
     8142        } else {
     8143            for (var i=0, len=values.length; i < len; i++) {
     8144                func(values[i], i, values);
     8145            }
     8146        }
     8147    }
     8148};
     8149
     8150/*
     8151 * Rule: Don't use adjoining classes (.foo.bar).
     8152 */
     8153
     8154CSSLint.addRule({
     8155
     8156    // rule information
     8157    id: "adjoining-classes",
     8158    name: "Disallow adjoining classes",
     8159    desc: "Don't use adjoining classes.",
     8160    url: "https://github.com/CSSLint/csslint/wiki/Disallow-adjoining-classes",
     8161    browsers: "IE6",
     8162
     8163    // initialization
     8164    init: function(parser, reporter) {
     8165        "use strict";
     8166        var rule = this;
     8167        parser.addListener("startrule", function(event) {
     8168            var selectors = event.selectors,
     8169                selector,
     8170                part,
     8171                modifier,
     8172                classCount,
     8173                i, j, k;
     8174
     8175            for (i=0; i < selectors.length; i++) {
     8176                selector = selectors[i];
     8177                for (j=0; j < selector.parts.length; j++) {
     8178                    part = selector.parts[j];
     8179                    if (part.type === parser.SELECTOR_PART_TYPE) {
     8180                        classCount = 0;
     8181                        for (k=0; k < part.modifiers.length; k++) {
     8182                            modifier = part.modifiers[k];
     8183                            if (modifier.type === "class") {
     8184                                classCount++;
     8185                            }
     8186                            if (classCount > 1){
     8187                                reporter.report("Adjoining classes: "+selectors[i].text, part.line, part.col, rule);
     8188                            }
     8189                        }
     8190                    }
     8191                }
     8192            }
     8193        });
     8194    }
     8195
     8196});
     8197
     8198/*
     8199 * Rule: Don't use width or height when using padding or border.
     8200 */
     8201CSSLint.addRule({
     8202
     8203    // rule information
     8204    id: "box-model",
     8205    name: "Beware of broken box size",
     8206    desc: "Don't use width or height when using padding or border.",
     8207    url: "https://github.com/CSSLint/csslint/wiki/Beware-of-box-model-size",
     8208    browsers: "All",
     8209
     8210    // initialization
     8211    init: function(parser, reporter) {
     8212        "use strict";
     8213        var rule = this,
     8214            widthProperties = {
     8215                border: 1,
     8216                "border-left": 1,
     8217                "border-right": 1,
     8218                padding: 1,
     8219                "padding-left": 1,
     8220                "padding-right": 1
     8221            },
     8222            heightProperties = {
     8223                border: 1,
     8224                "border-bottom": 1,
     8225                "border-top": 1,
     8226                padding: 1,
     8227                "padding-bottom": 1,
     8228                "padding-top": 1
     8229            },
     8230            properties,
     8231            boxSizing = false;
     8232
     8233        function startRule() {
     8234            properties = {};
     8235            boxSizing = false;
     8236        }
     8237
     8238        function endRule() {
     8239            var prop, value;
     8240
     8241            if (!boxSizing) {
     8242                if (properties.height) {
     8243                    for (prop in heightProperties) {
     8244                        if (heightProperties.hasOwnProperty(prop) && properties[prop]) {
     8245                            value = properties[prop].value;
     8246                            // special case for padding
     8247                            if (!(prop === "padding" && value.parts.length === 2 && value.parts[0].value === 0)) {
     8248                                reporter.report("Using height with " + prop + " can sometimes make elements larger than you expect.", properties[prop].line, properties[prop].col, rule);
     8249                            }
     8250                        }
     8251                    }
     8252                }
     8253
     8254                if (properties.width) {
     8255                    for (prop in widthProperties) {
     8256                        if (widthProperties.hasOwnProperty(prop) && properties[prop]) {
     8257                            value = properties[prop].value;
     8258
     8259                            if (!(prop === "padding" && value.parts.length === 2 && value.parts[1].value === 0)) {
     8260                                reporter.report("Using width with " + prop + " can sometimes make elements larger than you expect.", properties[prop].line, properties[prop].col, rule);
     8261                            }
     8262                        }
     8263                    }
     8264                }
     8265            }
     8266        }
     8267
     8268        parser.addListener("startrule", startRule);
     8269        parser.addListener("startfontface", startRule);
     8270        parser.addListener("startpage", startRule);
     8271        parser.addListener("startpagemargin", startRule);
     8272        parser.addListener("startkeyframerule", startRule);
     8273        parser.addListener("startviewport", startRule);
     8274
     8275        parser.addListener("property", function(event) {
     8276            var name = event.property.text.toLowerCase();
     8277
     8278            if (heightProperties[name] || widthProperties[name]) {
     8279                if (!/^0\S*$/.test(event.value) && !(name === "border" && event.value.toString() === "none")) {
     8280                    properties[name] = {
     8281                        line: event.property.line,
     8282                        col: event.property.col,
     8283                        value: event.value
     8284                    };
     8285                }
     8286            } else {
     8287                if (/^(width|height)/i.test(name) && /^(length|percentage)/.test(event.value.parts[0].type)) {
     8288                    properties[name] = 1;
     8289                } else if (name === "box-sizing") {
     8290                    boxSizing = true;
     8291                }
     8292            }
     8293
     8294        });
     8295
     8296        parser.addListener("endrule", endRule);
     8297        parser.addListener("endfontface", endRule);
     8298        parser.addListener("endpage", endRule);
     8299        parser.addListener("endpagemargin", endRule);
     8300        parser.addListener("endkeyframerule", endRule);
     8301        parser.addListener("endviewport", endRule);
     8302    }
     8303
     8304});
     8305
     8306/*
     8307 * Rule: box-sizing doesn't work in IE6 and IE7.
     8308 */
     8309
     8310CSSLint.addRule({
     8311
     8312    // rule information
     8313    id: "box-sizing",
     8314    name: "Disallow use of box-sizing",
     8315    desc: "The box-sizing properties isn't supported in IE6 and IE7.",
     8316    url: "https://github.com/CSSLint/csslint/wiki/Disallow-box-sizing",
     8317    browsers: "IE6, IE7",
     8318    tags: ["Compatibility"],
     8319
     8320    // initialization
     8321    init: function(parser, reporter) {
     8322        "use strict";
     8323        var rule = this;
     8324
     8325        parser.addListener("property", function(event) {
     8326            var name = event.property.text.toLowerCase();
     8327
     8328            if (name === "box-sizing") {
     8329                reporter.report("The box-sizing property isn't supported in IE6 and IE7.", event.line, event.col, rule);
     8330            }
     8331        });
     8332    }
     8333
     8334});
     8335
     8336/*
     8337 * Rule: Use the bulletproof @font-face syntax to avoid 404's in old IE
     8338 * (http://www.fontspring.com/blog/the-new-bulletproof-font-face-syntax)
     8339 */
     8340
     8341CSSLint.addRule({
     8342
     8343    // rule information
     8344    id: "bulletproof-font-face",
     8345    name: "Use the bulletproof @font-face syntax",
     8346    desc: "Use the bulletproof @font-face syntax to avoid 404's in old IE (http://www.fontspring.com/blog/the-new-bulletproof-font-face-syntax).",
     8347    url: "https://github.com/CSSLint/csslint/wiki/Bulletproof-font-face",
     8348    browsers: "All",
     8349
     8350    // initialization
     8351    init: function(parser, reporter) {
     8352        "use strict";
     8353        var rule = this,
     8354            fontFaceRule = false,
     8355            firstSrc = true,
     8356            ruleFailed = false,
     8357            line, col;
     8358
     8359        // Mark the start of a @font-face declaration so we only test properties inside it
     8360        parser.addListener("startfontface", function() {
     8361            fontFaceRule = true;
     8362        });
     8363
     8364        parser.addListener("property", function(event) {
     8365            // If we aren't inside an @font-face declaration then just return
     8366            if (!fontFaceRule) {
     8367                return;
     8368            }
     8369
     8370            var propertyName = event.property.toString().toLowerCase(),
     8371                value = event.value.toString();
     8372
     8373            // Set the line and col numbers for use in the endfontface listener
     8374            line = event.line;
     8375            col = event.col;
     8376
     8377            // This is the property that we care about, we can ignore the rest
     8378            if (propertyName === "src") {
     8379                var regex = /^\s?url\(['"].+\.eot\?.*['"]\)\s*format\(['"]embedded-opentype['"]\).*$/i;
     8380
     8381                // We need to handle the advanced syntax with two src properties
     8382                if (!value.match(regex) && firstSrc) {
     8383                    ruleFailed = true;
     8384                    firstSrc = false;
     8385                } else if (value.match(regex) && !firstSrc) {
     8386                    ruleFailed = false;
     8387                }
     8388            }
     8389
     8390
     8391        });
     8392
     8393        // Back to normal rules that we don't need to test
     8394        parser.addListener("endfontface", function() {
     8395            fontFaceRule = false;
     8396
     8397            if (ruleFailed) {
     8398                reporter.report("@font-face declaration doesn't follow the fontspring bulletproof syntax.", line, col, rule);
     8399            }
     8400        });
     8401    }
     8402});
     8403
     8404/*
     8405 * Rule: Include all compatible vendor prefixes to reach a wider
     8406 * range of users.
     8407 */
     8408
     8409CSSLint.addRule({
     8410
     8411    // rule information
     8412    id: "compatible-vendor-prefixes",
     8413    name: "Require compatible vendor prefixes",
     8414    desc: "Include all compatible vendor prefixes to reach a wider range of users.",
     8415    url: "https://github.com/CSSLint/csslint/wiki/Require-compatible-vendor-prefixes",
     8416    browsers: "All",
     8417
     8418    // initialization
     8419    init: function (parser, reporter) {
     8420        "use strict";
     8421        var rule = this,
     8422            compatiblePrefixes,
     8423            properties,
     8424            prop,
     8425            variations,
     8426            prefixed,
     8427            i,
     8428            len,
     8429            inKeyFrame = false,
     8430            arrayPush = Array.prototype.push,
     8431            applyTo = [];
     8432
     8433        // See http://peter.sh/experiments/vendor-prefixed-css-property-overview/ for details
     8434        compatiblePrefixes = {
     8435            "animation"                  : "webkit",
     8436            "animation-delay"            : "webkit",
     8437            "animation-direction"        : "webkit",
     8438            "animation-duration"         : "webkit",
     8439            "animation-fill-mode"        : "webkit",
     8440            "animation-iteration-count"  : "webkit",
     8441            "animation-name"             : "webkit",
     8442            "animation-play-state"       : "webkit",
     8443            "animation-timing-function"  : "webkit",
     8444            "appearance"                 : "webkit moz",
     8445            "border-end"                 : "webkit moz",
     8446            "border-end-color"           : "webkit moz",
     8447            "border-end-style"           : "webkit moz",
     8448            "border-end-width"           : "webkit moz",
     8449            "border-image"               : "webkit moz o",
     8450            "border-radius"              : "webkit",
     8451            "border-start"               : "webkit moz",
     8452            "border-start-color"         : "webkit moz",
     8453            "border-start-style"         : "webkit moz",
     8454            "border-start-width"         : "webkit moz",
     8455            "box-align"                  : "webkit moz ms",
     8456            "box-direction"              : "webkit moz ms",
     8457            "box-flex"                   : "webkit moz ms",
     8458            "box-lines"                  : "webkit ms",
     8459            "box-ordinal-group"          : "webkit moz ms",
     8460            "box-orient"                 : "webkit moz ms",
     8461            "box-pack"                   : "webkit moz ms",
     8462            "box-sizing"                 : "",
     8463            "box-shadow"                 : "",
     8464            "column-count"               : "webkit moz ms",
     8465            "column-gap"                 : "webkit moz ms",
     8466            "column-rule"                : "webkit moz ms",
     8467            "column-rule-color"          : "webkit moz ms",
     8468            "column-rule-style"          : "webkit moz ms",
     8469            "column-rule-width"          : "webkit moz ms",
     8470            "column-width"               : "webkit moz ms",
     8471            "hyphens"                    : "epub moz",
     8472            "line-break"                 : "webkit ms",
     8473            "margin-end"                 : "webkit moz",
     8474            "margin-start"               : "webkit moz",
     8475            "marquee-speed"              : "webkit wap",
     8476            "marquee-style"              : "webkit wap",
     8477            "padding-end"                : "webkit moz",
     8478            "padding-start"              : "webkit moz",
     8479            "tab-size"                   : "moz o",
     8480            "text-size-adjust"           : "webkit ms",
     8481            "transform"                  : "webkit ms",
     8482            "transform-origin"           : "webkit ms",
     8483            "transition"                 : "",
     8484            "transition-delay"           : "",
     8485            "transition-duration"        : "",
     8486            "transition-property"        : "",
     8487            "transition-timing-function" : "",
     8488            "user-modify"                : "webkit moz",
     8489            "user-select"                : "webkit moz ms",
     8490            "word-break"                 : "epub ms",
     8491            "writing-mode"               : "epub ms"
     8492        };
     8493
     8494
     8495        for (prop in compatiblePrefixes) {
     8496            if (compatiblePrefixes.hasOwnProperty(prop)) {
     8497                variations = [];
     8498                prefixed = compatiblePrefixes[prop].split(" ");
     8499                for (i = 0, len = prefixed.length; i < len; i++) {
     8500                    variations.push("-" + prefixed[i] + "-" + prop);
     8501                }
     8502                compatiblePrefixes[prop] = variations;
     8503                arrayPush.apply(applyTo, variations);
     8504            }
     8505        }
     8506
     8507        parser.addListener("startrule", function () {
     8508            properties = [];
     8509        });
     8510
     8511        parser.addListener("startkeyframes", function (event) {
     8512            inKeyFrame = event.prefix || true;
     8513        });
     8514
     8515        parser.addListener("endkeyframes", function () {
     8516            inKeyFrame = false;
     8517        });
     8518
     8519        parser.addListener("property", function (event) {
     8520            var name = event.property;
     8521            if (CSSLint.Util.indexOf(applyTo, name.text) > -1) {
     8522
     8523                // e.g., -moz-transform is okay to be alone in @-moz-keyframes
     8524                if (!inKeyFrame || typeof inKeyFrame !== "string" ||
     8525                        name.text.indexOf("-" + inKeyFrame + "-") !== 0) {
     8526                    properties.push(name);
     8527                }
     8528            }
     8529        });
     8530
     8531        parser.addListener("endrule", function () {
     8532            if (!properties.length) {
     8533                return;
     8534            }
     8535
     8536            var propertyGroups = {},
     8537                i,
     8538                len,
     8539                name,
     8540                prop,
     8541                variations,
     8542                value,
     8543                full,
     8544                actual,
     8545                item,
     8546                propertiesSpecified;
     8547
     8548            for (i = 0, len = properties.length; i < len; i++) {
     8549                name = properties[i];
     8550
     8551                for (prop in compatiblePrefixes) {
     8552                    if (compatiblePrefixes.hasOwnProperty(prop)) {
     8553                        variations = compatiblePrefixes[prop];
     8554                        if (CSSLint.Util.indexOf(variations, name.text) > -1) {
     8555                            if (!propertyGroups[prop]) {
     8556                                propertyGroups[prop] = {
     8557                                    full: variations.slice(0),
     8558                                    actual: [],
     8559                                    actualNodes: []
     8560                                };
     8561                            }
     8562                            if (CSSLint.Util.indexOf(propertyGroups[prop].actual, name.text) === -1) {
     8563                                propertyGroups[prop].actual.push(name.text);
     8564                                propertyGroups[prop].actualNodes.push(name);
     8565                            }
     8566                        }
     8567                    }
     8568                }
     8569            }
     8570
     8571            for (prop in propertyGroups) {
     8572                if (propertyGroups.hasOwnProperty(prop)) {
     8573                    value = propertyGroups[prop];
     8574                    full = value.full;
     8575                    actual = value.actual;
     8576
     8577                    if (full.length > actual.length) {
     8578                        for (i = 0, len = full.length; i < len; i++) {
     8579                            item = full[i];
     8580                            if (CSSLint.Util.indexOf(actual, item) === -1) {
     8581                                propertiesSpecified = (actual.length === 1) ? actual[0] : (actual.length === 2) ? actual.join(" and ") : actual.join(", ");
     8582                                reporter.report("The property " + item + " is compatible with " + propertiesSpecified + " and should be included as well.", value.actualNodes[0].line, value.actualNodes[0].col, rule);
     8583                            }
     8584                        }
     8585
     8586                    }
     8587                }
     8588            }
     8589        });
     8590    }
     8591});
     8592
     8593/*
     8594 * Rule: Certain properties don't play well with certain display values.
     8595 * - float should not be used with inline-block
     8596 * - height, width, margin-top, margin-bottom, float should not be used with inline
     8597 * - vertical-align should not be used with block
     8598 * - margin, float should not be used with table-*
     8599 */
     8600
     8601CSSLint.addRule({
     8602
     8603    // rule information
     8604    id: "display-property-grouping",
     8605    name: "Require properties appropriate for display",
     8606    desc: "Certain properties shouldn't be used with certain display property values.",
     8607    url: "https://github.com/CSSLint/csslint/wiki/Require-properties-appropriate-for-display",
     8608    browsers: "All",
     8609
     8610    // initialization
     8611    init: function(parser, reporter) {
     8612        "use strict";
     8613        var rule = this;
     8614
     8615        var propertiesToCheck = {
     8616                display: 1,
     8617                "float": "none",
     8618                height: 1,
     8619                width: 1,
     8620                margin: 1,
     8621                "margin-left": 1,
     8622                "margin-right": 1,
     8623                "margin-bottom": 1,
     8624                "margin-top": 1,
     8625                padding: 1,
     8626                "padding-left": 1,
     8627                "padding-right": 1,
     8628                "padding-bottom": 1,
     8629                "padding-top": 1,
     8630                "vertical-align": 1
     8631            },
     8632            properties;
     8633
     8634        function reportProperty(name, display, msg) {
     8635            if (properties[name]) {
     8636                if (typeof propertiesToCheck[name] !== "string" || properties[name].value.toLowerCase() !== propertiesToCheck[name]) {
     8637                    reporter.report(msg || name + " can't be used with display: " + display + ".", properties[name].line, properties[name].col, rule);
     8638                }
     8639            }
     8640        }
     8641
     8642        function startRule() {
     8643            properties = {};
     8644        }
     8645
     8646        function endRule() {
     8647
     8648            var display = properties.display ? properties.display.value : null;
     8649            if (display) {
     8650                switch (display) {
     8651
     8652                    case "inline":
     8653                        // height, width, margin-top, margin-bottom, float should not be used with inline
     8654                        reportProperty("height", display);
     8655                        reportProperty("width", display);
     8656                        reportProperty("margin", display);
     8657                        reportProperty("margin-top", display);
     8658                        reportProperty("margin-bottom", display);
     8659                        reportProperty("float", display, "display:inline has no effect on floated elements (but may be used to fix the IE6 double-margin bug).");
     8660                        break;
     8661
     8662                    case "block":
     8663                        // vertical-align should not be used with block
     8664                        reportProperty("vertical-align", display);
     8665                        break;
     8666
     8667                    case "inline-block":
     8668                        // float should not be used with inline-block
     8669                        reportProperty("float", display);
     8670                        break;
     8671
     8672                    default:
     8673                        // margin, float should not be used with table
     8674                        if (display.indexOf("table-") === 0) {
     8675                            reportProperty("margin", display);
     8676                            reportProperty("margin-left", display);
     8677                            reportProperty("margin-right", display);
     8678                            reportProperty("margin-top", display);
     8679                            reportProperty("margin-bottom", display);
     8680                            reportProperty("float", display);
     8681                        }
     8682
     8683                        // otherwise do nothing
     8684                }
     8685            }
     8686
     8687        }
     8688
     8689        parser.addListener("startrule", startRule);
     8690        parser.addListener("startfontface", startRule);
     8691        parser.addListener("startkeyframerule", startRule);
     8692        parser.addListener("startpagemargin", startRule);
     8693        parser.addListener("startpage", startRule);
     8694        parser.addListener("startviewport", startRule);
     8695
     8696        parser.addListener("property", function(event) {
     8697            var name = event.property.text.toLowerCase();
     8698
     8699            if (propertiesToCheck[name]) {
     8700                properties[name] = {
     8701                    value: event.value.text,
     8702                    line: event.property.line,
     8703                    col: event.property.col
     8704                };
     8705            }
     8706        });
     8707
     8708        parser.addListener("endrule", endRule);
     8709        parser.addListener("endfontface", endRule);
     8710        parser.addListener("endkeyframerule", endRule);
     8711        parser.addListener("endpagemargin", endRule);
     8712        parser.addListener("endpage", endRule);
     8713        parser.addListener("endviewport", endRule);
     8714
     8715    }
     8716
     8717});
     8718
     8719/*
     8720 * Rule: Disallow duplicate background-images (using url).
     8721 */
     8722
     8723CSSLint.addRule({
     8724
     8725    // rule information
     8726    id: "duplicate-background-images",
     8727    name: "Disallow duplicate background images",
     8728    desc: "Every background-image should be unique. Use a common class for e.g. sprites.",
     8729    url: "https://github.com/CSSLint/csslint/wiki/Disallow-duplicate-background-images",
     8730    browsers: "All",
     8731
     8732    // initialization
     8733    init: function(parser, reporter) {
     8734        "use strict";
     8735        var rule = this,
     8736            stack = {};
     8737
     8738        parser.addListener("property", function(event) {
     8739            var name = event.property.text,
     8740                value = event.value,
     8741                i, len;
     8742
     8743            if (name.match(/background/i)) {
     8744                for (i=0, len=value.parts.length; i < len; i++) {
     8745                    if (value.parts[i].type === "uri") {
     8746                        if (typeof stack[value.parts[i].uri] === "undefined") {
     8747                            stack[value.parts[i].uri] = event;
     8748                        } else {
     8749                            reporter.report("Background image '" + value.parts[i].uri + "' was used multiple times, first declared at line " + stack[value.parts[i].uri].line + ", col " + stack[value.parts[i].uri].col + ".", event.line, event.col, rule);
     8750                        }
     8751                    }
     8752                }
     8753            }
     8754        });
     8755    }
     8756});
     8757
     8758/*
     8759 * Rule: Duplicate properties must appear one after the other. If an already-defined
     8760 * property appears somewhere else in the rule, then it's likely an error.
     8761 */
     8762
     8763CSSLint.addRule({
     8764
     8765    // rule information
     8766    id: "duplicate-properties",
     8767    name: "Disallow duplicate properties",
     8768    desc: "Duplicate properties must appear one after the other.",
     8769    url: "https://github.com/CSSLint/csslint/wiki/Disallow-duplicate-properties",
     8770    browsers: "All",
     8771
     8772    // initialization
     8773    init: function(parser, reporter) {
     8774        "use strict";
     8775        var rule = this,
     8776            properties,
     8777            lastProperty;
     8778
     8779        function startRule() {
     8780            properties = {};
     8781        }
     8782
     8783        parser.addListener("startrule", startRule);
     8784        parser.addListener("startfontface", startRule);
     8785        parser.addListener("startpage", startRule);
     8786        parser.addListener("startpagemargin", startRule);
     8787        parser.addListener("startkeyframerule", startRule);
     8788        parser.addListener("startviewport", startRule);
     8789
     8790        parser.addListener("property", function(event) {
     8791            var property = event.property,
     8792                name = property.text.toLowerCase();
     8793
     8794            if (properties[name] && (lastProperty !== name || properties[name] === event.value.text)) {
     8795                reporter.report("Duplicate property '" + event.property + "' found.", event.line, event.col, rule);
     8796            }
     8797
     8798            properties[name] = event.value.text;
     8799            lastProperty = name;
     8800
     8801        });
     8802
     8803
     8804    }
     8805
     8806});
     8807
     8808/*
     8809 * Rule: Style rules without any properties defined should be removed.
     8810 */
     8811
     8812CSSLint.addRule({
     8813
     8814    // rule information
     8815    id: "empty-rules",
     8816    name: "Disallow empty rules",
     8817    desc: "Rules without any properties specified should be removed.",
     8818    url: "https://github.com/CSSLint/csslint/wiki/Disallow-empty-rules",
     8819    browsers: "All",
     8820
     8821    // initialization
     8822    init: function(parser, reporter) {
     8823        "use strict";
     8824        var rule = this,
     8825            count = 0;
     8826
     8827        parser.addListener("startrule", function() {
     8828            count=0;
     8829        });
     8830
     8831        parser.addListener("property", function() {
     8832            count++;
     8833        });
     8834
     8835        parser.addListener("endrule", function(event) {
     8836            var selectors = event.selectors;
     8837            if (count === 0) {
     8838                reporter.report("Rule is empty.", selectors[0].line, selectors[0].col, rule);
     8839            }
     8840        });
     8841    }
     8842
     8843});
     8844
     8845/*
     8846 * Rule: There should be no syntax errors. (Duh.)
     8847 */
     8848
     8849CSSLint.addRule({
     8850
     8851    // rule information
     8852    id: "errors",
     8853    name: "Parsing Errors",
     8854    desc: "This rule looks for recoverable syntax errors.",
     8855    browsers: "All",
     8856
     8857    // initialization
     8858    init: function(parser, reporter) {
     8859        "use strict";
     8860        var rule = this;
     8861
     8862        parser.addListener("error", function(event) {
     8863            reporter.error(event.message, event.line, event.col, rule);
     8864        });
     8865
     8866    }
     8867
     8868});
     8869
     8870CSSLint.addRule({
     8871
     8872    // rule information
     8873    id: "fallback-colors",
     8874    name: "Require fallback colors",
     8875    desc: "For older browsers that don't support RGBA, HSL, or HSLA, provide a fallback color.",
     8876    url: "https://github.com/CSSLint/csslint/wiki/Require-fallback-colors",
     8877    browsers: "IE6,IE7,IE8",
     8878
     8879    // initialization
     8880    init: function(parser, reporter) {
     8881        "use strict";
     8882        var rule = this,
     8883            lastProperty,
     8884            propertiesToCheck = {
     8885                color: 1,
     8886                background: 1,
     8887                "border-color": 1,
     8888                "border-top-color": 1,
     8889                "border-right-color": 1,
     8890                "border-bottom-color": 1,
     8891                "border-left-color": 1,
     8892                border: 1,
     8893                "border-top": 1,
     8894                "border-right": 1,
     8895                "border-bottom": 1,
     8896                "border-left": 1,
     8897                "background-color": 1
     8898            };
     8899
     8900        function startRule() {
     8901            lastProperty = null;
     8902        }
     8903
     8904        parser.addListener("startrule", startRule);
     8905        parser.addListener("startfontface", startRule);
     8906        parser.addListener("startpage", startRule);
     8907        parser.addListener("startpagemargin", startRule);
     8908        parser.addListener("startkeyframerule", startRule);
     8909        parser.addListener("startviewport", startRule);
     8910
     8911        parser.addListener("property", function(event) {
     8912            var property = event.property,
     8913                name = property.text.toLowerCase(),
     8914                parts = event.value.parts,
     8915                i = 0,
     8916                colorType = "",
     8917                len = parts.length;
     8918
     8919            if (propertiesToCheck[name]) {
     8920                while (i < len) {
     8921                    if (parts[i].type === "color") {
     8922                        if ("alpha" in parts[i] || "hue" in parts[i]) {
     8923
     8924                            if (/([^\)]+)\(/.test(parts[i])) {
     8925                                colorType = RegExp.$1.toUpperCase();
     8926                            }
     8927
     8928                            if (!lastProperty || (lastProperty.property.text.toLowerCase() !== name || lastProperty.colorType !== "compat")) {
     8929                                reporter.report("Fallback " + name + " (hex or RGB) should precede " + colorType + " " + name + ".", event.line, event.col, rule);
     8930                            }
     8931                        } else {
     8932                            event.colorType = "compat";
     8933                        }
     8934                    }
     8935
     8936                    i++;
     8937                }
     8938            }
     8939
     8940            lastProperty = event;
     8941        });
     8942
     8943    }
     8944
     8945});
     8946
     8947/*
     8948 * Rule: You shouldn't use more than 10 floats. If you do, there's probably
     8949 * room for some abstraction.
     8950 */
     8951
     8952CSSLint.addRule({
     8953
     8954    // rule information
     8955    id: "floats",
     8956    name: "Disallow too many floats",
     8957    desc: "This rule tests if the float property is used too many times",
     8958    url: "https://github.com/CSSLint/csslint/wiki/Disallow-too-many-floats",
     8959    browsers: "All",
     8960
     8961    // initialization
     8962    init: function(parser, reporter) {
     8963        "use strict";
     8964        var rule = this;
     8965        var count = 0;
     8966
     8967        // count how many times "float" is used
     8968        parser.addListener("property", function(event) {
     8969            if (event.property.text.toLowerCase() === "float" &&
     8970                    event.value.text.toLowerCase() !== "none") {
     8971                count++;
     8972            }
     8973        });
     8974
     8975        // report the results
     8976        parser.addListener("endstylesheet", function() {
     8977            reporter.stat("floats", count);
     8978            if (count >= 10) {
     8979                reporter.rollupWarn("Too many floats (" + count + "), you're probably using them for layout. Consider using a grid system instead.", rule);
     8980            }
     8981        });
     8982    }
     8983
     8984});
     8985
     8986/*
     8987 * Rule: Avoid too many @font-face declarations in the same stylesheet.
     8988 */
     8989
     8990CSSLint.addRule({
     8991
     8992    // rule information
     8993    id: "font-faces",
     8994    name: "Don't use too many web fonts",
     8995    desc: "Too many different web fonts in the same stylesheet.",
     8996    url: "https://github.com/CSSLint/csslint/wiki/Don%27t-use-too-many-web-fonts",
     8997    browsers: "All",
     8998
     8999    // initialization
     9000    init: function(parser, reporter) {
     9001        "use strict";
     9002        var rule = this,
     9003            count = 0;
     9004
     9005
     9006        parser.addListener("startfontface", function() {
     9007            count++;
     9008        });
     9009
     9010        parser.addListener("endstylesheet", function() {
     9011            if (count > 5) {
     9012                reporter.rollupWarn("Too many @font-face declarations (" + count + ").", rule);
     9013            }
     9014        });
     9015    }
     9016
     9017});
     9018
     9019/*
     9020 * Rule: You shouldn't need more than 9 font-size declarations.
     9021 */
     9022
     9023CSSLint.addRule({
     9024
     9025    // rule information
     9026    id: "font-sizes",
     9027    name: "Disallow too many font sizes",
     9028    desc: "Checks the number of font-size declarations.",
     9029    url: "https://github.com/CSSLint/csslint/wiki/Don%27t-use-too-many-font-size-declarations",
     9030    browsers: "All",
     9031
     9032    // initialization
     9033    init: function(parser, reporter) {
     9034        "use strict";
     9035        var rule = this,
     9036            count = 0;
     9037
     9038        // check for use of "font-size"
     9039        parser.addListener("property", function(event) {
     9040            if (event.property.toString() === "font-size") {
     9041                count++;
     9042            }
     9043        });
     9044
     9045        // report the results
     9046        parser.addListener("endstylesheet", function() {
     9047            reporter.stat("font-sizes", count);
     9048            if (count >= 10) {
     9049                reporter.rollupWarn("Too many font-size declarations (" + count + "), abstraction needed.", rule);
     9050            }
     9051        });
     9052    }
     9053
     9054});
     9055
     9056/*
     9057 * Rule: When using a vendor-prefixed gradient, make sure to use them all.
     9058 */
     9059
     9060CSSLint.addRule({
     9061
     9062    // rule information
     9063    id: "gradients",
     9064    name: "Require all gradient definitions",
     9065    desc: "When using a vendor-prefixed gradient, make sure to use them all.",
     9066    url: "https://github.com/CSSLint/csslint/wiki/Require-all-gradient-definitions",
     9067    browsers: "All",
     9068
     9069    // initialization
     9070    init: function(parser, reporter) {
     9071        "use strict";
     9072        var rule = this,
     9073            gradients;
     9074
     9075        parser.addListener("startrule", function() {
     9076            gradients = {
     9077                moz: 0,
     9078                webkit: 0,
     9079                oldWebkit: 0,
     9080                o: 0
     9081            };
     9082        });
     9083
     9084        parser.addListener("property", function(event) {
     9085
     9086            if (/\-(moz|o|webkit)(?:\-(?:linear|radial))\-gradient/i.test(event.value)) {
     9087                gradients[RegExp.$1] = 1;
     9088            } else if (/\-webkit\-gradient/i.test(event.value)) {
     9089                gradients.oldWebkit = 1;
     9090            }
     9091
     9092        });
     9093
     9094        parser.addListener("endrule", function(event) {
     9095            var missing = [];
     9096
     9097            if (!gradients.moz) {
     9098                missing.push("Firefox 3.6+");
     9099            }
     9100
     9101            if (!gradients.webkit) {
     9102                missing.push("Webkit (Safari 5+, Chrome)");
     9103            }
     9104
     9105            if (!gradients.oldWebkit) {
     9106                missing.push("Old Webkit (Safari 4+, Chrome)");
     9107            }
     9108
     9109            if (!gradients.o) {
     9110                missing.push("Opera 11.1+");
     9111            }
     9112
     9113            if (missing.length && missing.length < 4) {
     9114                reporter.report("Missing vendor-prefixed CSS gradients for " + missing.join(", ") + ".", event.selectors[0].line, event.selectors[0].col, rule);
     9115            }
     9116
     9117        });
     9118
     9119    }
     9120
     9121});
     9122
     9123/*
     9124 * Rule: Don't use IDs for selectors.
     9125 */
     9126
     9127CSSLint.addRule({
     9128
     9129    // rule information
     9130    id: "ids",
     9131    name: "Disallow IDs in selectors",
     9132    desc: "Selectors should not contain IDs.",
     9133    url: "https://github.com/CSSLint/csslint/wiki/Disallow-IDs-in-selectors",
     9134    browsers: "All",
     9135
     9136    // initialization
     9137    init: function(parser, reporter) {
     9138        "use strict";
     9139        var rule = this;
     9140        parser.addListener("startrule", function(event) {
     9141            var selectors = event.selectors,
     9142                selector,
     9143                part,
     9144                modifier,
     9145                idCount,
     9146                i, j, k;
     9147
     9148            for (i=0; i < selectors.length; i++) {
     9149                selector = selectors[i];
     9150                idCount = 0;
     9151
     9152                for (j=0; j < selector.parts.length; j++) {
     9153                    part = selector.parts[j];
     9154                    if (part.type === parser.SELECTOR_PART_TYPE) {
     9155                        for (k=0; k < part.modifiers.length; k++) {
     9156                            modifier = part.modifiers[k];
     9157                            if (modifier.type === "id") {
     9158                                idCount++;
     9159                            }
     9160                        }
     9161                    }
     9162                }
     9163
     9164                if (idCount === 1) {
     9165                    reporter.report("Don't use IDs in selectors.", selector.line, selector.col, rule);
     9166                } else if (idCount > 1) {
     9167                    reporter.report(idCount + " IDs in the selector, really?", selector.line, selector.col, rule);
     9168                }
     9169            }
     9170
     9171        });
     9172    }
     9173
     9174});
     9175
     9176/*
     9177 * Rule: IE6-9 supports up to 31 stylesheet import.
     9178 * Reference:
     9179 * http://blogs.msdn.com/b/ieinternals/archive/2011/05/14/internet-explorer-stylesheet-rule-selector-import-sheet-limit-maximum.aspx
     9180 */
     9181
     9182CSSLint.addRule({
     9183
     9184    // rule information
     9185    id: "import-ie-limit",
     9186    name: "@import limit on IE6-IE9",
     9187    desc: "IE6-9 supports up to 31 @import per stylesheet",
     9188    browsers: "IE6, IE7, IE8, IE9",
     9189
     9190    // initialization
     9191    init: function(parser, reporter) {
     9192        "use strict";
     9193        var rule = this,
     9194            MAX_IMPORT_COUNT = 31,
     9195            count = 0;
     9196
     9197        function startPage() {
     9198            count = 0;
     9199        }
     9200
     9201        parser.addListener("startpage", startPage);
     9202
     9203        parser.addListener("import", function() {
     9204            count++;
     9205        });
     9206
     9207        parser.addListener("endstylesheet", function() {
     9208            if (count > MAX_IMPORT_COUNT) {
     9209                reporter.rollupError(
     9210                    "Too many @import rules (" + count + "). IE6-9 supports up to 31 import per stylesheet.",
     9211                    rule
     9212                );
     9213            }
     9214        });
     9215    }
     9216
     9217});
     9218
     9219/*
     9220 * Rule: Don't use @import, use <link> instead.
     9221 */
     9222
     9223CSSLint.addRule({
     9224
     9225    // rule information
     9226    id: "import",
     9227    name: "Disallow @import",
     9228    desc: "Don't use @import, use <link> instead.",
     9229    url: "https://github.com/CSSLint/csslint/wiki/Disallow-%40import",
     9230    browsers: "All",
     9231
     9232    // initialization
     9233    init: function(parser, reporter) {
     9234        "use strict";
     9235        var rule = this;
     9236
     9237        parser.addListener("import", function(event) {
     9238            reporter.report("@import prevents parallel downloads, use <link> instead.", event.line, event.col, rule);
     9239        });
     9240
     9241    }
     9242
     9243});
     9244
     9245/*
     9246 * Rule: Make sure !important is not overused, this could lead to specificity
     9247 * war. Display a warning on !important declarations, an error if it's
     9248 * used more at least 10 times.
     9249 */
     9250
     9251CSSLint.addRule({
     9252
     9253    // rule information
     9254    id: "important",
     9255    name: "Disallow !important",
     9256    desc: "Be careful when using !important declaration",
     9257    url: "https://github.com/CSSLint/csslint/wiki/Disallow-%21important",
     9258    browsers: "All",
     9259
     9260    // initialization
     9261    init: function(parser, reporter) {
     9262        "use strict";
     9263        var rule = this,
     9264            count = 0;
     9265
     9266        // warn that important is used and increment the declaration counter
     9267        parser.addListener("property", function(event) {
     9268            if (event.important === true) {
     9269                count++;
     9270                reporter.report("Use of !important", event.line, event.col, rule);
     9271            }
     9272        });
     9273
     9274        // if there are more than 10, show an error
     9275        parser.addListener("endstylesheet", function() {
     9276            reporter.stat("important", count);
     9277            if (count >= 10) {
     9278                reporter.rollupWarn("Too many !important declarations (" + count + "), try to use less than 10 to avoid specificity issues.", rule);
     9279            }
     9280        });
     9281    }
     9282
     9283});
     9284
     9285/*
     9286 * Rule: Properties should be known (listed in CSS3 specification) or
     9287 * be a vendor-prefixed property.
     9288 */
     9289
     9290CSSLint.addRule({
     9291
     9292    // rule information
     9293    id: "known-properties",
     9294    name: "Require use of known properties",
     9295    desc: "Properties should be known (listed in CSS3 specification) or be a vendor-prefixed property.",
     9296    url: "https://github.com/CSSLint/csslint/wiki/Require-use-of-known-properties",
     9297    browsers: "All",
     9298
     9299    // initialization
     9300    init: function(parser, reporter) {
     9301        "use strict";
     9302        var rule = this;
     9303
     9304        parser.addListener("property", function(event) {
     9305
     9306            // the check is handled entirely by the parser-lib (https://github.com/nzakas/parser-lib)
     9307            if (event.invalid) {
     9308                reporter.report(event.invalid.message, event.line, event.col, rule);
     9309            }
     9310
     9311        });
     9312    }
     9313
     9314});
     9315
     9316/*
     9317 * Rule: All properties should be in alphabetical order.
     9318 */
     9319
     9320CSSLint.addRule({
     9321
     9322    // rule information
     9323    id: "order-alphabetical",
     9324    name: "Alphabetical order",
     9325    desc: "Assure properties are in alphabetical order",
     9326    browsers: "All",
     9327
     9328    // initialization
     9329    init: function(parser, reporter) {
     9330        "use strict";
     9331        var rule = this,
     9332            properties;
     9333
     9334        var startRule = function () {
     9335            properties = [];
     9336        };
     9337
     9338        var endRule = function(event) {
     9339            var currentProperties = properties.join(","),
     9340                expectedProperties = properties.sort().join(",");
     9341
     9342            if (currentProperties !== expectedProperties) {
     9343                reporter.report("Rule doesn't have all its properties in alphabetical order.", event.line, event.col, rule);
     9344            }
     9345        };
     9346
     9347        parser.addListener("startrule", startRule);
     9348        parser.addListener("startfontface", startRule);
     9349        parser.addListener("startpage", startRule);
     9350        parser.addListener("startpagemargin", startRule);
     9351        parser.addListener("startkeyframerule", startRule);
     9352        parser.addListener("startviewport", startRule);
     9353
     9354        parser.addListener("property", function(event) {
     9355            var name = event.property.text,
     9356                lowerCasePrefixLessName = name.toLowerCase().replace(/^-.*?-/, "");
     9357
     9358            properties.push(lowerCasePrefixLessName);
     9359        });
     9360
     9361        parser.addListener("endrule", endRule);
     9362        parser.addListener("endfontface", endRule);
     9363        parser.addListener("endpage", endRule);
     9364        parser.addListener("endpagemargin", endRule);
     9365        parser.addListener("endkeyframerule", endRule);
     9366        parser.addListener("endviewport", endRule);
     9367    }
     9368
     9369});
     9370
     9371/*
     9372 * Rule: outline: none or outline: 0 should only be used in a :focus rule
     9373 *       and only if there are other properties in the same rule.
     9374 */
     9375
     9376CSSLint.addRule({
     9377
     9378    // rule information
     9379    id: "outline-none",
     9380    name: "Disallow outline: none",
     9381    desc: "Use of outline: none or outline: 0 should be limited to :focus rules.",
     9382    url: "https://github.com/CSSLint/csslint/wiki/Disallow-outline%3Anone",
     9383    browsers: "All",
     9384    tags: ["Accessibility"],
     9385
     9386    // initialization
     9387    init: function(parser, reporter) {
     9388        "use strict";
     9389        var rule = this,
     9390            lastRule;
     9391
     9392        function startRule(event) {
     9393            if (event.selectors) {
     9394                lastRule = {
     9395                    line: event.line,
     9396                    col: event.col,
     9397                    selectors: event.selectors,
     9398                    propCount: 0,
     9399                    outline: false
     9400                };
     9401            } else {
     9402                lastRule = null;
     9403            }
     9404        }
     9405
     9406        function endRule() {
     9407            if (lastRule) {
     9408                if (lastRule.outline) {
     9409                    if (lastRule.selectors.toString().toLowerCase().indexOf(":focus") === -1) {
     9410                        reporter.report("Outlines should only be modified using :focus.", lastRule.line, lastRule.col, rule);
     9411                    } else if (lastRule.propCount === 1) {
     9412                        reporter.report("Outlines shouldn't be hidden unless other visual changes are made.", lastRule.line, lastRule.col, rule);
     9413                    }
     9414                }
     9415            }
     9416        }
     9417
     9418        parser.addListener("startrule", startRule);
     9419        parser.addListener("startfontface", startRule);
     9420        parser.addListener("startpage", startRule);
     9421        parser.addListener("startpagemargin", startRule);
     9422        parser.addListener("startkeyframerule", startRule);
     9423        parser.addListener("startviewport", startRule);
     9424
     9425        parser.addListener("property", function(event) {
     9426            var name = event.property.text.toLowerCase(),
     9427                value = event.value;
     9428
     9429            if (lastRule) {
     9430                lastRule.propCount++;
     9431                if (name === "outline" && (value.toString() === "none" || value.toString() === "0")) {
     9432                    lastRule.outline = true;
     9433                }
     9434            }
     9435
     9436        });
     9437
     9438        parser.addListener("endrule", endRule);
     9439        parser.addListener("endfontface", endRule);
     9440        parser.addListener("endpage", endRule);
     9441        parser.addListener("endpagemargin", endRule);
     9442        parser.addListener("endkeyframerule", endRule);
     9443        parser.addListener("endviewport", endRule);
     9444
     9445    }
     9446
     9447});
     9448
     9449/*
     9450 * Rule: Don't use classes or IDs with elements (a.foo or a#foo).
     9451 */
     9452
     9453CSSLint.addRule({
     9454
     9455    // rule information
     9456    id: "overqualified-elements",
     9457    name: "Disallow overqualified elements",
     9458    desc: "Don't use classes or IDs with elements (a.foo or a#foo).",
     9459    url: "https://github.com/CSSLint/csslint/wiki/Disallow-overqualified-elements",
     9460    browsers: "All",
     9461
     9462    // initialization
     9463    init: function(parser, reporter) {
     9464        "use strict";
     9465        var rule = this,
     9466            classes = {};
     9467
     9468        parser.addListener("startrule", function(event) {
     9469            var selectors = event.selectors,
     9470                selector,
     9471                part,
     9472                modifier,
     9473                i, j, k;
     9474
     9475            for (i=0; i < selectors.length; i++) {
     9476                selector = selectors[i];
     9477
     9478                for (j=0; j < selector.parts.length; j++) {
     9479                    part = selector.parts[j];
     9480                    if (part.type === parser.SELECTOR_PART_TYPE) {
     9481                        for (k=0; k < part.modifiers.length; k++) {
     9482                            modifier = part.modifiers[k];
     9483                            if (part.elementName && modifier.type === "id") {
     9484                                reporter.report("Element (" + part + ") is overqualified, just use " + modifier + " without element name.", part.line, part.col, rule);
     9485                            } else if (modifier.type === "class") {
     9486
     9487                                if (!classes[modifier]) {
     9488                                    classes[modifier] = [];
     9489                                }
     9490                                classes[modifier].push({
     9491                                    modifier: modifier,
     9492                                    part: part
     9493                                });
     9494                            }
     9495                        }
     9496                    }
     9497                }
     9498            }
     9499        });
     9500
     9501        parser.addListener("endstylesheet", function() {
     9502
     9503            var prop;
     9504            for (prop in classes) {
     9505                if (classes.hasOwnProperty(prop)) {
     9506
     9507                    // one use means that this is overqualified
     9508                    if (classes[prop].length === 1 && classes[prop][0].part.elementName) {
     9509                        reporter.report("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);
     9510                    }
     9511                }
     9512            }
     9513        });
     9514    }
     9515
     9516});
     9517
     9518/*
     9519 * Rule: Headings (h1-h6) should not be qualified (namespaced).
     9520 */
     9521
     9522CSSLint.addRule({
     9523
     9524    // rule information
     9525    id: "qualified-headings",
     9526    name: "Disallow qualified headings",
     9527    desc: "Headings should not be qualified (namespaced).",
     9528    url: "https://github.com/CSSLint/csslint/wiki/Disallow-qualified-headings",
     9529    browsers: "All",
     9530
     9531    // initialization
     9532    init: function(parser, reporter) {
     9533        "use strict";
     9534        var rule = this;
     9535
     9536        parser.addListener("startrule", function(event) {
     9537            var selectors = event.selectors,
     9538                selector,
     9539                part,
     9540                i, j;
     9541
     9542            for (i=0; i < selectors.length; i++) {
     9543                selector = selectors[i];
     9544
     9545                for (j=0; j < selector.parts.length; j++) {
     9546                    part = selector.parts[j];
     9547                    if (part.type === parser.SELECTOR_PART_TYPE) {
     9548                        if (part.elementName && /h[1-6]/.test(part.elementName.toString()) && j > 0) {
     9549                            reporter.report("Heading (" + part.elementName + ") should not be qualified.", part.line, part.col, rule);
     9550                        }
     9551                    }
     9552                }
     9553            }
     9554        });
     9555    }
     9556
     9557});
     9558
     9559/*
     9560 * Rule: Selectors that look like regular expressions are slow and should be avoided.
     9561 */
     9562
     9563CSSLint.addRule({
     9564
     9565    // rule information
     9566    id: "regex-selectors",
     9567    name: "Disallow selectors that look like regexs",
     9568    desc: "Selectors that look like regular expressions are slow and should be avoided.",
     9569    url: "https://github.com/CSSLint/csslint/wiki/Disallow-selectors-that-look-like-regular-expressions",
     9570    browsers: "All",
     9571
     9572    // initialization
     9573    init: function(parser, reporter) {
     9574        "use strict";
     9575        var rule = this;
     9576
     9577        parser.addListener("startrule", function(event) {
     9578            var selectors = event.selectors,
     9579                selector,
     9580                part,
     9581                modifier,
     9582                i, j, k;
     9583
     9584            for (i=0; i < selectors.length; i++) {
     9585                selector = selectors[i];
     9586                for (j=0; j < selector.parts.length; j++) {
     9587                    part = selector.parts[j];
     9588                    if (part.type === parser.SELECTOR_PART_TYPE) {
     9589                        for (k=0; k < part.modifiers.length; k++) {
     9590                            modifier = part.modifiers[k];
     9591                            if (modifier.type === "attribute") {
     9592                                if (/([~\|\^\$\*]=)/.test(modifier)) {
     9593                                    reporter.report("Attribute selectors with " + RegExp.$1 + " are slow!", modifier.line, modifier.col, rule);
     9594                                }
     9595                            }
     9596
     9597                        }
     9598                    }
     9599                }
     9600            }
     9601        });
     9602    }
     9603
     9604});
     9605
     9606/*
     9607 * Rule: Total number of rules should not exceed x.
     9608 */
     9609
     9610CSSLint.addRule({
     9611
     9612    // rule information
     9613    id: "rules-count",
     9614    name: "Rules Count",
     9615    desc: "Track how many rules there are.",
     9616    browsers: "All",
     9617
     9618    // initialization
     9619    init: function(parser, reporter) {
     9620        "use strict";
     9621        var count = 0;
     9622
     9623        // count each rule
     9624        parser.addListener("startrule", function() {
     9625            count++;
     9626        });
     9627
     9628        parser.addListener("endstylesheet", function() {
     9629            reporter.stat("rule-count", count);
     9630        });
     9631    }
     9632
     9633});
     9634
     9635/*
     9636 * Rule: Warn people with approaching the IE 4095 limit
     9637 */
     9638
     9639CSSLint.addRule({
     9640
     9641    // rule information
     9642    id: "selector-max-approaching",
     9643    name: "Warn when approaching the 4095 selector limit for IE",
     9644    desc: "Will warn when selector count is >= 3800 selectors.",
     9645    browsers: "IE",
     9646
     9647    // initialization
     9648    init: function(parser, reporter) {
     9649        "use strict";
     9650        var rule = this, count = 0;
     9651
     9652        parser.addListener("startrule", function(event) {
     9653            count += event.selectors.length;
     9654        });
     9655
     9656        parser.addListener("endstylesheet", function() {
     9657            if (count >= 3800) {
     9658                reporter.report("You have " + count + " selectors. Internet Explorer supports a maximum of 4095 selectors per stylesheet. Consider refactoring.", 0, 0, rule);
     9659            }
     9660        });
     9661    }
     9662
     9663});
     9664
     9665/*
     9666 * Rule: Warn people past the IE 4095 limit
     9667 */
     9668
     9669CSSLint.addRule({
     9670
     9671    // rule information
     9672    id: "selector-max",
     9673    name: "Error when past the 4095 selector limit for IE",
     9674    desc: "Will error when selector count is > 4095.",
     9675    browsers: "IE",
     9676
     9677    // initialization
     9678    init: function(parser, reporter) {
     9679        "use strict";
     9680        var rule = this, count = 0;
     9681
     9682        parser.addListener("startrule", function(event) {
     9683            count += event.selectors.length;
     9684        });
     9685
     9686        parser.addListener("endstylesheet", function() {
     9687            if (count > 4095) {
     9688                reporter.report("You have " + count + " selectors. Internet Explorer supports a maximum of 4095 selectors per stylesheet. Consider refactoring.", 0, 0, rule);
     9689            }
     9690        });
     9691    }
     9692
     9693});
     9694
     9695/*
     9696 * Rule: Avoid new-line characters in selectors.
     9697 */
     9698
     9699CSSLint.addRule({
     9700
     9701    // rule information
     9702    id: "selector-newline",
     9703    name: "Disallow new-line characters in selectors",
     9704    desc: "New-line characters in selectors are usually a forgotten comma and not a descendant combinator.",
     9705    browsers: "All",
     9706
     9707    // initialization
     9708    init: function(parser, reporter) {
     9709        "use strict";
     9710        var rule = this;
     9711
     9712        function startRule(event) {
     9713            var i, len, selector, p, n, pLen, part, part2, type, currentLine, nextLine,
     9714                selectors = event.selectors;
     9715
     9716            for (i = 0, len = selectors.length; i < len; i++) {
     9717                selector = selectors[i];
     9718                for (p = 0, pLen = selector.parts.length; p < pLen; p++) {
     9719                    for (n = p + 1; n < pLen; n++) {
     9720                        part = selector.parts[p];
     9721                        part2 = selector.parts[n];
     9722                        type = part.type;
     9723                        currentLine = part.line;
     9724                        nextLine = part2.line;
     9725
     9726                        if (type === "descendant" && nextLine > currentLine) {
     9727                            reporter.report("newline character found in selector (forgot a comma?)", currentLine, selectors[i].parts[0].col, rule);
     9728                        }
     9729                    }
     9730                }
     9731
     9732            }
     9733        }
     9734
     9735        parser.addListener("startrule", startRule);
     9736
     9737    }
     9738});
     9739
     9740/*
     9741 * Rule: Use shorthand properties where possible.
     9742 *
     9743 */
     9744
     9745CSSLint.addRule({
     9746
     9747    // rule information
     9748    id: "shorthand",
     9749    name: "Require shorthand properties",
     9750    desc: "Use shorthand properties where possible.",
     9751    url: "https://github.com/CSSLint/csslint/wiki/Require-shorthand-properties",
     9752    browsers: "All",
     9753
     9754    // initialization
     9755    init: function(parser, reporter) {
     9756        "use strict";
     9757        var rule = this,
     9758            prop, i, len,
     9759            propertiesToCheck = {},
     9760            properties,
     9761            mapping = {
     9762                "margin": [
     9763                    "margin-top",
     9764                    "margin-bottom",
     9765                    "margin-left",
     9766                    "margin-right"
     9767                ],
     9768                "padding": [
     9769                    "padding-top",
     9770                    "padding-bottom",
     9771                    "padding-left",
     9772                    "padding-right"
     9773                ]
     9774            };
     9775
     9776        // initialize propertiesToCheck
     9777        for (prop in mapping) {
     9778            if (mapping.hasOwnProperty(prop)) {
     9779                for (i=0, len=mapping[prop].length; i < len; i++) {
     9780                    propertiesToCheck[mapping[prop][i]] = prop;
     9781                }
     9782            }
     9783        }
     9784
     9785        function startRule() {
     9786            properties = {};
     9787        }
     9788
     9789        // event handler for end of rules
     9790        function endRule(event) {
     9791
     9792            var prop, i, len, total;
     9793
     9794            // check which properties this rule has
     9795            for (prop in mapping) {
     9796                if (mapping.hasOwnProperty(prop)) {
     9797                    total=0;
     9798
     9799                    for (i=0, len=mapping[prop].length; i < len; i++) {
     9800                        total += properties[mapping[prop][i]] ? 1 : 0;
     9801                    }
     9802
     9803                    if (total === mapping[prop].length) {
     9804                        reporter.report("The properties " + mapping[prop].join(", ") + " can be replaced by " + prop + ".", event.line, event.col, rule);
     9805                    }
     9806                }
     9807            }
     9808        }
     9809
     9810        parser.addListener("startrule", startRule);
     9811        parser.addListener("startfontface", startRule);
     9812
     9813        // check for use of "font-size"
     9814        parser.addListener("property", function(event) {
     9815            var name = event.property.toString().toLowerCase();
     9816
     9817            if (propertiesToCheck[name]) {
     9818                properties[name] = 1;
     9819            }
     9820        });
     9821
     9822        parser.addListener("endrule", endRule);
     9823        parser.addListener("endfontface", endRule);
     9824
     9825    }
     9826
     9827});
     9828
     9829/*
     9830 * Rule: Don't use properties with a star prefix.
     9831 *
     9832 */
     9833
     9834CSSLint.addRule({
     9835
     9836    // rule information
     9837    id: "star-property-hack",
     9838    name: "Disallow properties with a star prefix",
     9839    desc: "Checks for the star property hack (targets IE6/7)",
     9840    url: "https://github.com/CSSLint/csslint/wiki/Disallow-star-hack",
     9841    browsers: "All",
     9842
     9843    // initialization
     9844    init: function(parser, reporter) {
     9845        "use strict";
     9846        var rule = this;
     9847
     9848        // check if property name starts with "*"
     9849        parser.addListener("property", function(event) {
     9850            var property = event.property;
     9851
     9852            if (property.hack === "*") {
     9853                reporter.report("Property with star prefix found.", event.property.line, event.property.col, rule);
     9854            }
     9855        });
     9856    }
     9857});
     9858
     9859/*
     9860 * Rule: Don't use text-indent for image replacement if you need to support rtl.
     9861 *
     9862 */
     9863
     9864CSSLint.addRule({
     9865
     9866    // rule information
     9867    id: "text-indent",
     9868    name: "Disallow negative text-indent",
     9869    desc: "Checks for text indent less than -99px",
     9870    url: "https://github.com/CSSLint/csslint/wiki/Disallow-negative-text-indent",
     9871    browsers: "All",
     9872
     9873    // initialization
     9874    init: function(parser, reporter) {
     9875        "use strict";
     9876        var rule = this,
     9877            textIndent,
     9878            direction;
     9879
     9880
     9881        function startRule() {
     9882            textIndent = false;
     9883            direction = "inherit";
     9884        }
     9885
     9886        // event handler for end of rules
     9887        function endRule() {
     9888            if (textIndent && direction !== "ltr") {
     9889                reporter.report("Negative text-indent doesn't work well with RTL. If you use text-indent for image replacement explicitly set direction for that item to ltr.", textIndent.line, textIndent.col, rule);
     9890            }
     9891        }
     9892
     9893        parser.addListener("startrule", startRule);
     9894        parser.addListener("startfontface", startRule);
     9895
     9896        // check for use of "font-size"
     9897        parser.addListener("property", function(event) {
     9898            var name = event.property.toString().toLowerCase(),
     9899                value = event.value;
     9900
     9901            if (name === "text-indent" && value.parts[0].value < -99) {
     9902                textIndent = event.property;
     9903            } else if (name === "direction" && value.toString() === "ltr") {
     9904                direction = "ltr";
     9905            }
     9906        });
     9907
     9908        parser.addListener("endrule", endRule);
     9909        parser.addListener("endfontface", endRule);
     9910
     9911    }
     9912
     9913});
     9914
     9915/*
     9916 * Rule: Don't use properties with a underscore prefix.
     9917 *
     9918 */
     9919
     9920CSSLint.addRule({
     9921
     9922    // rule information
     9923    id: "underscore-property-hack",
     9924    name: "Disallow properties with an underscore prefix",
     9925    desc: "Checks for the underscore property hack (targets IE6)",
     9926    url: "https://github.com/CSSLint/csslint/wiki/Disallow-underscore-hack",
     9927    browsers: "All",
     9928
     9929    // initialization
     9930    init: function(parser, reporter) {
     9931        "use strict";
     9932        var rule = this;
     9933
     9934        // check if property name starts with "_"
     9935        parser.addListener("property", function(event) {
     9936            var property = event.property;
     9937
     9938            if (property.hack === "_") {
     9939                reporter.report("Property with underscore prefix found.", event.property.line, event.property.col, rule);
     9940            }
     9941        });
     9942    }
     9943});
     9944
     9945/*
     9946 * Rule: Headings (h1-h6) should be defined only once.
     9947 */
     9948
     9949CSSLint.addRule({
     9950
     9951    // rule information
     9952    id: "unique-headings",
     9953    name: "Headings should only be defined once",
     9954    desc: "Headings should be defined only once.",
     9955    url: "https://github.com/CSSLint/csslint/wiki/Headings-should-only-be-defined-once",
     9956    browsers: "All",
     9957
     9958    // initialization
     9959    init: function(parser, reporter) {
     9960        "use strict";
     9961        var rule = this;
     9962
     9963        var headings = {
     9964            h1: 0,
     9965            h2: 0,
     9966            h3: 0,
     9967            h4: 0,
     9968            h5: 0,
     9969            h6: 0
     9970        };
     9971
     9972        parser.addListener("startrule", function(event) {
     9973            var selectors = event.selectors,
     9974                selector,
     9975                part,
     9976                pseudo,
     9977                i, j;
     9978
     9979            for (i=0; i < selectors.length; i++) {
     9980                selector = selectors[i];
     9981                part = selector.parts[selector.parts.length-1];
     9982
     9983                if (part.elementName && /(h[1-6])/i.test(part.elementName.toString())) {
     9984
     9985                    for (j=0; j < part.modifiers.length; j++) {
     9986                        if (part.modifiers[j].type === "pseudo") {
     9987                            pseudo = true;
     9988                            break;
     9989                        }
     9990                    }
     9991
     9992                    if (!pseudo) {
     9993                        headings[RegExp.$1]++;
     9994                        if (headings[RegExp.$1] > 1) {
     9995                            reporter.report("Heading (" + part.elementName + ") has already been defined.", part.line, part.col, rule);
     9996                        }
     9997                    }
     9998                }
     9999            }
     10000        });
     10001
     10002        parser.addListener("endstylesheet", function() {
     10003            var prop,
     10004                messages = [];
     10005
     10006            for (prop in headings) {
     10007                if (headings.hasOwnProperty(prop)) {
     10008                    if (headings[prop] > 1) {
     10009                        messages.push(headings[prop] + " " + prop + "s");
     10010                    }
     10011                }
     10012            }
     10013
     10014            if (messages.length) {
     10015                reporter.rollupWarn("You have " + messages.join(", ") + " defined in this stylesheet.", rule);
     10016            }
     10017        });
     10018    }
     10019
     10020});
     10021
     10022/*
     10023 * Rule: Don't use universal selector because it's slow.
     10024 */
     10025
     10026CSSLint.addRule({
     10027
     10028    // rule information
     10029    id: "universal-selector",
     10030    name: "Disallow universal selector",
     10031    desc: "The universal selector (*) is known to be slow.",
     10032    url: "https://github.com/CSSLint/csslint/wiki/Disallow-universal-selector",
     10033    browsers: "All",
     10034
     10035    // initialization
     10036    init: function(parser, reporter) {
     10037        "use strict";
     10038        var rule = this;
     10039
     10040        parser.addListener("startrule", function(event) {
     10041            var selectors = event.selectors,
     10042                selector,
     10043                part,
     10044                i;
     10045
     10046            for (i=0; i < selectors.length; i++) {
     10047                selector = selectors[i];
     10048
     10049                part = selector.parts[selector.parts.length-1];
     10050                if (part.elementName === "*") {
     10051                    reporter.report(rule.desc, part.line, part.col, rule);
     10052                }
     10053            }
     10054        });
     10055    }
     10056
     10057});
     10058
     10059/*
     10060 * Rule: Don't use unqualified attribute selectors because they're just like universal selectors.
     10061 */
     10062
     10063CSSLint.addRule({
     10064
     10065    // rule information
     10066    id: "unqualified-attributes",
     10067    name: "Disallow unqualified attribute selectors",
     10068    desc: "Unqualified attribute selectors are known to be slow.",
     10069    url: "https://github.com/CSSLint/csslint/wiki/Disallow-unqualified-attribute-selectors",
     10070    browsers: "All",
     10071
     10072    // initialization
     10073    init: function(parser, reporter) {
     10074        "use strict";
     10075
     10076        var rule = this;
     10077
     10078        parser.addListener("startrule", function(event) {
     10079
     10080            var selectors = event.selectors,
     10081                selectorContainsClassOrId = false,
     10082                selector,
     10083                part,
     10084                modifier,
     10085                i, k;
     10086
     10087            for (i=0; i < selectors.length; i++) {
     10088                selector = selectors[i];
     10089
     10090                part = selector.parts[selector.parts.length-1];
     10091                if (part.type === parser.SELECTOR_PART_TYPE) {
     10092                    for (k=0; k < part.modifiers.length; k++) {
     10093                        modifier = part.modifiers[k];
     10094
     10095                        if (modifier.type === "class" || modifier.type === "id") {
     10096                            selectorContainsClassOrId = true;
     10097                            break;
     10098                        }
     10099                    }
     10100
     10101                    if (!selectorContainsClassOrId) {
     10102                        for (k=0; k < part.modifiers.length; k++) {
     10103                            modifier = part.modifiers[k];
     10104                            if (modifier.type === "attribute" && (!part.elementName || part.elementName === "*")) {
     10105                                reporter.report(rule.desc, part.line, part.col, rule);
     10106                            }
     10107                        }
     10108                    }
     10109                }
     10110
     10111            }
     10112        });
     10113    }
     10114
     10115});
     10116
     10117/*
     10118 * Rule: When using a vendor-prefixed property, make sure to
     10119 * include the standard one.
     10120 */
     10121
     10122CSSLint.addRule({
     10123
     10124    // rule information
     10125    id: "vendor-prefix",
     10126    name: "Require standard property with vendor prefix",
     10127    desc: "When using a vendor-prefixed property, make sure to include the standard one.",
     10128    url: "https://github.com/CSSLint/csslint/wiki/Require-standard-property-with-vendor-prefix",
     10129    browsers: "All",
     10130
     10131    // initialization
     10132    init: function(parser, reporter) {
     10133        "use strict";
     10134        var rule = this,
     10135            properties,
     10136            num,
     10137            propertiesToCheck = {
     10138                "-webkit-border-radius": "border-radius",
     10139                "-webkit-border-top-left-radius": "border-top-left-radius",
     10140                "-webkit-border-top-right-radius": "border-top-right-radius",
     10141                "-webkit-border-bottom-left-radius": "border-bottom-left-radius",
     10142                "-webkit-border-bottom-right-radius": "border-bottom-right-radius",
     10143
     10144                "-o-border-radius": "border-radius",
     10145                "-o-border-top-left-radius": "border-top-left-radius",
     10146                "-o-border-top-right-radius": "border-top-right-radius",
     10147                "-o-border-bottom-left-radius": "border-bottom-left-radius",
     10148                "-o-border-bottom-right-radius": "border-bottom-right-radius",
     10149
     10150                "-moz-border-radius": "border-radius",
     10151                "-moz-border-radius-topleft": "border-top-left-radius",
     10152                "-moz-border-radius-topright": "border-top-right-radius",
     10153                "-moz-border-radius-bottomleft": "border-bottom-left-radius",
     10154                "-moz-border-radius-bottomright": "border-bottom-right-radius",
     10155
     10156                "-moz-column-count": "column-count",
     10157                "-webkit-column-count": "column-count",
     10158
     10159                "-moz-column-gap": "column-gap",
     10160                "-webkit-column-gap": "column-gap",
     10161
     10162                "-moz-column-rule": "column-rule",
     10163                "-webkit-column-rule": "column-rule",
     10164
     10165                "-moz-column-rule-style": "column-rule-style",
     10166                "-webkit-column-rule-style": "column-rule-style",
     10167
     10168                "-moz-column-rule-color": "column-rule-color",
     10169                "-webkit-column-rule-color": "column-rule-color",
     10170
     10171                "-moz-column-rule-width": "column-rule-width",
     10172                "-webkit-column-rule-width": "column-rule-width",
     10173
     10174                "-moz-column-width": "column-width",
     10175                "-webkit-column-width": "column-width",
     10176
     10177                "-webkit-column-span": "column-span",
     10178                "-webkit-columns": "columns",
     10179
     10180                "-moz-box-shadow": "box-shadow",
     10181                "-webkit-box-shadow": "box-shadow",
     10182
     10183                "-moz-transform": "transform",
     10184                "-webkit-transform": "transform",
     10185                "-o-transform": "transform",
     10186                "-ms-transform": "transform",
     10187
     10188                "-moz-transform-origin": "transform-origin",
     10189                "-webkit-transform-origin": "transform-origin",
     10190                "-o-transform-origin": "transform-origin",
     10191                "-ms-transform-origin": "transform-origin",
     10192
     10193                "-moz-box-sizing": "box-sizing",
     10194                "-webkit-box-sizing": "box-sizing"
     10195            };
     10196
     10197        // event handler for beginning of rules
     10198        function startRule() {
     10199            properties = {};
     10200            num = 1;
     10201        }
     10202
     10203        // event handler for end of rules
     10204        function endRule() {
     10205            var prop,
     10206                i,
     10207                len,
     10208                needed,
     10209                actual,
     10210                needsStandard = [];
     10211
     10212            for (prop in properties) {
     10213                if (propertiesToCheck[prop]) {
     10214                    needsStandard.push({
     10215                        actual: prop,
     10216                        needed: propertiesToCheck[prop]
     10217                    });
     10218                }
     10219            }
     10220
     10221            for (i=0, len=needsStandard.length; i < len; i++) {
     10222                needed = needsStandard[i].needed;
     10223                actual = needsStandard[i].actual;
     10224
     10225                if (!properties[needed]) {
     10226                    reporter.report("Missing standard property '" + needed + "' to go along with '" + actual + "'.", properties[actual][0].name.line, properties[actual][0].name.col, rule);
     10227                } else {
     10228                    // make sure standard property is last
     10229                    if (properties[needed][0].pos < properties[actual][0].pos) {
     10230                        reporter.report("Standard property '" + needed + "' should come after vendor-prefixed property '" + actual + "'.", properties[actual][0].name.line, properties[actual][0].name.col, rule);
     10231                    }
     10232                }
     10233            }
     10234
     10235        }
     10236
     10237        parser.addListener("startrule", startRule);
     10238        parser.addListener("startfontface", startRule);
     10239        parser.addListener("startpage", startRule);
     10240        parser.addListener("startpagemargin", startRule);
     10241        parser.addListener("startkeyframerule", startRule);
     10242        parser.addListener("startviewport", startRule);
     10243
     10244        parser.addListener("property", function(event) {
     10245            var name = event.property.text.toLowerCase();
     10246
     10247            if (!properties[name]) {
     10248                properties[name] = [];
     10249            }
     10250
     10251            properties[name].push({
     10252                name: event.property,
     10253                value: event.value,
     10254                pos: num++
     10255            });
     10256        });
     10257
     10258        parser.addListener("endrule", endRule);
     10259        parser.addListener("endfontface", endRule);
     10260        parser.addListener("endpage", endRule);
     10261        parser.addListener("endpagemargin", endRule);
     10262        parser.addListener("endkeyframerule", endRule);
     10263        parser.addListener("endviewport", endRule);
     10264    }
     10265
     10266});
     10267
     10268/*
     10269 * Rule: You don't need to specify units when a value is 0.
     10270 */
     10271
     10272CSSLint.addRule({
     10273
     10274    // rule information
     10275    id: "zero-units",
     10276    name: "Disallow units for 0 values",
     10277    desc: "You don't need to specify units when a value is 0.",
     10278    url: "https://github.com/CSSLint/csslint/wiki/Disallow-units-for-zero-values",
     10279    browsers: "All",
     10280
     10281    // initialization
     10282    init: function(parser, reporter) {
     10283        "use strict";
     10284        var rule = this;
     10285
     10286        // count how many times "float" is used
     10287        parser.addListener("property", function(event) {
     10288            var parts = event.value.parts,
     10289                i = 0,
     10290                len = parts.length;
     10291
     10292            while (i < len) {
     10293                if ((parts[i].units || parts[i].type === "percentage") && parts[i].value === 0 && parts[i].type !== "time") {
     10294                    reporter.report("Values of 0 shouldn't have units specified.", parts[i].line, parts[i].col, rule);
     10295                }
     10296                i++;
     10297            }
     10298
     10299        });
     10300
     10301    }
     10302
     10303});
     10304
     10305(function() {
     10306    "use strict";
     10307
     10308    /**
     10309     * Replace special characters before write to output.
     10310     *
     10311     * Rules:
     10312     *  - single quotes is the escape sequence for double-quotes
     10313     *  - &amp; is the escape sequence for &
     10314     *  - &lt; is the escape sequence for <
     10315     *  - &gt; is the escape sequence for >
     10316     *
     10317     * @param {String} message to escape
     10318     * @return escaped message as {String}
     10319     */
     10320    var xmlEscape = function(str) {
     10321        if (!str || str.constructor !== String) {
     10322            return "";
     10323        }
     10324
     10325        return str.replace(/["&><]/g, function(match) {
     10326            switch (match) {
     10327                case "\"":
     10328                    return "&quot;";
     10329                case "&":
     10330                    return "&amp;";
     10331                case "<":
     10332                    return "&lt;";
     10333                case ">":
     10334                    return "&gt;";
     10335            }
     10336        });
     10337    };
     10338
     10339    CSSLint.addFormatter({
     10340        // format information
     10341        id: "checkstyle-xml",
     10342        name: "Checkstyle XML format",
     10343
     10344        /**
     10345         * Return opening root XML tag.
     10346         * @return {String} to prepend before all results
     10347         */
     10348        startFormat: function() {
     10349            return "<?xml version=\"1.0\" encoding=\"utf-8\"?><checkstyle>";
     10350        },
     10351
     10352        /**
     10353         * Return closing root XML tag.
     10354         * @return {String} to append after all results
     10355         */
     10356        endFormat: function() {
     10357            return "</checkstyle>";
     10358        },
     10359
     10360        /**
     10361         * Returns message when there is a file read error.
     10362         * @param {String} filename The name of the file that caused the error.
     10363         * @param {String} message The error message
     10364         * @return {String} The error message.
     10365         */
     10366        readError: function(filename, message) {
     10367            return "<file name=\"" + xmlEscape(filename) + "\"><error line=\"0\" column=\"0\" severty=\"error\" message=\"" + xmlEscape(message) + "\"></error></file>";
     10368        },
     10369
     10370        /**
     10371         * Given CSS Lint results for a file, return output for this format.
     10372         * @param results {Object} with error and warning messages
     10373         * @param filename {String} relative file path
     10374         * @param options {Object} (UNUSED for now) specifies special handling of output
     10375         * @return {String} output for results
     10376         */
     10377        formatResults: function(results, filename/*, options*/) {
     10378            var messages = results.messages,
     10379                output = [];
     10380
     10381            /**
     10382             * Generate a source string for a rule.
     10383             * Checkstyle source strings usually resemble Java class names e.g
     10384             * net.csslint.SomeRuleName
     10385             * @param {Object} rule
     10386             * @return rule source as {String}
     10387             */
     10388            var generateSource = function(rule) {
     10389                if (!rule || !("name" in rule)) {
     10390                    return "";
     10391                }
     10392                return "net.csslint." + rule.name.replace(/\s/g, "");
     10393            };
     10394
     10395
     10396            if (messages.length > 0) {
     10397                output.push("<file name=\""+filename+"\">");
     10398                CSSLint.Util.forEach(messages, function (message) {
     10399                    // ignore rollups for now
     10400                    if (!message.rollup) {
     10401                        output.push("<error line=\"" + message.line + "\" column=\"" + message.col + "\" severity=\"" + message.type + "\"" +
     10402                          " message=\"" + xmlEscape(message.message) + "\" source=\"" + generateSource(message.rule) +"\"/>");
     10403                    }
     10404                });
     10405                output.push("</file>");
     10406            }
     10407
     10408            return output.join("");
     10409        }
     10410    });
     10411
     10412}());
     10413
     10414CSSLint.addFormatter({
     10415    // format information
     10416    id: "compact",
     10417    name: "Compact, 'porcelain' format",
     10418
     10419    /**
     10420     * Return content to be printed before all file results.
     10421     * @return {String} to prepend before all results
     10422     */
     10423    startFormat: function() {
     10424        "use strict";
     10425        return "";
     10426    },
     10427
     10428    /**
     10429     * Return content to be printed after all file results.
     10430     * @return {String} to append after all results
     10431     */
     10432    endFormat: function() {
     10433        "use strict";
     10434        return "";
     10435    },
     10436
     10437    /**
     10438     * Given CSS Lint results for a file, return output for this format.
     10439     * @param results {Object} with error and warning messages
     10440     * @param filename {String} relative file path
     10441     * @param options {Object} (Optional) specifies special handling of output
     10442     * @return {String} output for results
     10443     */
     10444    formatResults: function(results, filename, options) {
     10445        "use strict";
     10446        var messages = results.messages,
     10447            output = "";
     10448        options = options || {};
     10449
     10450        /**
     10451         * Capitalize and return given string.
     10452         * @param str {String} to capitalize
     10453         * @return {String} capitalized
     10454         */
     10455        var capitalize = function(str) {
     10456            return str.charAt(0).toUpperCase() + str.slice(1);
     10457        };
     10458
     10459        if (messages.length === 0) {
     10460            return options.quiet ? "" : filename + ": Lint Free!";
     10461        }
     10462
     10463        CSSLint.Util.forEach(messages, function(message) {
     10464            if (message.rollup) {
     10465                output += filename + ": " + capitalize(message.type) + " - " + message.message + " (" + message.rule.id + ")\n";
     10466            } else {
     10467                output += filename + ": line " + message.line +
     10468                    ", col " + message.col + ", " + capitalize(message.type) + " - " + message.message + " (" + message.rule.id + ")\n";
     10469            }
     10470        });
     10471
     10472        return output;
     10473    }
     10474});
     10475
     10476CSSLint.addFormatter({
     10477    // format information
     10478    id: "csslint-xml",
     10479    name: "CSSLint XML format",
     10480
     10481    /**
     10482     * Return opening root XML tag.
     10483     * @return {String} to prepend before all results
     10484     */
     10485    startFormat: function() {
     10486        "use strict";
     10487        return "<?xml version=\"1.0\" encoding=\"utf-8\"?><csslint>";
     10488    },
     10489
     10490    /**
     10491     * Return closing root XML tag.
     10492     * @return {String} to append after all results
     10493     */
     10494    endFormat: function() {
     10495        "use strict";
     10496        return "</csslint>";
     10497    },
     10498
     10499    /**
     10500     * Given CSS Lint results for a file, return output for this format.
     10501     * @param results {Object} with error and warning messages
     10502     * @param filename {String} relative file path
     10503     * @param options {Object} (UNUSED for now) specifies special handling of output
     10504     * @return {String} output for results
     10505     */
     10506    formatResults: function(results, filename/*, options*/) {
     10507        "use strict";
     10508        var messages = results.messages,
     10509            output = [];
     10510
     10511        /**
     10512         * Replace special characters before write to output.
     10513         *
     10514         * Rules:
     10515         *  - single quotes is the escape sequence for double-quotes
     10516         *  - &amp; is the escape sequence for &
     10517         *  - &lt; is the escape sequence for <
     10518         *  - &gt; is the escape sequence for >
     10519         *
     10520         * @param {String} message to escape
     10521         * @return escaped message as {String}
     10522         */
     10523        var escapeSpecialCharacters = function(str) {
     10524            if (!str || str.constructor !== String) {
     10525                return "";
     10526            }
     10527            return str.replace(/"/g, "'").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
     10528        };
     10529
     10530        if (messages.length > 0) {
     10531            output.push("<file name=\""+filename+"\">");
     10532            CSSLint.Util.forEach(messages, function (message) {
     10533                if (message.rollup) {
     10534                    output.push("<issue severity=\"" + message.type + "\" reason=\"" + escapeSpecialCharacters(message.message) + "\" evidence=\"" + escapeSpecialCharacters(message.evidence) + "\"/>");
     10535                } else {
     10536                    output.push("<issue line=\"" + message.line + "\" char=\"" + message.col + "\" severity=\"" + message.type + "\"" +
     10537                        " reason=\"" + escapeSpecialCharacters(message.message) + "\" evidence=\"" + escapeSpecialCharacters(message.evidence) + "\"/>");
     10538                }
     10539            });
     10540            output.push("</file>");
     10541        }
     10542
     10543        return output.join("");
     10544    }
     10545});
     10546
     10547/* globals JSON: true */
     10548
     10549CSSLint.addFormatter({
     10550    // format information
     10551    id: "json",
     10552    name: "JSON",
     10553
     10554    /**
     10555     * Return content to be printed before all file results.
     10556     * @return {String} to prepend before all results
     10557     */
     10558    startFormat: function() {
     10559        "use strict";
     10560        this.json = [];
     10561        return "";
     10562    },
     10563
     10564    /**
     10565     * Return content to be printed after all file results.
     10566     * @return {String} to append after all results
     10567     */
     10568    endFormat: function() {
     10569        "use strict";
     10570        var ret = "";
     10571        if (this.json.length > 0) {
     10572            if (this.json.length === 1) {
     10573                ret = JSON.stringify(this.json[0]);
     10574            } else {
     10575                ret = JSON.stringify(this.json);
     10576            }
     10577        }
     10578        return ret;
     10579    },
     10580
     10581    /**
     10582     * Given CSS Lint results for a file, return output for this format.
     10583     * @param results {Object} with error and warning messages
     10584     * @param filename {String} relative file path (Unused)
     10585     * @return {String} output for results
     10586     */
     10587    formatResults: function(results, filename, options) {
     10588        "use strict";
     10589        if (results.messages.length > 0 || !options.quiet) {
     10590            this.json.push({
     10591                filename: filename,
     10592                messages: results.messages,
     10593                stats: results.stats
     10594            });
     10595        }
     10596        return "";
     10597    }
     10598});
     10599
     10600CSSLint.addFormatter({
     10601    // format information
     10602    id: "junit-xml",
     10603    name: "JUNIT XML format",
     10604
     10605    /**
     10606     * Return opening root XML tag.
     10607     * @return {String} to prepend before all results
     10608     */
     10609    startFormat: function() {
     10610        "use strict";
     10611        return "<?xml version=\"1.0\" encoding=\"utf-8\"?><testsuites>";
     10612    },
     10613
     10614    /**
     10615     * Return closing root XML tag.
     10616     * @return {String} to append after all results
     10617     */
     10618    endFormat: function() {
     10619        "use strict";
     10620        return "</testsuites>";
     10621    },
     10622
     10623    /**
     10624     * Given CSS Lint results for a file, return output for this format.
     10625     * @param results {Object} with error and warning messages
     10626     * @param filename {String} relative file path
     10627     * @param options {Object} (UNUSED for now) specifies special handling of output
     10628     * @return {String} output for results
     10629     */
     10630    formatResults: function(results, filename/*, options*/) {
     10631        "use strict";
     10632
     10633        var messages = results.messages,
     10634            output = [],
     10635            tests = {
     10636                "error": 0,
     10637                "failure": 0
     10638            };
     10639
     10640        /**
     10641         * Generate a source string for a rule.
     10642         * JUNIT source strings usually resemble Java class names e.g
     10643         * net.csslint.SomeRuleName
     10644         * @param {Object} rule
     10645         * @return rule source as {String}
     10646         */
     10647        var generateSource = function(rule) {
     10648            if (!rule || !("name" in rule)) {
     10649                return "";
     10650            }
     10651            return "net.csslint." + rule.name.replace(/\s/g, "");
     10652        };
     10653
     10654        /**
     10655         * Replace special characters before write to output.
     10656         *
     10657         * Rules:
     10658         *  - single quotes is the escape sequence for double-quotes
     10659         *  - &lt; is the escape sequence for <
     10660         *  - &gt; is the escape sequence for >
     10661         *
     10662         * @param {String} message to escape
     10663         * @return escaped message as {String}
     10664         */
     10665        var escapeSpecialCharacters = function(str) {
     10666
     10667            if (!str || str.constructor !== String) {
     10668                return "";
     10669            }
     10670
     10671            return str.replace(/"/g, "'").replace(/</g, "&lt;").replace(/>/g, "&gt;");
     10672
     10673        };
     10674
     10675        if (messages.length > 0) {
     10676
     10677            messages.forEach(function (message) {
     10678
     10679                // since junit has no warning class
     10680                // all issues as errors
     10681                var type = message.type === "warning" ? "error" : message.type;
     10682
     10683                // ignore rollups for now
     10684                if (!message.rollup) {
     10685
     10686                    // build the test case separately, once joined
     10687                    // we'll add it to a custom array filtered by type
     10688                    output.push("<testcase time=\"0\" name=\"" + generateSource(message.rule) + "\">");
     10689                    output.push("<" + type + " message=\"" + escapeSpecialCharacters(message.message) + "\"><![CDATA[" + message.line + ":" + message.col + ":" + escapeSpecialCharacters(message.evidence) + "]]></" + type + ">");
     10690                    output.push("</testcase>");
     10691
     10692                    tests[type] += 1;
     10693
     10694                }
     10695
     10696            });
     10697
     10698            output.unshift("<testsuite time=\"0\" tests=\"" + messages.length + "\" skipped=\"0\" errors=\"" + tests.error + "\" failures=\"" + tests.failure + "\" package=\"net.csslint\" name=\"" + filename + "\">");
     10699            output.push("</testsuite>");
     10700
     10701        }
     10702
     10703        return output.join("");
     10704
     10705    }
     10706});
     10707
     10708CSSLint.addFormatter({
     10709    // format information
     10710    id: "lint-xml",
     10711    name: "Lint XML format",
     10712
     10713    /**
     10714     * Return opening root XML tag.
     10715     * @return {String} to prepend before all results
     10716     */
     10717    startFormat: function() {
     10718        "use strict";
     10719        return "<?xml version=\"1.0\" encoding=\"utf-8\"?><lint>";
     10720    },
     10721
     10722    /**
     10723     * Return closing root XML tag.
     10724     * @return {String} to append after all results
     10725     */
     10726    endFormat: function() {
     10727        "use strict";
     10728        return "</lint>";
     10729    },
     10730
     10731    /**
     10732     * Given CSS Lint results for a file, return output for this format.
     10733     * @param results {Object} with error and warning messages
     10734     * @param filename {String} relative file path
     10735     * @param options {Object} (UNUSED for now) specifies special handling of output
     10736     * @return {String} output for results
     10737     */
     10738    formatResults: function(results, filename/*, options*/) {
     10739        "use strict";
     10740        var messages = results.messages,
     10741            output = [];
     10742
     10743        /**
     10744         * Replace special characters before write to output.
     10745         *
     10746         * Rules:
     10747         *  - single quotes is the escape sequence for double-quotes
     10748         *  - &amp; is the escape sequence for &
     10749         *  - &lt; is the escape sequence for <
     10750         *  - &gt; is the escape sequence for >
     10751         *
     10752         * @param {String} message to escape
     10753         * @return escaped message as {String}
     10754         */
     10755        var escapeSpecialCharacters = function(str) {
     10756            if (!str || str.constructor !== String) {
     10757                return "";
     10758            }
     10759            return str.replace(/"/g, "'").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
     10760        };
     10761
     10762        if (messages.length > 0) {
     10763
     10764            output.push("<file name=\""+filename+"\">");
     10765            CSSLint.Util.forEach(messages, function (message) {
     10766                if (message.rollup) {
     10767                    output.push("<issue severity=\"" + message.type + "\" reason=\"" + escapeSpecialCharacters(message.message) + "\" evidence=\"" + escapeSpecialCharacters(message.evidence) + "\"/>");
     10768                } else {
     10769                    var rule = "";
     10770                    if (message.rule && message.rule.id) {
     10771                        rule = "rule=\"" + escapeSpecialCharacters(message.rule.id) + "\" ";
     10772                    }
     10773                    output.push("<issue " + rule + "line=\"" + message.line + "\" char=\"" + message.col + "\" severity=\"" + message.type + "\"" +
     10774                        " reason=\"" + escapeSpecialCharacters(message.message) + "\" evidence=\"" + escapeSpecialCharacters(message.evidence) + "\"/>");
     10775                }
     10776            });
     10777            output.push("</file>");
     10778        }
     10779
     10780        return output.join("");
     10781    }
     10782});
     10783
     10784CSSLint.addFormatter({
     10785    // format information
     10786    id: "text",
     10787    name: "Plain Text",
     10788
     10789    /**
     10790     * Return content to be printed before all file results.
     10791     * @return {String} to prepend before all results
     10792     */
     10793    startFormat: function() {
     10794        "use strict";
     10795        return "";
     10796    },
     10797
     10798    /**
     10799     * Return content to be printed after all file results.
     10800     * @return {String} to append after all results
     10801     */
     10802    endFormat: function() {
     10803        "use strict";
     10804        return "";
     10805    },
     10806
     10807    /**
     10808     * Given CSS Lint results for a file, return output for this format.
     10809     * @param results {Object} with error and warning messages
     10810     * @param filename {String} relative file path
     10811     * @param options {Object} (Optional) specifies special handling of output
     10812     * @return {String} output for results
     10813     */
     10814    formatResults: function(results, filename, options) {
     10815        "use strict";
     10816        var messages = results.messages,
     10817            output = "";
     10818        options = options || {};
     10819
     10820        if (messages.length === 0) {
     10821            return options.quiet ? "" : "\n\ncsslint: No errors in " + filename + ".";
     10822        }
     10823
     10824        output = "\n\ncsslint: There ";
     10825        if (messages.length === 1) {
     10826            output += "is 1 problem";
     10827        } else {
     10828            output += "are " + messages.length + " problems";
     10829        }
     10830        output += " in " + filename + ".";
     10831
     10832        var pos = filename.lastIndexOf("/"),
     10833            shortFilename = filename;
     10834
     10835        if (pos === -1) {
     10836            pos = filename.lastIndexOf("\\");
     10837        }
     10838        if (pos > -1) {
     10839            shortFilename = filename.substring(pos+1);
     10840        }
     10841
     10842        CSSLint.Util.forEach(messages, function (message, i) {
     10843            output = output + "\n\n" + shortFilename;
     10844            if (message.rollup) {
     10845                output += "\n" + (i+1) + ": " + message.type;
     10846                output += "\n" + message.message;
     10847            } else {
     10848                output += "\n" + (i+1) + ": " + message.type + " at line " + message.line + ", col " + message.col;
     10849                output += "\n" + message.message;
     10850                output += "\n" + message.evidence;
     10851            }
     10852        });
     10853
     10854        return output;
     10855    }
     10856});
     10857
     10858return CSSLint;
    1085910859})();
     10860 No newline at end of file