eslint-plugin-vuetify 2.6.0 → 2.7.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 +15 -1
- package/lib/eslint-typegen.d.ts +136 -0
- package/lib/index.d.ts +20 -0
- package/lib/rules/custom-deprecated-components.js +111 -0
- package/lib/rules/custom-deprecated-events.js +103 -0
- package/lib/rules/custom-deprecated-props.js +100 -0
- package/lib/rules/custom-deprecated-slots.js +100 -0
- package/lib/rules/grid-unknown-attributes.js +1 -1
- package/lib/util/grid-attributes.js +3 -1
- package/package.json +8 -15
package/README.md
CHANGED
|
@@ -31,6 +31,9 @@ Use [eslint-plugin-vuetify@vuetify-2](https://www.npmjs.com/package/eslint-plugi
|
|
|
31
31
|
|
|
32
32
|
## Vuetify 4 Migration
|
|
33
33
|
|
|
34
|
+
> [!IMPORTANT]
|
|
35
|
+
> We suggest running [vuetify-codemods](https://www.npmjs.com/package/vuetify-codemods) first to automatically apply most migration fixes
|
|
36
|
+
|
|
34
37
|
This plugin includes four new rules for migrating from Vuetify v3 to v4:
|
|
35
38
|
|
|
36
39
|
- **`no-deprecated-typography`** — replaces MD2 typography classes (`text-h1`) with MD3 equivalents (`text-display-large`)
|
|
@@ -147,6 +150,14 @@ These rules help migrate from Vuetify v3 to v4. They are included in the `recomm
|
|
|
147
150
|
- Disallow elevation classes above the MD3 maximum ([`no-elevation-overflow`])
|
|
148
151
|
- Disallow deprecated props and slots on snackbar components ([`no-deprecated-snackbar`])
|
|
149
152
|
|
|
153
|
+
### Custom deprecations
|
|
154
|
+
|
|
155
|
+
User-configurable rules for deprecating components, props, events, and slots - helpful to enforce standardization.
|
|
156
|
+
|
|
157
|
+
- Disallow usage of specified components, with optional replacements ([`custom-deprecated-components`])
|
|
158
|
+
- Disallow usage of specified component props ([`custom-deprecated-props`])
|
|
159
|
+
- Disallow usage of specified component events ([`custom-deprecated-events`])
|
|
160
|
+
- Disallow usage of specified component slots ([`custom-deprecated-slots`])
|
|
150
161
|
|
|
151
162
|
[`grid-unknown-attributes`]: ./docs/rules/grid-unknown-attributes.md
|
|
152
163
|
[`no-deprecated-components`]: ./docs/rules/no-deprecated-components.md
|
|
@@ -157,10 +168,13 @@ These rules help migrate from Vuetify v3 to v4. They are included in the `recomm
|
|
|
157
168
|
[`no-deprecated-slots`]: ./docs/rules/no-deprecated-slots.md
|
|
158
169
|
[`no-deprecated-imports`]: ./docs/rules/no-deprecated-imports.md
|
|
159
170
|
[`icon-button-variant`]: ./docs/rules/icon-button-variant.md
|
|
171
|
+
[`custom-deprecated-components`]: ./docs/rules/custom-deprecated-components.md
|
|
172
|
+
[`custom-deprecated-props`]: ./docs/rules/custom-deprecated-props.md
|
|
173
|
+
[`custom-deprecated-events`]: ./docs/rules/custom-deprecated-events.md
|
|
174
|
+
[`custom-deprecated-slots`]: ./docs/rules/custom-deprecated-slots.md
|
|
160
175
|
[`no-deprecated-typography`]: ./docs/rules/no-deprecated-typography.md
|
|
161
176
|
[`no-legacy-grid-props`]: ./docs/rules/no-legacy-grid-props.md
|
|
162
177
|
[`no-elevation-overflow`]: ./docs/rules/no-elevation-overflow.md
|
|
163
|
-
[`no-deprecated-snackbar`]: ./docs/rules/no-deprecated-snackbar.md
|
|
164
178
|
|
|
165
179
|
|
|
166
180
|
## 💪 Supporting Vuetify
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
/* prettier-ignore */
|
|
3
|
+
import type { Linter } from 'eslint'
|
|
4
|
+
|
|
5
|
+
export interface RuleOptions {
|
|
6
|
+
/**
|
|
7
|
+
* Disallow usage of specified components, with optional replacements
|
|
8
|
+
*/
|
|
9
|
+
'vuetify/custom-deprecated-components'?: Linter.RuleEntry<VuetifyCustomDeprecatedComponents>
|
|
10
|
+
/**
|
|
11
|
+
* Disallow usage of specified component events, with optional replacements
|
|
12
|
+
*/
|
|
13
|
+
'vuetify/custom-deprecated-events'?: Linter.RuleEntry<VuetifyCustomDeprecatedEvents>
|
|
14
|
+
/**
|
|
15
|
+
* Disallow usage of specified component props, with optional replacements
|
|
16
|
+
*/
|
|
17
|
+
'vuetify/custom-deprecated-props'?: Linter.RuleEntry<VuetifyCustomDeprecatedProps>
|
|
18
|
+
/**
|
|
19
|
+
* Disallow usage of specified component slots, with optional replacements
|
|
20
|
+
*/
|
|
21
|
+
'vuetify/custom-deprecated-slots'?: Linter.RuleEntry<VuetifyCustomDeprecatedSlots>
|
|
22
|
+
/**
|
|
23
|
+
* Warn about v1 grid attributes not being auto-converted to classes in v2.
|
|
24
|
+
*/
|
|
25
|
+
'vuetify/grid-unknown-attributes'?: Linter.RuleEntry<[]>
|
|
26
|
+
/**
|
|
27
|
+
* Ensure icon buttons have a variant defined.
|
|
28
|
+
*/
|
|
29
|
+
'vuetify/icon-button-variant'?: Linter.RuleEntry<VuetifyIconButtonVariant>
|
|
30
|
+
/**
|
|
31
|
+
* Disallow the `border` prop; use Tailwind border utilities instead.
|
|
32
|
+
*/
|
|
33
|
+
'vuetify/no-border-prop'?: Linter.RuleEntry<[]>
|
|
34
|
+
/**
|
|
35
|
+
* Disallow the use of classes that have been removed from Vuetify
|
|
36
|
+
*/
|
|
37
|
+
'vuetify/no-deprecated-classes'?: Linter.RuleEntry<[]>
|
|
38
|
+
/**
|
|
39
|
+
* Disallow the use of classes that have been removed from Vuetify
|
|
40
|
+
*/
|
|
41
|
+
'vuetify/no-deprecated-colors'?: Linter.RuleEntry<VuetifyNoDeprecatedColors>
|
|
42
|
+
/**
|
|
43
|
+
* Prevent the use of components that have been removed from Vuetify
|
|
44
|
+
*/
|
|
45
|
+
'vuetify/no-deprecated-components'?: Linter.RuleEntry<[]>
|
|
46
|
+
/**
|
|
47
|
+
* Prevent the use of removed and deprecated events.
|
|
48
|
+
*/
|
|
49
|
+
'vuetify/no-deprecated-events'?: Linter.RuleEntry<[]>
|
|
50
|
+
/**
|
|
51
|
+
* disallow import from "vuetify/lib/util/colors", suggest "vuetify/util/colors" instead
|
|
52
|
+
*/
|
|
53
|
+
'vuetify/no-deprecated-imports'?: Linter.RuleEntry<[]>
|
|
54
|
+
/**
|
|
55
|
+
* Prevent the use of removed and deprecated props.
|
|
56
|
+
*/
|
|
57
|
+
'vuetify/no-deprecated-props'?: Linter.RuleEntry<[]>
|
|
58
|
+
/**
|
|
59
|
+
* Prevent the use of removed and deprecated slots.
|
|
60
|
+
*/
|
|
61
|
+
'vuetify/no-deprecated-slots'?: Linter.RuleEntry<[]>
|
|
62
|
+
/**
|
|
63
|
+
* Disallow deprecated props and slots on Vuetify snackbar components.
|
|
64
|
+
*/
|
|
65
|
+
'vuetify/no-deprecated-snackbar'?: Linter.RuleEntry<[]>
|
|
66
|
+
/**
|
|
67
|
+
* Disallow deprecated MD2 typography classes, with configurable replacements.
|
|
68
|
+
*/
|
|
69
|
+
'vuetify/no-deprecated-typography'?: Linter.RuleEntry<VuetifyNoDeprecatedTypography>
|
|
70
|
+
/**
|
|
71
|
+
* Disallow elevation classes above the MD3 maximum (0–5).
|
|
72
|
+
*/
|
|
73
|
+
'vuetify/no-elevation-overflow'?: Linter.RuleEntry<[]>
|
|
74
|
+
/**
|
|
75
|
+
* Disallow the `elevation` prop; use Tailwind shadow utilities instead.
|
|
76
|
+
*/
|
|
77
|
+
'vuetify/no-elevation-prop'?: Linter.RuleEntry<[]>
|
|
78
|
+
/**
|
|
79
|
+
* Prevent the use of removed grid props.
|
|
80
|
+
*/
|
|
81
|
+
'vuetify/no-legacy-grid-props'?: Linter.RuleEntry<[]>
|
|
82
|
+
/**
|
|
83
|
+
* Disallow Vuetify utility classes; use Tailwind equivalents instead.
|
|
84
|
+
*/
|
|
85
|
+
'vuetify/no-legacy-utilities'?: Linter.RuleEntry<VuetifyNoLegacyUtilities>
|
|
86
|
+
/**
|
|
87
|
+
* Disallow the `rounded` prop; use Tailwind rounded utilities instead.
|
|
88
|
+
*/
|
|
89
|
+
'vuetify/no-rounded-prop'?: Linter.RuleEntry<[]>
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/* ======= Declarations ======= */
|
|
93
|
+
// ----- vuetify/custom-deprecated-components -----
|
|
94
|
+
type VuetifyCustomDeprecatedComponents = []|[{
|
|
95
|
+
[k: string]: (string | false | {
|
|
96
|
+
message: string
|
|
97
|
+
}) | undefined
|
|
98
|
+
}]
|
|
99
|
+
// ----- vuetify/custom-deprecated-events -----
|
|
100
|
+
type VuetifyCustomDeprecatedEvents = []|[{
|
|
101
|
+
[k: string]: {
|
|
102
|
+
[k: string]: (string | false | {
|
|
103
|
+
message: string
|
|
104
|
+
}) | undefined
|
|
105
|
+
} | undefined
|
|
106
|
+
}]
|
|
107
|
+
// ----- vuetify/custom-deprecated-props -----
|
|
108
|
+
type VuetifyCustomDeprecatedProps = []|[{
|
|
109
|
+
[k: string]: {
|
|
110
|
+
[k: string]: (string | false | {
|
|
111
|
+
message: string
|
|
112
|
+
}) | undefined
|
|
113
|
+
} | undefined
|
|
114
|
+
}]
|
|
115
|
+
// ----- vuetify/custom-deprecated-slots -----
|
|
116
|
+
type VuetifyCustomDeprecatedSlots = []|[{
|
|
117
|
+
[k: string]: {
|
|
118
|
+
[k: string]: (string | false | {
|
|
119
|
+
message: string
|
|
120
|
+
}) | undefined
|
|
121
|
+
} | undefined
|
|
122
|
+
}]
|
|
123
|
+
// ----- vuetify/icon-button-variant -----
|
|
124
|
+
type VuetifyIconButtonVariant = []|[string]
|
|
125
|
+
// ----- vuetify/no-deprecated-colors -----
|
|
126
|
+
type VuetifyNoDeprecatedColors = []|[{
|
|
127
|
+
themeColors?: string[]
|
|
128
|
+
}]
|
|
129
|
+
// ----- vuetify/no-deprecated-typography -----
|
|
130
|
+
type VuetifyNoDeprecatedTypography = []|[{
|
|
131
|
+
[k: string]: (string | false) | undefined
|
|
132
|
+
}]
|
|
133
|
+
// ----- vuetify/no-legacy-utilities -----
|
|
134
|
+
type VuetifyNoLegacyUtilities = []|[{
|
|
135
|
+
[k: string]: (string | false) | undefined
|
|
136
|
+
}]
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/// <reference path="./eslint-typegen.d.ts" />
|
|
2
|
+
import type { Linter } from 'eslint'
|
|
3
|
+
|
|
4
|
+
declare const vuetify: {
|
|
5
|
+
configs: {
|
|
6
|
+
base: Linter.LegacyConfig
|
|
7
|
+
recommended: Linter.LegacyConfig
|
|
8
|
+
'recommended-v4': Linter.LegacyConfig
|
|
9
|
+
|
|
10
|
+
'flat/base': Linter.FlatConfig[]
|
|
11
|
+
'flat/recommended': Linter.FlatConfig[]
|
|
12
|
+
'flat/recommended-v4': Linter.FlatConfig[]
|
|
13
|
+
|
|
14
|
+
tailwindcss: Linter.LegacyConfig
|
|
15
|
+
'flat/tailwindcss': Linter.FlatConfig[]
|
|
16
|
+
}
|
|
17
|
+
rules: Record<string, any>
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export = vuetify
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
classify,
|
|
5
|
+
hyphenate,
|
|
6
|
+
isVueTemplate
|
|
7
|
+
} = require('../util/helpers');
|
|
8
|
+
module.exports = {
|
|
9
|
+
meta: {
|
|
10
|
+
docs: {
|
|
11
|
+
description: 'Disallow usage of specified components, with optional replacements'
|
|
12
|
+
},
|
|
13
|
+
fixable: 'code',
|
|
14
|
+
schema: [{
|
|
15
|
+
type: 'object',
|
|
16
|
+
additionalProperties: {
|
|
17
|
+
oneOf: [{
|
|
18
|
+
type: 'string'
|
|
19
|
+
}, {
|
|
20
|
+
type: 'boolean',
|
|
21
|
+
enum: [false]
|
|
22
|
+
}, {
|
|
23
|
+
type: 'object',
|
|
24
|
+
properties: {
|
|
25
|
+
message: {
|
|
26
|
+
type: 'string'
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
required: ['message'],
|
|
30
|
+
additionalProperties: false
|
|
31
|
+
}]
|
|
32
|
+
}
|
|
33
|
+
}],
|
|
34
|
+
messages: {
|
|
35
|
+
deprecated: `'{{ name }}' is deprecated`,
|
|
36
|
+
deprecatedWithMessage: `'{{ name }}' is deprecated: {{ message }}`,
|
|
37
|
+
deprecatedWithReplacement: `'{{ name }}' is deprecated, use '<{{ tag }}{{ classAttr }}>' instead`
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
create(context) {
|
|
41
|
+
if (!isVueTemplate(context)) return {};
|
|
42
|
+
const options = context.options[0];
|
|
43
|
+
if (!options || !Object.keys(options).length) return {};
|
|
44
|
+
|
|
45
|
+
// Normalize keys to PascalCase for lookup
|
|
46
|
+
const banned = new Map();
|
|
47
|
+
for (const [key, value] of Object.entries(options)) {
|
|
48
|
+
banned.set(classify(key), value);
|
|
49
|
+
}
|
|
50
|
+
return context.sourceCode.parserServices.defineTemplateBodyVisitor({
|
|
51
|
+
VElement(element) {
|
|
52
|
+
const tag = classify(element.rawName);
|
|
53
|
+
if (!banned.has(tag)) return;
|
|
54
|
+
const replacement = banned.get(tag);
|
|
55
|
+
const tokens = context.sourceCode.parserServices.getTemplateBodyTokenStore();
|
|
56
|
+
if (typeof replacement === 'string') {
|
|
57
|
+
// Parse replacement: tag part and optional classes (dot-separated)
|
|
58
|
+
const parts = replacement.split('.');
|
|
59
|
+
const replacementTag = parts[0];
|
|
60
|
+
const classes = parts.slice(1);
|
|
61
|
+
context.report({
|
|
62
|
+
node: element,
|
|
63
|
+
messageId: 'deprecatedWithReplacement',
|
|
64
|
+
data: {
|
|
65
|
+
name: hyphenate(tag),
|
|
66
|
+
tag: replacementTag,
|
|
67
|
+
classAttr: classes.length ? ` class="${classes.join(' ')}"` : ''
|
|
68
|
+
},
|
|
69
|
+
fix(fixer) {
|
|
70
|
+
const fixes = [];
|
|
71
|
+
const open = tokens.getFirstToken(element.startTag);
|
|
72
|
+
fixes.push(fixer.replaceText(open, `<${replacementTag}`));
|
|
73
|
+
if (element.endTag) {
|
|
74
|
+
const endTagOpen = tokens.getFirstToken(element.endTag);
|
|
75
|
+
fixes.push(fixer.replaceText(endTagOpen, `</${replacementTag}`));
|
|
76
|
+
}
|
|
77
|
+
if (classes.length) {
|
|
78
|
+
const classValue = classes.join(' ');
|
|
79
|
+
const classAttr = element.startTag.attributes.find(attr => !attr.directive && attr.key.rawName === 'class');
|
|
80
|
+
if (classAttr && classAttr.value) {
|
|
81
|
+
const existing = classAttr.value.value;
|
|
82
|
+
fixes.push(fixer.replaceText(classAttr.value, `"${existing} ${classValue}"`));
|
|
83
|
+
} else if (!classAttr) {
|
|
84
|
+
fixes.push(fixer.insertTextAfter(open, ` class="${classValue}"`));
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return fixes;
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
} else if (typeof replacement === 'object' && replacement !== null) {
|
|
91
|
+
context.report({
|
|
92
|
+
node: element,
|
|
93
|
+
messageId: 'deprecatedWithMessage',
|
|
94
|
+
data: {
|
|
95
|
+
name: hyphenate(tag),
|
|
96
|
+
message: replacement.message
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
} else {
|
|
100
|
+
context.report({
|
|
101
|
+
node: element,
|
|
102
|
+
messageId: 'deprecated',
|
|
103
|
+
data: {
|
|
104
|
+
name: hyphenate(tag)
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
};
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
hyphenate,
|
|
5
|
+
classify,
|
|
6
|
+
isVueTemplate
|
|
7
|
+
} = require('../util/helpers');
|
|
8
|
+
module.exports = {
|
|
9
|
+
meta: {
|
|
10
|
+
docs: {
|
|
11
|
+
description: 'Disallow usage of specified component events, with optional replacements'
|
|
12
|
+
},
|
|
13
|
+
fixable: 'code',
|
|
14
|
+
schema: [{
|
|
15
|
+
type: 'object',
|
|
16
|
+
additionalProperties: {
|
|
17
|
+
type: 'object',
|
|
18
|
+
additionalProperties: {
|
|
19
|
+
oneOf: [{
|
|
20
|
+
type: 'string'
|
|
21
|
+
}, {
|
|
22
|
+
type: 'boolean',
|
|
23
|
+
enum: [false]
|
|
24
|
+
}, {
|
|
25
|
+
type: 'object',
|
|
26
|
+
properties: {
|
|
27
|
+
message: {
|
|
28
|
+
type: 'string'
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
required: ['message'],
|
|
32
|
+
additionalProperties: false
|
|
33
|
+
}]
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}],
|
|
37
|
+
messages: {
|
|
38
|
+
replacedWith: `{{ tag }}: @{{ a }} has been replaced with @{{ b }}`,
|
|
39
|
+
removed: `{{ tag }}: @{{ name }} has been removed`,
|
|
40
|
+
removedWithMessage: `{{ tag }}: @{{ name }} has been removed: {{ message }}`
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
create(context) {
|
|
44
|
+
if (!isVueTemplate(context)) return {};
|
|
45
|
+
const options = context.options[0];
|
|
46
|
+
if (!options || !Object.keys(options).length) return {};
|
|
47
|
+
|
|
48
|
+
// Normalize keys to PascalCase
|
|
49
|
+
const replacements = new Map();
|
|
50
|
+
for (const [component, events] of Object.entries(options)) {
|
|
51
|
+
const normalizedEvents = {};
|
|
52
|
+
for (const [event, value] of Object.entries(events)) {
|
|
53
|
+
normalizedEvents[hyphenate(event)] = value;
|
|
54
|
+
}
|
|
55
|
+
replacements.set(classify(component), normalizedEvents);
|
|
56
|
+
}
|
|
57
|
+
return context.sourceCode.parserServices.defineTemplateBodyVisitor({
|
|
58
|
+
VAttribute(attr) {
|
|
59
|
+
if (!(attr.directive && attr.key.name.name === 'on' && attr.key.argument?.type === 'VIdentifier')) return;
|
|
60
|
+
const tag = classify(attr.parent.parent.rawName);
|
|
61
|
+
if (!replacements.has(tag)) return;
|
|
62
|
+
const eventNameNode = attr.key.argument;
|
|
63
|
+
const eventName = hyphenate(eventNameNode.rawName);
|
|
64
|
+
const events = replacements.get(tag);
|
|
65
|
+
const replace = events[eventName];
|
|
66
|
+
if (replace === undefined) return;
|
|
67
|
+
if (replace === false) {
|
|
68
|
+
context.report({
|
|
69
|
+
messageId: 'removed',
|
|
70
|
+
data: {
|
|
71
|
+
tag,
|
|
72
|
+
name: eventName
|
|
73
|
+
},
|
|
74
|
+
node: eventNameNode
|
|
75
|
+
});
|
|
76
|
+
} else if (typeof replace === 'string') {
|
|
77
|
+
context.report({
|
|
78
|
+
messageId: 'replacedWith',
|
|
79
|
+
data: {
|
|
80
|
+
tag,
|
|
81
|
+
a: eventName,
|
|
82
|
+
b: replace
|
|
83
|
+
},
|
|
84
|
+
node: eventNameNode,
|
|
85
|
+
fix(fixer) {
|
|
86
|
+
return fixer.replaceText(eventNameNode, hyphenate(replace));
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
} else if (typeof replace === 'object' && replace !== null) {
|
|
90
|
+
context.report({
|
|
91
|
+
messageId: 'removedWithMessage',
|
|
92
|
+
data: {
|
|
93
|
+
tag,
|
|
94
|
+
name: eventName,
|
|
95
|
+
message: replace.message
|
|
96
|
+
},
|
|
97
|
+
node: eventNameNode
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
};
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
hyphenate,
|
|
5
|
+
classify,
|
|
6
|
+
isVueTemplate
|
|
7
|
+
} = require('../util/helpers');
|
|
8
|
+
module.exports = {
|
|
9
|
+
meta: {
|
|
10
|
+
docs: {
|
|
11
|
+
description: 'Disallow usage of specified component props, with optional replacements'
|
|
12
|
+
},
|
|
13
|
+
fixable: 'code',
|
|
14
|
+
schema: [{
|
|
15
|
+
type: 'object',
|
|
16
|
+
additionalProperties: {
|
|
17
|
+
type: 'object',
|
|
18
|
+
additionalProperties: {
|
|
19
|
+
oneOf: [{
|
|
20
|
+
type: 'string'
|
|
21
|
+
}, {
|
|
22
|
+
type: 'boolean',
|
|
23
|
+
enum: [false]
|
|
24
|
+
}, {
|
|
25
|
+
type: 'object',
|
|
26
|
+
properties: {
|
|
27
|
+
message: {
|
|
28
|
+
type: 'string'
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
required: ['message'],
|
|
32
|
+
additionalProperties: false
|
|
33
|
+
}]
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}],
|
|
37
|
+
messages: {
|
|
38
|
+
replacedWith: `'{{ a }}' has been replaced with '{{ b }}'`,
|
|
39
|
+
removed: `'{{ name }}' has been removed`,
|
|
40
|
+
removedWithMessage: `'{{ name }}' has been removed: {{ message }}`
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
create(context) {
|
|
44
|
+
if (!isVueTemplate(context)) return {};
|
|
45
|
+
const options = context.options[0];
|
|
46
|
+
if (!options || !Object.keys(options).length) return {};
|
|
47
|
+
|
|
48
|
+
// Normalize keys to PascalCase
|
|
49
|
+
const replacements = new Map();
|
|
50
|
+
for (const [component, props] of Object.entries(options)) {
|
|
51
|
+
const normalizedProps = {};
|
|
52
|
+
for (const [prop, value] of Object.entries(props)) {
|
|
53
|
+
normalizedProps[hyphenate(prop)] = value;
|
|
54
|
+
}
|
|
55
|
+
replacements.set(classify(component), normalizedProps);
|
|
56
|
+
}
|
|
57
|
+
return context.sourceCode.parserServices.defineTemplateBodyVisitor({
|
|
58
|
+
VAttribute(attr) {
|
|
59
|
+
if (attr.directive && (attr.key.name.name !== 'bind' || !attr.key.argument)) return;
|
|
60
|
+
const tag = classify(attr.parent.parent.rawName);
|
|
61
|
+
if (!replacements.has(tag)) return;
|
|
62
|
+
const propName = attr.directive ? hyphenate(attr.key.argument.rawName) : hyphenate(attr.key.rawName);
|
|
63
|
+
const propNameNode = attr.directive ? attr.key.argument : attr.key;
|
|
64
|
+
const props = replacements.get(tag);
|
|
65
|
+
const replace = props[propName];
|
|
66
|
+
if (replace === undefined) return;
|
|
67
|
+
if (replace === false) {
|
|
68
|
+
context.report({
|
|
69
|
+
messageId: 'removed',
|
|
70
|
+
data: {
|
|
71
|
+
name: propName
|
|
72
|
+
},
|
|
73
|
+
node: propNameNode
|
|
74
|
+
});
|
|
75
|
+
} else if (typeof replace === 'string') {
|
|
76
|
+
context.report({
|
|
77
|
+
messageId: 'replacedWith',
|
|
78
|
+
data: {
|
|
79
|
+
a: propName,
|
|
80
|
+
b: replace
|
|
81
|
+
},
|
|
82
|
+
node: propNameNode,
|
|
83
|
+
fix(fixer) {
|
|
84
|
+
return fixer.replaceText(propNameNode, replace);
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
} else if (typeof replace === 'object' && replace !== null) {
|
|
88
|
+
context.report({
|
|
89
|
+
messageId: 'removedWithMessage',
|
|
90
|
+
data: {
|
|
91
|
+
name: propName,
|
|
92
|
+
message: replace.message
|
|
93
|
+
},
|
|
94
|
+
node: propNameNode
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
};
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
classify,
|
|
5
|
+
isVueTemplate
|
|
6
|
+
} = require('../util/helpers');
|
|
7
|
+
module.exports = {
|
|
8
|
+
meta: {
|
|
9
|
+
docs: {
|
|
10
|
+
description: 'Disallow usage of specified component slots, with optional replacements'
|
|
11
|
+
},
|
|
12
|
+
fixable: 'code',
|
|
13
|
+
schema: [{
|
|
14
|
+
type: 'object',
|
|
15
|
+
additionalProperties: {
|
|
16
|
+
type: 'object',
|
|
17
|
+
additionalProperties: {
|
|
18
|
+
oneOf: [{
|
|
19
|
+
type: 'string'
|
|
20
|
+
}, {
|
|
21
|
+
type: 'boolean',
|
|
22
|
+
enum: [false]
|
|
23
|
+
}, {
|
|
24
|
+
type: 'object',
|
|
25
|
+
properties: {
|
|
26
|
+
message: {
|
|
27
|
+
type: 'string'
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
required: ['message'],
|
|
31
|
+
additionalProperties: false
|
|
32
|
+
}]
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}],
|
|
36
|
+
messages: {
|
|
37
|
+
replacedWith: `{{ component }}'s '{{ slot }}' slot has been replaced with '{{ newSlot }}'`,
|
|
38
|
+
removed: `{{ component }}'s '{{ slot }}' slot has been removed`,
|
|
39
|
+
removedWithMessage: `{{ component }}'s '{{ slot }}' slot has been removed: {{ message }}`
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
create(context) {
|
|
43
|
+
if (!isVueTemplate(context)) return {};
|
|
44
|
+
const options = context.options[0];
|
|
45
|
+
if (!options || !Object.keys(options).length) return {};
|
|
46
|
+
|
|
47
|
+
// Normalize keys to PascalCase
|
|
48
|
+
const replacements = new Map();
|
|
49
|
+
for (const [component, slots] of Object.entries(options)) {
|
|
50
|
+
replacements.set(classify(component), slots);
|
|
51
|
+
}
|
|
52
|
+
return context.sourceCode.parserServices.defineTemplateBodyVisitor({
|
|
53
|
+
VElement(node) {
|
|
54
|
+
if (node.name !== 'template' || node.parent.type !== 'VElement') return;
|
|
55
|
+
const tag = classify(node.parent.name);
|
|
56
|
+
if (!replacements.has(tag)) return;
|
|
57
|
+
const slots = replacements.get(tag);
|
|
58
|
+
const directive = node.startTag.attributes.find(attr => {
|
|
59
|
+
return attr.directive && attr.key.name.name === 'slot' && attr.key.argument?.name && slots[attr.key.argument.name] !== undefined;
|
|
60
|
+
});
|
|
61
|
+
if (!directive) return;
|
|
62
|
+
const slotName = directive.key.argument.name;
|
|
63
|
+
const replace = slots[slotName];
|
|
64
|
+
if (replace === false) {
|
|
65
|
+
context.report({
|
|
66
|
+
messageId: 'removed',
|
|
67
|
+
data: {
|
|
68
|
+
component: node.parent.name,
|
|
69
|
+
slot: slotName
|
|
70
|
+
},
|
|
71
|
+
node: directive
|
|
72
|
+
});
|
|
73
|
+
} else if (typeof replace === 'string') {
|
|
74
|
+
context.report({
|
|
75
|
+
messageId: 'replacedWith',
|
|
76
|
+
data: {
|
|
77
|
+
component: node.parent.name,
|
|
78
|
+
slot: slotName,
|
|
79
|
+
newSlot: replace
|
|
80
|
+
},
|
|
81
|
+
node: directive,
|
|
82
|
+
fix(fixer) {
|
|
83
|
+
return fixer.replaceText(directive.key.argument, replace);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
} else if (typeof replace === 'object' && replace !== null) {
|
|
87
|
+
context.report({
|
|
88
|
+
messageId: 'removedWithMessage',
|
|
89
|
+
data: {
|
|
90
|
+
component: node.parent.name,
|
|
91
|
+
slot: slotName,
|
|
92
|
+
message: replace.message
|
|
93
|
+
},
|
|
94
|
+
node: directive
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
};
|
|
@@ -33,7 +33,7 @@ const tags = Object.keys(VGrid).reduce((t, k) => {
|
|
|
33
33
|
module.exports = {
|
|
34
34
|
meta: {
|
|
35
35
|
docs: {
|
|
36
|
-
description: '
|
|
36
|
+
description: 'Warn about v1 grid attributes not being auto-converted to classes in v2.',
|
|
37
37
|
category: 'recommended'
|
|
38
38
|
},
|
|
39
39
|
fixable: 'code',
|
|
@@ -7,7 +7,9 @@ const alignmentClasses = [/^align-(content-)?(start|baseline|center|end|space-ar
|
|
|
7
7
|
const noFix = {
|
|
8
8
|
VContainer: [...alignmentClasses, /^grid-list-(xs|sm|md|lg|xl)$/],
|
|
9
9
|
VRow: [...alignmentClasses, 'row', 'column', 'reverse', 'wrap'],
|
|
10
|
-
VCol: [
|
|
10
|
+
VCol: ['align', 'justify',
|
|
11
|
+
// user error, don't attempt to fix
|
|
12
|
+
/^align-self-(start|baseline|center|end)$/, /^offset-(xs|sm|md|lg|xl)\d{1,2}$/, /^order-(xs|sm|md|lg|xl)\d{1,2}$/, /^(xs|sm|md|lg|xl)\d{1,2}$/]
|
|
11
13
|
};
|
|
12
14
|
function isGridAttribute(tag, name) {
|
|
13
15
|
return noFix[tag] && noFix[tag].some(match => {
|
package/package.json
CHANGED
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-vuetify",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.7.0",
|
|
4
4
|
"description": "An eslint plugin for Vuetify",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
|
+
"types": "lib/index.d.ts",
|
|
6
7
|
"author": "Kael Watts-Deuchar <kaelwd@gmail.com>",
|
|
7
8
|
"license": "MIT",
|
|
8
9
|
"repository": "github:vuetifyjs/eslint-plugin-vuetify",
|
|
9
10
|
"scripts": {
|
|
10
|
-
"build": "rimraf lib && babel src --out-dir lib",
|
|
11
|
+
"build": "rimraf lib && babel src --out-dir lib --copy-files && pnpm typegen",
|
|
11
12
|
"test": "mocha tests --recursive --reporter dot",
|
|
12
13
|
"test:8": "cross-env ESLINT8=true mocha tests --recursive --reporter dot",
|
|
13
14
|
"test:9": "cross-env ESLINT9=true mocha tests --recursive --reporter dot",
|
|
14
15
|
"test:coverage": "nyc mocha tests --recursive --reporter dot",
|
|
15
16
|
"test:ci": "nyc --reporter=lcov mocha tests --recursive --reporter dot",
|
|
17
|
+
"typegen": "node tools/generate-dts.mjs",
|
|
16
18
|
"lint": "eslint src tests",
|
|
17
19
|
"prepublishOnly": "npm run build",
|
|
18
20
|
"release": "bumpp -r"
|
|
@@ -32,11 +34,12 @@
|
|
|
32
34
|
"@stylistic/eslint-plugin": "^4.4.1",
|
|
33
35
|
"bumpp": "^10.1.0",
|
|
34
36
|
"conventional-changelog-cli": "^2.2.2",
|
|
35
|
-
"cross-env": "^7.0.3",
|
|
36
37
|
"conventional-changelog-vuetify": "^2.0.2",
|
|
37
38
|
"conventional-github-releaser": "^3.1.5",
|
|
39
|
+
"cross-env": "^7.0.3",
|
|
38
40
|
"eslint": "^10.0.0",
|
|
39
41
|
"eslint-plugin-vue": "^10.0.0",
|
|
42
|
+
"eslint-typegen": "^2.3.1",
|
|
40
43
|
"eslint8": "npm:eslint@8.57.1",
|
|
41
44
|
"eslint9": "npm:eslint@9",
|
|
42
45
|
"husky": "^8.0.1",
|
|
@@ -50,17 +53,7 @@
|
|
|
50
53
|
},
|
|
51
54
|
"peerDependencies": {
|
|
52
55
|
"eslint": "^8.0.0 || ^9.0.0 || ^10.0.0",
|
|
53
|
-
"vuetify": "^3.0.0"
|
|
56
|
+
"vuetify": "^3.0.0 || ^4.0.0"
|
|
54
57
|
},
|
|
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
|
-
}
|
|
58
|
+
"packageManager": "pnpm@10.26.1"
|
|
66
59
|
}
|