diff --git a/docs/rules/README.md b/docs/rules/README.md index 97117319f..d6ba3f9d8 100644 --- a/docs/rules/README.md +++ b/docs/rules/README.md @@ -152,6 +152,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-restricted-syntax](./no-restricted-syntax.md) | disallow specified syntax | | diff --git a/docs/rules/no-deprecated-slot-attribute.md b/docs/rules/no-deprecated-slot-attribute.md new file mode 100644 index 000000000..9da6225a7 --- /dev/null +++ b/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+. + + + +```vue + +``` + + + +## :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) diff --git a/lib/index.js b/lib/index.js index 6c727f241..40f8efa51 100644 --- a/lib/index.js +++ b/lib/index.js @@ -36,6 +36,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'), diff --git a/lib/rules/no-deprecated-slot-attribute.js b/lib/rules/no-deprecated-slot-attribute.js new file mode 100644 index 000000000..cf3e7c6ba --- /dev/null +++ b/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) + } +} diff --git a/lib/rules/syntaxes/slot-attribute.js b/lib/rules/syntaxes/slot-attribute.js new file mode 100644 index 000000000..db91e24fb --- /dev/null +++ b/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 + } + } +} diff --git a/tests/lib/rules/no-deprecated-slot-attribute.js b/tests/lib/rules/no-deprecated-slot-attribute.js new file mode 100644 index 000000000..bb0dd9b54 --- /dev/null +++ b/tests/lib/rules/no-deprecated-slot-attribute.js @@ -0,0 +1,353 @@ +'use strict' + +const RuleTester = require('eslint').RuleTester +const rule = require('../../../lib/rules/no-deprecated-slot-attribute.js') + +const tester = new RuleTester({ + parser: 'vue-eslint-parser', + parserOptions: { + ecmaVersion: 2015 + } +}) + +tester.run('no-deprecated-slot-attribute', rule, { + valid: [ + ``, + ``, + ``, + ``, + ``, + ``, + `` + ], + invalid: [ + { + code: ` + `, + output: ` + `, + errors: [ + { + message: '`slot` attributes are deprecated.', + line: 4 + } + ] + }, + { + code: ` + `, + output: ` + `, + errors: [ + { + message: '`slot` attributes are deprecated.', + line: 4 + } + ] + }, + { + code: ` + `, + output: ` + `, + errors: [ + { + message: '`slot` attributes are deprecated.', + line: 4 + } + ] + }, + { + code: ` + `, + output: ` + `, + errors: [ + { + message: '`slot` attributes are deprecated.', + line: 4 + } + ] + }, + { + code: ` + `, + output: ` + `, + errors: [ + { + message: '`slot` attributes are deprecated.', + line: 4 + } + ] + }, + { + code: ` + `, + output: null, + errors: [ + { + message: '`slot` attributes are deprecated.', + line: 4 + }, + { + message: '`slot` attributes are deprecated.', + line: 5 + }, + { + message: '`slot` attributes are deprecated.', + line: 6 + }, + { + message: '`slot` attributes are deprecated.', + line: 7 + }, + { + message: '`slot` attributes are deprecated.', + line: 8 + } + ] + }, + { + code: ` + `, + output: ` + `, + errors: [ + { + message: '`slot` attributes are deprecated.', + line: 4 + } + ] + }, + { + code: ` + `, + output: null, + errors: [ + { + message: '`slot` attributes are deprecated.', + line: 4 + } + ] + }, + { + code: ` + `, + output: ` + `, + errors: [ + { + message: '`slot` attributes are deprecated.', + line: 4 + } + ] + }, + { + code: ` + `, + output: null, + errors: [ + { + message: '`slot` attributes are deprecated.', + line: 4 + } + ] + }, + { + code: ` + `, + output: null, + errors: [ + { + message: '`slot` attributes are deprecated.', + line: 4 + } + ] + }, + { + code: ` + `, + output: null, + errors: [ + { + message: '`slot` attributes are deprecated.', + line: 4 + } + ] + }, + { + code: ` + `, + output: null, + errors: [ + { + message: '`slot` attributes are deprecated.', + line: 4 + } + ] + }, + { + code: ` + `, + output: ` + `, + errors: [ + { + message: '`slot` attributes are deprecated.', + line: 4 + } + ] + }, + { + code: ` + `, + output: null, + errors: [ + { + message: '`slot` attributes are deprecated.', + line: 4 + } + ] + }, + { + code: ` + `, + output: null, + errors: [ + { + message: '`slot` attributes are deprecated.', + line: 4 + } + ] + } + ] +})