eslint-plugin-vuetify 2.0.5 → 2.1.1
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 +2 -0
- package/lib/configs/base.js +2 -1
- package/lib/rules/no-deprecated-classes.js +1 -1
- package/lib/rules/no-deprecated-components.js +11 -4
- package/lib/rules/no-deprecated-events.js +2 -6
- package/lib/rules/no-deprecated-props.js +82 -10
- package/lib/rules/no-deprecated-slots.js +199 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# eslint-plugin-vuetify
|
|
2
2
|
|
|
3
|
+
This package is for migrating from Vuetify v2 to v3, use [eslint-plugin-vuetify@vuetify-2](https://www.npmjs.com/package/eslint-plugin-vuetify/v/vuetify-2) for v1 to v2.
|
|
4
|
+
|
|
3
5
|
<br>
|
|
4
6
|
|
|
5
7
|
<p align="center">Support the maintainer of this plugin:</p>
|
package/lib/configs/base.js
CHANGED
|
@@ -10,6 +10,7 @@ module.exports = {
|
|
|
10
10
|
'vuetify/no-deprecated-colors': 'error',
|
|
11
11
|
'vuetify/no-deprecated-components': 'error',
|
|
12
12
|
'vuetify/no-deprecated-events': 'error',
|
|
13
|
-
'vuetify/no-deprecated-props': 'error'
|
|
13
|
+
'vuetify/no-deprecated-props': 'error',
|
|
14
|
+
'vuetify/no-deprecated-slots': 'error'
|
|
14
15
|
}
|
|
15
16
|
};
|
|
@@ -17,7 +17,7 @@ const replacements = new Map([[/^rounded-(r|l|tr|tl|br|bl)(-.*)?$/, ([side, rest
|
|
|
17
17
|
l: 's'
|
|
18
18
|
}[side];
|
|
19
19
|
return `border-${side}${rest}`;
|
|
20
|
-
}], [/^text-xs-(left|right|center|justify)$/, ([align]) => `text-${align}`], ['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]]);
|
|
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]]);
|
|
21
21
|
|
|
22
22
|
// ------------------------------------------------------------------------------
|
|
23
23
|
// Rule Definition
|
|
@@ -14,6 +14,8 @@ const replacements = {
|
|
|
14
14
|
VListTileSubTitle: 'v-list-item-subtitle',
|
|
15
15
|
VJumbotron: false,
|
|
16
16
|
VToolbarSideIcon: 'v-app-bar-nav-icon',
|
|
17
|
+
VExpansionPanelHeader: 'v-expansion-panel-title',
|
|
18
|
+
VExpansionPanelContent: 'v-expansion-panel-text',
|
|
17
19
|
// Possible typos
|
|
18
20
|
VListItemSubTitle: 'v-list-item-subtitle',
|
|
19
21
|
VListTileSubtitle: 'v-list-item-subtitle',
|
|
@@ -21,20 +23,25 @@ const replacements = {
|
|
|
21
23
|
VCalendar: false,
|
|
22
24
|
VData: false,
|
|
23
25
|
VListItemGroup: false,
|
|
24
|
-
VListItemAvatar:
|
|
26
|
+
VListItemAvatar: {
|
|
27
|
+
custom: '`v-list-item` with `avatar` props, or `v-avatar` in the list item append or prepend slot'
|
|
28
|
+
},
|
|
25
29
|
VListItemContent: false,
|
|
26
|
-
VListItemIcon:
|
|
27
|
-
|
|
30
|
+
VListItemIcon: {
|
|
31
|
+
custom: '`v-list-item` with `icon` props, or `v-icon` in the list item append or prepend slot'
|
|
32
|
+
},
|
|
28
33
|
VOverflowBtn: false,
|
|
29
34
|
VPicker: false,
|
|
30
35
|
VSimpleCheckbox: 'v-checkbox-btn',
|
|
31
36
|
VSparkline: false,
|
|
32
37
|
VSpeedDial: false,
|
|
33
|
-
VStepper: false,
|
|
34
38
|
VSubheader: {
|
|
35
39
|
custom: 'v-list-subheader or class="text-subheader-2"'
|
|
36
40
|
},
|
|
37
41
|
VSimpleTable: 'v-table',
|
|
42
|
+
VTabsSlider: false,
|
|
43
|
+
VTabsItems: false,
|
|
44
|
+
VTabItem: false,
|
|
38
45
|
VTimePicker: false,
|
|
39
46
|
VTreeview: false
|
|
40
47
|
};
|
|
@@ -115,17 +115,13 @@ const replacements = {
|
|
|
115
115
|
VRangeSlider: {
|
|
116
116
|
...model,
|
|
117
117
|
change: false,
|
|
118
|
-
'update:error': false
|
|
119
|
-
start: false,
|
|
120
|
-
end: false
|
|
118
|
+
'update:error': false
|
|
121
119
|
},
|
|
122
120
|
VRating: model,
|
|
123
121
|
VSlider: {
|
|
124
122
|
...model,
|
|
125
123
|
change: false,
|
|
126
|
-
'update:error': false
|
|
127
|
-
start: false,
|
|
128
|
-
end: false
|
|
124
|
+
'update:error': false
|
|
129
125
|
},
|
|
130
126
|
VSlideGroup: {
|
|
131
127
|
change: 'update:modelValue',
|
|
@@ -99,7 +99,6 @@ const select = {
|
|
|
99
99
|
itemText: 'item-title',
|
|
100
100
|
searchInput: 'search',
|
|
101
101
|
smallChips: false,
|
|
102
|
-
valueComparator: false,
|
|
103
102
|
filter: 'customFilter',
|
|
104
103
|
...inputs,
|
|
105
104
|
dense: false
|
|
@@ -127,17 +126,30 @@ const replacements = {
|
|
|
127
126
|
app: false,
|
|
128
127
|
clippedLeft: false,
|
|
129
128
|
clippedRight: false,
|
|
130
|
-
collapseOnScroll:
|
|
131
|
-
|
|
132
|
-
|
|
129
|
+
collapseOnScroll: {
|
|
130
|
+
name: 'scroll-behavior',
|
|
131
|
+
value: 'collapse'
|
|
132
|
+
},
|
|
133
|
+
elevateOnScroll: {
|
|
134
|
+
name: 'scroll-behavior',
|
|
135
|
+
value: 'elevate'
|
|
136
|
+
},
|
|
137
|
+
fadeImgOnScroll: {
|
|
138
|
+
name: 'scroll-behavior',
|
|
139
|
+
value: 'fade-image'
|
|
140
|
+
},
|
|
133
141
|
fixed: false,
|
|
134
|
-
hideOnScroll:
|
|
135
|
-
|
|
142
|
+
hideOnScroll: {
|
|
143
|
+
name: 'scroll-behavior',
|
|
144
|
+
value: 'hide'
|
|
145
|
+
},
|
|
146
|
+
invertedScroll: {
|
|
147
|
+
name: 'scroll-behavior',
|
|
148
|
+
value: 'inverted'
|
|
149
|
+
},
|
|
136
150
|
outlined: 'border',
|
|
137
151
|
prominent: false,
|
|
138
152
|
scrollOffScreen: false,
|
|
139
|
-
scrollTarget: false,
|
|
140
|
-
scrollThreshold: false,
|
|
141
153
|
shaped: false,
|
|
142
154
|
short: false,
|
|
143
155
|
shrinkOnScroll: false,
|
|
@@ -392,7 +404,9 @@ const replacements = {
|
|
|
392
404
|
},
|
|
393
405
|
selected: 'value',
|
|
394
406
|
textColor: false,
|
|
395
|
-
...link
|
|
407
|
+
...link,
|
|
408
|
+
...sizes,
|
|
409
|
+
...theme
|
|
396
410
|
},
|
|
397
411
|
VChipGroup: {
|
|
398
412
|
activeClass: 'selected-class',
|
|
@@ -408,6 +422,39 @@ const replacements = {
|
|
|
408
422
|
hideModeSwitch: false,
|
|
409
423
|
value: 'model-value'
|
|
410
424
|
},
|
|
425
|
+
VDataTable: {
|
|
426
|
+
serverItemsLength: {
|
|
427
|
+
custom: '<v-data-table-server>'
|
|
428
|
+
},
|
|
429
|
+
itemClass: {
|
|
430
|
+
custom: 'row-props'
|
|
431
|
+
},
|
|
432
|
+
itemStyle: {
|
|
433
|
+
custom: 'row-props'
|
|
434
|
+
},
|
|
435
|
+
sortDesc: {
|
|
436
|
+
custom: 'sort-by'
|
|
437
|
+
},
|
|
438
|
+
groupDesc: {
|
|
439
|
+
custom: 'group-by'
|
|
440
|
+
}
|
|
441
|
+
},
|
|
442
|
+
VDatePicker: {
|
|
443
|
+
activePicker: 'view-mode',
|
|
444
|
+
pickerDate: {
|
|
445
|
+
custom: 'separate month and year props'
|
|
446
|
+
},
|
|
447
|
+
locale: false,
|
|
448
|
+
localeFirstDayOfYear: false,
|
|
449
|
+
firstDayOfWeek: false,
|
|
450
|
+
dayFormat: false,
|
|
451
|
+
weekdayFormat: false,
|
|
452
|
+
monthFormat: false,
|
|
453
|
+
yearFormat: false,
|
|
454
|
+
headerDateFormat: false,
|
|
455
|
+
titleDateFormat: false,
|
|
456
|
+
range: false
|
|
457
|
+
},
|
|
411
458
|
VExpansionPanels: {
|
|
412
459
|
accordion: {
|
|
413
460
|
name: 'variant',
|
|
@@ -657,6 +704,7 @@ const replacements = {
|
|
|
657
704
|
VRadioGroup: {
|
|
658
705
|
activeClass: false,
|
|
659
706
|
backgroundColor: false,
|
|
707
|
+
row: 'inline',
|
|
660
708
|
column: false,
|
|
661
709
|
multiple: false,
|
|
662
710
|
...inputs
|
|
@@ -910,11 +958,35 @@ module.exports = {
|
|
|
910
958
|
schema: [],
|
|
911
959
|
messages: {
|
|
912
960
|
replacedWith: `'{{ a }}' has been replaced with '{{ b }}'`,
|
|
913
|
-
removed: `'{{ name }}' has been removed
|
|
961
|
+
removed: `'{{ name }}' has been removed`,
|
|
962
|
+
combined: `multiple {{ a }} attributes have been combined`
|
|
914
963
|
}
|
|
915
964
|
},
|
|
916
965
|
create(context) {
|
|
917
966
|
return context.parserServices.defineTemplateBodyVisitor({
|
|
967
|
+
VStartTag(tag) {
|
|
968
|
+
const attrGroups = {};
|
|
969
|
+
tag.attributes.forEach(attr => {
|
|
970
|
+
if (['location'].includes(attr.key.name)) {
|
|
971
|
+
attrGroups[attr.key.name] = attrGroups[attr.key.name] ?? [];
|
|
972
|
+
attrGroups[attr.key.name].push(attr);
|
|
973
|
+
}
|
|
974
|
+
});
|
|
975
|
+
Object.values(attrGroups).forEach(attrGroup => {
|
|
976
|
+
const [head, ...tail] = attrGroup;
|
|
977
|
+
if (!tail.length) return;
|
|
978
|
+
context.report({
|
|
979
|
+
messageId: 'combined',
|
|
980
|
+
data: {
|
|
981
|
+
a: head.key.name
|
|
982
|
+
},
|
|
983
|
+
node: head,
|
|
984
|
+
fix(fixer) {
|
|
985
|
+
return [fixer.replaceText(head.value, `"${attrGroup.map(a => a.value.value).join(' ')}"`), ...tail.map(a => fixer.remove(a))];
|
|
986
|
+
}
|
|
987
|
+
});
|
|
988
|
+
});
|
|
989
|
+
},
|
|
918
990
|
VAttribute(attr) {
|
|
919
991
|
if (attr.directive && (attr.key.name.name !== 'bind' || !attr.key.argument)) return;
|
|
920
992
|
const tag = classify(attr.parent.parent.rawName);
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
classify,
|
|
5
|
+
getAttributes
|
|
6
|
+
} = require('../util/helpers');
|
|
7
|
+
const groups = [{
|
|
8
|
+
components: ['VDialog', 'VMenu', 'VTooltip'],
|
|
9
|
+
slots: ['activator'],
|
|
10
|
+
handler(context, node, directive, param) {
|
|
11
|
+
if (param.type === 'Identifier') {
|
|
12
|
+
// #activator="data"
|
|
13
|
+
const boundVariables = {};
|
|
14
|
+
node.variables.find(variable => variable.id.name === param.name)?.references.forEach(ref => {
|
|
15
|
+
if (ref.id.parent.type !== 'MemberExpression') return;
|
|
16
|
+
if (
|
|
17
|
+
// v-bind="data.props"
|
|
18
|
+
ref.id.parent.property.name === 'props' && ref.id.parent.parent.parent.directive && ref.id.parent.parent.parent.key.name.name === 'bind' && !ref.id.parent.parent.parent.key.argument) return;
|
|
19
|
+
if (ref.id.parent.property.name === 'on') {
|
|
20
|
+
boundVariables.on = ref.id;
|
|
21
|
+
} else if (ref.id.parent.property.name === 'attrs') {
|
|
22
|
+
boundVariables.attrs = ref.id;
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
if (boundVariables.on) {
|
|
26
|
+
const ref = boundVariables.on;
|
|
27
|
+
context.report({
|
|
28
|
+
node: ref,
|
|
29
|
+
messageId: 'changedProps',
|
|
30
|
+
data: {
|
|
31
|
+
component: node.parent.name,
|
|
32
|
+
slot: directive.key.argument.name
|
|
33
|
+
},
|
|
34
|
+
fix(fixer) {
|
|
35
|
+
return fixer.replaceText(ref.parent.parent.parent, `v-bind="${param.name}.props"`);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
if (boundVariables.attrs) {
|
|
40
|
+
const ref = boundVariables.attrs;
|
|
41
|
+
if (!boundVariables.on) {
|
|
42
|
+
context.report({
|
|
43
|
+
node: boundVariables.attrs,
|
|
44
|
+
messageId: 'invalidProps'
|
|
45
|
+
});
|
|
46
|
+
} else {
|
|
47
|
+
context.report({
|
|
48
|
+
node: ref,
|
|
49
|
+
messageId: 'changedProps',
|
|
50
|
+
data: {
|
|
51
|
+
component: node.parent.name,
|
|
52
|
+
slot: directive.key.argument.name
|
|
53
|
+
},
|
|
54
|
+
fix(fixer) {
|
|
55
|
+
return fixer.removeRange([ref.parent.parent.parent.range[0] - 1, ref.parent.parent.parent.range[1]]);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
} else if (param.type === 'ObjectPattern') {
|
|
61
|
+
// #activator="{ on, attrs }"
|
|
62
|
+
const boundVariables = {};
|
|
63
|
+
param.properties.forEach(prop => {
|
|
64
|
+
node.variables.find(variable => variable.id.name === prop.value.name)?.references.forEach(ref => {
|
|
65
|
+
if (prop.key.name === 'on') {
|
|
66
|
+
boundVariables.on = {
|
|
67
|
+
prop,
|
|
68
|
+
id: ref.id
|
|
69
|
+
};
|
|
70
|
+
} else if (prop.key.name === 'attrs') {
|
|
71
|
+
boundVariables.attrs = {
|
|
72
|
+
prop,
|
|
73
|
+
id: ref.id
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
if (boundVariables.on || boundVariables.attrs) {
|
|
79
|
+
if (boundVariables.attrs && !boundVariables.on) {
|
|
80
|
+
context.report({
|
|
81
|
+
node: boundVariables.attrs.prop.key,
|
|
82
|
+
messageId: 'invalidProps'
|
|
83
|
+
});
|
|
84
|
+
} else {
|
|
85
|
+
context.report({
|
|
86
|
+
node: param,
|
|
87
|
+
messageId: 'changedProps',
|
|
88
|
+
data: {
|
|
89
|
+
component: node.parent.name,
|
|
90
|
+
slot: directive.key.argument.name
|
|
91
|
+
},
|
|
92
|
+
*fix(fixer) {
|
|
93
|
+
if (boundVariables.on) {
|
|
94
|
+
const ref = boundVariables.on;
|
|
95
|
+
yield fixer.replaceText(ref.prop, 'props');
|
|
96
|
+
yield fixer.replaceText(ref.id.parent.parent, `v-bind="props"`);
|
|
97
|
+
}
|
|
98
|
+
if (boundVariables.attrs) {
|
|
99
|
+
const template = context.parserServices.getTemplateBodyTokenStore();
|
|
100
|
+
const ref = boundVariables.attrs;
|
|
101
|
+
const isLast = ref.prop === param.properties.at(-1);
|
|
102
|
+
if (isLast) {
|
|
103
|
+
const comma = template.getTokenBefore(ref.prop, {
|
|
104
|
+
filter: token => token.value === ','
|
|
105
|
+
});
|
|
106
|
+
if (comma) {
|
|
107
|
+
yield fixer.removeRange([comma.range[0], ref.prop.range[1]]);
|
|
108
|
+
} else {
|
|
109
|
+
yield fixer.removeRange([ref.prop.range[0] - 1, ref.prop.range[1]]);
|
|
110
|
+
}
|
|
111
|
+
} else {
|
|
112
|
+
const comma = template.getTokenAfter(ref.prop, {
|
|
113
|
+
filter: token => token.value === ','
|
|
114
|
+
});
|
|
115
|
+
if (comma) {
|
|
116
|
+
yield fixer.removeRange([ref.prop.range[0] - 1, comma.range[1]]);
|
|
117
|
+
} else {
|
|
118
|
+
yield fixer.removeRange([ref.prop.range[0] - 1, ref.prop.range[1]]);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
yield fixer.removeRange([ref.id.parent.parent.range[0] - 1, ref.id.parent.parent.range[1]]);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
} else {
|
|
128
|
+
context.report({
|
|
129
|
+
node: directive,
|
|
130
|
+
messageId: 'invalidProps'
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}, {
|
|
135
|
+
components: ['VSelect', 'VAutocomplete', 'VCombobox'],
|
|
136
|
+
slots: ['selection'],
|
|
137
|
+
handler(context, node, directive, param) {
|
|
138
|
+
if (!getAttributes(node.parent).some(attr => ['chips', 'closable-chips'].includes(attr.name))) return;
|
|
139
|
+
context.report({
|
|
140
|
+
node: directive,
|
|
141
|
+
messageId: 'renamed',
|
|
142
|
+
data: {
|
|
143
|
+
component: node.parent.name,
|
|
144
|
+
slot: directive.key.argument.name,
|
|
145
|
+
newSlot: 'chip'
|
|
146
|
+
},
|
|
147
|
+
fix(fixer) {
|
|
148
|
+
return fixer.replaceText(directive.key.argument, 'chip');
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
}];
|
|
153
|
+
|
|
154
|
+
// ------------------------------------------------------------------------------
|
|
155
|
+
// Rule Definition
|
|
156
|
+
// ------------------------------------------------------------------------------
|
|
157
|
+
|
|
158
|
+
module.exports = {
|
|
159
|
+
meta: {
|
|
160
|
+
docs: {
|
|
161
|
+
description: 'Prevent the use of removed and deprecated slots.',
|
|
162
|
+
category: 'recommended'
|
|
163
|
+
},
|
|
164
|
+
fixable: 'code',
|
|
165
|
+
schema: [],
|
|
166
|
+
messages: {
|
|
167
|
+
renamed: `{{ component }}'s '{{ slot }}' slot has been renamed to '{{ newSlot }}'`,
|
|
168
|
+
changedProps: `{{ component }}'s '{{ slot }}' slot has changed props`,
|
|
169
|
+
invalidProps: `Slot has invalid props`
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
create(context) {
|
|
173
|
+
let scopeStack;
|
|
174
|
+
return context.parserServices.defineTemplateBodyVisitor({
|
|
175
|
+
VElement(node) {
|
|
176
|
+
scopeStack = {
|
|
177
|
+
parent: scopeStack,
|
|
178
|
+
nodes: scopeStack ? [...scopeStack.nodes] : []
|
|
179
|
+
};
|
|
180
|
+
for (const variable of node.variables) {
|
|
181
|
+
scopeStack.nodes.push(variable.id);
|
|
182
|
+
}
|
|
183
|
+
if (node.name !== 'template' || node.parent.type !== 'VElement') return;
|
|
184
|
+
for (const group of groups) {
|
|
185
|
+
if (!group.components.includes(classify(node.parent.name)) && !group.components.includes(node.parent.name)) continue;
|
|
186
|
+
const directive = node.startTag.attributes.find(attr => {
|
|
187
|
+
return attr.directive && attr.key.name.name === 'slot' && group.slots.includes(attr.key.argument?.name);
|
|
188
|
+
});
|
|
189
|
+
if (!directive || !directive.value || directive.value.type !== 'VExpressionContainer' || !directive.value.expression || directive.value.expression.params.length !== 1) continue;
|
|
190
|
+
const param = directive.value.expression.params[0];
|
|
191
|
+
group.handler(context, node, directive, param);
|
|
192
|
+
}
|
|
193
|
+
},
|
|
194
|
+
'VElement:exit'() {
|
|
195
|
+
scopeStack = scopeStack && scopeStack.parent;
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
};
|