From f3145a915aaec11c915f1df258c5209ae4782bcc Mon Sep 17 00:00:00 2001 From: Evan You Date: Tue, 4 Apr 2023 18:10:13 +0800 Subject: [PATCH] fix(compiler-sfc): check binding is prop before erroring fix #8017 --- .../compileScriptPropsDestructure.spec.ts | 15 +++ .../src/compileScriptPropsDestructure.ts | 95 +++++++++---------- 2 files changed, 58 insertions(+), 52 deletions(-) diff --git a/packages/compiler-sfc/__tests__/compileScriptPropsDestructure.spec.ts b/packages/compiler-sfc/__tests__/compileScriptPropsDestructure.spec.ts index 8487270c265..193b3e5a1af 100644 --- a/packages/compiler-sfc/__tests__/compileScriptPropsDestructure.spec.ts +++ b/packages/compiler-sfc/__tests__/compileScriptPropsDestructure.spec.ts @@ -354,5 +354,20 @@ describe('sfc props transform', () => { ) ).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( + `` + ) + ).not.toThrowError() + }) }) }) diff --git a/packages/compiler-sfc/src/compileScriptPropsDestructure.ts b/packages/compiler-sfc/src/compileScriptPropsDestructure.ts index 4ee09070d76..d0addf6fcbc 100644 --- a/packages/compiler-sfc/src/compileScriptPropsDestructure.ts +++ b/packages/compiler-sfc/src/compileScriptPropsDestructure.ts @@ -17,7 +17,7 @@ import { isCallOf, unwrapTSNode } from '@vue/compiler-core' -import { hasOwn, genPropsAccessExp } from '@vue/shared' +import { genPropsAccessExp } from '@vue/shared' import { PropsDestructureBindings } from './compileScript' /** @@ -47,6 +47,15 @@ export function transformDestructuredProps( 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) { excludedIds.add(id) if (currentScope) { @@ -108,54 +117,41 @@ export function transformDestructuredProps( } } - function rewriteId( - scope: Scope, - id: Identifier, - parent: Node, - parentStack: Node[] - ): boolean { - if (hasOwn(scope, id.name)) { - const binding = scope[id.name] - - if (binding) { - if ( - (parent.type === 'AssignmentExpression' && id === parent.left) || - parent.type === 'UpdateExpression' - ) { - error(`Cannot assign to destructured props as they are readonly.`, id) - } + function rewriteId(id: Identifier, parent: Node, parentStack: Node[]) { + if ( + (parent.type === 'AssignmentExpression' && id === parent.left) || + parent.type === 'UpdateExpression' + ) { + error(`Cannot assign to destructured props as they are readonly.`, id) + } - if (isStaticProperty(parent) && parent.shorthand) { - // let binding used in a property shorthand - // skip for destructure patterns - if ( - !(parent as any).inPattern || - isInDestructureAssignment(parent, parentStack) - ) { - // { prop } -> { prop: __props.prop } - s.appendLeft( - id.end! + offset, - `: ${genPropsAccessExp(propsLocalToPublicMap[id.name])}` - ) - } - } else { - // x --> __props.x - s.overwrite( - id.start! + offset, - id.end! + offset, - genPropsAccessExp(propsLocalToPublicMap[id.name]) - ) - } + if (isStaticProperty(parent) && parent.shorthand) { + // let binding used in a property shorthand + // skip for destructure patterns + if ( + !(parent as any).inPattern || + isInDestructureAssignment(parent, parentStack) + ) { + // { prop } -> { prop: __props.prop } + s.appendLeft( + id.end! + offset, + `: ${genPropsAccessExp(propsLocalToPublicMap[id.name])}` + ) } - return true + } else { + // x --> __props.x + s.overwrite( + id.start! + offset, + id.end! + offset, + genPropsAccessExp(propsLocalToPublicMap[id.name]) + ) } - return false } function checkUsage(node: Node, method: string, alias = method) { if (isCallOf(node, alias)) { const arg = unwrapTSNode(node.arguments[0]) - if (arg.type === 'Identifier') { + if (arg.type === 'Identifier' && currentScope[arg.name]) { error( `"${arg.name}" is a destructured prop and should not be passed directly to ${method}(). ` + `Pass a getter () => ${arg.name} instead.`, @@ -187,7 +183,7 @@ export function transformDestructuredProps( // function scopes if (isFunctionType(node)) { - scopeStack.push((currentScope = {})) + pushScope() walkFunctionParams(node, registerLocalBinding) if (node.body.type === 'BlockStatement') { walkScope(node.body) @@ -197,7 +193,7 @@ export function transformDestructuredProps( // catch param if (node.type === 'CatchClause') { - scopeStack.push((currentScope = {})) + pushScope() if (node.param && node.param.type === 'Identifier') { registerLocalBinding(node.param) } @@ -207,7 +203,7 @@ export function transformDestructuredProps( // non-function block scopes if (node.type === 'BlockStatement' && !isFunctionType(parent!)) { - scopeStack.push((currentScope = {})) + pushScope() walkScope(node) return } @@ -217,12 +213,8 @@ export function transformDestructuredProps( isReferencedIdentifier(node, parent!, parentStack) && !excludedIds.has(node) ) { - // walk up the scope chain to check if id should be appended .value - let i = scopeStack.length - while (i--) { - if (rewriteId(scopeStack[i], node, parent!, parentStack)) { - return - } + if (currentScope[node.name]) { + rewriteId(node, parent!, parentStack) } } } @@ -233,8 +225,7 @@ export function transformDestructuredProps( (node.type === 'BlockStatement' && !isFunctionType(parent!)) || isFunctionType(node) ) { - scopeStack.pop() - currentScope = scopeStack[scopeStack.length - 1] || null + popScope() } } })