UNPKG

3.5 kBJavaScriptView Raw
1'use strict';
2
3const valueParser = require('postcss-value-parser');
4
5const declarationValueIndex = require('../../utils/declarationValueIndex');
6const getDeclarationValue = require('../../utils/getDeclarationValue');
7const isStandardSyntaxColorFunction = require('../../utils/isStandardSyntaxColorFunction');
8const report = require('../../utils/report');
9const ruleMessages = require('../../utils/ruleMessages');
10const setDeclarationValue = require('../../utils/setDeclarationValue');
11const { isValueFunction } = require('../../utils/typeGuards');
12const validateOptions = require('../../utils/validateOptions');
13
14const ruleName = 'color-function-notation';
15
16const messages = ruleMessages(ruleName, {
17 expected: (primary) => `Expected ${primary} color-function notation`,
18});
19
20const meta = {
21 url: 'https://stylelint.io/user-guide/rules/list/color-function-notation',
22};
23
24const LEGACY_FUNCS = new Set(['rgba', 'hsla']);
25const LEGACY_NOTATION_FUNCS = new Set(['rgb', 'rgba', 'hsl', 'hsla']);
26
27/** @type {import('stylelint').Rule} */
28const rule = (primary, _secondaryOptions, context) => {
29 return (root, result) => {
30 const validOptions = validateOptions(result, ruleName, {
31 actual: primary,
32 possible: ['modern', 'legacy'],
33 });
34
35 if (!validOptions) return;
36
37 root.walkDecls((decl) => {
38 let needsFix = false;
39 const parsedValue = valueParser(getDeclarationValue(decl));
40
41 parsedValue.walk((node) => {
42 if (!isValueFunction(node)) return;
43
44 if (!isStandardSyntaxColorFunction(node)) return;
45
46 const { value, sourceIndex, nodes } = node;
47
48 if (!LEGACY_NOTATION_FUNCS.has(value.toLowerCase())) return;
49
50 if (primary === 'modern' && !hasCommas(node)) return;
51
52 if (primary === 'legacy' && hasCommas(node)) return;
53
54 if (context.fix && primary === 'modern') {
55 let commaCount = 0;
56
57 // Convert punctuation
58 node.nodes = nodes.map((childNode) => {
59 if (isComma(childNode)) {
60 // Non-alpha commas to space and alpha commas to slashes
61 if (commaCount < 2) {
62 // @ts-expect-error -- TS2322: Type '"space"' is not assignable to type '"div"'.
63 childNode.type = 'space';
64 childNode.value = atLeastOneSpace(childNode.after);
65 commaCount++;
66 } else {
67 childNode.value = '/';
68 childNode.before = atLeastOneSpace(childNode.before);
69 childNode.after = atLeastOneSpace(childNode.after);
70 }
71 }
72
73 return childNode;
74 });
75
76 // Remove trailing 'a' from legacy function name
77 if (LEGACY_FUNCS.has(node.value.toLowerCase())) {
78 node.value = node.value.slice(0, -1);
79 }
80
81 needsFix = true;
82
83 return;
84 }
85
86 report({
87 message: messages.expected(primary),
88 node: decl,
89 index: declarationValueIndex(decl) + sourceIndex,
90 result,
91 ruleName,
92 });
93 });
94
95 if (needsFix) {
96 setDeclarationValue(decl, parsedValue.toString());
97 }
98 });
99 };
100};
101
102/**
103 * @param {string} whitespace
104 */
105function atLeastOneSpace(whitespace) {
106 return whitespace !== '' ? whitespace : ' ';
107}
108
109/**
110 * @param {import('postcss-value-parser').Node} node
111 * @returns {node is import('postcss-value-parser').DivNode}
112 */
113function isComma(node) {
114 return node.type === 'div' && node.value === ',';
115}
116
117/**
118 * @param {import('postcss-value-parser').FunctionNode} node
119 */
120function hasCommas(node) {
121 return node.nodes && node.nodes.some((childNode) => isComma(childNode));
122}
123
124rule.ruleName = ruleName;
125rule.messages = messages;
126rule.meta = meta;
127module.exports = rule;