fix(compiler-core/compiler-sfc): handle destructure assignment expressions

This commit is contained in:
Evan You 2020-11-18 22:39:08 -05:00
parent 4d5242128e
commit 4c6078ce25
5 changed files with 188 additions and 49 deletions

View File

@ -111,22 +111,26 @@ export function processExpression(
const rewriteIdentifier = (raw: string, parent?: Node, id?: Identifier) => { const rewriteIdentifier = (raw: string, parent?: Node, id?: Identifier) => {
const type = hasOwn(bindingMetadata, raw) && bindingMetadata[raw] const type = hasOwn(bindingMetadata, raw) && bindingMetadata[raw]
if (inline) { if (inline) {
// x = y
const isAssignmentLVal = const isAssignmentLVal =
parent && parent.type === 'AssignmentExpression' && parent.left === id parent && parent.type === 'AssignmentExpression' && parent.left === id
// x++
const isUpdateArg = const isUpdateArg =
parent && parent.type === 'UpdateExpression' && parent.argument === id parent && parent.type === 'UpdateExpression' && parent.argument === id
// setup inline mode // ({ x } = y)
const isDestructureAssignment =
parent && isInDestructureAssignment(parent, parentStack)
if (type === BindingTypes.SETUP_CONST) { if (type === BindingTypes.SETUP_CONST) {
return raw return raw
} else if ( } else if (type === BindingTypes.SETUP_REF) {
type === BindingTypes.SETUP_REF || return `${raw}.value`
type === BindingTypes.SETUP_MAYBE_REF } else if (type === BindingTypes.SETUP_MAYBE_REF) {
) {
// const binding that may or may not be ref // const binding that may or may not be ref
// if it's not a ref, then assignments don't make sense - // if it's not a ref, then assignments don't make sense -
// so we ignore the non-ref assignment case and generate code // so we ignore the non-ref assignment case and generate code
// that assumes the value to be a ref for more efficiency // that assumes the value to be a ref for more efficiency
return isAssignmentLVal || isUpdateArg return isAssignmentLVal || isUpdateArg || isDestructureAssignment
? `${raw}.value` ? `${raw}.value`
: `${context.helperString(UNREF)}(${raw})` : `${context.helperString(UNREF)}(${raw})`
} else if (type === BindingTypes.SETUP_LET) { } else if (type === BindingTypes.SETUP_LET) {
@ -157,6 +161,13 @@ export function processExpression(
return `${context.helperString(IS_REF)}(${raw})${ return `${context.helperString(IS_REF)}(${raw})${
context.isTS ? ` //@ts-ignore\n` : `` context.isTS ? ` //@ts-ignore\n` : ``
} ? ${prefix}${raw}.value${postfix} : ${prefix}${raw}${postfix}` } ? ${prefix}${raw}.value${postfix} : ${prefix}${raw}${postfix}`
} else if (isDestructureAssignment) {
// TODO
// let binding in a destructure assignment - it's very tricky to
// handle both possible cases here without altering the original
// structure of the code, so we just assume it's not a ref here
// for now
return raw
} else { } else {
return `${context.helperString(UNREF)}(${raw})` return `${context.helperString(UNREF)}(${raw})`
} }
@ -231,22 +242,24 @@ export function processExpression(
const knownIds = Object.create(context.identifiers) const knownIds = Object.create(context.identifiers)
const isDuplicate = (node: Node & PrefixMeta): boolean => const isDuplicate = (node: Node & PrefixMeta): boolean =>
ids.some(id => id.start === node.start) ids.some(id => id.start === node.start)
const parentStack: Node[] = []
// walk the AST and look for identifiers that need to be prefixed. // walk the AST and look for identifiers that need to be prefixed.
;(walk as any)(ast, { ;(walk as any)(ast, {
enter(node: Node & PrefixMeta, parent: Node) { enter(node: Node & PrefixMeta, parent: Node | undefined) {
parent && parentStack.push(parent)
if (node.type === 'Identifier') { if (node.type === 'Identifier') {
if (!isDuplicate(node)) { if (!isDuplicate(node)) {
const needPrefix = shouldPrefix(node, parent) const needPrefix = shouldPrefix(node, parent!, parentStack)
if (!knownIds[node.name] && needPrefix) { if (!knownIds[node.name] && needPrefix) {
if (isStaticProperty(parent) && parent.shorthand) { if (isStaticProperty(parent!) && parent.shorthand) {
// property shorthand like { foo }, we need to add the key since // property shorthand like { foo }, we need to add the key since
// we rewrite the value // we rewrite the value
node.prefix = `${node.name}: ` node.prefix = `${node.name}: `
} }
node.name = rewriteIdentifier(node.name, parent, node) node.name = rewriteIdentifier(node.name, parent, node)
ids.push(node) ids.push(node)
} else if (!isStaticPropertyKey(node, parent)) { } else if (!isStaticPropertyKey(node, parent!)) {
// The identifier is considered constant unless it's pointing to a // The identifier is considered constant unless it's pointing to a
// scope variable (a v-for alias, or a v-slot prop) // scope variable (a v-for alias, or a v-slot prop)
if (!(needPrefix && knownIds[node.name]) && !bailConstant) { if (!(needPrefix && knownIds[node.name]) && !bailConstant) {
@ -291,7 +304,8 @@ export function processExpression(
) )
} }
}, },
leave(node: Node & PrefixMeta) { leave(node: Node & PrefixMeta, parent: Node | undefined) {
parent && parentStack.pop()
if (node !== ast.body[0].expression && node.scopeIds) { if (node !== ast.body[0].expression && node.scopeIds) {
node.scopeIds.forEach((id: string) => { node.scopeIds.forEach((id: string) => {
knownIds[id]-- knownIds[id]--
@ -359,7 +373,7 @@ const isStaticProperty = (node: Node): node is ObjectProperty =>
const isStaticPropertyKey = (node: Node, parent: Node) => const isStaticPropertyKey = (node: Node, parent: Node) =>
isStaticProperty(parent) && parent.key === node isStaticProperty(parent) && parent.key === node
function shouldPrefix(id: Identifier, parent: Node) { function shouldPrefix(id: Identifier, parent: Node, parentStack: Node[]) {
// declaration id // declaration id
if ( if (
(parent.type === 'VariableDeclarator' || (parent.type === 'VariableDeclarator' ||
@ -386,8 +400,11 @@ function shouldPrefix(id: Identifier, parent: Node) {
return false return false
} }
// array destructure pattern // non-assignment array destructure pattern
if (parent.type === 'ArrayPattern') { if (
parent.type === 'ArrayPattern' &&
!isInDestructureAssignment(parent, parentStack)
) {
return false return false
} }
@ -419,6 +436,24 @@ function shouldPrefix(id: Identifier, parent: Node) {
return true return true
} }
function isInDestructureAssignment(parent: Node, parentStack: Node[]): boolean {
if (
parent &&
(parent.type === 'ObjectProperty' || parent.type === 'ArrayPattern')
) {
let i = parentStack.length
while (i--) {
const p = parentStack[i]
if (p.type === 'AssignmentExpression') {
return true
} else if (p.type !== 'ObjectProperty' && !p.type.endsWith('Pattern')) {
break
}
}
}
return false
}
function stringifyExpression(exp: ExpressionNode | string): string { function stringifyExpression(exp: ExpressionNode | string): string {
if (isString(exp)) { if (isString(exp)) {
return exp return exp

View File

@ -117,7 +117,7 @@ return { ref }
`; `;
exports[`SFC compile <script setup> inlineTemplate mode avoid unref() when necessary 1`] = ` exports[`SFC compile <script setup> inlineTemplate mode avoid unref() when necessary 1`] = `
"import { createVNode as _createVNode, unref as _unref, toDisplayString as _toDisplayString, Fragment as _Fragment, openBlock as _openBlock, createBlock as _createBlock } from \\"vue\\" "import { createVNode as _createVNode, toDisplayString as _toDisplayString, unref as _unref, Fragment as _Fragment, openBlock as _openBlock, createBlock as _createBlock } from \\"vue\\"
import { ref } from 'vue' import { ref } from 'vue'
import Foo from './Foo.vue' import Foo from './Foo.vue'
@ -129,12 +129,14 @@ export default {
const count = ref(0) const count = ref(0)
const constant = {} const constant = {}
const maybe = foo()
let lett = 1
function fn() {} function fn() {}
return (_ctx, _cache) => { return (_ctx, _cache) => {
return (_openBlock(), _createBlock(_Fragment, null, [ return (_openBlock(), _createBlock(_Fragment, null, [
_createVNode(Foo), _createVNode(Foo),
_createVNode(\\"div\\", { onClick: fn }, _toDisplayString(_unref(count)) + \\" \\" + _toDisplayString(constant) + \\" \\" + _toDisplayString(_unref(other)), 1 /* TEXT */) _createVNode(\\"div\\", { onClick: fn }, _toDisplayString(count.value) + \\" \\" + _toDisplayString(constant) + \\" \\" + _toDisplayString(_unref(maybe)) + \\" \\" + _toDisplayString(_unref(lett)) + \\" \\" + _toDisplayString(_unref(other)), 1 /* TEXT */)
], 64 /* STABLE_FRAGMENT */)) ], 64 /* STABLE_FRAGMENT */))
} }
} }
@ -143,7 +145,7 @@ return (_ctx, _cache) => {
`; `;
exports[`SFC compile <script setup> inlineTemplate mode should work 1`] = ` exports[`SFC compile <script setup> inlineTemplate mode should work 1`] = `
"import { unref as _unref, toDisplayString as _toDisplayString, createVNode as _createVNode, Fragment as _Fragment, openBlock as _openBlock, createBlock as _createBlock } from \\"vue\\" "import { toDisplayString as _toDisplayString, createVNode as _createVNode, Fragment as _Fragment, openBlock as _openBlock, createBlock as _createBlock } from \\"vue\\"
const _hoisted_1 = /*#__PURE__*/_createVNode(\\"div\\", null, \\"static\\", -1 /* HOISTED */) const _hoisted_1 = /*#__PURE__*/_createVNode(\\"div\\", null, \\"static\\", -1 /* HOISTED */)
@ -157,7 +159,7 @@ export default {
return (_ctx, _cache) => { return (_ctx, _cache) => {
return (_openBlock(), _createBlock(_Fragment, null, [ return (_openBlock(), _createBlock(_Fragment, null, [
_createVNode(\\"div\\", null, _toDisplayString(_unref(count)), 1 /* TEXT */), _createVNode(\\"div\\", null, _toDisplayString(count.value), 1 /* TEXT */),
_hoisted_1 _hoisted_1
], 64 /* STABLE_FRAGMENT */)) ], 64 /* STABLE_FRAGMENT */))
} }
@ -167,7 +169,7 @@ return (_ctx, _cache) => {
`; `;
exports[`SFC compile <script setup> inlineTemplate mode template assignment expression codegen 1`] = ` exports[`SFC compile <script setup> inlineTemplate mode template assignment expression codegen 1`] = `
"import { createVNode as _createVNode, isRef as _isRef, unref as _unref, Fragment as _Fragment, openBlock as _openBlock, createBlock as _createBlock } from \\"vue\\" "import { createVNode as _createVNode, isRef as _isRef, Fragment as _Fragment, openBlock as _openBlock, createBlock as _createBlock } from \\"vue\\"
import { ref } from 'vue' import { ref } from 'vue'
@ -185,10 +187,42 @@ return (_ctx, _cache) => {
onClick: _cache[1] || (_cache[1] = $event => (count.value = 1)) onClick: _cache[1] || (_cache[1] = $event => (count.value = 1))
}), }),
_createVNode(\\"div\\", { _createVNode(\\"div\\", {
onClick: _cache[2] || (_cache[2] = $event => (!_isRef(maybe) ? null : maybe.value = _unref(count))) onClick: _cache[2] || (_cache[2] = $event => (maybe.value = count.value))
}), }),
_createVNode(\\"div\\", { _createVNode(\\"div\\", {
onClick: _cache[3] || (_cache[3] = $event => (_isRef(lett) ? lett.value = _unref(count) : lett = _unref(count))) onClick: _cache[3] || (_cache[3] = $event => (_isRef(lett) ? lett.value = count.value : lett = count.value))
})
], 64 /* STABLE_FRAGMENT */))
}
}
}"
`;
exports[`SFC compile <script setup> inlineTemplate mode template destructure assignment codegen 1`] = `
"import { createVNode as _createVNode, Fragment as _Fragment, openBlock as _openBlock, createBlock as _createBlock } from \\"vue\\"
import { ref } from 'vue'
export default {
expose: [],
setup(__props) {
const val = {}
const count = ref(0)
const maybe = foo()
let lett = 1
return (_ctx, _cache) => {
return (_openBlock(), _createBlock(_Fragment, null, [
_createVNode(\\"div\\", {
onClick: _cache[1] || (_cache[1] = $event => (({ count: count.value } = val)))
}),
_createVNode(\\"div\\", {
onClick: _cache[2] || (_cache[2] = $event => ([maybe.value] = val))
}),
_createVNode(\\"div\\", {
onClick: _cache[3] || (_cache[3] = $event => (({ lett: lett } = val)))
}) })
], 64 /* STABLE_FRAGMENT */)) ], 64 /* STABLE_FRAGMENT */))
} }
@ -219,10 +253,10 @@ return (_ctx, _cache) => {
onClick: _cache[2] || (_cache[2] = $event => (--count.value)) onClick: _cache[2] || (_cache[2] = $event => (--count.value))
}), }),
_createVNode(\\"div\\", { _createVNode(\\"div\\", {
onClick: _cache[3] || (_cache[3] = $event => (!_isRef(maybe) ? null : maybe.value++)) onClick: _cache[3] || (_cache[3] = $event => (maybe.value++))
}), }),
_createVNode(\\"div\\", { _createVNode(\\"div\\", {
onClick: _cache[4] || (_cache[4] = $event => (!_isRef(maybe) ? null : --maybe.value)) onClick: _cache[4] || (_cache[4] = $event => (--maybe.value))
}), }),
_createVNode(\\"div\\", { _createVNode(\\"div\\", {
onClick: _cache[5] || (_cache[5] = $event => (_isRef(lett) ? lett.value++ : lett++)) onClick: _cache[5] || (_cache[5] = $event => (_isRef(lett) ? lett.value++ : lett++))
@ -238,7 +272,7 @@ return (_ctx, _cache) => {
`; `;
exports[`SFC compile <script setup> inlineTemplate mode v-model codegen 1`] = ` exports[`SFC compile <script setup> inlineTemplate mode v-model codegen 1`] = `
"import { unref as _unref, vModelText as _vModelText, createVNode as _createVNode, withDirectives as _withDirectives, isRef as _isRef, Fragment as _Fragment, openBlock as _openBlock, createBlock as _createBlock } from \\"vue\\" "import { vModelText as _vModelText, createVNode as _createVNode, withDirectives as _withDirectives, unref as _unref, isRef as _isRef, Fragment as _Fragment, openBlock as _openBlock, createBlock as _createBlock } from \\"vue\\"
import { ref } from 'vue' import { ref } from 'vue'
@ -255,7 +289,7 @@ return (_ctx, _cache) => {
_withDirectives(_createVNode(\\"input\\", { _withDirectives(_createVNode(\\"input\\", {
\\"onUpdate:modelValue\\": _cache[1] || (_cache[1] = $event => (count.value = $event)) \\"onUpdate:modelValue\\": _cache[1] || (_cache[1] = $event => (count.value = $event))
}, null, 512 /* NEED_PATCH */), [ }, null, 512 /* NEED_PATCH */), [
[_vModelText, _unref(count)] [_vModelText, count.value]
]), ]),
_withDirectives(_createVNode(\\"input\\", { _withDirectives(_createVNode(\\"input\\", {
\\"onUpdate:modelValue\\": _cache[2] || (_cache[2] = $event => (_isRef(maybe) ? maybe.value = $event : null)) \\"onUpdate:modelValue\\": _cache[2] || (_cache[2] = $event => (_isRef(maybe) ? maybe.value = $event : null))
@ -364,6 +398,8 @@ export default {
a.value = a.value + 1 a.value = a.value + 1
b.value.count++ b.value.count++
b.value.count = b.value.count + 1 b.value.count = b.value.count + 1
;({ a: a.value } = { a: 2 })
;[a.value] = [1]
} }
return { a, b, inc } return { a, b, inc }

View File

@ -128,28 +128,34 @@ const bar = 1
import other from './util' import other from './util'
const count = ref(0) const count = ref(0)
const constant = {} const constant = {}
const maybe = foo()
let lett = 1
function fn() {} function fn() {}
</script> </script>
<template> <template>
<Foo/> <Foo/>
<div @click="fn">{{ count }} {{ constant }} {{ other }}</div> <div @click="fn">{{ count }} {{ constant }} {{ maybe }} {{ lett }} {{ other }}</div>
</template> </template>
`, `,
{ inlineTemplate: true } { inlineTemplate: true }
) )
assertCode(content)
// no need to unref vue component import // no need to unref vue component import
expect(content).toMatch(`createVNode(Foo)`) expect(content).toMatch(`createVNode(Foo)`)
// should unref other imports // should unref other imports
expect(content).toMatch(`unref(other)`) expect(content).toMatch(`unref(other)`)
// no need to unref constant literals // no need to unref constant literals
expect(content).not.toMatch(`unref(constant)`) expect(content).not.toMatch(`unref(constant)`)
// should unref const w/ call init (e.g. ref()) // should directly use .value for known refs
expect(content).toMatch(`unref(count)`) expect(content).toMatch(`count.value`)
// should unref() on const bindings that may be refs
expect(content).toMatch(`unref(maybe)`)
// should unref() on let bindings
expect(content).toMatch(`unref(lett)`)
// no need to unref function declarations // no need to unref function declarations
expect(content).toMatch(`{ onClick: fn }`) expect(content).toMatch(`{ onClick: fn }`)
// no need to mark constant fns in patch flag // no need to mark constant fns in patch flag
expect(content).not.toMatch(`PROPS`) expect(content).not.toMatch(`PROPS`)
assertCode(content)
}) })
test('v-model codegen', () => { test('v-model codegen', () => {
@ -170,8 +176,9 @@ const bar = 1
) )
// known const ref: set value // known const ref: set value
expect(content).toMatch(`count.value = $event`) expect(content).toMatch(`count.value = $event`)
// const but maybe ref: only assign after check // const but maybe ref: also assign .value directly since non-ref
expect(content).toMatch(`_isRef(maybe) ? maybe.value = $event : null`) // won't work
expect(content).toMatch(`maybe.value = $event`)
// let: handle both cases // let: handle both cases
expect(content).toMatch( expect(content).toMatch(
`_isRef(lett) ? lett.value = $event : lett = $event` `_isRef(lett) ? lett.value = $event : lett = $event`
@ -198,12 +205,10 @@ const bar = 1
// known const ref: set value // known const ref: set value
expect(content).toMatch(`count.value = 1`) expect(content).toMatch(`count.value = 1`)
// const but maybe ref: only assign after check // const but maybe ref: only assign after check
expect(content).toMatch( expect(content).toMatch(`maybe.value = count.value`)
`!_isRef(maybe) ? null : maybe.value = _unref(count)`
)
// let: handle both cases // let: handle both cases
expect(content).toMatch( expect(content).toMatch(
`_isRef(lett) ? lett.value = _unref(count) : lett = _unref(count)` `_isRef(lett) ? lett.value = count.value : lett = count.value`
) )
assertCode(content) assertCode(content)
}) })
@ -230,14 +235,40 @@ const bar = 1
// known const ref: set value // known const ref: set value
expect(content).toMatch(`count.value++`) expect(content).toMatch(`count.value++`)
expect(content).toMatch(`--count.value`) expect(content).toMatch(`--count.value`)
// const but maybe ref: only assign after check // const but maybe ref (non-ref case ignored)
expect(content).toMatch(`!_isRef(maybe) ? null : maybe.value++`) expect(content).toMatch(`maybe.value++`)
expect(content).toMatch(`!_isRef(maybe) ? null : --maybe.value`) expect(content).toMatch(`--maybe.value`)
// let: handle both cases // let: handle both cases
expect(content).toMatch(`_isRef(lett) ? lett.value++ : lett++`) expect(content).toMatch(`_isRef(lett) ? lett.value++ : lett++`)
expect(content).toMatch(`_isRef(lett) ? --lett.value : --lett`) expect(content).toMatch(`_isRef(lett) ? --lett.value : --lett`)
assertCode(content) assertCode(content)
}) })
test('template destructure assignment codegen', () => {
const { content } = compile(
`<script setup>
import { ref } from 'vue'
const val = {}
const count = ref(0)
const maybe = foo()
let lett = 1
</script>
<template>
<div @click="({ count } = val)"/>
<div @click="[maybe] = val"/>
<div @click="({ lett } = val)"/>
</template>
`,
{ inlineTemplate: true }
)
// known const ref: set value
expect(content).toMatch(`({ count: count.value } = val)`)
// const but maybe ref (non-ref case ignored)
expect(content).toMatch(`[maybe.value] = val`)
// let: assumes non-ref
expect(content).toMatch(`{ lett: lett } = val`)
assertCode(content)
})
}) })
describe('with TypeScript', () => { describe('with TypeScript', () => {
@ -524,12 +555,16 @@ const { props, emit } = defineOptions({
a = a + 1 a = a + 1
b.count++ b.count++
b.count = b.count + 1 b.count = b.count + 1
;({ a } = { a: 2 })
;[a] = [1]
} }
</script>`) </script>`)
expect(content).toMatch(`a.value++`) expect(content).toMatch(`a.value++`)
expect(content).toMatch(`a.value = a.value + 1`) expect(content).toMatch(`a.value = a.value + 1`)
expect(content).toMatch(`b.value.count++`) expect(content).toMatch(`b.value.count++`)
expect(content).toMatch(`b.value.count = b.value.count + 1`) expect(content).toMatch(`b.value.count = b.value.count + 1`)
expect(content).toMatch(`;({ a: a.value } = { a: 2 })`)
expect(content).toMatch(`;[a.value] = [1]`)
assertCode(content) assertCode(content)
}) })

View File

@ -670,7 +670,10 @@ export function compileScript(
// let binding used in a property shorthand // let binding used in a property shorthand
// { foo } -> { foo: foo.value } // { foo } -> { foo: foo.value }
// skip for destructure patterns // skip for destructure patterns
if (!(parent as any).inPattern) { if (
!(parent as any).inPattern ||
isInDestructureAssignment(parent, parentStack)
) {
s.appendLeft(id.end! + startOffset, `: ${id.name}.value`) s.appendLeft(id.end! + startOffset, `: ${id.name}.value`)
} }
} else { } else {
@ -1258,6 +1261,8 @@ function genRuntimeEmits(emits: Set<string>) {
: `` : ``
} }
const parentStack: Node[] = []
/** /**
* Walk an AST and find identifiers that are variable references. * Walk an AST and find identifiers that are variable references.
* This is largely the same logic with `transformExpressions` in compiler-core * This is largely the same logic with `transformExpressions` in compiler-core
@ -1270,10 +1275,11 @@ function walkIdentifiers(
) { ) {
const knownIds: Record<string, number> = Object.create(null) const knownIds: Record<string, number> = Object.create(null)
;(walk as any)(root, { ;(walk as any)(root, {
enter(node: Node & { scopeIds?: Set<string> }, parent: Node) { enter(node: Node & { scopeIds?: Set<string> }, parent: Node | undefined) {
parent && parentStack.push(parent)
if (node.type === 'Identifier') { if (node.type === 'Identifier') {
if (!knownIds[node.name] && isRefIdentifier(node, parent)) { if (!knownIds[node.name] && isRefIdentifier(node, parent!)) {
onIdentifier(node, parent) onIdentifier(node, parent!)
} }
} else if (isFunction(node)) { } else if (isFunction(node)) {
// walk function expressions and add its arguments to known identifiers // walk function expressions and add its arguments to known identifiers
@ -1309,13 +1315,14 @@ function walkIdentifiers(
) )
} else if ( } else if (
node.type === 'ObjectProperty' && node.type === 'ObjectProperty' &&
parent.type === 'ObjectPattern' parent!.type === 'ObjectPattern'
) { ) {
// mark property in destructure pattern // mark property in destructure pattern
;(node as any).inPattern = true ;(node as any).inPattern = true
} }
}, },
leave(node: Node & { scopeIds?: Set<string> }) { leave(node: Node & { scopeIds?: Set<string> }, parent: Node | undefined) {
parent && parentStack.pop()
if (node.scopeIds) { if (node.scopeIds) {
node.scopeIds.forEach((id: string) => { node.scopeIds.forEach((id: string) => {
knownIds[id]-- knownIds[id]--
@ -1355,8 +1362,11 @@ function isRefIdentifier(id: Identifier, parent: Node) {
return false return false
} }
// array destructure pattern // non-assignment array destructure pattern
if (parent.type === 'ArrayPattern') { if (
parent.type === 'ArrayPattern' &&
!isInDestructureAssignment(parent, parentStack)
) {
return false return false
} }
@ -1464,6 +1474,27 @@ function canNeverBeRef(node: Node, userReactiveImport: string): boolean {
} }
} }
function isInDestructureAssignment(parent: Node, parentStack: Node[]): boolean {
if (
parent &&
(parent.type === 'ObjectProperty' || parent.type === 'ArrayPattern')
) {
let i = parentStack.length
while (i--) {
const p = parentStack[i]
if (p.type === 'AssignmentExpression') {
const root = parentStack[0]
// if this is a ref: destructure, it should be treated like a
// variable decalration!
return !(root.type === 'LabeledStatement' && root.label.name === 'ref')
} else if (p.type !== 'ObjectProperty' && !p.type.endsWith('Pattern')) {
break
}
}
}
return false
}
/** /**
* Analyze bindings in normal `<script>` * Analyze bindings in normal `<script>`
* Note that `compileScriptSetup` already analyzes bindings as part of its * Note that `compileScriptSetup` already analyzes bindings as part of its

View File

@ -14,9 +14,11 @@ export const compilerOptions: CompilerOptions = reactive({
inline: false, inline: false,
ssrCssVars: `{ color }`, ssrCssVars: `{ color }`,
bindingMetadata: { bindingMetadata: {
TestComponent: BindingTypes.SETUP, TestComponent: BindingTypes.SETUP_CONST,
foo: BindingTypes.SETUP, setupRef: BindingTypes.SETUP_REF,
bar: BindingTypes.PROPS setupLet: BindingTypes.SETUP_LET,
setupMaybeRef: BindingTypes.SETUP_MAYBE_REF,
setupProp: BindingTypes.PROPS
} }
}) })