@dialpad/stylelint-plugin-dialtone 1.4.0-next.1 → 1.4.0-next.3

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.
@@ -72,6 +72,27 @@ const VALUE_MAP = {
72
72
  },
73
73
  };
74
74
 
75
+ // Physical → logical utility class prefix mappings
76
+ // These map directional class names to their logical equivalents in templates.
77
+ // Sorted by longest prefix first to avoid partial matches (d-ml- before d-l-).
78
+ const CLASS_PREFIX_MAP = [
79
+ // Margin
80
+ ['d-mt-', 'd-mbs-'],
81
+ ['d-mr-', 'd-mie-'],
82
+ ['d-mb-', 'd-mbe-'],
83
+ ['d-ml-', 'd-mis-'],
84
+ // Padding
85
+ ['d-pt-', 'd-pbs-'],
86
+ ['d-pr-', 'd-pie-'],
87
+ ['d-pb-', 'd-pbe-'],
88
+ ['d-pl-', 'd-pis-'],
89
+ // Position (inset)
90
+ ['d-t-', 'd-ibs-'],
91
+ ['d-r-', 'd-iie-'],
92
+ ['d-b-', 'd-ibe-'],
93
+ ['d-l-', 'd-iis-'],
94
+ ];
95
+
75
96
  function escapeRegex(string) {
76
97
  return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
77
98
  }
@@ -117,6 +138,47 @@ function fixLogicalProperties(content) {
117
138
  return fixedLines.join('\n');
118
139
  }
119
140
 
141
+ /**
142
+ * Replace physical utility class prefixes with logical equivalents in a string.
143
+ * e.g. "d-pt-100 d-ml-200" → "d-pbs-100 d-mis-200"
144
+ */
145
+ function fixLogicalClassNames(content) {
146
+ let fixed = content;
147
+ for (const [physical, logical] of CLASS_PREFIX_MAP) {
148
+ // Match the physical prefix followed by a token stop number or 'n' + number (negative)
149
+ // Word boundary ensures we don't match partial class names
150
+ const regex = new RegExp(escapeRegex(physical) + '(n?[0-9]+)', 'g');
151
+ fixed = fixed.replace(regex, `${logical}$1`);
152
+ }
153
+ return fixed;
154
+ }
155
+
156
+ /**
157
+ * Process class="..." and :class="..." attributes in template content,
158
+ * replacing physical class prefixes with logical equivalents.
159
+ */
160
+ function processTemplateClassNames(content) {
161
+ let hasChanges = false;
162
+
163
+ // Match class="..." attributes (static)
164
+ const staticClassRegex = /(class=")(.*?)(")/g;
165
+ let fixed = content.replace(staticClassRegex, (_match, open, classes, close) => {
166
+ const fixedClasses = fixLogicalClassNames(classes);
167
+ if (fixedClasses !== classes) hasChanges = true;
168
+ return open + fixedClasses + close;
169
+ });
170
+
171
+ // Match :class="'...'" attributes (dynamic with string literal)
172
+ const dynamicClassRegex = /(:class="')(.*?)(')/g;
173
+ fixed = fixed.replace(dynamicClassRegex, (_match, open, classes, close) => {
174
+ const fixedClasses = fixLogicalClassNames(classes);
175
+ if (fixedClasses !== classes) hasChanges = true;
176
+ return open + fixedClasses + close;
177
+ });
178
+
179
+ return { fixed, hasChanges };
180
+ }
181
+
120
182
  function processStyleBlocks(content) {
121
183
  // Match all <style> blocks (with any attributes like lang, scoped, etc.)
122
184
  const styleRegex = /(<style[^>]*>)([\s\S]*?)(<\/style>)/gi;
@@ -150,11 +212,12 @@ function processMarkdownFile(filePath) {
150
212
  return part; // Keep code blocks unchanged
151
213
  }
152
214
  // Process style blocks in non-code parts
153
- const result = processStyleBlocks(part);
154
- if (result.hasChanges) {
155
- hasChanges = true;
156
- }
157
- return result.fixed;
215
+ const styleResult = processStyleBlocks(part);
216
+ if (styleResult.hasChanges) hasChanges = true;
217
+ // Process class names in non-code parts
218
+ const classResult = processTemplateClassNames(styleResult.fixed);
219
+ if (classResult.hasChanges) hasChanges = true;
220
+ return classResult.fixed;
158
221
  });
