Skip to content

sclcoder/swift-symbol-index

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

SwiftCodeIndexer

一个学习用的迷你代码索引器:用纯 Swift 复刻 ragflow 那类"代码索引器"的内核。 目标不是做产品,而是把"代码索引"这件看着唬人的事拆开、祛魅、能自己讲清楚每一行

面向对象:iOS 开发者。下面会一直用 Xcode / SourceKit 这些你熟的东西当锚点。


1. 缘起:代码索引器到底牛在哪

我接触过一个 ragflow 这类的代码索引器,它把上百个代码仓库索引起来, 能精确查符号、查引用、查 API 调用方、甚至跨服务追 Feign 调用 / Kafka topic / 共享数据库表。

看清楚之后会发现:它不是一个天才魔法,是一堆成熟技术拼起来的组合体。 拆成三层:

作用 ragflow 用什么 本项目用什么
① 结构索引(精确) 解析代码,抽出符号/引用/接口/表,存起来精确查 tree-sitter + SQLite SwiftSyntax + SQLite
② 语义索引(模糊) 把代码片段变成向量,按"意思"搜 开源 RAGFlow embeddings (Phase 2 待做)
③ 接入层 把以上能力暴露成工具给 LLM 调 MCP 协议封装 CLI (Phase 3 升级为 MCP)

真正难、真正值钱的是 ragflow 在多个仓库之间连边(A 服务的 Feign 接口 = B 服务的 controller, 同一个 topic 字符串的两端)。但那是放大题;内核就是上面这三层。

重要事实: 这类索引器的 MCP 通常不索引它自己,只暴露它对目标仓库的索引产出。 所以没法"用 ragflow 读 ragflow 源码"——本项目就是自己把内核搭一遍来理解它。


2. iOS 视角:你不是从零开始

ragflow 的能力 你早就在用的对应物
find_symbol(找定义) Xcode 的 Jump to Definition / Open Quickly (⌘⇧O)
find_references(找引用) Xcode 的 Call Hierarchy / Find Callers
结构索引层(存符号的库) SourceKit-LSP / IndexStoreDB(Xcode 编译时建的 index store)

也就是说,ragflow 的"结构层"≈ 苹果给 Swift 做的那套东西的跨语言、跨仓库版。 你已经在"用户那一侧"用了好几年,只是没拆开看过后端。


3. 本项目架构

刻意按 ragflow 三层来组织,文件一一对应:

SwiftCodeIndexer/
├── Package.swift                  依赖 swift-syntax
└── Sources/SwiftCodeIndexer/
    ├── Symbol.swift               数据模型:Symbol(声明)+ Reference(引用)
    ├── SymbolCollector.swift      ① 解析层 —— SyntaxVisitor 走语法树抽"声明"(对应 tree-sitter)
    ├── ReferenceCollector.swift   ①(下半场)—— 再走一遍树抽"引用",做出"谁用到了它"
    ├── IndexStore.swift           ② 存储+查询 —— SQLite 两张表(symbols / refs),建表/插入/按名查
    └── main.swift                 ③ CLI 入口 —— index / find / references 三条命令(对应 ragflow 的 MCP 工具)

技术选型说明:

  • SwiftSyntax / SwiftParser:苹果官方 Swift 解析器,把源码变成语法树(AST)。
  • SQLite3:走系统自带模块 import SQLite3,不引第三方依赖。生成的 index.db 是普通 SQLite 文件,可用任意 SQLite 浏览器打开——"索引"祛魅:本质就是一张表。

4. 用法

cd ~/Desktop/SwiftCodeIndexer

# 索引:传一个 .swift 文件,或递归索引一个目录(同时抽声明 + 引用)
swift run SwiftCodeIndexer index <文件或目录>

# 查声明:按符号名查(支持子串;精确命中排在前面)—— 这就是你自己的 find_symbol
swift run SwiftCodeIndexer find <名字>

# 查引用:谁用到了这个名字(精确名)—— 这就是你自己的 find_references
swift run SwiftCodeIndexer references <名字>

索引库默认生成在当前目录的 index.db(里面有 symbolsrefs 两张表)。每次 index 会先清空再重建。

find 现在会顺带显示每个符号的引用热度:

$ swift run SwiftCodeIndexer find Symbol
找到 2 个:
  [struct] Symbol  →  Sources/SwiftCodeIndexer/Symbol.swift:5  (7 处引用)
  [class] SymbolCollector  →  Sources/SwiftCodeIndexer/SymbolCollector.swift:9  (2 处引用)

5. find vs references:这层索引能拿来干嘛

单独看,引用层像是"多存了一张表";但它把这个工具从查字典升级成了查关系

  • find <名字> 回答 "它定义在哪"(跳转到定义)。
  • references <名字> 回答 "谁用到了它"(找调用方)。

两个加起来,你手里就有了一个最小的调用关系数据库——不再只是符号清单,而是符号之间的"边"。

