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.
Files changed (34) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/CONTRIBUTING.md +19 -3
  3. package/README.md +2 -1
  4. package/package.json +7 -7
  5. package/src/audit/args.js +103 -307
  6. package/src/audit/config.js +1 -1
  7. package/src/audit/contract.js +5 -28
  8. package/src/audit/index.js +4 -7
  9. package/src/audit/report.js +14 -14
  10. package/src/audit/scan/files.js +262 -0
  11. package/src/audit/scan/stylesheets.js +275 -0
  12. package/src/audit/scan/templates.js +175 -0
  13. package/src/cli/doctor.js +1 -1
  14. package/src/cli/quickstart.js +1 -1
  15. package/src/{utils → core}/length.js +21 -0
  16. package/src/{utils → core}/options.js +32 -108
  17. package/src/{utils → core}/scale-inference.js +151 -15
  18. package/src/{utils → core}/token-sources.js +121 -12
  19. package/src/{utils/value-utils.js → core/value-nodes.js} +1 -17
  20. package/src/eslint/rules/tailwind-class-use-motion-scale.js +2 -2
  21. package/src/eslint/rules/tailwind-class-use-scale.js +2 -2
  22. package/src/rules/no-offscale-transform/index.js +22 -91
  23. package/src/rules/prefer-token/index.js +15 -73
  24. package/src/rules/report.js +75 -0
  25. package/src/rules/use-motion-scale/index.js +25 -42
  26. package/src/rules/use-scale/index.js +21 -94
  27. package/src/rules/validate.js +132 -0
  28. package/src/audit/scan.js +0 -676
  29. /package/src/{utils/constants.js → core/css-vocabulary.js} +0 -0
  30. /package/src/{utils → core}/tailwind-class-analysis.js +0 -0
  31. /package/src/{utils → core}/tailwind-motion-analysis.js +0 -0
  32. /package/src/{utils → core}/time.js +0 -0
  33. /package/src/{utils → core}/token-map.js +0 -0
  34. /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 declarationPattern = /(--[\w-]+)\s*:\s*([^;{}]+)/g;
242
- let match;
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
- while ((match = declarationPattern.exec(source)) !== null) {
245
- const token = match[1];
246
- if (!matchesKind(token)) {
247
- continue;
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
- tokens.push({
251
- token,
252
- value: match[2].trim(),
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
- tokens.push(...collectScssTokens(source, matchesKind));
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('./constants');
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('../../utils/time');
4
- const { createTailwindMotionAnalyzer } = require('../../utils/tailwind-motion-analysis');
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('../../utils/length');
4
- const { createTailwindClassAnalyzer } = require('../../utils/tailwind-class-analysis');
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('../../utils/length');
13
+ } = require('../../core/length');
16
14
  const {
17
15
  buildScaleOptions,
18
- resolvePropertyScale,
19
- validateNoOffscaleTransformSecondaryOptions,
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('../../utils/value-utils');
23
+ } = require('../../core/value-nodes');
28
24
 
29
25
  const {
30
- DEFAULT_AUTO_TOKEN_PATTERN,
31
- resolveAutoScale,
32
- } = require('../../utils/scale-inference');
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 = stylelint.utils.validateOptions(result, ruleName, {
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
- if (options.invalidPreset) {
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
- if (options.scaleAuto) {
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
- scaleCache.set(prop, next);
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
- const index = declarationValueIndex(decl) + node.sourceIndex;
134
- const endIndex = index + node.value.length;
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: decl,
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
- ? getFixedNodeValue(parsedLength, nearest.nearest, options)
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
- ? getFixedNodeValue(parsedLength, nearest.nearest, options)
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('../../utils/length');
11
+ } = require('../../core/length');
14
12
  const {
15
13
  buildTokenOptions,
16
- resolvePropertyScale,
17
- validatePreferTokenSecondaryOptions,
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('../../utils/value-utils');
30
- const { buildEffectiveTokenMap } = require('../../utils/token-map');
24
+ } = require('../../core/value-nodes');
25
+ const { buildEffectiveTokenMap } = require('../../core/token-map');
31
26
 
32
27
  const {
33
- DEFAULT_AUTO_TOKEN_PATTERN,
34
- resolveAutoScale,
35
- } = require('../../utils/scale-inference');
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 = stylelint.utils.validateOptions(result, ruleName, {
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
- if (options.invalidPreset) {
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
- if (options.scaleAuto) {
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 scaleCache = new Map();
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
- const index = declarationValueIndex(decl) + node.sourceIndex;
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
+ };