Skip to content

Commit

Permalink
feat(compile-core): handle falsy dynamic args for v-on and v-bind (#2393
Browse files Browse the repository at this point in the history
)

fix #2388
  • Loading branch information
unbyte authored Oct 19, 2020
1 parent 7390487 commit 052a621
Show file tree
Hide file tree
Showing 15 changed files with 95 additions and 70 deletions.
6 changes: 4 additions & 2 deletions packages/compiler-core/__tests__/transforms/vBind.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ describe('compiler: transform v-bind', () => {
const props = (node.codegenNode as VNodeCall).props as ObjectExpression
expect(props.properties[0]).toMatchObject({
key: {
content: `id`,
content: `id || ""`,
isStatic: false
},
value: {
Expand Down Expand Up @@ -130,7 +130,7 @@ describe('compiler: transform v-bind', () => {
const props = (node.codegenNode as VNodeCall).props as ObjectExpression
expect(props.properties[0]).toMatchObject({
key: {
content: `_${helperNameMap[CAMELIZE]}(foo)`,
content: `_${helperNameMap[CAMELIZE]}(foo || "")`,
isStatic: false
},
value: {
Expand All @@ -149,10 +149,12 @@ describe('compiler: transform v-bind', () => {
key: {
children: [
`_${helperNameMap[CAMELIZE]}(`,
`(`,
{ content: `_ctx.foo` },
`(`,
{ content: `_ctx.bar` },
`)`,
`) || ""`,
`)`
]
},
Expand Down
18 changes: 9 additions & 9 deletions packages/compiler-core/__tests__/transforms/vOn.spec.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import {
baseParse as parse,
transform,
ElementNode,
ObjectExpression,
CompilerOptions,
ElementNode,
ErrorCodes,
NodeTypes,
VNodeCall,
TO_HANDLER_KEY,
helperNameMap,
CAPITALIZE
NodeTypes,
ObjectExpression,
transform,
VNodeCall
} from '../../src'
import { transformOn } from '../../src/transforms/vOn'
import { transformElement } from '../../src/transforms/transformElement'
Expand Down Expand Up @@ -76,7 +76,7 @@ describe('compiler: transform v-on', () => {
key: {
type: NodeTypes.COMPOUND_EXPRESSION,
children: [
`"on" + _${helperNameMap[CAPITALIZE]}(`,
`_${helperNameMap[TO_HANDLER_KEY]}(`,
{ content: `event` },
`)`
]
Expand All @@ -101,7 +101,7 @@ describe('compiler: transform v-on', () => {
key: {
type: NodeTypes.COMPOUND_EXPRESSION,
children: [
`"on" + _${helperNameMap[CAPITALIZE]}(`,
`_${helperNameMap[TO_HANDLER_KEY]}(`,
{ content: `_ctx.event` },
`)`
]
Expand All @@ -126,7 +126,7 @@ describe('compiler: transform v-on', () => {
key: {
type: NodeTypes.COMPOUND_EXPRESSION,
children: [
`"on" + _${helperNameMap[CAPITALIZE]}(`,
`_${helperNameMap[TO_HANDLER_KEY]}(`,
{ content: `_ctx.event` },
`(`,
{ content: `_ctx.foo` },
Expand Down
2 changes: 2 additions & 0 deletions packages/compiler-core/src/runtimeHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export const MERGE_PROPS = Symbol(__DEV__ ? `mergeProps` : ``)
export const TO_HANDLERS = Symbol(__DEV__ ? `toHandlers` : ``)
export const CAMELIZE = Symbol(__DEV__ ? `camelize` : ``)
export const CAPITALIZE = Symbol(__DEV__ ? `capitalize` : ``)
export const TO_HANDLER_KEY = Symbol(__DEV__ ? `toHandlerKey` : ``)
export const SET_BLOCK_TRACKING = Symbol(__DEV__ ? `setBlockTracking` : ``)
export const PUSH_SCOPE_ID = Symbol(__DEV__ ? `pushScopeId` : ``)
export const POP_SCOPE_ID = Symbol(__DEV__ ? `popScopeId` : ``)
Expand Down Expand Up @@ -56,6 +57,7 @@ export const helperNameMap: any = {
[TO_HANDLERS]: `toHandlers`,
[CAMELIZE]: `camelize`,
[CAPITALIZE]: `capitalize`,
[TO_HANDLER_KEY]: `toHandlerKey`,
[SET_BLOCK_TRACKING]: `setBlockTracking`,
[PUSH_SCOPE_ID]: `pushScopeId`,
[POP_SCOPE_ID]: `popScopeId`,
Expand Down
8 changes: 8 additions & 0 deletions packages/compiler-core/src/transforms/vBind.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,14 @@ import { CAMELIZE } from '../runtimeHelpers'
export const transformBind: DirectiveTransform = (dir, node, context) => {
const { exp, modifiers, loc } = dir
const arg = dir.arg!

if (arg.type !== NodeTypes.SIMPLE_EXPRESSION) {
arg.children.unshift(`(`)
arg.children.push(`) || ""`)
} else if (!arg.isStatic) {
arg.content = `${arg.content} || ""`
}

// .prop is no longer necessary due to new patch behavior
// .sync is replaced by v-model:arg
if (modifiers.includes('camel')) {
Expand Down
26 changes: 15 additions & 11 deletions packages/compiler-core/src/transforms/vOn.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
import { DirectiveTransform, DirectiveTransformResult } from '../transform'
import {
DirectiveNode,
createCompoundExpression,
createObjectProperty,
createSimpleExpression,
DirectiveNode,
ElementTypes,
ExpressionNode,
NodeTypes,
createCompoundExpression,
SimpleExpressionNode,
ElementTypes
SimpleExpressionNode
} from '../ast'
import { capitalize, camelize } from '@vue/shared'
import { camelize, toHandlerKey } from '@vue/shared'
import { createCompilerError, ErrorCodes } from '../errors'
import { processExpression } from './transformExpression'
import { validateBrowserExpression } from '../validateExpression'
import { isMemberExpression, hasScopeRef } from '../utils'
import { CAPITALIZE } from '../runtimeHelpers'
import { hasScopeRef, isMemberExpression } from '../utils'
import { TO_HANDLER_KEY } from '../runtimeHelpers'

const fnExpRE = /^\s*([\w$_]+|\([^)]*?\))\s*=>|^\s*function(?:\s+[\w$]+)?\s*\(/

Expand Down Expand Up @@ -43,19 +43,23 @@ export const transformOn: DirectiveTransform = (
if (arg.isStatic) {
const rawName = arg.content
// for all event listeners, auto convert it to camelCase. See issue #2249
const normalizedName = capitalize(camelize(rawName))
eventName = createSimpleExpression(`on${normalizedName}`, true, arg.loc)
eventName = createSimpleExpression(
toHandlerKey(camelize(rawName)),
true,
arg.loc
)
} else {
// #2388
eventName = createCompoundExpression([
`"on" + ${context.helperString(CAPITALIZE)}(`,
`${context.helperString(TO_HANDLER_KEY)}(`,
arg,
`)`
])
}
} else {
// already a compound expression.
eventName = arg
eventName.children.unshift(`"on" + ${context.helperString(CAPITALIZE)}(`)
eventName.children.unshift(`${context.helperString(TO_HANDLER_KEY)}(`)
eventName.children.push(`)`)
}

Expand Down
24 changes: 12 additions & 12 deletions packages/compiler-dom/__tests__/transforms/vOn.spec.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import {
baseParse as parse,
transform,
CompilerOptions,
ElementNode,
ObjectExpression,
NodeTypes,
VNodeCall,
TO_HANDLER_KEY,
helperNameMap,
CAPITALIZE
NodeTypes,
ObjectExpression,
transform,
VNodeCall
} from '@vue/compiler-core'
import { transformOn } from '../../src/transforms/vOn'
import { V_ON_WITH_MODIFIERS, V_ON_WITH_KEYS } from '../../src/runtimeHelpers'
import { V_ON_WITH_KEYS, V_ON_WITH_MODIFIERS } from '../../src/runtimeHelpers'
import { transformElement } from '../../../compiler-core/src/transforms/transformElement'
import { transformExpression } from '../../../compiler-core/src/transforms/transformExpression'
import { genFlagText } from '../../../compiler-core/__tests__/testUtils'
Expand Down Expand Up @@ -195,22 +195,22 @@ describe('compiler-dom: transform v-on', () => {
const {
props: [prop2]
} = parseWithVOn(`<div @[event].right="test"/>`)
// ("on" + (event)).toLowerCase() === "onclick" ? "onContextmenu" : ("on" + (event))
// (_toHandlerKey(event)).toLowerCase() === "onclick" ? "onContextmenu" : (_toHandlerKey(event))
expect(prop2.key).toMatchObject({
type: NodeTypes.COMPOUND_EXPRESSION,
children: [
`(`,
{
children: [
`"on" + _${helperNameMap[CAPITALIZE]}(`,
`_${helperNameMap[TO_HANDLER_KEY]}(`,
{ content: 'event' },
`)`
]
},
`) === "onClick" ? "onContextmenu" : (`,
{
children: [
`"on" + _${helperNameMap[CAPITALIZE]}(`,
`_${helperNameMap[TO_HANDLER_KEY]}(`,
{ content: 'event' },
`)`
]
Expand All @@ -233,22 +233,22 @@ describe('compiler-dom: transform v-on', () => {
const {
props: [prop2]
} = parseWithVOn(`<div @[event].middle="test"/>`)
// ("on" + (event)).toLowerCase() === "onclick" ? "onMouseup" : ("on" + (event))
// (_eventNaming(event)).toLowerCase() === "onclick" ? "onMouseup" : (_eventNaming(event))
expect(prop2.key).toMatchObject({
type: NodeTypes.COMPOUND_EXPRESSION,
children: [
`(`,
{
children: [
`"on" + _${helperNameMap[CAPITALIZE]}(`,
`_${helperNameMap[TO_HANDLER_KEY]}(`,
{ content: 'event' },
`)`
]
},
`) === "onClick" ? "onMouseup" : (`,
{
children: [
`"on" + _${helperNameMap[CAPITALIZE]}(`,
`_${helperNameMap[TO_HANDLER_KEY]}(`,
{ content: 'event' },
`)`
]
Expand Down
8 changes: 4 additions & 4 deletions packages/compiler-ssr/__tests__/ssrElement.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ describe('ssr: element', () => {
expect(getCompiledString(`<div v-bind:[key]="value"></div>`))
.toMatchInlineSnapshot(`
"\`<div\${
_ssrRenderAttrs({ [_ctx.key]: _ctx.value })
_ssrRenderAttrs({ [_ctx.key || \\"\\"]: _ctx.value })
}></div>\`"
`)

Expand All @@ -170,7 +170,7 @@ describe('ssr: element', () => {
"\`<div\${
_ssrRenderAttrs({
class: \\"foo\\",
[_ctx.key]: _ctx.value
[_ctx.key || \\"\\"]: _ctx.value
})
}></div>\`"
`)
Expand All @@ -180,7 +180,7 @@ describe('ssr: element', () => {
"\`<div\${
_ssrRenderAttrs({
id: _ctx.id,
[_ctx.key]: _ctx.value
[_ctx.key || \\"\\"]: _ctx.value
})
}></div>\`"
`)
Expand Down Expand Up @@ -212,7 +212,7 @@ describe('ssr: element', () => {
expect(getCompiledString(`<div :[key]="id" v-bind="obj"></div>`))
.toMatchInlineSnapshot(`
"\`<div\${
_ssrRenderAttrs(_mergeProps({ [_ctx.key]: _ctx.id }, _ctx.obj))
_ssrRenderAttrs(_mergeProps({ [_ctx.key || \\"\\"]: _ctx.id }, _ctx.obj))
}></div>\`"
`)

Expand Down
14 changes: 6 additions & 8 deletions packages/runtime-core/src/apiLifecycle.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import {
ComponentInternalInstance,
LifecycleHooks,
currentInstance,
setCurrentInstance,
isInSSRComponentSetup
isInSSRComponentSetup,
LifecycleHooks,
setCurrentInstance
} from './component'
import { ComponentPublicInstance } from './componentPublicInstance'
import { callWithAsyncErrorHandling, ErrorTypeStrings } from './errorHandling'
import { warn } from './warning'
import { capitalize } from '@vue/shared'
import { pauseTracking, resetTracking, DebuggerEvent } from '@vue/reactivity'
import { toHandlerKey } from '@vue/shared'
import { DebuggerEvent, pauseTracking, resetTracking } from '@vue/reactivity'

export { onActivated, onDeactivated } from './components/KeepAlive'

Expand Down Expand Up @@ -49,9 +49,7 @@ export function injectHook(
}
return wrappedHook
} else if (__DEV__) {
const apiName = `on${capitalize(
ErrorTypeStrings[type].replace(/ hook$/, '')
)}`
const apiName = toHandlerKey(ErrorTypeStrings[type].replace(/ hook$/, ''))
warn(
`${apiName} is called when there is no active component instance to be ` +
`associated with. ` +
Expand Down
22 changes: 11 additions & 11 deletions packages/runtime-core/src/componentEmits.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import {
isArray,
isOn,
hasOwn,
camelize,
EMPTY_OBJ,
capitalize,
toHandlerKey,
extend,
hasOwn,
hyphenate,
isArray,
isFunction,
extend,
camelize
isOn
} from '@vue/shared'
import {
ComponentInternalInstance,
Expand Down Expand Up @@ -56,10 +56,10 @@ export function emit(
} = instance
if (emitsOptions) {
if (!(event in emitsOptions)) {
if (!propsOptions || !(`on` + capitalize(event) in propsOptions)) {
if (!propsOptions || !(toHandlerKey(event) in propsOptions)) {
warn(
`Component emitted event "${event}" but it is neither declared in ` +
`the emits option nor as an "on${capitalize(event)}" prop.`
`the emits option nor as an "${toHandlerKey(event)}" prop.`
)
}
} else {
Expand All @@ -82,7 +82,7 @@ export function emit(

if (__DEV__) {
const lowerCaseEvent = event.toLowerCase()
if (lowerCaseEvent !== event && props[`on` + capitalize(lowerCaseEvent)]) {
if (lowerCaseEvent !== event && props[toHandlerKey(lowerCaseEvent)]) {
warn(
`Event "${lowerCaseEvent}" is emitted in component ` +
`${formatComponentName(
Expand All @@ -97,12 +97,12 @@ export function emit(
}

// convert handler name to camelCase. See issue #2249
let handlerName = `on${capitalize(camelize(event))}`
let handlerName = toHandlerKey(camelize(event))
let handler = props[handlerName]
// for v-model update:xxx events, also trigger kebab-case equivalent
// for props passed via kebab-case
if (!handler && event.startsWith('update:')) {
handlerName = `on${capitalize(hyphenate(event))}`
handlerName = toHandlerKey(hyphenate(event))
handler = props[handlerName]
}
if (!handler) {
Expand Down
4 changes: 2 additions & 2 deletions packages/runtime-core/src/helpers/toHandlers.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { isObject, capitalize } from '@vue/shared'
import { toHandlerKey, isObject } from '@vue/shared'
import { warn } from '../warning'

/**
Expand All @@ -12,7 +12,7 @@ export function toHandlers(obj: Record<string, any>): Record<string, any> {
return ret
}
for (const key in obj) {
ret[`on${capitalize(key)}`] = obj[key]
ret[toHandlerKey(key)] = obj[key]
}
return ret
}
7 changes: 6 additions & 1 deletion packages/runtime-core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,12 @@ export {
createCommentVNode,
createStaticVNode
} from './vnode'
export { toDisplayString, camelize, capitalize } from '@vue/shared'
export {
toDisplayString,
camelize,
capitalize,
toHandlerKey
} from '@vue/shared'

// For test-utils
export { transformVNodeArgs } from './vnode'
Expand Down
Loading

0 comments on commit 052a621

Please sign in to comment.