parser.addListener("endstylesheet", function() {
if (headings.hasOwnProperty(prop)) {
if (headings[prop] > 1) {
messages.push(headings[prop] + " " + prop + "s");
reporter.rollupWarn("You have " + messages.join(", ") + " defined in this stylesheet.", rule);
* Rule: Don't use universal selector because it's slow.
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",
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 === "*") {
reporter.report(rule.desc, part.line, part.col, rule);
* Rule: Don't use unqualified attribute selectors because they're just like universal selectors.
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",
init: function(parser, reporter) {
parser.addListener("startrule", function(event) {
var selectors = event.selectors,
selectorContainsClassOrId = false,
for (i=0; i < selectors.length; i++) {
part = selector.parts[selector.parts.length-1];
if (part.type === parser.SELECTOR_PART_TYPE) {
for (k=0; k < part.modifiers.length; k++) {
modifier = part.modifiers[k];
if (modifier.type === "class" || modifier.type === "id") {
selectorContainsClassOrId = true;
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);
* Rule: When using a vendor-prefixed property, make sure to
* include the standard one.
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",
init: function(parser, reporter) {
"-webkit-border-radius": "border-radius",
"-webkit-border-top-left-radius": "border-top-left-radius",
"-webkit-border-top-right-radius": "border-top-right-radius",
"-webkit-border-bottom-left-radius": "border-bottom-left-radius",
"-webkit-border-bottom-right-radius": "border-bottom-right-radius",
"-o-border-radius": "border-radius",
"-o-border-top-left-radius": "border-top-left-radius",
"-o-border-top-right-radius": "border-top-right-radius",
"-o-border-bottom-left-radius": "border-bottom-left-radius",
"-o-border-bottom-right-radius": "border-bottom-right-radius",
"-moz-border-radius": "border-radius",
"-moz-border-radius-topleft": "border-top-left-radius",
"-moz-border-radius-topright": "border-top-right-radius",
"-moz-border-radius-bottomleft": "border-bottom-left-radius",
"-moz-border-radius-bottomright": "border-bottom-right-radius",
"-moz-column-count": "column-count",
"-webkit-column-count": "column-count",
"-moz-column-gap": "column-gap",
"-webkit-column-gap": "column-gap",
"-moz-column-rule": "column-rule",
"-webkit-column-rule": "column-rule",
"-moz-column-rule-style": "column-rule-style",
"-webkit-column-rule-style": "column-rule-style",
"-moz-column-rule-color": "column-rule-color",
"-webkit-column-rule-color": "column-rule-color",
"-moz-column-rule-width": "column-rule-width",
"-webkit-column-rule-width": "column-rule-width",
"-moz-column-width": "column-width",
"-webkit-column-width": "column-width",
"-webkit-column-span": "column-span",
"-webkit-columns": "columns",
"-moz-box-shadow": "box-shadow",
"-webkit-box-shadow": "box-shadow",
"-moz-transform": "transform",
"-webkit-transform": "transform",
"-o-transform": "transform",
"-ms-transform": "transform",
"-moz-transform-origin": "transform-origin",
"-webkit-transform-origin": "transform-origin",
"-o-transform-origin": "transform-origin",
"-ms-transform-origin": "transform-origin",
"-moz-box-sizing": "box-sizing",
"-webkit-box-sizing": "box-sizing"
// event handler for beginning of rules
// event handler for end of rules
for (prop in properties) {
if (propertiesToCheck[prop]) {
needed: propertiesToCheck[prop]
for (i=0, len=needsStandard.length; i < len; i++) {
needed = needsStandard[i].needed;
actual = needsStandard[i].actual;
if (!properties[needed]) {
reporter.report("Missing standard property '" + needed + "' to go along with '" + actual + "'.", properties[actual][0].name.line, properties[actual][0].name.col, rule);
// 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("startviewport", startRule);
parser.addListener("property", function(event) {
var name = event.property.text.toLowerCase();
parser.addListener("endrule", endRule);
parser.addListener("endfontface", endRule);
parser.addListener("endpage", endRule);
parser.addListener("endpagemargin", endRule);
parser.addListener("endkeyframerule", endRule);
parser.addListener("endviewport", endRule);
* Rule: You don't need to specify units when a value is 0.
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",
init: function(parser, reporter) {
// count how many times "float" is used
parser.addListener("property", function(event) {
var parts = event.value.parts,
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);
* Replace special characters before write to output.
* - single quotes is the escape sequence for double-quotes
* - & is the escape sequence for &
* - < is the escape sequence for <
* - > is the escape sequence for >
* @param {String} message to escape
* @return escaped message as {String}
var xmlEscape = function(str) {
if (!str || str.constructor !== String) {
return str.replace(/["&><]/g, function(match) {
name: "Checkstyle XML format",
* Return opening root XML tag.
* @return {String} to prepend before all results
startFormat: function() {
return "<?xml version=\"1.0\" encoding=\"utf-8\"?><checkstyle>";
* Return closing root XML tag.
* @return {String} to append after all results
* Returns message when there is a file read error.
* @param {String} filename The name of the file that caused the error.
* @param {String} message The error message
* @return {String} The error message.
readError: function(filename, message) {
return "<file name=\"" + xmlEscape(filename) + "\"><error line=\"0\" column=\"0\" severty=\"error\" message=\"" + xmlEscape(message) + "\"></error></file>";
* Given CSS Lint results for a file, return output for this format.
* @param results {Object} with error and warning messages
* @param filename {String} relative file path
* @param options {Object} (UNUSED for now) specifies special handling of output
* @return {String} output for results
formatResults: function(results, filename/*, options*/) {
var messages = results.messages,
* Generate a source string for a rule.
* Checkstyle source strings usually resemble Java class names e.g
* net.csslint.SomeRuleName
* @return rule source as {String}
var generateSource = function(rule) {
if (!rule || !("name" in rule)) {
return "net.csslint." + rule.name.replace(/\s/g, "");
if (messages.length > 0) {
output.push("<file name=\""+filename+"\">");
CSSLint.Util.forEach(messages, function (message) {
// ignore rollups for now
output.push("<error line=\"" + message.line + "\" column=\"" + message.col + "\" severity=\"" + message.type + "\"" +
" message=\"" + xmlEscape(message.message) + "\" source=\"" + generateSource(message.rule) +"\"/>");
name: "Compact, 'porcelain' format",
* Return content to be printed before all file results.
* @return {String} to prepend before all results
startFormat: function() {
* Return content to be printed after all file results.
* @return {String} to append after all results
* Given CSS Lint results for a file, return output for this format.
* @param results {Object} with error and warning messages
* @param filename {String} relative file path
* @param options {Object} (Optional) specifies special handling of output
* @return {String} output for results
formatResults: function(results, filename, options) {
var messages = results.messages,
* Capitalize and return given string.
* @param str {String} to capitalize
* @return {String} capitalized
var capitalize = function(str) {
return str.charAt(0).toUpperCase() + str.slice(1);
if (messages.length === 0) {
return options.quiet ? "" : filename + ": Lint Free!";
CSSLint.Util.forEach(messages, function(message) {
output += filename + ": " + capitalize(message.type) + " - " + message.message + " (" + message.rule.id + ")\n";
output += filename + ": line " + message.line +
", col " + message.col + ", " + capitalize(message.type) + " - " + message.message + " (" + message.rule.id + ")\n";
name: "CSSLint XML format",
* Return opening root XML tag.
* @return {String} to prepend before all results
startFormat: function() {
return "<?xml version=\"1.0\" encoding=\"utf-8\"?><csslint>";
* Return closing root XML tag.
* @return {String} to append after all results
* Given CSS Lint results for a file, return output for this format.