diff --git a/src/rules/check-fonts-alternatives.js b/src/rules/check-fonts-alternatives.js new file mode 100644 index 00000000..8dc3ee0a --- /dev/null +++ b/src/rules/check-fonts-alternatives.js @@ -0,0 +1,114 @@ +/*global CSSLint*/ +CSSLint.addRule({ + + //rule information + id: "check-fonts-alternatives", + name: "Check font alternatives", + desc: "Check font alternatives for Mac OS (OS X, iOS)", + browsers: "All", + + //initialization + init: function(parser, reporter){ + var rule = this, + macMap = { + "Tahoma": "Geneva", + "Lucida Console": "Monaco", + "Lucida Sans Unicode": "Lucida Grande", + "Palatino Linotype": "Palatino", + "Book Antiqua": "Palatino", + "Wingdings": "Zapf Dingbats", + "MS Sans Serif": "Geneva", + "MS Serif": "New York" + }, + skipIdentifiers = { + // font-style related + "normal": 1, + "italic": 2, + "oblique": 3, + // any + "inherit": 4, + // font-variant related + "small-caps": 5, + // font-weight related + "bold": 6, + "bolder": 7, + "lighter": 8, + // font-size related + "xx-small": 9, + "x-small": 10, + "small": 11, + "medium": 12, + "large": 13, + "x-large": 14, + "xx-large": 15, + "larger": 16, + "smaller": 17 + }, + 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){ + + // font: [font-style||font-variant||font-weight] font-size[/line-height] font-family | inherit + // font-style: normal | italic | oblique | inherit + // font-variant: normal | small-caps | inherit + // font-weight: bold|bolder|lighter|normal|100|200|300|400|500|600|700|800|900 + // font-size: absolute | relative | value | precents | inherit + // font-size-absolute: xx-small, x-small, small, medium, large, x-large, xx-large + // font-size-relative: larger | smaller + + var property = event.property, + propertyName = property.text.toLowerCase(), + valueParts = event.value.parts, + currentRuleFonts = {}, + idStack = [], + i, l, value; + + // Skip any detection if we inside @font-face block + if(fontFaceRule) { + return; + } + + if(propertyName == "font" || propertyName == "font-family") { + for(i = 0, l = valueParts.length; i < l; i++) { + value = valueParts[i]; + if(value.type == 'identifier') { + // skip not-font-family-related identifiers + if(value.text in skipIdentifiers) { + continue; + } + idStack.push(value.text); + } else if(value.type == 'operator' && value.text == ',') { + currentRuleFonts[idStack.join(' ')] = { line: value.line, col: value.col }; + idStack = []; + } + } + if(idStack.length > 0) { + currentRuleFonts[idStack.join(' ')] = { line: value.line, col: value.col }; + } + + for(i in currentRuleFonts) { + if(currentRuleFonts.hasOwnProperty(i)) { + if(i in macMap && currentRuleFonts[macMap[i]] === undefined) { + reporter.warn("No MacOS-alternative for font '" + i + "'. " + + "Consider adding '" + macMap[i] + "'.", + currentRuleFonts[i].line, + currentRuleFonts[i].col, + rule); + } + } + } + } + }); + + } + +}); \ No newline at end of file diff --git a/tests/rules/check-fonts-alternatives.js b/tests/rules/check-fonts-alternatives.js new file mode 100644 index 00000000..3251ec56 --- /dev/null +++ b/tests/rules/check-fonts-alternatives.js @@ -0,0 +1,152 @@ +(function(){ + + /*global YUITest, CSSLint*/ + var + Assert = YUITest.Assert, + testSpec = { "check-fonts-alternatives": 1 }; + + function mkErrorMessage(font, alternative) { + return "No MacOS-alternative for font '" + font + "'. Consider adding '" + alternative + "'."; + } + + YUITest.TestRunner.add(new YUITest.TestCase({ + + name: "Mac alternative font checker - font property", + + "No erros when no Mac alternative needed": function(){ + var result = CSSLint.verify(".c { font-family: Arial }", testSpec); + + Assert.areEqual(0, result.messages.length); + }, + + "No erros when Mac alternative is specified": function(){ + var result = CSSLint.verify(".c { font: 14px Tahoma, Geneva }", testSpec); + + Assert.areEqual(0, result.messages.length); + }, + + "Font size + family form": function(){ + var result = CSSLint.verify(".c { font: 14px Lucida Console }", testSpec); + + Assert.areEqual(1, result.messages.length); + Assert.areEqual(mkErrorMessage("Lucida Console", "Monaco"), result.messages[0].message); + }, + + "Font variant, size, family form": function(){ + var result = CSSLint.verify(".c { font: italic 14px MS Serif }", testSpec); + + Assert.areEqual(1, result.messages.length); + Assert.areEqual(mkErrorMessage("MS Serif", "New York"), result.messages[0].message); + }, + + "Font weight, size, family form": function(){ + var result = CSSLint.verify(".c { font: bold 14px MS Serif }", testSpec); + + Assert.areEqual(1, result.messages.length); + Assert.areEqual(mkErrorMessage("MS Serif", "New York"), result.messages[0].message); + }, + + "Font weight as number, size + family form": function(){ + var result = CSSLint.verify(".c { font: 500 14px MS Serif }", testSpec); + + Assert.areEqual(1, result.messages.length); + Assert.areEqual(mkErrorMessage("MS Serif", "New York"), result.messages[0].message); + }, + + "Font size/height + family form": function(){ + var result = CSSLint.verify(".c { font: 14px/20px MS Serif }", testSpec); + + Assert.areEqual(1, result.messages.length); + Assert.areEqual(mkErrorMessage("MS Serif", "New York"), result.messages[0].message); + }, + + "Font size (absolute) + family form": function(){ + var result = CSSLint.verify(".c { font: xx-small MS Serif }", testSpec); + + Assert.areEqual(1, result.messages.length); + Assert.areEqual(mkErrorMessage("MS Serif", "New York"), result.messages[0].message); + }, + + "Font size (relative) + family form": function(){ + var result = CSSLint.verify(".c { font: larger MS Serif }", testSpec); + + Assert.areEqual(1, result.messages.length); + Assert.areEqual(mkErrorMessage("MS Serif", "New York"), result.messages[0].message); + }, + + "Font - badly formed rule": function(){ + // This is equal to identifier 'Tahoma Tahoma Tahoma' + var result = CSSLint.verify(".c { font: 14px Tahoma Tahoma Tahoma }", testSpec); + + Assert.areEqual(0, result.messages.length); + }, + + "Multiple font families without an alternative": function(){ + var result = CSSLint.verify(".c { font: 14px MS Serif, Tahoma, Verdana }", testSpec); + + Assert.areEqual(2, result.messages.length); + Assert.areEqual(mkErrorMessage("MS Serif", "New York"), result.messages[0].message); + Assert.areEqual(mkErrorMessage("Tahoma", "Geneva"), result.messages[1].message); + }, + + "Font = inherit": function() { + var result = CSSLint.verify(".c { font: inherit }", testSpec); + + Assert.areEqual(0, result.messages.length); + } + + })); + + YUITest.TestRunner.add(new YUITest.TestCase({ + + name: "Mac alternative font checker - font-family property", + + "No erros when no Mac alternative needed": function(){ + var result = CSSLint.verify(".c { font-family: Arial }", testSpec); + + Assert.areEqual(0, result.messages.length); + }, + + "No erros when Mac alternative is specified": function(){ + var result = CSSLint.verify(".c { font-family: Tahoma, Geneva }", testSpec); + + Assert.areEqual(0, result.messages.length); + }, + + "No alternative specified, font-family without space": function(){ + var result = CSSLint.verify(".c { font-family: Tahoma }", testSpec); + + Assert.areEqual(1, result.messages.length); + Assert.areEqual(mkErrorMessage("Tahoma", "Geneva"), result.messages[0].message); + }, + + "No alternative specified, font-family with space": function(){ + var result = CSSLint.verify(".c { font-family: MS Serif }", testSpec); + + Assert.areEqual(1, result.messages.length); + Assert.areEqual(mkErrorMessage("MS Serif", "New York"), result.messages[0].message); + }, + + "No alternative specified, font-family with space, multiple families": function(){ + var result = CSSLint.verify(".c { font-family: MS Serif, Tahoma, Arial }", testSpec); + + Assert.areEqual(2, result.messages.length); + Assert.areEqual(mkErrorMessage("MS Serif", "New York"), result.messages[0].message); + Assert.areEqual(mkErrorMessage("Tahoma", "Geneva"), result.messages[1].message); + }, + + "No detection inside @font-face rule 1": function() { + var result = CSSLint.verify("@font-face { font-family: MyFont; }", testSpec); + + Assert.areEqual(0, result.messages.length); + }, + + "No detection inside @font-face rule 2": function() { + var result = CSSLint.verify("@font-face { font-family: Tahoma; }", testSpec); + + Assert.areEqual(0, result.messages.length); + } + + })); + +})(); \ No newline at end of file