]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
refactor: improve warning output
authorEvan You <yyx990803@gmail.com>
Sun, 3 Nov 2019 03:21:02 +0000 (23:21 -0400)
committerEvan You <yyx990803@gmail.com>
Sun, 3 Nov 2019 03:21:02 +0000 (23:21 -0400)
packages/runtime-core/src/warning.ts

index dfc77d6cda617a32a98b1362e2d3d7917ded1fc6..0c6d2bda936773b7be4b67c8c1b4a84537b3b1eb 100644 (file)
@@ -1,7 +1,7 @@
 import { VNode } from './vnode'
 import { Data, ComponentInternalInstance, Component } from './component'
 import { isString, isFunction } from '@vue/shared'
-import { toRaw } from '@vue/reactivity'
+import { toRaw, isRef, pauseTracking, resumeTracking } from '@vue/reactivity'
 import { callWithErrorHandling, ErrorCodes } from './errorHandling'
 
 type ComponentVNode = VNode & {
@@ -26,6 +26,10 @@ export function popWarningContext() {
 }
 
 export function warn(msg: string, ...args: any[]) {
+  // avoid props formatting or warn handler tracking deps that might be mutated
+  // during patch, leading to infinite recursion.
+  pauseTracking()
+
   const instance = stack.length ? stack[stack.length - 1].component : null
   const appWarnHandler = instance && instance.appContext.config.warnHandler
   const trace = getComponentTrace()
@@ -38,32 +42,25 @@ export function warn(msg: string, ...args: any[]) {
       [
         msg + args.join(''),
         instance && instance.renderProxy,
-        formatTrace(trace).join('')
+        trace
+          .map(({ vnode }) => `at <${formatComponentName(vnode)}>`)
+          .join('\n'),
+        trace
       ]
     )
-    return
-  }
-
-  console.warn(`[Vue warn]: ${msg}`, ...args)
-  // avoid spamming console during tests
-  if (typeof process !== 'undefined' && process.env.NODE_ENV === 'test') {
-    return
-  }
-  if (!trace.length) {
-    return
-  }
-  if (trace.length > 1 && console.groupCollapsed) {
-    console.groupCollapsed('at', ...formatTraceEntry(trace[0]))
-    const logs: string[] = []
-    trace.slice(1).forEach((entry, i) => {
-      if (i !== 0) logs.push('\n')
-      logs.push(...formatTraceEntry(entry, i + 1))
-    })
-    console.log(...logs)
-    console.groupEnd()
   } else {
-    console.log(...formatTrace(trace))
+    const warnArgs = [`[Vue warn]: ${msg}`, ...args]
+    if (
+      trace.length &&
+      // avoid spamming console during tests
+      (typeof process === 'undefined' || process.env.NODE_ENV !== 'test')
+    ) {
+      warnArgs.push(`\n`, ...formatTrace(trace))
+    }
+    console.warn(...warnArgs)
   }
+
+  resumeTracking()
 }
 
 function getComponentTrace(): ComponentTraceStack {
@@ -95,27 +92,18 @@ function getComponentTrace(): ComponentTraceStack {
   return normalizedStack
 }
 
-function formatTrace(trace: ComponentTraceStack): string[] {
-  const logs: string[] = []
+function formatTrace(trace: ComponentTraceStack): any[] {
+  const logs: any[] = []
   trace.forEach((entry, i) => {
-    const formatted = formatTraceEntry(entry, i)
-    if (i === 0) {
-      logs.push('at', ...formatted)
-    } else {
-      logs.push('\n', ...formatted)
-    }
+    logs.push(...(i === 0 ? [] : [`\n`]), ...formatTraceEntry(entry))
   })
   return logs
 }
 
-function formatTraceEntry(
-  { vnode, recurseCount }: TraceEntry,
-  depth: number = 0
-): string[] {
-  const padding = depth === 0 ? '' : ' '.repeat(depth * 2 + 1)
+function formatTraceEntry({ vnode, recurseCount }: TraceEntry): any[] {
   const postfix =
     recurseCount > 0 ? `... (${recurseCount} recursive calls)` : ``
-  const open = padding + `<${formatComponentName(vnode)}`
+  const open = ` at <${formatComponentName(vnode)}`
   const close = `>` + postfix
   const rootLabel = vnode.component!.parent == null ? `(Root)` : ``
   return vnode.props
@@ -129,25 +117,39 @@ const classify = (str: string): string =>
 
 function formatComponentName(vnode: ComponentVNode, file?: string): string {
   const Component = vnode.type
-  let name = isFunction(Component) ? Component.displayName : Component.name
+  let name = isFunction(Component)
+    ? Component.displayName || Component.name
+    : Component.name
   if (!name && file) {
     const match = file.match(/([^/\\]+)\.vue$/)
     if (match) {
       name = match[1]
     }
   }
-  return name ? classify(name) : 'AnonymousComponent'
+  return name ? classify(name) : 'Anonymous'
 }
 
-function formatProps(props: Data): string[] {
-  const res: string[] = []
+function formatProps(props: Data): any[] {
+  const res: any[] = []
   for (const key in props) {
-    const value = props[key]
-    if (isString(value)) {
-      res.push(`${key}=${JSON.stringify(value)}`)
-    } else {
-      res.push(`${key}=`, String(toRaw(value)))
-    }
+    res.push(...formatProp(key, props[key]))
   }
   return res
 }
+
+function formatProp(key: string, value: unknown): any[]
+function formatProp(key: string, value: unknown, raw: true): any
+function formatProp(key: string, value: unknown, raw?: boolean): any {
+  if (isString(value)) {
+    value = JSON.stringify(value)
+    return raw ? value : [`${key}=${value}`]
+  } else if (typeof value === 'number' || value == null) {
+    return raw ? value : [`${key}=${value}`]
+  } else if (isRef(value)) {
+    value = formatProp(key, toRaw(value.value), true)
+    return raw ? value : [`${key}=Ref<`, value, `>`]
+  } else {
+    value = toRaw(value)
+    return raw ? value : [`${key}=`, value]
+  }
+}