fix(compiler-sfc): check binding is prop before erroring

fix #8017
This commit is contained in:
Evan You 2023-04-04 18:10:13 +08:00
parent 9a09e47667
commit f3145a915a
2 changed files with 60 additions and 54 deletions

View File

@ -354,5 +354,20 @@ describe('sfc props transform', () => {
) )
).toThrow(`Default value of prop "foo" does not match declared type.`) ).toThrow(`Default value of prop "foo" does not match declared type.`)
}) })
// #8017
test('should not throw an error if the variable is not a props', () => {
expect(() =>
compile(
`<script setup lang='ts'>
import { watch } from 'vue'
const { userId } = defineProps({ userId: Number })
const { error: e, info } = useRequest();
watch(e, () => {});
watch(info, () => {});
</script>`
)
).not.toThrowError()
})
}) })
}) })

View File

@ -17,7 +17,7 @@ import {
isCallOf, isCallOf,
unwrapTSNode unwrapTSNode
} from '@vue/compiler-core' } from '@vue/compiler-core'
import { hasOwn, genPropsAccessExp } from '@vue/shared' import { genPropsAccessExp } from '@vue/shared'
import { PropsDestructureBindings } from './compileScript' import { PropsDestructureBindings } from './compileScript'
/** /**
@ -47,6 +47,15 @@ export function transformDestructuredProps(
propsLocalToPublicMap[local] = key propsLocalToPublicMap[local] = key
} }
function pushScope() {
scopeStack.push((currentScope = Object.create(currentScope)))
}
function popScope() {
scopeStack.pop()
currentScope = scopeStack[scopeStack.length - 1] || null
}
function registerLocalBinding(id: Identifier) { function registerLocalBinding(id: Identifier) {
excludedIds.add(id) excludedIds.add(id)
if (currentScope) { if (currentScope) {
@ -108,54 +117,41 @@ export function transformDestructuredProps(
} }
} }
function rewriteId( function rewriteId(id: Identifier, parent: Node, parentStack: Node[]) {
scope: Scope, if (
id: Identifier, (parent.type === 'AssignmentExpression' && id === parent.left) ||
parent: Node, parent.type === 'UpdateExpression'
parentStack: Node[] ) {
): boolean { error(`Cannot assign to destructured props as they are readonly.`, id)
if (hasOwn(scope, id.name)) { }
const binding = scope[id.name]
if (isStaticProperty(parent) && parent.shorthand) {
if (binding) { // let binding used in a property shorthand
if ( // skip for destructure patterns
(parent.type === 'AssignmentExpression' && id === parent.left) || if (
parent.type === 'UpdateExpression' !(parent as any).inPattern ||
) { isInDestructureAssignment(parent, parentStack)
error(`Cannot assign to destructured props as they are readonly.`, id) ) {
} // { prop } -> { prop: __props.prop }
s.appendLeft(
if (isStaticProperty(parent) && parent.shorthand) { id.end! + offset,
// let binding used in a property shorthand `: ${genPropsAccessExp(propsLocalToPublicMap[id.name])}`
// skip for destructure patterns )
if ( }
!(parent as any).inPattern || } else {
isInDestructureAssignment(parent, parentStack) // x --> __props.x
) { s.overwrite(
// { prop } -> { prop: __props.prop } id.start! + offset,
s.appendLeft( id.end! + offset,
id.end! + offset, genPropsAccessExp(propsLocalToPublicMap[id.name])
`: ${genPropsAccessExp(propsLocalToPublicMap[id.name])}` )
)
}
} else {
// x --> __props.x
s.overwrite(
id.start! + offset,
id.end! + offset,
genPropsAccessExp(propsLocalToPublicMap[id.name])
)
}
}
return true
} }
return false
} }
function checkUsage(node: Node, method: string, alias = method) { function checkUsage(node: Node, method: string, alias = method) {
if (isCallOf(node, alias)) { if (isCallOf(node, alias)) {
const arg = unwrapTSNode(node.arguments[0]) const arg = unwrapTSNode(node.arguments[0])
if (arg.type === 'Identifier') { if (arg.type === 'Identifier' && currentScope[arg.name]) {
error( error(
`"${arg.name}" is a destructured prop and should not be passed directly to ${method}(). ` + `"${arg.name}" is a destructured prop and should not be passed directly to ${method}(). ` +
`Pass a getter () => ${arg.name} instead.`, `Pass a getter () => ${arg.name} instead.`,
@ -187,7 +183,7 @@ export function transformDestructuredProps(
// function scopes // function scopes
if (isFunctionType(node)) { if (isFunctionType(node)) {
scopeStack.push((currentScope = {})) pushScope()
walkFunctionParams(node, registerLocalBinding) walkFunctionParams(node, registerLocalBinding)
if (node.body.type === 'BlockStatement') { if (node.body.type === 'BlockStatement') {
walkScope(node.body) walkScope(node.body)
@ -197,7 +193,7 @@ export function transformDestructuredProps(
// catch param // catch param
if (node.type === 'CatchClause') { if (node.type === 'CatchClause') {
scopeStack.push((currentScope = {})) pushScope()
if (node.param && node.param.type === 'Identifier') { if (node.param && node.param.type === 'Identifier') {
registerLocalBinding(node.param) registerLocalBinding(node.param)
} }
@ -207,7 +203,7 @@ export function transformDestructuredProps(
// non-function block scopes // non-function block scopes
if (node.type === 'BlockStatement' && !isFunctionType(parent!)) { if (node.type === 'BlockStatement' && !isFunctionType(parent!)) {
scopeStack.push((currentScope = {})) pushScope()
walkScope(node) walkScope(node)
return return
} }
@ -217,12 +213,8 @@ export function transformDestructuredProps(
isReferencedIdentifier(node, parent!, parentStack) && isReferencedIdentifier(node, parent!, parentStack) &&
!excludedIds.has(node) !excludedIds.has(node)
) { ) {
// walk up the scope chain to check if id should be appended .value if (currentScope[node.name]) {
let i = scopeStack.length rewriteId(node, parent!, parentStack)
while (i--) {
if (rewriteId(scopeStack[i], node, parent!, parentStack)) {
return
}
} }
} }
} }
@ -233,8 +225,7 @@ export function transformDestructuredProps(
(node.type === 'BlockStatement' && !isFunctionType(parent!)) || (node.type === 'BlockStatement' && !isFunctionType(parent!)) ||
isFunctionType(node) isFunctionType(node)
) { ) {
scopeStack.pop() popScope()
currentScope = scopeStack[scopeStack.length - 1] || null
} }
} }
}) })