stylelint-plugin-rhythmguard 3.3.0 → 3.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -0
- package/CONTRIBUTING.md +19 -3
- package/README.md +2 -1
- package/package.json +7 -7
- package/src/audit/args.js +103 -307
- package/src/audit/config.js +1 -1
- package/src/audit/contract.js +5 -28
- package/src/audit/index.js +4 -7
- package/src/audit/report.js +14 -14
- package/src/audit/scan/files.js +262 -0
- package/src/audit/scan/stylesheets.js +275 -0
- package/src/audit/scan/templates.js +175 -0
- package/src/cli/doctor.js +1 -1
- package/src/cli/quickstart.js +1 -1
- package/src/{utils → core}/length.js +21 -0
- package/src/{utils → core}/options.js +32 -108
- package/src/{utils → core}/scale-inference.js +151 -15
- package/src/{utils → core}/token-sources.js +121 -12
- package/src/{utils/value-utils.js → core/value-nodes.js} +1 -17
- package/src/eslint/rules/tailwind-class-use-motion-scale.js +2 -2
- package/src/eslint/rules/tailwind-class-use-scale.js +2 -2
- package/src/rules/no-offscale-transform/index.js +22 -91
- package/src/rules/prefer-token/index.js +15 -73
- package/src/rules/report.js +75 -0
- package/src/rules/use-motion-scale/index.js +25 -42
- package/src/rules/use-scale/index.js +21 -94
- package/src/rules/validate.js +132 -0
- package/src/audit/scan.js +0 -676
- /package/src/{utils/constants.js → core/css-vocabulary.js} +0 -0
- /package/src/{utils → core}/tailwind-class-analysis.js +0 -0
- /package/src/{utils → core}/tailwind-motion-analysis.js +0 -0
- /package/src/{utils → core}/time.js +0 -0
- /package/src/{utils → core}/token-map.js +0 -0
- /package/src/{utils → core}/token-packages.json +0 -0
|
@@ -238,23 +238,107 @@ function detectJsonTokenFormat(parsed) {
|
|
|
238
238
|
|
|
239
239
|
function collectCssTokens(source, matchesKind) {
|
|
240
240
|
const tokens = [];
|
|
241
|
-
const
|
|
242
|
-
|
|
241
|
+
for (const declaration of customPropertyDeclarations(source)) {
|
|
242
|
+
if (matchesKind(declaration.token)) {
|
|
243
|
+
tokens.push(declaration);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
for (const sassToken of collectScssTokens(source, matchesKind)) {
|
|
247
|
+
tokens.push({ ...sassToken, scope: 'root' });
|
|
248
|
+
}
|
|
249
|
+
return tokens;
|
|
250
|
+
}
|
|
243
251
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
252
|
+
const ROOT_SELECTOR = /^(?::root|html|:host|:(?:where|is)\(\s*(?::root|html)\s*\))$/i;
|
|
253
|
+
// At-rules that only condition their contents; the enclosing selector still decides the scope.
|
|
254
|
+
const TRANSPARENT_AT_RULES = new Set(['media', 'supports', 'layer', 'container', 'scope', 'document']);
|
|
255
|
+
|
|
256
|
+
function stripComments(text) {
|
|
257
|
+
return text.replace(/\/\*[\s\S]*?\*\//g, ' ').replace(/(^|[^:])\/\/[^\n]*/g, '$1');
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function scopeOfBlockHeaders(headers) {
|
|
261
|
+
for (let index = headers.length - 1; index >= 0; index -= 1) {
|
|
262
|
+
const header = headers[index];
|
|
263
|
+
if (header.startsWith('@')) {
|
|
264
|
+
const name = header.slice(1).match(/^[\w-]+/);
|
|
265
|
+
if (name && name[0].toLowerCase() === 'theme') return 'root';
|
|
266
|
+
if (name && TRANSPARENT_AT_RULES.has(name[0].toLowerCase())) continue;
|
|
267
|
+
return 'component';
|
|
248
268
|
}
|
|
269
|
+
return header.split(',').every((selector) => ROOT_SELECTOR.test(selector.trim())) ? 'root' : 'component';
|
|
270
|
+
}
|
|
271
|
+
return 'root';
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Custom property declarations with the scope they are declared in: `root`
|
|
276
|
+
* for `:root`, `html`, `:host` and `@theme` blocks (through conditional
|
|
277
|
+
* at-rules), `component` for anything else. One pass over the source that
|
|
278
|
+
* skips comments and strings, so a brace in a comment cannot shift the
|
|
279
|
+
* scope; this is the text-side twin of scopeOfNode in scale-inference.js.
|
|
280
|
+
*/
|
|
281
|
+
function customPropertyDeclarations(source) {
|
|
282
|
+
const declarations = [];
|
|
283
|
+
const headers = [];
|
|
284
|
+
let headerStart = 0;
|
|
285
|
+
let index = 0;
|
|
286
|
+
const length = source.length;
|
|
287
|
+
|
|
288
|
+
while (index < length) {
|
|
289
|
+
const char = source[index];
|
|
290
|
+
const next = source[index + 1];
|
|
249
291
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
292
|
+
if (char === '/' && next === '*') {
|
|
293
|
+
const end = source.indexOf('*/', index + 2);
|
|
294
|
+
index = end === -1 ? length : end + 2;
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
297
|
+
if (char === '/' && next === '/') {
|
|
298
|
+
const end = source.indexOf('\n', index);
|
|
299
|
+
index = end === -1 ? length : end;
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
if (char === '"' || char === "'") {
|
|
303
|
+
let end = index + 1;
|
|
304
|
+
while (end < length && source[end] !== char) end += source[end] === '\\' ? 2 : 1;
|
|
305
|
+
index = end + 1;
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
308
|
+
if (char === '{') {
|
|
309
|
+
if (source[index - 1] === '#') {
|
|
310
|
+
const end = source.indexOf('}', index);
|
|
311
|
+
index = end === -1 ? length : end + 1;
|
|
312
|
+
continue;
|
|
313
|
+
}
|
|
314
|
+
headers.push(stripComments(source.slice(headerStart, index)).trim().replace(/\s+/g, ' '));
|
|
315
|
+
headerStart = index + 1;
|
|
316
|
+
index += 1;
|
|
317
|
+
continue;
|
|
318
|
+
}
|
|
319
|
+
if (char === '}') {
|
|
320
|
+
headers.pop();
|
|
321
|
+
headerStart = index + 1;
|
|
322
|
+
index += 1;
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
325
|
+
if (char === ';') {
|
|
326
|
+
headerStart = index + 1;
|
|
327
|
+
index += 1;
|
|
328
|
+
continue;
|
|
329
|
+
}
|
|
330
|
+
if (char === '-' && next === '-' && !/[\w-]/.test(source[index - 1] || '')) {
|
|
331
|
+
const match = /^(--[\w-]+)\s*:\s*([^;{}]+)/.exec(source.slice(index));
|
|
332
|
+
if (match) {
|
|
333
|
+
declarations.push({ scope: scopeOfBlockHeaders(headers), token: match[1], value: match[2].trim() });
|
|
334
|
+
index += match[0].length;
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
index += 1;
|
|
254
339
|
}
|
|
255
340
|
|
|
256
|
-
|
|
257
|
-
return tokens;
|
|
341
|
+
return declarations;
|
|
258
342
|
}
|
|
259
343
|
|
|
260
344
|
/**
|
|
@@ -410,7 +494,16 @@ function splitTopLevel(text, separator) {
|
|
|
410
494
|
return parts;
|
|
411
495
|
}
|
|
412
496
|
|
|
497
|
+
// Real token maps nest two or three levels; generated or adversarial input can
|
|
498
|
+
// nest without bound. Past these limits the value is treated like any other
|
|
499
|
+
// expression the evaluator cannot handle: skipped, never guessed, never thrown.
|
|
500
|
+
const MAX_SCSS_MAP_DEPTH = 16;
|
|
501
|
+
const MAX_SCSS_EXPRESSION_DEPTH = 64;
|
|
502
|
+
|
|
413
503
|
function walkScssMap(raw, pathSegments, visit) {
|
|
504
|
+
if (pathSegments.length > MAX_SCSS_MAP_DEPTH) {
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
414
507
|
const inner = raw.slice(1, -1);
|
|
415
508
|
for (const entry of splitTopLevel(inner, ',')) {
|
|
416
509
|
const pair = splitTopLevel(entry, ':');
|
|
@@ -483,7 +576,18 @@ function evaluateScssExpression(expression, resolveVariable, stack) {
|
|
|
483
576
|
return { number: op === '+' ? left.number + right.number : left.number - right.number, unit: left.unit || right.unit };
|
|
484
577
|
};
|
|
485
578
|
|
|
579
|
+
let depth = 0;
|
|
486
580
|
const parsePrimary = () => {
|
|
581
|
+
if (depth > MAX_SCSS_EXPRESSION_DEPTH) return null;
|
|
582
|
+
depth += 1;
|
|
583
|
+
try {
|
|
584
|
+
return parsePrimaryInner();
|
|
585
|
+
} finally {
|
|
586
|
+
depth -= 1;
|
|
587
|
+
}
|
|
588
|
+
};
|
|
589
|
+
|
|
590
|
+
const parsePrimaryInner = () => {
|
|
487
591
|
const token = next();
|
|
488
592
|
if (!token) return null;
|
|
489
593
|
if (token.type === 'number') return parseNumber(token.raw);
|
|
@@ -662,6 +766,7 @@ function extractTokenName(value) {
|
|
|
662
766
|
function addDefinition(definitions, {
|
|
663
767
|
baseFontSize,
|
|
664
768
|
file,
|
|
769
|
+
scope = 'root',
|
|
665
770
|
source,
|
|
666
771
|
token,
|
|
667
772
|
value,
|
|
@@ -669,12 +774,14 @@ function addDefinition(definitions, {
|
|
|
669
774
|
const entry = definitions.get(token) || {
|
|
670
775
|
files: new Set(),
|
|
671
776
|
normalizedValues: new Set(),
|
|
777
|
+
scopes: new Set(),
|
|
672
778
|
sources: new Set(),
|
|
673
779
|
token,
|
|
674
780
|
values: new Set(),
|
|
675
781
|
};
|
|
676
782
|
|
|
677
783
|
entry.files.add(file);
|
|
784
|
+
entry.scopes.add(scope);
|
|
678
785
|
entry.sources.add(source);
|
|
679
786
|
entry.values.add(String(value).trim());
|
|
680
787
|
|
|
@@ -748,7 +855,9 @@ module.exports = {
|
|
|
748
855
|
VALID_TOKEN_KINDS,
|
|
749
856
|
VALID_TOKEN_SOURCE_FORMATS,
|
|
750
857
|
addDefinition,
|
|
858
|
+
collectCssTokens,
|
|
751
859
|
collectScssTokens,
|
|
860
|
+
customPropertyDeclarations,
|
|
752
861
|
createTokenKindMatcher,
|
|
753
862
|
getNormalizedValueKeys,
|
|
754
863
|
normalizeTokenKind,
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const valueParser = require('postcss-value-parser');
|
|
4
|
-
const stylelint = require('stylelint');
|
|
5
4
|
const {
|
|
6
5
|
MATH_FUNCTIONS,
|
|
7
6
|
TRANSLATE_FUNCTIONS,
|
|
8
|
-
} = require('./
|
|
7
|
+
} = require('./css-vocabulary');
|
|
9
8
|
|
|
10
9
|
function propertyMatches(prop, patterns) {
|
|
11
10
|
const normalized = prop.toLowerCase();
|
|
@@ -26,20 +25,6 @@ function isKeyword(value, ignoreValues) {
|
|
|
26
25
|
return ignoreValues.includes(String(value).toLowerCase());
|
|
27
26
|
}
|
|
28
27
|
|
|
29
|
-
function createTokenRegex(tokenPattern, result, ruleName) {
|
|
30
|
-
try {
|
|
31
|
-
return new RegExp(tokenPattern);
|
|
32
|
-
} catch {
|
|
33
|
-
stylelint.utils.report({
|
|
34
|
-
message: `Invalid tokenPattern regex: ${tokenPattern}`,
|
|
35
|
-
result,
|
|
36
|
-
ruleName,
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
return /^--space-/;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
28
|
function isTokenFunction(node, tokenFunctions, tokenRegex) {
|
|
44
29
|
if (node.type !== 'function') {
|
|
45
30
|
return false;
|
|
@@ -213,7 +198,6 @@ function declarationValueIndex(decl) {
|
|
|
213
198
|
}
|
|
214
199
|
|
|
215
200
|
module.exports = {
|
|
216
|
-
createTokenRegex,
|
|
217
201
|
declarationValueIndex,
|
|
218
202
|
isKeyword,
|
|
219
203
|
isMathFunction,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { formatTime } = require('../../
|
|
4
|
-
const { createTailwindMotionAnalyzer } = require('../../
|
|
3
|
+
const { formatTime } = require('../../core/time');
|
|
4
|
+
const { createTailwindMotionAnalyzer } = require('../../core/tailwind-motion-analysis');
|
|
5
5
|
|
|
6
6
|
const RULE_NAME = 'tailwind-class-use-motion-scale';
|
|
7
7
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { formatLength } = require('../../
|
|
4
|
-
const { createTailwindClassAnalyzer } = require('../../
|
|
3
|
+
const { formatLength } = require('../../core/length');
|
|
4
|
+
const { createTailwindClassAnalyzer } = require('../../core/tailwind-class-analysis');
|
|
5
5
|
|
|
6
6
|
const RULE_NAME = 'tailwind-class-use-scale';
|
|
7
7
|
|
|
@@ -3,33 +3,32 @@
|
|
|
3
3
|
const stylelint = require('stylelint');
|
|
4
4
|
const valueParser = require('postcss-value-parser');
|
|
5
5
|
const {
|
|
6
|
+
fixedLengthValue,
|
|
6
7
|
formatLength,
|
|
7
|
-
fromPx,
|
|
8
8
|
isHairlineLength,
|
|
9
9
|
nearestScaleValues,
|
|
10
|
-
normalizeScale,
|
|
11
|
-
normalizeScaleByUnit,
|
|
12
10
|
numbersEqual,
|
|
13
11
|
parseLengthToken,
|
|
14
12
|
toPx,
|
|
15
|
-
} = require('../../
|
|
13
|
+
} = require('../../core/length');
|
|
16
14
|
const {
|
|
17
15
|
buildScaleOptions,
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
} = require('../../utils/options');
|
|
16
|
+
createPropertyScaleResolver,
|
|
17
|
+
} = require('../../core/options');
|
|
21
18
|
const {
|
|
22
|
-
declarationValueIndex,
|
|
23
19
|
isMathFunction,
|
|
24
20
|
shouldLintMathArgument,
|
|
25
21
|
walkRootValueNodes,
|
|
26
22
|
walkTransformTranslateNodes,
|
|
27
|
-
} = require('../../
|
|
23
|
+
} = require('../../core/value-nodes');
|
|
28
24
|
|
|
29
25
|
const {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
26
|
+
withResolvedScale,
|
|
27
|
+
} = require('../../core/scale-inference');
|
|
28
|
+
|
|
29
|
+
const { validatePrimary, validateNoOffscaleTransformSecondaryOptions } = require('../validate');
|
|
30
|
+
|
|
31
|
+
const { reportInvalidPreset, reportValueNode } = require('../report');
|
|
33
32
|
|
|
34
33
|
const ruleName = 'rhythmguard/no-offscale-transform';
|
|
35
34
|
const messages = stylelint.utils.ruleMessages(ruleName, {
|
|
@@ -39,33 +38,9 @@ const messages = stylelint.utils.ruleMessages(ruleName, {
|
|
|
39
38
|
`Unexpected transform translation value "${value}". Use scale values (nearest: ${lower} or ${upper}).`,
|
|
40
39
|
});
|
|
41
40
|
|
|
42
|
-
function getFixedNodeValue(parsedLength, nearestPx, options) {
|
|
43
|
-
const unit = parsedLength.unit || 'px';
|
|
44
|
-
if (unit === '%' || !options.units.includes(unit)) {
|
|
45
|
-
return null;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const signedNearest = parsedLength.number < 0 ? -Math.abs(nearestPx) : nearestPx;
|
|
49
|
-
|
|
50
|
-
if (options.unitStrategy === 'exact') {
|
|
51
|
-
return formatLength(signedNearest, parsedLength.unit || 'px');
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const converted = fromPx(signedNearest, unit, options.baseFontSize);
|
|
55
|
-
|
|
56
|
-
if (converted === null) {
|
|
57
|
-
return null;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
return formatLength(converted, unit);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
41
|
const ruleFunction = (primary, secondaryOptions) => {
|
|
64
42
|
return (root, result) => {
|
|
65
|
-
const valid =
|
|
66
|
-
actual: primary,
|
|
67
|
-
possible: [true],
|
|
68
|
-
});
|
|
43
|
+
const valid = validatePrimary(result, ruleName, primary);
|
|
69
44
|
|
|
70
45
|
if (!valid) {
|
|
71
46
|
return;
|
|
@@ -81,43 +56,11 @@ const ruleFunction = (primary, secondaryOptions) => {
|
|
|
81
56
|
}
|
|
82
57
|
|
|
83
58
|
const options = buildScaleOptions(secondaryOptions);
|
|
84
|
-
|
|
85
|
-
stylelint.utils.report({
|
|
86
|
-
message: messages.invalidPreset(options.invalidPreset, options.presetNames),
|
|
87
|
-
node: root,
|
|
88
|
-
result,
|
|
89
|
-
ruleName,
|
|
90
|
-
});
|
|
91
|
-
}
|
|
59
|
+
reportInvalidPreset(options, { message: messages.invalidPreset, result, root, ruleName });
|
|
92
60
|
|
|
93
|
-
|
|
94
|
-
const inference = resolveAutoScale({
|
|
95
|
-
baseFontSize: options.baseFontSize,
|
|
96
|
-
root,
|
|
97
|
-
scaleSources: options.scaleSources,
|
|
98
|
-
tailwindConfigPath: options.tailwindConfigPath,
|
|
99
|
-
tokenPattern: options.tokenPatternExplicit ? options.tokenPattern : DEFAULT_AUTO_TOKEN_PATTERN,
|
|
100
|
-
});
|
|
101
|
-
options.scale = inference.scale;
|
|
102
|
-
options.scaleInference = inference;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
const scaleCache = new Map();
|
|
106
|
-
const getScaleStateForProperty = (prop) => {
|
|
107
|
-
const cached = scaleCache.get(prop);
|
|
108
|
-
if (cached) {
|
|
109
|
-
return cached;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const selectedScale = resolvePropertyScale(prop, options);
|
|
113
|
-
const next = {
|
|
114
|
-
scaleByUnit: normalizeScaleByUnit(selectedScale),
|
|
115
|
-
scalePx: normalizeScale(selectedScale, options.baseFontSize),
|
|
116
|
-
};
|
|
61
|
+
withResolvedScale(options, root);
|
|
117
62
|
|
|
118
|
-
|
|
119
|
-
return next;
|
|
120
|
-
};
|
|
63
|
+
const getScaleStateForProperty = createPropertyScaleResolver(options);
|
|
121
64
|
|
|
122
65
|
root.walkDecls((decl) => {
|
|
123
66
|
const prop = decl.prop.toLowerCase();
|
|
@@ -130,30 +73,18 @@ const ruleFunction = (primary, secondaryOptions) => {
|
|
|
130
73
|
let changed = false;
|
|
131
74
|
|
|
132
75
|
const report = (node, nearest, nearestUnit, fixedValue = null) => {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
const payload = {
|
|
137
|
-
endIndex,
|
|
138
|
-
index,
|
|
76
|
+
reportValueNode({
|
|
77
|
+
decl,
|
|
139
78
|
message: messages.rejected(
|
|
140
79
|
node.value,
|
|
141
80
|
formatLength(nearest.lower, nearestUnit),
|
|
142
81
|
formatLength(nearest.upper, nearestUnit),
|
|
143
82
|
),
|
|
144
|
-
node
|
|
83
|
+
node,
|
|
84
|
+
replacement: fixedValue,
|
|
145
85
|
result,
|
|
146
86
|
ruleName,
|
|
147
|
-
};
|
|
148
|
-
|
|
149
|
-
if (fixedValue) {
|
|
150
|
-
payload.fix = () => {
|
|
151
|
-
node.value = fixedValue;
|
|
152
|
-
return true;
|
|
153
|
-
};
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
stylelint.utils.report(payload);
|
|
87
|
+
});
|
|
157
88
|
};
|
|
158
89
|
|
|
159
90
|
const checkNode = (node) => {
|
|
@@ -205,7 +136,7 @@ const ruleFunction = (primary, secondaryOptions) => {
|
|
|
205
136
|
}
|
|
206
137
|
|
|
207
138
|
const fixedValue = options.fixToScale
|
|
208
|
-
?
|
|
139
|
+
? fixedLengthValue(parsedLength, nearest.nearest, options)
|
|
209
140
|
: null;
|
|
210
141
|
|
|
211
142
|
report(node, nearest, unit, fixedValue);
|
|
@@ -229,7 +160,7 @@ const ruleFunction = (primary, secondaryOptions) => {
|
|
|
229
160
|
}
|
|
230
161
|
|
|
231
162
|
const fixedValue = options.fixToScale
|
|
232
|
-
?
|
|
163
|
+
? fixedLengthValue(parsedLength, nearest.nearest, options)
|
|
233
164
|
: null;
|
|
234
165
|
|
|
235
166
|
report(node, nearest, 'px', fixedValue);
|
|
@@ -5,20 +5,15 @@ const valueParser = require('postcss-value-parser');
|
|
|
5
5
|
const {
|
|
6
6
|
formatLength,
|
|
7
7
|
isHairlineLength,
|
|
8
|
-
normalizeScale,
|
|
9
|
-
normalizeScaleByUnit,
|
|
10
8
|
numbersEqual,
|
|
11
9
|
parseLengthToken,
|
|
12
10
|
toPx,
|
|
13
|
-
} = require('../../
|
|
11
|
+
} = require('../../core/length');
|
|
14
12
|
const {
|
|
15
13
|
buildTokenOptions,
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
} = require('../../utils/options');
|
|
14
|
+
createPropertyScaleResolver,
|
|
15
|
+
} = require('../../core/options');
|
|
19
16
|
const {
|
|
20
|
-
createTokenRegex,
|
|
21
|
-
declarationValueIndex,
|
|
22
17
|
isKeyword,
|
|
23
18
|
isMathFunction,
|
|
24
19
|
isTokenFunction,
|
|
@@ -26,13 +21,15 @@ const {
|
|
|
26
21
|
shouldLintMathArgument,
|
|
27
22
|
walkRootValueNodes,
|
|
28
23
|
walkTransformTranslateNodes,
|
|
29
|
-
} = require('../../
|
|
30
|
-
const { buildEffectiveTokenMap } = require('../../
|
|
24
|
+
} = require('../../core/value-nodes');
|
|
25
|
+
const { buildEffectiveTokenMap } = require('../../core/token-map');
|
|
31
26
|
|
|
32
27
|
const {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
28
|
+
withResolvedScale,
|
|
29
|
+
} = require('../../core/scale-inference');
|
|
30
|
+
|
|
31
|
+
const { createTokenRegex, reportInvalidPreset, reportValueNode } = require('../report');
|
|
32
|
+
const { validatePrimary, validatePreferTokenSecondaryOptions } = require('../validate');
|
|
36
33
|
|
|
37
34
|
const ruleName = 'rhythmguard/prefer-token';
|
|
38
35
|
|
|
@@ -90,10 +87,7 @@ function resolveTokenReplacement(tokenMap, raw, parsedLength, options) {
|
|
|
90
87
|
|
|
91
88
|
const ruleFunction = (primary, secondaryOptions) => {
|
|
92
89
|
return (root, result) => {
|
|
93
|
-
const valid =
|
|
94
|
-
actual: primary,
|
|
95
|
-
possible: [true],
|
|
96
|
-
});
|
|
90
|
+
const valid = validatePrimary(result, ruleName, primary);
|
|
97
91
|
|
|
98
92
|
if (!valid) {
|
|
99
93
|
return;
|
|
@@ -109,26 +103,9 @@ const ruleFunction = (primary, secondaryOptions) => {
|
|
|
109
103
|
}
|
|
110
104
|
|
|
111
105
|
const options = buildTokenOptions(secondaryOptions);
|
|
112
|
-
|
|
113
|
-
stylelint.utils.report({
|
|
114
|
-
message: messages.invalidPreset(options.invalidPreset, options.presetNames),
|
|
115
|
-
node: root,
|
|
116
|
-
result,
|
|
117
|
-
ruleName,
|
|
118
|
-
});
|
|
119
|
-
}
|
|
106
|
+
reportInvalidPreset(options, { message: messages.invalidPreset, result, root, ruleName });
|
|
120
107
|
|
|
121
|
-
|
|
122
|
-
const inference = resolveAutoScale({
|
|
123
|
-
baseFontSize: options.baseFontSize,
|
|
124
|
-
root,
|
|
125
|
-
scaleSources: options.scaleSources,
|
|
126
|
-
tailwindConfigPath: options.tailwindConfigPath,
|
|
127
|
-
tokenPattern: options.tokenPatternExplicit ? options.tokenPattern : DEFAULT_AUTO_TOKEN_PATTERN,
|
|
128
|
-
});
|
|
129
|
-
options.scale = inference.scale;
|
|
130
|
-
options.scaleInference = inference;
|
|
131
|
-
}
|
|
108
|
+
withResolvedScale(options, root);
|
|
132
109
|
|
|
133
110
|
const tokenRegex = createTokenRegex(options.tokenPattern, result, ruleName);
|
|
134
111
|
const tokenMap = buildEffectiveTokenMap({
|
|
@@ -137,23 +114,7 @@ const ruleFunction = (primary, secondaryOptions) => {
|
|
|
137
114
|
tokenRegex,
|
|
138
115
|
});
|
|
139
116
|
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
const getScaleStateForProperty = (prop) => {
|
|
143
|
-
const cached = scaleCache.get(prop);
|
|
144
|
-
if (cached) {
|
|
145
|
-
return cached;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
const selectedScale = resolvePropertyScale(prop, options);
|
|
149
|
-
const next = {
|
|
150
|
-
scaleByUnit: normalizeScaleByUnit(selectedScale),
|
|
151
|
-
scalePx: normalizeScale(selectedScale, options.baseFontSize),
|
|
152
|
-
};
|
|
153
|
-
|
|
154
|
-
scaleCache.set(prop, next);
|
|
155
|
-
return next;
|
|
156
|
-
};
|
|
117
|
+
const getScaleStateForProperty = createPropertyScaleResolver(options);
|
|
157
118
|
|
|
158
119
|
root.walkDecls((decl) => {
|
|
159
120
|
const prop = decl.prop.toLowerCase();
|
|
@@ -170,26 +131,7 @@ const ruleFunction = (primary, secondaryOptions) => {
|
|
|
170
131
|
let changed = false;
|
|
171
132
|
|
|
172
133
|
const reportNode = (node, replacement = null) => {
|
|
173
|
-
|
|
174
|
-
const endIndex = index + node.value.length;
|
|
175
|
-
|
|
176
|
-
const payload = {
|
|
177
|
-
endIndex,
|
|
178
|
-
index,
|
|
179
|
-
message: messages.rejected(node.value),
|
|
180
|
-
node: decl,
|
|
181
|
-
result,
|
|
182
|
-
ruleName,
|
|
183
|
-
};
|
|
184
|
-
|
|
185
|
-
if (replacement) {
|
|
186
|
-
payload.fix = () => {
|
|
187
|
-
node.value = replacement;
|
|
188
|
-
return true;
|
|
189
|
-
};
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
stylelint.utils.report(payload);
|
|
134
|
+
reportValueNode({ decl, message: messages.rejected(node.value), node, replacement, result, ruleName });
|
|
193
135
|
};
|
|
194
136
|
|
|
195
137
|
const checkWordNode = (node, context) => {
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The Stylelint side of a rule: everything that touches `stylelint.utils` or
|
|
5
|
+
* the PostCSS result lives here, so that src/core stays free of the framework
|
|
6
|
+
* and the same primitives can serve the ESLint plugin and the audit.
|
|
7
|
+
*/
|
|
8
|
+
const stylelint = require('stylelint');
|
|
9
|
+
const { declarationValueIndex } = require('../core/value-nodes');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Compile a user-supplied token pattern. An invalid pattern is reported once
|
|
13
|
+
* against the root and replaced with the historical default so the file still
|
|
14
|
+
* lints instead of throwing inside Stylelint.
|
|
15
|
+
*/
|
|
16
|
+
function createTokenRegex(tokenPattern, result, ruleName) {
|
|
17
|
+
try {
|
|
18
|
+
return new RegExp(tokenPattern);
|
|
19
|
+
} catch {
|
|
20
|
+
stylelint.utils.report({
|
|
21
|
+
message: `Invalid tokenPattern regex: ${tokenPattern}`,
|
|
22
|
+
result,
|
|
23
|
+
ruleName,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
return /^--space-/;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Report one value node inside a declaration, positioned on the node rather
|
|
32
|
+
* than the whole declaration, with an optional fix. The default fix replaces
|
|
33
|
+
* the node's text; pass `fix` for anything else.
|
|
34
|
+
*/
|
|
35
|
+
function reportValueNode({ decl, fix = null, length = null, message, node, replacement = null, result, ruleName }) {
|
|
36
|
+
const index = declarationValueIndex(decl) + node.sourceIndex;
|
|
37
|
+
const payload = {
|
|
38
|
+
endIndex: index + (length === null ? node.value.length : length),
|
|
39
|
+
index,
|
|
40
|
+
message,
|
|
41
|
+
node: decl,
|
|
42
|
+
result,
|
|
43
|
+
ruleName,
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
if (fix) {
|
|
47
|
+
payload.fix = fix;
|
|
48
|
+
} else if (replacement) {
|
|
49
|
+
payload.fix = () => {
|
|
50
|
+
node.value = replacement;
|
|
51
|
+
return true;
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
stylelint.utils.report(payload);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** A preset name the options did not recognise is reported once, on the root. */
|
|
59
|
+
function reportInvalidPreset(options, { message, result, root, ruleName }) {
|
|
60
|
+
if (!options.invalidPreset) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
stylelint.utils.report({
|
|
64
|
+
message: message(options.invalidPreset, options.presetNames),
|
|
65
|
+
node: root,
|
|
66
|
+
result,
|
|
67
|
+
ruleName,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
module.exports = {
|
|
72
|
+
createTokenRegex,
|
|
73
|
+
reportInvalidPreset,
|
|
74
|
+
reportValueNode,
|
|
75
|
+
};
|