diff --git a/src/rules/font-stack.js b/src/rules/font-stack.js new file mode 100644 index 00000000..129d1bad --- /dev/null +++ b/src/rules/font-stack.js @@ -0,0 +1,54 @@ +/* + * Rule: You should always end your font/font-family declarations with sans-serif, serif, or monospace. + */ + +/*global CSSLint*/ +CSSLint.addRule({ + + //rule information + id: "font-stack", + name: "Disallow incomplete font stacks", + desc: "Checks if font and font-family declarations end with a sans-serif, serif, or monospace.", + browsers: "All", + + //initialization + init: function(parser, reporter){ + var rule = this, + whiteList = ["sans-serif", "serif", "monospace"], // "cursive" often equals comic sans and "fantasy" can mean virtually anything + onTheWhitelist, + trim; + + onTheWhitelist = function (thing) { + var i; + for (i = whiteList.length; i--;) { + if (whiteList[i] === thing) { + return true; + } + } + return false; + }; + + trim = function (s) { + return s.replace(/^\s*(\S*(?:\s+\S+)*)\s*$/, "$1"); + }; + + //check for use of "font" and "font-size" + parser.addListener("property", function(event){ + var parts, lastPart, pieces, lastPiece; + if (event.property.text === "font" || event.property.text === "font-family"){ + parts = event.value.parts; + lastPart = parts[parts.length - 1]; + + // With "font-family: sans serif;" (note the missing dash), the last part would be "serif", which *is* on the whitelist. + // In order to be able to catch this, we have to chop it up manually using slightly different rules. + pieces = event.value.text.split(','); + lastPiece = pieces[pieces.length - 1]; + + if (!onTheWhitelist(trim(lastPiece))) { + reporter.report("Did not find 'sans-serif', 'serif', or 'monospace' at the end of the " + event.property.text + " declaration.", lastPart.line, lastPart.col, rule); + } + } + }); + } + +}); \ No newline at end of file diff --git a/tests/rules/font-stack.js b/tests/rules/font-stack.js new file mode 100644 index 00000000..10c0f3db --- /dev/null +++ b/tests/rules/font-stack.js @@ -0,0 +1,45 @@ +(function(){ + + /*global YUITest, CSSLint*/ + var Assert = YUITest.Assert; + + YUITest.TestRunner.add(new YUITest.TestCase({ + + name: "font-stack Rule Errors", + + "omitting the generic family in a font-family declaration should result in a warning": function(){ + var result = CSSLint.verify(".foo{font-family: 'nop';}", {"font-stack": 1 }); + Assert.areEqual(1, result.messages.length); + Assert.areEqual("warning", result.messages[0].type); + Assert.areEqual("Did not find 'sans-serif', 'serif', or 'monospace' at the end of the font-family declaration.", result.messages[0].message); + }, + "omitting the generic family in a font declaration should result in a warning": function(){ + result = CSSLint.verify(".foo{font: normal small-caps 120%/120% 'nop';}", {"font-stack": 1 }); + Assert.areEqual(1, result.messages.length); + Assert.areEqual("warning", result.messages[0].type); + Assert.areEqual("Did not find 'sans-serif', 'serif', or 'monospace' at the end of the font declaration.", result.messages[0].message); + }, + + "forgetting the dash in 'sans-serif' in a font-family declaration should result in a warning": function(){ + var result = CSSLint.verify(".foo{font-family: 'nop', sans serif;}", {"font-stack": 1 }); + Assert.areEqual(1, result.messages.length); + Assert.areEqual("warning", result.messages[0].type); + Assert.areEqual("Did not find 'sans-serif', 'serif', or 'monospace' at the end of the font-family declaration.", result.messages[0].message); + }, + "forgetting the dash in 'sans-serif' in a font declaration should result in a warning": function(){ + result = CSSLint.verify(".foo{font: normal small-caps 120%/120% 'nop', sans serif;}", {"font-stack": 1 }); + Assert.areEqual(1, result.messages.length); + Assert.areEqual("warning", result.messages[0].type); + Assert.areEqual("Did not find 'sans-serif', 'serif', or 'monospace' at the end of the font declaration.", result.messages[0].message); + }, + + "including the generic family in a font-family declaration should not result in a warning": function(){ + var result = CSSLint.verify(".foo{font-family: 'nop', sans-serif;}", {"font-stack": 1 }); + Assert.areEqual(0, result.messages.length); + }, + "including the generic family in a font declaration should not result in a warning": function(){ + result = CSSLint.verify(".foo{font: normal small-caps 120%/120% 'nop', sans-serif;}", {"font-stack": 1 }); + Assert.areEqual(0, result.messages.length); + } + })); +})();