@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,136 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Detects usage of deprecated t-shirt size props (xs, sm, md, lg, xl) on Dialtone
|
|
3
|
-
* components and suggests numeric equivalents (100, 200, 300, 400, 500).
|
|
4
|
-
* @author Dialtone Team
|
|
5
|
-
*/
|
|
6
|
-
'use strict';
|
|
7
|
-
|
|
8
|
-
// ------------------------------------------------------------------------------
|
|
9
|
-
// Rule Definition
|
|
10
|
-
// ------------------------------------------------------------------------------
|
|
11
|
-
|
|
12
|
-
const SIZE_MAP = {
|
|
13
|
-
xs: '100',
|
|
14
|
-
sm: '200',
|
|
15
|
-
md: '300',
|
|
16
|
-
lg: '400',
|
|
17
|
-
xl: '500',
|
|
18
|
-
'2xl': '600',
|
|
19
|
-
'3xl': '700',
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
const TSHIRT_VALUES = new Set(Object.keys(SIZE_MAP));
|
|
23
|
-
|
|
24
|
-
// Props that accept the component size scale
|
|
25
|
-
const SIZE_PROPS = ['size', 'label-size', 'labelSize'];
|
|
26
|
-
|
|
27
|
-
// Speed prop on motion-text also uses the same scale
|
|
28
|
-
const SPEED_PROPS = ['speed'];
|
|
29
|
-
|
|
30
|
-
// All size-related prop names
|
|
31
|
-
const ALL_SIZE_PROPS = [...SIZE_PROPS, ...SPEED_PROPS];
|
|
32
|
-
|
|
33
|
-
// Only flag on Dialtone components (dt-* or Dt*)
|
|
34
|
-
function isDialtoneComponent (node) {
|
|
35
|
-
const parent = node.parent;
|
|
36
|
-
if (!parent || parent.type !== 'VStartTag') return false;
|
|
37
|
-
const element = parent.parent;
|
|
38
|
-
if (!element || element.type !== 'VElement') return false;
|
|
39
|
-
const name = element.rawName || element.name || '';
|
|
40
|
-
return name.startsWith('dt-') || name.startsWith('Dt');
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
module.exports = {
|
|
44
|
-
meta: {
|
|
45
|
-
type: 'suggestion',
|
|
46
|
-
docs: {
|
|
47
|
-
description: 'T-shirt sizes (xs, sm, md, lg, xl) are deprecated. Use numeric scale (100, 200, 300, 400, 500) instead.',
|
|
48
|
-
recommended: false,
|
|
49
|
-
url: 'https://github.com/dialpad/dialtone/blob/staging/packages/eslint-plugin-dialtone/docs/rules/deprecated-tshirt-sizes.md',
|
|
50
|
-
},
|
|
51
|
-
fixable: 'code',
|
|
52
|
-
schema: [],
|
|
53
|
-
messages: {
|
|
54
|
-
deprecatedSize: 'Size "{{oldSize}}" is deprecated. Use :{{prop}}="{{newSize}}" instead.',
|
|
55
|
-
deprecatedSizeInBinding: 'T-shirt size "{{oldSize}}" in dynamic binding is deprecated. Use numeric {{newSize}} instead.',
|
|
56
|
-
},
|
|
57
|
-
},
|
|
58
|
-
|
|
59
|
-
create (context) {
|
|
60
|
-
const sourceCode = context.sourceCode ?? context.getSourceCode();
|
|
61
|
-
return sourceCode.parserServices.defineTemplateBodyVisitor({
|
|
62
|
-
VAttribute (node) {
|
|
63
|
-
if (!isDialtoneComponent(node)) return;
|
|
64
|
-
|
|
65
|
-
// Get the prop name and check if it's a size-related prop
|
|
66
|
-
const isDirective = node.directive;
|
|
67
|
-
const propName = isDirective
|
|
68
|
-
? (node.key.argument && node.key.argument.name)
|
|
69
|
-
: node.key.name;
|
|
70
|
-
|
|
71
|
-
if (!propName || !ALL_SIZE_PROPS.includes(propName)) return;
|
|
72
|
-
|
|
73
|
-
// --- Static attributes: size="sm" → auto-fixable ---
|
|
74
|
-
if (!isDirective && node.value && node.value.value) {
|
|
75
|
-
const sizeValue = node.value.value;
|
|
76
|
-
if (SIZE_MAP[sizeValue]) {
|
|
77
|
-
context.report({
|
|
78
|
-
node,
|
|
79
|
-
messageId: 'deprecatedSize',
|
|
80
|
-
data: {
|
|
81
|
-
oldSize: sizeValue,
|
|
82
|
-
newSize: SIZE_MAP[sizeValue],
|
|
83
|
-
prop: propName,
|
|
84
|
-
},
|
|
85
|
-
fix (fixer) {
|
|
86
|
-
const newAttr = `:${propName}="${SIZE_MAP[sizeValue]}"`;
|
|
87
|
-
return fixer.replaceTextRange(
|
|
88
|
-
[node.range[0], node.range[1]],
|
|
89
|
-
newAttr,
|
|
90
|
-
);
|
|
91
|
-
},
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
return;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// --- Dynamic bindings: :size="'sm'" or :size="x ? 'sm' : 'md'" ---
|
|
98
|
-
if (isDirective && node.value && node.value.expression) {
|
|
99
|
-
// Walk the expression tree for string literals with t-shirt values
|
|
100
|
-
const expression = node.value.expression;
|
|
101
|
-
const literals = [];
|
|
102
|
-
|
|
103
|
-
(function findLiterals (n) {
|
|
104
|
-
if (!n) return;
|
|
105
|
-
if (n.type === 'Literal' && typeof n.value === 'string' && TSHIRT_VALUES.has(n.value)) {
|
|
106
|
-
literals.push(n);
|
|
107
|
-
}
|
|
108
|
-
// Walk child nodes
|
|
109
|
-
for (const key of Object.keys(n)) {
|
|
110
|
-
if (key === 'parent') continue;
|
|
111
|
-
const child = n[key];
|
|
112
|
-
if (child && typeof child === 'object') {
|
|
113
|
-
if (Array.isArray(child)) {
|
|
114
|
-
child.forEach(c => { if (c && c.type) findLiterals(c); });
|
|
115
|
-
} else if (child.type) {
|
|
116
|
-
findLiterals(child);
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
})(expression);
|
|
121
|
-
|
|
122
|
-
for (const literal of literals) {
|
|
123
|
-
context.report({
|
|
124
|
-
node: literal,
|
|
125
|
-
messageId: 'deprecatedSizeInBinding',
|
|
126
|
-
data: {
|
|
127
|
-
oldSize: literal.value,
|
|
128
|
-
newSize: SIZE_MAP[literal.value],
|
|
129
|
-
},
|
|
130
|
-
});
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
},
|
|
134
|
-
});
|
|
135
|
-
},
|
|
136
|
-
};
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Warns when v-dt-focusgroup is used without an accessible label on the same element.
|
|
3
|
-
* @author Dialtone
|
|
4
|
-
*/
|
|
5
|
-
'use strict';
|
|
6
|
-
|
|
7
|
-
module.exports = {
|
|
8
|
-
meta: {
|
|
9
|
-
type: 'suggestion',
|
|
10
|
-
docs: {
|
|
11
|
-
description:
|
|
12
|
-
'Warns when v-dt-focusgroup is used on an element without aria-label or aria-labelledby. ' +
|
|
13
|
-
'Screen readers need an accessible name to identify the widget.',
|
|
14
|
-
recommended: false,
|
|
15
|
-
url: 'https://github.com/dialpad/dialtone/blob/staging/packages/eslint-plugin-dialtone/docs/rules/focusgroup-requires-label.md',
|
|
16
|
-
},
|
|
17
|
-
fixable: null,
|
|
18
|
-
schema: [],
|
|
19
|
-
messages: {
|
|
20
|
-
missingLabel:
|
|
21
|
-
'v-dt-focusgroup requires an accessible name via "aria-label" or "aria-labelledby" ' +
|
|
22
|
-
'so screen readers can identify the widget.',
|
|
23
|
-
},
|
|
24
|
-
},
|
|
25
|
-
|
|
26
|
-
create (context) {
|
|
27
|
-
const sourceCode = context.sourceCode ?? context.getSourceCode();
|
|
28
|
-
return sourceCode.parserServices.defineTemplateBodyVisitor({
|
|
29
|
-
VAttribute (node) {
|
|
30
|
-
if (!node.directive) return;
|
|
31
|
-
if (node.key.name.name !== 'dt-focusgroup') return;
|
|
32
|
-
|
|
33
|
-
const element = node.parent;
|
|
34
|
-
const hasLabel = element.attributes.some(
|
|
35
|
-
attr =>
|
|
36
|
-
(!attr.directive && (
|
|
37
|
-
attr.key.name === 'aria-label' ||
|
|
38
|
-
attr.key.name === 'aria-labelledby'
|
|
39
|
-
)) ||
|
|
40
|
-
(attr.directive && attr.key.name.name === 'bind' && (
|
|
41
|
-
attr.key.argument?.name === 'aria-label' ||
|
|
42
|
-
attr.key.argument?.name === 'aria-labelledby'
|
|
43
|
-
)),
|
|
44
|
-
);
|
|
45
|
-
|
|
46
|
-
if (!hasLabel) {
|
|
47
|
-
context.report({ node, messageId: 'missingLabel' });
|
|
48
|
-
}
|
|
49
|
-
},
|
|
50
|
-
});
|
|
51
|
-
},
|
|
52
|
-
};
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Warns when v-dt-focusgroup is used without a role attribute on the same element.
|
|
3
|
-
* @author Dialtone
|
|
4
|
-
*/
|
|
5
|
-
'use strict';
|
|
6
|
-
|
|
7
|
-
module.exports = {
|
|
8
|
-
meta: {
|
|
9
|
-
type: 'suggestion',
|
|
10
|
-
docs: {
|
|
11
|
-
description:
|
|
12
|
-
'Warns when v-dt-focusgroup is used on an element without a role attribute. ' +
|
|
13
|
-
'Screen readers need a role to announce the widget correctly.',
|
|
14
|
-
recommended: false,
|
|
15
|
-
url: 'https://github.com/dialpad/dialtone/blob/staging/packages/eslint-plugin-dialtone/docs/rules/focusgroup-requires-role.md',
|
|
16
|
-
},
|
|
17
|
-
fixable: null,
|
|
18
|
-
schema: [],
|
|
19
|
-
messages: {
|
|
20
|
-
missingRole:
|
|
21
|
-
'v-dt-focusgroup requires a "role" attribute (e.g. toolbar, tablist, listbox, radiogroup, menu) ' +
|
|
22
|
-
'so screen readers can announce the widget correctly.',
|
|
23
|
-
},
|
|
24
|
-
},
|
|
25
|
-
|
|
26
|
-
create (context) {
|
|
27
|
-
const sourceCode = context.sourceCode ?? context.getSourceCode();
|
|
28
|
-
return sourceCode.parserServices.defineTemplateBodyVisitor({
|
|
29
|
-
VAttribute (node) {
|
|
30
|
-
if (!node.directive) return;
|
|
31
|
-
if (node.key.name.name !== 'dt-focusgroup') return;
|
|
32
|
-
|
|
33
|
-
const element = node.parent;
|
|
34
|
-
const hasRole = element.attributes.some(
|
|
35
|
-
attr =>
|
|
36
|
-
(!attr.directive && attr.key.name === 'role') ||
|
|
37
|
-
(attr.directive && attr.key.name.name === 'bind' && attr.key.argument?.name === 'role'),
|
|
38
|
-
);
|
|
39
|
-
|
|
40
|
-
if (!hasRole) {
|
|
41
|
-
context.report({ node, messageId: 'missingRole' });
|
|
42
|
-
}
|
|
43
|
-
},
|
|
44
|
-
});
|
|
45
|
-
},
|
|
46
|
-
};
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Helpers for ESLint rules that autofix deprecated CSS utility classes in
|
|
3
|
-
* Vue template class attributes.
|
|
4
|
-
*/
|
|
5
|
-
'use strict';
|
|
6
|
-
|
|
7
|
-
// Token-boundary anchors. `\b` treats `-` as a non-word char, so `\bd-h16\b`
|
|
8
|
-
// would match inside `foo-d-h16` — use whitespace/start/end instead.
|
|
9
|
-
const START = '(?<=^|\\s)';
|
|
10
|
-
const END = '(?=$|\\s)';
|
|
11
|
-
|
|
12
|
-
function buildDetectRegex (regexes) {
|
|
13
|
-
return new RegExp(regexes.map(r => r.source).join('|'));
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Returns a `create` function for a rule that detects and autofixes deprecated
|
|
18
|
-
* class names in `class` attributes on Vue template nodes. Preserves the
|
|
19
|
-
* attribute's quoting style (double, single, or unquoted).
|
|
20
|
-
*/
|
|
21
|
-
function createClassAttributeRule ({ detect, rewrite, messageId }) {
|
|
22
|
-
return (context) => {
|
|
23
|
-
const sourceCode = context.sourceCode ?? context.getSourceCode();
|
|
24
|
-
const defineTemplateBodyVisitor = sourceCode.parserServices?.defineTemplateBodyVisitor;
|
|
25
|
-
if (!defineTemplateBodyVisitor) return {};
|
|
26
|
-
|
|
27
|
-
return defineTemplateBodyVisitor({
|
|
28
|
-
VAttribute (node) {
|
|
29
|
-
if (node.key.name !== 'class') return;
|
|
30
|
-
const classes = node.value?.value;
|
|
31
|
-
if (!classes || !detect.test(classes)) return;
|
|
32
|
-
|
|
33
|
-
context.report({
|
|
34
|
-
node,
|
|
35
|
-
messageId,
|
|
36
|
-
fix (fixer) {
|
|
37
|
-
const rewritten = rewrite(classes);
|
|
38
|
-
if (rewritten === classes) return null;
|
|
39
|
-
const firstChar = sourceCode.getText(node.value)[0];
|
|
40
|
-
const quote = firstChar === '"' || firstChar === '\'' ? firstChar : '';
|
|
41
|
-
return fixer.replaceText(node.value, `${quote}${rewritten}${quote}`);
|
|
42
|
-
},
|
|
43
|
-
});
|
|
44
|
-
},
|
|
45
|
-
});
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
module.exports = { START, END, buildDetectRegex, createClassAttributeRule };
|