warning.ts 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. import {
  2. type Data,
  3. type GenericComponentInstance,
  4. formatComponentName,
  5. } from './component'
  6. import { isFunction, isString } from '@vue/shared'
  7. import { isRef, setActiveSub, toRaw } from '@vue/reactivity'
  8. import { ErrorCodes, callWithErrorHandling } from './errorHandling'
  9. import { type VNode, isVNode } from './vnode'
  10. const stack: (GenericComponentInstance | VNode)[] = []
  11. type TraceEntry = {
  12. ctx: GenericComponentInstance | VNode
  13. recurseCount: number
  14. }
  15. type ComponentTraceStack = TraceEntry[]
  16. /**
  17. * @internal
  18. */
  19. export function pushWarningContext(
  20. ctx: GenericComponentInstance | VNode,
  21. ): void {
  22. stack.push(ctx)
  23. }
  24. /**
  25. * @internal
  26. */
  27. export function popWarningContext(): void {
  28. stack.pop()
  29. }
  30. let isWarning = false
  31. export function warn(msg: string, ...args: any[]): void {
  32. if (isWarning) return
  33. isWarning = true
  34. // avoid props formatting or warn handler tracking deps that might be mutated
  35. // during patch, leading to infinite recursion.
  36. const prevSub = setActiveSub()
  37. const entry = stack.length ? stack[stack.length - 1] : null
  38. const instance = isVNode(entry) ? entry.component : entry
  39. const appWarnHandler = instance && instance.appContext.config.warnHandler
  40. const trace = getComponentTrace()
  41. if (appWarnHandler) {
  42. callWithErrorHandling(
  43. appWarnHandler,
  44. instance,
  45. ErrorCodes.APP_WARN_HANDLER,
  46. [
  47. msg +
  48. args
  49. .map(a => {
  50. const toString = a.toString
  51. return toString == null ? JSON.stringify(a) : toString.call(a)
  52. })
  53. .join(''),
  54. (instance && instance.proxy) || instance,
  55. trace
  56. .map(
  57. ({ ctx }) =>
  58. `at <${formatComponentName(instance, (ctx as any).type)}>`,
  59. )
  60. .join('\n'),
  61. trace,
  62. ],
  63. )
  64. } else {
  65. const warnArgs = [`[Vue warn]: ${msg}`, ...args]
  66. if (
  67. trace.length &&
  68. // avoid spamming console during tests
  69. !__TEST__
  70. ) {
  71. /* v8 ignore next 2 */
  72. warnArgs.push(`\n`, ...formatTrace(trace))
  73. }
  74. console.warn(...warnArgs)
  75. }
  76. setActiveSub(prevSub)
  77. isWarning = false
  78. }
  79. export function getComponentTrace(): ComponentTraceStack {
  80. let currentCtx: TraceEntry['ctx'] | null = stack[stack.length - 1]
  81. if (!currentCtx) {
  82. return []
  83. }
  84. // we can't just use the stack because it will be incomplete during updates
  85. // that did not start from the root. Re-construct the parent chain using
  86. // instance parent pointers.
  87. const normalizedStack: ComponentTraceStack = []
  88. while (currentCtx) {
  89. const last = normalizedStack[0]
  90. if (last && last.ctx === currentCtx) {
  91. last.recurseCount++
  92. } else {
  93. normalizedStack.push({
  94. ctx: currentCtx,
  95. recurseCount: 0,
  96. })
  97. }
  98. if (isVNode(currentCtx)) {
  99. const parent: GenericComponentInstance | null =
  100. currentCtx.component && currentCtx.component.parent
  101. currentCtx = (parent && parent.vnode) || parent
  102. } else {
  103. currentCtx = currentCtx.parent
  104. }
  105. }
  106. return normalizedStack
  107. }
  108. /* v8 ignore start */
  109. function formatTrace(trace: ComponentTraceStack): any[] {
  110. const logs: any[] = []
  111. trace.forEach((entry, i) => {
  112. logs.push(...(i === 0 ? [] : [`\n`]), ...formatTraceEntry(entry))
  113. })
  114. return logs
  115. }
  116. function formatTraceEntry({ ctx, recurseCount }: TraceEntry): any[] {
  117. const postfix =
  118. recurseCount > 0 ? `... (${recurseCount} recursive calls)` : ``
  119. const instance = isVNode(ctx) ? ctx.component : ctx
  120. const isRoot = instance ? instance.parent == null : false
  121. const open = ` at <${formatComponentName(instance, (ctx as any).type, isRoot)}`
  122. const close = `>` + postfix
  123. return ctx.props ? [open, ...formatProps(ctx.props), close] : [open + close]
  124. }
  125. function formatProps(props: Data): any[] {
  126. const res: any[] = []
  127. const keys = Object.keys(props)
  128. keys.slice(0, 3).forEach(key => {
  129. res.push(...formatProp(key, props[key]))
  130. })
  131. if (keys.length > 3) {
  132. res.push(` ...`)
  133. }
  134. return res
  135. }
  136. function formatProp(key: string, value: unknown): any[]
  137. function formatProp(key: string, value: unknown, raw: true): any
  138. function formatProp(key: string, value: unknown, raw?: boolean): any {
  139. if (isString(value)) {
  140. value = JSON.stringify(value)
  141. return raw ? value : [`${key}=${value}`]
  142. } else if (
  143. typeof value === 'number' ||
  144. typeof value === 'boolean' ||
  145. value == null
  146. ) {
  147. return raw ? value : [`${key}=${value}`]
  148. } else if (isRef(value)) {
  149. value = formatProp(key, toRaw(value.value), true)
  150. return raw ? value : [`${key}=Ref<`, value, `>`]
  151. } else if (isFunction(value)) {
  152. return [`${key}=fn${value.name ? `<${value.name}>` : ``}`]
  153. } else {
  154. value = toRaw(value)
  155. return raw ? value : [`${key}=`, value]
  156. }
  157. }
  158. /**
  159. * @internal
  160. */
  161. export function assertNumber(val: unknown, type: string): void {
  162. if (!__DEV__) return
  163. if (val === undefined) {
  164. return
  165. } else if (typeof val !== 'number') {
  166. warn(`${type} is not a valid number - ` + `got ${JSON.stringify(val)}.`)
  167. } else if (isNaN(val)) {
  168. warn(`${type} is NaN - ` + 'the duration expression might be incorrect.')
  169. }
  170. }
  171. /* v8 ignore stop */