eslint-plugin-vuetify 2.3.0 → 2.5.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/README.md +18 -3
- package/lib/configs/flat/base.js +11 -0
- package/lib/configs/flat/recommended.js +10 -0
- package/lib/index.js +6 -1
- package/lib/rules/grid-unknown-attributes.js +1 -1
- package/lib/rules/icon-button-variant.js +49 -0
- package/lib/rules/no-deprecated-classes.js +4 -11
- package/lib/rules/no-deprecated-colors.js +1 -1
- package/lib/rules/no-deprecated-components.js +3 -4
- package/lib/rules/no-deprecated-events.js +1 -1
- package/lib/rules/no-deprecated-props.js +7 -12
- package/lib/rules/no-deprecated-slots.js +2 -2
- package/lib/util/fixers.js +2 -2
- package/lib/util/helpers.js +1 -1
- package/package.json +12 -10
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@ This package is for migrating from Vuetify v2 to v3, use [eslint-plugin-vuetify@
|
|
|
15
15
|
|
|
16
16
|
## 💿 Install
|
|
17
17
|
|
|
18
|
-
You should have [`eslint`](https://eslint.org/docs/
|
|
18
|
+
You should have [`eslint`](https://eslint.org/docs/latest/use/getting-started) and [`eslint-plugin-vue`](https://eslint.vuejs.org/user-guide/#installation) set up first.
|
|
19
19
|
|
|
20
20
|
```bash
|
|
21
21
|
yarn add eslint-plugin-vuetify -D
|
|
@@ -23,6 +23,19 @@ yarn add eslint-plugin-vuetify -D
|
|
|
23
23
|
npm install eslint-plugin-vuetify --save-dev
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
+
```js
|
|
27
|
+
// eslint.config.js
|
|
28
|
+
import vue from 'eslint-plugin-vue'
|
|
29
|
+
import vuetify from 'eslint-plugin-vuetify'
|
|
30
|
+
|
|
31
|
+
export default [
|
|
32
|
+
...vue.configs['flat/base'],
|
|
33
|
+
...vuetify.configs['flat/base'],
|
|
34
|
+
]
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Eslint 8 can alternatively use the older configuration format:
|
|
38
|
+
|
|
26
39
|
```js
|
|
27
40
|
// .eslintrc.js
|
|
28
41
|
module.exports = {
|
|
@@ -40,7 +53,7 @@ module.exports = {
|
|
|
40
53
|
|
|
41
54
|
### Deprecations
|
|
42
55
|
|
|
43
|
-
These rules will help you avoid deprecated components, props, and classes. They are included in the `
|
|
56
|
+
These rules will help you avoid deprecated components, props, and classes. They are included in the `base` preset.
|
|
44
57
|
|
|
45
58
|
- Prevent the use of components that have been removed from Vuetify ([`no-deprecated-components`])
|
|
46
59
|
- Prevent the use of props that have been removed from Vuetify ([`no-deprecated-props`])
|
|
@@ -48,10 +61,11 @@ These rules will help you avoid deprecated components, props, and classes. They
|
|
|
48
61
|
- Prevent the use of classes that have been removed from Vuetify ([`no-deprecated-classes`])
|
|
49
62
|
- Prevent the use of the old theme class syntax ([`no-deprecated-colors`])
|
|
50
63
|
- Prevent the use of deprecated import paths ([`no-deprecated-imports`])
|
|
64
|
+
- Ensure icon buttons have a variant defined ([`icon-button-variant`])
|
|
51
65
|
|
|
52
66
|
### Grid system
|
|
53
67
|
|
|
54
|
-
These rules are designed to help migrate to the new grid system in Vuetify v2. They are included in the `
|
|
68
|
+
These rules are designed to help migrate to the new grid system in Vuetify v2. They are included in the `recommended` preset.
|
|
55
69
|
|
|
56
70
|
- Warn about unknown attributes not being converted to classes on new grid components ([`grid-unknown-attributes`])
|
|
57
71
|
|
|
@@ -63,6 +77,7 @@ These rules are designed to help migrate to the new grid system in Vuetify v2. T
|
|
|
63
77
|
[`no-deprecated-classes`]: ./docs/rules/no-deprecated-classes.md
|
|
64
78
|
[`no-deprecated-colors`]: ./docs/rules/no-deprecated-colors.md
|
|
65
79
|
[`no-deprecated-imports`]: ./docs/rules/no-deprecated-imports.md
|
|
80
|
+
[`icon-button-variant`]: ./docs/rules/icon-button-variant.md
|
|
66
81
|
|
|
67
82
|
|
|
68
83
|
## 💪 Supporting Vuetify
|
package/lib/index.js
CHANGED
|
@@ -3,6 +3,11 @@
|
|
|
3
3
|
const path = require('path');
|
|
4
4
|
const requireindex = require('requireindex');
|
|
5
5
|
module.exports = {
|
|
6
|
-
configs:
|
|
6
|
+
configs: {
|
|
7
|
+
base: require('./configs/base'),
|
|
8
|
+
recommended: require('./configs/recommended'),
|
|
9
|
+
'flat/base': require('./configs/flat/base'),
|
|
10
|
+
'flat/recommended': require('./configs/flat/recommended')
|
|
11
|
+
},
|
|
7
12
|
rules: requireindex(path.join(__dirname, './rules'))
|
|
8
13
|
};
|
|
@@ -39,7 +39,7 @@ module.exports = {
|
|
|
39
39
|
schema: []
|
|
40
40
|
},
|
|
41
41
|
create(context) {
|
|
42
|
-
return context.parserServices.defineTemplateBodyVisitor({
|
|
42
|
+
return context.sourceCode.parserServices.defineTemplateBodyVisitor({
|
|
43
43
|
VElement(element) {
|
|
44
44
|
const tag = classify(element.rawName);
|
|
45
45
|
if (!Object.keys(tags).includes(tag)) return;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
classify,
|
|
5
|
+
getAttributes
|
|
6
|
+
} = require('../util/helpers');
|
|
7
|
+
|
|
8
|
+
// ------------------------------------------------------------------------------
|
|
9
|
+
// Rule Definition
|
|
10
|
+
// ------------------------------------------------------------------------------
|
|
11
|
+
|
|
12
|
+
module.exports = {
|
|
13
|
+
meta: {
|
|
14
|
+
docs: {
|
|
15
|
+
description: 'Ensure icon buttons have a variant defined.',
|
|
16
|
+
category: 'recommended'
|
|
17
|
+
},
|
|
18
|
+
fixable: 'code',
|
|
19
|
+
schema: [{
|
|
20
|
+
type: 'string'
|
|
21
|
+
}],
|
|
22
|
+
messages: {
|
|
23
|
+
needsVariant: 'Icon buttons should have {{ a }} defined.'
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
create(context) {
|
|
27
|
+
return context.sourceCode.parserServices.defineTemplateBodyVisitor({
|
|
28
|
+
VElement(element) {
|
|
29
|
+
const tag = classify(element.rawName);
|
|
30
|
+
if (tag !== 'VBtn') return;
|
|
31
|
+
const attributes = getAttributes(element);
|
|
32
|
+
const iconAttribute = attributes.find(attr => attr.name === 'icon');
|
|
33
|
+
if (!iconAttribute) return;
|
|
34
|
+
if (attributes.some(attr => attr.name === 'variant')) return;
|
|
35
|
+
const variant = `variant="${context.options[0] || 'text'}"`;
|
|
36
|
+
context.report({
|
|
37
|
+
node: iconAttribute.node,
|
|
38
|
+
messageId: 'needsVariant',
|
|
39
|
+
data: {
|
|
40
|
+
a: variant
|
|
41
|
+
},
|
|
42
|
+
fix(fixer) {
|
|
43
|
+
return fixer.insertTextAfter(iconAttribute.node, ' ' + variant);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
};
|
|
@@ -11,13 +11,7 @@ const replacements = new Map([[/^rounded-(r|l|tr|tl|br|bl)(-.*)?$/, ([side, rest
|
|
|
11
11
|
bl: 'bs'
|
|
12
12
|
}[side];
|
|
13
13
|
return `rounded-${side}${rest || ''}`;
|
|
14
|
-
}], [/^
|
|
15
|
-
side = {
|
|
16
|
-
r: 'e',
|
|
17
|
-
l: 's'
|
|
18
|
-
}[side];
|
|
19
|
-
return `border-${side}${rest}`;
|
|
20
|
-
}], [/^text-xs-(left|right|center|justify)$/, ([align]) => `text-${align}`], [/hidden-(xs|sm|md|lg|xl)-only/, ([breakpoint]) => `hidden-${breakpoint}`], ['scroll-y', 'overflow-y-auto'], ['hide-overflow', 'overflow-hidden'], ['show-overflow', 'overflow-visible'], ['no-wrap', 'text-no-wrap'], ['ellipsis', 'text-truncate'], ['left', 'float-left'], ['right', 'float-right'], ['display-4', 'text-h1'], ['display-3', 'text-h2'], ['display-2', 'text-h3'], ['display-1', 'text-h4'], ['headline', 'text-h5'], ['title', 'text-h6'], ['subtitle-1', 'text-subtitle-1'], ['subtitle-2', 'text-subtitle-2'], ['body-1', 'text-body-1'], ['body-2', 'text-body-2'], ['caption', 'text-caption'], ['caption', 'text-caption'], ['overline', 'text-overline'], [/^transition-(fast-out-slow-in|linear-out-slow-in|fast-out-linear-in|ease-in-out|fast-in-fast-out|swing)$/, false]]);
|
|
14
|
+
}], [/^text-xs-(left|right|center|justify)$/, ([align]) => `text-${align}`], [/^hidden-(xs|sm|md|lg|xl)-only$/, ([breakpoint]) => `hidden-${breakpoint}`], ['scroll-y', 'overflow-y-auto'], ['hide-overflow', 'overflow-hidden'], ['show-overflow', 'overflow-visible'], ['no-wrap', 'text-no-wrap'], ['ellipsis', 'text-truncate'], ['left', 'float-left'], ['right', 'float-right'], ['display-4', 'text-h1'], ['display-3', 'text-h2'], ['display-2', 'text-h3'], ['display-1', 'text-h4'], ['headline', 'text-h5'], ['title', 'text-h6'], ['subtitle-1', 'text-subtitle-1'], ['subtitle-2', 'text-subtitle-2'], ['body-1', 'text-body-1'], ['body-2', 'text-body-2'], ['caption', 'text-caption'], ['caption', 'text-caption'], ['overline', 'text-overline'], [/^transition-(fast-out-slow-in|linear-out-slow-in|fast-out-linear-in|ease-in-out|fast-in-fast-out|swing)$/, false]]);
|
|
21
15
|
|
|
22
16
|
// ------------------------------------------------------------------------------
|
|
23
17
|
// Rule Definition
|
|
@@ -36,11 +30,10 @@ module.exports = {
|
|
|
36
30
|
}
|
|
37
31
|
},
|
|
38
32
|
create(context) {
|
|
39
|
-
return context.parserServices.defineTemplateBodyVisitor({
|
|
33
|
+
return context.sourceCode.parserServices.defineTemplateBodyVisitor({
|
|
40
34
|
'VAttribute[key.name="class"]'(node) {
|
|
41
35
|
if (!node.value || !node.value.value) return;
|
|
42
36
|
const classes = node.value.value.split(/\s+/).filter(s => !!s);
|
|
43
|
-
const source = context.getSourceCode();
|
|
44
37
|
const changed = [];
|
|
45
38
|
classes.forEach(className => {
|
|
46
39
|
for (const replacer of replacements) {
|
|
@@ -64,8 +57,8 @@ module.exports = {
|
|
|
64
57
|
const idx = node.value.value.indexOf(change[0]) + 1;
|
|
65
58
|
const range = [node.value.range[0] + idx, node.value.range[0] + idx + change[0].length];
|
|
66
59
|
const loc = {
|
|
67
|
-
start:
|
|
68
|
-
end:
|
|
60
|
+
start: context.sourceCode.getLocFromIndex(range[0]),
|
|
61
|
+
end: context.sourceCode.getLocFromIndex(range[1])
|
|
69
62
|
};
|
|
70
63
|
if (change[1]) {
|
|
71
64
|
context.report({
|
|
@@ -45,7 +45,7 @@ module.exports = {
|
|
|
45
45
|
const variant = classes.findIndex(t => textVariants.includes(t));
|
|
46
46
|
return [base, variant];
|
|
47
47
|
}
|
|
48
|
-
return context.parserServices.defineTemplateBodyVisitor({
|
|
48
|
+
return context.sourceCode.parserServices.defineTemplateBodyVisitor({
|
|
49
49
|
'VAttribute[key.name="color"]'(node) {
|
|
50
50
|
if (!node.value || !node.value.value) return;
|
|
51
51
|
const color = node.value.value.split(/\s+/).filter(s => !!s);
|
|
@@ -38,8 +38,7 @@ const replacements = {
|
|
|
38
38
|
VSimpleTable: 'v-table',
|
|
39
39
|
VTabsSlider: false,
|
|
40
40
|
VTabsItems: false,
|
|
41
|
-
VTabItem: false
|
|
42
|
-
VTimePicker: false
|
|
41
|
+
VTabItem: false
|
|
43
42
|
};
|
|
44
43
|
|
|
45
44
|
// ------------------------------------------------------------------------------
|
|
@@ -61,10 +60,10 @@ module.exports = {
|
|
|
61
60
|
}
|
|
62
61
|
},
|
|
63
62
|
create(context) {
|
|
64
|
-
return context.parserServices.defineTemplateBodyVisitor({
|
|
63
|
+
return context.sourceCode.parserServices.defineTemplateBodyVisitor({
|
|
65
64
|
VElement(element) {
|
|
66
65
|
const tag = classify(element.rawName);
|
|
67
|
-
const tokens = context.parserServices.getTemplateBodyTokenStore();
|
|
66
|
+
const tokens = context.sourceCode.parserServices.getTemplateBodyTokenStore();
|
|
68
67
|
if (Object.prototype.hasOwnProperty.call(replacements, tag)) {
|
|
69
68
|
const replacement = replacements[tag];
|
|
70
69
|
if (typeof replacement === 'object' && 'custom' in replacement) {
|
|
@@ -156,7 +156,7 @@ module.exports = {
|
|
|
156
156
|
}
|
|
157
157
|
},
|
|
158
158
|
create(context) {
|
|
159
|
-
return context.parserServices.defineTemplateBodyVisitor({
|
|
159
|
+
return context.sourceCode.parserServices.defineTemplateBodyVisitor({
|
|
160
160
|
VAttribute(attr) {
|
|
161
161
|
if (!(attr.directive && attr.key.name.name === 'on' && attr.key.argument?.type === 'VIdentifier')) return;
|
|
162
162
|
const tag = classify(attr.parent.parent.rawName);
|
|
@@ -89,9 +89,6 @@ const select = {
|
|
|
89
89
|
cacheItems: false,
|
|
90
90
|
deletableChips: 'closable-chips',
|
|
91
91
|
disableLookup: false,
|
|
92
|
-
itemColor: {
|
|
93
|
-
custom: 'item-props.color'
|
|
94
|
-
},
|
|
95
92
|
itemDisabled: {
|
|
96
93
|
custom: 'item-props.disabled'
|
|
97
94
|
},
|
|
@@ -316,10 +313,7 @@ const replacements = {
|
|
|
316
313
|
VCard: {
|
|
317
314
|
activeClass: false,
|
|
318
315
|
loaderHeight: false,
|
|
319
|
-
outlined:
|
|
320
|
-
name: 'variant',
|
|
321
|
-
value: 'outlined'
|
|
322
|
-
},
|
|
316
|
+
outlined: 'border',
|
|
323
317
|
raised: {
|
|
324
318
|
name: 'elevation',
|
|
325
319
|
value: 8
|
|
@@ -545,7 +539,8 @@ const replacements = {
|
|
|
545
539
|
...size
|
|
546
540
|
},
|
|
547
541
|
VForm: {
|
|
548
|
-
value: 'model-value'
|
|
542
|
+
value: 'model-value',
|
|
543
|
+
lazyValidation: false
|
|
549
544
|
},
|
|
550
545
|
VHover: {
|
|
551
546
|
value: 'model-value'
|
|
@@ -943,7 +938,7 @@ module.exports = {
|
|
|
943
938
|
}
|
|
944
939
|
},
|
|
945
940
|
create(context) {
|
|
946
|
-
return context.parserServices.defineTemplateBodyVisitor({
|
|
941
|
+
return context.sourceCode.parserServices.defineTemplateBodyVisitor({
|
|
947
942
|
VStartTag(tag) {
|
|
948
943
|
const attrGroups = {};
|
|
949
944
|
tag.attributes.forEach(attr => {
|
|
@@ -999,7 +994,7 @@ module.exports = {
|
|
|
999
994
|
}
|
|
1000
995
|
});
|
|
1001
996
|
} else if (typeof replace === 'object' && 'name' in replace && 'value' in replace) {
|
|
1002
|
-
const oldValue = attr.directive ? context.
|
|
997
|
+
const oldValue = attr.directive ? context.sourceCode.getText(attr.value.expression) : attr.value?.value;
|
|
1003
998
|
const value = typeof replace.value === 'function' ? replace.value(oldValue) : replace.value;
|
|
1004
999
|
if (value == null || value === oldValue) return;
|
|
1005
1000
|
context.report({
|
|
@@ -1017,8 +1012,8 @@ module.exports = {
|
|
|
1017
1012
|
}
|
|
1018
1013
|
return [fixer.replaceText(propNameNode, replace.name), fixer.replaceText(attr.value, `"${value}"`)];
|
|
1019
1014
|
} else {
|
|
1020
|
-
const expression = context.
|
|
1021
|
-
return [fixer.replaceText(propNameNode, replace.name), fixer.replaceText(attr.value, `"${expression}
|
|
1015
|
+
const expression = context.sourceCode.getText(attr.value.expression);
|
|
1016
|
+
return [fixer.replaceText(propNameNode, replace.name), fixer.replaceText(attr.value, `"${expression} ? '${value}' : undefined"`)];
|
|
1022
1017
|
}
|
|
1023
1018
|
} else {
|
|
1024
1019
|
return fixer.replaceText(attr, `${replace.bind ? ':' : ''}${replace.name}="${value}"`);
|
|
@@ -96,7 +96,7 @@ const groups = [{
|
|
|
96
96
|
yield fixer.replaceText(ref.id.parent.parent, `v-bind="props"`);
|
|
97
97
|
}
|
|
98
98
|
if (boundVariables.attrs) {
|
|
99
|
-
const template = context.parserServices.getTemplateBodyTokenStore();
|
|
99
|
+
const template = context.sourceCode.parserServices.getTemplateBodyTokenStore();
|
|
100
100
|
const ref = boundVariables.attrs;
|
|
101
101
|
const isLast = ref.prop === param.properties.at(-1);
|
|
102
102
|
if (isLast) {
|
|
@@ -188,7 +188,7 @@ module.exports = {
|
|
|
188
188
|
},
|
|
189
189
|
create(context) {
|
|
190
190
|
let scopeStack;
|
|
191
|
-
return context.parserServices.defineTemplateBodyVisitor({
|
|
191
|
+
return context.sourceCode.parserServices.defineTemplateBodyVisitor({
|
|
192
192
|
VElement(node) {
|
|
193
193
|
scopeStack = {
|
|
194
194
|
parent: scopeStack,
|
package/lib/util/fixers.js
CHANGED
|
@@ -10,11 +10,11 @@ function addClass(context, fixer, element, className) {
|
|
|
10
10
|
return fixer.insertTextAfter(classNode, `="${className}"`);
|
|
11
11
|
} else {
|
|
12
12
|
// nothing
|
|
13
|
-
return fixer.insertTextAfter(context.parserServices.getTemplateBodyTokenStore().getFirstToken(element.startTag), ` class="${className}"`);
|
|
13
|
+
return fixer.insertTextAfter(context.sourceCode.parserServices.getTemplateBodyTokenStore().getFirstToken(element.startTag), ` class="${className}"`);
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
16
|
function removeAttr(context, fixer, node) {
|
|
17
|
-
const source = context.
|
|
17
|
+
const source = context.sourceCode.text;
|
|
18
18
|
let [start, end] = node.range;
|
|
19
19
|
// Remove extra whitespace before attributes
|
|
20
20
|
start -= /\s*$/g.exec(source.substring(0, start))[0].length;
|
package/lib/util/helpers.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-vuetify",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.5.0",
|
|
4
4
|
"description": "An eslint plugin for Vuetify",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"author": "Kael Watts-Deuchar <kaelwd@gmail.com>",
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
"scripts": {
|
|
10
10
|
"build": "rimraf lib && babel src --out-dir lib",
|
|
11
11
|
"test": "mocha tests --recursive --reporter dot",
|
|
12
|
+
"test:8": "ESLINT8=true mocha tests --recursive --reporter dot",
|
|
12
13
|
"test:coverage": "nyc mocha tests --recursive --reporter dot",
|
|
13
14
|
"test:ci": "nyc --reporter=lcov mocha tests --recursive --reporter dot",
|
|
14
15
|
"lint": "eslint src tests",
|
|
@@ -26,23 +27,24 @@
|
|
|
26
27
|
"@babel/cli": "^7.19.3",
|
|
27
28
|
"@babel/core": "^7.19.6",
|
|
28
29
|
"@babel/preset-env": "^7.19.4",
|
|
30
|
+
"@stylistic/eslint-plugin": "^2.10.1",
|
|
29
31
|
"conventional-changelog-cli": "^2.2.2",
|
|
30
32
|
"conventional-changelog-vuetify": "^1.1.0",
|
|
31
33
|
"conventional-github-releaser": "^3.1.5",
|
|
32
|
-
"eslint": "^
|
|
33
|
-
"
|
|
34
|
-
"eslint-plugin-import": "^2.26.0",
|
|
35
|
-
"eslint-plugin-n": "^15.3.0",
|
|
36
|
-
"eslint-plugin-promise": "^6.1.1",
|
|
34
|
+
"eslint": "^9.14.0",
|
|
35
|
+
"eslint8": "npm:eslint@8.57.1",
|
|
37
36
|
"husky": "^8.0.1",
|
|
38
37
|
"mocha": "^10.1.0",
|
|
38
|
+
"neostandard": "^0.11.8",
|
|
39
39
|
"nyc": "^15.1.0",
|
|
40
40
|
"rimraf": "^3.0.2",
|
|
41
|
-
"vue": "^3.
|
|
42
|
-
"
|
|
41
|
+
"vue": "^3.5.12",
|
|
42
|
+
"vue-eslint-parser": "^9.4.3",
|
|
43
|
+
"vuetify": "^3.7.4"
|
|
43
44
|
},
|
|
44
45
|
"peerDependencies": {
|
|
45
|
-
"eslint": "^8.0.0",
|
|
46
|
+
"eslint": "^8.0.0 || ^9.0.0",
|
|
46
47
|
"vuetify": "^3.0.0"
|
|
47
|
-
}
|
|
48
|
+
},
|
|
49
|
+
"packageManager": "pnpm@9.13.2"
|
|
48
50
|
}
|