stylelint-plugin-rhythmguard 1.4.0 → 1.4.1

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 CHANGED
@@ -6,6 +6,16 @@ The format follows Keep a Changelog principles and semantic versioning.
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [1.4.1] - 2026-02-21
10
+
11
+ ### Fixed
12
+
13
+ - `properties` now correctly supports regex-like string entries (for example `"/^(margin|padding)$/"`) in rule options.
14
+ - Property matcher behavior is now deterministic for regex matchers with stateful `g`/`y` flags by normalizing stateful behavior during matching.
15
+ - Generic rule messaging for expanded property groups:
16
+ - `rhythmguard/use-scale` now reports off-scale values without spacing-only wording.
17
+ - `rhythmguard/prefer-token` now reports raw scale values without spacing-only wording.
18
+
9
19
  ## [1.4.0] - 2026-02-21
10
20
 
11
21
  ### Added
package/README.md CHANGED
@@ -349,8 +349,8 @@ Options:
349
349
  | `mathFunctionArguments` | `Record<mathFn, number[]>` | `{}` | Restricts linting to specific 1-based argument indexes per math function |
350
350
  | `ignoreMathFunctionArguments` | `Record<mathFn, number[]>` | `{}` | Excludes specific 1-based argument indexes per math function |
351
351
  | `propertyGroups` | `Array<'spacing' \| 'radius' \| 'typography' \| 'size'>` | `['spacing']` | Selects built-in property groups when `properties` is not provided |
352
- | `properties` | `Array<string|RegExp>` | built-in spacing patterns | Override targeted property set; string values must be supported spacing property names |
353
- | `propertyScales` | `Record<propertyOrRegex, scaleOrPreset>` | `{}` | Per-property scale overrides (supports exact names or `/regex/flags` keys) |
352
+ | `properties` | `Array<string|RegExp>` | built-in spacing patterns | Override targeted property set; string values may be supported property names or regex-like strings (`/pattern/flags`) |
353
+ | `propertyScales` | `Record<propertyOrRegex, scaleOrPreset>` | `{}` | Per-property scale overrides (supports exact names or `/regex/flags` keys; stateful `g`/`y` flags are normalized for deterministic matching) |
354
354
 
355
355
  ### `rhythmguard/prefer-token`
356
356
 
@@ -395,8 +395,8 @@ Options:
395
395
  | `tailwindConfigPath` | `string` | `null` | Path to Tailwind config used by `tokenMapFromTailwindSpacing` (`.js`, `.cjs`, `.mjs`) |
396
396
  | `ignoreValues` | `string[]` | CSS global keywords + `auto` | Skips keyword literals |
397
397
  | `propertyGroups` | `Array<'spacing' \| 'radius' \| 'typography' \| 'size'>` | `['spacing']` | Selects built-in property groups when `properties` is not provided |
398
- | `properties` | `Array<string|RegExp>` | built-in spacing patterns | Override targeted property set; string values must be supported spacing property names |
399
- | `propertyScales` | `Record<propertyOrRegex, scaleOrPreset>` | `{}` | Per-property scale overrides for numeric migration mode |
398
+ | `properties` | `Array<string|RegExp>` | built-in spacing patterns | Override targeted property set; string values may be supported property names or regex-like strings (`/pattern/flags`) |
399
+ | `propertyScales` | `Record<propertyOrRegex, scaleOrPreset>` | `{}` | Per-property scale overrides for numeric migration mode (stateful `g`/`y` flags are normalized for deterministic matching) |
400
400
 
401
401
  ### `rhythmguard/no-offscale-transform`
402
402
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stylelint-plugin-rhythmguard",
3
- "version": "1.4.0",
3
+ "version": "1.4.1",
4
4
  "description": "Stylelint plugin for spacing scale, token enforcement, and Tailwind class-string governance",
