Skip to content

Commit f3145a9

Browse files
committed
fix(compiler-sfc): check binding is prop before erroring
fix #8017
1 parent 9a09e47 commit f3145a9

File tree

2 files changed

+58
-52
lines changed

2 files changed

+58
-52
lines changed

packages/compiler-sfc/__tests__/compileScriptPropsDestructure.spec.ts

+15
Original file line numberDiff line numberDiff line change
@@ -354,5 +354,20 @@ describe('sfc props transform', () => {
354354
)
355355
).toThrow(`Default value of prop "foo" does not match declared type.`)
356356
})
357+
358+
// #8017
359+
test('should not throw an error if the variable is not a props', () => {
360+
expect(() =>
361+
compile(
362+
`<script setup lang='ts'>
363+
import { watch } from 'vue'
364+
const { userId } = defineProps({ userId: Number })
365+
const { error: e, info } = useRequest();
366+
watch(e, () => {});
367+
watch(info, () => {});
368+
</script>`
369+
)
370+
).not.toThrowError()
371+
})
357372
})
358373
})

packages/compiler-sfc/src/compileScriptPropsDestructure.ts

+43-52
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import {
1717
isCallOf,
1818
unwrapTSNode
1919
} from '@vue/compiler-core'
20-
import { hasOwn, genPropsAccessExp } from '@vue/shared'
20+
import { genPropsAccessExp } from '@vue/shared'
2121
import { PropsDestructureBindings } from './compileScript'
2222

2323
/**
@@ -47,6 +47,15 @@ export function transformDestructuredProps(
4747
propsLocalToPublicMap[local] = key
4848
}
4949

50+
function pushScope() {
51+
scopeStack.push((currentScope = Object.create(currentScope)))
52+
}
53+
54+
function popScope() {
55+
scopeStack.pop()
56+
currentScope = scopeStack[scopeStack.length - 1] || null
57+
}
58+
5059
function registerLocalBinding(id: Identifier) {
5160
excludedIds.add(id)
5261
if (currentScope) {
@@ -108,54 +117,41 @@ export function transformDestructuredProps(
108117
}
109118
}
110119

111-
function rewriteId(
112-
scope: Scope,
113-
id: Identifier,
114-
parent: Node,
115-
parentStack: Node[]
116-
): boolean {
117-
if (hasOwn(scope, id.name)) {
118-
const binding = scope[id.name]
119-
120-
if (binding) {
121-
if (
122-
(parent.type === 'AssignmentExpression' && id === parent.left) ||
123-
parent.type === 'UpdateExpression'
124-
) {
125-
error(`Cannot assign to destructured props as they are readonly.`, id)
126-
}
120+
function rewriteId(id: Identifier, parent: Node, parentStack: Node[]) {
121+
if (
122+
(parent.type === 'AssignmentExpression' && id === parent.left) ||
123+
parent.type === 'UpdateExpression'
124+
) {
125+
error(`Cannot assign to destructured props as they are readonly.`, id)
126+
}
127127

128-
if (isStaticProperty(parent) && parent.shorthand) {
129-
// let binding used in a property shorthand
130-
// skip for destructure patterns
131-
if (
132-
!(parent as any).inPattern ||
133-
isInDestructureAssignment(parent, parentStack)
134-
) {
135-
// { prop } -> { prop: __props.prop }
136-
s.appendLeft(
137-
id.end! + offset,
138-
`: ${genPropsAccessExp(propsLocalToPublicMap[id.name])}`
139-
)
140-
}
141-
} else {
142-
// x --> __props.x
143-
s.overwrite(
144-
id.start! + offset,
145-
id.end! + offset,
146-
genPropsAccessExp(propsLocalToPublicMap[id.name])
147-
)
148-
}
128+
if (isStaticProperty(parent) && parent.shorthand) {
129+
// let binding used in a property shorthand
130+
// skip for destructure patterns
131+
if (
132+
!(parent as any).inPattern ||
133+
isInDestructureAssignment(parent, parentStack)
134+
) {
135+
// { prop } -> { prop: __props.prop }
136+
s.appendLeft(
137+
id.end! + offset,
138+
`: ${genPropsAccessExp(propsLocalToPublicMap[id.name])}`
139+
)
149140
}
150-
return true
141+
} else {
142+
// x --> __props.x
143+
s.overwrite(
144+
id.start! + offset,
145+
id.end! + offset,
146+
genPropsAccessExp(propsLocalToPublicMap[id.name])
147+
)
151148
}
152-
return false
153149
}
154150

155151
function checkUsage(node: Node, method: string, alias = method) {
156152
if (isCallOf(node, alias)) {
157153
const arg = unwrapTSNode(node.arguments[0])
158-
if (arg.type === 'Identifier') {
154+
if (arg.type === 'Identifier' && currentScope[arg.name]) {
159155
error(
160156
`"${arg.name}" is a destructured prop and should not be passed directly to ${method}(). ` +
161157
`Pass a getter () => ${arg.name} instead.`,
@@ -187,7 +183,7 @@ export function transformDestructuredProps(
187183

188184
// function scopes
189185
if (isFunctionType(node)) {
190-
scopeStack.push((currentScope = {}))
186+
pushScope()
191187
walkFunctionParams(node, registerLocalBinding)
192188
if (node.body.type === 'BlockStatement') {
193189
walkScope(node.body)
@@ -197,7 +193,7 @@ export function transformDestructuredProps(
197193

198194
// catch param
199195
if (node.type === 'CatchClause') {
200-
scopeStack.push((currentScope = {}))
196+
pushScope()
201197
if (node.param && node.param.type === 'Identifier') {
202198
registerLocalBinding(node.param)
203199
}
@@ -207,7 +203,7 @@ export function transformDestructuredProps(
207203

208204
// non-function block scopes
209205
if (node.type === 'BlockStatement' && !isFunctionType(parent!)) {
210-
scopeStack.push((currentScope = {}))
206+
pushScope()
211207
walkScope(node)
212208
return
213209
}
@@ -217,12 +213,8 @@ export function transformDestructuredProps(
217213
isReferencedIdentifier(node, parent!, parentStack) &&
218214
!excludedIds.has(node)
219215
) {
220-
// walk up the scope chain to check if id should be appended .value
221-
let i = scopeStack.length
222-
while (i--) {
223-
if (rewriteId(scopeStack[i], node, parent!, parentStack)) {
224-
return
225-
}
216+
if (currentScope[node.name]) {
217+
rewriteId(node, parent!, parentStack)
226218
}
227219
}
228220
}
@@ -233,8 +225,7 @@ export function transformDestructuredProps(
233225
(node.type === 'BlockStatement' && !isFunctionType(parent!)) ||
234226
isFunctionType(node)
235227
) {
236-
scopeStack.pop()
237-
currentScope = scopeStack[scopeStack.length - 1] || null
228+
popScope()
238229
}
239230
}
240231
})

0 commit comments

Comments
 (0)