UNPKG

4.47 kBJavaScriptView Raw
1'use strict';
2
3const declarationValueIndex = require('../../utils/declarationValueIndex');
4const isStandardSyntaxFunction = require('../../utils/isStandardSyntaxFunction');
5const isStandardSyntaxValue = require('../../utils/isStandardSyntaxValue');
6const keywordSets = require('../../reference/keywordSets');
7const namedColorDataHex = require('../../reference/namedColorData');
8const optionsMatches = require('../../utils/optionsMatches');
9const propertySets = require('../../reference/propertySets');
10const report = require('../../utils/report');
11const ruleMessages = require('../../utils/ruleMessages');
12const validateOptions = require('../../utils/validateOptions');
13const valueParser = require('postcss-value-parser');
14const { isRegExp, isString } = require('../../utils/validateTypes');
15
16const generateColorFuncs = require('./generateColorFuncs');
17
18const ruleName = 'color-named';
19
20const messages = ruleMessages(ruleName, {
21 expected: (named, original) => `Expected "${original}" to be "${named}"`,
22 rejected: (named) => `Unexpected named color "${named}"`,
23});
24
25// Todo tested on case insensitivity
26const NODE_TYPES = new Set(['word', 'function']);
27
28/** @type {import('stylelint').Rule} */
29const rule = (primary, secondaryOptions) => {
30 return (root, result) => {
31 const validOptions = validateOptions(
32 result,
33 ruleName,
34 {
35 actual: primary,
36 possible: ['never', 'always-where-possible'],
37 },
38 {
39 actual: secondaryOptions,
40 possible: {
41 ignoreProperties: [isString, isRegExp],
42 ignore: ['inside-function'],
43 },
44 optional: true,
45 },
46 );
47
48 if (!validOptions) {
49 return;
50 }
51
52 const namedColors = Object.keys(namedColorDataHex);
53
54 /** @type {Record<string, { hex: string[], func: string[] }>} */
55 const namedColorData = {};
56
57 namedColors.forEach((name) => {
58 const hex = namedColorDataHex[name];
59
60 namedColorData[name] = {
61 hex,
62 func: generateColorFuncs(hex[0]),
63 };
64 });
65
66 root.walkDecls((decl) => {
67 if (propertySets.acceptCustomIdents.has(decl.prop)) {
68 return;
69 }
70
71 // Return early if the property is to be ignored
72 if (optionsMatches(secondaryOptions, 'ignoreProperties', decl.prop)) {
73 return;
74 }
75
76 valueParser(decl.value).walk((node) => {
77 const value = node.value;
78 const type = node.type;
79 const sourceIndex = node.sourceIndex;
80
81 if (optionsMatches(secondaryOptions, 'ignore', 'inside-function') && type === 'function') {
82 return false;
83 }
84
85 if (!isStandardSyntaxFunction(node)) {
86 return false;
87 }
88
89 if (!isStandardSyntaxValue(value)) {
90 return;
91 }
92
93 // Return early if neither a word nor a function
94 if (!NODE_TYPES.has(type)) {
95 return;
96 }
97
98 // Check for named colors for "never" option
99 if (primary === 'never' && type === 'word' && namedColors.includes(value.toLowerCase())) {
100 complain(messages.rejected(value), decl, declarationValueIndex(decl) + sourceIndex);
101
102 return;
103 }
104
105 // Check "always-where-possible" option ...
106 if (primary !== 'always-where-possible') {
107 return;
108 }
109
110 // First by checking for alternative color function representations ...
111 if (type === 'function' && keywordSets.colorFunctionNames.has(value.toLowerCase())) {
112 // Remove all spaces to match what's in `representations`
113 const normalizedFunctionString = valueParser.stringify(node).replace(/\s+/g, '');
114
115 for (const namedColor of namedColors) {
116 if (namedColorData[namedColor].func.includes(normalizedFunctionString.toLowerCase())) {
117 complain(
118 messages.expected(namedColor, normalizedFunctionString),
119 decl,
120 declarationValueIndex(decl) + sourceIndex,
121 );
122
123 return; // Exit as soon as a problem is found
124 }
125 }
126
127 return;
128 }
129
130 // Then by checking for alternative hex representations
131
132 for (const namedColor of namedColors) {
133 if (namedColorData[namedColor].hex.includes(value.toLowerCase())) {
134 complain(
135 messages.expected(namedColor, value),
136 decl,
137 declarationValueIndex(decl) + sourceIndex,
138 );
139
140 return; // Exit as soon as a problem is found
141 }
142 }
143 });
144 });
145
146 /**
147 * @param {string} message
148 * @param {import('postcss').Node} node
149 * @param {number} index
150 */
151 function complain(message, node, index) {
152 report({
153 result,
154 ruleName,
155 message,
156 node,
157 index,
158 });
159 }
160 };
161};
162
163rule.ruleName = ruleName;
164rule.messages = messages;
165module.exports = rule;