Skip to content

Commit 3306b03

Browse files
⭐️New: Add vue/no-deprecated-slot-attribute rule (#839)
* ⭐️New: Add vue/no-deprecated-slot-attribute rule * Made the following changes - Changed to check `v-bind:slot`. - Changed not to autofix, if it becomes an invalid attribute name after autofix. * Changed not to autofix if `v-bind:slot` contains non Latin characters. * Changed it to autofix only when `slot` is attached to `<template>` Co-authored-by: Toru Nagashima <[email protected]>
1 parent 6a78831 commit 3306b03

File tree

6 files changed

+555
-0
lines changed

6 files changed

+555
-0
lines changed

Diff for: docs/rules/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ For example:
153153
| [vue/keyword-spacing](./keyword-spacing.md) | enforce consistent spacing before and after keywords | :wrench: |
154154
| [vue/match-component-file-name](./match-component-file-name.md) | require component name property to match its file name | |
155155
| [vue/no-boolean-default](./no-boolean-default.md) | disallow boolean defaults | :wrench: |
156+
| [vue/no-deprecated-slot-attribute](./no-deprecated-slot-attribute.md) | disallow deprecated `slot` attribute (in Vue.js 2.6.0+) | :wrench: |
156157
| [vue/no-deprecated-scope-attribute](./no-deprecated-scope-attribute.md) | disallow deprecated `scope` attribute (in Vue.js 2.5.0+) | :wrench: |
157158
| [vue/no-empty-pattern](./no-empty-pattern.md) | disallow empty destructuring patterns | |
158159
| [vue/no-reserved-component-names](./no-reserved-component-names.md) | disallow the use of reserved names in component definitions | |

Diff for: docs/rules/no-deprecated-slot-attribute.md

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
---
2+
pageClass: rule-details
3+
sidebarDepth: 0
4+
title: vue/no-deprecated-slot-attribute
5+
description: disallow deprecated `slot` attribute (in Vue.js 2.6.0+)
6+
---
7+
# vue/no-deprecated-slot-attribute
8+
> disallow deprecated `slot` attribute (in Vue.js 2.6.0+)
9+
10+
- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule.
11+
12+
## :book: Rule Details
13+
14+
This rule reports deprecated `slot` attribute in Vue.js v2.6.0+.
15+
16+
<eslint-code-block fix :rules="{'vue/no-deprecated-slot-attribute': ['error']}">
17+
18+
```vue
19+
<template>
20+
<ListComponent>
21+
<!-- ✓ GOOD -->
22+
<template v-slot:name>
23+
{{ props.title }}
24+
</template>
25+
</ListComponent>
26+
<ListComponent>
27+
<!-- ✗ BAD -->
28+
<template slot="name">
29+
{{ props.title }}
30+
</template>
31+
</ListComponent>
32+
</template>
33+
```
34+
35+
</eslint-code-block>
36+
37+
## :books: Further reading
38+
39+
- [API - slot](https://vuejs.org/v2/api/#slot-deprecated)
40+
41+
## :mag: Implementation
42+
43+
- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-deprecated-slot-attribute.js)
44+
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-deprecated-slot-attribute.js)

