Skip to content

Commit

Permalink
fix(custom-elements): use strict number casting
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
yyx990803 committed Nov 14, 2022
1 parent efa2ac5 commit 7d0c63f
Show file tree
Hide file tree
Showing 8 changed files with 51 additions and 29 deletions.
6 changes: 3 additions & 3 deletions packages/runtime-core/src/compat/instance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import {
extend,
looseEqual,
looseIndexOf,
looseToNumber,
NOOP,
toDisplayString,
toNumber
toDisplayString
} from '@vue/shared'
import {
ComponentPublicInstance,
Expand Down Expand Up @@ -148,7 +148,7 @@ export function installCompatInstanceProperties(map: PublicPropertiesMap) {
$createElement: () => compatH,
_c: () => compatH,
_o: () => legacyMarkOnce,
_n: () => toNumber,
_n: () => looseToNumber,
_s: () => toDisplayString,
_l: () => renderList,
_t: i => legacyRenderSlot.bind(null, i),
Expand Down
6 changes: 3 additions & 3 deletions packages/runtime-core/src/componentEmits.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import {
isObject,
isString,
isOn,
toNumber,
UnionToIntersection
UnionToIntersection,
looseToNumber
} from '@vue/shared'
import {
ComponentInternalInstance,
Expand Down Expand Up @@ -126,7 +126,7 @@ export function emit(
args = rawArgs.map(a => (isString(a) ? a.trim() : a))
}
if (number) {
args = rawArgs.map(toNumber)
args = rawArgs.map(looseToNumber)
}
}

Expand Down
11 changes: 10 additions & 1 deletion packages/runtime-core/src/components/Suspense.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,12 @@ import {
} from '../renderer'
import { queuePostFlushCb } from '../scheduler'
import { filterSingleRoot, updateHOCHostEl } from '../componentRenderUtils'
import { pushWarningContext, popWarningContext, warn } from '../warning'
import {
pushWarningContext,
popWarningContext,
warn,
assertNumber
} from '../warning'
import { handleError, ErrorCodes } from '../errorHandling'

export interface SuspenseProps {
Expand Down Expand Up @@ -419,6 +424,10 @@ function createSuspenseBoundary(
} = rendererInternals

const timeout = toNumber(vnode.props && vnode.props.timeout)
if (__DEV__) {
assertNumber(timeout, `Suspense timeout`)
}

const suspense: SuspenseBoundary = {
vnode,
parent,
Expand Down
2 changes: 1 addition & 1 deletion packages/runtime-core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ export { useSSRContext, ssrContextKey } from './helpers/useSsrContext'

export { createRenderer, createHydrationRenderer } from './renderer'
export { queuePostFlushCb } from './scheduler'
export { warn } from './warning'
export { warn, assertNumber } from './warning'
export {
handleError,
callWithErrorHandling,
Expand Down
12 changes: 12 additions & 0 deletions packages/runtime-core/src/warning.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,3 +162,15 @@ function formatProp(key: string, value: unknown, raw?: boolean): any {
return raw ? value : [`${key}=`, value]
}
}

/**
* @internal
*/
export function assertNumber(val: unknown, type: string) {
if (!__DEV__) return
if (typeof val !== 'number') {
warn(`${type} is not a valid number - ` + `got ${JSON.stringify(val)}.`)
} else if (isNaN(val)) {
warn(`${type} is NaN - ` + 'the duration expression might be incorrect.')
}
}
18 changes: 2 additions & 16 deletions packages/runtime-dom/src/components/Transition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {
BaseTransition,
BaseTransitionProps,
h,
warn,
assertNumber,
FunctionalComponent,
compatUtils,
DeprecationTypes
Expand Down Expand Up @@ -283,24 +283,10 @@ function normalizeDuration(

function NumberOf(val: unknown): number {
const res = toNumber(val)
if (__DEV__) validateDuration(res)
if (__DEV__) assertNumber(res, '<transition> explicit duration')
return res
}

function validateDuration(val: unknown) {
if (typeof val !== 'number') {
warn(
`<transition> explicit duration is not a valid number - ` +
`got ${JSON.stringify(val)}.`
)
} else if (isNaN(val)) {
warn(
`<transition> explicit duration is NaN - ` +
'the duration expression might be incorrect.'
)
}
}

export function addTransitionClass(el: Element, cls: string) {
cls.split(/\s+/).forEach(c => c && el.classList.add(c))
;(
Expand Down
11 changes: 7 additions & 4 deletions packages/runtime-dom/src/directives/vModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
looseEqual,
looseIndexOf,
invokeArrayFns,
toNumber,
looseToNumber,
isSet
} from '@vue/shared'

Expand Down Expand Up @@ -54,7 +54,7 @@ export const vModelText: ModelDirective<
domValue = domValue.trim()
}
if (castToNumber) {
domValue = toNumber(domValue)
domValue = looseToNumber(domValue)
}
el._assign(domValue)
})
Expand Down Expand Up @@ -88,7 +88,10 @@ export const vModelText: ModelDirective<
if (trim && el.value.trim() === value) {
return
}
if ((number || el.type === 'number') && toNumber(el.value) === value) {
if (
(number || el.type === 'number') &&
looseToNumber(el.value) === value
) {
return
}
}
Expand Down Expand Up @@ -182,7 +185,7 @@ export const vModelSelect: ModelDirective<HTMLSelectElement> = {
const selectedVal = Array.prototype.filter
.call(el.options, (o: HTMLOptionElement) => o.selected)
.map((o: HTMLOptionElement) =>
number ? toNumber(getValue(o)) : getValue(o)
number ? looseToNumber(getValue(o)) : getValue(o)
)
el._assign(
el.multiple
Expand Down
14 changes: 13 additions & 1 deletion packages/shared/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,11 +153,23 @@ export const def = (obj: object, key: string | symbol, value: any) => {
})
}

export const toNumber = (val: any): any => {
/**
* "123-foo" will be parsed to 123
* This is used for the .number modifier in v-model
*/
export const looseToNumber = (val: any): any => {
const n = parseFloat(val)
return isNaN(n) ? val : n
}

/**
* "123-foo" will be returned as-is
*/
export const toNumber = (val: any): any => {
const n = Number(val)
return isNaN(n) ? val : n
}

let _globalThis: any
export const getGlobalThis = (): any => {
return (
Expand Down

0 comments on commit 7d0c63f

Please sign in to comment.