1 | 'use strict';
|
2 |
|
3 | const valueParser = require('postcss-value-parser');
|
4 |
|
5 | const declarationValueIndex = require('../../utils/declarationValueIndex');
|
6 | const getDeclarationValue = require('../../utils/getDeclarationValue');
|
7 | const isStandardSyntaxValue = require('../../utils/isStandardSyntaxValue');
|
8 | const optionsMatches = require('../../utils/optionsMatches');
|
9 | const report = require('../../utils/report');
|
10 | const ruleMessages = require('../../utils/ruleMessages');
|
11 | const setDeclarationValue = require('../../utils/setDeclarationValue');
|
12 | const validateOptions = require('../../utils/validateOptions');
|
13 | const { isRegExp, isString, assert } = require('../../utils/validateTypes');
|
14 |
|
15 | const ruleName = 'alpha-value-notation';
|
16 |
|
17 | const messages = ruleMessages(ruleName, {
|
18 | expected: (unfixed, fixed) => `Expected "${unfixed}" to be "${fixed}"`,
|
19 | });
|
20 |
|
21 | const meta = {
|
22 | url: 'https://stylelint.io/user-guide/rules/list/alpha-value-notation',
|
23 | };
|
24 |
|
25 | const ALPHA_PROPS = new Set(['opacity', 'shape-image-threshold']);
|
26 | const ALPHA_FUNCS = new Set(['hsl', 'hsla', 'hwb', 'lab', 'lch', 'rgb', 'rgba']);
|
27 |
|
28 |
|
29 | const rule = (primary, secondaryOptions, context) => {
|
30 | return (root, result) => {
|
31 | const validOptions = validateOptions(
|
32 | result,
|
33 | ruleName,
|
34 | {
|
35 | actual: primary,
|
36 | possible: ['number', 'percentage'],
|
37 | },
|
38 | {
|
39 | actual: secondaryOptions,
|
40 | possible: {
|
41 | exceptProperties: [isString, isRegExp],
|
42 | },
|
43 | optional: true,
|
44 | },
|
45 | );
|
46 |
|
47 | if (!validOptions) return;
|
48 |
|
49 | const optionFuncs = Object.freeze({
|
50 | number: {
|
51 | expFunc: isNumber,
|
52 | fixFunc: asNumber,
|
53 | },
|
54 | percentage: {
|
55 | expFunc: isPercentage,
|
56 | fixFunc: asPercentage,
|
57 | },
|
58 | });
|
59 |
|
60 | root.walkDecls((decl) => {
|
61 | let needsFix = false;
|
62 | const parsedValue = valueParser(getDeclarationValue(decl));
|
63 |
|
64 | parsedValue.walk((node) => {
|
65 |
|
66 | let alpha;
|
67 |
|
68 | if (ALPHA_PROPS.has(decl.prop.toLowerCase())) {
|
69 | alpha = findAlphaInValue(node);
|
70 | } else {
|
71 | if (node.type !== 'function') return;
|
72 |
|
73 | if (!ALPHA_FUNCS.has(node.value.toLowerCase())) return;
|
74 |
|
75 | alpha = findAlphaInFunction(node);
|
76 | }
|
77 |
|
78 | if (!alpha) return;
|
79 |
|
80 | const { value } = alpha;
|
81 |
|
82 | if (!isStandardSyntaxValue(value)) return;
|
83 |
|
84 | if (!isNumber(value) && !isPercentage(value)) return;
|
85 |
|
86 |
|
87 | let expectation = primary;
|
88 |
|
89 | if (optionsMatches(secondaryOptions, 'exceptProperties', decl.prop)) {
|
90 | if (expectation === 'number') {
|
91 | expectation = 'percentage';
|
92 | } else if (expectation === 'percentage') {
|
93 | expectation = 'number';
|
94 | }
|
95 | }
|
96 |
|
97 | if (optionFuncs[expectation].expFunc(value)) return;
|
98 |
|
99 | const fixed = optionFuncs[expectation].fixFunc(value);
|
100 | const unfixed = value;
|
101 |
|
102 | if (context.fix) {
|
103 | alpha.value = String(fixed);
|
104 | needsFix = true;
|
105 |
|
106 | return;
|
107 | }
|
108 |
|
109 | report({
|
110 | message: messages.expected(unfixed, fixed),
|
111 | node: decl,
|
112 | index: declarationValueIndex(decl) + alpha.sourceIndex,
|
113 | result,
|
114 | ruleName,
|
115 | });
|
116 | });
|
117 |
|
118 | if (needsFix) {
|
119 | setDeclarationValue(decl, parsedValue.toString());
|
120 | }
|
121 | });
|
122 | };
|
123 | };
|
124 |
|
125 |
|
126 |
|
127 |
|
128 |
|
129 | function asPercentage(value) {
|
130 | const number = Number(value);
|
131 |
|
132 | return `${Number((number * 100).toPrecision(3))}%`;
|
133 | }
|
134 |
|
135 |
|
136 |
|
137 |
|
138 |
|
139 | function asNumber(value) {
|
140 | const dimension = valueParser.unit(value);
|
141 |
|
142 | assert(dimension);
|
143 |
|
144 | const number = Number(dimension.number);
|
145 |
|
146 | return Number((number / 100).toPrecision(3)).toString();
|
147 | }
|
148 |
|
149 |
|
150 |
|
151 |
|
152 |
|
153 |
|
154 | function findAlphaInValue(node) {
|
155 | return node.type === 'word' || node.type === 'function' ? node : undefined;
|
156 | }
|
157 |
|
158 |
|
159 |
|
160 |
|
161 |
|
162 | function findAlphaInFunction(node) {
|
163 | const args = node.nodes.filter(({ type }) => type === 'word' || type === 'function');
|
164 |
|
165 | if (args.length === 4) return args[3];
|
166 |
|
167 | const slashNodeIndex = node.nodes.findIndex(({ type, value }) => type === 'div' && value === '/');
|
168 |
|
169 | if (slashNodeIndex !== -1) {
|
170 | const nodesAfterSlash = node.nodes.slice(slashNodeIndex + 1, node.nodes.length);
|
171 |
|
172 | return nodesAfterSlash.find(({ type }) => type === 'word');
|
173 | }
|
174 |
|
175 | return undefined;
|
176 | }
|
177 |
|
178 |
|
179 |
|
180 |
|
181 |
|
182 | function isPercentage(value) {
|
183 | const dimension = valueParser.unit(value);
|
184 |
|
185 | return dimension && dimension.unit === '%';
|
186 | }
|
187 |
|
188 |
|
189 |
|
190 |
|
191 |
|
192 | function isNumber(value) {
|
193 | const dimension = valueParser.unit(value);
|
194 |
|
195 | return dimension && dimension.unit === '';
|
196 | }
|
197 |
|
198 | rule.ruleName = ruleName;
|
199 | rule.messages = messages;
|
200 | rule.meta = meta;
|
201 | module.exports = rule;
|