@dialpad/eslint-plugin-dialtone 1.13.0-next.1 → 1.13.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.
- package/lib/rules/deprecated-class-props.js +262 -0
- package/lib/rules/prefer-stack-over-flex.js +11 -5
- package/package.json +17 -3
- package/lib/rules/deprecated-headline-sizes.js +0 -77
- package/lib/rules/deprecated-link-styling-classes.js +0 -96
- package/lib/rules/deprecated-physical-naming.js +0 -228
- package/lib/rules/deprecated-pixel-utility-classes.js +0 -95
- package/lib/rules/deprecated-radius-utility-classes.js +0 -64
- package/lib/rules/deprecated-success-color-classes.js +0 -180
- package/lib/rules/deprecated-tshirt-sizes.js +0 -136
- package/lib/rules/focusgroup-requires-label.js +0 -52
- package/lib/rules/focusgroup-requires-role.js +0 -46
- package/lib/util/class-attribute-rule.js +0 -49
|
@@ -1,228 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Detect deprecated physical direction names in Dialtone component
|
|
3
|
-
* slots, props, prop values, and events. Suggests logical replacements.
|
|
4
|
-
*/
|
|
5
|
-
'use strict';
|
|
6
|
-
|
|
7
|
-
//------------------------------------------------------------------------------
|
|
8
|
-
// Constants
|
|
9
|
-
//------------------------------------------------------------------------------
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Maps component names to their deprecated slot names and replacements.
|
|
13
|
-
* null value = special handling (ambiguous, e.g. #icon on dt-button).
|
|
14
|
-
*/
|
|
15
|
-
const DEPRECATED_SLOTS = {
|
|
16
|
-
'dt-badge': { leftIcon: 'startIcon', rightIcon: 'endIcon' },
|
|
17
|
-
'dt-button': { icon: null },
|
|
18
|
-
'dt-input': { leftIcon: 'startIcon', rightIcon: 'endIcon' },
|
|
19
|
-
'dt-tab': { leftIcon: 'startIcon' },
|
|
20
|
-
'dt-split-button': { alphaIcon: 'startIcon', omegaIcon: 'endIcon', omega: 'end' },
|
|
21
|
-
'dt-item-layout': { left: 'start', right: 'end', bottom: 'blockEnd' },
|
|
22
|
-
'dt-recipe-callbox': { right: 'end', bottom: 'blockEnd' },
|
|
23
|
-
'dt-recipe-contact-centers-row': { right: 'end' },
|
|
24
|
-
'dt-recipe-general-row': { left: 'start' },
|
|
25
|
-
'dt-recipe-top-banner-info': { left: 'start', right: 'end' },
|
|
26
|
-
'dt-recipe-grouped-chip': {
|
|
27
|
-
leftIcon: 'startIcon',
|
|
28
|
-
rightIcon: 'endIcon',
|
|
29
|
-
leftContent: 'startContent',
|
|
30
|
-
rightContent: 'endContent',
|
|
31
|
-
},
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Maps component names to their deprecated prop names (kebab-case) and replacements.
|
|
36
|
-
*/
|
|
37
|
-
const DEPRECATED_PROPS = {
|
|
38
|
-
'dt-item-layout': {
|
|
39
|
-
'left-class': 'start-class',
|
|
40
|
-
'right-class': 'end-class',
|
|
41
|
-
'bottom-class': 'block-end-class',
|
|
42
|
-
},
|
|
43
|
-
'dt-split-button': {
|
|
44
|
-
'alpha-active': 'start-active',
|
|
45
|
-
'alpha-aria-label': 'start-aria-label',
|
|
46
|
-
'alpha-icon-position': 'start-icon-position',
|
|
47
|
-
'alpha-leading-class': 'start-leading-class',
|
|
48
|
-
'alpha-trailing-class': 'start-trailing-class',
|
|
49
|
-
'alpha-label-class': 'start-label-class',
|
|
50
|
-
'alpha-disabled': 'start-disabled',
|
|
51
|
-
'alpha-loading': 'start-loading',
|
|
52
|
-
'alpha-tooltip-text': 'start-tooltip-text',
|
|
53
|
-
'omega-active': 'end-active',
|
|
54
|
-
'omega-aria-label': 'end-aria-label',
|
|
55
|
-
'omega-disabled': 'end-disabled',
|
|
56
|
-
'omega-id': 'end-id',
|
|
57
|
-
'omega-tooltip-text': 'end-tooltip-text',
|
|
58
|
-
},
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Maps component names to props whose specific values are deprecated.
|
|
63
|
-
*/
|
|
64
|
-
const DEPRECATED_PROP_VALUES = {
|
|
65
|
-
'dt-button': {
|
|
66
|
-
'icon-position': { left: 'start', right: 'end', top: 'blockStart', bottom: 'blockEnd' },
|
|
67
|
-
},
|
|
68
|
-
'dt-root-layout': {
|
|
69
|
-
'sidebar-position': { left: 'start', right: 'end' },
|
|
70
|
-
},
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Maps component names to their deprecated event names and replacements.
|
|
75
|
-
*/
|
|
76
|
-
const DEPRECATED_EVENTS = {
|
|
77
|
-
'dt-split-button': { 'alpha-clicked': 'start-clicked', 'omega-clicked': 'end-clicked' },
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
//------------------------------------------------------------------------------
|
|
81
|
-
// Helpers
|
|
82
|
-
//------------------------------------------------------------------------------
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Converts PascalCase or camelCase to kebab-case.
|
|
86
|
-
* e.g. 'DtBadge' → 'dt-badge', 'DtRecipeCallbox' → 'dt-recipe-callbox'
|
|
87
|
-
*/
|
|
88
|
-
function toKebabCase (str) {
|
|
89
|
-
return str.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase();
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
//------------------------------------------------------------------------------
|
|
93
|
-
// Rule Definition
|
|
94
|
-
//------------------------------------------------------------------------------
|
|
95
|
-
|
|
96
|
-
module.exports = {
|
|
97
|
-
meta: {
|
|
98
|
-
type: 'suggestion',
|
|
99
|
-
docs: {
|
|
100
|
-
description: 'Detect deprecated physical direction names in Dialtone component slots, props, prop values, and events',
|
|
101
|
-
recommended: false,
|
|
102
|
-
url: 'https://github.com/dialpad/dialtone/blob/staging/packages/eslint-plugin-dialtone/docs/rules/deprecated-physical-naming.md',
|
|
103
|
-
},
|
|
104
|
-
fixable: null,
|
|
105
|
-
schema: [],
|
|
106
|
-
messages: {
|
|
107
|
-
deprecatedSlot:
|
|
108
|
-
'#{{ oldSlot }} on <{{ component }}> is deprecated. Use #{{ newSlot }} instead.',
|
|
109
|
-
deprecatedIconSlot:
|
|
110
|
-
'The #icon slot on <dt-button> is deprecated. Use #startIcon, #endIcon, #blockStartIcon, or #blockEndIcon instead.',
|
|
111
|
-
deprecatedProp:
|
|
112
|
-
'{{ oldProp }} on <{{ component }}> is deprecated. Use {{ newProp }} instead.',
|
|
113
|
-
deprecatedPropValue:
|
|
114
|
-
'{{ prop }}="{{ oldValue }}" on <{{ component }}> is deprecated. Use {{ prop }}="{{ newValue }}" instead.',
|
|
115
|
-
deprecatedEvent:
|
|
116
|
-
'@{{ oldEvent }} on <{{ component }}> is deprecated. Use @{{ newEvent }} instead.',
|
|
117
|
-
},
|
|
118
|
-
},
|
|
119
|
-
|
|
120
|
-
create (context) {
|
|
121
|
-
const sourceCode = context.sourceCode ?? context.getSourceCode();
|
|
122
|
-
return sourceCode.parserServices.defineTemplateBodyVisitor({
|
|
123
|
-
|
|
124
|
-
VElement (node) {
|
|
125
|
-
const rawName = node.rawName || node.name;
|
|
126
|
-
const elementName = rawName.includes('-') ? rawName : toKebabCase(rawName);
|
|
127
|
-
|
|
128
|
-
const slotsMap = DEPRECATED_SLOTS[elementName];
|
|
129
|
-
const propsMap = DEPRECATED_PROPS[elementName];
|
|
130
|
-
const propValuesMap = DEPRECATED_PROP_VALUES[elementName];
|
|
131
|
-
const eventsMap = DEPRECATED_EVENTS[elementName];
|
|
132
|
-
|
|
133
|
-
// Check attributes in a single pass (props, prop values, events)
|
|
134
|
-
if (propsMap || propValuesMap || eventsMap) {
|
|
135
|
-
for (const attr of node.startTag.attributes) {
|
|
136
|
-
if (!attr.directive) {
|
|
137
|
-
// Static props: alpha-active, left-class="x", icon-position="left"
|
|
138
|
-
const attrName = attr.key && (attr.key.rawName || attr.key.name);
|
|
139
|
-
if (attrName && propsMap && propsMap[attrName]) {
|
|
140
|
-
context.report({
|
|
141
|
-
node: attr,
|
|
142
|
-
messageId: 'deprecatedProp',
|
|
143
|
-
data: {
|
|
144
|
-
component: elementName,
|
|
145
|
-
oldProp: attrName,
|
|
146
|
-
newProp: propsMap[attrName],
|
|
147
|
-
},
|
|
148
|
-
});
|
|
149
|
-
}
|
|
150
|
-
if (attrName && propValuesMap && propValuesMap[attrName] && attr.value) {
|
|
151
|
-
const val = attr.value.value;
|
|
152
|
-
if (propValuesMap[attrName][val]) {
|
|
153
|
-
context.report({
|
|
154
|
-
node: attr,
|
|
155
|
-
messageId: 'deprecatedPropValue',
|
|
156
|
-
data: {
|
|
157
|
-
component: elementName,
|
|
158
|
-
prop: attrName,
|
|
159
|
-
oldValue: val,
|
|
160
|
-
newValue: propValuesMap[attrName][val],
|
|
161
|
-
},
|
|
162
|
-
});
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
} else if (attr.directive && attr.key.name.name === 'bind') {
|
|
166
|
-
// Dynamic props: :alpha-active="x", v-bind:omega-disabled="y"
|
|
167
|
-
const bindName = attr.key.argument && (attr.key.argument.rawName || attr.key.argument.name);
|
|
168
|
-
if (bindName && propsMap && propsMap[bindName]) {
|
|
169
|
-
context.report({
|
|
170
|
-
node: attr,
|
|
171
|
-
messageId: 'deprecatedProp',
|
|
172
|
-
data: {
|
|
173
|
-
component: elementName,
|
|
174
|
-
oldProp: bindName,
|
|
175
|
-
newProp: propsMap[bindName],
|
|
176
|
-
},
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
} else if (eventsMap && attr.key.name.name === 'on') {
|
|
180
|
-
const eventName = attr.key.argument && (attr.key.argument.rawName || attr.key.argument.name);
|
|
181
|
-
if (eventName && eventsMap[eventName]) {
|
|
182
|
-
context.report({
|
|
183
|
-
node: attr,
|
|
184
|
-
messageId: 'deprecatedEvent',
|
|
185
|
-
data: {
|
|
186
|
-
component: elementName,
|
|
187
|
-
oldEvent: eventName,
|
|
188
|
-
newEvent: eventsMap[eventName],
|
|
189
|
-
},
|
|
190
|
-
});
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
// Check child <template> elements for deprecated slot names
|
|
197
|
-
if (slotsMap) {
|
|
198
|
-
for (const child of node.children) {
|
|
199
|
-
if (child.type === 'VElement' && child.name === 'template') {
|
|
200
|
-
for (const attr of child.startTag.attributes) {
|
|
201
|
-
if (attr.directive && attr.key.name.name === 'slot') {
|
|
202
|
-
const slotName = attr.key.argument && (attr.key.argument.rawName || attr.key.argument.name);
|
|
203
|
-
if (slotName && slotName in slotsMap) {
|
|
204
|
-
if (slotsMap[slotName] === null) {
|
|
205
|
-
// #icon on dt-button is ambiguous — consumer must choose replacement
|
|
206
|
-
context.report({ node: attr, messageId: 'deprecatedIconSlot' });
|
|
207
|
-
} else {
|
|
208
|
-
context.report({
|
|
209
|
-
node: attr,
|
|
210
|
-
messageId: 'deprecatedSlot',
|
|
211
|
-
data: {
|
|
212
|
-
component: elementName,
|
|
213
|
-
oldSlot: slotName,
|
|
214
|
-
newSlot: slotsMap[slotName],
|
|
215
|
-
},
|
|
216
|
-
});
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
break;
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
},
|
|
226
|
-
});
|
|
227
|
-
},
|
|
228
|
-
};
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Detects usage of pixel-based utility classes (d-h16, d-p8, d-m8, etc.)
|
|
3
|
-
* which should be replaced with token-stop-based equivalents (d-h-25, d-p-100, d-m-100).
|
|
4
|
-
* Autofixes via `lint-staged` — mirrors the `utility-class-to-token-stops` migration helper.
|
|
5
|
-
* @author Joshua Hynes
|
|
6
|
-
*/
|
|
7
|
-
'use strict';
|
|
8
|
-
|
|
9
|
-
const { START, END, buildDetectRegex, createClassAttributeRule } = require('../util/class-attribute-rule');
|
|
10
|
-
|
|
11
|
-
// MUST STAY IN SYNC with:
|
|
12
|
-
// - LAYOUT_STOPS, MARGIN_SIZES_SPACING, MARGIN_SIZES_LAYOUT in dialtone-css/postcss/constants.cjs
|
|
13
|
-
// - SIZING_MAP, SPACING_MAP, NEGATIVE_SPACING_MAP, SPACING_LAYOUT_MAP in
|
|
14
|
-
// dialtone-css/.../migration_helper/configs/utility-class-to-token-stops.mjs
|
|
15
|
-
|
|
16
|
-
// Ordered by descending string-length then descending value so regex alternation matches
|
|
17
|
-
// longest first (d-w1024 resolves `1024`, not `1`).
|
|
18
|
-
const SIZING_PIXELS = '1024|992|960|928|896|864|832|800|768|736|704|672|640|608|576|544|512|480|448|416|384|352|320|288|256|224|192|160|128|112|96|80|64|48|32|24|20|16|8|2|1';
|
|
19
|
-
const SPACING_PIXELS = '0|1|2|4|6|8|10|12|14|16|20|24|32|48|64|96|128';
|
|
20
|
-
const NEGATIVE_PIXELS = '1|2|4|6|8|10|12|14|16|20|24|32|48|64';
|
|
21
|
-
|
|
22
|
-
// Sizing autofix: scale-indexed layout stops + off-scale pixel-indexed exceptions (DLT-3330).
|
|
23
|
-
const SIZING_MAP = {
|
|
24
|
-
1: '1px', 2: '2px', 8: '8px', 20: '20px', 24: '24px',
|
|
25
|
-
16: '25', 32: '50', 48: '75', 64: '100', 80: '125', 96: '150',
|
|
26
|
-
112: '175', 128: '200', 160: '250', 192: '300', 224: '350', 256: '400',
|
|
27
|
-
288: '450', 320: '500', 352: '550', 384: '600', 416: '650', 448: '700',
|
|
28
|
-
480: '750', 512: '800', 544: '850', 576: '900', 608: '950', 640: '1000',
|
|
29
|
-
672: '1050', 704: '1100', 736: '1150', 768: '1200', 800: '1250',
|
|
30
|
-
832: '1300', 864: '1350', 896: '1400', 928: '1450', 960: '1500',
|
|
31
|
-
992: '1550', 1024: '1600',
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
const SPACING_MAP = {
|
|
35
|
-
0: '0', 1: '1', 2: '25', 4: '50', 6: '75', 8: '100',
|
|
36
|
-
10: '125', 12: '150', 14: '175', 16: '200', 20: '250', 24: '300',
|
|
37
|
-
32: '400', 48: '600', 64: '800',
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
const NEGATIVE_SPACING_MAP = {
|
|
41
|
-
1: '1', 2: '25', 4: '50', 6: '75', 8: '100',
|
|
42
|
-
10: '125', 12: '150', 14: '175', 16: '200', 20: '250', 24: '300',
|
|
43
|
-
32: '400', 48: '600', 64: '800',
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
const SPACING_LAYOUT_MAP = { 96: '150', 128: '200' };
|
|
47
|
-
|
|
48
|
-
// Per-category regexes with capture groups. Negative variants precede positive so `d-mtn8`
|
|
49
|
-
// matches the negative pattern (rule order is load-order in `rewriteClassString`).
|
|
50
|
-
const SIZING_RE = new RegExp(`${START}d-(h|w|hmn|hmx|wmn|wmx)(${SIZING_PIXELS})${END}`, 'g');
|
|
51
|
-
const NEGATIVE_MARGIN_RE = new RegExp(`${START}d-m(t|r|b|l|x|y)?n(${NEGATIVE_PIXELS})${END}`, 'g');
|
|
52
|
-
const MARGIN_RE = new RegExp(`${START}d-m(t|r|b|l|x|y)?(${SPACING_PIXELS})${END}`, 'g');
|
|
53
|
-
const PADDING_RE = new RegExp(`${START}d-p(t|r|b|l|x|y)?(${SPACING_PIXELS})${END}`, 'g');
|
|
54
|
-
const GAP_RE = new RegExp(`${START}d-(g|rg|cg)(${SPACING_PIXELS})${END}`, 'g');
|
|
55
|
-
const NEGATIVE_POS_RE = new RegExp(`${START}d-(t|r|b|l|x|y|all)n(${NEGATIVE_PIXELS})${END}`, 'g');
|
|
56
|
-
const POSITION_RE = new RegExp(`${START}d-(t|r|b|l|x|y|all)(${SPACING_PIXELS})${END}`, 'g');
|
|
57
|
-
|
|
58
|
-
const DETECT = buildDetectRegex([SIZING_RE, NEGATIVE_MARGIN_RE, MARGIN_RE, PADDING_RE, GAP_RE, NEGATIVE_POS_RE, POSITION_RE]);
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Rewrite a class attribute string from legacy pixel-suffix to token-stop naming.
|
|
62
|
-
* Returns the input unchanged when no rewrites apply.
|
|
63
|
-
*/
|
|
64
|
-
function rewriteClassString (input) {
|
|
65
|
-
return input
|
|
66
|
-
.replace(NEGATIVE_MARGIN_RE, (m, dir, px) => NEGATIVE_SPACING_MAP[px] ? `d-m${dir ?? ''}-n${NEGATIVE_SPACING_MAP[px]}` : m)
|
|
67
|
-
.replace(NEGATIVE_POS_RE, (m, dir, px) => NEGATIVE_SPACING_MAP[px] ? `d-${dir}-n${NEGATIVE_SPACING_MAP[px]}` : m)
|
|
68
|
-
.replace(SIZING_RE, (m, dir, px) => SIZING_MAP[px] ? `d-${dir}-${SIZING_MAP[px]}` : m)
|
|
69
|
-
.replace(MARGIN_RE, (m, dir, px) => { const stop = SPACING_MAP[px] ?? SPACING_LAYOUT_MAP[px]; return stop ? `d-m${dir ?? ''}-${stop}` : m; })
|
|
70
|
-
.replace(PADDING_RE, (m, dir, px) => { const stop = SPACING_MAP[px] ?? SPACING_LAYOUT_MAP[px]; return stop ? `d-p${dir ?? ''}-${stop}` : m; })
|
|
71
|
-
.replace(GAP_RE, (m, dir, px) => SPACING_MAP[px] ? `d-${dir}-${SPACING_MAP[px]}` : m)
|
|
72
|
-
.replace(POSITION_RE, (m, dir, px) => { const stop = SPACING_MAP[px] ?? SPACING_LAYOUT_MAP[px]; return stop ? `d-${dir}-${stop}` : m; });
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
module.exports = {
|
|
76
|
-
meta: {
|
|
77
|
-
type: 'suggestion',
|
|
78
|
-
docs: {
|
|
79
|
-
description: 'Pixel-based utility classes (d-h16, d-p8, d-m8) are deprecated. Use token-stop-based equivalents (d-h-25, d-p-100, d-m-100).',
|
|
80
|
-
recommended: false,
|
|
81
|
-
url: 'https://github.com/dialpad/dialtone/blob/staging/packages/eslint-plugin-dialtone/docs/rules/deprecated-pixel-utility-classes.md',
|
|
82
|
-
},
|
|
83
|
-
fixable: 'code',
|
|
84
|
-
schema: [],
|
|
85
|
-
messages: {
|
|
86
|
-
deprecatedPixelClass: 'Pixel-based utility classes are deprecated. Use token-stop-based equivalents instead (e.g. d-h16 → d-h-25, d-p8 → d-p-100, d-w1 → d-w-1px).',
|
|
87
|
-
},
|
|
88
|
-
},
|
|
89
|
-
|
|
90
|
-
create: createClassAttributeRule({
|
|
91
|
-
detect: DETECT,
|
|
92
|
-
rewrite: rewriteClassString,
|
|
93
|
-
messageId: 'deprecatedPixelClass',
|
|
94
|
-
}),
|
|
95
|
-
};
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Detects usage of legacy border-radius utility classes (d-bar6, d-btr8, d-bbr-pill,
|
|
3
|
-
* etc.) and autofixes them to the new token-stop-indexed logical names (d-bar-350, d-bbsr-400,
|
|
4
|
-
* d-bber-pill).
|
|
5
|
-
*/
|
|
6
|
-
'use strict';
|
|
7
|
-
|
|
8
|
-
const { START, END, buildDetectRegex, createClassAttributeRule } = require('../util/class-attribute-rule');
|
|
9
|
-
|
|
10
|
-
// MUST STAY IN SYNC with:
|
|
11
|
-
// - RADIUS_STOPS in dialtone-css/postcss/constants.cjs
|
|
12
|
-
// - RADIUS_MAP / RADIUS_PAIR_PREFIX_MAP in dialtone-css/.../migration_helper/configs/utility-class-to-token-stops.mjs
|
|
13
|
-
|
|
14
|
-
const RADIUS_STOP_MAP = {
|
|
15
|
-
0: '0', 1: '100', 2: '200', 4: '300', 6: '350',
|
|
16
|
-
8: '400', 12: '450', 16: '500', 24: '550', 32: '600',
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
const PAIR_PREFIX_MAP = {
|
|
20
|
-
btr: 'bbsr', // top → block-start pair
|
|
21
|
-
bbr: 'bber', // bottom → block-end pair
|
|
22
|
-
blr: 'bisr', // left → inline-start pair
|
|
23
|
-
brr: 'bier', // right → inline-end pair
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
// Ordered by descending string length so regex alternation matches longest first
|
|
27
|
-
// (.d-bar32 resolves as `32`, not `3`).
|
|
28
|
-
const NUMERIC_SUFFIXES = Object.keys(RADIUS_STOP_MAP).sort((a, b) => b.length - a.length || Number(b) - Number(a)).join('|');
|
|
29
|
-
const PAIR_PREFIXES = Object.keys(PAIR_PREFIX_MAP).join('|');
|
|
30
|
-
|
|
31
|
-
const ALL_CORNERS_NUMERIC = new RegExp(`${START}d-bar(${NUMERIC_SUFFIXES})${END}`, 'g');
|
|
32
|
-
const PAIR_NUMERIC = new RegExp(`${START}d-(${PAIR_PREFIXES})(${NUMERIC_SUFFIXES})${END}`, 'g');
|
|
33
|
-
const PAIR_KEYWORD = new RegExp(`${START}d-(${PAIR_PREFIXES})-(pill|circle)${END}`, 'g');
|
|
34
|
-
|
|
35
|
-
const DETECT = buildDetectRegex([ALL_CORNERS_NUMERIC, PAIR_NUMERIC, PAIR_KEYWORD]);
|
|
36
|
-
|
|
37
|
-
function rewriteClassString (input) {
|
|
38
|
-
return input
|
|
39
|
-
.replace(ALL_CORNERS_NUMERIC, (_, px) => `d-bar-${RADIUS_STOP_MAP[px]}`)
|
|
40
|
-
.replace(PAIR_NUMERIC, (_, legacyPrefix, px) => `d-${PAIR_PREFIX_MAP[legacyPrefix]}-${RADIUS_STOP_MAP[px]}`)
|
|
41
|
-
.replace(PAIR_KEYWORD, (_, legacyPrefix, keyword) => `d-${PAIR_PREFIX_MAP[legacyPrefix]}-${keyword}`);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
module.exports = {
|
|
45
|
-
meta: {
|
|
46
|
-
type: 'suggestion',
|
|
47
|
-
docs: {
|
|
48
|
-
description: 'Legacy border-radius utility classes (d-bar6, d-btr8, d-bbr-pill) are deprecated. Use token-stop-indexed logical names (d-bar-350, d-bbsr-400, d-bber-pill).',
|
|
49
|
-
recommended: false,
|
|
50
|
-
url: 'https://github.com/dialpad/dialtone/blob/staging/packages/eslint-plugin-dialtone/docs/rules/deprecated-radius-utility-classes.md',
|
|
51
|
-
},
|
|
52
|
-
fixable: 'code',
|
|
53
|
-
schema: [],
|
|
54
|
-
messages: {
|
|
55
|
-
deprecatedRadiusClass: 'Legacy border-radius utility classes are deprecated. Use token-stop-indexed logical names instead (e.g. d-bar6 → d-bar-350, d-btr6 → d-bbsr-350, d-btr-pill → d-bbsr-pill).',
|
|
56
|
-
},
|
|
57
|
-
},
|
|
58
|
-
|
|
59
|
-
create: createClassAttributeRule({
|
|
60
|
-
detect: DETECT,
|
|
61
|
-
rewrite: rewriteClassString,
|
|
62
|
-
messageId: 'deprecatedRadiusClass',
|
|
63
|
-
}),
|
|
64
|
-
};
|
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Detects usage of deprecated `success`-named color utility classes
|
|
3
|
-
* (`d-bgc-success*`, `d-bc-success*`, `d-fc-success*`) and recommends the
|
|
4
|
-
* `positive`-named replacements. The foreground (`d-fc-success*`) variants are
|
|
5
|
-
* already deprecated as part of the base-color cleanup but are still in active
|
|
6
|
-
* use, so this rule covers them too.
|
|
7
|
-
* @author Dialtone Team
|
|
8
|
-
*/
|
|
9
|
-
'use strict';
|
|
10
|
-
|
|
11
|
-
// ------------------------------------------------------------------------------
|
|
12
|
-
// Rule Definition
|
|
13
|
-
// ------------------------------------------------------------------------------
|
|
14
|
-
|
|
15
|
-
// Anchor at token boundaries. `\b` would slice through
|
|
16
|
-
// `d-bgc-success-strong-inverted-foo`, so we use explicit lookarounds.
|
|
17
|
-
// TOKEN_END mirrors the `success-to-positive` migration helper's CLASS_BOUNDARY
|
|
18
|
-
// so every lint finding is auto-fixable by the CLI the rule's message points to.
|
|
19
|
-
const TOKEN_START = '(?<![A-Za-z0-9_-])';
|
|
20
|
-
const TOKEN_END = '(?=$|[\\s"\'><:=,;{}()\\[\\]!`./])';
|
|
21
|
-
|
|
22
|
-
// Suffixes that follow `success` in the deprecated namespace. Order longest
|
|
23
|
-
// first so alternation matches greedily where needed (regex matching is
|
|
24
|
-
// leftmost-first, but we still keep this ordered for clarity).
|
|
25
|
-
const SUCCESS_SUFFIXES = [
|
|
26
|
-
'-subtle-opaque-inverted',
|
|
27
|
-
'-strong-inverted',
|
|
28
|
-
'-subtle-inverted',
|
|
29
|
-
'-opaque-inverted',
|
|
30
|
-
'-inverted-hover',
|
|
31
|
-
'-subtle-opaque',
|
|
32
|
-
'-inverted',
|
|
33
|
-
'-subtle',
|
|
34
|
-
'-strong',
|
|
35
|
-
'-opaque',
|
|
36
|
-
'-hover',
|
|
37
|
-
].join('|');
|
|
38
|
-
|
|
39
|
-
// Per-role detect regexes. Each matches `d-{role}-success` optionally followed
|
|
40
|
-
// by one of the known suffixes — and ONLY by one of the known suffixes (the
|
|
41
|
-
// trailing `TOKEN_END` rejects unrelated continuations like
|
|
42
|
-
// `d-bgc-success-foo`).
|
|
43
|
-
const BG_SUCCESS_RE = new RegExp(`${TOKEN_START}d-bgc-success(?:${SUCCESS_SUFFIXES})?${TOKEN_END}`);
|
|
44
|
-
const FG_SUCCESS_RE = new RegExp(`${TOKEN_START}d-fc-success(?:${SUCCESS_SUFFIXES})?${TOKEN_END}`);
|
|
45
|
-
const BORDER_SUCCESS_RE = new RegExp(`${TOKEN_START}d-bc-success(?:${SUCCESS_SUFFIXES})?${TOKEN_END}`);
|
|
46
|
-
|
|
47
|
-
// Generic detect for the script-string visitor (any role).
|
|
48
|
-
const ANY_SUCCESS_RE = new RegExp(`${TOKEN_START}d-(?:bgc|bc|fc)-success(?:${SUCCESS_SUFFIXES})?${TOKEN_END}`);
|
|
49
|
-
|
|
50
|
-
const DOCS_BACKGROUND = 'https://dialtone.dialpad.com/utilities/backgrounds/color.html';
|
|
51
|
-
const DOCS_FOREGROUND = 'https://dialtone.dialpad.com/utilities/typography/font-color.html';
|
|
52
|
-
const DOCS_BORDER = 'https://dialtone.dialpad.com/utilities/borders/color.html';
|
|
53
|
-
|
|
54
|
-
// Build per-role messages following the precedent of deprecated-base-color-classes
|
|
55
|
-
// (separate messageId per role family).
|
|
56
|
-
const MESSAGE_PREFIX = '`d-{role}-success-...` is deprecated. Use `d-{role}-positive-...` instead. Run `npx dialtone-migration-helper --config success-to-positive` to migrate automatically.';
|
|
57
|
-
const buildMessage = (role, docs) =>
|
|
58
|
-
MESSAGE_PREFIX.replace(/\{role\}/g, role) + ` See the utility docs: ${docs}`;
|
|
59
|
-
|
|
60
|
-
function checkClassString (context, node, classes) {
|
|
61
|
-
if (BG_SUCCESS_RE.test(classes)) {
|
|
62
|
-
context.report({ node, messageId: 'deprecatedBackgroundSuccess' });
|
|
63
|
-
}
|
|
64
|
-
if (FG_SUCCESS_RE.test(classes)) {
|
|
65
|
-
context.report({ node, messageId: 'deprecatedForegroundSuccess' });
|
|
66
|
-
}
|
|
67
|
-
if (BORDER_SUCCESS_RE.test(classes)) {
|
|
68
|
-
context.report({ node, messageId: 'deprecatedBorderSuccess' });
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Walk every Literal/TemplateElement string descendant of an expression node
|
|
74
|
-
* (used for `:class="[...]"` / `:class="{...}"` / template-literal bindings).
|
|
75
|
-
* Best-effort — fully dynamic class names (`d-bgc-' + variant`) cannot be
|
|
76
|
-
* matched without runtime knowledge.
|
|
77
|
-
*/
|
|
78
|
-
function findStringLiterals (n, out) {
|
|
79
|
-
if (!n || typeof n !== 'object') return;
|
|
80
|
-
if (n.type === 'Literal' && typeof n.value === 'string') {
|
|
81
|
-
out.push({ node: n, value: n.value });
|
|
82
|
-
} else if (n.type === 'TemplateElement' && n.value && typeof n.value.cooked === 'string') {
|
|
83
|
-
out.push({ node: n, value: n.value.cooked });
|
|
84
|
-
}
|
|
85
|
-
for (const key of Object.keys(n)) {
|
|
86
|
-
if (key === 'parent') continue;
|
|
87
|
-
const child = n[key];
|
|
88
|
-
if (child && typeof child === 'object') {
|
|
89
|
-
if (Array.isArray(child)) {
|
|
90
|
-
for (const c of child) findStringLiterals(c, out);
|
|
91
|
-
} else {
|
|
92
|
-
findStringLiterals(child, out);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
module.exports = {
|
|
99
|
-
meta: {
|
|
100
|
-
type: 'suggestion',
|
|
101
|
-
docs: {
|
|
102
|
-
description:
|
|
103
|
-
'`d-bgc-success*`, `d-bc-success*`, and `d-fc-success*` utility classes are deprecated. Use the `positive`-named replacements.',
|
|
104
|
-
recommended: false,
|
|
105
|
-
url: 'https://github.com/dialpad/dialtone/blob/staging/packages/eslint-plugin-dialtone/docs/rules/deprecated-success-color-classes.md',
|
|
106
|
-
},
|
|
107
|
-
fixable: null,
|
|
108
|
-
schema: [],
|
|
109
|
-
messages: {
|
|
110
|
-
deprecatedBackgroundSuccess: buildMessage('bgc', DOCS_BACKGROUND),
|
|
111
|
-
deprecatedForegroundSuccess: buildMessage('fc', DOCS_FOREGROUND),
|
|
112
|
-
deprecatedBorderSuccess: buildMessage('bc', DOCS_BORDER),
|
|
113
|
-
},
|
|
114
|
-
},
|
|
115
|
-
|
|
116
|
-
create (context) {
|
|
117
|
-
const sourceCode = context.sourceCode ?? context.getSourceCode();
|
|
118
|
-
const defineTemplateBodyVisitor = sourceCode.parserServices?.defineTemplateBodyVisitor;
|
|
119
|
-
|
|
120
|
-
// Visitor that scans all string-typed AST nodes inside `<script>` or in
|
|
121
|
-
// plain `.js`/`.ts` files for class-name strings containing the deprecated
|
|
122
|
-
// tokens. This catches things like `:class="['d-bgc-success']"` (the array
|
|
123
|
-
// literal lives in the script-side AST that vue-eslint-parser exposes via
|
|
124
|
-
// the standard ESLint visitor) and `const c = 'd-bgc-success';`.
|
|
125
|
-
const scriptVisitor = {
|
|
126
|
-
Literal (node) {
|
|
127
|
-
if (typeof node.value !== 'string') return;
|
|
128
|
-
if (!ANY_SUCCESS_RE.test(node.value)) return;
|
|
129
|
-
checkClassString(context, node, node.value);
|
|
130
|
-
},
|
|
131
|
-
TemplateElement (node) {
|
|
132
|
-
const cooked = node.value && node.value.cooked;
|
|
133
|
-
if (typeof cooked !== 'string') return;
|
|
134
|
-
if (!ANY_SUCCESS_RE.test(cooked)) return;
|
|
135
|
-
checkClassString(context, node, cooked);
|
|
136
|
-
},
|
|
137
|
-
};
|
|
138
|
-
|
|
139
|
-
if (!defineTemplateBodyVisitor) {
|
|
140
|
-
// Plain JS/TS file — no Vue template, only the script visitor applies.
|
|
141
|
-
return scriptVisitor;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
return defineTemplateBodyVisitor(
|
|
145
|
-
// Template visitor — handles `class="..."` static attributes and the
|
|
146
|
-
// string-literal halves of `:class="..."` dynamic bindings.
|
|
147
|
-
{
|
|
148
|
-
VAttribute (node) {
|
|
149
|
-
// Static `class="..."` attribute.
|
|
150
|
-
if (!node.directive && node.key.name === 'class' && node.value && typeof node.value.value === 'string') {
|
|
151
|
-
checkClassString(context, node, node.value.value);
|
|
152
|
-
return;
|
|
153
|
-
}
|
|
154
|
-
// Dynamic `:class="..."` / `v-bind:class="..."` binding.
|
|
155
|
-
if (
|
|
156
|
-
node.directive &&
|
|
157
|
-
node.key.name &&
|
|
158
|
-
node.key.name.name === 'bind' &&
|
|
159
|
-
node.key.argument &&
|
|
160
|
-
(node.key.argument.rawName === 'class' || node.key.argument.name === 'class') &&
|
|
161
|
-
node.value &&
|
|
162
|
-
node.value.expression
|
|
163
|
-
) {
|
|
164
|
-
const literals = [];
|
|
165
|
-
findStringLiterals(node.value.expression, literals);
|
|
166
|
-
for (const { node: litNode, value } of literals) {
|
|
167
|
-
if (ANY_SUCCESS_RE.test(value)) {
|
|
168
|
-
checkClassString(context, litNode, value);
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
},
|
|
173
|
-
},
|
|
174
|
-
// ESLint visitor for the script half (i.e., the surrounding JS/TS that
|
|
175
|
-
// vue-eslint-parser exposes through the regular AST traversal). Plain
|
|
176
|
-
// `.js`/`.ts` files also fall through here.
|
|
177
|
-
scriptVisitor,
|
|
178
|
-
);
|
|
179
|
-
},
|
|
180
|
-
};
|