eslint-plugin-vuetify 2.5.2 → 2.6.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 CHANGED
@@ -1,6 +1,22 @@
1
+ <div align="center">
2
+ <picture>
3
+ <source media="(prefers-color-scheme: dark)" srcset="https://cdn.vuetifyjs.com/docs/images/one/logos/veslplugin-logo-dark.png">
4
+ <img alt="Vuetify ESLint Plugin Logo" src="https://cdn.vuetifyjs.com/docs/images/one/logos/veslplugin-logo-light.png" height="100">
5
+ </picture>
6
+ </div>
7
+
8
+ <p align="center">
9
+ <a href="https://www.npmjs.com/package/eslint-plugin-vuetify"><img src="https://img.shields.io/npm/v/eslint-plugin-vuetify.svg" alt="npm version"></a>
10
+ <a href="https://npm.chart.dev/eslint-plugin-vuetify"><img src="https://img.shields.io/npm/dm/eslint-plugin-vuetify?color=blue" alt="npm downloads"></a>
11
+ <a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/License-MIT-blue.svg" alt="License: MIT"></a>
12
+ <a href="https://community.vuetifyjs.com"><img src="https://discordapp.com/api/guilds/340160225338195969/widget.png" alt="Discord"></a>
13
+ </p>
14
+
1
15
  # eslint-plugin-vuetify
