From f28bdaa2d10d62465047b778865392cdc88b2dfd Mon Sep 17 00:00:00 2001 From: ota Date: Tue, 5 Feb 2019 17:17:17 +0900 Subject: [PATCH 1/4] =?UTF-8?q?=E2=AD=90=EF=B8=8FNew:=20Add=20vue/no-depre?= =?UTF-8?q?cated-slot-attribute=20rule?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/rules/README.md | 1 + docs/rules/no-deprecated-slot-attribute.md | 44 ++++++ lib/index.js | 1 + lib/rules/no-deprecated-slot-attribute.js | 28 ++++ lib/rules/syntaxes/slot-attribute.js | 60 +++++++++ .../lib/rules/no-deprecated-slot-attribute.js | 127 ++++++++++++++++++ 6 files changed, 261 insertions(+) create mode 100644 docs/rules/no-deprecated-slot-attribute.md create mode 100644 lib/rules/no-deprecated-slot-attribute.js create mode 100644 lib/rules/syntaxes/slot-attribute.js create mode 100644 tests/lib/rules/no-deprecated-slot-attribute.js diff --git a/docs/rules/README.md b/docs/rules/README.md index 82baedd01..d44d1b8cd 100644 --- a/docs/rules/README.md +++ b/docs/rules/README.md @@ -151,6 +151,7 @@ For example: | [vue/key-spacing](./key-spacing.md) | enforce consistent spacing between keys and values in object literal properties | :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-empty-pattern](./no-empty-pattern.md) | disallow empty destructuring patterns | | | [vue/no-restricted-syntax](./no-restricted-syntax.md) | disallow specified syntax | | | [vue/object-curly-spacing](./object-curly-spacing.md) | enforce consistent spacing inside braces | :wrench: | 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 8dbae6823..d0162b85e 100644 --- a/lib/index.js +++ b/lib/index.js @@ -35,6 +35,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-dupe-keys': require('./rules/no-dupe-keys'), 'no-duplicate-attributes': require('./rules/no-duplicate-attributes'), 'no-empty-pattern': require('./rules/no-empty-pattern'), 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..7054647fb --- /dev/null +++ b/lib/rules/syntaxes/slot-attribute.js @@ -0,0 +1,60 @@ +/** + * @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() + /** + * Convert to `v-slot`. + * @param {object} fixer fixer + * @param {VAttribute | null} slotAttr node of `slot` + * @param {VAttribute | null} scopeAttr node of `scope` + * @returns {*} fix data + */ + function fixSlotToVSlot (fixer, slotAttr, scopeAttr) { + const nameArgument = slotAttr && slotAttr.value && slotAttr && slotAttr.value.value + ? `:${slotAttr.value.value}` + : '' + 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) { + 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' + )) + return fixSlotToVSlot(fixer, slotAttr, scopeAttr) + } + }) + } + + return { + "VAttribute[directive=false][key.name='slot']": reportSlot + } + } +} 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..d2d71e5e5 --- /dev/null +++ b/tests/lib/rules/no-deprecated-slot-attribute.js @@ -0,0 +1,127 @@ +'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 + } + ] + } + ] +}) From 1ce47a119c0cac98e1618ddb888e5f794202abf4 Mon Sep 17 00:00:00 2001 From: yosuke ota Date: Thu, 28 Feb 2019 14:53:08 +0900 Subject: [PATCH 2/4] Made the following changes - Changed to check `v-bind:slot`. - Changed not to autofix, if it becomes an invalid attribute name after autofix. --- lib/rules/syntaxes/slot-attribute.js | 88 ++++++++-- .../lib/rules/no-deprecated-slot-attribute.js | 160 ++++++++++++++++++ 2 files changed, 234 insertions(+), 14 deletions(-) diff --git a/lib/rules/syntaxes/slot-attribute.js b/lib/rules/syntaxes/slot-attribute.js index 7054647fb..4dd7a7896 100644 --- a/lib/rules/syntaxes/slot-attribute.js +++ b/lib/rules/syntaxes/slot-attribute.js @@ -7,17 +7,56 @@ 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.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.value) { + return true + } + + if (!slotAttr.value.expression) { + // parse error or empty expression + return false + } + const slotName = sourceCode.getText(slotAttr.value.expression).trim() + // If space is included it can not be converted. + return !/\s/.test(slotName) + } + /** * Convert to `v-slot`. * @param {object} fixer fixer - * @param {VAttribute | null} slotAttr node of `slot` - * @param {VAttribute | null} scopeAttr node of `scope` + * @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, scopeAttr) { - const nameArgument = slotAttr && slotAttr.value && slotAttr && slotAttr.value.value - ? `:${slotAttr.value.value}` - : '' + 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)}` : '' @@ -42,19 +81,40 @@ module.exports = { messageId: 'forbiddenSlotAttribute', // fix to use `v-slot` fix (fixer) { - 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' - )) - return fixSlotToVSlot(fixer, slotAttr, scopeAttr) + 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=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 index d2d71e5e5..548e6c74d 100644 --- a/tests/lib/rules/no-deprecated-slot-attribute.js +++ b/tests/lib/rules/no-deprecated-slot-attribute.js @@ -122,6 +122,166 @@ tester.run('no-deprecated-slot-attribute', rule, { 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: ` + `, + 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 + } + ] } ] }) From bf5f03f7b3114ea16db4f53468981ddfd423b99c Mon Sep 17 00:00:00 2001 From: yosuke ota Date: Thu, 28 Feb 2019 18:46:24 +0900 Subject: [PATCH 3/4] Changed not to autofix if `v-bind:slot` contains non Latin characters. --- lib/rules/syntaxes/slot-attribute.js | 5 +++-- .../lib/rules/no-deprecated-slot-attribute.js | 22 ++++++++++++++----- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/lib/rules/syntaxes/slot-attribute.js b/lib/rules/syntaxes/slot-attribute.js index 4dd7a7896..01a4ead3d 100644 --- a/lib/rules/syntaxes/slot-attribute.js +++ b/lib/rules/syntaxes/slot-attribute.js @@ -37,8 +37,9 @@ module.exports = { return false } const slotName = sourceCode.getText(slotAttr.value.expression).trim() - // If space is included it can not be converted. - return !/\s/.test(slotName) + // 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) } /** diff --git a/tests/lib/rules/no-deprecated-slot-attribute.js b/tests/lib/rules/no-deprecated-slot-attribute.js index 548e6c74d..ac26f6f20 100644 --- a/tests/lib/rules/no-deprecated-slot-attribute.js +++ b/tests/lib/rules/no-deprecated-slot-attribute.js @@ -205,12 +205,7 @@ tester.run('no-deprecated-slot-attribute', rule, { `, - output: ` - `, + output: null, errors: [ { message: '`slot` attributes are deprecated.', @@ -253,6 +248,21 @@ tester.run('no-deprecated-slot-attribute', rule, { } ] }, + { + code: ` + `, + output: null, + errors: [ + { + message: '`slot` attributes are deprecated.', + line: 4 + } + ] + }, { code: `