mirror of
https://github.com/tiennm99/serena.git
synced 2026-10-11 12:29:04 +00:00
fix: disable automatic type acquisition in TypeScript backends (#1990)
* fix: disable automatic type acquisition in TypeScript backends * fix: use shared initialization builder for VTS Follow review guidance to apply shallow per-key overrides without restoring the ATA default after a user replaces the typescript block. Preserve the legacy settings alias, document the behavior, and cover canonical-key precedence. --------- Co-authored-by: GuddXzy <267062192+GuddXzy@users.noreply.github.com>
This commit is contained in:
1 parent
9f9db76622
commit
2d2e87763a
4 files changed
+137
-39
No files matched your search
@@ -33,6 +33,11 @@ Status of the `main` branch. Changes prior to the next official version change w
|
||||
Serena's own tools to close the gap (#1852)
|
||||
|
||||
* Language Servers:
|
||||
- Fix: TypeScript and VTS now disable automatic type acquisition as intended, while VTS
|
||||
preserves explicit user settings across initialization and configuration requests (#1989)
|
||||
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.
|
||||
- Add FreeBSD mapping to platform detection
|
||||
- Remove unnecessary platform checks from the following language servers, expanding the set of
|
||||
supported platforms accordingly: Elixir Tools, Intelephense, Perl, TypeScript, VTS
|
||||
|
||||
@@ -339,9 +339,7 @@ class TypeScriptLanguageServer(SolidLanguageServer):
|
||||
# slow, network-dependent, and nondeterministic (and can hang on offline/locked-down
|
||||
# machines). Serena relies on the types already installed in the project instead.
|
||||
"initializationOptions": {
|
||||
"preferences": {
|
||||
"disableAutomaticTypingAcquisition": True,
|
||||
},
|
||||
"disableAutomaticTypingAcquisition": True,
|
||||
},
|
||||
"capabilities": {
|
||||
"textDocument": {
|
||||
|
||||
@@ -8,6 +8,7 @@ import logging
|
||||
import os
|
||||
import shutil
|
||||
import threading
|
||||
from typing import cast
|
||||
|
||||
from overrides import override
|
||||
|
||||
@@ -34,16 +35,18 @@ class VtsLanguageServer(SolidLanguageServer):
|
||||
Supported entries in ``ls_specific_settings["typescript_vts"]``:
|
||||
- ``vtsls_version``: version of ``@vtsls/language-server`` to install (default: ``"0.2.9"``).
|
||||
- ``npm_registry``: custom npm registry for the managed install.
|
||||
- ``initialization_options``: optional dict forwarded verbatim as LSP
|
||||
``initializationOptions``. Useful for Yarn PnP projects, e.g.::
|
||||
- ``initializationOptions``: optional LSP configuration, overriding defaults per top-level key.
|
||||
Automatic type acquisition is disabled by default, but a user-provided ``typescript`` block
|
||||
replaces that entire default block. Useful for Yarn PnP projects, e.g.::
|
||||
|
||||
initialization_options:
|
||||
initializationOptions:
|
||||
typescript:
|
||||
tsdk: "project/.yarn/sdks/typescript/lib"
|
||||
vtsls:
|
||||
autoUseWorkspaceTsdk: true
|
||||
|
||||
See https://github.com/yioneko/vtsls/issues/169 for the PnP recipe.
|
||||
- ``initialization_options``: legacy alias, used only when ``initializationOptions`` is absent.
|
||||
"""
|
||||
|
||||
def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings):
|
||||
@@ -109,35 +112,18 @@ class VtsLanguageServer(SolidLanguageServer):
|
||||
assert os.path.exists(vts_executable_path), "vtsls executable not found. Please install @vtsls/language-server and try again."
|
||||
return f"{vts_executable_path} --stdio"
|
||||
|
||||
@property
|
||||
def _initialization_options(self) -> dict:
|
||||
"""
|
||||
Validated user-provided ``initializationOptions``.
|
||||
|
||||
:raises ValueError: if ``ls_specific_settings.typescript_vts.initialization_options``
|
||||
is set to a value that is not a dict.
|
||||
"""
|
||||
opts = self._custom_settings.get("initialization_options")
|
||||
if opts is None:
|
||||
return {}
|
||||
if not isinstance(opts, dict):
|
||||
raise ValueError(f"ls_specific_settings.typescript_vts.initialization_options must be a dict, got {type(opts).__name__}")
|
||||
return opts
|
||||
|
||||
def _create_base_initialize_params(self) -> dict:
|
||||
"""
|
||||
Returns the initialize params for the VTS Language Server.
|
||||
|
||||
If ``initialization_options`` is set in ``ls_specific_settings["typescript_vts"]``,
|
||||
it is forwarded verbatim as LSP ``initializationOptions``.
|
||||
"""
|
||||
# normalize the legacy key for the shared builder without changing caller-owned settings
|
||||
settings = self._custom_settings.settings
|
||||
if "initialization_options" in settings and "initializationOptions" not in settings:
|
||||
self._custom_settings.settings = {**settings, "initializationOptions": settings["initialization_options"]}
|
||||
|
||||
initialize_params: dict = {
|
||||
"locale": "en",
|
||||
"initializationOptions": {
|
||||
"preferences": {
|
||||
"disableAutomaticTypingAcquisition": True,
|
||||
},
|
||||
},
|
||||
"initializationOptions": {"typescript": {"disableAutomaticTypeAcquisition": True}},
|
||||
"capabilities": {
|
||||
"textDocument": {
|
||||
"synchronization": {"didSave": True, "dynamicRegistration": True},
|
||||
@@ -161,10 +147,6 @@ class VtsLanguageServer(SolidLanguageServer):
|
||||
},
|
||||
}
|
||||
|
||||
if self._initialization_options:
|
||||
log.info("Forwarding user-provided initializationOptions to vtsls: %s", self._initialization_options)
|
||||
initialize_params["initializationOptions"] = self._initialization_options
|
||||
|
||||
return initialize_params
|
||||
|
||||
def _start_server(self) -> None:
|
||||
@@ -192,12 +174,13 @@ class VtsLanguageServer(SolidLanguageServer):
|
||||
def execute_client_command_handler(params: dict) -> list:
|
||||
return []
|
||||
|
||||
init_options = self._initialization_options
|
||||
# share the final configuration across initialization, requests and notifications
|
||||
initialize_params = self._create_initialize_params()
|
||||
init_options = cast(dict, initialize_params["initializationOptions"])
|
||||
|
||||
def workspace_configuration_handler(params: dict) -> list[object]:
|
||||
# vtsls pulls settings for sections like "typescript", "vtsls", "javascript".
|
||||
# Return the matching sub-dicts from the user-provided initialization_options.
|
||||
return [init_options.get(item.get("section", ""), {}) for item in params["items"]]
|
||||
# vtsls requests the whole configuration (empty section) during startup
|
||||
return [init_options.get(item["section"], {}) if item.get("section") else init_options for item in params["items"]]
|
||||
|
||||
def do_nothing(params: dict) -> None:
|
||||
return
|
||||
@@ -222,7 +205,6 @@ class VtsLanguageServer(SolidLanguageServer):
|
||||
|
||||
log.info("Starting VTS server process")
|
||||
self.server.start()
|
||||
initialize_params = self._create_initialize_params()
|
||||
|
||||
log.info("Sending initialize request from LSP client to LSP server and awaiting response")
|
||||
init_response = self.server.send.initialize(initialize_params)
|
||||
@@ -243,7 +225,7 @@ class VtsLanguageServer(SolidLanguageServer):
|
||||
|
||||
# vtsls also reads settings via workspace/didChangeConfiguration (in addition
|
||||
# to initializationOptions and workspace/configuration pulls). Push the same
|
||||
# user-provided settings on all three channels for maximum compatibility,
|
||||
# effective settings on all three channels for maximum compatibility,
|
||||
# e.g. so that `typescript.tsdk` is honoured for Yarn PnP projects.
|
||||
if init_options:
|
||||
self.server.notify.workspace_did_change_configuration({"settings": init_options})
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
"""Automatic type acquisition must be disabled on the semantic TypeScript worker."""
|
||||
|
||||
import copy
|
||||
import os
|
||||
import threading
|
||||
from pathlib import Path
|
||||
from unittest.mock import Mock
|
||||
|
||||
import psutil
|
||||
import pytest
|
||||
|
||||
from solidlsp.language_servers.vts_language_server import VtsLanguageServer
|
||||
from solidlsp.ls_config import LanguageServerConfig, LanguageServerId
|
||||
from solidlsp.settings import SolidLSPSettings
|
||||
from test.conftest import get_repo_path, start_ls_context
|
||||
|
||||
|
||||
@pytest.mark.typescript
|
||||
@pytest.mark.parametrize("backend", [LanguageServerId.TYPESCRIPT, LanguageServerId.TYPESCRIPT_VTS])
|
||||
def test_default_disables_type_acquisition_in_running_server(backend: LanguageServerId) -> None:
|
||||
parent = psutil.Process(os.getpid())
|
||||
existing_pids = {child.pid for child in parent.children(recursive=True)}
|
||||
|
||||
# VTS uses the same fixture project, but has no default repository alias.
|
||||
with start_ls_context(backend, repo_path=str(get_repo_path(LanguageServerId.TYPESCRIPT))) as ls:
|
||||
symbols = ls.request_document_symbols("index.ts").get_all_symbols_and_roots()[0]
|
||||
assert any(symbol["name"] == "DemoClass" for symbol in symbols)
|
||||
|
||||
semantic_workers = []
|
||||
typings_installers = []
|
||||
for child in parent.children(recursive=True):
|
||||
if child.pid in existing_pids:
|
||||
continue
|
||||
try:
|
||||
command = child.cmdline()
|
||||
except psutil.NoSuchProcess:
|
||||
continue
|
||||
filenames = {Path(arg).name for arg in command}
|
||||
if "typingsInstaller.js" in filenames:
|
||||
typings_installers.append(command)
|
||||
if "tsserver.js" in filenames and "partialSemantic" not in command and "--syntaxOnly" not in command:
|
||||
semantic_workers.append(command)
|
||||
|
||||
# The syntax worker disables ATA independently, even on the broken baseline.
|
||||
assert semantic_workers, "No semantic tsserver was observed after a successful symbol request"
|
||||
assert all("--disableAutomaticTypingAcquisition" in command for command in semantic_workers), semantic_workers
|
||||
assert not typings_installers, typings_installers
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("custom_settings", "expected"),
|
||||
[
|
||||
({}, {"typescript": {"disableAutomaticTypeAcquisition": True}}),
|
||||
(
|
||||
{"initialization_options": {"typescript": {"tsdk": "workspace/typescript/lib"}, "vtsls": {"autoUseWorkspaceTsdk": True}}},
|
||||
{
|
||||
"typescript": {"tsdk": "workspace/typescript/lib"},
|
||||
"vtsls": {"autoUseWorkspaceTsdk": True},
|
||||
},
|
||||
),
|
||||
(
|
||||
{"initialization_options": {"typescript": {"disableAutomaticTypeAcquisition": False}}},
|
||||
{"typescript": {"disableAutomaticTypeAcquisition": False}},
|
||||
),
|
||||
(
|
||||
{"initializationOptions": {"typescript": {"tsdk": "workspace/typescript/lib"}}},
|
||||
{"typescript": {"tsdk": "workspace/typescript/lib"}},
|
||||
),
|
||||
(
|
||||
{"initialization_options": {"vtsls": {"autoUseWorkspaceTsdk": True}}},
|
||||
{"typescript": {"disableAutomaticTypeAcquisition": True}, "vtsls": {"autoUseWorkspaceTsdk": True}},
|
||||
),
|
||||
(
|
||||
{
|
||||
"initialization_options": {
|
||||
"typescript": {"disableAutomaticTypeAcquisition": False},
|
||||
"vtsls": {"autoUseWorkspaceTsdk": True},
|
||||
},
|
||||
"initializationOptions": {"typescript": {"tsdk": "workspace/typescript/lib"}},
|
||||
},
|
||||
{"typescript": {"tsdk": "workspace/typescript/lib"}},
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_vts_sends_consistent_effective_configuration(tmp_path: Path, custom_settings: dict, expected: dict) -> None:
|
||||
original_settings = copy.deepcopy(custom_settings)
|
||||
|
||||
# Use the real startup/configuration path with only the remote LSP transport replaced.
|
||||
server = object.__new__(VtsLanguageServer)
|
||||
server._custom_settings = SolidLSPSettings.CustomLSSettings(custom_settings)
|
||||
server.repository_root_path = str(tmp_path)
|
||||
server.config = LanguageServerConfig(ls_id=LanguageServerId.TYPESCRIPT_VTS)
|
||||
server.server_ready = threading.Event()
|
||||
server.server_ready.set()
|
||||
server.initialize_searcher_command_available = threading.Event()
|
||||
transport = Mock()
|
||||
transport.send.initialize.return_value = {"capabilities": {"textDocumentSync": 1, "completionProvider": {}}}
|
||||
handlers = {}
|
||||
transport.on_request.side_effect = handlers.__setitem__
|
||||
server.server = transport
|
||||
|
||||
server._start_server()
|
||||
|
||||
assert transport.send.initialize.call_args.args[0]["initializationOptions"] == expected
|
||||
configuration = handlers["workspace/configuration"]
|
||||
assert configuration({"items": [{"section": ""}, {}, {"section": "typescript"}, {"section": "missing"}]}) == [
|
||||
expected,
|
||||
expected,
|
||||
expected["typescript"],
|
||||
{},
|
||||
]
|
||||
assert transport.notify.workspace_did_change_configuration.call_args.args[0] == {"settings": expected}
|
||||
assert custom_settings == original_settings
|
||||
Reference in new issue
Block a user