From d1479f219651fdf7d4bb9fdd17b6f77e18549808 Mon Sep 17 00:00:00 2001 From: waynzh Date: Fri, 30 Aug 2024 15:46:42 +0800 Subject: [PATCH 1/6] feat: add no-deprecated-delete-set rule --- docs/rules/index.md | 1 + docs/rules/no-deprecated-delete-set.md | 49 ++++ lib/configs/flat/vue3-essential.js | 1 + lib/configs/vue3-essential.js | 1 + lib/index.js | 1 + lib/rules/no-deprecated-delete-set.js | 141 +++++++++ tests/lib/rules/no-deprecated-delete-set.js | 308 ++++++++++++++++++++ 7 files changed, 502 insertions(+) create mode 100644 docs/rules/no-deprecated-delete-set.md create mode 100644 lib/rules/no-deprecated-delete-set.js create mode 100644 tests/lib/rules/no-deprecated-delete-set.js diff --git a/docs/rules/index.md b/docs/rules/index.md index 113ff416b..0d3ea5c4b 100644 --- a/docs/rules/index.md +++ b/docs/rules/index.md @@ -48,6 +48,7 @@ Rules in this category are enabled for all presets provided by eslint-plugin-vue | [vue/no-computed-properties-in-data](./no-computed-properties-in-data.md) | disallow accessing computed properties in `data` | | :three::two::warning: | | [vue/no-custom-modifiers-on-v-model](./no-custom-modifiers-on-v-model.md) | disallow custom modifiers on v-model used on the component | | :two::warning: | | [vue/no-deprecated-data-object-declaration](./no-deprecated-data-object-declaration.md) | disallow using deprecated object declaration on data (in Vue.js 3.0.0+) | :wrench: | :three::warning: | +| [vue/no-deprecated-delete-set](./no-deprecated-delete-set.md) | disallow using deprecated `$delete` and `$set` (in Vue.js 3.0.0+) | | :three::warning: | | [vue/no-deprecated-destroyed-lifecycle](./no-deprecated-destroyed-lifecycle.md) | disallow using deprecated `destroyed` and `beforeDestroy` lifecycle hooks (in Vue.js 3.0.0+) | :wrench: | :three::warning: | | [vue/no-deprecated-dollar-listeners-api](./no-deprecated-dollar-listeners-api.md) | disallow using deprecated `$listeners` (in Vue.js 3.0.0+) | | :three::warning: | | [vue/no-deprecated-dollar-scopedslots-api](./no-deprecated-dollar-scopedslots-api.md) | disallow using deprecated `$scopedSlots` (in Vue.js 3.0.0+) | :wrench: | :three::warning: | diff --git a/docs/rules/no-deprecated-delete-set.md b/docs/rules/no-deprecated-delete-set.md new file mode 100644 index 000000000..20a33fe58 --- /dev/null +++ b/docs/rules/no-deprecated-delete-set.md @@ -0,0 +1,49 @@ +--- +pageClass: rule-details +sidebarDepth: 0 +title: vue/no-deprecated-delete-set +description: disallow using deprecated `$delete` and `$set` (in Vue.js 3.0.0+) +--- + +# vue/no-deprecated-delete-set + +> disallow using deprecated `$delete` and `$set` (in Vue.js 3.0.0+) + +- :exclamation: _**This rule has not been released yet.**_ +- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `*.configs["flat/essential"]`, `"plugin:vue/vue3-strongly-recommended"`, `*.configs["flat/strongly-recommended"]`, `"plugin:vue/vue3-recommended"` and `*.configs["flat/recommended"]`. + +## :book: Rule Details + +This rule reports use of deprecated `$delete` and `$set`. (in Vue.js 3.0.0+). + + + +```vue + +``` + + + +## :wrench: Options + +Nothing. + +## :mag: Implementation + +- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-deprecated-delete-set.js) +- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-deprecated-delete-set.js) diff --git a/lib/configs/flat/vue3-essential.js b/lib/configs/flat/vue3-essential.js index 18d8c8570..50a6d6787 100644 --- a/lib/configs/flat/vue3-essential.js +++ b/lib/configs/flat/vue3-essential.js @@ -17,6 +17,7 @@ module.exports = [ 'vue/no-child-content': 'error', 'vue/no-computed-properties-in-data': 'error', 'vue/no-deprecated-data-object-declaration': 'error', + 'vue/no-deprecated-delete-set': 'error', 'vue/no-deprecated-destroyed-lifecycle': 'error', 'vue/no-deprecated-dollar-listeners-api': 'error', 'vue/no-deprecated-dollar-scopedslots-api': 'error', diff --git a/lib/configs/vue3-essential.js b/lib/configs/vue3-essential.js index f71508f75..c58520f26 100644 --- a/lib/configs/vue3-essential.js +++ b/lib/configs/vue3-essential.js @@ -12,6 +12,7 @@ module.exports = { 'vue/no-child-content': 'error', 'vue/no-computed-properties-in-data': 'error', 'vue/no-deprecated-data-object-declaration': 'error', + 'vue/no-deprecated-delete-set': 'error', 'vue/no-deprecated-destroyed-lifecycle': 'error', 'vue/no-deprecated-dollar-listeners-api': 'error', 'vue/no-deprecated-dollar-scopedslots-api': 'error', diff --git a/lib/index.js b/lib/index.js index ff3f69c6e..60bccf38c 100644 --- a/lib/index.js +++ b/lib/index.js @@ -96,6 +96,7 @@ const plugin = { 'no-constant-condition': require('./rules/no-constant-condition'), 'no-custom-modifiers-on-v-model': require('./rules/no-custom-modifiers-on-v-model'), 'no-deprecated-data-object-declaration': require('./rules/no-deprecated-data-object-declaration'), + 'no-deprecated-delete-set': require('./rules/no-deprecated-delete-set'), 'no-deprecated-destroyed-lifecycle': require('./rules/no-deprecated-destroyed-lifecycle'), 'no-deprecated-dollar-listeners-api': require('./rules/no-deprecated-dollar-listeners-api'), 'no-deprecated-dollar-scopedslots-api': require('./rules/no-deprecated-dollar-scopedslots-api'), diff --git a/lib/rules/no-deprecated-delete-set.js b/lib/rules/no-deprecated-delete-set.js new file mode 100644 index 000000000..1872f45d4 --- /dev/null +++ b/lib/rules/no-deprecated-delete-set.js @@ -0,0 +1,141 @@ +/** + * @author Wayne Zhang + * See LICENSE file in root directory for full license. + */ +'use strict' + +const utils = require('../utils') +const { findVariable } = require('@eslint-community/eslint-utils') + +const DeprecatedApis = ['set', 'delete'] +const DeprecatedDollarApis = new Set(DeprecatedApis.map((item) => `$${item}`)) + +/** + * @param {Expression|Super} node + */ +function isVue(node) { + return node.type === 'Identifier' && node.name === 'Vue' +} + +module.exports = { + meta: { + type: 'problem', + docs: { + description: + 'disallow using deprecated `$delete` and `$set` (in Vue.js 3.0.0+)', + categories: ['vue3-essential'], + url: 'https://eslint.vuejs.org/rules/no-deprecated-delete-set.html' + }, + fixable: null, + schema: [], + messages: { + deprecated: 'The `$delete`, `$set` is deprecated.' + } + }, + /** @param {RuleContext} context */ + create(context) { + /** + * @param {Identifier} identifier + * @param {RuleContext} context + * @returns {CallExpression|undefined} + */ + function getVueDeprecatedCallExpression(identifier, context) { + // Instance API: this.$set() + if ( + DeprecatedDollarApis.has(identifier.name) && + identifier.parent.type === 'MemberExpression' && + utils.isThis(identifier.parent.object, context) && + identifier.parent.parent.type === 'CallExpression' && + identifier.parent.parent.callee === identifier.parent + ) { + return identifier.parent.parent + } + + // Vue 2 Global API: Vue.set() + if ( + DeprecatedApis.includes(identifier.name) && + identifier.parent.type === 'MemberExpression' && + isVue(identifier.parent.object) && + identifier.parent.parent.type === 'CallExpression' && + identifier.parent.parent.callee === identifier.parent + ) { + return identifier.parent.parent + } + + // Vue 3 Global API + if ( + identifier.parent.type === 'CallExpression' && + identifier.parent.callee === identifier + ) { + const variable = findVariable( + utils.getScope(context, identifier), + identifier + ) + + if (variable != null && variable.defs.length === 1) { + const def = variable.defs[0] + + // import { set as st } from 'vue'; st() + if ( + def.type === 'ImportBinding' && + def.node.type === 'ImportSpecifier' && + def.node.imported.type === 'Identifier' && + DeprecatedApis.includes(def.node.imported.name) && + def.node.parent.type === 'ImportDeclaration' && + def.node.parent.source.value === 'vue' + ) { + return identifier.parent + } + + // const { set, delete } = require('vue'); set() + if ( + def.type === 'Variable' && + def.node.type === 'VariableDeclarator' && + def.node.id.type === 'ObjectPattern' && + def.node.init?.type === 'CallExpression' && + def.node.init.callee.type === 'Identifier' && + def.node.init.callee.name === 'require' && + def.node.init.arguments.length === 1 && + def.node.init.arguments[0].type === 'Literal' && + def.node.init.arguments[0].value === 'vue' + ) { + const properties = def.node.id.properties + for (const prop of properties) { + if ( + prop.type === 'Property' && + prop.key.type === 'Identifier' && + DeprecatedApis.includes(prop.key.name) && + prop.value.type === 'Identifier' && + prop.value.name === identifier.name + ) { + return identifier.parent + } + } + } + } + } + + return undefined + } + + const nodeVisitor = { + /** @param {Identifier} node */ + Identifier(node) { + const callExpression = getVueDeprecatedCallExpression(node, context) + if (!callExpression) { + return + } + + context.report({ + node, + messageId: 'deprecated' + }) + } + } + + return utils.compositingVisitors( + utils.defineVueVisitor(context, nodeVisitor), + utils.defineScriptSetupVisitor(context, nodeVisitor) + ) + } +} diff --git a/tests/lib/rules/no-deprecated-delete-set.js b/tests/lib/rules/no-deprecated-delete-set.js new file mode 100644 index 000000000..d13227cd3 --- /dev/null +++ b/tests/lib/rules/no-deprecated-delete-set.js @@ -0,0 +1,308 @@ +/** + * @author Wayne Zhang + * See LICENSE file in root directory for full license. + */ +'use strict' + +const RuleTester = require('../../eslint-compat').RuleTester +const rule = require('../../../lib/rules/no-deprecated-delete-set') + +const tester = new RuleTester({ + languageOptions: { + parser: require('vue-eslint-parser'), + ecmaVersion: 2020, + sourceType: 'module' + } +}) + +tester.run('no-deprecated-delete-set', rule, { + valid: [ + { + filename: 'test.js', + code: ` + createApp({ + mounted () { + this.$emit('start') + }, + methods: { + click () { + this.$emit('click') + } + } + }) + ` + }, + { + filename: 'test.js', + code: ` + const another = function () { + this.$set(obj, key, value) + this.$delete(obj, key) + } + + createApp({ + mounted () { + this.$emit('start') + } + }) + ` + }, + { + filename: 'test.vue', + code: ` + + ` + }, + { + filename: 'test.vue', + code: ` + + ` + }, + // from vue + { + filename: 'test.js', + code: ` + app.component('some-comp', { + mounted () { + Vue.nextTick() + } + }) + ` + }, + { + filename: 'test.vue', + code: ` + + ` + }, + { + filename: 'test.vue', + code: ` + + ` + } + ], + + invalid: [ + { + filename: 'test.js', + code: ` + app.component('some-comp', { + mounted () { + this.$set(obj, key, value) + this.$delete(obj, key) + } + }) + `, + errors: [ + { + messageId: 'deprecated', + line: 4 + }, + { + messageId: 'deprecated', + line: 5 + } + ] + }, + { + filename: 'test.js', + code: ` + app.component('some-comp', { + mounted () { + const vm = this + vm.$set(obj, key, value) + vm.$delete(obj, key) + } + }) + `, + errors: [ + { + messageId: 'deprecated', + line: 5 + }, + { + messageId: 'deprecated', + line: 6 + } + ] + }, + { + filename: 'test.js', + code: ` + app.component('some-comp', { + mounted () { + this?.$set(obj, key, value) + this?.$delete(obj, key) + } + }) + `, + errors: [ + { messageId: 'deprecated', line: 4 }, + { messageId: 'deprecated', line: 5 } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + errors: [ + { messageId: 'deprecated', line: 5 }, + { messageId: 'deprecated', line: 6 } + ] + }, + // from vue + { + filename: 'test.js', + code: ` + app.component('some-comp', { + mounted () { + Vue.set(obj, key, value) + Vue.delete(obj, key) + } + }) + `, + errors: [ + { + messageId: 'deprecated', + line: 4 + }, + { + messageId: 'deprecated', + line: 5 + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + errors: [ + { + messageId: 'deprecated', + line: 6 + }, + { + messageId: 'deprecated', + line: 7 + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + errors: [ + { + messageId: 'deprecated', + line: 5 + }, + { + messageId: 'deprecated', + line: 6 + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + errors: [ + { + messageId: 'deprecated', + line: 6 + }, + { + messageId: 'deprecated', + line: 7 + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + errors: [ + { + messageId: 'deprecated', + line: 5 + }, + { + messageId: 'deprecated', + line: 6 + } + ] + } + ] +}) From 72e3c3115e7e2a5aadb70af3673518decc9b338f Mon Sep 17 00:00:00 2001 From: waynzh Date: Fri, 30 Aug 2024 17:01:30 +0800 Subject: [PATCH 2/6] feat: address comments --- docs/rules/index.md | 1 - docs/rules/no-deprecated-delete-set.md | 9 ++- lib/configs/flat/vue3-essential.js | 1 - lib/configs/vue3-essential.js | 1 - lib/rules/no-deprecated-delete-set.js | 15 ++-- tests/lib/rules/no-deprecated-delete-set.js | 79 ++++++++++++++++----- 6 files changed, 77 insertions(+), 29 deletions(-) diff --git a/docs/rules/index.md b/docs/rules/index.md index 0d3ea5c4b..113ff416b 100644 --- a/docs/rules/index.md +++ b/docs/rules/index.md @@ -48,7 +48,6 @@ Rules in this category are enabled for all presets provided by eslint-plugin-vue | [vue/no-computed-properties-in-data](./no-computed-properties-in-data.md) | disallow accessing computed properties in `data` | | :three::two::warning: | | [vue/no-custom-modifiers-on-v-model](./no-custom-modifiers-on-v-model.md) | disallow custom modifiers on v-model used on the component | | :two::warning: | | [vue/no-deprecated-data-object-declaration](./no-deprecated-data-object-declaration.md) | disallow using deprecated object declaration on data (in Vue.js 3.0.0+) | :wrench: | :three::warning: | -| [vue/no-deprecated-delete-set](./no-deprecated-delete-set.md) | disallow using deprecated `$delete` and `$set` (in Vue.js 3.0.0+) | | :three::warning: | | [vue/no-deprecated-destroyed-lifecycle](./no-deprecated-destroyed-lifecycle.md) | disallow using deprecated `destroyed` and `beforeDestroy` lifecycle hooks (in Vue.js 3.0.0+) | :wrench: | :three::warning: | | [vue/no-deprecated-dollar-listeners-api](./no-deprecated-dollar-listeners-api.md) | disallow using deprecated `$listeners` (in Vue.js 3.0.0+) | | :three::warning: | | [vue/no-deprecated-dollar-scopedslots-api](./no-deprecated-dollar-scopedslots-api.md) | disallow using deprecated `$scopedSlots` (in Vue.js 3.0.0+) | :wrench: | :three::warning: | diff --git a/docs/rules/no-deprecated-delete-set.md b/docs/rules/no-deprecated-delete-set.md index 20a33fe58..6bd4ed829 100644 --- a/docs/rules/no-deprecated-delete-set.md +++ b/docs/rules/no-deprecated-delete-set.md @@ -10,7 +10,6 @@ description: disallow using deprecated `$delete` and `$set` (in Vue.js 3.0.0+) > disallow using deprecated `$delete` and `$set` (in Vue.js 3.0.0+) - :exclamation: _**This rule has not been released yet.**_ -- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `*.configs["flat/essential"]`, `"plugin:vue/vue3-strongly-recommended"`, `*.configs["flat/strongly-recommended"]`, `"plugin:vue/vue3-recommended"` and `*.configs["flat/recommended"]`. ## :book: Rule Details @@ -20,7 +19,7 @@ This rule reports use of deprecated `$delete` and `$set`. (in Vue.js 3.0.0+). ```vue ` }, { @@ -104,10 +106,10 @@ tester.run('no-deprecated-delete-set', rule, { filename: 'test.vue', code: ` + `, + errors: [ + { + messageId: 'deprecated', + line: 5 + }, + { + messageId: 'deprecated', + line: 6 + } + ] + }, { filename: 'test.vue', code: ` + `, + errors: [ + { + messageId: 'deprecated', + line: 6 + }, + { + messageId: 'deprecated', + line: 7 + } + ] + }, { filename: 'test.vue', code: ` + `, + errors: [ + { + messageId: 'deprecated', + line: 5 + }, + { + messageId: 'deprecated', + line: 6 + } + ] } ] }) From 9bbf8b8ee1b5f6c72573c2b9c48f4aa11c9fc027 Mon Sep 17 00:00:00 2001 From: waynzh Date: Wed, 11 Sep 2024 19:25:03 +0800 Subject: [PATCH 6/6] refactor --- lib/rules/no-deprecated-delete-set.js | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/lib/rules/no-deprecated-delete-set.js b/lib/rules/no-deprecated-delete-set.js index 873654976..305dacf91 100644 --- a/lib/rules/no-deprecated-delete-set.js +++ b/lib/rules/no-deprecated-delete-set.js @@ -108,15 +108,6 @@ module.exports = { ...deletedImportApisMap } } - - for (const { node } of tracker.iterateEsmReferences(esmTraceMap)) { - const refNode = /** @type {CallExpression} */ (node) - context.report({ - node: refNode.callee, - messageId: 'deprecated' - }) - } - // const { set } = require('vue'); set() const cjsTraceMap = { vue: { @@ -124,7 +115,10 @@ module.exports = { } } - for (const { node } of tracker.iterateCjsReferences(cjsTraceMap)) { + for (const { node } of [ + ...tracker.iterateEsmReferences(esmTraceMap), + ...tracker.iterateCjsReferences(cjsTraceMap) + ]) { const refNode = /** @type {CallExpression} */ (node) context.report({ node: refNode.callee,