+++ /dev/null
-"use strict";
-
-const findFontFamily = require("../../utils/findFontFamily");
-const isStandardSyntaxValue = require("../../utils/isStandardSyntaxValue");
-const isVariable = require("../../utils/isVariable");
-const keywordSets = require("../../reference/keywordSets");
-const report = require("../../utils/report");
-const ruleMessages = require("../../utils/ruleMessages");
-const validateOptions = require("../../utils/validateOptions");
-
-const ruleName = "font-family-name-quotes";
-
-const messages = ruleMessages(ruleName, {
- expected: family => `Expected quotes around "${family}"`,
- rejected: family => `Unexpected quotes around "${family}"`
-});
-
-function isSystemFontKeyword(font) {
- if (font.indexOf("-apple-") === 0) {
- return true;
- }
- if (font === "BlinkMacSystemFont") {
- return true;
- }
- return false;
-}
-
-// "To avoid mistakes in escaping, it is recommended to quote font family names
-// that contain white space, digits, or punctuation characters other than hyphens"
-// (https://www.w3.org/TR/CSS2/fonts.html#font-family-prop)
-function quotesRecommended(family) {
- return !/^[-a-zA-Z]+$/.test(family);
-}
-
-// Quotes are required if the family is not a valid CSS identifier
-// (regexes from https://mathiasbynens.be/notes/unquoted-font-family)
-function quotesRequired(family) {
- return family.split(/\s+/).some(word => {
- return (
- /^(-?\d|--)/.test(word) || !/^[-_a-zA-Z0-9\u00A0-\u10FFFF]+$/.test(word)
- );
- });
-}
-
-const rule = function(expectation) {
- return (root, result) => {
- const validOptions = validateOptions(result, ruleName, {
- actual: expectation,
- possible: [
- "always-where-required",
- "always-where-recommended",
- "always-unless-keyword"
- ]
- });
- if (!validOptions) {
- return;
- }
-
- root.walkDecls(/^font(-family)?$/i, decl => {
- const fontFamilies = findFontFamily(decl.value);
-
- if (fontFamilies.length === 0) {
- return;
- }
-
- fontFamilies.forEach(fontFamilyNode => {
- let rawFamily = fontFamilyNode.value;
-
- if (fontFamilyNode.quote) {
- rawFamily = fontFamilyNode.quote + rawFamily + fontFamilyNode.quote;
- }
-
- checkFamilyName(rawFamily, decl);
- });
- });
-
- function checkFamilyName(rawFamily, decl) {
- if (!isStandardSyntaxValue(rawFamily)) {
- return;
- }
- if (isVariable(rawFamily)) {
- return;
- }
-
- const hasQuotes = rawFamily[0] === "'" || rawFamily[0] === '"';
-
- // Clean the family of its quotes
- const family = rawFamily.replace(/^['"]|['"]$/g, "");
-
- // Disallow quotes around (case-insensitive) keywords
- // and system font keywords in all cases
- if (
- keywordSets.fontFamilyKeywords.has(family.toLowerCase()) ||
- isSystemFontKeyword(family)
- ) {
- if (hasQuotes) {
- return complain(messages.rejected(family), family, decl);
- }
- return;
- }
-
- const required = quotesRequired(family);
- const recommended = quotesRecommended(family);
-
- switch (expectation) {
- case "always-unless-keyword":
- if (!hasQuotes) {
- return complain(messages.expected(family), family, decl);
- }
- return;
-
- case "always-where-recommended":
- if (!recommended && hasQuotes) {
- return complain(messages.rejected(family), family, decl);
- }
- if (recommended && !hasQuotes) {
- return complain(messages.expected(family), family, decl);
- }
- return;
-
- case "always-where-required":
- if (!required && hasQuotes) {
- return complain(messages.rejected(family), family, decl);
- }
- if (required && !hasQuotes) {
- return complain(messages.expected(family), family, decl);
- }
- return;
- }
- }
-
- function complain(message, family, decl) {
- report({
- result,
- ruleName,
- message,
- node: decl,
- word: family
- });
- }
- };
-};
-
-rule.ruleName = ruleName;
-rule.messages = messages;
-module.exports = rule;