5
5
  "keywords": [
6
6
  "stylelint",
@@ -34,7 +34,7 @@ const messages = stylelint.utils.ruleMessages(ruleName, {
34
34
  invalidPreset: (presetName, presetNames) =>
35
35
  `Unknown scale preset "${presetName}". Available presets: ${presetNames.join(', ')}.`,
36
36
  rejected: (value) =>
37
- `Unexpected raw spacing value "${value}". Use design tokens for spacing decisions.`,
37
+ `Unexpected raw scale value "${value}". Use design tokens for scale decisions.`,
38
38
  });
39
39
 
40
40
  function applyNegativeToken(replacement, parsedLength) {
@@ -35,7 +35,7 @@ const messages = stylelint.utils.ruleMessages(ruleName, {
35
35
  invalidPreset: (presetName, presetNames) =>
36
36
  `Unknown scale preset "${presetName}". Available presets: ${presetNames.join(', ')}.`,
37
37
  rejected: (value, lower, upper) =>
38
- `Unexpected off-scale spacing value "${value}". Use spacing scale values (nearest: ${lower} or ${upper}).`,
38
+ `Unexpected off-scale value "${value}". Use scale values (nearest: ${lower} or ${upper}).`,
39
39
  });
40
40
 
41
41
  function getFixedNodeValue(parsedLength, nearestPx, options) {
@@ -102,6 +102,19 @@ function parseRegexLikeString(value) {
102
102
  }
103
103
  }
104
104
 
105
+ function toStableRegex(regex) {
106
+ if (!(regex instanceof RegExp)) {
107
+ return null;
108
+ }
109
+
110
+ if (!regex.global && !regex.sticky) {
111
+ return regex;
112
+ }
113
+
114
+ const stableFlags = regex.flags.replace(/[gy]/g, '');
115
+ return new RegExp(regex.source, stableFlags);
116
+ }
117
+
105
118
  function isPropertyPatternEntry(value) {
106
119
  if (value instanceof RegExp) {
107
120
  return true;
@@ -300,7 +313,9 @@ function normalizePropertyGroups(rawGroups) {
300
313
 
301
314
  function resolvePropertyPatterns(options) {
302
315
  if (Array.isArray(options.properties)) {
303
- return options.properties;
316
+ return options.properties
317
+ .map((entry) => compilePropertyMatcher(entry))
318
+ .filter((entry) => entry !== null);
304
319
  }
305
320
 
306
321
  const groups = normalizePropertyGroups(options.propertyGroups);
@@ -315,7 +330,7 @@ function resolvePropertyPatterns(options) {
315
330
 
316
331
  function compilePropertyMatcher(entry) {
317
332
  if (entry instanceof RegExp) {
318
- return entry;
333
+ return toStableRegex(entry);
319
334
  }
320
335
 
321
336
  if (!isNonEmptyString(entry)) {
@@ -325,7 +340,7 @@ function compilePropertyMatcher(entry) {
325
340
  const trimmed = entry.trim();
326
341
  const parsedRegex = parseRegexLikeString(trimmed);
327
342
  if (parsedRegex) {
328
- return parsedRegex;
343
+ return toStableRegex(parsedRegex);
329
344
  }
330
345
 
331
346
  return trimmed.toLowerCase();
@@ -655,6 +670,10 @@ function resolvePropertyScale(prop, options) {
655
670
 
656
671
  for (const override of options.propertyScaleOverrides) {
657
672
  if (override.matcher instanceof RegExp) {
673
+ if (override.matcher.global || override.matcher.sticky) {
674
+ override.matcher.lastIndex = 0;
675
+ }
676
+
658
677
  if (override.matcher.test(prop)) {
659
678
  return override.scale;
660
679
  }
@@ -11,6 +11,10 @@ function propertyMatches(prop, patterns) {
11
11
  const normalized = prop.toLowerCase();
12
12
  return patterns.some((pattern) => {
13
13
  if (pattern instanceof RegExp) {
14
+ if (pattern.global || pattern.sticky) {
15
+ pattern.lastIndex = 0;
16
+ }
17
+
14
18
  return pattern.test(normalized);
15
19
  }
16
20