Skip to content

Commit 7d0c63f

Browse files
committed
fix(custom-elements): use strict number casting
close #4946 close #2598 close #2604 This commit also refactors internal usage of previous loose implementation of `toNumber` to the stricter version where applicable. Use of `looseToNumber` is preserved for `v-model.number` modifier to ensure backwards compatibility and consistency with Vue 2 behavior.
1 parent efa2ac5 commit 7d0c63f

File tree

8 files changed

+51
-29
lines changed

8 files changed

+51
-29
lines changed

packages/runtime-core/src/compat/instance.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ import {
22
extend,
33
looseEqual,
44
looseIndexOf,
5+
looseToNumber,
56
NOOP,
6-
toDisplayString,
7-
toNumber
7+
toDisplayString
88
} from '@vue/shared'
99
import {
1010
ComponentPublicInstance,
@@ -148,7 +148,7 @@ export function installCompatInstanceProperties(map: PublicPropertiesMap) {
148148
$createElement: () => compatH,
149149
_c: () => compatH,
150150
_o: () => legacyMarkOnce,
151-
_n: () => toNumber,
151+
_n: () => looseToNumber,
152152
_s: () => toDisplayString,
153153
_l: () => renderList,
154154
_t: i => legacyRenderSlot.bind(null, i),

packages/runtime-core/src/componentEmits.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ import {
1010
isObject,
1111
isString,
1212
isOn,
13-
toNumber,
14-
UnionToIntersection
13+
UnionToIntersection,
14+
looseToNumber
1515
} from '@vue/shared'
1616
import {
1717
ComponentInternalInstance,
@@ -126,7 +126,7 @@ export function emit(
126126
args = rawArgs.map(a => (isString(a) ? a.trim() : a))
127127
}
128128
if (number) {
129-
args = rawArgs.map(toNumber)
129+
args = rawArgs.map(looseToNumber)
130130
}
131131
}
132132

packages/runtime-core/src/components/Suspense.ts

+10-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,12 @@ import {
2222
} from '../renderer'
2323
import { queuePostFlushCb } from '../scheduler'
2424
import { filterSingleRoot, updateHOCHostEl } from '../componentRenderUtils'
25-
import { pushWarningContext, popWarningContext, warn } from '../warning'
25+
import {
26+
pushWarningContext,
27+
popWarningContext,
28+
warn,
29+
assertNumber
30+
} from '../warning'
2631
import { handleError, ErrorCodes } from '../errorHandling'
2732

2833
export interface SuspenseProps {
@@ -419,6 +424,10 @@ function createSuspenseBoundary(
419424
} = rendererInternals
420425

421426
const timeout = toNumber(vnode.props && vnode.props.timeout)
427+
if (__DEV__) {
428+
assertNumber(timeout, `Suspense timeout`)
429+
}
430+
422431
const suspense: SuspenseBoundary = {
423432
vnode,
424433
parent,

packages/runtime-core/src/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ export { useSSRContext, ssrContextKey } from './helpers/useSsrContext'
104104

105105
export { createRenderer, createHydrationRenderer } from './renderer'
106106
export { queuePostFlushCb } from './scheduler'
107-
export { warn } from './warning'
107+
export { warn, assertNumber } from './warning'
108108
export {
109109
handleError,
110110
callWithErrorHandling,

packages/runtime-core/src/warning.ts

+12
Original file line numberDiff line numberDiff line change
@@ -162,3 +162,15 @@ function formatProp(key: string, value: unknown, raw?: boolean): any {
162162
return raw ? value : [`${key}=`, value]
163163
}
164164
}
165+
166+
/**
167+
* @internal
168+
*/
169+
export function assertNumber(val: unknown, type: string) {
170+
if (!__DEV__) return
171+
if (typeof val !== 'number') {
172+
warn(`${type} is not a valid number - ` + `got ${JSON.stringify(val)}.`)
173+
} else if (isNaN(val)) {
174+
warn(`${type} is NaN - ` + 'the duration expression might be incorrect.')
175+
}
176+
}

packages/runtime-dom/src/components/Transition.ts

+2-16
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import {
22
BaseTransition,
33
BaseTransitionProps,
44
h,
5-
warn,
5+
assertNumber,
66
FunctionalComponent,
77
compatUtils,
88
DeprecationTypes
@@ -283,24 +283,10 @@ function normalizeDuration(
283283

284284
function NumberOf(val: unknown): number {
285285
const res = toNumber(val)
286-
if (__DEV__) validateDuration(res)
286+
if (__DEV__) assertNumber(res, '<transition> explicit duration')
287287
return res
288288
}
289289

290-
function validateDuration(val: unknown) {
291-
if (typeof val !== 'number') {
292-
warn(
293-
`<transition> explicit duration is not a valid number - ` +
294-
`got ${JSON.stringify(val)}.`
295-
)
296-
} else if (isNaN(val)) {
297-
warn(
298-
`<transition> explicit duration is NaN - ` +
299-
'the duration expression might be incorrect.'
300-
)
301-
}
302-
}
303-
304290
export function addTransitionClass(el: Element, cls: string) {
305291
cls.split(/\s+/).forEach(c => c && el.classList.add(c))
306292
;(

packages/runtime-dom/src/directives/vModel.ts

+7-4
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
looseEqual,
1212
looseIndexOf,
1313
invokeArrayFns,
14-
toNumber,
14+
looseToNumber,
1515
isSet
1616
} from '@vue/shared'
1717

@@ -54,7 +54,7 @@ export const vModelText: ModelDirective<
5454
domValue = domValue.trim()
5555
}
5656
if (castToNumber) {
57-
domValue = toNumber(domValue)
57+
domValue = looseToNumber(domValue)
5858
}
5959
el._assign(domValue)
6060
})
@@ -88,7 +88,10 @@ export const vModelText: ModelDirective<
8888
if (trim && el.value.trim() === value) {
8989
return
9090
}
91-
if ((number || el.type === 'number') && toNumber(el.value) === value) {
91+
if (
92+
(number || el.type === 'number') &&
93+
looseToNumber(el.value) === value
94+
) {
9295
return
9396
}
9497
}
@@ -182,7 +185,7 @@ export const vModelSelect: ModelDirective<HTMLSelectElement> = {
182185
const selectedVal = Array.prototype.filter
183186
.call(el.options, (o: HTMLOptionElement) => o.selected)
184187
.map((o: HTMLOptionElement) =>
185-
number ? toNumber(getValue(o)) : getValue(o)
188+
number ? looseToNumber(getValue(o)) : getValue(o)
186189
)
187190
el._assign(
188191
el.multiple

packages/shared/src/index.ts

+13-1
Original file line numberDiff line numberDiff line change
@@ -153,11 +153,23 @@ export const def = (obj: object, key: string | symbol, value: any) => {
153153
})
154154
}
155155

156-
export const toNumber = (val: any): any => {
156+
/**
157+
* "123-foo" will be parsed to 123
158+
* This is used for the .number modifier in v-model
159+
*/
160+
export const looseToNumber = (val: any): any => {
157161
const n = parseFloat(val)
158162
return isNaN(n) ? val : n
159163
}
160164

165+
/**
166+
* "123-foo" will be returned as-is
167+
*/
168+
export const toNumber = (val: any): any => {
169+
const n = Number(val)
170+
return isNaN(n) ? val : n
171+
}
172+
161173
let _globalThis: any
162174
export const getGlobalThis = (): any => {
163175
return (

0 commit comments

Comments
 (0)