warning.ts 4.3 KB

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