stylelint-plugin-rhythmguard 3.2.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 +31 -0
- package/CONTRIBUTING.md +19 -3
- package/README.md +2 -1
- package/package.json +9 -8
- package/src/audit/args.js +103 -307
- package/src/audit/config.js +1 -1
- package/src/audit/contract.js +6 -28
- package/src/audit/index.js +4 -7
- package/src/audit/render-markdown.js +3 -0
- package/src/audit/render-text.js +2 -1
- package/src/audit/report.js +26 -19
- 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 +5 -2
- package/src/{utils → core}/length.js +24 -1
- package/src/{utils → core}/options.js +32 -108
- package/src/core/scale-inference.js +560 -0
- package/src/{utils → core}/token-packages.json +7 -0
- package/src/{utils → core}/token-sources.js +155 -17
- 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/types/audit.d.ts +11 -0
- package/src/audit/scan.js +0 -676
- package/src/utils/scale-inference.js +0 -351
- /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
|
@@ -38,7 +38,7 @@ const TOKEN_KIND_PATTERNS = Object.freeze({
|
|
|
38
38
|
// --spacing) but never letter-/word-spacing. Sass names must start with the scale word
|
|
39
39
|
// (an optional `system-` prefix allowed): $spacer, $spacers.3, $spacing-01, $system-spacing.
|
|
40
40
|
// Component variables such as $dropdown-spacer or $card-spacer-y are not scale tokens.
|
|
41
|
-
spacing: /^(?:\$(?:system-)?(?:space|spacing|spacer)s?(?:-|$|\.)|--(?:[\w-]*-)?(?<!letter-)(?<!word-)(?:space|spacing)(?:-|$))/,
|
|
41
|
+
spacing: /^(?:\$(?:system-)?(?:space|spacing|spacer)s?(?:-|$|\.)|--(?:[\w-]*-)?(?<!letter-)(?<!word-)(?:space|spacing|spacer)(?:-|$))/,
|
|
42
42
|
typography: /^--(?:font|font-size|line-height|leading|tracking|typography)-/,
|
|
43
43
|
});
|
|
44
44
|
|
|
@@ -193,7 +193,8 @@ function normalizeTokenSource(source) {
|
|
|
193
193
|
}
|
|
194
194
|
|
|
195
195
|
function detectSourceFormat(filePath) {
|
|
196
|
-
|
|
196
|
+
// .scss token files are read by the CSS collector too: custom properties plus Sass variables and maps.
|
|
197
|
+
if (['.css', '.scss'].includes(path.extname(filePath).toLowerCase())) {
|
|
197
198
|
return 'css';
|
|
198
199
|
}
|
|
199
200
|
|
|
@@ -237,23 +238,107 @@ function detectJsonTokenFormat(parsed) {
|
|
|
237
238
|
|
|
238
239
|
function collectCssTokens(source, matchesKind) {
|
|
239
240
|
const tokens = [];
|
|
240
|
-
const
|
|
241
|
-
|
|
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
|
+
}
|
|
242
251
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
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';
|
|
247
268
|
}
|
|
269
|
+
return header.split(',').every((selector) => ROOT_SELECTOR.test(selector.trim())) ? 'root' : 'component';
|
|
270
|
+
}
|
|
271
|
+
return 'root';
|
|
272
|
+
}
|
|
248
273
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
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];
|
|
291
|
+
|
|
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;
|
|
253
339
|
}
|
|
254
340
|
|
|
255
|
-
|
|
256
|
-
return tokens;
|
|
341
|
+
return declarations;
|
|
257
342
|
}
|
|
258
343
|
|
|
259
344
|
/**
|
|
@@ -283,16 +368,40 @@ function collectScssTokens(source, matchesKind) {
|
|
|
283
368
|
};
|
|
284
369
|
|
|
285
370
|
const tokens = [];
|
|
286
|
-
const push = (tokenName, value) => {
|
|
287
|
-
if (!value || !matchesKind(tokenName)) {
|
|
371
|
+
const push = (tokenName, value, force = false) => {
|
|
372
|
+
if (!value || (!force && !matchesKind(tokenName))) {
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
const formatted = formatScssValue(value);
|
|
376
|
+
// A unitless number is a multiplier or a map index, not a length. Zero is fine.
|
|
377
|
+
if (/^-?\d*\.?\d+$/.test(formatted) && parseFloat(formatted) !== 0) {
|
|
288
378
|
return;
|
|
289
379
|
}
|
|
290
|
-
tokens.push({ token: tokenName, value:
|
|
380
|
+
tokens.push({ token: tokenName, value: formatted });
|
|
291
381
|
};
|
|
382
|
+
// Namespaced maps such as GOV.UK's $govuk-spacing-points are only accepted
|
|
383
|
+
// for the spacing kind, and only when they hold a real ladder (issue #86).
|
|
384
|
+
const spacingKind = matchesKind('$spacing-probe');
|
|
292
385
|
|
|
293
386
|
for (const [name, raw] of declarations) {
|
|
294
387
|
const tokenName = `$${name}`;
|
|
295
388
|
if (isScssMap(raw)) {
|
|
389
|
+
if (!matchesKind(tokenName) && spacingKind && NAMESPACED_SPACING_MAP.test(tokenName)) {
|
|
390
|
+
const entries = [];
|
|
391
|
+
walkScssMap(raw, [tokenName], (pathName, expression) => {
|
|
392
|
+
const value = evaluateScssExpression(expression, resolveVariable, new Set());
|
|
393
|
+
if (value) {
|
|
394
|
+
entries.push([pathName, value]);
|
|
395
|
+
}
|
|
396
|
+
});
|
|
397
|
+
const distinct = new Set(entries.map(([, value]) => formatScssValue(value)).filter((value) => /^-?\d*\.?\d+(?:px|rem|em)$/.test(value) && parseFloat(value) !== 0));
|
|
398
|
+
if (distinct.size >= MIN_NAMESPACED_MAP_LENGTHS) {
|
|
399
|
+
for (const [pathName, value] of entries) {
|
|
400
|
+
push(pathName, value, true);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
continue;
|
|
404
|
+
}
|
|
296
405
|
walkScssMap(raw, [tokenName], (pathName, expression) => {
|
|
297
406
|
push(pathName, evaluateScssExpression(expression, resolveVariable, new Set()));
|
|
298
407
|
});
|
|
@@ -304,6 +413,10 @@ function collectScssTokens(source, matchesKind) {
|
|
|
304
413
|
return tokens;
|
|
305
414
|
}
|
|
306
415
|
|
|
416
|
+
/** `$<namespace>-spacing-points`, `$<ns>-space-scale`: one namespace segment before the anchor. */
|
|
417
|
+
const NAMESPACED_SPACING_MAP = /^\$[a-z0-9]+-(?:space|spacing|spacer)s?(?:-|$)/i;
|
|
418
|
+
const MIN_NAMESPACED_MAP_LENGTHS = 4;
|
|
419
|
+
|
|
307
420
|
function stripScssComments(source) {
|
|
308
421
|
return source
|
|
309
422
|
.replace(/\/\*[\s\S]*?\*\//g, '')
|
|
@@ -381,7 +494,16 @@ function splitTopLevel(text, separator) {
|
|
|
381
494
|
return parts;
|
|
382
495
|
}
|
|
383
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
|
+
|
|
384
503
|
function walkScssMap(raw, pathSegments, visit) {
|
|
504
|
+
if (pathSegments.length > MAX_SCSS_MAP_DEPTH) {
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
385
507
|
const inner = raw.slice(1, -1);
|
|
386
508
|
for (const entry of splitTopLevel(inner, ',')) {
|
|
387
509
|
const pair = splitTopLevel(entry, ':');
|
|
@@ -454,7 +576,18 @@ function evaluateScssExpression(expression, resolveVariable, stack) {
|
|
|
454
576
|
return { number: op === '+' ? left.number + right.number : left.number - right.number, unit: left.unit || right.unit };
|
|
455
577
|
};
|
|
456
578
|
|
|
579
|
+
let depth = 0;
|
|
457
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 = () => {
|
|
458
591
|
const token = next();
|
|
459
592
|
if (!token) return null;
|
|
460
593
|
if (token.type === 'number') return parseNumber(token.raw);
|
|
@@ -633,6 +766,7 @@ function extractTokenName(value) {
|
|
|
633
766
|
function addDefinition(definitions, {
|
|
634
767
|
baseFontSize,
|
|
635
768
|
file,
|
|
769
|
+
scope = 'root',
|
|
636
770
|
source,
|
|
637
771
|
token,
|
|
638
772
|
value,
|
|
@@ -640,12 +774,14 @@ function addDefinition(definitions, {
|
|
|
640
774
|
const entry = definitions.get(token) || {
|
|
641
775
|
files: new Set(),
|
|
642
776
|
normalizedValues: new Set(),
|
|
777
|
+
scopes: new Set(),
|
|
643
778
|
sources: new Set(),
|
|
644
779
|
token,
|
|
645
780
|
values: new Set(),
|
|
646
781
|
};
|
|
647
782
|
|
|
648
783
|
entry.files.add(file);
|
|
784
|
+
entry.scopes.add(scope);
|
|
649
785
|
entry.sources.add(source);
|
|
650
786
|
entry.values.add(String(value).trim());
|
|
651
787
|
|
|
@@ -719,7 +855,9 @@ module.exports = {
|
|
|
719
855
|
VALID_TOKEN_KINDS,
|
|
720
856
|
VALID_TOKEN_SOURCE_FORMATS,
|
|
721
857
|
addDefinition,
|
|
858
|
+
collectCssTokens,
|
|
722
859
|
collectScssTokens,
|
|
860
|
+
customPropertyDeclarations,
|
|
723
861
|
createTokenKindMatcher,
|
|
724
862
|
getNormalizedValueKeys,
|
|
725
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) => {
|