]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(custom-elements): use strict number casting
authorEvan You <yyx990803@gmail.com>
Mon, 14 Nov 2022 08:20:12 +0000 (16:20 +0800)
committerEvan You <yyx990803@gmail.com>
Mon, 14 Nov 2022 08:20:12 +0000 (16:20 +0800)
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.

packages/runtime-core/src/compat/instance.ts
packages/runtime-core/src/componentEmits.ts
packages/runtime-core/src/components/Suspense.ts
packages/runtime-core/src/index.ts
packages/runtime-core/src/warning.ts
packages/runtime-dom/src/components/Transition.ts
packages/runtime-dom/src/directives/vModel.ts
packages/shared/src/index.ts

index 01e6618d45bfe20ff6ddb3421600ed82557169b1..141f0bf0a0fb16a57e077569d23689ff278de7c3 100644 (file)
@@ -2,9 +2,9 @@ import {
   extend,
   looseEqual,
   looseIndexOf,
+  looseToNumber,
   NOOP,
-  toDisplayString,
-  toNumber
+  toDisplayString
 } from '@vue/shared'
 import {
   ComponentPublicInstance,
@@ -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),
index 9a9fe050150495d01c10d6c8ef08f65bdf3c59f6..7568741e24e8d154ef80fafda725704042bc0ce8 100644 (file)
@@ -10,8 +10,8 @@ import {
   isObject,
   isString,
   isOn,
-  toNumber,
-  UnionToIntersection
+  UnionToIntersection,
+  looseToNumber
 } from '@vue/shared'
 import {
   ComponentInternalInstance,
@@ -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)
     }
   }
 
index baf570886266adaf574897483cf27d4c64be3c73..6ccca1e26b84e07723a54586ffd6462e53bde47b 100644 (file)
@@ -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 {
@@ -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,
index 7f822489bdc7ecfe843ed876498fe7227d2778a2..086beaa6a97d5c8a86037c0a6f379c4f70f3ea42 100644 (file)
@@ -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,
index 9b793ab514847472a1488d77d91e77c81c3119d0..b314985b7713c55262e75d7b36e9af91978d6339 100644 (file)
@@ -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.')
+  }
+}
index 2c483c76e723712cbc05488d759e1b1686d44e3d..205bea9668fc094cfa8b19dbab8ed220a7015a62 100644 (file)
@@ -2,7 +2,7 @@ import {
   BaseTransition,
   BaseTransitionProps,
   h,
-  warn,
+  assertNumber,
   FunctionalComponent,
   compatUtils,
   DeprecationTypes
@@ -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))
   ;(
index 722b4d9b44cd596a41b3cadc093c9c6dea262a2d..2cf5f4cfc16a0eb013c9b634f7f246be64bfa7ca 100644 (file)
@@ -11,7 +11,7 @@ import {
   looseEqual,
   looseIndexOf,
   invokeArrayFns,
-  toNumber,
+  looseToNumber,
   isSet
 } from '@vue/shared'
 
@@ -54,7 +54,7 @@ export const vModelText: ModelDirective<
         domValue = domValue.trim()
       }
       if (castToNumber) {
-        domValue = toNumber(domValue)
+        domValue = looseToNumber(domValue)
       }
       el._assign(domValue)
     })
@@ -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
       }
     }
@@ -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
index ff30250381925e8724f49e87445cca33687c5427..5c1629d5bc45e791e5f5ec320b77d63811f950c4 100644 (file)
@@ -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 (