fix(dart): omit rootUri/rootPath (#2045) (#2051)

The Dart analysis server treats rootUri as an additional analysis root on top of
workspaceFolders, with no de-duplication, so on a monorepo root the whole tree is
analysed and the server burns CPU at idle. Always omit rootUri/rootPath and rely on
workspaceFolders alone.
This commit is contained in:
Max authored and GitHub committed 2026-09-21 21:57:33 +02:00
1 parent 24068f7b31
commit 9f19a79a04
5 files changed
+102 -3

No files matched your search

+1
View File
@@ -104,6 +104,7 @@ Status of the `main` branch. Changes prior to the next official version change w
thread (#2038)
* Language Servers:
- Fix: Dart analysis server no longer receives rootUri/rootPath, which added the monorepo root as an extra analysis root and could pin a CPU core at idle (#2045)
- Fix: The C# language server opened every `.csproj` found anywhere under the repository root,
without consulting the project's ignore settings. On repositories that vendor third-party or
sample C# projects, this loads projects the server cannot restore on every start, and their
+16 -3
View File
@@ -40,10 +40,11 @@ class InitializeParamsBuilder(ABC):
class DefaultInitializeParamsBuilder(InitializeParamsBuilder):
def __init__(self, ls: "SolidLanguageServer", set_workspace_folders: bool = True):
def __init__(self, ls: "SolidLanguageServer", set_workspace_folders: bool = True, set_root_uri: bool = True):
super().__init__()
self._ls = ls
self._set_workspace_folders = set_workspace_folders
self._set_root_uri = set_root_uri
@staticmethod
def _create_workspace_folder_entry(path: str) -> WorkspaceFolder:
@@ -54,10 +55,22 @@ class DefaultInitializeParamsBuilder(InitializeParamsBuilder):
root_abs_path = self._ls.repository_root_path
self._set("processId", os.getpid())
self._set("rootPath", root_abs_path)
self._set("rootUri", pathlib.Path(root_abs_path).as_uri())
self._set("clientInfo", {"name": "Serena"})
# Some language servers treat rootUri as an additional analysis root on top of
# workspaceFolders, with no de-duplication, which can cause unbounded indexing.
# When set_root_uri is False, rootUri/rootPath are omitted so that workspaceFolders
# alone determine the analysis roots.
if self._set_root_uri:
self._set("rootPath", root_abs_path)
self._set("rootUri", pathlib.Path(root_abs_path).as_uri())
else:
# Some servers reject initialize when the key is absent
# ("params.rootUri must not be undefined"). Send explicit null so the
# field is present but not used as an analysis root (#2045).
self._set("rootPath", None)
self._set("rootUri", None)
if self._set_workspace_folders:
abs_workspace_paths = self._ls.config.get_absolute_workspace_folders(root_abs_path)
log.info("Workspace folders: %s", abs_workspace_paths)
@@ -7,6 +7,7 @@ from collections.abc import Hashable
from overrides import override
from solidlsp.initialize_params import DefaultInitializeParamsBuilder, InitializeParamsBuilder
from solidlsp.ls import RawDocumentSymbol, SolidLanguageServer
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
from solidlsp.settings import SolidLSPSettings
@@ -73,6 +74,13 @@ class DartLanguageServer(SolidLanguageServer):
# via either notification it sends for this (see _start_server).
self.analysis_complete = threading.Event()
def _create_initialize_params_builder(self) -> InitializeParamsBuilder:
# The Dart analysis server treats rootUri as an additional analysis root on top of
# workspaceFolders, with no de-duplication, so on a monorepo root that is not a Dart
# package the whole tree is analysed and the server burns CPU at idle (oraios/serena#2045).
# Omit rootUri/rootPath and rely on workspaceFolders alone.
return DefaultInitializeParamsBuilder(self, set_root_uri=False)
@override
def _document_symbols_cache_fingerprint(self) -> Hashable:
normalize_symbol_name_version = 1
+38
View File
@@ -0,0 +1,38 @@
# SPDX-License-Identifier: MIT
import tempfile
from pathlib import Path
from solidlsp.language_servers.dart_language_server import DartLanguageServer
def _make_dart_ls() -> DartLanguageServer:
ls = object.__new__(DartLanguageServer)
ls._custom_settings = {}
# Windows: as_uri() rejects drive-less paths like "/tmp/..."
project_dir = Path(tempfile.mkdtemp(prefix="fake-dart-project-")) / "project"
project_dir.mkdir(parents=True, exist_ok=True)
ls.repository_root_path = str(project_dir)
class _Cfg:
@staticmethod
def get_absolute_workspace_folders(root):
return [root]
@staticmethod
def get_absolute_additional_workspace_folders(root):
return []
ls.config = _Cfg()
# custom_settings property reads from _custom_settings on SolidLanguageServer
return ls
def test_dart_omits_root_uri():
builder = _make_dart_ls()._create_initialize_params_builder()
params = builder.build()
# rootUri must be present (some servers reject an undefined key) but null, so that only
# workspaceFolders determine the analysis roots (oraios/serena#2045).
assert params["rootUri"] is None
assert params["rootPath"] is None
assert params["workspaceFolders"]
@@ -0,0 +1,39 @@
# SPDX-License-Identifier: MIT
import tempfile
from pathlib import Path
from solidlsp.initialize_params import DefaultInitializeParamsBuilder
class _FakeLS:
# Windows: as_uri() rejects drive-less paths like "/tmp/..."
repository_root_path = str(Path(tempfile.mkdtemp(prefix="fake-project-")) / "root")
class config:
@staticmethod
def get_absolute_workspace_folders(root):
return [root]
@staticmethod
def get_absolute_additional_workspace_folders(root):
return []
custom_settings: dict = {}
def test_default_builder_sets_root_uri():
builder = DefaultInitializeParamsBuilder(_FakeLS())
params = builder.build()
assert "rootUri" in params
assert "rootPath" in params
def test_builder_sends_null_root_uri_when_disabled():
builder = DefaultInitializeParamsBuilder(_FakeLS(), set_root_uri=False)
params = builder.build()
# keys must be present (some servers reject an undefined rootUri); values are null
assert params["rootUri"] is None
assert params["rootPath"] is None
assert params["processId"] is not None
assert params["clientInfo"] == {"name": "Serena"}