-
Notifications
You must be signed in to change notification settings - Fork 151
LSP Indexing Boundaries
Lithe 同时跨 Rust Core、Swift、TypeScript/Tauri、JDTLS、Maven 和 Tree-sitter。这里的 bug 往往不是算法完全错误,而是输入边界、所有权或协议阶段被简化得过头。
#626 — 深层 Java 文件触发 stack overflow 并让应用闪退
症状:MyBatis 启动索引扫描 Java 文件时,哪怕文件不大,只要有足够深的嵌套块或左结合表达式,宿主线程就耗尽栈并收到 SIGABRT。
根因:collect_types 对 Tree-sitter AST 逐层递归。递归深度由用户代码决定,不能用“正常 Java 文件通常不深”作为安全假设;这不是把线程栈调大或截断索引能可靠解决的问题。
方案:改为堆上的显式栈执行迭代 DFS,逆序压入子节点以保留源码先序;继续保持全限定名、Mapper 匹配优先级和导航范围不变。
回归:PR 报告用 8,192 次加法和 4,096 层嵌套块复现旧实现的 stack overflow,修复后验证深层声明完整索引、泛型方法和源码顺序。关键经验是:把递归改成迭代时,必须同时锁住遍历顺序和 tie-break 规则,否则只是“没崩但结果换序”。
#389 — JDTLS 变化路径无法进入 Java workspace policy
症状:日志显示 JDTLS 已经 Ready,但 Windows 的 JDTLS watcher 更新发送绝对路径到 Rust Core 后,Java workspace change 处理失败。
根因:Windows verbatim drive/UNC 路径(包括大小写不同的 \\?\\UNC\\...)没有先转换为 Core 期望的路径表示;JDTLS 自身不是失败点,失败发生在 Java workspace change 的跨层输入上。
方案:在 Windows 前端进入 workspace policy 前统一规范化 verbatim drive/UNC 前缀,并覆盖小写/混合大小写前缀测试;把 Git repository root 的更深层契约修复留给 #625。
经验:日志中“服务 Ready”只能证明一个阶段。跨边界输入还要单独验证编码、路径和资源身份。
#539 — Swift/Rust/TypeScript 仓库被误启动 JDTLS
症状:只要工作区存在一个未忽略的 .java 文件,旧 policy 就启动 JDTLS。Lithe 自己的深层 Spring fixture 被当成真实 Maven 项目,导致 JVM、全量 import 和长期缓存开销。
根因:激活判据把 representative_java_path.is_some() 当成 should_start,只看文件存在性,不看项目主体或构建描述。
方案:在共享 Core 中要求 .java 同时满足以下其一:根目录两层内有 pom.xml、Gradle build/settings 或 wrapper;或根目录两层内有 Java 源文件作为无构建系统项目的兜底。特意不复用更宽松的 is_build_configuration(),避免无关的 .mvn/gradle 目录触发 JDTLS。
边界:主动打开单个 .java 仍可按需获得语言支持;这里只收紧预热激活条件。共享 Core 只消费 shouldStart,两端无需各写一套 heuristics。
#318 — @GetMappingCustom 被识别成 @GetMapping
根因:Spring endpoint indexing 用 contains 判断注解名,导致自定义注解共享前缀时生成错误 endpoint/base route。
方案:按精确 annotation boundary 匹配 @GetMapping/@RequestMapping,再只在该注解自己的参数范围内解析;在 Core 修复一次,macOS/Windows 通过 spring.index 自动一致。
#344 — Bean 名称被前缀注解或相邻注解污染
症状:@BeanFactory("decoy") @Bean("real") 返回 decoy,或者 @Service("s") @Component("c") 解析出 ) @Component( 之类跨注解字符串。
根因:检测阶段已使用准确边界,但命名阶段仍用 find("@Bean") 和宽范围正则;参数解析没有被限制在命中的注解内,多个组件注解的优先级也依赖数组顺序。
方案:复用 SpringAnnotation 的精确 pattern,先定位 annotation,再调用 isolate_annotation_at 解析自己的参数;多个候选按 Match::start() 选源码中最靠前者;无参数/空值回退默认 Bean 名称。
经验:同一领域的 detection 和 extraction 必须共用边界定义;否则“能检测到”与“能正确提取”会形成两套语义。
#545 — Java Mapper 方法只能跳回自己
根因:JDTLS 的 textDocument/definition 只知道 Java 符号自身,不知道 XML namespace + statement id 的业务关系。
方案:新增共享 mybatis.index,只配对同时存在的 Java 方法和 XML select/insert/update/delete statement;macOS/Windows 在请求 LSP 前优先消费该确定性索引,也支持从 XML statement 跳回 Mapper 方法。注释和有方法体的 default 方法被明确排除。
经验:不要把外部 LSP 能力当作完整领域模型;可以在 LSP 前加一个确定性、可测试的领域索引,并把 fallback 保留给真正没有映射的场景。
#384 — Maven test main 启动为 ClassNotFoundException
根因:不同 Maven module 可以有相同 FQCN;旧生成器用 qualified-name map 找源码,无法区分 src/main 与 src/test。运行计划又没有使用 test classpath。
方案:从 Java scanning 传递精确 sourcePath 和 sourceSet,test main 在 test-compile 后使用 Exec Plugin 的 test classpath;生成器 fingerprint 升到 revision 3,让 revision 2 文档被诊断为 stale 并重新生成;无 sourceSet 的旧文档仍走 source path fallback。
经验:FQCN 是语言身份,不一定是构建归属身份。持久化生成结果时要有 schema/generator revision,不能让旧格式静默解释成新语义。
#274 — 混合工作区的 Java 入口被第一个 reactor 劫持
根因:Windows 一次提交所有 Java path,旧逻辑按 request 选择一个 Maven owner;独立 Java 文件和另一个 nested reactor 的入口都被送进第一个 reactor。
方案:对每个 Java path 向上查找 ancestor Maven reactor,分别决定 owner、working directory、module 和 Maven toolchain;Maven-owned entry 绑定 project-maven,standalone entry 保持普通 JDK 运行。生成器 fingerprint 变化会使持久化配置失效并重建。
#476 — 运行子模块未构建 reactor 依赖
根因:选中 reactor module 后生成的 Run/Debug 命令只执行目标模块,依赖模块未先构建。
方案:选中非根模块时加入 Maven -am(also-make);根 reactor . 保持原语义。方案小,但关键是把“根模块”和“子模块”作为不同边界写进 fixture。
#477 — wrapper 版本精确不匹配阻止运行
根因:wrapper 声明 3.6.3 时,更新的本机 Maven 3.9.16 被当成不匹配并拦截;同时没有 machine-local maven.repo.local 配置入口。
方案:放宽“更新本机 Maven”与 wrapper 的兼容判断,添加本地仓库路径的 machine-local 配置,并把 -Dmaven.repo.local=<path> 注入 Core launch plan;同步 Swift/Windows UI、持久化、契约和 fixture。
#260 — 恢复的 Java 文档在 JDTLS import 前打开
根因:LSP 标准 initialize handshake 完成后,Lithe 立即发送 didOpen;JDTLS 还有 Maven project import 的第二阶段。过早绑定 working copy 会让有效项目类型在该 server 进程生命周期内一直解析失败。
方案:只有 ServiceReady 后才同步 Java 文档和 watcher;多个恢复更新合并,最终文本以 version 1 打开。PR 报告同项目 A/B 测试从 60 条 Java diagnostics 降到 0 条(剩余为无关的 YAML false positives)。
#292 — 长 import 被普通 LSP 初始化 deadline 杀死
根因:普通 LSP initialize deadline 与 JDTLS 的 Maven import readiness 共用一套等待语义,长 import 被误判为挂死;Windows 又有一套自己的 frontend deadline,双端行为不一致。
方案:Core 内分离 initialize deadline 与 ServiceReady;使用 45 秒 no-progress timeout + 10 分钟 absolute cap,有模块/下载/吞吐等有意义进度时延长存活;输出结构化 stall diagnostics,移除 Windows 自己的 readiness deadline,并在初始 import 关闭非必要的源码附件下载。
#217 — 自动选中 JDK 25 导致 JDTLS exit code 13
根因:Windows 自动发现没有按 JDTLS 支持范围过滤,误选 JDK 25;失败状态又永久记录,后续不会自动重试。
方案:限制自动选择 JDK 17–23;失败改为 5 秒 cooldown,手动 Restart 立即清除失败状态。这里的设计平衡了失败风暴与可恢复性:不是永久失败,也不是每次渲染都重启。
#539 解决“什么时候应该启动 JDTLS”;#276 解决“打包后如何可靠启动 JDTLS”。后者把 Equinox、platform configuration、Lombok 和架构资源解析留在平台 adapter,让 Core 负责 JVM 参数;universal app 按当前进程架构选择 arm64/x86_64 JDK,Windows 直接以 bundled java.exe 启动,避免把 PowerShell/shell 作为隐含中间层。工程经验是:共享层决定可重复的 launch plan,平台层决定本地 executable/resource。
#47 — Maven 项目检测恢复后又被取消结果污染
根因:Rust Maven model 的 groupId/artifactId(含 nested modules)没有完整映射到 Swift DTO;runtime discovery 被取消或被新请求取代后,旧状态没有安全 reset。
方案:补全 DTO 映射,取消/取代时清理 runtime discovery state,并加 canceled discovery 回归测试。
#278 — 缺 Node 阻塞了不需要 Node 的 Java 服务
根因:toolchain diagnostic 按项目或全局计算,不按 run configuration 的实际 consumer 计算;Spring Boot backend 与 Vue/npm frontend 被同一条 requirement 绑在一起。
方案:由 detector 声明 runtime consumption,要求只阻塞真正消费该 runtime 的配置;Node 的版本校验和 process launch 使用同一个 effective path,Bun 不错误绑定 Node,旧 v2 文档在 resolve 时做内存 reconciliation。
#163 — Windows 项目级运行配置保存后不生效
根因:运行配置有 generated/team/local 多层文档,但 Windows host 的 machine-local layer 没有作为同一份输入贯穿 inspect、resolve、updateOptions 和 createLaunchPlan;保存后的内存/本地配置与 Core 下一次解析看到的文档不一致。普通 Java main 还被默认绑定到 Maven,无法在没有 Maven 项目的工作区中直接用 JDK 启动。
方案:在共享契约中允许 host 提供 localDocument,Core 使用它替代磁盘 local layer;全局 JDK/Maven 路径只写 local layer,并在 resolve 时统一应用。纯 Java main 只绑定 project-jdk,带 Maven toolchain 的配置才走 Maven launch plan;schema、fixture、Windows adapter 和回归测试同步更新。
经验:分层配置的“读、改、合并、执行”必须接收同一个层模型;machine-local 数据不能被误写入项目共享层,也不能只修保存端而不修解析/执行端。
#219 — Unicode Java interface 被识别为 generic,重启后导航还可能缺文档
根因:macOS Java symbol regex 只接受 [A-Za-z_$],拒绝 用户服务 等 Unicode identifier;语言服务器重启后,编辑器里的文档仍存在,但 session 没有该 URI 的最新文本,导航请求早于重新同步。
方案:symbol regex 使用 \\p{L};导航前确认已有 ready session 时重新同步当前文档,让 server 拥有 URI 后再请求 definition/implementation/reference。这个案例同时提醒:UI 的符号分类和 LSP 的文档生命周期是两个独立边界,都要有回归测试。
- 任何
contains、startsWith、ASCII-only regex 是否真的表达了完整 token/identifier? - 路径是否包含 Windows drive root、UNC、verbatim、大小写和
//\\差异? - 同一 request 是否包含多个 owner/module/source set?不要把 request 级结论套到每个 entry。
- 生成配置是否有 revision/fingerprint,旧文档是否可明确诊断为 stale?
- LSP 的 initialized、ready、imported、document-synced 是否是不同状态?
- adapter 是否保留 exit code、错误码和 scope,而不是只返回成功/失败布尔值?
- Core 的结果是否能通过 shared schema/fixture 同时约束 macOS 与 Windows?