Skip to content

Commit

Permalink
Improve the vue/no-setup-props-destructure rule (#2244)
Browse files Browse the repository at this point in the history
  • Loading branch information
davidmyersdev committed Jul 29, 2023
1 parent b8814c7 commit 684c847
Show file tree
Hide file tree
Showing 2 changed files with 213 additions and 1 deletion.
61 changes: 60 additions & 1 deletion lib/rules/no-setup-props-destructure.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@ module.exports = {
'Getting a value from the `props` in root scope of `{{scopeName}}` will cause the value to lose reactivity.'
}
},
/** @param {RuleContext} context */
/**
* @param {RuleContext} context
* @returns {RuleListener}
**/
create(context) {
/**
* @typedef {object} ScopePropsReferences
Expand All @@ -32,6 +35,10 @@ module.exports = {
*/
/** @type {Map<FunctionDeclaration | FunctionExpression | ArrowFunctionExpression | Program, ScopePropsReferences>} */
const setupScopePropsReferenceIds = new Map()
const wrapperExpressionTypes = new Set([
'ArrayExpression',
'ObjectExpression'
])

/**
* @param {ESNode} node
Expand Down Expand Up @@ -59,13 +66,22 @@ module.exports = {
}

const rightNode = utils.skipChainExpression(right)

if (
wrapperExpressionTypes.has(rightNode.type) &&
isPropsMemberAccessed(rightNode, propsReferences)
) {
return report(rightNode, 'getProperty', propsReferences.scopeName)
}

if (
left.type !== 'ArrayPattern' &&
left.type !== 'ObjectPattern' &&
rightNode.type !== 'MemberExpression'
) {
return
}

/** @type {Expression | Super} */
let rightId = rightNode
while (rightId.type === 'MemberExpression') {
Expand All @@ -75,6 +91,24 @@ module.exports = {
report(left, 'getProperty', propsReferences.scopeName)
}
}

/**
* @param {Expression} node
* @param {ScopePropsReferences} propsReferences
*/
function isPropsMemberAccessed(node, propsReferences) {
const propRefs = [...propsReferences.refs.values()]

return propRefs.some((props) => {
const isPropsInExpressionRange = utils.inRange(node.range, props)
const isPropsMemberExpression =
props.parent.type === 'MemberExpression' &&
props.parent.object === props

return isPropsInExpressionRange && isPropsMemberExpression
})
}

/**
* @typedef {object} ScopeStack
* @property {ScopeStack | null} upper
Expand Down Expand Up @@ -114,6 +148,11 @@ module.exports = {
}
const propsReferenceIds = new Set()
for (const reference of variable.references) {
// If reference is in another scope, we can't check it.
if (reference.from !== context.getScope()) {
continue
}

if (!reference.isRead()) {
continue
}
Expand Down Expand Up @@ -144,6 +183,26 @@ module.exports = {

setupScopePropsReferenceIds.delete(node)
},
/**
* @param {CallExpression} node
*/
CallExpression(node) {
if (!scopeStack) {
return
}

const propsReferenceIds = setupScopePropsReferenceIds.get(
scopeStack.scopeNode
)

if (!propsReferenceIds) {
return
}

if (isPropsMemberAccessed(node, propsReferenceIds)) {
report(node, 'getProperty', propsReferenceIds.scopeName)
}
},
/**
* @param {VariableDeclarator} node
*/
Expand Down
153 changes: 153 additions & 0 deletions tests/lib/rules/no-setup-props-destructure.js
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,27 @@ tester.run('no-setup-props-destructure', rule, {
line: 4
}
]
},
{
filename: 'test.vue',
code: `
<script>
export default {
setup: (props) => {
const count = computed(() => props.count)
}
}
</script>
`
},
{
filename: 'test.vue',
code: `
<script setup>
const props = defineProps({ count: Number })
const count = computed(() => props.count)
</script>
`
}
],
invalid: [
Expand Down Expand Up @@ -410,6 +431,18 @@ tester.run('no-setup-props-destructure', rule, {
{
messageId: 'getProperty',
line: 7
},
{
messageId: 'getProperty',
line: 9
},
{
messageId: 'getProperty',
line: 10
},
{
messageId: 'getProperty',
line: 11
}
]
},
Expand Down Expand Up @@ -524,6 +557,126 @@ tester.run('no-setup-props-destructure', rule, {
line: 5
}
]
},
{
filename: 'test.vue',
code: `
<script>
export default {
setup: (props) => {
const count = ref(props.count)
count = fn(props.count)
}
}
</script>
`,
errors: [
{
messageId: 'getProperty',
line: 5
},
{
messageId: 'getProperty',
line: 6
}
]
},
{
filename: 'test.vue',
code: `
<script setup>
const props = defineProps({ count: Number })
const count = ref(props.count)
count = fn(props.count)
</script>
`,
errors: [
{
messageId: 'getProperty',
line: 4
},
{
messageId: 'getProperty',
line: 5
}
]
},
{
filename: 'test.vue',
code: `
<script setup>
const props = defineProps({ count: Number })
const newProps = ref({ count: props.count })
</script>
`,
errors: [
{
messageId: 'getProperty',
line: 4
}
]
},
{
filename: 'test.vue',
code: `
<script setup>
const props = defineProps({ count: Number })
const counts = [props.count]
</script>
`,
errors: [
{
messageId: 'getProperty',
line: 4
}
]
},
{
filename: 'test.vue',
code: `
<script setup>
const props = defineProps({ count: Number })
const counter = { count: props.count }
</script>
`,
errors: [
{
messageId: 'getProperty',
line: 4
}
]
},
{
filename: 'test.vue',
code: `
<script setup>
const props = defineProps({ count: Number })
const counters = [{ count: [props.count] }]
</script>
`,
errors: [
{
messageId: 'getProperty',
line: 4
}
]
},
{
filename: 'test.vue',
code: `
<script setup>
const props = defineProps({ count: Number })
const buildCounter = (count) => ({ count })
buildCounter(props.count)
</script>
`,
errors: [
{
messageId: 'getProperty',
line: 6
}
]
}
]
})

0 comments on commit 684c847

Please sign in to comment.