stylelint-plugin-rhythmguard 3.10.2 → 3.12.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.
Files changed (44) hide show
  1. package/CHANGELOG.md +77 -0
  2. package/README.md +5 -1
  3. package/package.json +1 -1
  4. package/src/audit/args.js +22 -2
  5. package/src/audit/baseline.js +184 -27
  6. package/src/audit/codemod.js +39 -14
  7. package/src/audit/config.js +25 -31
  8. package/src/audit/contract.js +96 -10
  9. package/src/audit/render-github.js +4 -0
  10. package/src/audit/render-html.js +7 -0
  11. package/src/audit/render-markdown.js +27 -0
  12. package/src/audit/render-text.js +23 -1
  13. package/src/audit/report.js +60 -17
  14. package/src/audit/scan/files.js +154 -27
  15. package/src/audit/scan/stylesheets.js +87 -34
  16. package/src/audit/scan/templates.js +22 -7
  17. package/src/audit/token-chains.js +18 -7
  18. package/src/cli/audit.js +7 -3
  19. package/src/cli/doctor.js +48 -7
  20. package/src/cli/fix.js +56 -15
  21. package/src/cli/init.js +136 -23
  22. package/src/cli/quickstart.js +34 -8
  23. package/src/core/fs-cache.js +6 -0
  24. package/src/core/length.js +15 -0
  25. package/src/core/options.js +8 -1
  26. package/src/core/scale-inference.js +99 -68
  27. package/src/core/tailwind-class-analysis.js +26 -16
  28. package/src/core/tailwind-motion-analysis.js +12 -3
  29. package/src/core/token-index.js +76 -24
  30. package/src/core/token-map.js +23 -10
  31. package/src/core/token-source-entries.js +64 -0
  32. package/src/core/token-sources.js +361 -69
  33. package/src/core/value-nodes.js +280 -54
  34. package/src/eslint/literal-spans.js +319 -0
  35. package/src/eslint/rules/tailwind-class-use-motion-scale.js +9 -32
  36. package/src/eslint/rules/tailwind-class-use-scale.js +45 -40
  37. package/src/rules/no-offscale-transform/index.js +35 -14
  38. package/src/rules/prefer-token/index.js +72 -48
  39. package/src/rules/report.js +3 -1
  40. package/src/rules/use-motion-scale/index.js +68 -5
  41. package/src/rules/use-scale/index.js +54 -39
  42. package/types/__checks__/eslint-config.ts +37 -0
  43. package/types/audit.d.ts +10 -2
  44. package/types/shared.d.ts +21 -3
@@ -71,11 +71,12 @@ function addLengthValueMapping(map, rawLength, tokenReference, baseFontSize, ori
71
71
  origins[token] = origin;
72
72
  }
73
73
 
74
- const absolute = Math.abs(parsed.number);
75
- const normalizedRaw = formatLength(absolute, parsed.unit || 'px');
74
+ // Keyed by the signed value: a -8px token stands for -8px, never for 8px
75
+ // (issue #221). prefer-token negates a positive token for a negative literal.
76
+ const normalizedRaw = formatLength(parsed.number, parsed.unit || 'px');
76
77
  map[normalizedRaw] = token;
77
78
 
78
- const px = toPx(absolute, parsed.unit, baseFontSize);
79
+ const px = toPx(parsed.number, parsed.unit, baseFontSize);
79
80
  if (px !== null) {
80
81
  map[`${px}px`] = token;
81
82
  }
