parser.addListener("endstylesheet", function() {
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);
* Rule: Headings (h1-h6) should not be qualified (namespaced).
id: "qualified-headings",
name: "Disallow qualified headings",
desc: "Headings should not be qualified (namespaced).",
url: "https://github.com/CSSLint/csslint/wiki/Disallow-qualified-headings",
init: function(parser, reporter) {
parser.addListener("startrule", function(event) {
var selectors = event.selectors,
for (i=0; i < selectors.length; i++) {
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) {
reporter.report("Heading (" + part.elementName + ") should not be qualified.", part.line, part.col, rule);
* Rule: Selectors that look like regular expressions are slow and should be avoided.
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",
init: function(parser, reporter) {
parser.addListener("startrule", function(event) {
var selectors = event.selectors,
for (i=0; i < selectors.length; i++) {
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++) {
modifier = part.modifiers[k];
if (modifier.type === "attribute") {
if (/([~\|\^\$\*]=)/.test(modifier)) {
reporter.report("Attribute selectors with " + RegExp.$1 + " are slow!", modifier.line, modifier.col, rule);
* Rule: Total number of rules should not exceed x.
desc: "Track how many rules there are.",
init: function(parser, reporter) {
parser.addListener("startrule", function() {
parser.addListener("endstylesheet", function() {
reporter.stat("rule-count", count);
* Rule: Warn people with approaching the IE 4095 limit
id: "selector-max-approaching",
name: "Warn when approaching the 4095 selector limit for IE",
desc: "Will warn when selector count is >= 3800 selectors.",
init: function(parser, reporter) {
var rule = this, count = 0;
parser.addListener("startrule", function(event) {
count += event.selectors.length;
parser.addListener("endstylesheet", function() {
reporter.report("You have " + count + " selectors. Internet Explorer supports a maximum of 4095 selectors per stylesheet. Consider refactoring.", 0, 0, rule);
* Rule: Warn people past the IE 4095 limit
name: "Error when past the 4095 selector limit for IE",
desc: "Will error when selector count is > 4095.",
init: function(parser, reporter) {
var rule = this, count = 0;
parser.addListener("startrule", function(event) {
count += event.selectors.length;
parser.addListener("endstylesheet", function() {
reporter.report("You have " + count + " selectors. Internet Explorer supports a maximum of 4095 selectors per stylesheet. Consider refactoring.", 0, 0, rule);
* Rule: Avoid new-line characters in selectors.
name: "Disallow new-line characters in selectors",
desc: "New-line characters in selectors are usually a forgotten comma and not a descendant combinator.",
init: function(parser, reporter) {
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++) {
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];
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);
* Rule: Use shorthand properties where possible.
name: "Require shorthand properties",
desc: "Use shorthand properties where possible.",
url: "https://github.com/CSSLint/csslint/wiki/Require-shorthand-properties",
init: function(parser, reporter) {
// initialize propertiesToCheck
if (mapping.hasOwnProperty(prop)) {
for (i=0, len=mapping[prop].length; i < len; i++) {
propertiesToCheck[mapping[prop][i]] = prop;
// event handler for end of rules
function endRule(event) {
// check which properties this rule has
if (mapping.hasOwnProperty(prop)) {
for (i=0, len=mapping[prop].length; i < len; i++) {
total += properties[mapping[prop][i]] ? 1 : 0;
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();
if (propertiesToCheck[name]) {
parser.addListener("endrule", endRule);
parser.addListener("endfontface", endRule);
* Rule: Don't use properties with a star prefix.
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",
init: function(parser, reporter) {
// check if property name starts with "*"
parser.addListener("property", function(event) {
var property = event.property;
if (property.hack === "*") {
reporter.report("Property with star prefix found.", event.property.line, event.property.col, rule);
* Rule: Don't use text-indent for image replacement if you need to support rtl.
name: "Disallow negative text-indent",
desc: "Checks for text indent less than -99px",
url: "https://github.com/CSSLint/csslint/wiki/Disallow-negative-text-indent",
init: function(parser, reporter) {
// event handler for end of rules
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) {
var name = event.property.toString().toLowerCase(),
if (name === "text-indent" && value.parts[0].value < -99) {
textIndent = event.property;
} else if (name === "direction" && value.toString() === "ltr") {
parser.addListener("endrule", endRule);
parser.addListener("endfontface", endRule);
* Rule: Don't use properties with a underscore prefix.
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",
init: function(parser, reporter) {
// check if property name starts with "_"
parser.addListener("property", function(event) {
var property = event.property;
if (property.hack === "_") {
reporter.report("Property with underscore prefix found.", event.property.line, event.property.col, rule);
* Rule: Headings (h1-h6) should be defined only once.
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",
init: function(parser, reporter) {
parser.addListener("startrule", function(event) {
var selectors = event.selectors,
for (i=0; i < selectors.length; 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 (headings[RegExp.$1] > 1) {
reporter.report("Heading (" + part.elementName + ") has already been defined.", part.line, part.col, rule);