Diff for: lib/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ module.exports = {
3737
'no-async-in-computed-properties': require('./rules/no-async-in-computed-properties'),
3838
'no-boolean-default': require('./rules/no-boolean-default'),
3939
'no-confusing-v-for-v-if': require('./rules/no-confusing-v-for-v-if'),
40+
'no-deprecated-slot-attribute': require('./rules/no-deprecated-slot-attribute'),
4041
'no-deprecated-scope-attribute': require('./rules/no-deprecated-scope-attribute'),
4142
'no-dupe-keys': require('./rules/no-dupe-keys'),
4243
'no-duplicate-attributes': require('./rules/no-duplicate-attributes'),

Diff for: lib/rules/no-deprecated-slot-attribute.js

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/**
2+
* @author Yosuke Ota
3+
* See LICENSE file in root directory for full license.
4+
*/
5+
'use strict'
6+
7+
const utils = require('../utils')
8+
const slotAttribute = require('./syntaxes/slot-attribute')
9+
10+
module.exports = {
11+
meta: {
12+
type: 'suggestion',
13+
docs: {
14+
description: 'disallow deprecated `slot` attribute (in Vue.js 2.6.0+)',
15+
category: undefined,
16+
url: 'https://eslint.vuejs.org/rules/no-deprecated-slot-attribute.html'
17+
},
18+
fixable: 'code',
19+
schema: [],
20+
messages: {
21+
forbiddenSlotAttribute: '`slot` attributes are deprecated.'
22+
}
23+
},
24+
create (context) {
25+
const templateBodyVisitor = slotAttribute.createTemplateBodyVisitor(context)
26+
return utils.defineTemplateBodyVisitor(context, templateBodyVisitor)
27+
}
28+
}

Diff for: lib/rules/syntaxes/slot-attribute.js

+128
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
/**
2+
* @author Yosuke Ota
3+
* See LICENSE file in root directory for full license.
4+
*/
5+
'use strict'
6+
module.exports = {
7+
deprecated: '2.6.0',
8+
createTemplateBodyVisitor (context) {
9+
const sourceCode = context.getSourceCode()
10+
11+
/**
12+
* Checks whether the given node can convert to the `v-slot`.
13+
* @param {VAttribute} slotAttr node of `slot`
14+
* @returns {boolean} `true` if the given node can convert to the `v-slot`
15+
*/
16+
function canConvertFromSlotToVSlot (slotAttr) {
17+
if (slotAttr.parent.parent.name !== 'template') {
18+
return false
19+
}
20+
if (!slotAttr.value) {
21+
return true
22+
}
23+
const slotName = slotAttr.value.value
24+
// If non-Latin characters are included it can not be converted.
25+
return !/[^a-z]/i.test(slotName)
26+
}
27+
28+
/**
29+
* Checks whether the given node can convert to the `v-slot`.
30+
* @param {VAttribute} slotAttr node of `v-bind:slot`
31+
* @returns {boolean} `true` if the given node can convert to the `v-slot`
32+
*/
33+
function canConvertFromVBindSlotToVSlot (slotAttr) {
34+
if (slotAttr.parent.parent.name !== 'template') {
35+
return false
36+
}
37+
38+
if (!slotAttr.value) {
39+
return true
40+
}
41+
42+
if (!slotAttr.value.expression) {
43+
// parse error or empty expression
44+
return false
45+
}
46+
const slotName = sourceCode.getText(slotAttr.value.expression).trim()
47+
// If non-Latin characters are included it can not be converted.
48+
// It does not check the space only because `a>b?c:d` should be rejected.
49+
return !/[^a-z]/i.test(slotName)
50+
}
51+
52+
/**
53+
* Convert to `v-slot`.
54+
* @param {object} fixer fixer
55+
* @param {VAttribute} slotAttr node of `slot`
56+
* @param {string | null} slotName name of `slot`
57+
* @param {boolean} vBind `true` if `slotAttr` is `v-bind:slot`
58+
* @returns {*} fix data
59+
*/
60+
function fixSlotToVSlot (fixer, slotAttr, slotName, vBind) {
61+
const element = slotAttr.parent
62+
const scopeAttr = element.attributes
63+
.find(attr => attr.directive === true && attr.key.name && (
64+
attr.key.name.name === 'slot-scope' ||
65+
attr.key.name.name === 'scope'
66+
))
67+
const nameArgument = slotName ? (vBind ? `:[${slotName}]` : `:${slotName}`) : ''
68+
const scopeValue = scopeAttr && scopeAttr.value
69+
? `=${sourceCode.getText(scopeAttr.value)}`
70+
: ''
71+
72+
const replaceText = `v-slot${nameArgument}${scopeValue}`
73+
const fixers = [
74+
fixer.replaceText(slotAttr || scopeAttr, replaceText)
75+
]
76+
if (slotAttr && scopeAttr) {
77+
fixers.push(fixer.remove(scopeAttr))
78+
}
79+
return fixers
80+
}
81+
/**
82+
* Reports `slot` node
83+
* @param {VAttribute} slotAttr node of `slot`
84+
* @returns {void}
85+
*/
86+
function reportSlot (slotAttr) {
87+
context.report({
88+
node: slotAttr.key,
89+
messageId: 'forbiddenSlotAttribute',
90+
// fix to use `v-slot`
91+
fix (fixer) {
92+
if (!canConvertFromSlotToVSlot(slotAttr)) {
93+
return null
94+
}
95+
const slotName = slotAttr.value &&
96+
slotAttr.value.value
97+
return fixSlotToVSlot(fixer, slotAttr, slotName, false)
98+
}
99+
})
100+
}
101+
/**
102+
* Reports `v-bind:slot` node
103+
* @param {VAttribute} slotAttr node of `v-bind:slot`
104+
* @returns {void}
105+
*/
106+
function reportVBindSlot (slotAttr) {
107+
context.report({
108+
node: slotAttr.key,
109+
messageId: 'forbiddenSlotAttribute',
110+
// fix to use `v-slot`
111+
fix (fixer) {
112+
if (!canConvertFromVBindSlotToVSlot(slotAttr)) {
113+
return null
114+
}
115+
const slotName = slotAttr.value &&
116+
slotAttr.value.expression &&
117+
sourceCode.getText(slotAttr.value.expression).trim()
118+
return fixSlotToVSlot(fixer, slotAttr, slotName, true)
119+
}
120+
})
121+
}
122+
123+
return {
124+
"VAttribute[directive=false][key.name='slot']": reportSlot,
125+
"VAttribute[directive=true][key.name.name='bind'][key.argument.name='slot']": reportVBindSlot
126+
}
127+
}
128+
}

0 commit comments

Comments
 (0)