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 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>
@@ -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: false,
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: false,
27
- VOtpInput: false,
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: false,
131
- elevateOnScroll: false,
132
- fadeImgOnScroll: false,
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: false,
135
- invertedScroll: false,
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
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-vuetify",
3
- "version": "2.0.5",
3
+ "version": "2.1.1",
4
4
  "description": "An eslint plugin for Vuetify",
5
5
  "main": "lib/index.js",
6
6
  "author": "Kael Watts-Deuchar <kaelwd@gmail.com>",