diff --git a/src/rules/generic-font-family.js b/src/rules/generic-font-family.js new file mode 100644 index 00000000..ee840ad1 --- /dev/null +++ b/src/rules/generic-font-family.js @@ -0,0 +1,93 @@ +/*global CSSLint*/ +/* + * Rule: Always specify generic font-family alternative + */ +CSSLint.addRule({ + + //rule information + id: "generic-font-family", + name: "Generic font family", + desc: "Check for generic font family presence", + browsers: "All", + + //initialization + init: function(parser, reporter){ + + var rule = this, + genericFonts = { + 'serif': 1, + 'sans-serif': 2, + 'fantasy': 3, + 'monospace': 4, + 'cursive': 5 + }, + fontFaceRule = false; + + // Disable checking inside @font-face block + parser.addListener("startfontface", function(){ + fontFaceRule = true; + }); + + parser.addListener("endfontface", function(){ + fontFaceRule = false; + }); + + parser.addListener("property", function(event){ + + var property = event.property, + propertyName = property.text.toLowerCase(), + valueParts = event.value.parts, + hasGeneric = false, + i, l, value, type, fontName; + + // Check if we inside @font-face rule + if(fontFaceRule) { + return; + } + + if(propertyName == "font" || propertyName == "font-family") { + + // Special case "font: inherit" + if(propertyName == 'font' && + valueParts.length == 1 && + valueParts[0].type == 'identifier' && + valueParts[0].text == 'inherit') { + return; + } + + for(i = 0, l = valueParts.length; i < l; i++) { + value = valueParts[i]; + type = value.type; + if(type == 'identifier') { + if(value.text in genericFonts) { + hasGeneric = true; + break; + } + } else if(type == 'string') { + + // Quoted generic are not generics + fontName = value.text.substr(1, value.text.length - 2); + if(fontName in genericFonts) { + hasGeneric = true; + reporter.report("Generic font-family specified as string, not as an identifier (" + + value.text + " instead of " + fontName + "). Consider removing quotes.", + value.line, + value.col, + rule); + } + } + } + + if(!hasGeneric) { + reporter.report("No generic font-family alternative specified. Consider adding one of the following:\n" + + "serif, sans-serif, monospace, fantasy, cursive.", + event.line, + event.col, + rule); + } + } + }); + + } + +}); \ No newline at end of file diff --git a/tests/rules/generic-font-family.js b/tests/rules/generic-font-family.js new file mode 100644 index 00000000..1243ab5c --- /dev/null +++ b/tests/rules/generic-font-family.js @@ -0,0 +1,149 @@ +(function(){ + + /*global YUITest, CSSLint*/ + var + Assert = YUITest.Assert, + testSpec = { "generic-font-family": 1 }, + noGenericsError = "No generic font-family alternative specified. Consider adding one of the following:\n" + + "serif, sans-serif, monospace, fantasy, cursive."; + + function quotedGenericError(fontFace, quote) { + return "Generic font-family specified as string, not as an identifier (" + + quote + fontFace + quote + " instead of " + fontFace + "). Consider removing quotes."; + } + + function mkTestSnippet(font) { + return ".c { font: 14px " + font + " }"; + } + + function mkFontFamilySnippet(font) { + return ".c { font-family: " + font + " }"; + } + + YUITest.TestRunner.add(new YUITest.TestCase({ + + name: "Generic font family checker - font property", + + "No any generic causes error": function(){ + var result = CSSLint.verify(mkTestSnippet('Tahoma'), testSpec); + + Assert.areEqual(1, result.messages.length); + Assert.areEqual(noGenericsError, result.messages[0].message); + }, + + "Single-quoted generic causes error": function(){ + var result = CSSLint.verify(mkTestSnippet("Tahoma, 'serif'"), testSpec); + + Assert.areEqual(1, result.messages.length); + Assert.areEqual(quotedGenericError('serif', '\''), result.messages[0].message); + }, + + "Double-quoted generic causes error": function(){ + var result = CSSLint.verify(mkTestSnippet("Tahoma, \"serif\""), testSpec); + + Assert.areEqual(1, result.messages.length); + Assert.areEqual(quotedGenericError('serif', '\"'), result.messages[0].message); + }, + + "No error with serif": function() { + var result = CSSLint.verify(mkTestSnippet("Tahoma, serif"), testSpec); + + Assert.areEqual(0, result.messages.length); + }, + + "No error with sans-serif": function() { + var result = CSSLint.verify(mkTestSnippet("Tahoma, sans-serif"), testSpec); + + Assert.areEqual(0, result.messages.length); + }, + + "No error with cursive": function() { + var result = CSSLint.verify(mkTestSnippet("Tahoma, cursive"), testSpec); + + Assert.areEqual(0, result.messages.length); + }, + + "No error with monospace": function() { + var result = CSSLint.verify(mkTestSnippet("Tahoma, monospace"), testSpec); + + Assert.areEqual(0, result.messages.length); + }, + + "No error with fantasy": function() { + var result = CSSLint.verify(mkTestSnippet("Tahoma, fantasy"), testSpec); + + Assert.areEqual(0, result.messages.length); + }, + + "'font: inherit' causes no error": function() { + var result = CSSLint.verify(".c { font: inherit }", testSpec); + + Assert.areEqual(0, result.messages.length); + } + + })); + + YUITest.TestRunner.add(new YUITest.TestCase({ + + name: "Generic font family checker - font-family property", + + "No any generic causes error": function(){ + var result = CSSLint.verify(mkFontFamilySnippet('Tahoma'), testSpec); + + Assert.areEqual(1, result.messages.length); + Assert.areEqual(noGenericsError, result.messages[0].message); + }, + + "Single-quoted generic causes error": function(){ + var result = CSSLint.verify(mkFontFamilySnippet("Tahoma, 'serif'"), testSpec); + + Assert.areEqual(1, result.messages.length); + Assert.areEqual(quotedGenericError('serif', '\''), result.messages[0].message); + }, + + "Double-quoted generic causes error": function(){ + var result = CSSLint.verify(mkFontFamilySnippet("Tahoma, \"serif\""), testSpec); + + Assert.areEqual(1, result.messages.length); + Assert.areEqual(quotedGenericError('serif', '\"'), result.messages[0].message); + }, + + "No error with serif": function() { + var result = CSSLint.verify(mkFontFamilySnippet("Tahoma, serif"), testSpec); + + Assert.areEqual(0, result.messages.length); + }, + + "No error with sans-serif": function() { + var result = CSSLint.verify(mkFontFamilySnippet("Tahoma, sans-serif"), testSpec); + + Assert.areEqual(0, result.messages.length); + }, + + "No error with cursive": function() { + var result = CSSLint.verify(mkFontFamilySnippet("Tahoma, cursive"), testSpec); + + Assert.areEqual(0, result.messages.length); + }, + + "No error with monospace": function() { + var result = CSSLint.verify(mkFontFamilySnippet("Tahoma, monospace"), testSpec); + + Assert.areEqual(0, result.messages.length); + }, + + "No error with fantasy": function() { + var result = CSSLint.verify(mkFontFamilySnippet("Tahoma, fantasy"), testSpec); + + Assert.areEqual(0, result.messages.length); + }, + + "No detection inside @font-face rule": function() { + var result = CSSLint.verify("@font-face { font-family: MyFont; }", testSpec); + + Assert.areEqual(0, result.messages.length); + } + + })); + +})();