warning.ts 4.6 KB

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