mirror of
https://github.com/vitest-dev/vitest.git
synced 2025-12-08 18:26:03 +00:00
169 lines
4.5 KiB
TypeScript
169 lines
4.5 KiB
TypeScript
import type {
|
|
Graph,
|
|
GraphConfig,
|
|
GraphController,
|
|
GraphLink,
|
|
GraphNode,
|
|
} from 'd3-graph-controller'
|
|
import type { ModuleGraphData } from 'vitest'
|
|
import { defineGraph, defineLink, defineNode } from 'd3-graph-controller'
|
|
|
|
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()
|
|
}
|
|
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 })
|
|
}
|