forked from vitest-dev/vitest
-
Notifications
You must be signed in to change notification settings - Fork 0
/
module-graph.ts
141 lines (132 loc) · 4.42 KB
/
module-graph.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
132
133
134
135
136
137
138
139
140
141
import type { Graph, GraphConfig, GraphController, GraphLink, GraphNode } from 'd3-graph-controller'
import { defineGraph, defineLink, defineNode } from 'd3-graph-controller'
import type { ModuleGraphData } from '../../../vitest/src/types'
export type ModuleType = 'external' | 'inline'
export type ModuleNode = GraphNode<ModuleType>
export type ModuleLink = GraphLink<ModuleType, ModuleNode>
export type ModuleGraph = Graph<ModuleType, ModuleNode, ModuleLink>
export type ModuleGraphController = GraphController<ModuleType, ModuleNode, ModuleLink>
export type ModuleGraphConfig = GraphConfig<ModuleType, ModuleNode, ModuleLink>
export interface ModuleLabelItem {
id: string
raw: string
splits: string[]
candidate: string
finished: boolean
}
export function calcExternalLabels(labels: ModuleLabelItem[]): Map<string, string> {
const result: Map<string, string> = new Map()
const splitMap: Map<string, number[]> = new Map()
const firsts: number[] = []
while (true) {
let finishedCount = 0
labels.forEach((label, i) => {
const { splits, finished } = label
// record the candidate as final label text when label is marked finished
if (finished) {
finishedCount++
const { raw, candidate } = label
result.set(raw, candidate)
return
}
if (splits.length === 0) {
label.finished = true
return
}
const head = splits[0]
if (splitMap.has(head)) {
label.candidate += label.candidate === '' ? head : `/${head}`
splitMap.get(head)?.push(i)
splits.shift()
// eslint-disable-next-line @typescript-eslint/brace-style
} else {
splitMap.set(head, [i])
// record the index of the label where the head first appears
firsts.push(i)
}
})
// update candidate of label which index appears in first array
firsts.forEach((i) => {
const label = labels[i]
const head = label.splits.shift()
label.candidate += label.candidate === '' ? head : `/${head}`
})
splitMap.forEach((value) => {
if (value.length === 1) {
const index = value[0]
labels[index].finished = true
}
})
splitMap.clear()
firsts.length = 0
if (finishedCount === labels.length)
break
}
return result
}
export function createModuleLabelItem(module: string): ModuleLabelItem {
let raw = module
if (raw.includes('/node_modules/'))
raw = module.split(/\/node_modules\//g).pop()!
const splits = raw.split(/\//g)
return {
raw,
splits,
candidate: '',
finished: false,
id: module,
}
}
function defineExternalModuleNodes(modules: string[]): ModuleNode[] {
const labels: ModuleLabelItem[] = modules.map(module => createModuleLabelItem(module))
const map = calcExternalLabels(labels)
return labels.map(({ raw, id }) => {
return defineNode<ModuleType, ModuleNode>({
color: 'var(--color-node-external)',
label: {
color: 'var(--color-node-external)',
fontSize: '0.875rem',
text: map.get(raw) ?? '',
},
isFocused: false,
id,
type: 'external',
})
})
}
function defineInlineModuleNode(module: string, isRoot: boolean): ModuleNode {
return defineNode<ModuleType, ModuleNode>({
color: isRoot ? 'var(--color-node-root)' : 'var(--color-node-inline)',
label: {
color: isRoot ? 'var(--color-node-root)' : 'var(--color-node-inline)',
fontSize: '0.875rem',
text: module.split(/\//g).pop()!,
},
isFocused: false,
id: module,
type: 'inline',
})
}
export function getModuleGraph(data: ModuleGraphData, rootPath: string | undefined): ModuleGraph {
if (!data)
return defineGraph({})
const externalizedNodes = defineExternalModuleNodes(data.externalized)
const inlinedNodes = data.inlined.map(module => defineInlineModuleNode(module, module === rootPath)) ?? []
const nodes = [...externalizedNodes, ...inlinedNodes]
const nodeMap = Object.fromEntries(nodes.map(node => [node.id, node]))
const links = Object
.entries(data.graph)
.flatMap(([module, deps]) => deps.map((dep) => {
const source = nodeMap[module]
const target = nodeMap[dep]
if (source === undefined || target === undefined)
return undefined
return defineLink({
source,
target,
color: 'var(--color-link)',
label: false,
})
}).filter(link => link !== undefined) as ModuleLink[])
return defineGraph({ nodes, links })
}