Skip to content

Commit e52650b

Browse files
dvirdukhanCopilot
andcommitted
fix(analyzer): resolve LSP CALLS edges on repos without a venv
The Python analyzer hardcoded `environment_path={path}/venv` when starting jedi-language-server via multilspy. When the repo had no venv (the common case for cloned codebases like sphinx, sympy, anything from SWE-bench), jedi raised `InvalidPythonEnvironment` on every `request_definition()` call. analyzer.resolve() then swallowed the exception silently and the indexer produced a graph with DEFINES edges only — zero CALLS, zero EXTENDS. Benchmark validation showed sphinx (5K functions) and sympy (41K functions) had no resolved cross-references at all. Fix: - source_analyzer.py: prefer {repo}/venv, then {repo}/.venv, then fall back to the host interpreter's environment (sys.executable's prefix) so jedi always has a valid Python to introspect. - analyzer.py: log resolve() failures at WARN with file/line context instead of swallowing them silently, so the next regression is loud. Verified: re-indexed sphinx-doc/sphinx-9230 with the fix: DEFINES: 5640, CALLS: 4931, EXTENDS: 484 (was DEFINES-only). Fixes #685. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent b066064 commit e52650b

2 files changed

Lines changed: 27 additions & 2 deletions

File tree

api/analyzers/analyzer.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,12 @@ def resolve(self, files: dict[Path, File], lsp: SyncLanguageServer, file_path: P
5656
try:
5757
locations = lsp.request_definition(str(file_path), node.start_point.row, node.start_point.column)
5858
return [(files[Path(self.resolve_path(location['absolutePath'], path))], files[Path(self.resolve_path(location['absolutePath'], path))].tree.root_node.descendant_for_point_range(Point(location['range']['start']['line'], location['range']['start']['character']), Point(location['range']['end']['line'], location['range']['end']['character']))) for location in locations if location and Path(self.resolve_path(location['absolutePath'], path)) in files]
59-
except Exception:
59+
except Exception as e:
60+
import logging
61+
logging.getLogger(__name__).warning(
62+
"resolve() failed for %s @%d:%d: %s",
63+
file_path, node.start_point.row, node.start_point.column, e,
64+
)
6065
return []
6166

6267
@abstractmethod

api/analyzers/source_analyzer.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,27 @@ def second_pass(self, graph: Graph, files: list[Path], path: Path) -> None:
139139
else:
140140
lsps[".java"] = NullLanguageServer()
141141
if any(path.rglob('*.py')):
142-
config = MultilspyConfig.from_dict({"code_language": "python", "environment_path": f"{path}/venv"})
142+
import sys
143+
py_venv = path / "venv"
144+
py_dotvenv = path / ".venv"
145+
if py_venv.is_dir() and (py_venv / "bin" / "python").exists():
146+
env_path = str(py_venv)
147+
elif py_dotvenv.is_dir() and (py_dotvenv / "bin" / "python").exists():
148+
env_path = str(py_dotvenv)
149+
else:
150+
# Fall back to the host's Python environment so jedi has a
151+
# valid interpreter to introspect; otherwise every
152+
# request_definition() raises InvalidPythonEnvironment and
153+
# we'd silently produce a graph with zero CALLS edges.
154+
env_path = str(Path(sys.executable).resolve().parent.parent)
155+
logging.info(
156+
"No venv at %s; falling back to host env %s for jedi LSP",
157+
path, env_path,
158+
)
159+
config = MultilspyConfig.from_dict({
160+
"code_language": "python",
161+
"environment_path": env_path,
162+
})
143163
lsps[".py"] = SyncLanguageServer.create(config, logger, str(path))
144164
else:
145165
lsps[".py"] = NullLanguageServer()

0 commit comments

Comments
 (0)