Skip to content

Commit

Permalink
⭐️New: Add vue/no-deprecated-slot-attribute rule (#839)
Browse files Browse the repository at this point in the history
* ⭐️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 <public@mysticatea.dev>
  • Loading branch information
ota-meshi and mysticatea committed Dec 26, 2019
1 parent 6a78831 commit 3306b03
Show file tree
Hide file tree
Showing 6 changed files with 555 additions and 0 deletions.
1 change: 1 addition & 0 deletions docs/rules/README.md
Expand Up @@ -153,6 +153,7 @@ For example:
| [vue/keyword-spacing](./keyword-spacing.md) | enforce consistent spacing before and after keywords | :wrench: |
| [vue/match-component-file-name](./match-component-file-name.md) | require component name property to match its file name | |
| [vue/no-boolean-default](./no-boolean-default.md) | disallow boolean defaults | :wrench: |
| [vue/no-deprecated-slot-attribute](./no-deprecated-slot-attribute.md) | disallow deprecated `slot` attribute (in Vue.js 2.6.0+) | :wrench: |
| [vue/no-deprecated-scope-attribute](./no-deprecated-scope-attribute.md) | disallow deprecated `scope` attribute (in Vue.js 2.5.0+) | :wrench: |
| [vue/no-empty-pattern](./no-empty-pattern.md) | disallow empty destructuring patterns | |
| [vue/no-reserved-component-names](./no-reserved-component-names.md) | disallow the use of reserved names in component definitions | |
Expand Down
44 changes: 44 additions & 0 deletions docs/rules/no-deprecated-slot-attribute.md
@@ -0,0 +1,44 @@
---
pageClass: rule-details
sidebarDepth: 0
title: vue/no-deprecated-slot-attribute
description: disallow deprecated `slot` attribute (in Vue.js 2.6.0+)
---
# vue/no-deprecated-slot-attribute
> disallow deprecated `slot` attribute (in Vue.js 2.6.0+)
- :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.

## :book: Rule Details

This rule reports deprecated `slot` attribute in Vue.js v2.6.0+.

<eslint-code-block fix :rules="{'vue/no-deprecated-slot-attribute': ['error']}">

```vue
<template>
<ListComponent>
<!-- ✓ GOOD -->
<template v-slot:name>
{{ props.title }}
</template>
</ListComponent>
<ListComponent>
<!-- ✗ BAD -->
<template slot="name">
{{ props.title }}
</template>
</ListComponent>
</template>
```

</eslint-code-block>

## :books: Further reading

- [API - slot](https://vuejs.org/v2/api/#slot-deprecated)

## :mag: Implementation

- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-deprecated-slot-attribute.js)
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-deprecated-slot-attribute.js)
1 change: 1 addition & 0 deletions lib/index.js
Expand Up @@ -37,6 +37,7 @@ module.exports = {
'no-async-in-computed-properties': require('./rules/no-async-in-computed-properties'),
'no-boolean-default': require('./rules/no-boolean-default'),
'no-confusing-v-for-v-if': require('./rules/no-confusing-v-for-v-if'),
'no-deprecated-slot-attribute': require('./rules/no-deprecated-slot-attribute'),
'no-deprecated-scope-attribute': require('./rules/no-deprecated-scope-attribute'),
'no-dupe-keys': require('./rules/no-dupe-keys'),
'no-duplicate-attributes': require('./rules/no-duplicate-attributes'),
Expand Down
28 changes: 28 additions & 0 deletions lib/rules/no-deprecated-slot-attribute.js
@@ -0,0 +1,28 @@
/**
* @author Yosuke Ota
* See LICENSE file in root directory for full license.
*/
'use strict'

const utils = require('../utils')
const slotAttribute = require('./syntaxes/slot-attribute')

module.exports = {
meta: {
type: 'suggestion',
docs: {
description: 'disallow deprecated `slot` attribute (in Vue.js 2.6.0+)',
category: undefined,
url: 'https://eslint.vuejs.org/rules/no-deprecated-slot-attribute.html'
},
fixable: 'code',
schema: [],
messages: {
forbiddenSlotAttribute: '`slot` attributes are deprecated.'
}
},
create (context) {
const templateBodyVisitor = slotAttribute.createTemplateBodyVisitor(context)
return utils.defineTemplateBodyVisitor(context, templateBodyVisitor)
}
}
128 changes: 128 additions & 0 deletions lib/rules/syntaxes/slot-attribute.js
@@ -0,0 +1,128 @@
/**
* @author Yosuke Ota
* See LICENSE file in root directory for full license.
*/
'use strict'
module.exports = {
deprecated: '2.6.0',
createTemplateBodyVisitor (context) {
const sourceCode = context.getSourceCode()

/**
* Checks whether the given node can convert to the `v-slot`.
* @param {VAttribute} slotAttr node of `slot`
* @returns {boolean} `true` if the given node can convert to the `v-slot`
*/
function canConvertFromSlotToVSlot (slotAttr) {
if (slotAttr.parent.parent.name !== 'template') {
return false
}
if (!slotAttr.value) {
return true
}
const slotName = slotAttr.value.value
// If non-Latin characters are included it can not be converted.
return !/[^a-z]/i.test(slotName)
}

/**
* Checks whether the given node can convert to the `v-slot`.
* @param {VAttribute} slotAttr node of `v-bind:slot`
* @returns {boolean} `true` if the given node can convert to the `v-slot`
*/
function canConvertFromVBindSlotToVSlot (slotAttr) {
if (slotAttr.parent.parent.name !== 'template') {
return false
}

if (!slotAttr.value) {
return true
}

if (!slotAttr.value.expression) {
// parse error or empty expression
return false
}
const slotName = sourceCode.getText(slotAttr.value.expression).trim()
// If non-Latin characters are included it can not be converted.
// It does not check the space only because `a>b?c:d` should be rejected.
return !/[^a-z]/i.test(slotName)
}

/**
* Convert to `v-slot`.
* @param {object} fixer fixer
* @param {VAttribute} slotAttr node of `slot`
* @param {string | null} slotName name of `slot`
* @param {boolean} vBind `true` if `slotAttr` is `v-bind:slot`
* @returns {*} fix data
*/
function fixSlotToVSlot (fixer, slotAttr, slotName, vBind) {
const element = slotAttr.parent
const scopeAttr = element.attributes
.find(attr => attr.directive === true && attr.key.name && (
attr.key.name.name === 'slot-scope' ||
attr.key.name.name === 'scope'
))
const nameArgument = slotName ? (vBind ? `:[${slotName}]` : `:${slotName}`) : ''
const scopeValue = scopeAttr && scopeAttr.value
? `=${sourceCode.getText(scopeAttr.value)}`
: ''

const replaceText = `v-slot${nameArgument}${scopeValue}`
const fixers = [
fixer.replaceText(slotAttr || scopeAttr, replaceText)
]
if (slotAttr && scopeAttr) {
fixers.push(fixer.remove(scopeAttr))
}
return fixers
}
/**
* Reports `slot` node
* @param {VAttribute} slotAttr node of `slot`
* @returns {void}
*/
function reportSlot (slotAttr) {
context.report({
node: slotAttr.key,
messageId: 'forbiddenSlotAttribute',
// fix to use `v-slot`
fix (fixer) {
if (!canConvertFromSlotToVSlot(slotAttr)) {
return null
}
const slotName = slotAttr.value &&
slotAttr.value.value
return fixSlotToVSlot(fixer, slotAttr, slotName, false)
}
})
}
/**
* Reports `v-bind:slot` node
* @param {VAttribute} slotAttr node of `v-bind:slot`
* @returns {void}
*/
function reportVBindSlot (slotAttr) {
context.report({
node: slotAttr.key,
messageId: 'forbiddenSlotAttribute',
// fix to use `v-slot`
fix (fixer) {
if (!canConvertFromVBindSlotToVSlot(slotAttr)) {
return null
}
const slotName = slotAttr.value &&
slotAttr.value.expression &&
sourceCode.getText(slotAttr.value.expression).trim()
return fixSlotToVSlot(fixer, slotAttr, slotName, true)
}
})
}

return {
"VAttribute[directive=false][key.name='slot']": reportSlot,
"VAttribute[directive=true][key.name.name='bind'][key.argument.name='slot']": reportVBindSlot
}
}
}

0 comments on commit 3306b03

Please sign in to comment.