159
222
 
160
223
  if (hasChanges) {
@@ -171,11 +234,21 @@ function processMarkdownFile(filePath) {
171
234
 
172
235
  function processFileWithStyleBlocks(filePath) {
173
236
  try {
174
- const content = fs.readFileSync(filePath, 'utf8');
175
- const result = processStyleBlocks(content);
237
+ let content = fs.readFileSync(filePath, 'utf8');
238
+ let hasChanges = false;
239
+
240
+ // Fix CSS properties in <style> blocks
241
+ const styleResult = processStyleBlocks(content);
242
+ if (styleResult.hasChanges) hasChanges = true;
243
+ content = styleResult.fixed;
176
244
 
177
- if (result.hasChanges) {
178
- fs.writeFileSync(filePath, result.fixed, 'utf8');
245
+ // Fix class names in templates (outside <style> blocks)
246
+ const classResult = processTemplateClassNames(content);
247
+ if (classResult.hasChanges) hasChanges = true;
248
+ content = classResult.fixed;
249
+
250
+ if (hasChanges) {
251
+ fs.writeFileSync(filePath, content, 'utf8');
179
252
  console.log(`Fixed: ${filePath}`);
180
253
  return true;
181
254
  }
package/lib/index.js CHANGED
@@ -1,7 +1,9 @@
1
1
  'use strict';
2
2
 
3
3
  const noBaseColorTokens = require('./rules/no-base-color-tokens');
4
+ const noDeprecatedSizeTokens = require('./rules/no-deprecated-size-tokens');
4
5
  const noDeprecatedSpaceTokens = require('./rules/no-deprecated-space-tokens');
6
+ const noDeprecatedSuccessTokens = require('./rules/no-deprecated-success-tokens');
5
7
  const noMixins = require('./rules/no-mixins');
6
8
  const recommendFontStyleTokens = require('./rules/recommend-font-style-tokens');
7
9
  const useDialtoneTokens = require('./rules/use-dialtone-tokens');
@@ -9,7 +11,9 @@ const useLogical = require('stylelint-use-logical');
9
11
 
10
12
  module.exports = [
11
13
  noBaseColorTokens,
14
+ noDeprecatedSizeTokens,
12
15
  noDeprecatedSpaceTokens,
16
+ noDeprecatedSuccessTokens,
13
17
  noMixins,
14
18
  recommendFontStyleTokens,
15
19
  useDialtoneTokens,
@@ -0,0 +1,63 @@
1
+ const stylelint = require('stylelint');
2
+
3
+ const {
4
+ createPlugin,
5
+ utils: { report, ruleMessages, validateOptions },
6
+ } = stylelint;
7
+
8
+ const ruleName = '@dialpad/stylelint-plugin-dialtone/no-deprecated-size-tokens';
9
+
10
+ const messages = ruleMessages(ruleName, {
11
+ deprecatedSizeToken:
12
+ '--dt-size-* tokens have been replaced. Use --dt-layout-* for layout (widths/heights) or --dt-spacing-* for spacing (padding/margin). Run "npx dialtone-migration-helper" and select "size-to-layout".',
13
+ deprecatedSpaceToken:
14
+ '--dt-space-* tokens have been replaced by --dt-spacing-*. Run "npx dialtone-migration-helper" and select "space-to-spacing".',
15
+ });
16
+
17
+ const meta = {
18
+ url: 'https://github.com/dialpad/dialtone/blob/staging/packages/stylelint-plugin-dialtone/docs/rules/no-deprecated-size-tokens.md',
19
+ };
20
+
21
+ /** @type {import('stylelint').Rule} */
22
+ const ruleFunction = (primary) => {
23
+ return (root, result) => {
24
+ const validOptions = validateOptions(result, ruleName, {
25
+ actual: primary,
26
+ });
27
+
28
+ if (!validOptions) return;
29
+
30
+ root.walkDecls((declaration) => {
31
+ // Match --dt-size-{number} but NOT --dt-size-border-* or --dt-size-radius-* (those are valid)
32
+ const sizeTokenMatches = declaration.value.match(/var\(--dt-size-(?!border-|radius-)[^)]+\)/g);
33
+ if (sizeTokenMatches) {
34
+ sizeTokenMatches.forEach(() => {
35
+ report({
36
+ result,
37
+ ruleName,
38
+ node: declaration,
39
+ message: messages.deprecatedSizeToken,
40
+ });
41
+ });
42
+ }
43
+
44
+ const spaceTokenMatches = declaration.value.match(/var\(--dt-space-[^)]+\)/g);
45
+ if (spaceTokenMatches) {
46
+ spaceTokenMatches.forEach(() => {
47
+ report({
48
+ result,
49
+ ruleName,
50
+ node: declaration,
51
+ message: messages.deprecatedSpaceToken,
52
+ });
53
+ });
54
+ }
55
+ });
56
+ };
57
+ };
58
+
59
+ ruleFunction.ruleName = ruleName;
60
+ ruleFunction.messages = messages;
61
+ ruleFunction.meta = meta;
62
+
63
+ module.exports = createPlugin(ruleName, ruleFunction);
@@ -0,0 +1,58 @@
1
+ const stylelint = require('stylelint');
2
+
3
+ const {
4
+ createPlugin,
5
+ utils: { report, ruleMessages, validateOptions },
6
+ } = stylelint;
7
+
8
+ const ruleName = '@dialpad/stylelint-plugin-dialtone/no-deprecated-success-tokens';
9
+
10
+ const messages = ruleMessages(ruleName, {
11
+ deprecated: (successToken, positiveToken) =>
12
+ `Replace "${successToken}" with "${positiveToken}". Run "npx dialtone-migration-helper --config success-to-positive" to migrate automatically.`,
13
+ });
14
+
15
+ const meta = {
16
+ url: 'https://github.com/dialpad/dialtone/blob/staging/packages/stylelint-plugin-dialtone/docs/rules/no-deprecated-success-tokens.md',
17
+ };
18
+
19
+ // Match var(--dt-color-{role}-success{suffix?}) where role is foreground, surface,
20
+ // border, or link, and suffix (if present) is one of the known variants. The
21
+ // suffix list mirrors the `success-to-positive` migration helper so flagged
22
+ // tokens always have a valid `positive*` replacement.
23
+ const SUCCESS_SUFFIX = '(?:-(?:subtle-opaque-inverted|subtle-opaque|subtle-inverted|strong-inverted|opaque-inverted|inverted-hover|subtle|strong|opaque|inverted|hover))?';
24
+ const SUCCESS_TOKEN_RE = new RegExp(`var\\(--dt-color-(?:foreground|surface|border|link)-success${SUCCESS_SUFFIX}\\)`, 'g');
25
+
26
+ /** @type {import('stylelint').Rule} */
27
+ const ruleFunction = (primary) => {
28
+ return (root, result) => {
29
+ const validOptions = validateOptions(result, ruleName, {
30
+ actual: primary,
31
+ });
32
+
33
+ if (!validOptions) return;
34
+
35
+ root.walkDecls((declaration) => {
36
+ const matches = declaration.value.match(SUCCESS_TOKEN_RE);
37
+ if (!matches) return;
38
+
39
+ matches.forEach((match) => {
40
+ const successToken = match.replace('var(', '').replace(/\)$/, '');
41
+ const positiveToken = successToken.replace('-success', '-positive');
42
+
43
+ report({
44
+ result,
45
+ ruleName,
46
+ node: declaration,
47
+ message: messages.deprecated(successToken, positiveToken),
48
+ });
49
+ });
50
+ });
51
+ };
52
+ };
53
+
54
+ ruleFunction.ruleName = ruleName;
55
+ ruleFunction.messages = messages;
56
+ ruleFunction.meta = meta;
57
+
58
+ module.exports = createPlugin(ruleName, ruleFunction);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dialpad/stylelint-plugin-dialtone",
3
- "version": "1.4.0-next.1",
3
+ "version": "1.4.0-next.3",
4
4
  "description": "dialtone stylelint plugin",
5
5
  "keywords": [
6
6
  "Dialpad",