UNPKG

6.27 kBJavaScriptView Raw
1// @ts-nocheck
2
3'use strict';
4
5const _ = require('lodash');
6const declarationValueIndex = require('../../utils/declarationValueIndex');
7const getUnitFromValueNode = require('../../utils/getUnitFromValueNode');
8const isCounterIncrementCustomIdentValue = require('../../utils/isCounterIncrementCustomIdentValue');
9const isCounterResetCustomIdentValue = require('../../utils/isCounterResetCustomIdentValue');
10const isStandardSyntaxValue = require('../../utils/isStandardSyntaxValue');
11const keywordSets = require('../../reference/keywordSets');
12const matchesStringOrRegExp = require('../../utils/matchesStringOrRegExp');
13const report = require('../../utils/report');
14const ruleMessages = require('../../utils/ruleMessages');
15const validateOptions = require('../../utils/validateOptions');
16const valueParser = require('postcss-value-parser');
17
18const ruleName = 'value-keyword-case';
19
20const messages = ruleMessages(ruleName, {
21 expected: (actual, expected) => `Expected "${actual}" to be "${expected}"`,
22});
23
24// Operators are interpreted as "words" by the value parser, so we want to make sure to ignore them.
25const ignoredCharacters = new Set(['+', '-', '/', '*', '%']);
26const gridRowProps = new Set(['grid-row', 'grid-row-start', 'grid-row-end']);
27const gridColumnProps = new Set(['grid-column', 'grid-column-start', 'grid-column-end']);
28
29const mapLowercaseKeywordsToCamelCase = new Map();
30
31keywordSets.camelCaseKeywords.forEach((func) => {
32 mapLowercaseKeywordsToCamelCase.set(func.toLowerCase(), func);
33});
34
35function rule(expectation, options, context) {
36 return (root, result) => {
37 const validOptions = validateOptions(
38 result,
39 ruleName,
40 {
41 actual: expectation,
42 possible: ['lower', 'upper'],
43 },
44 {
45 actual: options,
46 possible: {
47 ignoreProperties: [_.isString, _.isRegExp],
48 ignoreKeywords: [_.isString, _.isRegExp],
49 ignoreFunctions: [_.isString, _.isRegExp],
50 },
51 optional: true,
52 },
53 );
54
55 if (!validOptions) {
56 return;
57 }
58
59 root.walkDecls((decl) => {
60 const prop = decl.prop;
61 const propLowerCase = decl.prop.toLowerCase();
62 const value = decl.value;
63
64 const parsed = valueParser(decl.raws.value ? decl.raws.value.raw : decl.value);
65
66 let needFix = false;
67
68 parsed.walk((node) => {
69 const valueLowerCase = node.value.toLowerCase();
70
71 // Ignore system colors
72 if (keywordSets.systemColors.has(valueLowerCase)) {
73 return;
74 }
75
76 // Ignore keywords within `url` and `var` function
77 if (
78 node.type === 'function' &&
79 (valueLowerCase === 'url' ||
80 valueLowerCase === 'var' ||
81 valueLowerCase === 'counter' ||
82 valueLowerCase === 'counters' ||
83 valueLowerCase === 'attr')
84 ) {
85 return false;
86 }
87
88 // ignore keywords within ignoreFunctions functions
89
90 const ignoreFunctions = (options && options.ignoreFunctions) || [];
91
92 if (
93 node.type === 'function' &&
94 ignoreFunctions.length > 0 &&
95 matchesStringOrRegExp(valueLowerCase, ignoreFunctions)
96 ) {
97 return false;
98 }
99
100 const keyword = node.value;
101
102 // Ignore css variables, and hex values, and math operators, and sass interpolation
103 if (
104 node.type !== 'word' ||
105 !isStandardSyntaxValue(node.value) ||
106 value.includes('#') ||
107 ignoredCharacters.has(keyword) ||
108 getUnitFromValueNode(node)
109 ) {
110 return;
111 }
112
113 if (
114 propLowerCase === 'animation' &&
115 !keywordSets.animationShorthandKeywords.has(valueLowerCase) &&
116 !keywordSets.animationNameKeywords.has(valueLowerCase)
117 ) {
118 return;
119 }
120
121 if (
122 propLowerCase === 'animation-name' &&
123 !keywordSets.animationNameKeywords.has(valueLowerCase)
124 ) {
125 return;
126 }
127
128 if (
129 propLowerCase === 'font' &&
130 !keywordSets.fontShorthandKeywords.has(valueLowerCase) &&
131 !keywordSets.fontFamilyKeywords.has(valueLowerCase)
132 ) {
133 return;
134 }
135
136 if (
137 propLowerCase === 'font-family' &&
138 !keywordSets.fontFamilyKeywords.has(valueLowerCase)
139 ) {
140 return;
141 }
142
143 if (
144 propLowerCase === 'counter-increment' &&
145 isCounterIncrementCustomIdentValue(valueLowerCase)
146 ) {
147 return;
148 }
149
150 if (propLowerCase === 'counter-reset' && isCounterResetCustomIdentValue(valueLowerCase)) {
151 return;
152 }
153
154 if (gridRowProps.has(propLowerCase) && !keywordSets.gridRowKeywords.has(valueLowerCase)) {
155 return;
156 }
157
158 if (
159 gridColumnProps.has(propLowerCase) &&
160 !keywordSets.gridColumnKeywords.has(valueLowerCase)
161 ) {
162 return;
163 }
164
165 if (propLowerCase === 'grid-area' && !keywordSets.gridAreaKeywords.has(valueLowerCase)) {
166 return;
167 }
168
169 if (
170 propLowerCase === 'list-style' &&
171 !keywordSets.listStyleShorthandKeywords.has(valueLowerCase) &&
172 !keywordSets.listStyleTypeKeywords.has(valueLowerCase)
173 ) {
174 return;
175 }
176
177 if (
178 propLowerCase === 'list-style-type' &&
179 !keywordSets.listStyleTypeKeywords.has(valueLowerCase)
180 ) {
181 return;
182 }
183
184 const ignoreKeywords = (options && options.ignoreKeywords) || [];
185 const ignoreProperties = (options && options.ignoreProperties) || [];
186
187 if (ignoreKeywords.length > 0 && matchesStringOrRegExp(keyword, ignoreKeywords)) {
188 return;
189 }
190
191 if (ignoreProperties.length > 0 && matchesStringOrRegExp(prop, ignoreProperties)) {
192 return;
193 }
194
195 const keywordLowerCase = keyword.toLocaleLowerCase();
196 let expectedKeyword = null;
197
198 if (expectation === 'lower' && mapLowercaseKeywordsToCamelCase.has(keywordLowerCase)) {
199 expectedKeyword = mapLowercaseKeywordsToCamelCase.get(keywordLowerCase);
200 } else if (expectation === 'lower') {
201 expectedKeyword = keyword.toLowerCase();
202 } else {
203 expectedKeyword = keyword.toUpperCase();
204 }
205
206 if (keyword === expectedKeyword) {
207 return;
208 }
209
210 if (context.fix) {
211 needFix = true;
212 node.value = expectedKeyword;
213
214 return;
215 }
216
217 report({
218 message: messages.expected(keyword, expectedKeyword),
219 node: decl,
220 index: declarationValueIndex(decl) + node.sourceIndex,
221 result,
222 ruleName,
223 });
224 });
225
226 if (context.fix && needFix) {
227 decl.value = parsed.toString();
228 }
229 });
230 };
231}
232
233rule.ruleName = ruleName;
234rule.messages = messages;
235module.exports = rule;