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
28 changes: 27 additions & 1 deletion shared/src/traceData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export const typeLine = z.object({
intrinsicName: z.string().optional(),
recursionId: z.number().optional(),
flags: z.array(z.string()).optional(),
ts: z.number(),
ts: z.number().optional(),
dur: z.number().optional(),
display: z.string().optional(),
})
Expand Down Expand Up @@ -39,3 +39,29 @@ export type DataLine = TraceLine | TypeLine

export type TraceData = z.infer<typeof traceData>
export const traceData = z.array(typeLine.or(traceLine))

export interface TraceDataSummary {
totalTypes: number
timestampedTypes: number
untimestampedTypes: number
parseWarning?: string
}

export function hasTypeTimestamp(line: TypeLine): line is TypeLine & { ts: number } {
return typeof line.ts === 'number'
}

export function getTraceDataSummary(data: TraceData): TraceDataSummary {
const typeLines = data.filter((line): line is TypeLine => 'id' in line)
const timestampedTypes = typeLines.filter(hasTypeTimestamp).length
const untimestampedTypes = typeLines.length - timestampedTypes

return {
totalTypes: typeLines.length,
timestampedTypes,
untimestampedTypes,
parseWarning: untimestampedTypes > 0
? `${untimestampedTypes} type entr${untimestampedTypes === 1 ? 'y is' : 'ies are'} missing timestamps and cannot be attributed to trace spans.`
: undefined,
}
}
8 changes: 6 additions & 2 deletions src/traceTree.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { isAbsolute, join, relative } from 'node:path'
import type { FileStat } from '../shared/src/messages'
import type { TraceData, TraceLine, TypeLine } from '../shared/src/traceData'
import { type TraceData, type TraceLine, type TypeLine, hasTypeTimestamp } from '../shared/src/traceData'
import { getWorkspacePath } from './storage'
import { postMessage } from './webview'
import { traceFiles } from './appState'
Expand All @@ -27,6 +27,10 @@ function getRoot(): Tree {
}

let treeIndexes: Tree[] = []
function hasTraceTimestamp(line: TraceData[number]): line is TraceLine | TypeLine & { ts: number } {
return 'cat' in line || ('id' in line && hasTypeTimestamp(line))
}

export function toTree(traceData: TraceData, workspacePath: string): Tree {
const tree: Tree = { ...getRoot() }
let endTs = Number.MAX_SAFE_INTEGER
Expand All @@ -38,7 +42,7 @@ export function toTree(traceData: TraceData, workspacePath: string): Tree {

treeIndexes = [tree]

const data = traceData.filter(x => 'id' in x || ('cat' in x)).sort((a, b) => a.ts - b.ts)
const data = traceData.filter(hasTraceTimestamp).sort((a, b) => a.ts - b.ts)
// const data = traceData.filter(x => 'id' in x || ('cat' in x && x.cat?.startsWith('check'))).sort((a, b) => a.ts - b.ts)
for (const line of data) {
if ('args' in line && line.args?.path && isAbsolute(line.args?.path))
Expand Down
69 changes: 69 additions & 0 deletions test/traceData.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { describe, expect, it } from 'vitest'
import { getTraceDataSummary, traceData } from '../shared/src/traceData'

describe('traceData', () => {
it('parses trace events', () => {
const result = traceData.safeParse([
{
pid: 1,
tid: 1,
ph: 'X',
cat: 'check',
ts: 10,
name: 'checkSourceFile',
dur: 20,
args: { path: '/workspace/src/index.ts', pos: 1, end: 2 },
},
])

expect(result.success).toBe(true)
})

it('parses timestamped types', () => {
const result = traceData.safeParse([
{
id: 1,
intrinsicName: 'string',
recursionId: 1,
flags: ['String'],
ts: 12,
dur: 1,
display: 'string',
},
])

expect(result.success).toBe(true)
expect(result.success && getTraceDataSummary(result.data)).toEqual({
totalTypes: 1,
timestampedTypes: 1,
untimestampedTypes: 0,
parseWarning: undefined,
})
})

it('parses untimestamped stock TypeScript types', () => {
const result = traceData.safeParse([
{
id: 1,
intrinsicName: 'string',
recursionId: 1,
flags: ['String'],
display: 'string',
},
{
id: 2,
recursionId: 2,
flags: ['Object'],
display: '{ value: string }',
},
])

expect(result.success).toBe(true)
expect(result.success && getTraceDataSummary(result.data)).toEqual({
totalTypes: 2,
timestampedTypes: 0,
untimestampedTypes: 2,
parseWarning: '2 type entries are missing timestamps and cannot be attributed to trace spans.',
})
})
})
65 changes: 65 additions & 0 deletions test/traceTree.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { describe, expect, it, vi } from 'vitest'
import { toTree } from '../src/traceTree'
import type { TraceData } from '../shared/src/traceData'

vi.mock('../src/storage', () => ({
getWorkspacePath: vi.fn(() => '/workspace'),
}))

vi.mock('../src/webview', () => ({
postMessage: vi.fn(),
}))

vi.mock('../src/appState', () => ({
traceFiles: { value: {} },
}))

vi.mock('vscode', () => ({
workspace: {
workspaceFolders: [{ uri: { fsPath: '/workspace' } }],
getConfiguration: vi.fn(() => ({
get: vi.fn(),
})),
onDidChangeConfiguration: vi.fn(),
},
window: {
showErrorMessage: vi.fn(),
createOutputChannel: vi.fn(() => ({
appendLine: vi.fn(),
show: vi.fn(),
})),
},
env: {
appRoot: '/Applications/Visual Studio Code.app',
},
}))

describe('traceTree', () => {
it('does not crash or attribute untimestamped types', () => {
const data: TraceData = [
{
pid: 1,
tid: 1,
ph: 'X',
cat: 'check',
ts: 10,
name: 'checkSourceFile',
dur: 20,
args: { path: '/workspace/src/index.ts', pos: 1, end: 2 },
},
{
id: 1,
intrinsicName: 'string',
recursionId: 1,
flags: ['String'],
display: 'string',
},
]

const tree = toTree(data, '/workspace')

expect(tree.children).toHaveLength(1)
expect(tree.children[0].types).toHaveLength(0)
expect(tree.children[0].typeCnt).toBe(0)
})
})