diff --git a/docs/rules/no-side-effects-in-computed-properties.md b/docs/rules/no-side-effects-in-computed-properties.md
index 7a3df6871..221be32d9 100644
--- a/docs/rules/no-side-effects-in-computed-properties.md
+++ b/docs/rules/no-side-effects-in-computed-properties.md
@@ -2,20 +2,20 @@
pageClass: rule-details
sidebarDepth: 0
title: vue/no-side-effects-in-computed-properties
-description: disallow side effects in computed properties
+description: disallow side effects in computed properties and functions
since: v3.6.0
---
# vue/no-side-effects-in-computed-properties
-> disallow side effects in computed properties
+> disallow side effects in computed properties and functions
- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/essential"`, `"plugin:vue/vue3-strongly-recommended"`, `"plugin:vue/strongly-recommended"`, `"plugin:vue/vue3-recommended"` and `"plugin:vue/recommended"`.
## :book: Rule Details
-This rule is aimed at preventing the code which makes side effects in computed properties.
+This rule is aimed at preventing the code which makes side effects in computed properties and functions.
-It is considered a very bad practice to introduce side effects inside computed properties. It makes the code not predictable and hard to understand.
+It is considered a very bad practice to introduce side effects inside computed properties and functions. It makes the code not predictable and hard to understand.
@@ -58,6 +58,51 @@ export default {
+
+
+```vue
+
+```
+
+
+
+
+
+```vue
+
+```
+
+
+
## :wrench: Options
Nothing.
diff --git a/lib/rules/no-side-effects-in-computed-properties.js b/lib/rules/no-side-effects-in-computed-properties.js
index 4d5c79e49..7c0639b95 100644
--- a/lib/rules/no-side-effects-in-computed-properties.js
+++ b/lib/rules/no-side-effects-in-computed-properties.js
@@ -3,7 +3,7 @@
* @author Michał Sajnóg
*/
'use strict'
-
+const { ReferenceTracker, findVariable } = require('eslint-utils')
const utils = require('../utils')
/**
@@ -31,6 +31,10 @@ module.exports = {
create(context) {
/** @type {Map} */
const computedPropertiesMap = new Map()
+ /** @type {Array} */
+ const computedCallNodes = []
+ /** @type {Array} */
+ const setupFunctions = []
/**
* @typedef {object} ScopeStack
@@ -54,56 +58,143 @@ module.exports = {
scopeStack = scopeStack && scopeStack.upper
}
- return utils.defineVueVisitor(context, {
- onVueObjectEnter(node) {
- computedPropertiesMap.set(node, utils.getComputedProperties(node))
- },
- ':function': onFunctionEnter,
- ':function:exit': onFunctionExit,
-
- /**
- * @param {(Identifier | ThisExpression) & {parent: MemberExpression}} node
- * @param {VueObjectData} data
- */
- 'MemberExpression > :matches(Identifier, ThisExpression)'(
- node,
- { node: vueNode }
- ) {
- if (!scopeStack) {
- return
- }
- const targetBody = scopeStack.body
- const computedProperty = /** @type {ComponentComputedProperty[]} */ (computedPropertiesMap.get(
- vueNode
- )).find((cp) => {
- return (
- cp.value &&
- node.loc.start.line >= cp.value.loc.start.line &&
- node.loc.end.line <= cp.value.loc.end.line &&
- targetBody === cp.value
- )
- })
- if (!computedProperty) {
- return
- }
+ return Object.assign(
+ {
+ Program() {
+ const tracker = new ReferenceTracker(context.getScope())
+ const traceMap = utils.createCompositionApiTraceMap({
+ [ReferenceTracker.ESM]: true,
+ computed: {
+ [ReferenceTracker.CALL]: true
+ }
+ })
- if (!utils.isThis(node, context)) {
- return
- }
- const mem = node.parent
- if (mem.object !== node) {
- return
+ for (const { node } of tracker.iterateEsmReferences(traceMap)) {
+ if (node.type !== 'CallExpression') {
+ continue
+ }
+
+ const getterBody = utils.getGetterBodyFromComputedFunction(node)
+ if (getterBody) {
+ computedCallNodes.push(getterBody)
+ }
+ }
}
+ },
+ utils.defineVueVisitor(context, {
+ onVueObjectEnter(node) {
+ computedPropertiesMap.set(node, utils.getComputedProperties(node))
+ },
+ ':function': onFunctionEnter,
+ ':function:exit': onFunctionExit,
+ onSetupFunctionEnter(node) {
+ setupFunctions.push(node)
+ },
- const invalid = utils.findMutating(mem)
- if (invalid) {
- context.report({
- node: invalid.node,
- message: 'Unexpected side effect in "{{key}}" computed property.',
- data: { key: computedProperty.key || 'Unknown' }
+ /**
+ * @param {(Identifier | ThisExpression) & {parent: MemberExpression}} node
+ * @param {VueObjectData} data
+ */
+ 'MemberExpression > :matches(Identifier, ThisExpression)'(
+ node,
+ { node: vueNode }
+ ) {
+ if (!scopeStack) {
+ return
+ }
+ const targetBody = scopeStack.body
+
+ const computedProperty = /** @type {ComponentComputedProperty[]} */ (computedPropertiesMap.get(
+ vueNode
+ )).find((cp) => {
+ return (
+ cp.value &&
+ node.loc.start.line >= cp.value.loc.start.line &&
+ node.loc.end.line <= cp.value.loc.end.line &&
+ targetBody === cp.value
+ )
})
+ if (computedProperty) {
+ if (!utils.isThis(node, context)) {
+ return
+ }
+ const mem = node.parent
+ if (mem.object !== node) {
+ return
+ }
+
+ const invalid = utils.findMutating(mem)
+ if (invalid) {
+ context.report({
+ node: invalid.node,
+ message:
+ 'Unexpected side effect in "{{key}}" computed property.',
+ data: { key: computedProperty.key || 'Unknown' }
+ })
+ }
+ return
+ }
+
+ // ignore `this` for computed functions
+ if (node.type === 'ThisExpression') {
+ return
+ }
+
+ const computedFunction = computedCallNodes.find(
+ (c) =>
+ node.loc.start.line >= c.loc.start.line &&
+ node.loc.end.line <= c.loc.end.line &&
+ targetBody === c.body
+ )
+ if (!computedFunction) {
+ return
+ }
+
+ const mem = node.parent
+ if (mem.object !== node) {
+ return
+ }
+
+ const variable = findVariable(context.getScope(), node)
+ if (!variable || variable.defs.length !== 1) {
+ return
+ }
+
+ const def = variable.defs[0]
+ if (
+ def.type === 'ImplicitGlobalVariable' ||
+ def.type === 'TDZ' ||
+ def.type === 'ImportBinding'
+ ) {
+ return
+ }
+
+ const isDeclaredInsideSetup = setupFunctions.some(
+ (setupFn) =>
+ def.node.loc.start.line >= setupFn.loc.start.line &&
+ def.node.loc.end.line <= setupFn.loc.end.line
+ )
+ if (!isDeclaredInsideSetup) {
+ return
+ }
+
+ if (
+ def.node.loc.start.line >= computedFunction.loc.start.line &&
+ def.node.loc.end.line <= computedFunction.loc.end.line
+ ) {
+ // mutating local variables are accepted
+ return
+ }
+
+ const invalid = utils.findMutating(node)
+ if (invalid) {
+ context.report({
+ node: invalid.node,
+ message: 'Unexpected side effect in computed function.'
+ })
+ }
}
- }
- })
+ })
+ )
}
}
diff --git a/tests/lib/rules/no-side-effects-in-computed-properties.js b/tests/lib/rules/no-side-effects-in-computed-properties.js
index ae5ff7512..647910d4a 100644
--- a/tests/lib/rules/no-side-effects-in-computed-properties.js
+++ b/tests/lib/rules/no-side-effects-in-computed-properties.js
@@ -20,7 +20,11 @@ const parserOptions = {
// Tests
// ------------------------------------------------------------------------------
-const ruleTester = new RuleTester()
+const ruleTester = new RuleTester({
+ parser: require.resolve('vue-eslint-parser'),
+ parserOptions
+})
+
ruleTester.run('no-side-effects-in-computed-properties', rule, {
valid: [
{
@@ -101,8 +105,7 @@ ruleTester.run('no-side-effects-in-computed-properties', rule, {
this.someArray.forEach(arr => console.log(arr))
}
}
- })`,
- parserOptions
+ })`
},
{
code: `Vue.component('test', {
@@ -120,8 +123,7 @@ ruleTester.run('no-side-effects-in-computed-properties', rule, {
return something.b
}
}
- })`,
- parserOptions
+ })`
},
{
code: `Vue.component('test', {
@@ -129,8 +131,7 @@ ruleTester.run('no-side-effects-in-computed-properties', rule, {
data() {
return {}
}
- })`,
- parserOptions
+ })`
},
{
code: `Vue.component('test', {
@@ -141,8 +142,7 @@ ruleTester.run('no-side-effects-in-computed-properties', rule, {
return a
},
}
- })`,
- parserOptions
+ })`
},
{
code: `Vue.component('test', {
@@ -161,8 +161,7 @@ ruleTester.run('no-side-effects-in-computed-properties', rule, {
}
},
}
- })`,
- parserOptions
+ })`
},
{
code: `Vue.component('test', {
@@ -171,15 +170,13 @@ ruleTester.run('no-side-effects-in-computed-properties', rule, {
return this.something['a']().reverse()
},
}
- })`,
- parserOptions
+ })`
},
{
code: `const test = { el: '#app' }
Vue.component('test', {
el: test.el
- })`,
- parserOptions
+ })`
},
{
code: `Vue.component('test', {
@@ -188,8 +185,92 @@ ruleTester.run('no-side-effects-in-computed-properties', rule, {
return [...this.items].reverse()
},
}
- })`,
- parserOptions
+ })`
+ },
+ {
+ filename: 'test.vue',
+ code: `
+ `
}
],
invalid: [
@@ -222,7 +303,6 @@ ruleTester.run('no-side-effects-in-computed-properties', rule, {
}
}
})`,
- parserOptions,
errors: [
{
line: 4,
@@ -286,7 +366,6 @@ ruleTester.run('no-side-effects-in-computed-properties', rule, {
},
}
})`,
- parserOptions,
errors: [
{
line: 5,
@@ -321,7 +400,6 @@ ruleTester.run('no-side-effects-in-computed-properties', rule, {
}
});
`,
- parserOptions,
errors: [
{
line: 5,
@@ -341,7 +419,6 @@ ruleTester.run('no-side-effects-in-computed-properties', rule, {
},
}
})`,
- parserOptions,
errors: [
{
line: 4,
@@ -363,12 +440,157 @@ ruleTester.run('no-side-effects-in-computed-properties', rule, {
},
}
})`,
- parserOptions,
errors: [
'Unexpected side effect in "test1" computed property.',
'Unexpected side effect in "test2" computed property.',
'Unexpected side effect in "test3" computed property.'
]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ errors: [
+ {
+ line: 12,
+ message: 'Unexpected side effect in computed function.'
+ },
+ {
+ line: 13,
+ message: 'Unexpected side effect in computed function.'
+ },
+ {
+ line: 17,
+ message: 'Unexpected side effect in computed function.'
+ },
+ {
+ line: 18,
+ message: 'Unexpected side effect in computed function.'
+ },
+ {
+ line: 21,
+ message: 'Unexpected side effect in computed function.'
+ },
+ {
+ line: 23,
+ message: 'Unexpected side effect in computed function.'
+ },
+ {
+ line: 27,
+ message: 'Unexpected side effect in computed function.'
+ },
+ {
+ line: 30,
+ message: 'Unexpected side effect in computed function.'
+ },
+ {
+ line: 33,
+ message: 'Unexpected side effect in computed function.'
+ },
+ {
+ line: 37,
+ message: 'Unexpected side effect in computed function.'
+ },
+ {
+ line: 40,
+ message: 'Unexpected side effect in computed function.'
+ },
+ {
+ line: 42,
+ message: 'Unexpected side effect in computed function.'
+ }
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ errors: [
+ {
+ line: 8,
+ message: 'Unexpected side effect in computed function.'
+ }
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ errors: [
+ {
+ line: 8,
+ message: 'Unexpected side effect in computed function.'
+ }
+ ]
}
]
})