/
warning.ts
131 lines (114 loc) 路 3.26 KB
/
warning.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
import { ComponentType, ComponentClass, FunctionalComponent } from './component'
import { EMPTY_OBJ, isString } from '@vue/shared'
import { VNode } from './vdom'
import { Data } from './componentOptions'
let stack: VNode[] = []
type TraceEntry = {
type: VNode
recurseCount: number
}
type ComponentTraceStack = TraceEntry[]
export function pushWarningContext(vnode: VNode) {
stack.push(vnode)
}
export function popWarningContext() {
stack.pop()
}
export function warn(msg: string, ...args: any[]) {
// TODO warn handler
console.warn(`[Vue warn]: ${msg}`, ...args)
const trace = getComponentTrace()
if (!trace.length) {
return
}
if (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 {
const logs: string[] = []
trace.forEach((entry, i) => {
const formatted = formatTraceEntry(entry, i)
if (i === 0) {
logs.push('at', ...formatted)
} else {
logs.push('\n', ...formatted)
}
})
console.log(...logs)
}
}
function getComponentTrace(): ComponentTraceStack {
let current: VNode | null | undefined = stack[stack.length - 1]
if (!current) {
return []
}
// we can't just use the stack because it will be incomplete during updates
// that did not start from the root. Re-construct the parent chain using
// contextVNode information.
const normlaizedStack: ComponentTraceStack = []
while (current) {
const last = normlaizedStack[0]
if (last && last.type === current) {
last.recurseCount++
} else {
normlaizedStack.push({
type: current,
recurseCount: 0
})
}
current = current.contextVNode
}
return normlaizedStack
}
function formatTraceEntry(
{ type, recurseCount }: TraceEntry,
depth: number = 0
): string[] {
const padding = depth === 0 ? '' : ' '.repeat(depth * 2 + 1)
const postfix =
recurseCount > 0 ? `... (${recurseCount} recursive calls)` : ``
const open = padding + `<${formatComponentName(type.tag as ComponentType)}`
const close = `>` + postfix
return type.data ? [open, ...formatProps(type.data), close] : [open + close]
}
const classifyRE = /(?:^|[-_])(\w)/g
const classify = (str: string): string =>
str.replace(classifyRE, c => c.toUpperCase()).replace(/[-_]/g, '')
function formatComponentName(c: ComponentType, file?: string): string {
let name: string
if (c.prototype && c.prototype.render) {
// stateful
const cc = c as ComponentClass
const options = cc.options || EMPTY_OBJ
name = options.displayName || cc.name
} else {
// functional
const fc = c as FunctionalComponent
name = fc.displayName || fc.name
}
if (file && name === 'AnonymousComponent') {
const match = file.match(/([^/\\]+)\.vue$/)
if (match) {
name = match[1]
}
}
return classify(name)
}
function formatProps(props: Data) {
const res = []
for (const key in props) {
const value = props[key]
if (isString(value)) {
res.push(`${key}=${JSON.stringify(value)}`)
} else {
res.push(`${key}=`, value)
}
}
return res
}