Skip to content

Commit

Permalink
Fix false positives and false negatives in vue/no-mutating-props ru…
Browse files Browse the repository at this point in the history
…le. (#1715)
  • Loading branch information
ota-meshi committed Nov 18, 2021
1 parent e0dddb1 commit ff47c73
Show file tree
Hide file tree
Showing 2 changed files with 166 additions and 2 deletions.
73 changes: 71 additions & 2 deletions lib/rules/no-mutating-props.js
Expand Up @@ -11,6 +11,34 @@ const { findVariable } = require('eslint-utils')
// Rule Definition
// ------------------------------------------------------------------------------

// https://github.com/vuejs/vue-next/blob/7c11c58faf8840ab97b6449c98da0296a60dddd8/packages/shared/src/globalsWhitelist.ts
const GLOBALS_WHITE_LISTED = new Set([
'Infinity',
'undefined',
'NaN',
'isFinite',
'isNaN',
'parseFloat',
'parseInt',
'decodeURI',
'decodeURIComponent',
'encodeURI',
'encodeURIComponent',
'Math',
'Number',
'Date',
'Array',
'Object',
'Boolean',
'String',
'RegExp',
'Map',
'Set',
'JSON',
'Intl',
'BigInt'
])

module.exports = {
meta: {
type: 'suggestion',
Expand Down Expand Up @@ -191,12 +219,43 @@ module.exports = {
}
}

function* extractDefineVariableNames() {
const globalScope = context.getSourceCode().scopeManager.globalScope
if (globalScope) {
for (const variable of globalScope.variables) {
if (variable.defs.length) {
yield variable.name
}
}
const moduleScope = globalScope.childScopes.find(
(scope) => scope.type === 'module'
)
for (const variable of (moduleScope && moduleScope.variables) || []) {
if (variable.defs.length) {
yield variable.name
}
}
}
}

return utils.compositingVisitors(
{},
utils.defineScriptSetupVisitor(context, {
onDefinePropsEnter(node, props) {
const defineVariableNames = new Set(extractDefineVariableNames())

const propsSet = new Set(
props.map((p) => p.propName).filter(utils.isDef)
props
.map((p) => p.propName)
.filter(
/**
* @returns {propName is string}
*/
(propName) =>
utils.isDef(propName) &&
!GLOBALS_WHITE_LISTED.has(propName) &&
!defineVariableNames.has(propName)
)
)
propsMap.set(node, propsSet)
vueObjectData = {
Expand Down Expand Up @@ -337,12 +396,22 @@ module.exports = {
}
},
/** @param {ESNode} node */
"VAttribute[directive=true][key.name.name='model'] VExpressionContainer > *"(
"VAttribute[directive=true]:matches([key.name.name='model'], [key.name.name='bind']) VExpressionContainer > *"(
node
) {
if (!vueObjectData) {
return
}
let attr = node.parent
while (attr && attr.type !== 'VAttribute') {
attr = attr.parent
}
if (attr && attr.directive && attr.key.name.name === 'bind') {
if (!attr.key.modifiers.some((mod) => mod.name === 'sync')) {
return
}
}

const nodes = utils.getMemberChaining(node)
const first = nodes[0]
let name
Expand Down
95 changes: 95 additions & 0 deletions tests/lib/rules/no-mutating-props.js
Expand Up @@ -331,6 +331,28 @@ ruleTester.run('no-mutating-props', rule, {
}
}
</script>`
},

{
// script setup with shadow
filename: 'test.vue',
code: `
<template>
<input v-model="foo">
<input v-model="bar">
<input v-model="Infinity">
</template>
<script setup>
import { ref } from 'vue'
import { bar } from './my-script'
defineProps({
foo: String,
bar: String,
Infinity: Number
})
const foo = ref('')
</script>
`
}
],

Expand Down Expand Up @@ -582,6 +604,42 @@ ruleTester.run('no-mutating-props', rule, {
}
]
},
{
filename: 'test.vue',
code: `
<template>
<div>
<MyComponent :data.sync="this.prop" />
<MyComponent :data.sync="prop" />
<MyComponent :data="this.prop" />
<MyComponent :data="prop" />
<template v-for="prop of data">
<MyComponent :data.sync="prop" />
<MyComponent :data.sync="this.prop" />
</template>
</div>
</template>
<script>
export default {
props: ['prop']
}
</script>
`,
errors: [
{
message: 'Unexpected mutation of "prop" prop.',
line: 4
},
{
message: 'Unexpected mutation of "prop" prop.',
line: 5
},
{
message: 'Unexpected mutation of "prop" prop.',
line: 10
}
]
},

// setup
{
Expand Down Expand Up @@ -817,6 +875,43 @@ ruleTester.run('no-mutating-props', rule, {
line: 6
}
]
},

{
// script setup with shadow
filename: 'test.vue',
code: `
<template>
<input v-model="foo">
<input v-model="bar">
<input v-model="window">
<input v-model="Infinity">
</template>
<script setup>
import { ref } from 'vue'
const { Infinity } = defineProps({
foo: String,
bar: String,
Infinity: String,
window: String,
})
const foo = ref('')
</script>
`,
errors: [
{
message: 'Unexpected mutation of "bar" prop.',
line: 4
},
{
message: 'Unexpected mutation of "window" prop.',
line: 5
},
{
message: 'Unexpected mutation of "Infinity" prop.',
line: 6
}
]
}
]
})

0 comments on commit ff47c73

Please sign in to comment.