diff --git a/.changeset/hot-beers-help.md b/.changeset/hot-beers-help.md new file mode 100644 index 000000000..b32ab0ad2 --- /dev/null +++ b/.changeset/hot-beers-help.md @@ -0,0 +1,5 @@ +--- +'eslint-plugin-vue': minor +--- + +Changed `vue/no-mutating-props` and `vue/no-side-effects-in-computed-properties` rules to detect `Object.assign` mutations diff --git a/lib/utils/index.js b/lib/utils/index.js index e47dc4acf..dabdc2ba3 100644 --- a/lib/utils/index.js +++ b/lib/utils/index.js @@ -2068,6 +2068,26 @@ module.exports = { return false }, + /** + * Check if a call expression is Object.assign with the given node as first argument + * @param {CallExpression} callExpr + * @param {ASTNode} targetNode + * @returns {boolean} + */ + isObjectAssignCall(callExpr, targetNode) { + const { callee, arguments: args } = callExpr + + return ( + args.length > 0 && + args[0] === targetNode && + callee?.type === 'MemberExpression' && + callee.object?.type === 'Identifier' && + callee.object.name === 'Object' && + callee.property?.type === 'Identifier' && + callee.property.name === 'assign' + ) + }, + /** * @param {MemberExpression|Identifier} props * @returns { { kind: 'assignment' | 'update' | 'call' , node: ESNode, pathNodes: MemberExpression[] } | null } @@ -2128,6 +2148,14 @@ module.exports = { } } } + if (this.isObjectAssignCall(target, node)) { + // Object.assign(xxx, {}) + return { + kind: 'call', + node: target, + pathNodes + } + } break } case 'MemberExpression': { diff --git a/tests/lib/rules/no-mutating-props.js b/tests/lib/rules/no-mutating-props.js index d4584b57c..1448ef22a 100644 --- a/tests/lib/rules/no-mutating-props.js +++ b/tests/lib/rules/no-mutating-props.js @@ -413,6 +413,22 @@ ruleTester.run('no-mutating-props', rule, { const foo = ref('') ` + }, + { + // Object.assign not mutating the prop + filename: 'test.vue', + code: ` + + ` } ], @@ -1420,6 +1436,31 @@ ruleTester.run('no-mutating-props', rule, { endColumn: 21 } ] + }, + { + // Object.assign mutating the prop as first argument + filename: 'test.vue', + code: ` + + `, + errors: [ + { + message: 'Unexpected mutation of "data" prop.', + line: 7, + column: 24, + endLine: 7, + endColumn: 68 + } + ] } ] }) 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 2d0a124a4..7ba23f875 100644 --- a/tests/lib/rules/no-side-effects-in-computed-properties.js +++ b/tests/lib/rules/no-side-effects-in-computed-properties.js @@ -259,6 +259,7 @@ ruleTester.run('no-side-effects-in-computed-properties', rule, { }) const test18 = computed(() => (console.log('a'), true)) const test19 = computed(() => utils.reverse(foo.array)) + const test20 = computed(() => Object.assign({}, foo.data, { extra: 'value' })) } } ` @@ -889,6 +890,54 @@ ruleTester.run('no-side-effects-in-computed-properties', rule, { endColumn: 59 } ] + }, + { + // Object.assign mutating the prop as first argument in computed properties + filename: 'test.vue', + code: ` + + `, + errors: [ + { + message: 'Unexpected side effect in computed function.', + line: 8, + column: 40, + endLine: 8, + endColumn: 83 + }, + { + message: 'Unexpected side effect in computed function.', + line: 10, + column: 20, + endLine: 10, + endColumn: 56 + }, + { + message: 'Unexpected side effect in computed function.', + line: 14, + column: 15, + endLine: 14, + endColumn: 61 + } + ] } ] })