stylelint-plugin-rhythmguard 3.10.2 → 3.11.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.
@@ -7,6 +7,7 @@ const {
7
7
  isHairlineLength,
8
8
  numbersEqual,
9
9
  parseLengthToken,
10
+ pxOnScale,
10
11
  toPx,
11
12
  } = require('../../core/length');
12
13
  const {
@@ -36,9 +37,9 @@ const { validatePrimary, validatePreferTokenSecondaryOptions } = require('../val
36
37
 
37
38
  const {
38
39
  negateReplacement,
40
+ signedTokensForLength,
39
41
  tokenFitsProperty,
40
42
  tokenIndexFromDefinitions,
41
- tokensForLength,
42
43
  } = require('../../core/token-index');
43
44
 
44
45
  const ruleName = 'rhythmguard/prefer-token';
@@ -61,30 +62,31 @@ function listTokens(tokens) {
61
62
  : references.join('');
62
63
  }
63
64
 
64
- function applyNegativeToken(replacement, parsedLength) {
65
- return !replacement || parsedLength.number >= 0 ? replacement : negateReplacement(replacement);
66
- }
67
-
68
- /** The token for a raw length, and the text the fix writes (negated when the literal is). */
65
+ /**
66
+ * The token for a raw length, and the text the fix writes. A key with the
67
+ * literal's sign holds exactly that value, so its token is written as is; a
68
+ * negative literal that only matches the positive key gets the token negated.
69
+ * Keys are signed, so a positive literal never finds a negative token (issue
70
+ * #221).
71
+ */
69
72
  function resolveTokenReplacement(tokenMap, raw, parsedLength, options) {
70
- const found = (token) => ({ replacement: applyNegativeToken(token, parsedLength), token });
71
-
72
- if (Object.prototype.hasOwnProperty.call(tokenMap, raw)) {
73
- return found(tokenMap[raw]);
74
- }
75
-
76
- const absoluteRaw = formatLength(Math.abs(parsedLength.number), parsedLength.unit || 'px');
77
- if (Object.prototype.hasOwnProperty.call(tokenMap, absoluteRaw)) {
78
- return found(tokenMap[absoluteRaw]);
73
+ const unit = parsedLength.unit || 'px';
74
+ const negative = parsedLength.number < 0;
75
+ // [key, negate]: the literal as written, then normalized, then in px.
76
+ const lookups = [[raw, false], [formatLength(parsedLength.number, unit), false]];
77
+ if (negative) lookups.push([formatLength(Math.abs(parsedLength.number), unit), true]);
78
+ if (options.unitStrategy === 'convert') {
79
+ const px = toPx(parsedLength.number, parsedLength.unit, options.baseFontSize);
80
+ if (px !== null) {
81
+ lookups.push([`${px}px`, false]);
82
+ if (negative) lookups.push([`${Math.abs(px)}px`, true]);
83
+ }
79
84
  }
80
85
 
81
- if (options.unitStrategy === 'convert') {
82
- const absPx = toPx(Math.abs(parsedLength.number), parsedLength.unit, options.baseFontSize);
83
- if (absPx !== null) {
84
- const pxKey = `${absPx}px`;
85
- if (Object.prototype.hasOwnProperty.call(tokenMap, pxKey)) {
86
- return found(tokenMap[pxKey]);
87
- }
86
+ for (const [key, negate] of lookups) {
87
+ if (Object.prototype.hasOwnProperty.call(tokenMap, key)) {
88
+ const token = tokenMap[key];
89
+ return { replacement: negate ? negateReplacement(token) : token, token };
88
90
  }
89
91
  }
90
92
 
@@ -98,15 +100,16 @@ function resolveTokenReplacement(tokenMap, raw, parsedLength, options) {
98
100
  * token in another unit, or several tokens, are named but never written.
99
101
  */
100
102
  function resolveFromIndex(tokenIndex, parsedLength, prop, options) {
101
- const px = toPx(Math.abs(parsedLength.number), parsedLength.unit || 'px', options.baseFontSize);
103
+ const px = toPx(parsedLength.number, parsedLength.unit || 'px', options.baseFontSize);
102
104
  if (px === null || tokenIndex.length === 0) {
103
105
  return { candidates: [] };
104
106
  }
105
107
  const unit = parsedLength.unit || 'px';
106
- const sameUnit = tokensForLength(tokenIndex, px, unit, { prop });
107
- const candidates = sameUnit.length > 0 || options.unitStrategy !== 'convert'
108
+ const sameUnit = signedTokensForLength(tokenIndex, px, unit, { prop });
109
+ const found = sameUnit.tokens.length > 0 || options.unitStrategy !== 'convert'
108
110
  ? sameUnit
109
- : tokensForLength(tokenIndex, px, unit, { anyUnit: true, prop });
111
+ : signedTokensForLength(tokenIndex, px, unit, { anyUnit: true, prop });
112
+ const candidates = found.tokens;
110
113
  if (candidates.length !== 1) {
111
114
  return { candidates };
112
115
  }
@@ -114,9 +117,9 @@ function resolveFromIndex(tokenIndex, parsedLength, prop, options) {
114
117
  const entry = tokenIndex.find((item) => item.token === candidates[0]);
115
118
  return {
116
119
  candidates,
117
- fixable: sameUnit.length === 1,
120
+ fixable: sameUnit.tokens.length === 1,
118
121
  origin: entry ? entry.file : null,
119
- replacement: applyNegativeToken(token, parsedLength),
122
+ replacement: found.negate ? negateReplacement(token) : token,
120
123
  token,
121
124
  };
122
125
  }
@@ -143,7 +146,7 @@ const ruleFunction = (primary, secondaryOptions) => {
143
146
 
144
147
  withResolvedScale(options, root);
145
148
 
146
- const tokenRegex = createTokenRegex(options.tokenPattern, result, ruleName);
149
+ const tokenRegex = createTokenRegex(options.tokenPattern, { result, root, ruleName });
147
150
  const origins = {};
148
151
  const tokenMap = buildEffectiveTokenMap({
149
152
  options,
@@ -253,7 +256,7 @@ const ruleFunction = (primary, secondaryOptions) => {
253
256
  } else {
254
257
  const absPx = toPx(Math.abs(parsedLength.number), parsedLength.unit, options.baseFontSize);
255
258
  if (absPx !== null) {
256
- const onScale = scalePx.some((entry) => numbersEqual(entry, absPx));
259
+ const onScale = pxOnScale(scalePx, absPx, parsedLength.unit, options.baseFontSize);
257
260
  if (onScale) {
258
261
  return false;
259
262
  }
@@ -13,12 +13,14 @@ const { declarationValueIndex } = require('../core/value-nodes');
13
13
  * against the root and replaced with the historical default so the file still
14
14
  * lints instead of throwing inside Stylelint.
15
15
  */
16
- function createTokenRegex(tokenPattern, result, ruleName) {
16
+ function createTokenRegex(tokenPattern, { result, root, ruleName }) {
17
17
  try {
18
18
  return new RegExp(tokenPattern);
19
19
  } catch {
20
+ // Stylelint throws on a report without a node, so the root carries it (issue #222).
20
21
  stylelint.utils.report({
21
22
  message: `Invalid tokenPattern regex: ${tokenPattern}`,
23
+ node: root,
22
24
  result,
23
25
  ruleName,
24
26
  });
@@ -9,6 +9,7 @@ const {
9
9
  nearestText,
10
10
  numbersEqual,
11
11
  parseLengthToken,
12
+ pxOnScale,
12
13
  toPx,
13
14
  } = require('../../core/length');
14
15
  const {
@@ -162,7 +163,7 @@ function checkLengthValue({
162
163
  return false;
163
164
  }
164
165
 
165
- const isOnScale = scalePx.some((scaleValue) => numbersEqual(scaleValue, pxValue));
166
+ const isOnScale = pxOnScale(scalePx, pxValue, parsedLength.unit, options.baseFontSize);
166
167
  if (isOnScale) {
167
168
  return false;
168
169
  }
@@ -249,7 +250,7 @@ const ruleFunction = (primary, secondaryOptions) => {
249
250
  options.decisions = readDecisions(options, { result, root });
250
251
  withResolvedScale(options, root);
251
252
 
252
- const tokenRegex = createTokenRegex(options.tokenPattern, result, ruleName);
253
+ const tokenRegex = createTokenRegex(options.tokenPattern, { result, root, ruleName });
253
254
  if (options.fixWith === 'token') {
254
255
  options.tokenIndex = tokenIndexFromDefinitions(
255
256
  collectTokenDefinitions({
@@ -269,7 +270,7 @@ const ruleFunction = (primary, secondaryOptions) => {
269
270
  const report = (value, decl, node, nearest, fixedValue = null, nearestUnit = 'px', extraNote = '', position = null) => {
270
271
  const lower = nearest ? formatLength(nearest.lower, nearestUnit) : 'n/a';
271
272
  const upper = nearest ? formatLength(nearest.upper, nearestUnit) : 'n/a';
272
- const tokenNote = nearest ? tokenHoldsNote(fixedValue, formatLength(nearest.nearest, nearestUnit)) : '';
273
+ const tokenNote = nearest ? tokenHoldsNote(fixedValue, formatLength(nearest.nearest, nearestUnit), String(value).trim().startsWith('-')) : '';
273
274
  reportValueNode({
274
275
  decl,
275
276
  message: messages.rejected(value, lower, upper, [fallbackNote, tokenNote, extraNote, options.note].filter(Boolean).join(' '), position, position ? pxAside(value, options.baseFontSize) : '', getScaleStateForProperty(decl.prop.toLowerCase()).group),