@@ -211,23 +212,36 @@ function mergeTokenMapFromCssCustomProperties({
211
212
  };
212
213
  const origin = displayOrigin(root && root.source && root.source.input ? root.source.input.file : null);
213
214
 
215
+ // Every value each custom property is given in the file. A property given
216
+ // two values (at the root and under a media query, a theme or a data
217
+ // attribute) renders differently there, so no fix writes it (issue #231).
218
+ const valuesByProp = new Map();
214
219
  root.walkDecls((decl) => {
215
220
  const prop = decl.prop.toLowerCase();
216
- if (!prop.startsWith('--')) {
221
+ if (!prop.startsWith('--') || !tokenRegex.test(prop)) {
217
222
  return;
218
223
  }
224
+ const parsed = parseTokenValueLength(decl.value);
225
+ const px = parsed ? toPx(parsed.number, parsed.unit, baseFontSize) : null;
226
+ const key = px === null ? `raw:${decl.value.trim()}` : `px:${formatLength(px, 'px')}`;
227
+ if (!valuesByProp.has(decl.prop)) {
228
+ valuesByProp.set(decl.prop, { decl, keys: new Set() });
229
+ }
230
+ valuesByProp.get(decl.prop).keys.add(key);
231
+ });
219
232
 
220
- if (!tokenRegex.test(prop)) {
221
- return;
233
+ for (const { decl, keys } of valuesByProp.values()) {
234
+ if (keys.size > 1) {
235
+ continue;
222
236
  }
223
237
 
224
238
  const parsed = parseTokenValueLength(decl.value);
225
239
  if (!parsed || parsed.number === 0) {
226
- return;
240
+ continue;
227
241
  }
228
242
 
229
243
  addLengthValueMapping(nextMap, decl.value, `var(${decl.prop})`, baseFontSize, origins, origin);
230
- });
244
+ }
231
245
 
232
246
  return nextMap;
233
247
  }
@@ -375,8 +389,7 @@ function mergeTokenMapFromTailwindSpacing({
375
389
  continue;
376
390
  }
377
391
 
378
- const absolute = Math.abs(parsed.number);
379
- const normalizedRaw = formatLength(absolute, parsed.unit || 'px');
392
+ const normalizedRaw = formatLength(parsed.number, parsed.unit || 'px');
380
393
  nextMap[normalizedRaw] = `theme(spacing.${key})`;
381
394
  if (origins && !origins[nextMap[normalizedRaw]]) {
382
395
  origins[nextMap[normalizedRaw]] = displayOrigin(resolvedPath);
@@ -0,0 +1,64 @@
1
+ 'use strict';
2
+
3
+ const path = require('node:path');
4
+
5
+ const { normalizeTokenSourceFormat } = require('./token-sources');
6
+
7
+ function isNonEmptyString(value) {
8
+ return typeof value === 'string' && value.trim().length > 0;
9
+ }
10
+
11
+ /**
12
+ * One reading of a token source entry, shared by the lint (`scaleSources`,
13
+ * `.rhythmguardrc.json`) and the audit (config file, CLI, API), so both read
14
+ * the same files from the same config (issue #271). An entry is a path string
15
+ * or an object with `path` (or its alias `file`), and optionally `format`,
16
+ * `baseDir` (resolved from `baseDir` given here, the config file's directory
17
+ * for a config file) and `tokenPattern`.
18
+ *
19
+ * Returns `{ source }` with `{ baseDir, format, path, tokenPattern? }`, ready
20
+ * for parseTokenSources, or `{ error }` with a message; it never throws, so
21
+ * the lint can turn a bad entry into a warning and the audit into an error.
22
+ */
23
+ function normalizeTokenSourceEntry(entry, baseDir, { defaultFormat = 'auto' } = {}) {
24
+ if (typeof entry === 'string') {
25
+ entry = { path: entry };
26
+ }
27
+
28
+ if (!entry || typeof entry !== 'object' || Array.isArray(entry)) {
29
+ return { error: 'audit.tokenSources entries must be strings or objects.' };
30
+ }
31
+
32
+ const sourcePath = entry.path || entry.file;
33
+ if (!isNonEmptyString(sourcePath)) {
34
+ return { error: 'token source objects must include a path.' };
35
+ }
36
+
37
+ if (entry.tokenPattern !== undefined && !isNonEmptyString(entry.tokenPattern)) {
38
+ return { error: 'token source tokenPattern must be a non-empty string.' };
39
+ }
40
+
41
+ if (entry.baseDir !== undefined && !isNonEmptyString(entry.baseDir)) {
42
+ return { error: 'token source baseDir must be a non-empty string.' };
43
+ }
44
+
45
+ let format;
46
+ try {
47
+ format = normalizeTokenSourceFormat(entry.format || defaultFormat);
48
+ } catch (err) {
49
+ return { error: err.message };
50
+ }
51
+
52
+ return {
53
+ source: {
54
+ baseDir: entry.baseDir ? path.resolve(baseDir, entry.baseDir) : baseDir,
55
+ format,
56
+ path: sourcePath,
57
+ ...(entry.tokenPattern ? { tokenPattern: entry.tokenPattern } : {}),
58
+ },
59
+ };
60
+ }
61
+
62
+ module.exports = {
63
+ normalizeTokenSourceEntry,
64
+ };