Skip to content

Commit

Permalink
Merge branch 'main' into markraw-non-extensible-objects
Browse files Browse the repository at this point in the history
  • Loading branch information
mattersj committed Feb 7, 2024
2 parents 769a854 + 75e02b5 commit 1fa8985
Show file tree
Hide file tree
Showing 9 changed files with 142 additions and 24 deletions.
18 changes: 18 additions & 0 deletions packages/compiler-core/__tests__/transforms/vBind.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -408,4 +408,22 @@ describe('compiler: transform v-bind', () => {
},
})
})

test('error on invalid argument for same-name shorthand', () => {
const onError = vi.fn()
parseWithVBind(`<div v-bind:[arg] />`, { onError })
expect(onError.mock.calls[0][0]).toMatchObject({
code: ErrorCodes.X_V_BIND_INVALID_SAME_NAME_ARGUMENT,
loc: {
start: {
line: 1,
column: 13,
},
end: {
line: 1,
column: 18,
},
},
})
})
})
2 changes: 2 additions & 0 deletions packages/compiler-core/src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ export enum ErrorCodes {
X_V_FOR_MALFORMED_EXPRESSION,
X_V_FOR_TEMPLATE_KEY_PLACEMENT,
X_V_BIND_NO_EXPRESSION,
X_V_BIND_INVALID_SAME_NAME_ARGUMENT,
X_V_ON_NO_EXPRESSION,
X_V_SLOT_UNEXPECTED_DIRECTIVE_ON_SLOT_OUTLET,
X_V_SLOT_MIXED_SLOT_USAGE,
Expand Down Expand Up @@ -156,6 +157,7 @@ export const errorMessages: Record<ErrorCodes, string> = {
[ErrorCodes.X_V_FOR_MALFORMED_EXPRESSION]: `v-for has invalid expression.`,
[ErrorCodes.X_V_FOR_TEMPLATE_KEY_PLACEMENT]: `<template v-for> key should be placed on the <template> tag.`,
[ErrorCodes.X_V_BIND_NO_EXPRESSION]: `v-bind is missing expression.`,
[ErrorCodes.X_V_BIND_INVALID_SAME_NAME_ARGUMENT]: `v-bind with same-name shorthand only allows static argument.`,
[ErrorCodes.X_V_ON_NO_EXPRESSION]: `v-on is missing expression.`,
[ErrorCodes.X_V_SLOT_UNEXPECTED_DIRECTIVE_ON_SLOT_OUTLET]: `Unexpected custom directive on <slot> outlet.`,
[ErrorCodes.X_V_SLOT_MIXED_SLOT_USAGE]:
Expand Down
52 changes: 39 additions & 13 deletions packages/compiler-core/src/transforms/vBind.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { DirectiveTransform } from '../transform'
import {
type ExpressionNode,
NodeTypes,
type SimpleExpressionNode,
createObjectProperty,
createSimpleExpression,
} from '../ast'
Expand All @@ -17,10 +18,45 @@ export const transformBind: DirectiveTransform = (dir, _node, context) => {
const { modifiers, loc } = dir
const arg = dir.arg!

// :arg is replaced by :arg="arg"
let { exp } = dir
if (!exp && arg.type === NodeTypes.SIMPLE_EXPRESSION) {
const propName = camelize(arg.content)

// handle empty expression
if (exp && exp.type === NodeTypes.SIMPLE_EXPRESSION && !exp.content.trim()) {
if (!__BROWSER__) {
// #10280 only error against empty expression in non-browser build
// because :foo in in-DOM templates will be parsed into :foo="" by the
// browser
context.onError(
createCompilerError(ErrorCodes.X_V_BIND_NO_EXPRESSION, loc),
)
return {
props: [
createObjectProperty(arg, createSimpleExpression('', true, loc)),
],
}
} else {
exp = undefined
}
}

// same-name shorthand - :arg is expanded to :arg="arg"
if (!exp) {
if (arg.type !== NodeTypes.SIMPLE_EXPRESSION || !arg.isStatic) {
// only simple expression is allowed for same-name shorthand
context.onError(
createCompilerError(
ErrorCodes.X_V_BIND_INVALID_SAME_NAME_ARGUMENT,
arg.loc,
),
)
return {
props: [
createObjectProperty(arg, createSimpleExpression('', true, loc)),
],
}
}

const propName = camelize((arg as SimpleExpressionNode).content)
exp = dir.exp = createSimpleExpression(propName, false, arg.loc)
if (!__BROWSER__) {
exp = dir.exp = processExpression(exp, context)
Expand Down Expand Up @@ -57,16 +93,6 @@ export const transformBind: DirectiveTransform = (dir, _node, context) => {
}
}

if (
!exp ||
(exp.type === NodeTypes.SIMPLE_EXPRESSION && !exp.content.trim())
) {
context.onError(createCompilerError(ErrorCodes.X_V_BIND_NO_EXPRESSION, loc))
return {
props: [createObjectProperty(arg, createSimpleExpression('', true, loc))],
}
}

return {
props: [createObjectProperty(arg, exp)],
}
Expand Down
7 changes: 7 additions & 0 deletions packages/runtime-core/__tests__/apiCreateApp.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,13 @@ describe('api: createApp', () => {

expect(app.runWithContext(() => inject('foo'))).toBe(1)

expect(
app.runWithContext(() => {
app.runWithContext(() => {})
return inject('foo')
}),
).toBe(1)

// ensure the context is restored
inject('foo')
expect('inject() can only be used inside setup').toHaveBeenWarned()
Expand Down
3 changes: 2 additions & 1 deletion packages/runtime-core/src/apiCreateApp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -387,11 +387,12 @@ export function createAppAPI<HostElement>(
},

runWithContext(fn) {
const lastApp = currentApp
currentApp = app
try {
return fn()
} finally {
currentApp = null
currentApp = lastApp
}
},
})
Expand Down
16 changes: 7 additions & 9 deletions packages/runtime-core/src/rendererTemplateRef.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,9 @@ export function setRef(
} else {
const _isString = isString(ref)
const _isRef = isRef(ref)
const isVFor = rawRef.f
if (_isString || _isRef) {
const doSet = () => {
if (isVFor) {
if (rawRef.f) {
const existing = _isString
? hasOwn(setupState, ref)
? setupState[ref]
Expand Down Expand Up @@ -119,15 +118,14 @@ export function setRef(
warn('Invalid template ref type:', ref, `(${typeof ref})`)
}
}
// #9908 ref on v-for mutates the same array for both mount and unmount
// and should be done together
if (isUnmount || isVFor) {
doSet()
} else {
// #1789: set new refs in a post job so that they don't get overwritten
// by unmounting ones.
if (value) {
// #1789: for non-null values, set them after render
// null values means this is unmount and it should not overwrite another
// ref with the same key
;(doSet as SchedulerJob).id = -1
queuePostRenderEffect(doSet, parentSuspense)
} else {
doSet()
}
} else if (__DEV__) {
warn('Invalid template ref type:', ref, `(${typeof ref})`)
Expand Down
65 changes: 65 additions & 0 deletions packages/runtime-dom/__tests__/directives/vShow.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -211,4 +211,69 @@ describe('runtime-dom: v-show directive', () => {
await nextTick()
expect($div.style.display).toEqual('')
})

// #10151
test('should respect the display value when v-show value is true', async () => {
const isVisible = ref(false)
const useDisplayStyle = ref(true)
const compStyle = ref({
display: 'none',
})
const withoutDisplayStyle = {
margin: '10px',
}

const Component = {
setup() {
return () => {
return withVShow(
h('div', {
style: useDisplayStyle.value
? compStyle.value
: withoutDisplayStyle,
}),
isVisible.value,
)
}
},
}
render(h(Component), root)

const $div = root.children[0]

expect($div.style.display).toEqual('none')

isVisible.value = true
await nextTick()
expect($div.style.display).toEqual('none')

compStyle.value.display = 'block'
await nextTick()
expect($div.style.display).toEqual('block')

compStyle.value.display = 'inline-block'
await nextTick()
expect($div.style.display).toEqual('inline-block')

isVisible.value = false
await nextTick()
expect($div.style.display).toEqual('none')

isVisible.value = true
await nextTick()
expect($div.style.display).toEqual('inline-block')

useDisplayStyle.value = false
await nextTick()
expect($div.style.display).toEqual('')
expect(getComputedStyle($div).display).toEqual('block')

isVisible.value = false
await nextTick()
expect($div.style.display).toEqual('none')

isVisible.value = true
await nextTick()
expect($div.style.display).toEqual('')
})
})
2 changes: 1 addition & 1 deletion packages/runtime-dom/src/directives/vShow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export const vShow: ObjectDirective<VShowElement> & { name?: 'show' } = {
}
},
updated(el, { value, oldValue }, { transition }) {
if (!value === !oldValue) return
if (!value === !oldValue && el.style.display === el[vShowOldKey]) return
if (transition) {
if (value) {
transition.beforeEnter(el)
Expand Down
1 change: 1 addition & 0 deletions packages/runtime-dom/src/modules/style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export function patchStyle(el: Element, prev: Style, next: Style) {
// so we always keep the current `display` value regardless of the `style`
// value, thus handing over control to `v-show`.
if (vShowOldKey in el) {
el[vShowOldKey] = style.display
style.display = currentDisplay
}
}
Expand Down

0 comments on commit 1fa8985

Please sign in to comment.