58 changes: 33 additions & 25 deletions src/rules/display-property-grouping.js
@@ -1,21 +1,23 @@
/*
* Rule: Certain properties don't play well with certain display values.
* Rule: Certain properties don't play well with certain display values.
* - float should not be used with inline-block
* - height, width, margin-top, margin-bottom, float should not be used with inline
* - vertical-align should not be used with block
* - margin, float should not be used with table-*
*/
/*global CSSLint*/

CSSLint.addRule({

//rule information
// rule information
id: "display-property-grouping",
name: "Require properties appropriate for display",
desc: "Certain properties shouldn't be used with certain display property values.",
url: "https://github.com/CSSLint/csslint/wiki/Require-properties-appropriate-for-display",
browsers: "All",

//initialization
init: function(parser, reporter){
// initialization
init: function(parser, reporter) {
"use strict";
var rule = this;

var propertiesToCheck = {
Expand All @@ -37,47 +39,47 @@ CSSLint.addRule({
},
properties;

function reportProperty(name, display, msg){
if (properties[name]){
if (typeof propertiesToCheck[name] != "string" || properties[name].value.toLowerCase() != propertiesToCheck[name]){
function reportProperty(name, display, msg) {
if (properties[name]) {
if (typeof propertiesToCheck[name] !== "string" || properties[name].value.toLowerCase() !== propertiesToCheck[name]) {
reporter.report(msg || name + " can't be used with display: " + display + ".", properties[name].line, properties[name].col, rule);
}
}
}
function startRule(){

function startRule() {
properties = {};
}

function endRule(){
function endRule() {

var display = properties.display ? properties.display.value : null;
if (display){
switch(display){
if (display) {
switch (display) {

case "inline":
//height, width, margin-top, margin-bottom, float should not be used with inline
// height, width, margin-top, margin-bottom, float should not be used with inline
reportProperty("height", display);
reportProperty("width", display);
reportProperty("margin", display);
reportProperty("margin-top", display);
reportProperty("margin-bottom", display);
reportProperty("margin-bottom", display);
reportProperty("float", display, "display:inline has no effect on floated elements (but may be used to fix the IE6 double-margin bug).");
break;

case "block":
//vertical-align should not be used with block
// vertical-align should not be used with block
reportProperty("vertical-align", display);
break;

case "inline-block":
//float should not be used with inline-block
// float should not be used with inline-block
reportProperty("float", display);
break;

default:
//margin, float should not be used with table
if (display.indexOf("table-") === 0){
// margin, float should not be used with table
if (display.indexOf("table-") === 0) {
reportProperty("margin", display);
reportProperty("margin-left", display);
reportProperty("margin-right", display);
Expand All @@ -86,23 +88,28 @@ CSSLint.addRule({
reportProperty("float", display);
}

//otherwise do nothing
// otherwise do nothing
}
}

}

parser.addListener("startrule", startRule);
parser.addListener("startfontface", startRule);
parser.addListener("startkeyframerule", startRule);
parser.addListener("startpagemargin", startRule);
parser.addListener("startpage", startRule);
parser.addListener("startviewport", startRule);

parser.addListener("property", function(event){
parser.addListener("property", function(event) {
var name = event.property.text.toLowerCase();

if (propertiesToCheck[name]){
properties[name] = { value: event.value.text, line: event.property.line, col: event.property.col };
if (propertiesToCheck[name]) {
properties[name] = {
value: event.value.text,
line: event.property.line,
col: event.property.col
};
}
});

Expand All @@ -111,7 +118,8 @@ CSSLint.addRule({
parser.addListener("endkeyframerule", endRule);
parser.addListener("endpagemargin", endRule);
parser.addListener("endpage", endRule);
parser.addListener("endviewport", endRule);

}

});
});
21 changes: 11 additions & 10 deletions src/rules/duplicate-background-images.js
@@ -1,37 +1,38 @@
/*
* Rule: Disallow duplicate background-images (using url).
*/
/*global CSSLint*/

CSSLint.addRule({

//rule information
// rule information
id: "duplicate-background-images",
name: "Disallow duplicate background images",
desc: "Every background-image should be unique. Use a common class for e.g. sprites.",
url: "https://github.com/CSSLint/csslint/wiki/Disallow-duplicate-background-images",
browsers: "All",

//initialization
init: function(parser, reporter){
// initialization
init: function(parser, reporter) {
"use strict";
var rule = this,
stack = {};

parser.addListener("property", function(event){
parser.addListener("property", function(event) {
var name = event.property.text,
value = event.value,
i, len;

if (name.match(/background/i)) {
for (i=0, len=value.parts.length; i < len; i++) {
if (value.parts[i].type == 'uri') {
if (typeof stack[value.parts[i].uri] === 'undefined') {
if (value.parts[i].type === "uri") {
if (typeof stack[value.parts[i].uri] === "undefined") {
stack[value.parts[i].uri] = event;
}
else {
} else {
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);
}
}
}
}
});
}
});
});
41 changes: 22 additions & 19 deletions src/rules/duplicate-properties.js
Expand Up @@ -2,45 +2,48 @@
* Rule: Duplicate properties must appear one after the other. If an already-defined
* property appears somewhere else in the rule, then it's likely an error.
*/
/*global CSSLint*/

CSSLint.addRule({

//rule information
// rule information
id: "duplicate-properties",
name: "Disallow duplicate properties",
desc: "Duplicate properties must appear one after the other.",
url: "https://github.com/CSSLint/csslint/wiki/Disallow-duplicate-properties",
browsers: "All",

//initialization
init: function(parser, reporter){
// initialization
init: function(parser, reporter) {
"use strict";
var rule = this,
properties,
lastProperty;
function startRule(event){
properties = {};
lastProperty;

function startRule() {
properties = {};
}

parser.addListener("startrule", startRule);
parser.addListener("startfontface", startRule);
parser.addListener("startpage", startRule);
parser.addListener("startpagemargin", startRule);
parser.addListener("startkeyframerule", startRule);

parser.addListener("property", function(event){
parser.addListener("startkeyframerule", startRule);
parser.addListener("startviewport", startRule);

parser.addListener("property", function(event) {
var property = event.property,
name = property.text.toLowerCase();
if (properties[name] && (lastProperty != name || properties[name] == event.value.text)){

if (properties[name] && (lastProperty !== name || properties[name] === event.value.text)) {
reporter.report("Duplicate property '" + event.property + "' found.", event.line, event.col, rule);
}

properties[name] = event.value.text;
lastProperty = name;

});


}

});
});
21 changes: 12 additions & 9 deletions src/rules/empty-rules.js
@@ -1,34 +1,37 @@
/*
* Rule: Style rules without any properties defined should be removed.
*/
/*global CSSLint*/

CSSLint.addRule({

//rule information
// rule information
id: "empty-rules",
name: "Disallow empty rules",
desc: "Rules without any properties specified should be removed.",
url: "https://github.com/CSSLint/csslint/wiki/Disallow-empty-rules",
browsers: "All",

//initialization
init: function(parser, reporter){
// initialization
init: function(parser, reporter) {
"use strict";
var rule = this,
count = 0;

parser.addListener("startrule", function(){
parser.addListener("startrule", function() {
count=0;
});

parser.addListener("property", function(){
parser.addListener("property", function() {
count++;
});

parser.addListener("endrule", function(event){
parser.addListener("endrule", function(event) {
var selectors = event.selectors;
if (count === 0){

if (count === 0) {
reporter.report("Rule is empty.", selectors[0].line, selectors[0].col, rule);
}
});
}

});
});
13 changes: 7 additions & 6 deletions src/rules/errors.js
@@ -1,23 +1,24 @@
/*
* Rule: There should be no syntax errors. (Duh.)
*/
/*global CSSLint*/

CSSLint.addRule({

//rule information
// rule information
id: "errors",
name: "Parsing Errors",
desc: "This rule looks for recoverable syntax errors.",
browsers: "All",

//initialization
init: function(parser, reporter){
// initialization
init: function(parser, reporter) {
"use strict";
var rule = this;

parser.addListener("error", function(event){
parser.addListener("error", function(event) {
reporter.error(event.message, event.line, event.col, rule);
});

}

});
});
59 changes: 29 additions & 30 deletions src/rules/fallback-colors.js
@@ -1,15 +1,15 @@

/*global CSSLint*/
CSSLint.addRule({

//rule information
// rule information
id: "fallback-colors",
name: "Require fallback colors",
desc: "For older browsers that don't support RGBA, HSL, or HSLA, provide a fallback color.",
url: "https://github.com/CSSLint/csslint/wiki/Require-fallback-colors",
browsers: "IE6,IE7,IE8",

//initialization
init: function(parser, reporter){
// initialization
init: function(parser, reporter) {
"use strict";
var rule = this,
lastProperty,
propertiesToCheck = {
Expand All @@ -26,52 +26,51 @@ CSSLint.addRule({
"border-bottom": 1,
"border-left": 1,
"background-color": 1
},
properties;

function startRule(event){
properties = {};
lastProperty = null;
};

function startRule() {
lastProperty = null;
}

parser.addListener("startrule", startRule);
parser.addListener("startfontface", startRule);
parser.addListener("startpage", startRule);
parser.addListener("startpagemargin", startRule);
parser.addListener("startkeyframerule", startRule);

parser.addListener("property", function(event){
parser.addListener("startkeyframerule", startRule);
parser.addListener("startviewport", startRule);

parser.addListener("property", function(event) {
var property = event.property,
name = property.text.toLowerCase(),
parts = event.value.parts,
i = 0,
i = 0,
colorType = "",
len = parts.length;
if(propertiesToCheck[name]){
while(i < len){
if (parts[i].type == "color"){
if ("alpha" in parts[i] || "hue" in parts[i]){
if (/([^\)]+)\(/.test(parts[i])){
len = parts.length;

if (propertiesToCheck[name]) {
while (i < len) {
if (parts[i].type === "color") {
if ("alpha" in parts[i] || "hue" in parts[i]) {

if (/([^\)]+)\(/.test(parts[i])) {
colorType = RegExp.$1.toUpperCase();
}
if (!lastProperty || (lastProperty.property.text.toLowerCase() != name || lastProperty.colorType != "compat")){

if (!lastProperty || (lastProperty.property.text.toLowerCase() !== name || lastProperty.colorType !== "compat")) {
reporter.report("Fallback " + name + " (hex or RGB) should precede " + colorType + " " + name + ".", event.line, event.col, rule);
}
} else {
event.colorType = "compat";
}
}

i++;
}
}

lastProperty = event;
});
});

}

});
});
30 changes: 17 additions & 13 deletions src/rules/floats.js
Expand Up @@ -2,35 +2,39 @@
* Rule: You shouldn't use more than 10 floats. If you do, there's probably
* room for some abstraction.
*/
/*global CSSLint*/

CSSLint.addRule({

//rule information
// rule information
id: "floats",
name: "Disallow too many floats",
desc: "This rule tests if the float property is used too many times",
url: "https://github.com/CSSLint/csslint/wiki/Disallow-too-many-floats",
browsers: "All",

//initialization
init: function(parser, reporter){
// initialization
init: function(parser, reporter) {
"use strict";
var rule = this;
var count = 0;

//count how many times "float" is used
parser.addListener("property", function(event){
if (event.property.text.toLowerCase() == "float" &&
event.value.text.toLowerCase() != "none"){
count++;
// count how many times "float" is used
parser.addListener("property", function(event) {
if (!reporter.isIgnored(event.property.line)) {
if (event.property.text.toLowerCase() === "float" &&
event.value.text.toLowerCase() !== "none") {
count++;
}
}
});

//report the results
parser.addListener("endstylesheet", function(){
// report the results
parser.addListener("endstylesheet", function() {
reporter.stat("floats", count);
if (count >= 10){
if (count >= 10) {
reporter.rollupWarn("Too many floats (" + count + "), you're probably using them for layout. Consider using a grid system instead.", rule);
}
});
}

});
});
22 changes: 13 additions & 9 deletions src/rules/font-faces.js
@@ -1,30 +1,34 @@
/*
* Rule: Avoid too many @font-face declarations in the same stylesheet.
*/
/*global CSSLint*/

CSSLint.addRule({

//rule information
// rule information
id: "font-faces",
name: "Don't use too many web fonts",
desc: "Too many different web fonts in the same stylesheet.",
url: "https://github.com/CSSLint/csslint/wiki/Don%27t-use-too-many-web-fonts",
browsers: "All",

//initialization
init: function(parser, reporter){
// initialization
init: function(parser, reporter) {
"use strict";
var rule = this,
count = 0;


parser.addListener("startfontface", function(){
count++;
parser.addListener("startfontface", function(event) {
if (!reporter.isIgnored(event.line)) {
count++;
}
});

parser.addListener("endstylesheet", function(){
if (count > 5){
parser.addListener("endstylesheet", function() {
if (count > 5) {
reporter.rollupWarn("Too many @font-face declarations (" + count + ").", rule);
}
});
}

});
});
27 changes: 15 additions & 12 deletions src/rules/font-sizes.js
Expand Up @@ -2,34 +2,37 @@
* Rule: You shouldn't need more than 9 font-size declarations.
*/

/*global CSSLint*/
CSSLint.addRule({

//rule information
// rule information
id: "font-sizes",
name: "Disallow too many font sizes",
desc: "Checks the number of font-size declarations.",
url: "https://github.com/CSSLint/csslint/wiki/Don%27t-use-too-many-font-size-declarations",
browsers: "All",

//initialization
init: function(parser, reporter){
// initialization
init: function(parser, reporter) {
"use strict";
var rule = this,
count = 0;

//check for use of "font-size"
parser.addListener("property", function(event){
if (event.property == "font-size"){
count++;
// check for use of "font-size"
parser.addListener("property", function(event) {
if (!reporter.isIgnored(event.property.line)) {
if (event.property.toString() === "font-size") {
count++;
}
}
});

//report the results
parser.addListener("endstylesheet", function(){
// report the results
parser.addListener("endstylesheet", function() {
reporter.stat("font-sizes", count);
if (count >= 10){
if (count >= 10) {
reporter.rollupWarn("Too many font-size declarations (" + count + "), abstraction needed.", rule);
}
});
}

});
});
34 changes: 18 additions & 16 deletions src/rules/gradients.js
@@ -1,21 +1,23 @@
/*
* Rule: When using a vendor-prefixed gradient, make sure to use them all.
*/
/*global CSSLint*/

CSSLint.addRule({

//rule information
// rule information
id: "gradients",
name: "Require all gradient definitions",
desc: "When using a vendor-prefixed gradient, make sure to use them all.",
url: "https://github.com/CSSLint/csslint/wiki/Require-all-gradient-definitions",
browsers: "All",

//initialization
init: function(parser, reporter){
// initialization
init: function(parser, reporter) {
"use strict";
var rule = this,
gradients;

parser.addListener("startrule", function(){
parser.addListener("startrule", function() {
gradients = {
moz: 0,
webkit: 0,
Expand All @@ -24,37 +26,37 @@ CSSLint.addRule({
};
});

parser.addListener("property", function(event){
parser.addListener("property", function(event) {

if (/\-(moz|o|webkit)(?:\-(?:linear|radial))\-gradient/i.test(event.value)){
if (/\-(moz|o|webkit)(?:\-(?:linear|radial))\-gradient/i.test(event.value)) {
gradients[RegExp.$1] = 1;
} else if (/\-webkit\-gradient/i.test(event.value)){
} else if (/\-webkit\-gradient/i.test(event.value)) {
gradients.oldWebkit = 1;
}

});

parser.addListener("endrule", function(event){
parser.addListener("endrule", function(event) {
var missing = [];

if (!gradients.moz){
if (!gradients.moz) {
missing.push("Firefox 3.6+");
}

if (!gradients.webkit){
if (!gradients.webkit) {
missing.push("Webkit (Safari 5+, Chrome)");
}
if (!gradients.oldWebkit){

if (!gradients.oldWebkit) {
missing.push("Old Webkit (Safari 4+, Chrome)");
}

if (!gradients.o){
if (!gradients.o) {
missing.push("Opera 11.1+");
}

if (missing.length && missing.length < 4){
reporter.report("Missing vendor-prefixed CSS gradients for " + missing.join(", ") + ".", event.selectors[0].line, event.selectors[0].col, rule);
if (missing.length && missing.length < 4) {
reporter.report("Missing vendor-prefixed CSS gradients for " + missing.join(", ") + ".", event.selectors[0].line, event.selectors[0].col, rule);
}

});
Expand Down
28 changes: 15 additions & 13 deletions src/rules/ids.js
@@ -1,50 +1,52 @@
/*
* Rule: Don't use IDs for selectors.
*/
/*global CSSLint*/

CSSLint.addRule({

//rule information
// rule information
id: "ids",
name: "Disallow IDs in selectors",
desc: "Selectors should not contain IDs.",
url: "https://github.com/CSSLint/csslint/wiki/Disallow-IDs-in-selectors",
browsers: "All",

//initialization
init: function(parser, reporter){
// initialization
init: function(parser, reporter) {
"use strict";
var rule = this;
parser.addListener("startrule", function(event){
parser.addListener("startrule", function(event) {
var selectors = event.selectors,
selector,
part,
modifier,
idCount,
i, j, k;

for (i=0; i < selectors.length; i++){
for (i=0; i < selectors.length; i++) {
selector = selectors[i];
idCount = 0;

for (j=0; j < selector.parts.length; j++){
for (j=0; j < selector.parts.length; j++) {
part = selector.parts[j];
if (part.type == parser.SELECTOR_PART_TYPE){
for (k=0; k < part.modifiers.length; k++){
if (part.type === parser.SELECTOR_PART_TYPE) {
for (k=0; k < part.modifiers.length; k++) {
modifier = part.modifiers[k];
if (modifier.type == "id"){
if (modifier.type === "id") {
idCount++;
}
}
}
}

if (idCount == 1){
if (idCount === 1) {
reporter.report("Don't use IDs in selectors.", selector.line, selector.col, rule);
} else if (idCount > 1){
} else if (idCount > 1) {
reporter.report(idCount + " IDs in the selector, really?", selector.line, selector.col, rule);
}
}

});
}

});
});
42 changes: 42 additions & 0 deletions src/rules/import-ie-limit.js
@@ -0,0 +1,42 @@
/*
* Rule: IE6-9 supports up to 31 stylesheet import.
* Reference:
* http://blogs.msdn.com/b/ieinternals/archive/2011/05/14/internet-explorer-stylesheet-rule-selector-import-sheet-limit-maximum.aspx
*/

CSSLint.addRule({

// rule information
id: "import-ie-limit",
name: "@import limit on IE6-IE9",
desc: "IE6-9 supports up to 31 @import per stylesheet",
browsers: "IE6, IE7, IE8, IE9",

// initialization
init: function(parser, reporter) {
"use strict";
var rule = this,
MAX_IMPORT_COUNT = 31,
count = 0;

function startPage() {
count = 0;
}

parser.addListener("startpage", startPage);

parser.addListener("import", function() {
count++;
});

parser.addListener("endstylesheet", function() {
if (count > MAX_IMPORT_COUNT) {
reporter.rollupError(
"Too many @import rules (" + count + "). IE6-9 supports up to 31 import per stylesheet.",
rule
);
}
});
}

});
16 changes: 9 additions & 7 deletions src/rules/import.js
@@ -1,23 +1,25 @@
/*
* Rule: Don't use @import, use <link> instead.
*/
/*global CSSLint*/

CSSLint.addRule({

//rule information
// rule information
id: "import",
name: "Disallow @import",
desc: "Don't use @import, use <link> instead.",
url: "https://github.com/CSSLint/csslint/wiki/Disallow-%40import",
browsers: "All",

//initialization
init: function(parser, reporter){
// initialization
init: function(parser, reporter) {
"use strict";
var rule = this;
parser.addListener("import", function(event){

parser.addListener("import", function(event) {
reporter.report("@import prevents parallel downloads, use <link> instead.", event.line, event.col, rule);
});

}

});
});
30 changes: 17 additions & 13 deletions src/rules/important.js
Expand Up @@ -3,35 +3,39 @@
* war. Display a warning on !important declarations, an error if it's
* used more at least 10 times.
*/
/*global CSSLint*/

CSSLint.addRule({

//rule information
// rule information
id: "important",
name: "Disallow !important",
desc: "Be careful when using !important declaration",
url: "https://github.com/CSSLint/csslint/wiki/Disallow-%21important",
browsers: "All",

//initialization
init: function(parser, reporter){
// initialization
init: function(parser, reporter) {
"use strict";
var rule = this,
count = 0;

//warn that important is used and increment the declaration counter
parser.addListener("property", function(event){
if (event.important === true){
count++;
reporter.report("Use of !important", event.line, event.col, rule);
// warn that important is used and increment the declaration counter
parser.addListener("property", function(event) {
if (!reporter.isIgnored(event.line)) {
if (event.important === true) {
count++;
reporter.report("Use of !important", event.line, event.col, rule);
}
}
});

//if there are more than 10, show an error
parser.addListener("endstylesheet", function(){
// if there are more than 10, show an error
parser.addListener("endstylesheet", function() {
reporter.stat("important", count);
if (count >= 10){
if (count >= 10) {
reporter.rollupWarn("Too many !important declarations (" + count + "), try to use less than 10 to avoid specificity issues.", rule);
}
});
}

});
});
15 changes: 8 additions & 7 deletions src/rules/known-properties.js
Expand Up @@ -2,21 +2,22 @@
* Rule: Properties should be known (listed in CSS3 specification) or
* be a vendor-prefixed property.
*/
/*global CSSLint*/

CSSLint.addRule({

//rule information
// rule information
id: "known-properties",
name: "Require use of known properties",
desc: "Properties should be known (listed in CSS3 specification) or be a vendor-prefixed property.",
url: "https://github.com/CSSLint/csslint/wiki/Require-use-of-known-properties",
browsers: "All",

//initialization
init: function(parser, reporter){
// initialization
init: function(parser, reporter) {
"use strict";
var rule = this;

parser.addListener("property", function(event){
var name = event.property.text.toLowerCase();
parser.addListener("property", function(event) {

// the check is handled entirely by the parser-lib (https://github.com/nzakas/parser-lib)
if (event.invalid) {
Expand All @@ -26,4 +27,4 @@ CSSLint.addRule({
});
}

});
});
54 changes: 54 additions & 0 deletions src/rules/order-alphabetical.js
@@ -0,0 +1,54 @@
/*
* Rule: All properties should be in alphabetical order.
*/

CSSLint.addRule({

// rule information
id: "order-alphabetical",
name: "Alphabetical order",
desc: "Assure properties are in alphabetical order",
browsers: "All",

// initialization
init: function(parser, reporter) {
"use strict";
var rule = this,
properties;

var startRule = function () {
properties = [];
};

var endRule = function(event) {
var currentProperties = properties.join(","),
expectedProperties = properties.sort().join(",");

if (currentProperties !== expectedProperties) {
reporter.report("Rule doesn't have all its properties in alphabetical order.", event.line, event.col, rule);
}
};

parser.addListener("startrule", startRule);
parser.addListener("startfontface", startRule);
parser.addListener("startpage", startRule);
parser.addListener("startpagemargin", startRule);
parser.addListener("startkeyframerule", startRule);
parser.addListener("startviewport", startRule);

parser.addListener("property", function(event) {
var name = event.property.text,
lowerCasePrefixLessName = name.toLowerCase().replace(/^-.*?-/, "");

properties.push(lowerCasePrefixLessName);
});

parser.addListener("endrule", endRule);
parser.addListener("endfontface", endRule);
parser.addListener("endpage", endRule);
parser.addListener("endpagemargin", endRule);
parser.addListener("endkeyframerule", endRule);
parser.addListener("endviewport", endRule);
}

});
52 changes: 28 additions & 24 deletions src/rules/outline-none.js
Expand Up @@ -2,23 +2,25 @@
* Rule: outline: none or outline: 0 should only be used in a :focus rule
* and only if there are other properties in the same rule.
*/
/*global CSSLint*/

CSSLint.addRule({

//rule information
// rule information
id: "outline-none",
name: "Disallow outline: none",
desc: "Use of outline: none or outline: 0 should be limited to :focus rules.",
url: "https://github.com/CSSLint/csslint/wiki/Disallow-outline%3Anone",
browsers: "All",
tags: ["Accessibility"],

//initialization
init: function(parser, reporter){
// initialization
init: function(parser, reporter) {
"use strict";
var rule = this,
lastRule;

function startRule(event){
if (event.selectors){
function startRule(event) {
if (event.selectors) {
lastRule = {
line: event.line,
col: event.col,
Expand All @@ -30,14 +32,14 @@ CSSLint.addRule({
lastRule = null;
}
}
function endRule(event){
if (lastRule){
if (lastRule.outline){
if (lastRule.selectors.toString().toLowerCase().indexOf(":focus") == -1){

function endRule() {
if (lastRule) {
if (lastRule.outline) {
if (lastRule.selectors.toString().toLowerCase().indexOf(":focus") === -1) {
reporter.report("Outlines should only be modified using :focus.", lastRule.line, lastRule.col, rule);
} else if (lastRule.propCount == 1) {
reporter.report("Outlines shouldn't be hidden unless other visual changes are made.", lastRule.line, lastRule.col, rule);
} else if (lastRule.propCount === 1) {
reporter.report("Outlines shouldn't be hidden unless other visual changes are made.", lastRule.line, lastRule.col, rule);
}
}
}
Expand All @@ -47,27 +49,29 @@ CSSLint.addRule({
parser.addListener("startfontface", startRule);
parser.addListener("startpage", startRule);
parser.addListener("startpagemargin", startRule);
parser.addListener("startkeyframerule", startRule);
parser.addListener("startkeyframerule", startRule);
parser.addListener("startviewport", startRule);

parser.addListener("property", function(event){
parser.addListener("property", function(event) {
var name = event.property.text.toLowerCase(),
value = event.value;
if (lastRule){
value = event.value;

if (lastRule) {
lastRule.propCount++;
if (name == "outline" && (value == "none" || value == "0")){
if (name === "outline" && (value.toString() === "none" || value.toString() === "0")) {
lastRule.outline = true;
}
}
}

});

parser.addListener("endrule", endRule);
parser.addListener("endfontface", endRule);
parser.addListener("endpage", endRule);
parser.addListener("endpagemargin", endRule);
parser.addListener("endkeyframerule", endRule);
parser.addListener("endkeyframerule", endRule);
parser.addListener("endviewport", endRule);

}

});
});
55 changes: 30 additions & 25 deletions src/rules/overqualified-elements.js
@@ -1,63 +1,68 @@
/*
* Rule: Don't use classes or IDs with elements (a.foo or a#foo).
*/
/*global CSSLint*/

CSSLint.addRule({

//rule information
// rule information
id: "overqualified-elements",
name: "Disallow overqualified elements",
desc: "Don't use classes or IDs with elements (a.foo or a#foo).",
url: "https://github.com/CSSLint/csslint/wiki/Disallow-overqualified-elements",
browsers: "All",

//initialization
init: function(parser, reporter){
// initialization
init: function(parser, reporter) {
"use strict";
var rule = this,
classes = {};
parser.addListener("startrule", function(event){

parser.addListener("startrule", function(event) {
var selectors = event.selectors,
selector,
part,
modifier,
i, j, k;

for (i=0; i < selectors.length; i++){
for (i=0; i < selectors.length; i++) {
selector = selectors[i];

for (j=0; j < selector.parts.length; j++){
for (j=0; j < selector.parts.length; j++) {
part = selector.parts[j];
if (part.type == parser.SELECTOR_PART_TYPE){
for (k=0; k < part.modifiers.length; k++){
if (part.type === parser.SELECTOR_PART_TYPE) {
for (k=0; k < part.modifiers.length; k++) {
modifier = part.modifiers[k];
if (part.elementName && modifier.type == "id"){
if (part.elementName && modifier.type === "id") {
reporter.report("Element (" + part + ") is overqualified, just use " + modifier + " without element name.", part.line, part.col, rule);
} else if (modifier.type == "class"){
if (!classes[modifier]){
} else if (modifier.type === "class") {

if (!classes[modifier]) {
classes[modifier] = [];
}
classes[modifier].push({ modifier: modifier, part: part });
classes[modifier].push({
modifier: modifier,
part: part
});
}
}
}
}
}
});
parser.addListener("endstylesheet", function(){

parser.addListener("endstylesheet", function() {

var prop;
for (prop in classes){
if (classes.hasOwnProperty(prop)){
//one use means that this is overqualified
if (classes[prop].length == 1 && classes[prop][0].part.elementName){
for (prop in classes) {
if (classes.hasOwnProperty(prop)) {

// one use means that this is overqualified
if (classes[prop].length === 1 && classes[prop][0].part.elementName) {
reporter.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);
}
}
}
}
});
}

});
});
38 changes: 38 additions & 0 deletions src/rules/performant-transitions.js
@@ -0,0 +1,38 @@
CSSLint.addRule({
id: "performant-transitions",
name: "Allow only performant transisitons",
desc: "Only allow transitions that trigger compositing for performant, 60fps transformations.",
url: "",
browsers: "All",

init: function(parser, reporter){
"use strict";
var rule = this;

var transitionProperties = ["transition-property", "transition", "-webkit-transition", "-o-transition"];
var allowedTransitions = [/-webkit-transform/g, /-ms-transform/g, /transform/g, /opacity/g];

parser.addListener("property", function(event) {
var propertyName = event.property.toString().toLowerCase(),
propertyValue = event.value.toString(),
line = event.line,
col = event.col;

var values = propertyValue.split(",");
if (transitionProperties.indexOf(propertyName) !== -1) {
var reportValues = values.filter(function(value) {
var didMatch = [];
for (var i = 0; i < allowedTransitions.length; i++) {
if(value.match(allowedTransitions[i])) {
didMatch.push(i);
}
}
return didMatch.length === 0;
});
if(reportValues.length > 0) {
reporter.report("Unexpected transition property '"+reportValues.join(",").trim()+"'", line, col, rule);
}
}
});
}
});
22 changes: 12 additions & 10 deletions src/rules/qualified-headings.js
@@ -1,32 +1,34 @@
/*
* Rule: Headings (h1-h6) should not be qualified (namespaced).
*/
/*global CSSLint*/

CSSLint.addRule({

//rule information
// rule information
id: "qualified-headings",
name: "Disallow qualified headings",
desc: "Headings should not be qualified (namespaced).",
url: "https://github.com/CSSLint/csslint/wiki/Disallow-qualified-headings",
browsers: "All",

//initialization
init: function(parser, reporter){
// initialization
init: function(parser, reporter) {
"use strict";
var rule = this;

parser.addListener("startrule", function(event){
parser.addListener("startrule", function(event) {
var selectors = event.selectors,
selector,
part,
i, j;

for (i=0; i < selectors.length; i++){
for (i=0; i < selectors.length; i++) {
selector = selectors[i];

for (j=0; j < selector.parts.length; j++){
for (j=0; j < selector.parts.length; j++) {
part = selector.parts[j];
if (part.type == parser.SELECTOR_PART_TYPE){
if (part.elementName && /h[1-6]/.test(part.elementName.toString()) && j > 0){
if (part.type === parser.SELECTOR_PART_TYPE) {
if (part.elementName && /h[1-6]/.test(part.elementName.toString()) && j > 0) {
reporter.report("Heading (" + part.elementName + ") should not be qualified.", part.line, part.col, rule);
}
}
Expand All @@ -35,4 +37,4 @@ CSSLint.addRule({
});
}

});
});
26 changes: 14 additions & 12 deletions src/rules/regex-selectors.js
@@ -1,35 +1,37 @@
/*
* Rule: Selectors that look like regular expressions are slow and should be avoided.
*/
/*global CSSLint*/

CSSLint.addRule({

//rule information
// rule information
id: "regex-selectors",
name: "Disallow selectors that look like regexs",
desc: "Selectors that look like regular expressions are slow and should be avoided.",
url: "https://github.com/CSSLint/csslint/wiki/Disallow-selectors-that-look-like-regular-expressions",
browsers: "All",

//initialization
init: function(parser, reporter){
// initialization
init: function(parser, reporter) {
"use strict";
var rule = this;

parser.addListener("startrule", function(event){
parser.addListener("startrule", function(event) {
var selectors = event.selectors,
selector,
part,
modifier,
i, j, k;

for (i=0; i < selectors.length; i++){
for (i=0; i < selectors.length; i++) {
selector = selectors[i];
for (j=0; j < selector.parts.length; j++){
for (j=0; j < selector.parts.length; j++) {
part = selector.parts[j];
if (part.type == parser.SELECTOR_PART_TYPE){
for (k=0; k < part.modifiers.length; k++){
if (part.type === parser.SELECTOR_PART_TYPE) {
for (k=0; k < part.modifiers.length; k++) {
modifier = part.modifiers[k];
if (modifier.type == "attribute"){
if (/([\~\|\^\$\*]=)/.test(modifier)){
if (modifier.type === "attribute") {
if (/([~\|\^\$\*]=)/.test(modifier)) {
reporter.report("Attribute selectors with " + RegExp.$1 + " are slow!", modifier.line, modifier.col, rule);
}
}
Expand All @@ -41,4 +43,4 @@ CSSLint.addRule({
});
}

});
});
20 changes: 10 additions & 10 deletions src/rules/rules-count.js
@@ -1,28 +1,28 @@
/*
* Rule: Total number of rules should not exceed x.
*/
/*global CSSLint*/

CSSLint.addRule({

//rule information
// rule information
id: "rules-count",
name: "Rules Count",
desc: "Track how many rules there are.",
browsers: "All",

//initialization
init: function(parser, reporter){
var rule = this,
count = 0;
// initialization
init: function(parser, reporter) {
"use strict";
var count = 0;

//count each rule
parser.addListener("startrule", function(){
// count each rule
parser.addListener("startrule", function() {
count++;
});

parser.addListener("endstylesheet", function(){
parser.addListener("endstylesheet", function() {
reporter.stat("rule-count", count);
});
}

});
});
13 changes: 7 additions & 6 deletions src/rules/selector-max-approaching.js
@@ -1,26 +1,27 @@
/*
* Rule: Warn people with approaching the IE 4095 limit
* Rule: Warn people with approaching the IE 4095 limit
*/
/*global CSSLint*/

CSSLint.addRule({

//rule information
// rule information
id: "selector-max-approaching",
name: "Warn when approaching the 4095 selector limit for IE",
desc: "Will warn when selector count is >= 3800 selectors.",
browsers: "IE",

//initialization
// initialization
init: function(parser, reporter) {
"use strict";
var rule = this, count = 0;

parser.addListener('startrule', function(event) {
parser.addListener("startrule", function(event) {
count += event.selectors.length;
});

parser.addListener("endstylesheet", function() {
if (count >= 3800) {
reporter.report("You have " + count + " selectors. Internet Explorer supports a maximum of 4095 selectors per stylesheet. Consider refactoring.",0,0,rule);
reporter.report("You have " + count + " selectors. Internet Explorer supports a maximum of 4095 selectors per stylesheet. Consider refactoring.", 0, 0, rule);
}
});
}
Expand Down
17 changes: 9 additions & 8 deletions src/rules/selector-max.js
@@ -1,28 +1,29 @@
/*
* Rule: Warn people past the IE 4095 limit
* Rule: Warn people past the IE 4095 limit
*/
/*global CSSLint*/

CSSLint.addRule({

//rule information
// rule information
id: "selector-max",
name: "Error when past the 4095 selector limit for IE",
desc: "Will error when selector count is > 4095.",
browsers: "IE",

//initialization
init: function(parser, reporter){
// initialization
init: function(parser, reporter) {
"use strict";
var rule = this, count = 0;

parser.addListener('startrule',function(event) {
parser.addListener("startrule", function(event) {
count += event.selectors.length;
});

parser.addListener("endstylesheet", function() {
if (count > 4095) {
reporter.report("You have " + count + " selectors. Internet Explorer supports a maximum of 4095 selectors per stylesheet. Consider refactoring.",0,0,rule);
reporter.report("You have " + count + " selectors. Internet Explorer supports a maximum of 4095 selectors per stylesheet. Consider refactoring.", 0, 0, rule);
}
});
}

});
});
44 changes: 44 additions & 0 deletions src/rules/selector-newline.js
@@ -0,0 +1,44 @@
/*
* Rule: Avoid new-line characters in selectors.
*/

CSSLint.addRule({

// rule information
id: "selector-newline",
name: "Disallow new-line characters in selectors",
desc: "New-line characters in selectors are usually a forgotten comma and not a descendant combinator.",
browsers: "All",

// initialization
init: function(parser, reporter) {
"use strict";
var rule = this;

function startRule(event) {
var i, len, selector, p, n, pLen, part, part2, type, currentLine, nextLine,
selectors = event.selectors;

for (i = 0, len = selectors.length; i < len; i++) {
selector = selectors[i];
for (p = 0, pLen = selector.parts.length; p < pLen; p++) {
for (n = p + 1; n < pLen; n++) {
part = selector.parts[p];
part2 = selector.parts[n];
type = part.type;
currentLine = part.line;
nextLine = part2.line;

if (type === "descendant" && nextLine > currentLine) {
reporter.report("newline character found in selector (forgot a comma?)", currentLine, selectors[i].parts[0].col, rule);
}
}
}

}
}

parser.addListener("startrule", startRule);

}
});
73 changes: 37 additions & 36 deletions src/rules/shorthand.js
@@ -1,18 +1,20 @@
/*
* Rule: Use shorthand properties where possible.
*
*
*/
/*global CSSLint*/

CSSLint.addRule({

//rule information
// rule information
id: "shorthand",
name: "Require shorthand properties",
desc: "Use shorthand properties where possible.",
url: "https://github.com/CSSLint/csslint/wiki/Require-shorthand-properties",
browsers: "All",

//initialization
init: function(parser, reporter){

// initialization
init: function(parser, reporter) {
"use strict";
var rule = this,
prop, i, len,
propertiesToCheck = {},
Expand All @@ -29,59 +31,58 @@ CSSLint.addRule({
"padding-bottom",
"padding-left",
"padding-right"
]
]
};
//initialize propertiesToCheck
for (prop in mapping){
if (mapping.hasOwnProperty(prop)){
for (i=0, len=mapping[prop].length; i < len; i++){

// initialize propertiesToCheck
for (prop in mapping) {
if (mapping.hasOwnProperty(prop)) {
for (i=0, len=mapping[prop].length; i < len; i++) {
propertiesToCheck[mapping[prop][i]] = prop;
}
}
}
function startRule(event){

function startRule() {
properties = {};
}
//event handler for end of rules
function endRule(event){

// event handler for end of rules
function endRule(event) {

var prop, i, len, total;
//check which properties this rule has
for (prop in mapping){
if (mapping.hasOwnProperty(prop)){

// check which properties this rule has
for (prop in mapping) {
if (mapping.hasOwnProperty(prop)) {
total=0;
for (i=0, len=mapping[prop].length; i < len; i++){

for (i=0, len=mapping[prop].length; i < len; i++) {
total += properties[mapping[prop][i]] ? 1 : 0;
}
if (total == mapping[prop].length){

if (total === mapping[prop].length) {
reporter.report("The properties " + mapping[prop].join(", ") + " can be replaced by " + prop + ".", event.line, event.col, rule);
}
}
}
}
}

parser.addListener("startrule", startRule);
parser.addListener("startfontface", startRule);

//check for use of "font-size"
parser.addListener("property", function(event){
var name = event.property.toString().toLowerCase(),
value = event.value.parts[0].value;

if (propertiesToCheck[name]){
// check for use of "font-size"
parser.addListener("property", function(event) {
var name = event.property.toString().toLowerCase();

if (propertiesToCheck[name]) {
properties[name] = 1;
}
});

parser.addListener("endrule", endRule);
parser.addListener("endfontface", endRule);
parser.addListener("endfontface", endRule);

}

});
});
18 changes: 10 additions & 8 deletions src/rules/star-property-hack.js
Expand Up @@ -2,26 +2,28 @@
* Rule: Don't use properties with a star prefix.
*
*/
/*global CSSLint*/

CSSLint.addRule({

//rule information
// rule information
id: "star-property-hack",
name: "Disallow properties with a star prefix",
desc: "Checks for the star property hack (targets IE6/7)",
url: "https://github.com/CSSLint/csslint/wiki/Disallow-star-hack",
browsers: "All",

//initialization
init: function(parser, reporter){
// initialization
init: function(parser, reporter) {
"use strict";
var rule = this;

//check if property name starts with "*"
parser.addListener("property", function(event){
// check if property name starts with "*"
parser.addListener("property", function(event) {
var property = event.property;

if (property.hack == "*") {
if (property.hack === "*") {
reporter.report("Property with star prefix found.", event.property.line, event.property.col, rule);
}
});
}
});
});
28 changes: 15 additions & 13 deletions src/rules/text-indent.js
Expand Up @@ -2,45 +2,47 @@
* Rule: Don't use text-indent for image replacement if you need to support rtl.
*
*/
/*global CSSLint*/

CSSLint.addRule({

//rule information
// rule information
id: "text-indent",
name: "Disallow negative text-indent",
desc: "Checks for text indent less than -99px",
url: "https://github.com/CSSLint/csslint/wiki/Disallow-negative-text-indent",
browsers: "All",

//initialization
init: function(parser, reporter){
// initialization
init: function(parser, reporter) {
"use strict";
var rule = this,
textIndent,
direction;


function startRule(event){
function startRule() {
textIndent = false;
direction = "inherit";
}

//event handler for end of rules
function endRule(event){
if (textIndent && direction != "ltr"){
// event handler for end of rules
function endRule() {
if (textIndent && direction !== "ltr") {
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);
}
}

parser.addListener("startrule", startRule);
parser.addListener("startfontface", startRule);

//check for use of "font-size"
parser.addListener("property", function(event){
// check for use of "font-size"
parser.addListener("property", function(event) {
var name = event.property.toString().toLowerCase(),
value = event.value;

if (name == "text-indent" && value.parts[0].value < -99){
if (name === "text-indent" && value.parts[0].value < -99) {
textIndent = event.property;
} else if (name == "direction" && value == "ltr"){
} else if (name === "direction" && value.toString() === "ltr") {
direction = "ltr";
}
});
Expand All @@ -50,4 +52,4 @@ CSSLint.addRule({

}

});
});
18 changes: 10 additions & 8 deletions src/rules/underscore-property-hack.js
Expand Up @@ -2,26 +2,28 @@
* Rule: Don't use properties with a underscore prefix.
*
*/
/*global CSSLint*/

CSSLint.addRule({

//rule information
// rule information
id: "underscore-property-hack",
name: "Disallow properties with an underscore prefix",
desc: "Checks for the underscore property hack (targets IE6)",
url: "https://github.com/CSSLint/csslint/wiki/Disallow-underscore-hack",
browsers: "All",

//initialization
init: function(parser, reporter){
// initialization
init: function(parser, reporter) {
"use strict";
var rule = this;

//check if property name starts with "_"
parser.addListener("property", function(event){
// check if property name starts with "_"
parser.addListener("property", function(event) {
var property = event.property;

if (property.hack == "_") {
if (property.hack === "_") {
reporter.report("Property with underscore prefix found.", event.property.line, event.property.col, rule);
}
});
}
});
});
66 changes: 36 additions & 30 deletions src/rules/unique-headings.js
@@ -1,49 +1,55 @@
/*
* Rule: Headings (h1-h6) should be defined only once.
*/
/*global CSSLint*/

CSSLint.addRule({

//rule information
// rule information
id: "unique-headings",
name: "Headings should only be defined once",
desc: "Headings should be defined only once.",
url: "https://github.com/CSSLint/csslint/wiki/Headings-should-only-be-defined-once",
browsers: "All",

//initialization
init: function(parser, reporter){
// initialization
init: function(parser, reporter) {
"use strict";
var rule = this;

var headings = {
h1: 0,
h2: 0,
h3: 0,
h4: 0,
h5: 0,
h6: 0
};
var headings = {
h1: 0,
h2: 0,
h3: 0,
h4: 0,
h5: 0,
h6: 0
};

parser.addListener("startrule", function(event){
parser.addListener("startrule", function(event) {
var selectors = event.selectors,
selector,
part,
pseudo,
i, j;

for (i=0; i < selectors.length; i++){
for (i=0; i < selectors.length; i++) {
selector = selectors[i];
part = selector.parts[selector.parts.length-1];

if (part.elementName && /(h[1-6])/i.test(part.elementName.toString())){

for (j=0; j < part.modifiers.length; j++){
if (part.modifiers[j].type == "pseudo"){
if (reporter.isIgnored(part.line)) {
continue;
}

if (part.elementName && /(h[1-6])/i.test(part.elementName.toString())) {

for (j=0; j < part.modifiers.length; j++) {
if (part.modifiers[j].type === "pseudo") {
pseudo = true;
break;
}
}
if (!pseudo){

if (!pseudo) {
headings[RegExp.$1]++;
if (headings[RegExp.$1] > 1) {
reporter.report("Heading (" + part.elementName + ") has already been defined.", part.line, part.col, rule);
Expand All @@ -52,23 +58,23 @@ CSSLint.addRule({
}
}
});
parser.addListener("endstylesheet", function(event){

parser.addListener("endstylesheet", function() {
var prop,
messages = [];
for (prop in headings){
if (headings.hasOwnProperty(prop)){
if (headings[prop] > 1){

for (prop in headings) {
if (headings.hasOwnProperty(prop)) {
if (headings[prop] > 1) {
messages.push(headings[prop] + " " + prop + "s");
}
}
}
if (messages.length){

if (messages.length) {
reporter.rollupWarn("You have " + messages.join(", ") + " defined in this stylesheet.", rule);
}
});
});
}

});
});
23 changes: 12 additions & 11 deletions src/rules/universal-selector.js
@@ -1,35 +1,36 @@
/*
* Rule: Don't use universal selector because it's slow.
*/
/*global CSSLint*/

CSSLint.addRule({

//rule information
// rule information
id: "universal-selector",
name: "Disallow universal selector",
desc: "The universal selector (*) is known to be slow.",
url: "https://github.com/CSSLint/csslint/wiki/Disallow-universal-selector",
browsers: "All",

//initialization
init: function(parser, reporter){
// initialization
init: function(parser, reporter) {
"use strict";
var rule = this;

parser.addListener("startrule", function(event){
parser.addListener("startrule", function(event) {
var selectors = event.selectors,
selector,
part,
modifier,
i, j, k;
i;

for (i=0; i < selectors.length; i++){
for (i=0; i < selectors.length; i++) {
selector = selectors[i];

part = selector.parts[selector.parts.length-1];
if (part.elementName == "*"){
if (part.elementName === "*") {
reporter.report(rule.desc, part.line, part.col, rule);
}
}
});
}

});
});
47 changes: 31 additions & 16 deletions src/rules/unqualified-attributes.js
@@ -1,42 +1,57 @@
/*
* Rule: Don't use unqualified attribute selectors because they're just like universal selectors.
*/
/*global CSSLint*/

CSSLint.addRule({

//rule information
// rule information
id: "unqualified-attributes",
name: "Disallow unqualified attribute selectors",
desc: "Unqualified attribute selectors are known to be slow.",
url: "https://github.com/CSSLint/csslint/wiki/Disallow-unqualified-attribute-selectors",
browsers: "All",

//initialization
init: function(parser, reporter){
// initialization
init: function(parser, reporter) {
"use strict";

var rule = this;

parser.addListener("startrule", function(event){
parser.addListener("startrule", function(event) {

var selectors = event.selectors,
selectorContainsClassOrId = false,
selector,
part,
modifier,
i, j, k;
i, k;

for (i=0; i < selectors.length; i++){
for (i=0; i < selectors.length; i++) {
selector = selectors[i];

part = selector.parts[selector.parts.length-1];
if (part.type == parser.SELECTOR_PART_TYPE){
for (k=0; k < part.modifiers.length; k++){
if (part.type === parser.SELECTOR_PART_TYPE) {
for (k=0; k < part.modifiers.length; k++) {
modifier = part.modifiers[k];
if (modifier.type == "attribute" && (!part.elementName || part.elementName == "*")){
reporter.report(rule.desc, part.line, part.col, rule);

if (modifier.type === "class" || modifier.type === "id") {
selectorContainsClassOrId = true;
break;
}
}

if (!selectorContainsClassOrId) {
for (k=0; k < part.modifiers.length; k++) {
modifier = part.modifiers[k];
if (modifier.type === "attribute" && (!part.elementName || part.elementName === "*")) {
reporter.report(rule.desc, part.line, part.col, rule);
}
}
}
}
}

}
});
}

});
});
119 changes: 63 additions & 56 deletions src/rules/vendor-prefix.js
Expand Up @@ -2,17 +2,19 @@
* Rule: When using a vendor-prefixed property, make sure to
* include the standard one.
*/
/*global CSSLint*/

CSSLint.addRule({

//rule information
// rule information
id: "vendor-prefix",
name: "Require standard property with vendor prefix",
desc: "When using a vendor-prefixed property, make sure to include the standard one.",
url: "https://github.com/CSSLint/csslint/wiki/Require-standard-property-with-vendor-prefix",
browsers: "All",

//initialization
init: function(parser, reporter){
// initialization
init: function(parser, reporter) {
"use strict";
var rule = this,
properties,
num,
Expand All @@ -22,122 +24,127 @@ CSSLint.addRule({
"-webkit-border-top-right-radius": "border-top-right-radius",
"-webkit-border-bottom-left-radius": "border-bottom-left-radius",
"-webkit-border-bottom-right-radius": "border-bottom-right-radius",

"-o-border-radius": "border-radius",
"-o-border-top-left-radius": "border-top-left-radius",
"-o-border-top-right-radius": "border-top-right-radius",
"-o-border-bottom-left-radius": "border-bottom-left-radius",
"-o-border-bottom-right-radius": "border-bottom-right-radius",

"-moz-border-radius": "border-radius",
"-moz-border-radius-topleft": "border-top-left-radius",
"-moz-border-radius-topright": "border-top-right-radius",
"-moz-border-radius-bottomleft": "border-bottom-left-radius",
"-moz-border-radius-bottomright": "border-bottom-right-radius",
"-moz-border-radius-bottomright": "border-bottom-right-radius",

"-moz-column-count": "column-count",
"-webkit-column-count": "column-count",

"-moz-column-gap": "column-gap",
"-webkit-column-gap": "column-gap",

"-moz-column-rule": "column-rule",
"-webkit-column-rule": "column-rule",

"-moz-column-rule-style": "column-rule-style",
"-webkit-column-rule-style": "column-rule-style",

"-moz-column-rule-color": "column-rule-color",
"-webkit-column-rule-color": "column-rule-color",

"-moz-column-rule-width": "column-rule-width",
"-webkit-column-rule-width": "column-rule-width",

"-moz-column-width": "column-width",
"-webkit-column-width": "column-width",

"-webkit-column-span": "column-span",
"-webkit-columns": "columns",

"-moz-box-shadow": "box-shadow",
"-webkit-box-shadow": "box-shadow",

"-moz-transform" : "transform",
"-webkit-transform" : "transform",
"-o-transform" : "transform",
"-ms-transform" : "transform",

"-moz-transform-origin" : "transform-origin",
"-webkit-transform-origin" : "transform-origin",
"-o-transform-origin" : "transform-origin",
"-ms-transform-origin" : "transform-origin",

"-moz-box-sizing" : "box-sizing",
"-webkit-box-sizing" : "box-sizing",

"-moz-user-select" : "user-select",
"-khtml-user-select" : "user-select",
"-webkit-user-select" : "user-select"

"-moz-transform": "transform",
"-webkit-transform": "transform",
"-o-transform": "transform",
"-ms-transform": "transform",

"-moz-transform-origin": "transform-origin",
"-webkit-transform-origin": "transform-origin",
"-o-transform-origin": "transform-origin",
"-ms-transform-origin": "transform-origin",

"-moz-box-sizing": "box-sizing",
"-webkit-box-sizing": "box-sizing"
};

//event handler for beginning of rules
function startRule(){
// event handler for beginning of rules
function startRule() {
properties = {};
num=1;
num = 1;
}
//event handler for end of rules
function endRule(event){

// event handler for end of rules
function endRule() {
var prop,
i, len,
standard,
i,
len,
needed,
actual,
needsStandard = [];

for (prop in properties){
if (propertiesToCheck[prop]){
needsStandard.push({ actual: prop, needed: propertiesToCheck[prop]});
for (prop in properties) {
if (propertiesToCheck[prop]) {
needsStandard.push({
actual: prop,
needed: propertiesToCheck[prop]
});
}
}

for (i=0, len=needsStandard.length; i < len; i++){
for (i=0, len=needsStandard.length; i < len; i++) {
needed = needsStandard[i].needed;
actual = needsStandard[i].actual;

if (!properties[needed]){
if (!properties[needed]) {
reporter.report("Missing standard property '" + needed + "' to go along with '" + actual + "'.", properties[actual][0].name.line, properties[actual][0].name.col, rule);
} else {
//make sure standard property is last
if (properties[needed][0].pos < properties[actual][0].pos){
// make sure standard property is last
if (properties[needed][0].pos < properties[actual][0].pos) {
reporter.report("Standard property '" + needed + "' should come after vendor-prefixed property '" + actual + "'.", properties[actual][0].name.line, properties[actual][0].name.col, rule);
}
}
}

}
}

parser.addListener("startrule", startRule);
parser.addListener("startfontface", startRule);
parser.addListener("startpage", startRule);
parser.addListener("startpagemargin", startRule);
parser.addListener("startkeyframerule", startRule);
parser.addListener("startkeyframerule", startRule);
parser.addListener("startviewport", startRule);

parser.addListener("property", function(event){
parser.addListener("property", function(event) {
var name = event.property.text.toLowerCase();

if (!properties[name]){
if (!properties[name]) {
properties[name] = [];
}

properties[name].push({ name: event.property, value : event.value, pos:num++ });
properties[name].push({
name: event.property,
value: event.value,
pos: num++
});
});

parser.addListener("endrule", endRule);
parser.addListener("endfontface", endRule);
parser.addListener("endpage", endRule);
parser.addListener("endpagemargin", endRule);
parser.addListener("endkeyframerule", endRule);
parser.addListener("endkeyframerule", endRule);
parser.addListener("endviewport", endRule);
}

});
});
22 changes: 12 additions & 10 deletions src/rules/zero-units.js
@@ -1,27 +1,29 @@
/*
* Rule: You don't need to specify units when a value is 0.
*/
/*global CSSLint*/

CSSLint.addRule({

//rule information
// rule information
id: "zero-units",
name: "Disallow units for 0 values",
desc: "You don't need to specify units when a value is 0.",
url: "https://github.com/CSSLint/csslint/wiki/Disallow-units-for-zero-values",
browsers: "All",

//initialization
init: function(parser, reporter){
// initialization
init: function(parser, reporter) {
"use strict";
var rule = this;

//count how many times "float" is used
parser.addListener("property", function(event){
// count how many times "float" is used
parser.addListener("property", function(event) {
var parts = event.value.parts,
i = 0,
i = 0,
len = parts.length;

while(i < len){
if ((parts[i].units || parts[i].type == "percentage") && parts[i].value === 0 && parts[i].type != "time"){
while (i < len) {
if ((parts[i].units || parts[i].type === "percentage") && parts[i].value === 0 && parts[i].type !== "time") {
reporter.report("Values of 0 shouldn't have units specified.", parts[i].line, parts[i].col, rule);
}
i++;
Expand All @@ -31,4 +33,4 @@ CSSLint.addRule({

}

});
});
18 changes: 10 additions & 8 deletions src/worker/Worker.js
@@ -1,11 +1,13 @@
/*
* Web worker for CSSLint
*/
/*global self, CSSLint, JSON*/
//message indicates to start linting
self.onmessage = function(event){

var data = event.data,
/* global self, JSON */

// message indicates to start linting
self.onmessage = function(event) {
"use strict";
var data = event.data,
message,
text,
ruleset,
Expand All @@ -15,12 +17,12 @@ self.onmessage = function(event){
message = JSON.parse(data);
text = message.text;
ruleset = message.ruleset;
} catch (ex){
} catch (ex) {
text = data;
}

results = CSSLint.verify(text, ruleset);

//Not all browsers support structured clone, so JSON stringify results
// Not all browsers support structured clone, so JSON stringify results
self.postMessage(JSON.stringify(results));
};
};
77 changes: 77 additions & 0 deletions tasks/changelog.js
@@ -0,0 +1,77 @@
/* jshint node:true */
"use strict";

module.exports = function(grunt) {
grunt.registerMultiTask("changelog", "Write the changelog file", function() {
var done = this.async();
var lastTag;
var files = this.filesSrc;


grunt.util.spawn({
cmd: "git",
args: ["tag"]
}, function(error, result) {
// Find the latest git tag
var tags = result.stdout.split("\n"),
semver = tags[0].replace("v", "").split("."),
major = parseInt(semver[0], 10),
minor = parseInt(semver[1], 10),
patch = parseInt(semver[2], 10);

// A simple array sort can't be used because of the comparison of
// the strings "0.9.9" > "0.9.10"
for (var i = 1, len = tags.length; i < len; i++) {
semver = tags[i].replace("v", "").split(".");

var currentMajor = parseInt(semver[0], 10);
if (currentMajor < major) {
continue;
} else if (currentMajor > major) {
major = currentMajor;
}

var currentMinor = parseInt(semver[1], 10);
if (currentMinor < minor) {
continue;
} else if (currentMinor > minor) {
minor = currentMinor;
}

var currentPatch = parseInt(semver[2], 10);
if (currentPatch < patch) {
continue;
} else if (currentPatch > patch) {
patch = currentPatch;
}
}

lastTag = "v" + major + "." + minor + "." + patch;

grunt.verbose.write("Last tag: " + lastTag).writeln();

grunt.util.spawn({
cmd: "git",
args: ["log", "--pretty=format:'* %s (%an)'", lastTag + "..HEAD"]
}, function(error, result) {
var prettyPrint = result.stdout.split("'\n'")
.join("\n")
.replace(/"$/, "")
.replace(/^"/, "")
.replace(/^'/, "")
.replace(/'$/, "");

grunt.verbose.writeln().write(prettyPrint).writeln();

var template = "<%= grunt.template.today('mmmm d, yyyy') %> - v<%= pkg.version %>\n\n" +
prettyPrint + "\n\n" +
grunt.file.read(files[0]);

grunt.file.write(files[0], grunt.template.process(template));

done();
});
});

});
};
24 changes: 24 additions & 0 deletions tasks/test_rhino.js
@@ -0,0 +1,24 @@
/* jshint node:true */
"use strict";

module.exports = function(grunt) {
// Run test suite through rhino
grunt.registerMultiTask("test_rhino", "Run the test suite through rhino", function() {
var done = this.async();
var files = this.filesSrc;
var progress = files.length;

files.forEach(function(filepath) {
grunt.util.spawn({
cmd: "java",
args: ["-jar", "lib/js.jar", "lib/yuitest-rhino-cli.js", "dist/csslint.js", filepath],
opts: { stdio: "inherit" }
}, function() {
progress--;
if (progress === 0) {
done();
}
});
});
});
};
107 changes: 107 additions & 0 deletions tasks/yuitest.js
@@ -0,0 +1,107 @@
/* jshint evil:true, node:true */
"use strict";

module.exports = function(grunt) {
grunt.registerMultiTask("yuitest", "Run the YUITests for the project", function() {

var YUITest = require("yuitest");
var CSSLint = require("../dist/csslint-node").CSSLint; // jshint ignore:line
var files = this.filesSrc;
var TestRunner = YUITest.TestRunner;
var done = this.async();
var failures = [],
stack = [];

// Eval each file so the tests are brought into this scope where CSSLint and YUITest are loaded already
files.forEach(function(filepath) {
eval(grunt.file.read(filepath));
});

// From YUITest Node CLI with minor colourization changes
function handleEvent(event) {

var message = "",
results = event.results,
i, len, gruntFailMessage;

switch (event.type) {
case TestRunner.BEGIN_EVENT:
grunt.verbose.subhead("YUITest for Node.js");

if (TestRunner._groups) {
grunt.verbose.writeln("Filtering on groups '" + TestRunner._groups.slice(1, -1) + "'");
}
break;

case TestRunner.COMPLETE_EVENT:
grunt.log.writeln("Total tests: " + results.total + ", " +
("Failures: " + results.failed).red + ", " +
("Skipped: " + results.ignored).yellow +
", Time: " + results.duration / 1000 + " seconds\n");

if (failures.length) {
grunt.log.writeln("Tests failed:");

for (i=0, len=failures.length; i < len; i++) {
gruntFailMessage += failures[i].name + "\n" + failures[i].error;
}
grunt.fail.warn(gruntFailMessage);
}

// Tell grunt we're done the async operation
done();
break;

case TestRunner.TEST_FAIL_EVENT:
message = "F".red;
failures.push({
name: stack.concat([event.testName]).join(" > "),
error: event.error
});

break;

case TestRunner.ERROR_EVENT:
grunt.fail.fatal(event.error, stack);
break;

case TestRunner.TEST_IGNORE_EVENT:
message = "S".yellow;
break;

case TestRunner.TEST_PASS_EVENT:
message = ".".green;
break;

case TestRunner.TEST_SUITE_BEGIN_EVENT:
stack.push(event.testSuite.name);
break;

case TestRunner.TEST_CASE_COMPLETE_EVENT:
case TestRunner.TEST_SUITE_COMPLETE_EVENT:
stack.pop();
break;

case TestRunner.TEST_CASE_BEGIN_EVENT:
stack.push(event.testCase.name);
break;

// no default
}

grunt.log.write(message);
}
// Add event listeners
TestRunner.subscribe(TestRunner.BEGIN_EVENT, handleEvent);
TestRunner.subscribe(TestRunner.TEST_FAIL_EVENT, handleEvent);
TestRunner.subscribe(TestRunner.TEST_PASS_EVENT, handleEvent);
TestRunner.subscribe(TestRunner.ERROR_EVENT, handleEvent);
TestRunner.subscribe(TestRunner.TEST_IGNORE_EVENT, handleEvent);
TestRunner.subscribe(TestRunner.TEST_CASE_BEGIN_EVENT, handleEvent);
TestRunner.subscribe(TestRunner.TEST_CASE_COMPLETE_EVENT, handleEvent);
TestRunner.subscribe(TestRunner.TEST_SUITE_BEGIN_EVENT, handleEvent);
TestRunner.subscribe(TestRunner.TEST_SUITE_COMPLETE_EVENT, handleEvent);
TestRunner.subscribe(TestRunner.COMPLETE_EVENT, handleEvent);
TestRunner.run();
});
};
6 changes: 6 additions & 0 deletions tests/.jshintrc
@@ -0,0 +1,6 @@
{
"extends" : "../.jshintrc",
"globals": {
"YUITest": true
}
}
71 changes: 39 additions & 32 deletions tests/all-rules.js
Expand Up @@ -7,53 +7,60 @@
* When run in addition to the other tests, this causes the Rhino CLI test
* to fail due to Java stack overflow. This must be run separate from other tests.
*/
(function(){
/*jshint loopfunc: true */
/*global YUITest, CSSLint*/

(function() {
"use strict";
var Assert = YUITest.Assert,
suite = new YUITest.TestSuite("General Tests for all Rules"),
rules = CSSLint.getRules(),
i, len;
suite = new YUITest.TestSuite("General Tests for all Rules"),
rules = CSSLint.getRules(),
len = rules.length,
i;

for (i=0, len=25; i < len; i++){
function testAll(i, rules) {

(function(i, rules){
suite.add(new YUITest.TestCase({

suite.add(new YUITest.TestCase({
name: "General Tests for " + rules[i].id,

name: "General Tests for " + rules[i].id,
setUp: function() {
this.options = {};
this.options[rules[i].id] = 1;
},

setUp: function(){
this.options = {};
this.options[rules[i].id] = 1;
},
"Using @viewport should not result in an error": function() {
var result = CSSLint.verify("@viewport { width: auto; }", this.options);
Assert.areEqual(0, result.messages.length);
},

"Using @keyframes should not result in an error": function(){
var result = CSSLint.verify("@keyframes resize { 0% {padding: 0;} 50% {padding: 0;} 100% {padding: 0;}}", this.options);
Assert.areEqual(0, result.messages.length);
},
"Using @keyframes should not result in an error": function() {
var result = CSSLint.verify("@keyframes resize { 0% {padding: 0;} 50% {padding: 0;} 100% {padding: 0;}}", this.options);
Assert.areEqual(0, result.messages.length);
},

"Using @page should not result in an error": function(){
var result = CSSLint.verify("@page { width: 100px; }", this.options);
Assert.areEqual(0, result.messages.length);
},
"Using @page should not result in an error": function() {
var result = CSSLint.verify("@page { width: 100px; }", this.options);
Assert.areEqual(0, result.messages.length);
},

"Using @page @top-left should not result in an error": function(){
var result = CSSLint.verify("@page { @top-left { content: ''; } }", this.options);
Assert.areEqual(0, result.messages.length);
},
"Using @page @top-left should not result in an error": function() {
var result = CSSLint.verify("@page { @top-left { content: ''; } }", this.options);
Assert.areEqual(0, result.messages.length);
},

"Using a regular rule should not result in an error": function(){
var result = CSSLint.verify("body { margin: 0; }", this.options);
Assert.areEqual(0, result.messages.length);
}
"Using a regular rule should not result in an error": function() {
var result = CSSLint.verify("body { margin: 0; }", this.options);
Assert.areEqual(0, result.messages.length);
}

}));
}));

})(i, rules);
}

for (i = 0; i < len; i++) {
testAll(i, rules);
}

YUITest.TestRunner.add(suite);

})();

65 changes: 65 additions & 0 deletions tests/cli/assets/apiStub.js
@@ -0,0 +1,65 @@
/* jshint node:true */

"use strict";

var stub = {
logbook: function(log) {
this.logs.push(log);
},
readLogs: function() {
return this.logs.slice();
},

getFullPath: function(path) {
return path;
},
getFiles: function(dir) {
var filesobj = this.fakedFs[dir],
fileix,
out = [];
for (fileix in filesobj) {
if (filesobj.hasOwnProperty(fileix) && /\.css$/.test(fileix)) {
out.push(dir + "/" + fileix);
}
}
return out;
},
readFile: function(path) {
var spath = path.split("/"),
spathLen = spath.length,
i,
out = this.fakedFs;

for (i = 0; i < spathLen; i += 1) {
out = out[spath[i]];
}

return out;
},
isDirectory: function(checkit) {
var result = this.fakedFs[checkit];
return typeof result === "object";
},
print: function(msg) {
this.logbook(msg);
},
quit: function(signal) {
this.logbook(signal);
}
};

module.exports = function(setup) {
var api,
setix;

api = Object.create(stub);

for (setix in setup) {
if (setup.hasOwnProperty(setix)) {
api[setix] = setup[setix];
}
}

api.logs = [];
return api;
};
59 changes: 59 additions & 0 deletions tests/cli/assets/data.js
@@ -0,0 +1,59 @@
/* jshint node:true */

module.exports = {
"suites": {
"config csslintrc override": {
"args": [
"--config=.rc1",
"dir"
],
"expecting": [
"csslint: No errors in dir/a.css.",
"csslint: No errors in dir/b.css.",
0
]
},
"straight linting": {
"args": [
"dir"
],
"expecting": [
"csslint: There is 1 problem in dir/a.css.",
"csslint: There is 1 problem in dir/b.css.",
0
]
},
"mix of cli options": {
"args": [
"--config=.rc1",
"--ignore=important",
"dir"
],
"expecting": [
"csslint: No errors in dir/a.css.",
"csslint: There is 1 problem in dir/b.css.",
0
]
},
"more mixes of cli options": {
"args": [
"--config=.rc1",
"--errors=important",
"dir"
],
"expecting": [
"csslint: There is 1 problem in dir/a.css.",
"csslint: No errors in dir/b.css.",
1
]
}
},

"fakedFs": {
".rc1": "--ignore=important,ids",
"dir": {
"a.css": ".a {color: red!important;}",
"b.css": "#a {color: red;}"
}
}
};
71 changes: 71 additions & 0 deletions tests/cli/cli-common.js
@@ -0,0 +1,71 @@
/* jshint loopfunc:true, node:true */

"use strict";
function include(path, sandbox) {
var vm = require("vm"),
fs = require("fs"),
file;

file = fs.readFileSync(path);
vm.runInNewContext(file, sandbox);
}


(function() {

var Assert = YUITest.Assert,
suite = new YUITest.TestSuite("General Tests for CLI"),
apiStub = require("../tests/cli/assets/apiStub.js"),
data = require("../tests/cli/assets/data.js"),
suites = data.suites,
suiteix,
sandbox = {
CSSLint: CSSLint
};

include("./src/cli/common.js", sandbox); /* expose sandbox.cli */

for (suiteix in suites) {
if (suites.hasOwnProperty(suiteix)) {
(function (suiteix) {

suite.add(new YUITest.TestCase({

name: "Test " + suiteix,

"Outcome logs should match expected": function () {
var it = suites[suiteix],
expecting = it.expecting,
expectingLen = expecting.length,
outcome,
api,
exp,
out,
i = 0;

data.args = it.args.slice();
api = apiStub(data);
sandbox.cli(api);
outcome = api.readLogs();

for (i; i < expectingLen; i += 1) {
exp = expecting[i];
out = outcome[i];

if (typeof out === "string") {
out = /^.*/.exec(out.trim())[0];
}
if (exp !== out) {
Assert.fail("Expecting: " + exp + " Got: " + out);
}
}
Assert.pass();

}
}));
})(suiteix);
}
}

YUITest.TestRunner.add(suite);
})();
98 changes: 85 additions & 13 deletions tests/core/CSSLint.js
@@ -1,31 +1,103 @@
(function(){

/*global YUITest, CSSLint*/
(function() {
"use strict";
var Assert = YUITest.Assert;

YUITest.TestRunner.add(new YUITest.TestCase({

name: "CSSLint object tests",

"Adjoining classes should not cause an error": function(){
var result = CSSLint.verify(".foo.bar{}", { });
"Adjoining classes should not cause an error": function() {
var result = CSSLint.verify(".foo.bar{}", {});
Assert.areEqual(0, result.messages.length);
},

"@media (max-width:400px) should not cause an error": function(){
var result = CSSLint.verify("@media (max-width:400px) {}", { });
"@media (max-width:400px) should not cause an error": function() {
var result = CSSLint.verify("@media (max-width:400px) {}", {});
Assert.areEqual(0, result.messages.length);
},

"Embedded ruleset should be honored": function(){
"Embedded ruleset should be honored": function() {
var result = CSSLint.verify("/*csslint bogus, adjoining-classes:true, box-sizing:false */\n.foo.bar{}", {
'text-indent': 1,
'box-sizing': 1
"text-indent": 1,
"box-sizing": 1
});

Assert.areEqual(2, result.ruleset['adjoining-classes']);
Assert.areEqual(1, result.ruleset['text-indent']);
Assert.areEqual(0, result.ruleset['box-sizing']);
Assert.areEqual(2, result.ruleset["adjoining-classes"]);
Assert.areEqual(1, result.ruleset["text-indent"]);
Assert.areEqual(0, result.ruleset["box-sizing"]);
},

"Embedded rulesets should not have the side-effect of modifying the ruleset object passed in by the caller of verify()": function() {
var ruleset = {
"text-indent": 1,
"box-sizing": 1
};
CSSLint.verify("/*csslint bogus, adjoining-classes:true, box-sizing:false */\n.foo.bar{}", ruleset);

Assert.areEqual(undefined, ruleset["adjoining-classes"]);
Assert.areEqual(1, ruleset["text-indent"]);
Assert.areEqual(1, ruleset["box-sizing"]);
},

"Embedded rulesets should accept whitespace between /* and 'csslint'": function() {
var result = CSSLint.verify("/* csslint bogus, adjoining-classes:true, box-sizing:false */\n.foo.bar{}", {
"text-indent": 1,
"box-sizing": 1
});

Assert.areEqual(2, result.ruleset["adjoining-classes"]);
Assert.areEqual(1, result.ruleset["text-indent"]);
Assert.areEqual(0, result.ruleset["box-sizing"]);
},

"Allow statement on one line with one rule should be added to report": function() {
var report = CSSLint.verify(".foo.bar{}\n.baz.qux{} /* csslint allow: box-sizing */\nquux.corge{}");
Assert.isTrue(report.allow.hasOwnProperty("2"));
Assert.isTrue(report.allow["2"].hasOwnProperty("box-sizing"));
},

"Allow statement on one line with multiple rules should be added to report": function() {
var report = CSSLint.verify(".foo.bar{}\n.baz.qux{} /* csslint allow: box-sizing, box-model */\nquux.corge{}");
Assert.isTrue(report.allow.hasOwnProperty("2"));
Assert.isTrue(report.allow["2"].hasOwnProperty("box-sizing"));
Assert.isTrue(report.allow["2"].hasOwnProperty("box-model"));
},

"Allow statements on multiple lines for different rules should be added to report": function() {
var report = CSSLint.verify(".foo.bar{}\n.baz.qux{} /* csslint allow: box-sizing */\nquux.corge{}\ngrault.garply{} /* csslint allow: box-model */");
Assert.isTrue(report.allow.hasOwnProperty("2"));
Assert.isTrue(report.allow["2"].hasOwnProperty("box-sizing"));
Assert.isTrue(report.allow.hasOwnProperty("4"));
Assert.isTrue(report.allow["4"].hasOwnProperty("box-model"));
},

"Full ignore blocks should be captured": function() {
var report = CSSLint.verify("/* csslint ignore:start */\n\n/* csslint ignore:end */");
Assert.areEqual(1, report.ignore.length);
Assert.areEqual(0, report.ignore[0][0]);
Assert.areEqual(2, report.ignore[0][1]);
},

"Whitespace should be no problem inside ignore comments": function() {
var report = CSSLint.verify("/* csslint ignore:start */\n\n/* csslint ignore:end */,\n/*csslint ignore:start*/\n/*csslint ignore:end*/");
Assert.areEqual(2, report.ignore.length);
Assert.areEqual(0, report.ignore[0][0]);
Assert.areEqual(2, report.ignore[0][1]);
Assert.areEqual(3, report.ignore[1][0]);
Assert.areEqual(4, report.ignore[1][1]);
},

"Ignore blocks should be autoclosed": function() {
var report = CSSLint.verify("/* csslint ignore:start */\n\n");
Assert.areEqual(1, report.ignore.length);
Assert.areEqual(0, report.ignore[0][0]);
Assert.areEqual(3, report.ignore[0][1]);
},

"Restarting ignore should be harmless": function() {
var report = CSSLint.verify("/* csslint ignore:start */\n/* csslint ignore:start */\n");
Assert.areEqual(1, report.ignore.length);
Assert.areEqual(0, report.ignore[0][0]);
}

}));
Expand Down
120 changes: 102 additions & 18 deletions tests/core/Reporter.js
@@ -1,34 +1,118 @@
(function(){

/*global YUITest, CSSLint, Reporter*/
(function() {
"use strict";
var Assert = YUITest.Assert;

YUITest.TestRunner.add(new YUITest.TestCase({

name: "Reporter Object Tests",

"Report should cause a warning": function(){
var reporter = new CSSLint._Reporter([], { "fake-rule": 1});
reporter.report("Foo", 1, 1, { id: "fake-rule" });


"Report should cause a warning": function() {
var reporter = new CSSLint._Reporter([], {
"fake-rule": 1
});
reporter.report("Foo", 1, 1, {
id: "fake-rule"
});

Assert.areEqual(1, reporter.messages.length);
Assert.areEqual("warning", reporter.messages[0].type);
},

"Report should cause an error": function(){
var reporter = new CSSLint._Reporter([], { "fake-rule": 2});
reporter.report("Foo", 1, 1, { id: "fake-rule" });


"Report should cause an error": function() {
var reporter = new CSSLint._Reporter([], {
"fake-rule": 2
});
reporter.report("Foo", 1, 1, {
id: "fake-rule"
});

Assert.areEqual(1, reporter.messages.length);
Assert.areEqual("error", reporter.messages[0].type);
},

"Calling error() should cause an error": function(){
var reporter = new CSSLint._Reporter([], { "fake-rule": 1});
reporter.error("Foo", 1, 1, { id: "fake-rule" });


"Calling error() should cause an error": function() {
var reporter = new CSSLint._Reporter([], {
"fake-rule": 1
});
reporter.error("Foo", 1, 1, {
id: "fake-rule"
});

Assert.areEqual(1, reporter.messages.length);
Assert.areEqual("error", reporter.messages[0].type);
},

"Allow statement should drop message about specific rule on specific line but not other lines": function() {
var reporter = new CSSLint._Reporter([], {
"fake-rule": 1
}, {
"3": {
"fake-rule": true
}
});
reporter.report("Foo", 2, 1, {
id: "fake-rule"
});
reporter.report("Bar", 3, 1, {
id: "fake-rule"
});

Assert.areEqual(1, reporter.messages.length);
},

"Allow statement should drop message about specific rule on specific line but not other rules": function() {
var reporter = new CSSLint._Reporter([], {
"fake-rule": 1,
"fake-rule2": 1
}, {
"3": {
"fake-rule": true
}
});
reporter.report("Foo", 3, 1, {
id: "fake-rule"
});
reporter.report("Bar", 3, 1, {
id: "fake-rule2"
});

Assert.areEqual(1, reporter.messages.length);
},

"Allow statement should drop messages about multiple rules on specific line": function() {
var reporter = new CSSLint._Reporter([], {
"fake-rule": 1,
"fake-rule2": 1
}, {
"3": {
"fake-rule": true,
"fake-rule2": true
}
});
reporter.report("Foo", 3, 1, {
id: "fake-rule"
});
reporter.report("Bar", 3, 1, {
id: "fake-rule2"
});

Assert.areEqual(0, reporter.messages.length);
},

"Ignores should step over a report in their range": function() {
var reporter = new CSSLint._Reporter([], {
"fake-rule": 1
}, {}, [
[1, 3]
]);
reporter.report("Foo", 2, 1, {
id: "fake-rule"
});
reporter.report("Bar", 5, 1, {
id: "fake-rule"
});

Assert.areEqual(1, reporter.messages.length);
}

}));
Expand Down