mirror of
https://github.com/deepseek-ai/deepseek-harness
synced 2026-08-15 21:04:50 +00:00
refactor(typert): share type node traversal
This commit is contained in:
@@ -6,6 +6,7 @@
|
||||
*/
|
||||
|
||||
import { WorkspaceAnalyzer } from './analyzer.ts'
|
||||
import { childTypeNodeIds } from './model.ts'
|
||||
import { TypeGraphRenderer } from './renderer.ts'
|
||||
import type {
|
||||
FaceModel,
|
||||
@@ -15,7 +16,6 @@ import type {
|
||||
SourceDeclarationModel,
|
||||
SourceLocation,
|
||||
TypeNodeId,
|
||||
TypeNodeModel,
|
||||
} from './model.ts'
|
||||
|
||||
type Mode = 'emit' | 'waterfall' | 'parallel' | 'serial'
|
||||
@@ -514,7 +514,7 @@ function signatureTypeNames(renderer: TypeGraphRenderer, signature: SignatureMod
|
||||
const node = renderer.node(id)
|
||||
if (node.kind === 'reference' && node.target.kind !== 'type-parameter') names.add(node.name)
|
||||
if (node.kind === 'type-query') names.add(node.expression)
|
||||
for (const child of childTypes(node)) visit(child)
|
||||
for (const child of childTypeNodeIds(node)) visit(child)
|
||||
if (node.kind === 'object') for (const member of node.members) visitMember(member)
|
||||
if (node.kind === 'function' || node.kind === 'constructor') visitSignature(node.signature)
|
||||
}
|
||||
@@ -522,39 +522,6 @@ function signatureTypeNames(renderer: TypeGraphRenderer, signature: SignatureMod
|
||||
return [...names].sort()
|
||||
}
|
||||
|
||||
function childTypes(node: TypeNodeModel): TypeNodeId[] {
|
||||
switch (node.kind) {
|
||||
case 'parenthesized': return [node.type]
|
||||
case 'reference': return [...node.arguments]
|
||||
case 'union':
|
||||
case 'intersection': return [...node.types]
|
||||
case 'array': return [node.element]
|
||||
case 'tuple': return node.elements.map(element => element.type)
|
||||
case 'indexed-access': return [node.object, node.index]
|
||||
case 'operator': return [node.type]
|
||||
case 'conditional': return [node.check, node.extends, node.whenTrue, node.whenFalse]
|
||||
case 'infer': return [node.parameter.constraint, node.parameter.default]
|
||||
.filter((value): value is TypeNodeId => value !== undefined)
|
||||
case 'mapped': return [node.parameter.constraint, node.parameter.default, node.nameType, node.value]
|
||||
.filter((value): value is TypeNodeId => value !== undefined)
|
||||
case 'template-literal': return node.spans.map(span => span.type)
|
||||
case 'type-query':
|
||||
case 'import-type': return [...node.arguments]
|
||||
case 'predicate': return node.type === undefined ? [] : [node.type]
|
||||
case 'keyword':
|
||||
case 'literal':
|
||||
case 'object':
|
||||
case 'function':
|
||||
case 'constructor':
|
||||
case 'this': return []
|
||||
default: return assertNever(node)
|
||||
}
|
||||
}
|
||||
|
||||
function assertNever(value: never): never {
|
||||
throw new Error(`unhandled TypeGraph node: ${JSON.stringify(value)}`)
|
||||
}
|
||||
|
||||
/** Declarations longer than this render as a truncated stub. */
|
||||
const MAX_DECL_CHARS = 1500
|
||||
|
||||
|
||||
@@ -324,8 +324,52 @@ export type TypeNodeModel =
|
||||
| { readonly id: TypeNodeId; readonly kind: 'predicate'; readonly asserts: boolean; readonly parameter: string; readonly type?: TypeNodeId }
|
||||
| { readonly id: TypeNodeId; readonly kind: 'this' }
|
||||
|
||||
/**
|
||||
* Return the direct type-expression edges owned by one node.
|
||||
* @param node - compiler-independent type node to inspect.
|
||||
* @returns graph-local ids of its direct child type nodes.
|
||||
*/
|
||||
export function childTypeNodeIds(node: TypeNodeModel): TypeNodeId[] {
|
||||
switch (node.kind) {
|
||||
case 'parenthesized':
|
||||
case 'operator': return [node.type]
|
||||
case 'reference': return [...node.arguments]
|
||||
case 'union':
|
||||
case 'intersection': return [...node.types]
|
||||
case 'array': return [node.element]
|
||||
case 'tuple': return node.elements.map(element => element.type)
|
||||
case 'indexed-access': return [node.object, node.index]
|
||||
case 'conditional': return [node.check, node.extends, node.whenTrue, node.whenFalse]
|
||||
case 'mapped': return [
|
||||
...(node.parameter.constraint === undefined ? [] : [node.parameter.constraint]),
|
||||
...(node.parameter.default === undefined ? [] : [node.parameter.default]),
|
||||
...(node.nameType === undefined ? [] : [node.nameType]),
|
||||
...(node.value === undefined ? [] : [node.value]),
|
||||
]
|
||||
case 'template-literal': return node.spans.map(span => span.type)
|
||||
case 'type-query':
|
||||
case 'import-type': return [...node.arguments]
|
||||
case 'predicate': return node.type === undefined ? [] : [node.type]
|
||||
case 'infer': return [
|
||||
...(node.parameter.constraint === undefined ? [] : [node.parameter.constraint]),
|
||||
...(node.parameter.default === undefined ? [] : [node.parameter.default]),
|
||||
]
|
||||
case 'keyword':
|
||||
case 'literal':
|
||||
case 'object':
|
||||
case 'function':
|
||||
case 'constructor':
|
||||
case 'this': return []
|
||||
default: return assertNever(node)
|
||||
}
|
||||
}
|
||||
|
||||
/** Type declarations and expressions owned by one face. */
|
||||
export interface TypeGraph {
|
||||
readonly declarations: readonly TypeDeclarationModel[]
|
||||
readonly nodes: readonly TypeNodeModel[]
|
||||
}
|
||||
|
||||
function assertNever(value: never): never {
|
||||
throw new Error(`unsupported model variant ${JSON.stringify(value)}`)
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
* @module @deepseek-ai/dsh-typert-generator/renderer
|
||||
*/
|
||||
|
||||
import { childTypeNodeIds } from './model.ts'
|
||||
import type {
|
||||
MemberModel,
|
||||
ParameterModel,
|
||||
@@ -254,7 +255,7 @@ export class TypeGraphRenderer {
|
||||
const node = this.node(id)
|
||||
if (node.kind === 'reference' && node.target.kind === 'declaration') visitDeclaration(node.target.symbol)
|
||||
if (node.kind === 'import-type' && node.target?.kind === 'declaration') visitDeclaration(node.target.symbol)
|
||||
for (const child of childTypeNodes(node)) visitNode(child)
|
||||
for (const child of childTypeNodeIds(node)) visitNode(child)
|
||||
for (const signature of nodeSignatures(node)) visitSignature(signature)
|
||||
if (node.kind === 'object') for (const member of node.members) visitMember(member)
|
||||
}
|
||||
@@ -328,41 +329,6 @@ export class TypeGraphRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
function childTypeNodes(node: TypeNodeModel): TypeNodeId[] {
|
||||
switch (node.kind) {
|
||||
case 'parenthesized':
|
||||
case 'operator': return [node.type]
|
||||
case 'reference': return [...node.arguments]
|
||||
case 'union':
|
||||
case 'intersection': return [...node.types]
|
||||
case 'array': return [node.element]
|
||||
case 'tuple': return node.elements.map(element => element.type)
|
||||
case 'indexed-access': return [node.object, node.index]
|
||||
case 'conditional': return [node.check, node.extends, node.whenTrue, node.whenFalse]
|
||||
case 'mapped': return [
|
||||
...(node.parameter.constraint === undefined ? [] : [node.parameter.constraint]),
|
||||
...(node.parameter.default === undefined ? [] : [node.parameter.default]),
|
||||
...(node.nameType === undefined ? [] : [node.nameType]),
|
||||
...(node.value === undefined ? [] : [node.value]),
|
||||
]
|
||||
case 'template-literal': return node.spans.map(span => span.type)
|
||||
case 'type-query':
|
||||
case 'import-type': return [...node.arguments]
|
||||
case 'predicate': return node.type === undefined ? [] : [node.type]
|
||||
case 'infer': return [
|
||||
...(node.parameter.constraint === undefined ? [] : [node.parameter.constraint]),
|
||||
...(node.parameter.default === undefined ? [] : [node.parameter.default]),
|
||||
]
|
||||
case 'keyword':
|
||||
case 'literal':
|
||||
case 'object':
|
||||
case 'function':
|
||||
case 'constructor':
|
||||
case 'this': return []
|
||||
default: return assertNever(node)
|
||||
}
|
||||
}
|
||||
|
||||
function nodeSignatures(node: TypeNodeModel): SignatureModel[] {
|
||||
return node.kind === 'function' || node.kind === 'constructor' ? [node.signature] : []
|
||||
}
|
||||
|
||||
@@ -6,12 +6,45 @@ import type {
|
||||
TypeGraph,
|
||||
TypeNodeModel,
|
||||
} from '../src/model.ts'
|
||||
import { childTypeNodeIds } from '../src/model.ts'
|
||||
import { TypeGraphRenderError, TypeGraphRenderer } from '../src/renderer.ts'
|
||||
|
||||
const location = { file: 'fixture.ts', line: 1, column: 1 } as const
|
||||
const documentation = { tags: [] } as const
|
||||
|
||||
describe('TypeGraphRenderer defensive and optional shapes', () => {
|
||||
it('enumerates direct child edges for every type node kind', () => {
|
||||
const signature = { typeParameters: [], parameters: [], returns: 'leaf' } as const
|
||||
const cases: readonly (readonly [TypeNodeModel, readonly string[]])[] = [
|
||||
[keyword('keyword', 'string'), []],
|
||||
[{ id: 'literal', kind: 'literal', value: 1, text: '1' }, []],
|
||||
[{ id: 'parenthesized', kind: 'parenthesized', type: 'leaf' }, ['leaf']],
|
||||
[{ id: 'reference', kind: 'reference', name: 'Ref', target: { kind: 'standard', name: 'Ref' }, arguments: ['left', 'right'] }, ['left', 'right']],
|
||||
[{ id: 'union', kind: 'union', types: ['left', 'right'] }, ['left', 'right']],
|
||||
[{ id: 'intersection', kind: 'intersection', types: ['left', 'right'] }, ['left', 'right']],
|
||||
[{ id: 'array', kind: 'array', element: 'leaf' }, ['leaf']],
|
||||
[{ id: 'tuple', kind: 'tuple', elements: [{ type: 'leaf', optional: false, rest: false }] }, ['leaf']],
|
||||
[{ id: 'object', kind: 'object', members: [] }, []],
|
||||
[{ id: 'function', kind: 'function', signature }, []],
|
||||
[{ id: 'constructor', kind: 'constructor', abstract: false, signature }, []],
|
||||
[{ id: 'indexed', kind: 'indexed-access', object: 'left', index: 'right' }, ['left', 'right']],
|
||||
[{ id: 'operator', kind: 'operator', operator: 'keyof', type: 'leaf' }, ['leaf']],
|
||||
[{ id: 'conditional', kind: 'conditional', check: 'check', extends: 'extends', whenTrue: 'yes', whenFalse: 'no' }, ['check', 'extends', 'yes', 'no']],
|
||||
[{ id: 'infer-full', kind: 'infer', parameter: { id: 'infer', name: 'Value', const: false, constraint: 'constraint', default: 'fallback' } }, ['constraint', 'fallback']],
|
||||
[{ id: 'infer-empty', kind: 'infer', parameter: { id: 'infer', name: 'Value', const: false } }, []],
|
||||
[{ id: 'mapped-full', kind: 'mapped', parameter: { id: 'key', name: 'Key', const: false, constraint: 'constraint', default: 'fallback' }, nameType: 'name', value: 'value', readonly: 'preserve', optional: 'preserve' }, ['constraint', 'fallback', 'name', 'value']],
|
||||
[{ id: 'mapped-empty', kind: 'mapped', parameter: { id: 'key', name: 'Key', const: false }, readonly: 'preserve', optional: 'preserve' }, []],
|
||||
[{ id: 'template', kind: 'template-literal', head: '', spans: [{ type: 'leaf', text: '' }] }, ['leaf']],
|
||||
[{ id: 'query', kind: 'type-query', expression: 'value', arguments: ['leaf'] }, ['leaf']],
|
||||
[{ id: 'import', kind: 'import-type', module: 'fixture', arguments: ['leaf'], typeof: false }, ['leaf']],
|
||||
[{ id: 'predicate-full', kind: 'predicate', asserts: false, parameter: 'value', type: 'leaf' }, ['leaf']],
|
||||
[{ id: 'predicate-empty', kind: 'predicate', asserts: true, parameter: 'value' }, []],
|
||||
[{ id: 'this', kind: 'this' }, []],
|
||||
]
|
||||
|
||||
for (const [node, expected] of cases) expect(childTypeNodeIds(node)).toEqual(expected)
|
||||
})
|
||||
|
||||
it('renders optional source shapes and traverses every optional closure edge', () => {
|
||||
const dependency = declaration('dependency', 'Dependency', 'interface')
|
||||
const graph: TypeGraph = {
|
||||
|
||||
Reference in New Issue
Block a user