代码图谱构建与解析
本页讲图谱的两个 pass:Build(收集声明节点)与 addEdges(解析关系边),以及承重原语 Resolve(跟随引用到真实声明)。源码 packages/ttsc/internal/graph/build.go、edges.go、resolve.go、graph.go。
两 pass 构建
Build(build.go:16)先遍历每个用户源文件 collectDeclarations 建节点,再 addEdges 解析关系。driver.SourceFiles 已去掉声明文件、且 program 从不编译依赖的 .ts,所以 Build emit 的每个节点都是 workspace 源(External=false);external 边界 leaf 只作为边的解析目标进图(见下)。
pass 1:收集声明
collectStatements(build.go:57)按节点 kind 建节点:
namespace 自身是分组容器,不建节点;它的成员是各自的声明,递归进 body 收集(build.go:74)。成员 ID 从符号构建(符号已在父链里带 enclosing namespace),所以这里记的节点与稍后解析的边目标无需 walk 线程化 namespace 名就能一致。
方法节点(NodeMethod,graph.go:13)的 ID 是 class 限定的(path#Class.method:method),让解析出的方法调用落到 build pass 记的同一节点。
pass 2:解析边
addEdges(edges.go:15)对每个源文件再遍历一次,收集五类:
markExports:经 checker 的导出表解析哪些节点是模块导出表面的一部分——re-export(export { Foo } from)或 barrel(export *)都算,不只 inlineexportmodifier。这是公共 API 投影过滤的信号。collectHeritage:class/interface 的extends/implements基类,经 checker 解析(解包 barrel)。collectCalls:value-call 边(调用、new T()、<Component/>JSX、tagged-template)。collectTypeRefs:type-ref 边(参数、返回、属性、别名提到的具名类型)。collectDecorators:装饰器(语法捕获,dump-only 元数据,让消费者解释@Controller/@Get约定)。
边去重
addEdgeAt(edges.go:35)按 wire kind(不是内部 kind)键控去重:from\x00to\x00wireEdgeKind(kind, origin)。这样同一目标的两种用法(一个 call 和一个 new、同一基类的 extends 和 implements)都保留,而同一形式的重复用法塌成一条边。seen map 让 O(1) 查找,N 条边 O(N) 构建而非 O(N²)(graph.go:113)。
Resolve:承重原语
Resolve(resolve.go:43)跟随一个引用到 checker 绑定的真实声明:
两步关键:
- alias 解包:符号带
SymbolFlagsAlias时(经 barrel re-export)调Checker_getAliasedSymbol解包到真正声明的兄弟源。停在GetSymbolAtLocation会落到本地 import alias、在 index 文件切断边、塌回 tree-sitter 质量。 - external 分类:声明文件是
.d.ts或路径含/node_modules/→External=true(resolve.go:59)。checker 因preserveSymlinks默认 false 解析 symlink 到 realpath,所以 pnpmworkspace:*兄弟解析到真实源、不被误判为 external(resolve.go:69注释)。
Target(resolve.go:30)返回解析符号、声明源文件、是否 external、以及位置。返回 nil 表示 checker 绑不了符号(数字字面量、标点 token、未解析名)。
external leaf 的物化
当一条边的目标住在 node_modules 或 .d.ts,graph 物化一个 external 边界 leaf 节点作为边目标,但不把依赖的内部拉进图(edges.go:14 注释)。这让图的边界停在外部类型上——你能看到"用了 lodash 的某函数"这条边,但不会把 lodash 内部铺进图。
SourceTexts:evidence 输入
SourceTexts(build.go:31)把每个源文件映射到文本——NewDump 把节点/边的 byte span 转成 line/column 的 evidence 输入。这就是 MCP 工具能给出 sourceSpan 锚点的来源。
不变量
- 节点 ID 位置无关(
path#name:kind),编辑不 re-key。 - 边去重按 wire kind,区分形式但塌重复。
- workspace 节点
External=false;external 只作边目标 leaf 进图。 - Resolve 解包 alias 链,barrel re-export 落到真实声明。