它实际能帮你干三件事

  1. 改动前的影响面评估(impact analysis)。 最实用。删 / 改 / 重命名一个 send 之前,先 references send 看它被哪些文件、多少处用到,改之前心里有数,而不是改完等编译器报错。 这类工具在大型代码库里最值钱的用法就是这个——"我改这个接口会炸到谁"。
  2. 找死代码。 一个符号有声明,但 (0 处引用) → 大概率没人用,可以删。现在 find 直接把这个数字显示出来了。
  3. 读陌生代码。 进一个新项目,references 一把列出某个核心类型的所有触点,比全文搜索准—— 它只命中真正的标识符使用,不会命中注释和字符串里的同名文本。

对应到你熟的东西

这就是 Xcode 的 Find Callers / Call Hierarchy。你现在等于把那套东西的后端自己搭了一遍: Xcode 前端点一下"谁调用了我",底层查的就是这种引用索引(它的 IndexStoreDB)。

诚实的局限:它是"线索",不是"事实"(最长见识的一段)

这一版引用是纯按名字撞。最直白的证据就在 find insert 的输出里:

$ swift run SwiftCodeIndexer find insert
找到 2 个:
  [func] insert  →  Sources/SwiftCodeIndexer/IndexStore.swift:41  (2 处引用)
  [func] insert  →  Sources/SwiftCodeIndexer/IndexStore.swift:83  (2 处引用)

两个 insert 重载(一个收 [Symbol]、一个收 [Reference])都显示"2 处引用"—— 但全项目对名字 insert 的引用统共就那 2 处,根本分不清哪一处归哪个重载。 因为我们不解析类型、不认作用域,只要名字相同就算一笔。

所以现在的引用结果够用来缩小范围、做粗粒度影响面,但不能当成精确调用图。 从"名字撞上了"升级到"确实是同一个东西",需要类型 / 作用域解析(resolve)—— 而跨文件、尤其跨仓库时,这步正是结构索引最脆弱的一环。ragflow 真正难、真正值钱的功夫, 全花在"确认 A 服务的 Feign 接口就是 B 服务那个 controller"这种跨服务连边上。 你这个 toy 版把这道坎儿摆在了明面上,这比直接用 Xcode 更长见识。


6. 验证记录

  • 自举(Phase 1):索引自己的 Sources/find Symbol 同时命中 Symbol(struct)和 SymbolCollector(class)。
  • 引用(Phase 1.5):自举后 references Symbol 返回 7 处使用(类型标注、构造调用),声明点不在其中 ——声明进 symbols 表、引用进 refs 表,互不污染;find insert 暴露出按名字撞的同名歧义(见上)。
  • 真实库:索引 Moya(网络库)→ 66 文件 / 295 符号; find MultiTarget 正确返回 enum MultiTarget(精确) + MultiTargetSpec(子串),精确命中排在前面。

结论:结构层在真实第三方代码上跑通,且能正确排序。等价于手搓了一个 Xcode "Open Quickly" + "Find Callers"。


7. 当前能力 & 已知局限

✅ 能做:

  • 解析 class / struct / enum / protocol / func 的声明,记录名字、种类、文件、行号。
  • 按名精确 + 子串查询,精确命中优先。
  • (Phase 1.5) 收集标识符引用(表达式里的调用/读取、类型位置的 : Foo / -> Foo 等), references <名字> 精确查出"谁用到了它"。声明进 symbols 表、引用进 refs 表,互不污染。

❌ 还不能(= 后续学习的动机):

  • 引用是纯按名字匹配:只知道源码里写了 send,不知道它指向哪一个 send 的声明 (同名不同物会一起命中)。这恰恰是 ragflow 跨文件、跨服务连边为何难的缩影。
  • 不认 extension、计算属性、typealias、嵌套类型的父子归属。
  • 不能按语义搜(如"找网络重试逻辑")——需要向量化。
  • 不解析 Objective-C(SwiftSyntax 只吃 .swift)。

8. 分阶段学习路线

  • Phase 1 — 结构索引层:SwiftSyntax 解析 → 抽符号 → SQLite 存 → 按名查。(已完成)
  • Phase 1.5 — references <名字>:收集标识符引用,做出"谁调用了它"。纯 Swift,当晚见效。(已完成) 关键体会:跨文件引用其实是"按名字猜"的,这正是 ragflow 跨服务连边为何难的缩影。
  • Phase 2 — 语义搜索:给符号/片段算 embedding,实现 search "网络重试逻辑"。 最长见识的一块,需踏出 Swift 调 embedding API + 算 cosine 相似度。
  • Phase 3 — MCP 封装:用 MCP SDK 把 find 暴露成工具,在 Claude Code 里直接调,复刻 ragflow 最外层。
  • Phase 4 — 读 ragflow 真源码:当作"规模化 + 跨服务"的参考架构来读,重点看它怎么连跨服务的边。

推荐坡度:1 → 1.5 → 2,最平缓。

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages