1 | 'use strict';
|
2 |
|
3 | const findFontFamily = require('../../utils/findFontFamily');
|
4 | const isStandardSyntaxValue = require('../../utils/isStandardSyntaxValue');
|
5 | const isVariable = require('../../utils/isVariable');
|
6 | const keywordSets = require('../../reference/keywordSets');
|
7 | const report = require('../../utils/report');
|
8 | const ruleMessages = require('../../utils/ruleMessages');
|
9 | const validateOptions = require('../../utils/validateOptions');
|
10 |
|
11 | const ruleName = 'font-family-name-quotes';
|
12 |
|
13 | const messages = ruleMessages(ruleName, {
|
14 | expected: (family) => `Expected quotes around "${family}"`,
|
15 | rejected: (family) => `Unexpected quotes around "${family}"`,
|
16 | });
|
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 | function isSystemFontKeyword(font) {
|
23 | if (font.startsWith('-apple-')) {
|
24 | return true;
|
25 | }
|
26 |
|
27 | if (font === 'BlinkMacSystemFont') {
|
28 | return true;
|
29 | }
|
30 |
|
31 | return false;
|
32 | }
|
33 |
|
34 |
|
35 |
|
36 |
|
37 |
|
38 |
|
39 |
|
40 |
|
41 |
|
42 | function quotesRecommended(family) {
|
43 | return !/^[-a-zA-Z]+$/.test(family);
|
44 | }
|
45 |
|
46 |
|
47 |
|
48 |
|
49 |
|
50 |
|
51 |
|
52 |
|
53 | function quotesRequired(family) {
|
54 | return family
|
55 | .split(/\s+/)
|
56 | .some((word) => /^(?:-?\d|--)/.test(word) || !/^[-\w\u{00A0}-\u{10FFFF}]+$/u.test(word));
|
57 | }
|
58 |
|
59 |
|
60 | const rule = (primary) => {
|
61 | return (root, result) => {
|
62 | const validOptions = validateOptions(result, ruleName, {
|
63 | actual: primary,
|
64 | possible: ['always-where-required', 'always-where-recommended', 'always-unless-keyword'],
|
65 | });
|
66 |
|
67 | if (!validOptions) {
|
68 | return;
|
69 | }
|
70 |
|
71 | root.walkDecls(/^font(-family)?$/i, (decl) => {
|
72 | const fontFamilies = findFontFamily(decl.value);
|
73 |
|
74 | if (fontFamilies.length === 0) {
|
75 | return;
|
76 | }
|
77 |
|
78 | fontFamilies.forEach((fontFamilyNode) => {
|
79 | let rawFamily = fontFamilyNode.value;
|
80 |
|
81 | if ('quote' in fontFamilyNode) {
|
82 | rawFamily = fontFamilyNode.quote + rawFamily + fontFamilyNode.quote;
|
83 | }
|
84 |
|
85 | checkFamilyName(rawFamily, decl);
|
86 | });
|
87 | });
|
88 |
|
89 | |
90 |
|
91 |
|
92 |
|
93 | function checkFamilyName(rawFamily, decl) {
|
94 | if (!isStandardSyntaxValue(rawFamily)) {
|
95 | return;
|
96 | }
|
97 |
|
98 | if (isVariable(rawFamily)) {
|
99 | return;
|
100 | }
|
101 |
|
102 | const hasQuotes = rawFamily.startsWith("'") || rawFamily.startsWith('"');
|
103 |
|
104 |
|
105 | const family = rawFamily.replace(/^['"]|['"]$/g, '');
|
106 |
|
107 |
|
108 |
|
109 | if (keywordSets.fontFamilyKeywords.has(family.toLowerCase()) || isSystemFontKeyword(family)) {
|
110 | if (hasQuotes) {
|
111 | return complain(messages.rejected(family), family, decl);
|
112 | }
|
113 |
|
114 | return;
|
115 | }
|
116 |
|
117 | const required = quotesRequired(family);
|
118 | const recommended = quotesRecommended(family);
|
119 |
|
120 | switch (primary) {
|
121 | case 'always-unless-keyword':
|
122 | if (!hasQuotes) {
|
123 | return complain(messages.expected(family), family, decl);
|
124 | }
|
125 |
|
126 | return;
|
127 |
|
128 | case 'always-where-recommended':
|
129 | if (!recommended && hasQuotes) {
|
130 | return complain(messages.rejected(family), family, decl);
|
131 | }
|
132 |
|
133 | if (recommended && !hasQuotes) {
|
134 | return complain(messages.expected(family), family, decl);
|
135 | }
|
136 |
|
137 | return;
|
138 |
|
139 | case 'always-where-required':
|
140 | if (!required && hasQuotes) {
|
141 | return complain(messages.rejected(family), family, decl);
|
142 | }
|
143 |
|
144 | if (required && !hasQuotes) {
|
145 | return complain(messages.expected(family), family, decl);
|
146 | }
|
147 | }
|
148 | }
|
149 |
|
150 | |
151 |
|
152 |
|
153 |
|
154 |
|
155 | function complain(message, family, decl) {
|
156 | report({
|
157 | result,
|
158 | ruleName,
|
159 | message,
|
160 | node: decl,
|
161 | word: family,
|
162 | });
|
163 | }
|
164 | };
|
165 | };
|
166 |
|
167 | rule.ruleName = ruleName;
|
168 | rule.messages = messages;
|
169 | module.exports = rule;
|