fix(typescript): prefer the src subtree over an adjacent tool config when warming up an additional workspace (#2097)

TypeScriptLanguageServer._find_representative_source_file scanned files directly
adjacent to tsconfig.json before checking a src/ subdirectory, so a same-level tool
config that the tsconfig excludes (vitest.config.ts, jest.config.ts, etc.) could be
picked as the file used to trigger project activation for an additional workspace
folder. The wrong inferred TypeScript project then loads, and cross-package
find_referencing_symbols queries silently return {} instead of raising or warning.

Reorder the scan to walk the src/ subtree first (recursively, respecting the usual
ignored-directory rules) and only fall back to a same-level file when no src/
directory exists, matching the fallback the reporter's issue proposed.

Fixes #2090

Signed-off-by: Amir Fathi <amirfathi.me@gmail.com>
This commit is contained in:
Amir Fathi authored and GitHub committed 2026-09-23 15:18:05 +02:00
1 parent 714c260e1f
commit a4dff9e0fc
3 files changed
+84 -7

No files matched your search

@@ -0,0 +1,68 @@
"""Regression test for oraios/serena#2090: additional-workspace activation must not pick a
root-level tool config (vitest.config.ts, jest.config.ts, ...) adjacent to tsconfig.json over
the project's actual source tree.
Exercises TypeScriptLanguageServer._find_representative_source_file directly against a real
filesystem layout, without spawning a language server process; same technique as
test_typescript_timeout_policy.py's _bare_ts_server.
"""
from solidlsp.language_servers.typescript_language_server import TypeScriptLanguageServer
def _bare_ts_server() -> TypeScriptLanguageServer:
return object.__new__(TypeScriptLanguageServer)
class TestFindRepresentativeSourceFile:
def test_prefers_nested_src_file_over_adjacent_tool_config(self, tmp_path) -> None:
"""The issue's own minimal layout: a tsconfig-adjacent vitest.config.ts must lose to a
real source file nested under src/, even when that source file is not directly inside
src/ itself (it is one level further down, under src/routes/).
"""
pkg = tmp_path / "apps" / "api"
routes = pkg / "src" / "routes"
routes.mkdir(parents=True)
(pkg / "tsconfig.json").write_text("{}")
(pkg / "vitest.config.ts").write_text("export default {};")
(routes / "money.ts").write_text("export const x = 1;")
result = _bare_ts_server()._find_representative_source_file(str(tmp_path))
assert result == str(routes / "money.ts")
def test_falls_back_to_adjacent_file_when_no_src_dir_exists(self, tmp_path) -> None:
"""Projects with source files directly next to tsconfig.json (no src/ subdirectory)
must keep working exactly as before.
"""
pkg = tmp_path / "pkg"
pkg.mkdir()
(pkg / "tsconfig.json").write_text("{}")
(pkg / "index.ts").write_text("export const x = 1;")
result = _bare_ts_server()._find_representative_source_file(str(tmp_path))
assert result == str(pkg / "index.ts")
def test_ignores_node_modules_under_src(self, tmp_path) -> None:
"""The src/ subtree walk must still respect is_ignored_dirname, or a vendored .ts file
under src/node_modules could be selected instead of real project source.
"""
pkg = tmp_path / "pkg"
vendored = pkg / "src" / "node_modules" / "dep"
real_src = pkg / "src" / "lib"
vendored.mkdir(parents=True)
real_src.mkdir(parents=True)
(pkg / "tsconfig.json").write_text("{}")
(pkg / "vitest.config.ts").write_text("export default {};")
(vendored / "vendored.ts").write_text("export const y = 1;")
(real_src / "app.ts").write_text("export const x = 1;")
result = _bare_ts_server()._find_representative_source_file(str(tmp_path))
assert result == str(real_src / "app.ts")
def test_returns_none_when_nothing_matches(self, tmp_path) -> None:
(tmp_path / "README.md").write_text("no typescript here")
assert _bare_ts_server()._find_representative_source_file(str(tmp_path)) is None