Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion shared/src/messages.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import z from 'zod'
import type { Tree } from '../../src/traceTree'
import { traceLine, typeLine } from './traceData'
import { traceLine, traceTypeRef, typeLine } from './traceData'

export const ping = z.object({
message: z.literal('ping'),
Expand Down Expand Up @@ -102,6 +102,7 @@ const zodTree: z.ZodType<Tree> = z.lazy(() =>
line: traceLine,
children: z.array(zodTree),
types: z.array(typeLine),
typeRefs: z.array(traceTypeRef),
childTypeCnt: z.number(),
childCnt: z.number(),
typeCnt: z.number(),
Expand Down
66 changes: 66 additions & 0 deletions shared/src/traceData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,33 @@ export const typeLine = z.object({
display: z.string().optional(),
})

export function describeTypeLine(line?: TypeLine) {
if (!line)
return 'unresolved type'

const name = line.display?.trim() || line.intrinsicName?.trim() || `Type ${line.id}`
const details = [
`id=${line.id}`,
line.recursionId !== undefined ? `recursionId=${line.recursionId}` : undefined,
line.flags?.length ? line.flags.join(', ') : undefined,
].filter((x): x is string => !!x)

return details.length ? `${name} (${details.join(' · ')})` : name
}

export const traceLineTypeArgNames = {
structuredTypeRelatedTo: ['sourceId', 'targetId'],
} as const

export type TraceTypeRef = z.infer<typeof traceTypeRef>
export const traceTypeRef = z.object({
key: z.string(),
typeId: z.number(),
type: typeLine.optional(),
label: z.string(),
title: z.string(),
})

export type TraceLine = z.infer<typeof traceLine>
export const traceLine = z.object({
pid: z.number(),
Expand All @@ -30,11 +57,50 @@ export const traceLine = z.object({
.object({
typeId: z.number().optional(),
})
.passthrough()
.optional(),
})
.passthrough()
.optional(),
})

export function getTraceLineTypeRefs(line: TraceLine, typeById = new Map<number, TypeLine>()) {
const args = line.args
if (!args)
return []

const refs: TraceTypeRef[] = []
const seenKeys = new Set<string>()
const knownKeys = traceLineTypeArgNames[line.name as keyof typeof traceLineTypeArgNames] ?? []

function pushRef(key: string, typeId: unknown) {
if (seenKeys.has(key) || typeof typeId !== 'number')
return

seenKeys.add(key)
const type = typeById.get(typeId)
refs.push({
key,
typeId,
type,
label: type?.display?.trim() || type?.intrinsicName?.trim() || `Type ${typeId}`,
title: describeTypeLine(type),
})
}

for (const key of knownKeys)
pushRef(key, args[key as keyof typeof args])

for (const [key, value] of Object.entries(args)) {
if (key === 'results' && value && typeof value === 'object' && 'typeId' in value)
pushRef(`${key}.typeId`, (value as { typeId?: unknown }).typeId)
else if (key.endsWith('Id'))
pushRef(key, value)
}

return refs
}

export type DataLine = TraceLine | TypeLine

export type TraceData = z.infer<typeof traceData>
Expand Down
32 changes: 29 additions & 3 deletions src/traceTree.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
import { isAbsolute, join, relative } from 'node:path'
import type { FileStat } from '../shared/src/messages'
import type { TraceData, TraceLine, TypeLine } from '../shared/src/traceData'
import { getTraceLineTypeRefs } from '../shared/src/traceData'
import type { TraceData, TraceLine, TraceTypeRef, TypeLine } from '../shared/src/traceData'
import { getWorkspacePath } from './storage'
import { postMessage } from './webview'
import { traceFiles } from './appState'

export interface Tree { id: number, line: TraceLine, children: Tree[], types: TypeLine[], childCnt: number, childTypeCnt: number, typeCnt: number }
export interface Tree {
id: number
line: TraceLine
children: Tree[]
types: TypeLine[]
typeRefs: TraceTypeRef[]
childCnt: number
childTypeCnt: number
typeCnt: number
}
function getRoot(): Tree {
return {
id: 0,
Expand All @@ -20,6 +30,7 @@ function getRoot(): Tree {
},
children: [],
types: [],
typeRefs: [],
childCnt: 0,
childTypeCnt: 0,
typeCnt: 0,
Expand All @@ -35,6 +46,12 @@ export function toTree(traceData: TraceData, workspacePath: string): Tree {
let id = 0

const stack: Tree[] = []
const typeById = new Map<number, TypeLine>()

for (const line of traceData) {
if ('id' in line)
typeById.set(line.id, line)
}

treeIndexes = [tree]

Expand Down Expand Up @@ -62,7 +79,16 @@ export function toTree(traceData: TraceData, workspacePath: string): Tree {
}
else if (line.dur) {
endTs = line.ts + (line.dur ?? 0)
const child = { id: ++id, line, children: [], types: [], childTypeCnt: 0, childCnt: 0, typeCnt: 0 }
const child = {
id: ++id,
line,
children: [],
types: [],
typeRefs: getTraceLineTypeRefs(line, typeById),
childTypeCnt: 0,
childCnt: 0,
typeCnt: 0,
}
treeIndexes[id] = child
curr.childCnt = curr.children.push(child)
stack.push(curr)
Expand Down
43 changes: 43 additions & 0 deletions test/trace-type-refs.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { describe, expect, it } from 'vitest'
import { getTraceLineTypeRefs, typeLine } from '../shared/src/traceData'

describe('getTraceLineTypeRefs', () => {
it('resolves mapped id fields and nested result type ids', () => {
const source = typeLine.parse({
id: 38,
ts: 1,
display: 'SourceType',
})
const target = typeLine.parse({
id: 229,
ts: 2,
intrinsicName: 'TargetType',
})

const refs = getTraceLineTypeRefs(
{
pid: 1,
tid: 1,
ph: 'X',
cat: 'checkTypes',
ts: 1,
name: 'structuredTypeRelatedTo',
dur: 10,
args: {
sourceId: 38,
targetId: 229,
results: {
typeId: 38,
},
},
},
new Map([[38, source], [229, target]]),
)

expect(refs.map(ref => ref.key)).toEqual(['sourceId', 'targetId', 'results.typeId'])
expect(refs[0].label).toBe('SourceType')
expect(refs[1].label).toBe('TargetType')
expect(refs[2].typeId).toBe(38)
expect(refs[2].title).toContain('SourceType')
})
})
44 changes: 44 additions & 0 deletions ui/components/TraceTypeRefs.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<script setup lang="ts">
import type { TraceTypeRef } from '../../shared/src/traceData'

const props = defineProps<{ refs: TraceTypeRef[] }>()

const openRef = ref<string | null>(null)

function toggle(ref: TraceTypeRef) {
const key = `${ref.key}:${ref.typeId}`
openRef.value = openRef.value === key ? null : key
}
</script>

<template>
<div v-if="props.refs.length" class="mt-1 flex flex-col gap-1 rounded border border-dashed border-[var(--vscode-editorWidget-border)] px-2 py-1 text-xs">
<div class="font-medium opacity-70">
Type refs
</div>

<div v-for="ref in props.refs" :key="`${ref.key}:${ref.typeId}`" class="flex flex-col gap-1">
<div class="flex items-start gap-2">
<button
class="mt-0.5 rounded bg-[var(--vscode-button-background, green)] px-1 py-0.5 text-[var(--vscode-button-foreground, white)] focus:outline-none focus:ring-1 focus:ring-[var(--vscode-focusBorder, blue)]"
:title="`Go to definition: ${ref.title}`"
@click="toggle(ref)"
>
<UIcon name="i-heroicons-arrow-top-right-on-square" class="relative top-0.5" />
</button>

<div class="flex min-w-0 flex-col">
<span class="truncate" :title="ref.title">
<span class="opacity-70">{{ ref.key }}</span>:
<span class="font-medium">{{ ref.label }}</span>
<span class="opacity-60">#{{ ref.typeId }}</span>
</span>

<div v-if="openRef === `${ref.key}:${ref.typeId}` && ref.type" class="pl-1 pt-1">
<TypeLine :line="ref.type" />
</div>
</div>
</div>
</div>
</div>
</template>
8 changes: 8 additions & 0 deletions ui/components/TreeNode.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const sendMessage = useNuxtApp().$sendMessage

const children = computed(() => childrenById.get(props.tree.id) ?? [])
const types = computed(() => typesById.get(props.tree.id) ?? [])
const typeRefs = computed(() => props.tree.typeRefs ?? [])

function fetchChildren() {
if (children.value.length === 0)
Expand Down Expand Up @@ -65,6 +66,13 @@ const insetClass = `border-e min-w-2 border-[var(--vscode-tree-inactiveIndentGui
</div>

<div class="flex flex-row justify-self-end justify-evenly">
<UExpand v-if="typeRefs.length > 0" class="min-w-40" :expandable="true">
<template #label>
<span class="pl-1">{{ `Type refs: ${typeRefs.length}` }}</span>
</template>
<TraceTypeRefs :refs="typeRefs" />
</UExpand>
<div v-else class="min-w-40" />
<UExpand v-if="props.tree.typeCnt > 0" class="min-w-40" @expand="fetchTypes">
<template #label>
<span class="pl-1">{{ `Types: ${props.tree.typeCnt}` }} {{ `${props.tree.childTypeCnt || props.tree.typeCnt ? `/ ${props.tree.childTypeCnt + props.tree.typeCnt}` : ''}` }}</span>
Expand Down