mirror of
https://github.com/tiennm99/serena.git
synced 2026-10-11 03:13:51 +00:00
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:
1 parent
714c260e1f
commit
a4dff9e0fc
3 files changed
+84
-7
No files matched your search
@@ -130,6 +130,10 @@ Status of the `main` branch. Changes prior to the next official version change w
|
||||
VTS initialization options now override defaults per top-level key rather than replacing the
|
||||
entire configuration; a user-provided `typescript` block replaces the ATA default too.
|
||||
`initializationOptions` takes precedence over the legacy `initialization_options` alias.
|
||||
- Fix: activating an additional TypeScript workspace folder could open a root-level tool
|
||||
config (`vitest.config.ts`, `jest.config.ts`, etc.) adjacent to `tsconfig.json` instead of
|
||||
a real source file, starting the wrong inferred project and silently losing cross-package
|
||||
references (#2090)
|
||||
- Fix: a C# file created after the project was already indexed was analyzed by Roslyn as a
|
||||
standalone Miscellaneous Files document instead of being folded into the loaded project,
|
||||
causing phantom diagnostics on the new file and on files referencing its symbols (#1961)
|
||||
|
||||
@@ -516,20 +516,25 @@ class TypeScriptLanguageServer(SolidLanguageServer):
|
||||
def _find_representative_source_file(self, directory: str) -> str | None:
|
||||
"""Find a TypeScript file suitable for triggering project loading.
|
||||
|
||||
Prefers a file adjacent to tsconfig.json (indicating the project root),
|
||||
then falls back to the first .ts/.tsx file found.
|
||||
Prefers a file under a `src` subdirectory adjacent to tsconfig.json (the
|
||||
conventional source root), so a root-level tool config that tsconfig excludes
|
||||
(vitest.config.ts, jest.config.ts, etc.) is not picked over the project's real
|
||||
source tree. Falls back to a file directly adjacent to tsconfig.json, then to
|
||||
the first .ts/.tsx file found anywhere in the directory.
|
||||
"""
|
||||
for root, dirs, files in os.walk(directory):
|
||||
dirs[:] = [d for d in dirs if not self.is_ignored_dirname(d)]
|
||||
if "tsconfig.json" in files:
|
||||
src_dir = os.path.join(root, "src")
|
||||
if os.path.isdir(src_dir):
|
||||
for src_root, src_dirs, src_files in os.walk(src_dir):
|
||||
src_dirs[:] = [d for d in src_dirs if not self.is_ignored_dirname(d)]
|
||||
for f in src_files:
|
||||
if f.endswith((".ts", ".tsx")) and not f.endswith(".d.ts"):
|
||||
return os.path.join(src_root, f)
|
||||
for f in files:
|
||||
if f.endswith((".ts", ".tsx")) and not f.endswith(".d.ts"):
|
||||
return os.path.join(root, f)
|
||||
src_dir = os.path.join(root, "src")
|
||||
if os.path.isdir(src_dir):
|
||||
for f in os.listdir(src_dir):
|
||||
if f.endswith((".ts", ".tsx")) and not f.endswith(".d.ts"):
|
||||
return os.path.join(src_dir, f)
|
||||
|
||||
for root, dirs, files in os.walk(directory):
|
||||
dirs[:] = [d for d in dirs if not self.is_ignored_dirname(d)]
|
||||
|
||||
@@ -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
|
||||
Reference in new issue
Block a user