2
16
 
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.
17
+ This package helps migrate between Vuetify major versions. It includes rules for **v2 v3** and **v3 v4** migrations.
18
+
19
+ Use [eslint-plugin-vuetify@vuetify-2](https://www.npmjs.com/package/eslint-plugin-vuetify/v/vuetify-2) for v1 to v2.
4
20
 
5
21
  <br>
6
22
 
@@ -13,6 +29,53 @@ This package is for migrating from Vuetify v2 to v3, use [eslint-plugin-vuetify@
13
29
  </a>
14
30
  </p>
15
31
 
32
+ ## Vuetify 4 Migration
33
+
34
+ This plugin includes four new rules for migrating from Vuetify v3 to v4:
35
+
36
+ - **`no-deprecated-typography`** — replaces MD2 typography classes (`text-h1`) with MD3 equivalents (`text-display-large`)
37
+ - **`no-legacy-grid-props`** — converts removed `VRow`/`VCol` props (`align`, `justify`, `dense`) to utility classes or renamed props
38
+ - **`no-elevation-overflow`** — flags elevation classes and props above the MD3 maximum of 5
39
+ - **`no-deprecated-snackbar`** — fixes renamed `VSnackbarQueue` slots and replaced `VSnackbar` props
40
+
41
+ ### Using the recommended-v4 preset
42
+
43
+ Enable all v4 migration rules at once:
44
+
45
+ ```js
46
+ // eslint.config.js
47
+ import vue from 'eslint-plugin-vue'
48
+ import vuetify from 'eslint-plugin-vuetify'
49
+
50
+ export default [
51
+ ...vue.configs['flat/base'],
52
+ ...vuetify.configs['flat/recommended-v4'],
53
+ ]
54
+ ```
55
+
56
+ ### Selecting individual rules
57
+
58
+ You can also enable rules selectively instead of using the preset:
59
+
60
+ ```js
61
+ // eslint.config.js
62
+ import vue from 'eslint-plugin-vue'
63
+ import vuetify from 'eslint-plugin-vuetify'
64
+
65
+ export default [
66
+ ...vue.configs['flat/base'],
67
+ ...vuetify.configs['flat/base'],
68
+ {
69
+ rules: {
70
+ 'vuetify/no-deprecated-typography': 'error',
71
+ 'vuetify/no-legacy-grid-props': 'error',
72
+ 'vuetify/no-elevation-overflow': 'error',
73
+ 'vuetify/no-deprecated-snackbar': 'error',
74
+ }
75
+ }
76
+ ]
77
+ ```
78
+
16
79
  ## 💿 Install
17
80
 
18
81
  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.
@@ -34,7 +97,7 @@ export default [
34
97
  ]
35
98
  ```
36
99
 
37
- Eslint 8 can alternatively use the older configuration format:
100
+ ESLint 8 can alternatively use the older configuration format:
38
101
 
39
102
  ```js
40
103
  // .eslintrc.js
@@ -46,7 +109,9 @@ module.exports = {
46
109
  }
47
110
  ```
48
111
 
49
- **NOTE** This plugin does not affect _**pug**_ templates due to [a limitation in vue-eslint-parser](https://github.com/mysticatea/vue-eslint-parser/issues/29). I suggest converting your pug templates to HTML with [pug-to-html](https://github.com/leo-buneev/pug-to-html) in order to use this plugin.
112
+ This plugin supports ESLint 8, 9, and 10. ESLint 10 only supports the flat config format (use `configs['flat/base']` or `configs['flat/recommended']`).
113
+
114
+ **NOTE** This plugin does not affect _**pug**_ templates due to [a limitation in vue-eslint-parser](https://github.com/vuejs/vue-eslint-parser/issues/29). I suggest converting your pug templates to HTML with [pug-to-html](https://github.com/leo-buneev/pug-to-html) in order to use this plugin.
50
115
 
51
116
 
52
117
  ## Rules
@@ -60,15 +125,28 @@ These rules will help you avoid deprecated components, props, and classes. They
60
125
  - Prevent the use of events that have been removed from Vuetify ([`no-deprecated-events`])
61
126
  - Prevent the use of classes that have been removed from Vuetify ([`no-deprecated-classes`])
62
127
  - Prevent the use of the old theme class syntax ([`no-deprecated-colors`])
128
+ - Prevent the use of slots that have been removed from Vuetify ([`no-deprecated-slots`])
63
129
  - Prevent the use of deprecated import paths ([`no-deprecated-imports`])
130
+
131
+ Additional rule (not included in presets):
132
+
64
133
  - Ensure icon buttons have a variant defined ([`icon-button-variant`])
65
134
 
66
135
  ### Grid system
67
136
 
68
- These rules are designed to help migrate to the new grid system in Vuetify v2. They are included in the `recommended` preset.
137
+ These rules are designed to help migrate to the new grid system in Vuetify v3. They are included in the `recommended` preset.
69
138
 
70
139
  - Warn about unknown attributes not being converted to classes on new grid components ([`grid-unknown-attributes`])
71
140
 
141
+ ### Vuetify 4
142
+
143
+ These rules help migrate from Vuetify v3 to v4. They are included in the `recommended-v4` preset.
144
+
145
+ - Disallow deprecated MD2 typography classes ([`no-deprecated-typography`])
146
+ - Prevent the use of removed grid props ([`no-legacy-grid-props`])
147
+ - Disallow elevation classes above the MD3 maximum ([`no-elevation-overflow`])
148
+ - Disallow deprecated props and slots on snackbar components ([`no-deprecated-snackbar`])
149
+
72
150
 
73
151
  [`grid-unknown-attributes`]: ./docs/rules/grid-unknown-attributes.md
74
152
  [`no-deprecated-components`]: ./docs/rules/no-deprecated-components.md
@@ -76,8 +154,13 @@ These rules are designed to help migrate to the new grid system in Vuetify v2. T
76
154
  [`no-deprecated-events`]: ./docs/rules/no-deprecated-events.md
77
155
  [`no-deprecated-classes`]: ./docs/rules/no-deprecated-classes.md
78
156
  [`no-deprecated-colors`]: ./docs/rules/no-deprecated-colors.md
157
+ [`no-deprecated-slots`]: ./docs/rules/no-deprecated-slots.md
79
158
  [`no-deprecated-imports`]: ./docs/rules/no-deprecated-imports.md
80
159
  [`icon-button-variant`]: ./docs/rules/icon-button-variant.md
160
+ [`no-deprecated-typography`]: ./docs/rules/no-deprecated-typography.md
161
+ [`no-legacy-grid-props`]: ./docs/rules/no-legacy-grid-props.md
162
+ [`no-elevation-overflow`]: ./docs/rules/no-elevation-overflow.md
163
+ [`no-deprecated-snackbar`]: ./docs/rules/no-deprecated-snackbar.md
81
164
 
82
165
 
83
166
  ## 💪 Supporting Vuetify
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+
3
+ module.exports = [...require('./base'), {
4
+ plugins: {
5
+ get vuetify() {
6
+ return require('../../index');
7
+ }
8
+ },
9
+ rules: require('../recommended-v4').rules
10
+ }];
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+
3
+ module.exports = [...require('./base'), {
4
+ plugins: {
5
+ get vuetify() {
6
+ return require('../../index');
7
+ }
8
+ },
9
+ rules: require('../tailwindcss').rules
10
+ }];
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+
3
+ module.exports = {
4
+ rules: {
5
+ 'vuetify/no-deprecated-snackbar': 'error',
6
+ 'vuetify/no-deprecated-typography': 'error',
7
+ 'vuetify/no-elevation-overflow': 'error',
8
+ 'vuetify/no-legacy-grid-props': 'error'
9
+ }
10
+ };
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+
3
+ module.exports = {
4
+ rules: {
5
+ 'vuetify/no-elevation-prop': 'error',
6
+ 'vuetify/no-rounded-prop': 'error',
7
+ 'vuetify/no-border-prop': 'error',
8
+ 'vuetify/no-legacy-utilities': 'error'
9
+ }
10
+ };
package/lib/index.js CHANGED
@@ -6,8 +6,12 @@ module.exports = {
6
6
  configs: {
7
7
  base: require('./configs/base'),
8
8
  recommended: require('./configs/recommended'),
9
+ 'recommended-v4': require('./configs/recommended-v4'),
9
10
  'flat/base': require('./configs/flat/base'),
10
- 'flat/recommended': require('./configs/flat/recommended')
11
+ 'flat/recommended': require('./configs/flat/recommended'),
12
+ 'flat/recommended-v4': require('./configs/flat/recommended-v4'),
13
+ tailwindcss: require('./configs/tailwindcss'),
14
+ 'flat/tailwindcss': require('./configs/flat/tailwindcss')
11
15
  },
12
16
  rules: requireindex(path.join(__dirname, './rules'))
13
17
  };
@@ -0,0 +1,73 @@
1
+ 'use strict';
2
+
3
+ const {
4
+ isVueTemplate
5
+ } = require('../util/helpers');
6
+ const {
7
+ addClass,
8
+ removeAttr
9
+ } = require('../util/fixers');
10
+ module.exports = {
11
+ meta: {
12
+ docs: {
13
+ description: 'Disallow the `border` prop; use Tailwind border utilities instead.',
14
+ category: 'tailwindcss'
15
+ },
16
+ fixable: 'code',
17
+ schema: [],
18
+ messages: {
19
+ replacedWith: `'border="{{ value }}"' should be replaced with class="{{ className }}"`,
20
+ noFix: `'border' prop should be replaced with Tailwind border utility classes`
21
+ }
22
+ },
23
+ create(context) {
24
+ if (!isVueTemplate(context)) return {};
25
+ return context.sourceCode.parserServices.defineTemplateBodyVisitor({
26
+ VAttribute(attr) {
27
+ if (attr.directive && (attr.key.name.name !== 'bind' || !attr.key.argument)) return;
28
+ const propName = attr.directive ? attr.key.argument.rawName : attr.key.rawName;
29
+ if (propName !== 'border') return;
30
+ const element = attr.parent.parent;
31
+ const propNameNode = attr.directive ? attr.key.argument : attr.key;
32
+
33
+ // Boolean attribute (no value) — `border` with no `="..."`
34
+ if (!attr.directive && !attr.value) {
35
+ context.report({
36
+ messageId: 'replacedWith',
37
+ data: {
38
+ value: '',
39
+ className: 'border'
40
+ },
41
+ node: propNameNode,
42
+ fix(fixer) {
43
+ return [addClass(context, fixer, element, 'border'), removeAttr(context, fixer, attr)];
44
+ }
45
+ });
46
+ return;
47
+ }
48
+
49
+ // Get static value
50
+ const value = attr.directive ? attr.value?.expression?.type === 'Literal' ? String(attr.value.expression.value) : null : attr.value?.value;
51
+ if (value != null) {
52
+ const className = value.split(/\s+/).filter(Boolean).map(part => `border-${part}`).join(' ');
53
+ context.report({
54
+ messageId: 'replacedWith',
55
+ data: {
56
+ value,
57
+ className
58
+ },
59
+ node: propNameNode,
60
+ fix(fixer) {
61
+ return [addClass(context, fixer, element, className), removeAttr(context, fixer, attr)];
62
+ }
63
+ });
64
+ } else {
65
+ context.report({
66
+ messageId: 'noFix',
67
+ node: propNameNode
68
+ });
69
+ }
70
+ }
71
+ });
72
+ }
73
+ };
@@ -15,7 +15,7 @@ const replacements = new Map([[/^rounded-(r|l|tr|tl|br|bl)(-.*)?$/, ([side, rest
15
15
  bl: 'bs'
16
16
  }[side];
17
17
  return `rounded-${side}${rest || ''}`;
18
- }], [/^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]]);
18
+ }], [/^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'], ['subheading', 'text-subtitle-1'], ['subtitle-1', 'text-subtitle-1'], ['subtitle-2', 'text-subtitle-2'], ['body-1', 'text-body-1'], ['body-2', 'text-body-2'], ['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]]);
19
19
 
20
20
  // ------------------------------------------------------------------------------
21
21
  // Rule Definition
@@ -31,7 +31,6 @@ const replacements = {
31
31
  custom: '`v-list-item` with `icon` props, or `v-icon` in the list item append or prepend slot'
32
32
  },
33
33
  VOverflowBtn: false,
34
- VPicker: false,
35
34
  VSimpleCheckbox: 'v-checkbox-btn',
36
35
  VSubheader: {
37
36
  custom: 'v-list-subheader or class="text-subtitle-2"'
@@ -144,7 +144,14 @@ const replacements = {
144
144
  value: 'inverted'
145
145
  },
146
146
  outlined: 'border',
147
- prominent: false,
147
+ dense: {
148
+ name: 'density',
149
+ value: 'compact'
150
+ },
151
+ prominent: {
152
+ name: 'density',
153
+ value: 'prominent'
154
+ },
148
155
  scrollOffScreen: false,
149
156
  shaped: false,
150
157
  short: false,
@@ -286,9 +293,11 @@ const replacements = {
286
293
  },
287
294
  round: 'rounded',
288
295
  shaped: false,
289
- text: {
290
- name: 'variant',
291
- value: 'text'
296
+ text(attr) {
297
+ return !attr.directive && !attr.value || attr.directive && [true, false].includes(attr.value.expression.value) ? {
298
+ name: 'variant',
299
+ value: 'text'
300
+ } : true;
292
301
  },
293
302
  top: {
294
303
  name: 'location',
@@ -417,6 +426,10 @@ const replacements = {
417
426
  },
418
427
  groupDesc: {
419
428
  custom: 'group-by'
429
+ },
430
+ dense: {
431
+ name: 'density',
432
+ value: 'compact'
420
433
  }
421
434
  },
422
435
  VDatePicker: {
@@ -425,8 +438,7 @@ const replacements = {
425
438
  custom: 'separate month and year props'
426
439
  },
427
440
  locale: false,
428
- localeFirstDayOfYear: false,
429
- firstDayOfWeek: false,
441
+ localeFirstDayOfYear: 'firstDayOfYear',
430
442
  dayFormat: false,
431
443
  weekdayFormat: false,
432
444
  monthFormat: false,
@@ -691,8 +703,8 @@ const replacements = {
691
703
  VSlider: {
692
704
  backgroundColor: false,
693
705
  tickLabels: 'ticks',
694
- ticks: {
695
- custom: 'show-ticks'
706
+ ticks(attr) {
707
+ return !attr.directive && !attr.value || attr.directive && [true, false].includes(attr.value.expression.value) ? 'show-ticks' : true;
696
708
  },
697
709
  vertical: {
698
710
  name: 'direction',
@@ -706,8 +718,8 @@ const replacements = {
706
718
  VRangeSlider: {
707
719
  backgroundColor: false,
708
720
  tickLabels: 'ticks',
709
- ticks: {
710
- custom: 'show-ticks'
721
+ ticks(attr) {
722
+ return !attr.directive && !attr.value || attr.directive && [true, false].includes(attr.value.expression.value) ? 'show-ticks' : true;
711
723
  },
712
724
  vertical: {
713
725
  name: 'direction',
@@ -830,6 +842,12 @@ const replacements = {
830
842
  link: false,
831
843
  ...link
832
844
  },
845
+ VTable: {
846
+ dense: {
847
+ name: 'density',
848
+ value: 'compact'
849
+ }
850
+ },
833
851
  VThemeProvider: {
834
852
  root: false
835
853
  },
@@ -854,7 +872,14 @@ const replacements = {
854
872
  VToolbar: {
855
873
  bottom: false,
856
874
  outlined: 'border',
857
- prominent: false,
875
+ dense: {
876
+ name: 'density',
877
+ value: 'compact'
878
+ },
879
+ prominent: {
880
+ name: 'density',
881
+ value: 'prominent'
882
+ },
858
883
  shaped: false,
859
884
  short: false,
860
885
  src: 'image',
@@ -904,6 +929,12 @@ const replacements = {
904
929
  value: 'model-value',
905
930
  ...overlay
906
931
  },
932
+ VTreeview: {
933
+ dense: {
934
+ name: 'density',
935
+ value: 'compact'
936
+ }
937
+ },
907
938
  VWindow: {
908
939
  activeClass: 'selected-class',
909
940
  showArrowsOnHover: false,
@@ -0,0 +1,108 @@
1
+ 'use strict';
2
+
3
+ const {
4
+ hyphenate,
5
+ classify,
6
+ isVueTemplate
7
+ } = require('../util/helpers');
8
+
9
+ // VSnackbarQueue: #default slot was renamed to #item in Vuetify 4
10
+ const slotRenames = [{
11
+ component: 'VSnackbarQueue',
12
+ from: 'default',
13
+ to: 'item'
14
+ }];
15
+
16
+ // VSnackbar props deprecated in Vuetify 4
17
+ const propReplacements = {
18
+ VSnackbar: {
19
+ multiLine: {
20
+ name: 'min-height',
21
+ value: '68'
22
+ }
23
+ }
24
+ };
25
+
26
+ // ------------------------------------------------------------------------------
27
+ // Rule Definition
28
+ // ------------------------------------------------------------------------------
29
+
30
+ module.exports = {
31
+ meta: {
32
+ docs: {
33
+ description: 'Disallow deprecated props and slots on Vuetify snackbar components.',
34
+ category: 'recommended'
35
+ },
36
+ fixable: 'code',
37
+ schema: [],
38
+ messages: {
39
+ replacedWith: `'{{ a }}' has been replaced with '{{ b }}'`,
40
+ renamed: `{{ component }}'s '{{ slot }}' slot has been renamed to '{{ newSlot }}'`
41
+ }
42
+ },
43
+ create(context) {
44
+ if (!isVueTemplate(context)) return {};
45
+ return context.sourceCode.parserServices.defineTemplateBodyVisitor({
46
+ VAttribute(attr) {
47
+ if (attr.directive && (attr.key.name.name !== 'bind' || !attr.key.argument)) return;
48
+ const tag = classify(attr.parent.parent.rawName);
49
+ if (!Object.keys(propReplacements).includes(tag)) return;
50
+ const propName = attr.directive ? hyphenate(attr.key.argument.rawName) : hyphenate(attr.key.rawName);
51
+ const propNameNode = attr.directive ? attr.key.argument : attr.key;
52
+ Object.entries(propReplacements[tag]).forEach(([test, replace]) => {
53
+ if (hyphenate(test) !== propName) return;
54
+ const oldValue = attr.directive ? context.sourceCode.getText(attr.value.expression) : attr.value?.value;
55
+ const value = typeof replace.value === 'function' ? replace.value(oldValue) : replace.value;
56
+ if (value == null || value === oldValue) return;
57
+ context.report({
58
+ messageId: 'replacedWith',
59
+ data: {
60
+ a: propName,
61
+ b: `${replace.name}="${value}"`
62
+ },
63
+ node: propNameNode,
64
+ fix(fixer) {
65
+ if (attr.directive && replace.bind !== false) {
66
+ if (replace.bind) {
67
+ return [fixer.replaceText(propNameNode, replace.name), fixer.replaceText(attr.value, `"${value}"`)];
68
+ } else {
69
+ const expression = context.sourceCode.getText(attr.value.expression);
70
+ return [fixer.replaceText(propNameNode, replace.name), fixer.replaceText(attr.value, `"${expression} ? '${value}' : undefined"`)];
71
+ }
72
+ } else {
73
+ return fixer.replaceText(attr, `${replace.bind ? ':' : ''}${replace.name}="${value}"`);
74
+ }
75
+ }
76
+ });
77
+ });
78
+ },
79
+ VElement(node) {
80
+ if (node.name !== 'template' || node.parent.type !== 'VElement') return;
81
+ const parentName = classify(node.parent.name);
82
+ for (const {
83
+ component,
84
+ from,
85
+ to
86
+ } of slotRenames) {
87
+ if (parentName !== component) continue;
88
+ const directive = node.startTag.attributes.find(attr => {
89
+ return attr.directive && attr.key.name.name === 'slot' && attr.key.argument?.name === from;
90
+ });
91
+ if (!directive) continue;
92
+ context.report({
93
+ node: directive,
94
+ messageId: 'renamed',
95
+ data: {
96
+ component: node.parent.name,
97
+ slot: from,
98
+ newSlot: to
99
+ },
100
+ fix(fixer) {
101
+ return fixer.replaceText(directive.key.argument, to);
102
+ }
103
+ });
104
+ }
105
+ }
106
+ });
107
+ }
108
+ };
@@ -0,0 +1,106 @@
1
+ 'use strict';
2
+
3
+ const {
4
+ isVueTemplate
5
+ } = require('../util/helpers');
6
+ const md2 = {
7
+ 'display-4': 'text-h1',
8
+ 'display-3': 'text-h2',
9
+ 'display-2': 'text-h3',
10
+ 'display-1': 'text-h4',
11
+ headline: 'text-h5',
12
+ title: 'text-h6',
13
+ subheading: 'text-subtitle-1',
14
+ 'subtitle-1': 'text-subtitle-1',
15
+ 'subtitle-2': 'text-subtitle-2',
16
+ 'body-1': 'text-body-1',
17
+ 'body-2': 'text-body-2',
18
+ caption: 'text-caption',
19
+ overline: 'text-overline'
20
+ };
21
+ const md3 = {
22
+ 'text-h1': 'text-display-large',
23
+ 'text-h2': 'text-display-medium',
24
+ 'text-h3': 'text-display-small',
25
+ 'text-h4': 'text-headline-large',
26
+ 'text-h5': 'text-headline-medium',
27
+ 'text-h6': 'text-headline-small',
28
+ 'text-subtitle-1': 'text-body-large',
29
+ 'text-subtitle-2': 'text-label-large',
30
+ 'text-body-1': 'text-body-large',
31
+ 'text-body-2': 'text-body-medium',
32
+ 'text-caption': 'text-body-small',
33
+ 'text-button': 'text-label-large',
34
+ 'text-overline': 'text-label-small'
35
+ };
36
+
37
+ // ------------------------------------------------------------------------------
38
+ // Rule Definition
39
+ // ------------------------------------------------------------------------------
40
+
41
+ module.exports = {
42
+ md2,
43
+ md3,
44
+ meta: {
45
+ docs: {
46
+ description: 'Disallow deprecated MD2 typography classes, with configurable replacements.',
47
+ category: 'recommended'
48
+ },
49
+ fixable: 'code',
50
+ schema: [{
51
+ type: 'object',
52
+ additionalProperties: {
53
+ oneOf: [{
54
+ type: 'string'
55
+ }, {
56
+ type: 'boolean',
57
+ enum: [false]
58
+ }]
59
+ }
60
+ }],
61
+ messages: {
62
+ replacedWith: `'{{ a }}' has been replaced with '{{ b }}'`,
63
+ removed: `'{{ name }}' has been removed`
64
+ }
65
+ },
66
+ create(context) {
67
+ if (!isVueTemplate(context)) return {};
68
+ const replacements = {
69
+ ...(context.options[0] || md3)
70
+ };
71
+
72
+ // Remove entries the user set to false
73
+ for (const key of Object.keys(replacements)) {
74
+ if (replacements[key] === false) delete replacements[key];
75
+ }
76
+ return context.sourceCode.parserServices.defineTemplateBodyVisitor({
77
+ 'VAttribute[key.name="class"]'(node) {
78
+ if (!node.value || !node.value.value) return;
79
+ const classes = node.value.value.split(/\s+/).filter(Boolean);
80
+ classes.forEach(className => {
81
+ const replace = replacements[className];
82
+ if (replace == null) return;
83
+ const idx = node.value.value.indexOf(className) + 1;
84
+ const range = [node.value.range[0] + idx, node.value.range[0] + idx + className.length];
85
+ const loc = {
86
+ start: context.sourceCode.getLocFromIndex(range[0]),
87
+ end: context.sourceCode.getLocFromIndex(range[1])
88
+ };
89
+ if (typeof replace === 'string') {
90
+ context.report({
91
+ loc,
92
+ messageId: 'replacedWith',
93
+ data: {
94
+ a: className,
95
+ b: replace
96
+ },
97
+ fix(fixer) {
98
+ return fixer.replaceTextRange(range, replace);
99
+ }
100
+ });
101
+ }
102
+ });
103
+ }
104
+ });
105
+ }
106
+ };
@@ -0,0 +1,77 @@
1
+ 'use strict';
2
+
3
+ const {
4
+ isVueTemplate
5
+ } = require('../util/helpers');
6
+ const MAX_ELEVATION = 5;
7
+
8
+ // Match elevation-N in a class string, return the number
9
+ const elevationRegex = /\belevation-(\d+)\b/g;
10
+ function checkClassValue(context, node, value) {
11
+ let match;
12
+ elevationRegex.lastIndex = 0;
13
+ while ((match = elevationRegex.exec(value)) !== null) {
14
+ const level = parseInt(match[1], 10);
15
+ if (level > MAX_ELEVATION) {
16
+ context.report({
17
+ messageId: 'overflow',
18
+ data: {
19
+ level: String(level),
20
+ max: String(MAX_ELEVATION)
21
+ },
22
+ node
23
+ });
24
+ }
25
+ }
26
+ }
27
+
28
+ // ------------------------------------------------------------------------------
29
+ // Rule Definition
30
+ // ------------------------------------------------------------------------------
31
+
32
+ module.exports = {
33
+ meta: {
34
+ docs: {
35
+ description: 'Disallow elevation classes above the MD3 maximum (0–5).',
36
+ category: 'recommended'
37
+ },
38
+ fixable: null,
39
+ schema: [],
40
+ messages: {
41
+ overflow: 'Elevation level {{ level }} exceeds the MD3 maximum of {{ max }}.'
42
+ }
43
+ },
44
+ create(context) {
45
+ if (!isVueTemplate(context)) return {};
46
+ return context.sourceCode.parserServices.defineTemplateBodyVisitor({
47
+ VAttribute(attr) {
48
+ // Static class="elevation-10"
49
+ if (!attr.directive && attr.key.rawName === 'class' && attr.value) {
50
+ checkClassValue(context, attr, attr.value.value);
51
+ return;
52
+ }
53
+
54
+ // :class="'elevation-10'" or :class="`elevation-${n}`" — check literal strings
55
+ if (attr.directive && attr.key.name.name === 'bind' && attr.key.argument?.rawName === 'class' && attr.value?.expression?.type === 'Literal' && typeof attr.value.expression.value === 'string') {
56
+ checkClassValue(context, attr, attr.value.expression.value);
57
+ return;
58
+ }
59
+
60
+ // :elevation="10" or elevation="10" on any component
61
+ const propName = attr.directive ? attr.key.argument?.rawName : attr.key.rawName;
62
+ if (propName !== 'elevation') return;
63
+ const value = attr.directive ? attr.value?.expression?.type === 'Literal' ? attr.value.expression.value : null : attr.value ? Number(attr.value.value) : null;
64
+ if (typeof value === 'number' && value > MAX_ELEVATION) {
65
+ context.report({
66
+ messageId: 'overflow',
67
+ data: {
68
+ level: String(value),
69
+ max: String(MAX_ELEVATION)
70
+ },
71
+ node: attr
72
+ });
73
+ }
74
+ }
75
+ });
76
+ }
77
+ };
@@ -0,0 +1,58 @@
1
+ 'use strict';
2
+
3
+ const {
4
+ isVueTemplate
5
+ } = require('../util/helpers');
6
+ const {
7
+ addClass,
8
+ removeAttr
9
+ } = require('../util/fixers');
10
+ const validElevations = new Set(['0', '1', '2', '3', '4', '5']);
11
+ module.exports = {
12
+ meta: {
13
+ docs: {
14
+ description: 'Disallow the `elevation` prop; use Tailwind shadow utilities instead.',
15
+ category: 'tailwindcss'
16
+ },
17
+ fixable: 'code',
18
+ schema: [],
19
+ messages: {
20
+ replacedWith: `'elevation="{{ value }}"' should be replaced with class="{{ className }}"`,
21
+ noFix: `'elevation' prop should be replaced with a Tailwind shadow utility class`
22
+ }
23
+ },
24
+ create(context) {
25
+ if (!isVueTemplate(context)) return {};
26
+ return context.sourceCode.parserServices.defineTemplateBodyVisitor({
27
+ VAttribute(attr) {
28
+ if (attr.directive && (attr.key.name.name !== 'bind' || !attr.key.argument)) return;
29
+ const propName = attr.directive ? attr.key.argument.rawName : attr.key.rawName;
30
+ if (propName !== 'elevation') return;
31
+ const element = attr.parent.parent;
32
+ const propNameNode = attr.directive ? attr.key.argument : attr.key;
33
+
34
+ // Get static value
35
+ const value = attr.directive ? attr.value?.expression?.type === 'Literal' ? String(attr.value.expression.value) : null : attr.value?.value;
36
+ if (value != null && validElevations.has(value)) {
37
+ const className = `elevation-${value}`;
38
+ context.report({
39
+ messageId: 'replacedWith',
40
+ data: {
41
+ value,
42
+ className
43
+ },
44
+ node: propNameNode,
45
+ fix(fixer) {
46
+ return [addClass(context, fixer, element, className), removeAttr(context, fixer, attr)];
47
+ }
48
+ });
49
+ } else {
50
+ context.report({
51
+ messageId: 'noFix',
52
+ node: propNameNode
53
+ });
54
+ }
55
+ }
56
+ });
57
+ }
58
+ };
@@ -0,0 +1,141 @@
1
+ 'use strict';
2
+
3
+ const {
4
+ hyphenate,
5
+ classify,
6
+ isVueTemplate
7
+ } = require('../util/helpers');
8
+ const {
9
+ addClass,
10
+ removeAttr
11
+ } = require('../util/fixers');
12
+ const breakpoints = ['sm', 'md', 'lg', 'xl', 'xxl'];
13
+
14
+ // VRow props that should become utility classes
15
+ const rowPropToClass = {
16
+ align: value => `align-${value}`,
17
+ justify: value => `justify-${value}`,
18
+ 'align-content': value => `align-content-${value}`
19
+ };
20
+
21
+ // Generate responsive variants: align-sm, align-md, justify-sm, etc.
22
+ for (const [prop, fn] of Object.entries({
23
+ ...rowPropToClass
24
+ })) {
25
+ for (const bp of breakpoints) {
26
+ rowPropToClass[`${prop}-${bp}`] = value => `${fn(value).replace(value, '')}-${bp}-${value}`;
27
+ }
28
+ }
29
+ // Fix responsive class generation — the base fn closures captured value incorrectly, redo properly
30
+ for (const bp of breakpoints) {
31
+ rowPropToClass[`align-${bp}`] = value => `align-${bp}-${value}`;
32
+ rowPropToClass[`justify-${bp}`] = value => `justify-${bp}-${value}`;
33
+ rowPropToClass[`align-content-${bp}`] = value => `align-content-${bp}-${value}`;
34
+ }
35
+
36
+ // VCol props that should become utility classes
37
+ const colPropToClass = {
38
+ order: value => `order-${value}`,
39
+ 'align-self': value => `align-self-${value}`
40
+ };
41
+ for (const bp of breakpoints) {
42
+ colPropToClass[`order-${bp}`] = value => `order-${bp}-${value}`;
43
+ }
44
+ const replacements = {
45
+ VRow: {
46
+ props: rowPropToClass,
47
+ renamed: {
48
+ dense: {
49
+ name: 'density',
50
+ value: 'compact'
51
+ }
52
+ }
53
+ },
54
+ VCol: {
55
+ props: colPropToClass,
56
+ renamed: {}
57
+ }
58
+ };
59
+
60
+ // ------------------------------------------------------------------------------
61
+ // Rule Definition
62
+ // ------------------------------------------------------------------------------
63
+
64
+ module.exports = {
65
+ meta: {
66
+ docs: {
67
+ description: 'Prevent the use of removed grid props.',
68
+ category: 'recommended'
69
+ },
70
+ fixable: 'code',
71
+ schema: [],
72
+ messages: {
73
+ movedToClass: `'{{ prop }}' has been removed, use class="{{ className }}" instead`,
74
+ replacedWith: `'{{ a }}' has been replaced with '{{ b }}'`
75
+ }
76
+ },
77
+ create(context) {
78
+ if (!isVueTemplate(context)) return {};
79
+ return context.sourceCode.parserServices.defineTemplateBodyVisitor({
80
+ VAttribute(attr) {
81
+ if (attr.directive && (attr.key.name.name !== 'bind' || !attr.key.argument)) return;
82
+ const tag = classify(attr.parent.parent.rawName);
83
+ const config = replacements[tag];
84
+ if (!config) return;
85
+ const propName = attr.directive ? hyphenate(attr.key.argument.rawName) : hyphenate(attr.key.rawName);
86
+ const propNameNode = attr.directive ? attr.key.argument : attr.key;
87
+ const element = attr.parent.parent;
88
+
89
+ // Check for props that should become utility classes
90
+ if (config.props[propName]) {
91
+ const toClass = config.props[propName];
92
+
93
+ // Need a static value to auto-fix
94
+ const value = attr.directive ? attr.value?.expression?.type === 'Literal' ? String(attr.value.expression.value) : null : attr.value?.value;
95
+ if (value) {
96
+ const className = toClass(value);
97
+ context.report({
98
+ messageId: 'movedToClass',
99
+ data: {
100
+ prop: propName,
101
+ className
102
+ },
103
+ node: propNameNode,
104
+ fix(fixer) {
105
+ return [addClass(context, fixer, element, className), removeAttr(context, fixer, attr)];
106
+ }
107
+ });
108
+ } else {
109
+ // Dynamic value — can't auto-fix but still report
110
+ const className = toClass('<value>');
111
+ context.report({
112
+ messageId: 'movedToClass',
113
+ data: {
114
+ prop: propName,
115
+ className
116
+ },
117
+ node: propNameNode
118
+ });
119
+ }
120
+ return;
121
+ }
122
+
123
+ // Check for renamed props (e.g. dense -> density="compact")
124
+ const renamed = config.renamed[propName];
125
+ if (renamed) {
126
+ context.report({
127
+ messageId: 'replacedWith',
128
+ data: {
129
+ a: propName,
130
+ b: `${renamed.name}="${renamed.value}"`
131
+ },
132
+ node: propNameNode,
133
+ fix(fixer) {
134
+ return fixer.replaceText(attr, `${renamed.name}="${renamed.value}"`);
135
+ }
136
+ });
137
+ }
138
+ }
139
+ });
140
+ }
141
+ };
@@ -0,0 +1,111 @@
1
+ 'use strict';
2
+
3
+ const {
4
+ isVueTemplate
5
+ } = require('../util/helpers');
6
+ const defaultReplacements = {
7
+ 'd-flex': 'flex',
8
+ 'd-inline-flex': 'inline-flex',
9
+ 'd-block': 'block',
10
+ 'd-inline-block': 'inline-block',
11
+ 'd-inline': 'inline',
12
+ 'd-none': 'hidden',
13
+ 'd-grid': 'grid',
14
+ 'align-center': 'items-center',
15
+ 'align-start': 'items-start',
16
+ 'align-end': 'items-end',
17
+ 'align-baseline': 'items-baseline',
18
+ 'align-stretch': 'items-stretch',
19
+ 'justify-space-between': 'justify-between',
20
+ 'justify-space-around': 'justify-around',
21
+ 'justify-space-evenly': 'justify-evenly',
22
+ 'flex-grow-1': 'grow',
23
+ 'flex-grow-0': 'grow-0',
24
+ 'flex-shrink-1': 'shrink',
25
+ 'flex-shrink-0': 'shrink-0',
26
+ 'flex-column': 'flex-col',
27
+ 'font-weight-bold': 'font-bold',
28
+ 'font-weight-medium': 'font-medium',
29
+ 'font-weight-regular': 'font-normal',
30
+ 'font-weight-light': 'font-light',
31
+ 'font-weight-thin': 'font-thin',
32
+ 'text-truncate': 'truncate',
33
+ 'text-no-wrap': 'whitespace-nowrap',
34
+ 'fill-height': 'h-full',
35
+ 'w-100': 'w-full',
36
+ 'h-100': 'h-full'
37
+ };
38
+
39
+ // Generate spacing utility mappings only for prefixes that differ between Vuetify and Tailwind
40
+ // ma → m, pa → p (all other prefixes like mx, mt, px, pt etc. are already identical)
41
+ for (let i = 0; i <= 16; i++) {
42
+ defaultReplacements[`ma-${i}`] = `m-${i}`;
43
+ defaultReplacements[`pa-${i}`] = `p-${i}`;
44
+ }
45
+ defaultReplacements['ma-auto'] = 'm-auto';
46
+
47
+ // Negative margins: ma-n1..ma-n16 → -m-1..-m-16
48
+ for (let i = 1; i <= 16; i++) {
49
+ defaultReplacements[`ma-n${i}`] = `-m-${i}`;
50
+ }
51
+ module.exports = {
52
+ defaultReplacements,
53
+ meta: {
54
+ docs: {
55
+ description: 'Disallow Vuetify utility classes; use Tailwind equivalents instead.',
56
+ category: 'tailwindcss'
57
+ },
58
+ fixable: 'code',
59
+ schema: [{
60
+ type: 'object',
61
+ additionalProperties: {
62
+ oneOf: [{
63
+ type: 'string'
64
+ }, {
65
+ type: 'boolean',
66
+ enum: [false]
67
+ }]
68
+ }
69
+ }],
70
+ messages: {
71
+ replacedWith: `'{{ a }}' has been replaced with '{{ b }}'`
72
+ }
73
+ },
74
+ create(context) {
75
+ if (!isVueTemplate(context)) return {};
76
+ const replacements = {
77
+ ...defaultReplacements,
78
+ ...(context.options[0] || {})
79
+ };
80
+ for (const key of Object.keys(replacements)) {
81
+ if (replacements[key] === false) delete replacements[key];
82
+ }
83
+ return context.sourceCode.parserServices.defineTemplateBodyVisitor({
84
+ 'VAttribute[key.name="class"]'(node) {
85
+ if (!node.value || !node.value.value) return;
86
+ const classes = node.value.value.split(/\s+/).filter(Boolean);
87
+ classes.forEach(className => {
88
+ const replace = replacements[className];
89
+ if (replace == null) return;
90
+ const idx = node.value.value.indexOf(className) + 1;
91
+ const range = [node.value.range[0] + idx, node.value.range[0] + idx + className.length];
92
+ const loc = {
93
+ start: context.sourceCode.getLocFromIndex(range[0]),
94
+ end: context.sourceCode.getLocFromIndex(range[1])
95
+ };
96
+ context.report({
97
+ loc,
98
+ messageId: 'replacedWith',
99
+ data: {
100
+ a: className,
101
+ b: replace
102
+ },
103
+ fix(fixer) {
104
+ return fixer.replaceTextRange(range, replace);
105
+ }
106
+ });
107
+ });
108
+ }
109
+ });
110
+ }
111
+ };
@@ -0,0 +1,84 @@
1
+ 'use strict';
2
+
3
+ const {
4
+ isVueTemplate
5
+ } = require('../util/helpers');
6
+ const {
7
+ addClass,
8
+ removeAttr
9
+ } = require('../util/fixers');
10
+ const roundedMap = {
11
+ '': 'rounded',
12
+ 0: 'rounded-none',
13
+ sm: 'rounded-sm',
14
+ lg: 'rounded-lg',
15
+ xl: 'rounded-xl',
16
+ circle: 'rounded-full',
17
+ pill: 'rounded-full',
18
+ shaped: 'rounded-te-xl rounded-bs-xl'
19
+ };
20
+ module.exports = {
21
+ meta: {
22
+ docs: {
23
+ description: 'Disallow the `rounded` prop; use Tailwind rounded utilities instead.',
24
+ category: 'tailwindcss'
25
+ },
26
+ fixable: 'code',
27
+ schema: [],
28
+ messages: {
29
+ replacedWith: `'rounded{{ valueDisplay }}' should be replaced with class="{{ className }}"`,
30
+ noFix: `'rounded' prop should be replaced with a Tailwind rounded utility class`
31
+ }
32
+ },
33
+ create(context) {
34
+ if (!isVueTemplate(context)) return {};
35
+ return context.sourceCode.parserServices.defineTemplateBodyVisitor({
36
+ VAttribute(attr) {
37
+ if (attr.directive && (attr.key.name.name !== 'bind' || !attr.key.argument)) return;
38
+ const propName = attr.directive ? attr.key.argument.rawName : attr.key.rawName;
39
+ if (propName !== 'rounded') return;
40
+ const element = attr.parent.parent;
41
+ const propNameNode = attr.directive ? attr.key.argument : attr.key;
42
+
43
+ // Boolean attribute (no value) — `rounded` with no `="..."`
44
+ if (!attr.directive && !attr.value) {
45
+ const className = roundedMap[''];
46
+ context.report({
47
+ messageId: 'replacedWith',
48
+ data: {
49
+ valueDisplay: '',
50
+ className
51
+ },
52
+ node: propNameNode,
53
+ fix(fixer) {
54
+ return [addClass(context, fixer, element, className), removeAttr(context, fixer, attr)];
55
+ }
56
+ });
57
+ return;
58
+ }
59
+
60
+ // Get static value
61
+ const value = attr.directive ? attr.value?.expression?.type === 'Literal' ? String(attr.value.expression.value) : null : attr.value?.value;
62
+ if (value != null && roundedMap[value] != null) {
63
+ const className = roundedMap[value];
64
+ context.report({
65
+ messageId: 'replacedWith',
66
+ data: {
67
+ valueDisplay: `="${value}"`,
68
+ className
69
+ },
70
+ node: propNameNode,
71
+ fix(fixer) {
72
+ return [addClass(context, fixer, element, className), removeAttr(context, fixer, attr)];
73
+ }
74
+ });
75
+ } else {
76
+ context.report({
77
+ messageId: 'noFix',
78
+ node: propNameNode
79
+ });
80
+ }
81
+ }
82
+ });
83
+ }
84
+ };
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const path = require('node:path');
4
- function hyphenate(/* istanbul ignore next */
4
+ function hyphenate( /* istanbul ignore next */
5
5
  str = '') {
6
6
  return str.replace(/\B([A-Z])/g, '-$1').toLowerCase();
7
7
  }
@@ -44,7 +44,8 @@ function mergeDeep(source, target) {
44
44
  }
45
45
  function isVueTemplate(context) {
46
46
  if (context.sourceCode.parserServices.defineTemplateBodyVisitor == null) {
47
- return path.extname(context.getFilename()) === '.vue';
47
+ const filename = context.filename ?? context.getFilename?.() ?? '';
48
+ return path.extname(filename) === '.vue';
48
49
  }
49
50
  return true;
50
51
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-vuetify",
3
- "version": "2.5.2",
3
+ "version": "2.6.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,34 +9,39 @@
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
+ "test:8": "cross-env ESLINT8=true mocha tests --recursive --reporter dot",
13
+ "test:9": "cross-env ESLINT9=true mocha tests --recursive --reporter dot",
13
14
  "test:coverage": "nyc mocha tests --recursive --reporter dot",
14
15
  "test:ci": "nyc --reporter=lcov mocha tests --recursive --reporter dot",
15
16
  "lint": "eslint src tests",
16
- "prepublishOnly": "npm run build"
17
+ "prepublishOnly": "npm run build",
18
+ "release": "bumpp -r"
17
19
  },
18
20
  "files": [
19
21
  "lib"
20
22
  ],
21
23
  "homepage": "https://github.com/vuetifyjs/eslint-plugin-vuetify#readme",
22
24
  "dependencies": {
23
- "eslint-plugin-vue": ">=9.6.0",
25
+ "eslint-plugin-vue": "^10.8.0",
24
26
  "requireindex": "^1.2.0"
25
27
  },
26
28
  "devDependencies": {
27
29
  "@babel/cli": "^7.19.3",
28
30
  "@babel/core": "^7.19.6",
29
31
  "@babel/preset-env": "^7.19.4",
30
- "@stylistic/eslint-plugin": "^2.10.1",
32
+ "@stylistic/eslint-plugin": "^4.4.1",
33
+ "bumpp": "^10.1.0",
31
34
  "conventional-changelog-cli": "^2.2.2",
32
- "conventional-changelog-vuetify": "^1.1.0",
35
+ "cross-env": "^7.0.3",
36
+ "conventional-changelog-vuetify": "^2.0.2",
33
37
  "conventional-github-releaser": "^3.1.5",
34
- "eslint": "^9.22.0",
35
- "eslint8": "npm:eslint@8.57.1",
38
+ "eslint": "^10.0.0",
36
39
  "eslint-plugin-vue": "^10.0.0",
40
+ "eslint8": "npm:eslint@8.57.1",
41
+ "eslint9": "npm:eslint@9",
37
42
  "husky": "^8.0.1",
38
43
  "mocha": "^10.1.0",
39
- "neostandard": "^0.11.8",
44
+ "neostandard": "^0.13.0",
40
45
  "nyc": "^15.1.0",
41
46
  "rimraf": "^3.0.2",
42
47
  "vue": "^3.5.13",
@@ -44,8 +49,18 @@
44
49
  "vuetify": "^3.7.17"
45
50
  },
46
51
  "peerDependencies": {
47
- "eslint": "^8.0.0 || ^9.0.0",
52
+ "eslint": "^8.0.0 || ^9.0.0 || ^10.0.0",
48
53
  "vuetify": "^3.0.0"
49
54
  },
50
- "packageManager": "pnpm@9.13.2"
55
+ "packageManager": "pnpm@10.26.1",
56
+ "pnpm": {
57
+ "overrides": {
58
+ "@stylistic/eslint-plugin": "$@stylistic/eslint-plugin"
59
+ },
60
+ "peerDependencyRules": {
61
+ "allowedVersions": {
62
+ "eslint": "10"
63
+ }
64
+ }
65
+ }
51
66
  }