Skip to content

Commit 7aaba4d

Browse files
waynzhCopilot
andauthored
feat: Object.assign detection to mutation rules (#2929)
Co-authored-by: Copilot <[email protected]>
1 parent 79d6a31 commit 7aaba4d

File tree

4 files changed

+123
-0
lines changed

4 files changed

+123
-0
lines changed

.changeset/hot-beers-help.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'eslint-plugin-vue': minor
3+
---
4+
5+
Changed `vue/no-mutating-props` and `vue/no-side-effects-in-computed-properties` rules to detect `Object.assign` mutations

lib/utils/index.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2068,6 +2068,26 @@ module.exports = {
20682068
return false
20692069
},
20702070

2071+
/**
2072+
* Check if a call expression is Object.assign with the given node as first argument
2073+
* @param {CallExpression} callExpr
2074+
* @param {ASTNode} targetNode
2075+
* @returns {boolean}
2076+
*/
2077+
isObjectAssignCall(callExpr, targetNode) {
2078+
const { callee, arguments: args } = callExpr
2079+
2080+
return (
2081+
args.length > 0 &&
2082+
args[0] === targetNode &&
2083+
callee?.type === 'MemberExpression' &&
2084+
callee.object?.type === 'Identifier' &&
2085+
callee.object.name === 'Object' &&
2086+
callee.property?.type === 'Identifier' &&
2087+
callee.property.name === 'assign'
2088+
)
2089+
},
2090+
20712091
/**
20722092
* @param {MemberExpression|Identifier} props
20732093
* @returns { { kind: 'assignment' | 'update' | 'call' , node: ESNode, pathNodes: MemberExpression[] } | null }
@@ -2128,6 +2148,14 @@ module.exports = {
21282148
}
21292149
}
21302150
}
2151+
if (this.isObjectAssignCall(target, node)) {
2152+
// Object.assign(xxx, {})
2153+
return {
2154+
kind: 'call',
2155+
node: target,
2156+
pathNodes
2157+
}
2158+
}
21312159
break
21322160
}
21332161
case 'MemberExpression': {

tests/lib/rules/no-mutating-props.js

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,22 @@ ruleTester.run('no-mutating-props', rule, {
413413
const foo = ref('')
414414
</script>
415415
`
416+
},
417+
{
418+
// Object.assign not mutating the prop
419+
filename: 'test.vue',
420+
code: `
421+
<script>
422+
export default {
423+
props: ['data'],
424+
methods: {
425+
update() {
426+
return Object.assign({}, this.data, { extra: 'value' })
427+
}
428+
}
429+
}
430+
</script>
431+
`
416432
}
417433
],
418434

@@ -1420,6 +1436,31 @@ ruleTester.run('no-mutating-props', rule, {
14201436
endColumn: 21
14211437
}
14221438
]
1439+
},
1440+
{
1441+
// Object.assign mutating the prop as first argument
1442+
filename: 'test.vue',
1443+
code: `
1444+
<script>
1445+
export default {
1446+
props: ['data'],
1447+
methods: {
1448+
update() {
1449+
return Object.assign(this.data, { extra: 'value' })
1450+
}
1451+
}
1452+
}
1453+
</script>
1454+
`,
1455+
errors: [
1456+
{
1457+
message: 'Unexpected mutation of "data" prop.',
1458+
line: 7,
1459+
column: 24,
1460+
endLine: 7,
1461+
endColumn: 68
1462+
}
1463+
]
14231464
}
14241465
]
14251466
})

tests/lib/rules/no-side-effects-in-computed-properties.js

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,7 @@ ruleTester.run('no-side-effects-in-computed-properties', rule, {
259259
})
260260
const test18 = computed(() => (console.log('a'), true))
261261
const test19 = computed(() => utils.reverse(foo.array))
262+
const test20 = computed(() => Object.assign({}, foo.data, { extra: 'value' }))
262263
}
263264
}
264265
</script>`
@@ -889,6 +890,54 @@ ruleTester.run('no-side-effects-in-computed-properties', rule, {
889890
endColumn: 59
890891
}
891892
]
893+
},
894+
{
895+
// Object.assign mutating the prop as first argument in computed properties
896+
filename: 'test.vue',
897+
code: `
898+
<script>
899+
import {ref, computed} from 'vue'
900+
export default {
901+
setup() {
902+
const foo = useFoo()
903+
904+
const test1 = computed(() => Object.assign(foo.data, { extra: 'value' }))
905+
const test2 = computed(() => {
906+
return Object.assign(foo.user, foo.updates)
907+
})
908+
const test3 = computed({
909+
get() {
910+
Object.assign(foo.settings, { theme: 'dark' })
911+
return foo.settings
912+
}
913+
})
914+
}
915+
}
916+
</script>
917+
`,
918+
errors: [
919+
{
920+
message: 'Unexpected side effect in computed function.',
921+
line: 8,
922+
column: 40,
923+
endLine: 8,
924+
endColumn: 83
925+
},
926+
{
927+
message: 'Unexpected side effect in computed function.',
928+
line: 10,
929+
column: 20,
930+
endLine: 10,
931+
endColumn: 56
932+
},
933+
{
934+
message: 'Unexpected side effect in computed function.',
935+
line: 14,
936+
column: 15,
937+
endLine: 14,
938+
endColumn: 61
939+
}
940+
]
892941
}
893942
]
894943
})

0 commit comments

Comments
 (0)