eslint-plugin-vuetify 2.0.5 → 2.1.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.
@@ -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',
@@ -24,17 +26,18 @@ const replacements = {
24
26
  VListItemAvatar: false,
25
27
  VListItemContent: false,
26
28
  VListItemIcon: false,
27
- VOtpInput: false,
28
29
  VOverflowBtn: false,
29
30
  VPicker: false,
30
31
  VSimpleCheckbox: 'v-checkbox-btn',
31
32
  VSparkline: false,
32
33
  VSpeedDial: false,
33
- VStepper: false,
34
34
  VSubheader: {
35
35
  custom: 'v-list-subheader or class="text-subheader-2"'
36
36
  },
37
37
  VSimpleTable: 'v-table',
38
+ VTabsSlider: false,
39
+ VTabsItems: false,
40
+ VTabItem: false,
38
41
  VTimePicker: false,
39
42
  VTreeview: false
40
43
  };
@@ -392,7 +392,9 @@ const replacements = {
392
392
  },
393
393
  selected: 'value',
394
394
  textColor: false,
395
- ...link
395
+ ...link,
396
+ ...sizes,
397
+ ...theme
396
398
  },
397
399
  VChipGroup: {
398
400
  activeClass: 'selected-class',
@@ -408,6 +410,39 @@ const replacements = {
408
410
  hideModeSwitch: false,
409
411
  value: 'model-value'
410
412
  },
413
+ VDataTable: {
414
+ serverItemsLength: {
415
+ custom: '<v-data-table-server>'
416
+ },
417
+ itemClass: {
418
+ custom: 'row-props'
419
+ },
420
+ itemStyle: {
421
+ custom: 'row-props'
422
+ },
423
+ sortDesc: {
424
+ custom: 'sort-by'
425
+ },
426
+ groupDesc: {
427
+ custom: 'group-by'
428
+ }
429
+ },
430
+ VDatePicker: {
431
+ activePicker: 'view-mode',
432
+ pickerDate: {
433
+ custom: 'separate month and year props'
434
+ },
435
+ locale: false,
436
+ localeFirstDayOfYear: false,
437
+ firstDayOfWeek: false,
438
+ dayFormat: false,
439
+ weekdayFormat: false,
440
+ monthFormat: false,
441
+ yearFormat: false,
442
+ headerDateFormat: false,
443
+ titleDateFormat: false,
444
+ range: false
445
+ },
411
446
  VExpansionPanels: {
412
447
  accordion: {
413
448
  name: 'variant',
@@ -657,6 +692,7 @@ const replacements = {
657
692
  VRadioGroup: {
658
693
  activeClass: false,
659
694
  backgroundColor: false,
695
+ row: 'inline',
660
696
  column: false,
661
697
  multiple: false,
662
698
  ...inputs
@@ -910,11 +946,35 @@ module.exports = {
910
946
  schema: [],
911
947
  messages: {
912
948
  replacedWith: `'{{ a }}' has been replaced with '{{ b }}'`,
913
- removed: `'{{ name }}' has been removed`
949
+ removed: `'{{ name }}' has been removed`,
950
+ combined: `multiple {{ a }} attributes have been combined`
914
951
  }
915
952
  },
916
953
  create(context) {
917
954
  return context.parserServices.defineTemplateBodyVisitor({
955
+ VStartTag(tag) {
956
+ const attrGroups = {};
957
+ tag.attributes.forEach(attr => {
958
+ if (['location'].includes(attr.key.name)) {
959
+ attrGroups[attr.key.name] = attrGroups[attr.key.name] ?? [];
960
+ attrGroups[attr.key.name].push(attr);
961
+ }
962
+ });
963
+ Object.values(attrGroups).forEach(attrGroup => {
964
+ const [head, ...tail] = attrGroup;
965
+ if (!tail.length) return;
966
+ context.report({
967
+ messageId: 'combined',
968
+ data: {
969
+ a: head.key.name
970
+ },
971
+ node: head,
972
+ fix(fixer) {
973
+ return [fixer.replaceText(head.value, `"${attrGroup.map(a => a.value.value).join(' ')}"`), ...tail.map(a => fixer.remove(a))];
974
+ }
975
+ });
976
+ });
977
+ },
918
978
  VAttribute(attr) {
919
979
  if (attr.directive && (attr.key.name.name !== 'bind' || !attr.key.argument)) return;
920
980
  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.start, ref.prop.end]);
108
+ } else {
109
+ yield fixer.removeRange([ref.prop.start - 1, ref.prop.end]);
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.start - 1, comma.end]);
117
+ } else {
118
+ yield fixer.removeRange([ref.prop.start - 1, ref.prop.end]);
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.0",
4
4
  "description": "An eslint plugin for Vuetify",
5
5
  "main": "lib/index.js",
6
6
  "author": "Kael Watts-Deuchar <kaelwd@gmail.com>",