feat: add BSL (1C:Enterprise) language server support (#1430)

Required bumping of java to 21 in CI
This commit is contained in:
Данилов Роман authored and GitHub committed 2026-05-11 10:09:51 +02:00
1 parent 737ef76723
commit fb9c822178
24 files changed
+930 -4

No files matched your search

+1 -1
View File
@@ -138,7 +138,7 @@ jobs:
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '17'
java-version: '21'
- name: Setup .NET SDK (for F# and C# languages)
uses: actions/setup-dotnet@v4
with:
+1
View File
@@ -29,6 +29,7 @@ Status of the `main` branch. Changes prior to the next official version change w
- Add **Angular** (experimental) via a dual-server architecture: `@angular/language-server` (ngserver) handles standalone `.html` template files, while a companion `typescript-language-server` with `@angular/language-service` loaded as a tsserver plugin handles all `.ts` operations including inline templates. Provides type-aware navigation between templates and component classes. Requires Node.js, npm, and `@angular/core` installed in the project (`npm install` in the project root). Subsumes `typescript`+`html` for `.ts`/`.html` files when active; SCSS is not subsumed.
- Add **HTML** (experimental) using `vscode-html-language-server` from the `vscode-langservers-extracted` npm package. Provides in-file element/id symbols via documentSymbol; cross-file references are not meaningful for HTML. Also used as a companion server by the Angular LS for plain HTML documentSymbol support.
- Add **SCSS / Sass / CSS** (experimental) using [some-sass-language-server](https://github.com/wkillerud/some-sass). Handles `.scss`, `.sass`, and `.css` through one server, with full `@use`/`@forward` workspace-wide go-to-definition and find-references for variables, mixins, and functions across Sass files. The `.css` path uses the same `vscode-css-languageservice` engine that powers the standalone CSS LS; CSS feature toggles default off upstream and are flipped on at startup so symbols, hover, completion, and syntax-level diagnostics work for plain CSS as well.
- Add **1C / OneScript** support using [BSL Language Server](https://github.com/1c-syntax/bsl-language-server/).
- Add support for more filenames to be considered by ccls and clangd.
- Clojure (`clojure-lsp`): Fix incomplete `find_referencing_symbols` results in multi-module monorepos. clojure-lsp only discovers source paths from the descriptor at the workspace root and does not recurse for sub-module `deps.edn` / `project.clj` / `shadow-cljs.edn` / `bb.edn` files, so references in sibling modules were silently missed until those files happened to be opened by `find_symbol` / `get_symbols_overview`. Serena now scans the repo for project descriptors at startup and passes the union of their declared source paths to clojure-lsp via `initializationOptions`. Project-local `.lsp/config.edn` files are honoured as-is (no override). New `ls_specific_settings.clojure` keys: `source_paths` (explicit override) and `config_edn_path` (parse `:source-paths` from a user-supplied config file).
+1 -1
View File
@@ -110,7 +110,7 @@ Serena incorporates a powerful abstraction layer for the integration of language
The underlying language servers are typically open-source projects or at least freely available for use.
When using Serena's language server backend, we provide **support for over 40 programming languages**, including
Ada / SPARK, AL, Angular, Ansible, Bash, C#, C/C++, Clojure, Crystal, Dart, Elixir, Elm, Erlang, Fortran, F#, GLSL, Go, Groovy, Haskell, Haxe, HLSL, HTML, Java, JavaScript, JSON, Julia, Kotlin, Lean 4, Lua, Luau, Markdown, MATLAB, mSL, Nix, OCaml, Perl, PHP, PowerShell, Python, R, Ruby, Rust, Scala, SCSS / Sass / CSS, Solidity, Swift, TOML, TypeScript, WGSL, YAML, and Zig.
Ada / SPARK, AL, Angular, Ansible, Bash, BSL, C#, C/C++, Clojure, Crystal, Dart, Elixir, Elm, Erlang, Fortran, F#, GLSL, Go, Groovy, Haskell, Haxe, HLSL, HTML, Java, JavaScript, JSON, Julia, Kotlin, Lean 4, Lua, Luau, Markdown, MATLAB, mSL, Nix, OCaml, Perl, PHP, PowerShell, Python, R, Ruby, Rust, Scala, SCSS / Sass / CSS, Solidity, Swift, TOML, TypeScript, WGSL, YAML, and Zig.
### The Serena JetBrains Plugin
@@ -47,6 +47,8 @@ Some languages require additional installations or setup steps, as noted.
semantic tokens, and validation; document symbols, workspace symbols, references, and rename
are not supported by this version)
* **Bash**
* **BSL** (1C:Enterprise / OneScript)
(requires Java 21+ on PATH; uses [bsl-language-server](https://github.com/1c-syntax/bsl-language-server) by 1c-syntax; the JAR is auto-downloaded and SHA-256-verified for the bundled default version; supports `.bsl` and `.os` files; configure optional `ls_path` or `bsl_ls_version` under `ls_specific_settings.bsl`)
* **C#**
(by default, uses the Roslyn language server (language `csharp`), requiring [.NET v10+](https://dotnet.microsoft.com/en-us/download/dotnet) and, on Windows, `pwsh` ([PowerShell 7+](https://learn.microsoft.com/en-us/powershell/scripting/install/install-powershell-on-windows?view=powershell-7.5));
set language to `csharp_omnisharp` to use OmiSharp instead)
+24 -1
View File
@@ -227,7 +227,7 @@ ls_specific_settings:
This is supported by all language servers deriving their dependency provider from `LanguageServerDependencyProviderSinglePath`,
and by some additional wrappers that explicitly expose `ls_path`.
Common examples include: `ansible`, `bash`, `clojure`, `cpp`, `cpp_ccls`, `hlsl`, `html`, `kotlin`, `lean4`, `luau`, `markdown`, `php`,
Common examples include: `ansible`, `bash`, `bsl`, `clojure`, `cpp`, `cpp_ccls`, `hlsl`, `html`, `kotlin`, `lean4`, `luau`, `markdown`, `php`,
`php_phpactor`, `python`, `rust`, `scss`, `solidity`, `systemverilog`, `toml`, `typescript`, and `yaml`.
Note: `angular` does **not** support `ls_path` — the Angular language server is part of a multi-process orchestration
@@ -308,6 +308,29 @@ Supported settings:
| `bash_language_server_version` | `5.6.0` | Override the npm package version Serena installs when `ls_path` is not set. |
| `npm_registry` | `null` | Override the npm registry Serena uses for the managed install. |
#### BSL (1C:Enterprise / OneScript)
Serena uses [bsl-language-server](https://github.com/1c-syntax/bsl-language-server) by 1c-syntax
for BSL support. The JAR is downloaded automatically on first use and SHA-256-verified for the
bundled default version. **Requires Java 21+ on `PATH`** — bsl-language-server v0.29.0 is built
with `targetCompatibility = JavaVersion.VERSION_21` and fails to launch under older JDKs.
Supported settings:
| Setting | Default | Description |
|---|---|---|
| `ls_path` | managed download | Override the path to an existing `bsl-language-server-*-exec.jar`. When set, Serena does not download anything; the JAR is launched directly via `java -jar`. |
| `bsl_ls_version` | `0.29.0` | Override the bsl-language-server release version Serena downloads when `ls_path` is not set. SHA-256 verification is performed only for the default version; user-overridden versions install without SHA verification. |
Example:
```yaml
ls_specific_settings:
bsl:
bsl_ls_version: "0.29.0"
# ls_path: "/opt/bsl/bsl-language-server-0.29.0-exec.jar" # optional
```
#### Clojure
Serena uses `clojure-lsp` for Clojure support.
+4
View File
@@ -294,6 +294,9 @@ max-complexity = 20
[tool.ruff.lint.per-file-ignores]
"tests/**" = ["D103"]
"scripts/**" = ["D103"]
# BSL integration tests deliberately contain Cyrillic identifiers (1C:Enterprise metadata
# object names and procedure names). ruff's ambiguous-unicode rules flag every such char.
"test/solidlsp/bsl/**" = ["RUF001", "RUF002", "RUF003"]
[tool.pytest.ini_options]
addopts = "--snapshot-patch-pycharm-diff"
@@ -349,6 +352,7 @@ markers = [
"solidity: language server running for Solidity (uses @nomicfoundation/solidity-language-server)",
"ansible: language server running for Ansible (uses @ansible/ansible-language-server)",
"msl: language server running for mSL (mIRC Scripting Language)",
"bsl: language server running for BSL (1C:Enterprise / OneScript)",
"html: language server running for HTML (vscode-html-language-server, used as companion by Angular)",
"scss: language server running for SCSS / Sass / CSS (some-sass-language-server)",
"angular: language server running for Angular (uses @angular/language-server + @angular/language-service tsserver plugin)",
@@ -0,0 +1,226 @@
"""
Provides BSL (1C:Enterprise) specific instantiation of the LanguageServer class
using bsl-language-server by 1c-syntax. Supports .bsl and .os files.
Requires Java 21+ on PATH (bsl-language-server v0.29.0 is built with
``targetCompatibility = JavaVersion.VERSION_21``).
You can configure the following options in ls_specific_settings (in serena_config.yml):
ls_specific_settings:
bsl:
ls_path: '/path/to/bsl-language-server.jar' # Custom path to BSL Language Server JAR
bsl_ls_version: '0.29.0' # BSL Language Server version (default: current bundled version)
"""
import logging
import os
import pathlib
import re
import shutil
import subprocess
import threading
from solidlsp.language_servers.common import RuntimeDependency, RuntimeDependencyCollection
from solidlsp.ls import (
LanguageServerDependencyProvider,
LanguageServerDependencyProviderSinglePath,
SolidLanguageServer,
)
from solidlsp.ls_config import LanguageServerConfig
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
from solidlsp.settings import SolidLSPSettings
log = logging.getLogger(__name__)
DEFAULT_BSL_LS_VERSION = "0.29.0"
BSL_LS_JAR_SHA256_BY_VERSION = {
"0.29.0": "d6fa9ad638ba51855e260b88ad1f8ce4e602385845a4ee43600d148f779bcf0b",
}
BSL_LS_MIN_JAVA_VERSION = 21
"""Minimum Java major version required to run the bundled bsl-language-server JAR.
bsl-language-server v0.29.0 is compiled with ``targetCompatibility = VERSION_21`` and
therefore fails at class-load time on older JDKs (``UnsupportedClassVersionError``),
which manifests as ``LanguageServerTerminatedException`` in Serena. We enforce the
check up front so the user gets an actionable error instead of a dead LSP process.
"""
def _get_java_major_version(java_exe: str) -> int | None:
"""Returns the detected Java major version (e.g. 21) or ``None`` if detection fails.
:param java_exe: path to the ``java`` executable.
"""
try:
result = subprocess.run(
[java_exe, "-version"],
capture_output=True,
text=True,
timeout=15,
check=False,
)
except (OSError, subprocess.TimeoutExpired) as exc:
log.warning("Failed to invoke '%s -version': %s", java_exe, exc)
return None
output = (result.stderr or "") + "\n" + (result.stdout or "")
# modern JDKs print: openjdk version "21.0.2" 2024-01-16
# legacy JDKs print: java version "1.8.0_402" (major = 8)
m = re.search(r'version "(\d+)(?:\.(\d+))?', output)
if not m:
log.warning("Could not parse Java version from output of '%s -version': %s", java_exe, output.strip()[:200])
return None
first = int(m.group(1))
if first == 1 and m.group(2):
return int(m.group(2)) # 1.8 -> 8
return first
def _verify_java_available() -> None:
"""Ensures a suitable ``java`` (>= :data:`BSL_LS_MIN_JAVA_VERSION`) is on ``PATH``.
:raises RuntimeError: if ``java`` is missing or its major version is too low.
"""
java_exe = shutil.which("java")
if java_exe is None:
raise RuntimeError(f"Java {BSL_LS_MIN_JAVA_VERSION}+ is required for BSL Language Server but 'java' was not found on PATH.")
major = _get_java_major_version(java_exe)
if major is None:
# detection failed; log a warning but let the LSP attempt to start
log.warning("Could not determine Java version for '%s'; BSL LSP requires Java %d+.", java_exe, BSL_LS_MIN_JAVA_VERSION)
return
if major < BSL_LS_MIN_JAVA_VERSION:
raise RuntimeError(
f"Java {BSL_LS_MIN_JAVA_VERSION}+ is required for BSL Language Server, but '{java_exe}' is Java {major}. "
f"Install a newer JDK (e.g. Temurin {BSL_LS_MIN_JAVA_VERSION}) and ensure it appears first on PATH "
f"or point JAVA_HOME to it."
)
class BSLLanguageServer(SolidLanguageServer):
"""
BSL (1C:Enterprise / OneScript) language server integration for Serena.
"""
def __init__(
self,
config: LanguageServerConfig,
repository_root_path: str,
solidlsp_settings: SolidLSPSettings,
):
super().__init__(
config,
repository_root_path,
None,
"bsl",
solidlsp_settings,
)
self.server_ready = threading.Event()
def _create_dependency_provider(self) -> LanguageServerDependencyProvider:
return self.DependencyProvider(self._custom_settings, self._ls_resources_dir)
class DependencyProvider(LanguageServerDependencyProviderSinglePath):
def _get_or_install_core_dependency(self) -> str:
# resolve target version; custom versions intentionally skip SHA verification below
bsl_version = self._custom_settings.get("bsl_ls_version", DEFAULT_BSL_LS_VERSION)
user_overrode_version = bsl_version != DEFAULT_BSL_LS_VERSION
jar_dir = os.path.join(self._ls_resources_dir, f"bsl-ls-{bsl_version}")
jar_path = os.path.join(jar_dir, f"bsl-language-server-{bsl_version}-exec.jar")
# download only when the versioned JAR is missing
if not os.path.exists(jar_path):
jar_url = (
f"https://github.com/1c-syntax/bsl-language-server/releases/download/"
f"v{bsl_version}/bsl-language-server-{bsl_version}-exec.jar"
)
expected_sha256 = None if user_overrode_version else BSL_LS_JAR_SHA256_BY_VERSION.get(bsl_version)
deps = RuntimeDependencyCollection(
[
RuntimeDependency(
id="bsl-language-server",
description="BSL Language Server JAR by 1c-syntax",
url=jar_url,
sha256=expected_sha256,
archive_type="binary",
binary_name=f"bsl-language-server-{bsl_version}-exec.jar",
platform_id="any",
),
]
)
deps.install(jar_dir)
if not os.path.exists(jar_path):
raise FileNotFoundError(f"BSL Language Server JAR not found at {jar_path} after installation.")
log.info(f"Using BSL Language Server v{bsl_version} at {jar_path}")
return jar_path
def _create_launch_command(self, core_path: str) -> list[str]:
# Java availability+version is required for both managed installs and user-provided jars (ls_path)
_verify_java_available()
return ["java", "-jar", core_path]
@staticmethod
def _get_initialize_params(repository_absolute_path: str) -> InitializeParams:
root_uri = pathlib.Path(repository_absolute_path).as_uri()
return { # type: ignore[return-value]
"locale": "en",
"capabilities": {
"textDocument": {
"synchronization": {"didSave": True, "dynamicRegistration": True},
"definition": {"dynamicRegistration": True},
"references": {"dynamicRegistration": True},
"documentSymbol": {
"dynamicRegistration": True,
"hierarchicalDocumentSymbolSupport": True,
"symbolKind": {"valueSet": list(range(1, 27))}, # type: ignore
},
"hover": {"dynamicRegistration": True, "contentFormat": ["markdown", "plaintext"]}, # type: ignore
"rename": {"dynamicRegistration": True, "prepareSupport": True},
},
"workspace": {
"workspaceFolders": True,
"didChangeConfiguration": {"dynamicRegistration": True},
"symbol": {"dynamicRegistration": True},
},
},
"processId": os.getpid(),
"rootPath": repository_absolute_path,
"rootUri": root_uri,
"workspaceFolders": [
{"uri": root_uri, "name": os.path.basename(repository_absolute_path)},
],
}
def _start_server(self) -> None:
def window_log_message(msg: dict) -> None:
log.info("BSL LSP: %s", msg.get("message", ""))
def do_nothing(_: dict) -> None:
return
self.server.on_notification("window/logMessage", window_log_message)
self.server.on_notification("$/progress", do_nothing)
self.server.on_notification("textDocument/publishDiagnostics", do_nothing)
self.server.on_request(
"client/registerCapability",
lambda params: None,
)
log.info("Starting BSL language server process")
self.server.start()
init_params = self._get_initialize_params(self.repository_root_path)
init_response = self.server.send.initialize(init_params)
log.debug("BSL LSP initialize response: %s", init_response)
assert "capabilities" in init_response, "BSL LSP did not return capabilities"
self.server.notify.initialized({})
self.server_ready.set()
+12 -1
View File
@@ -110,6 +110,11 @@ class Language(str, Enum):
Uses a custom LSP server based on pygls. Automatically sets up
a virtual environment with pygls dependencies on first use.
"""
BSL = "bsl"
"""BSL Language Server for 1C:Enterprise and OneScript languages.
Uses bsl-language-server by 1c-syntax. Automatically downloads the JAR.
Supports .bsl and .os files. Requires Java 21+ on PATH.
"""
ADA = "ada"
"""Ada / SPARK language server using AdaCore's Ada Language Server (ALS).
Supports .ads (specs), .adb (bodies), and .ada files. Auto-downloads the
@@ -463,6 +468,8 @@ class Language(str, Enum):
return FilenameMatcher(".yaml", ".yml")
case self.MSL:
return FilenameMatcher(".mrc")
case self.BSL:
return FilenameMatcher(".bsl", ".os")
case self.ADA:
return FilenameMatcher(".ads", ".adb", ".ada", case_sensitive=False)
case self.HTML:
@@ -515,7 +522,7 @@ class Language(str, Enum):
return CSharpLanguageServer
case self.CSHARP_OMNISHARP:
from solidlsp.language_servers.omnisharp import OmniSharp
from solidlsp.language_servers.omnisharp import OmniSharp # type: ignore[attr-defined]
return OmniSharp
case self.TYPESCRIPT:
@@ -712,6 +719,10 @@ class Language(str, Enum):
from solidlsp.language_servers.msl_language_server import MslLanguageServer
return MslLanguageServer
case self.BSL:
from solidlsp.language_servers.bsl_language_server import BSLLanguageServer
return BSLLanguageServer
case self.ADA:
from solidlsp.language_servers.ada_language_server import AdaLanguageServer
+8
View File
@@ -278,6 +278,10 @@ _LANGUAGE_PYTEST_MARKERS: dict[Language, list[MarkDecorator | Mark]] = {
Language.PYTHON_TY: [pytest.mark.python],
Language.RUST: [pytest.mark.rust],
Language.TYPESCRIPT: [pytest.mark.typescript],
Language.BSL: [
pytest.mark.bsl,
pytest.mark.skipif(_sh.which("java") is None, reason="Java is not installed"),
],
Language.ANGULAR: [pytest.mark.angular],
Language.HTML: [pytest.mark.html],
Language.SCSS: [pytest.mark.scss],
@@ -331,6 +335,10 @@ def _determine_disabled_languages() -> list[Language]:
if not al_tests_enabled:
result.append(Language.AL)
# Disable BSL tests only when Java is not available (Java IS present in CI via actions/setup-java)
if _sh.which("java") is None:
result.append(Language.BSL)
return result
@@ -0,0 +1,5 @@
{
"language": "ru",
"diagnosticLanguage": "ru",
"computeDiagnostics": "onSave"
}
Whitespace-only changes.
@@ -0,0 +1,14 @@
// CommonModule.bsl - common module with reusable procedures
Процедура ВывестиСообщение(Текст) Экспорт
Сообщить(Текст);
КонецПроцедуры
Функция ПолучитьПриветствие(Имя) Экспорт
Возврат "Привет, " + Имя + "!";
КонецФункции
Процедура ВызватьПриветствие() Экспорт
Сообщение = ПолучитьПриветствие("Мир");
ВывестиСообщение(Сообщение);
КонецПроцедуры
@@ -0,0 +1,12 @@
// ObjectModule.bsl - object module example
Перем ВнутреннееСостояние;
Процедура Инициализировать() Экспорт
ВнутреннееСостояние = "ready";
ВывестиСообщение("Объект инициализирован");
КонецПроцедуры
Функция ПолучитьСостояние() Экспорт
Возврат ВнутреннееСостояние;
КонецФункции
@@ -0,0 +1,6 @@
// diagnostics_sample.bsl — намеренно сломанный файл для проверки диагностик BSL LS.
// Содержит синтаксическую ошибку (незакрытая строка), которую bsl-language-server гарантированно флагнет.
Процедура СломаннаяПроцедура() Экспорт
Текст = "незакрытая строка;
КонецПроцедуры
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.20">
<CommonModule uuid="e63ad502-3c78-4345-97f7-b72ac549ddf2">
<Properties>
<Name>ОбщийМодуль1</Name>
<Synonym/>
<Comment/>
<Global>false</Global>
<ClientManagedApplication>false</ClientManagedApplication>
<Server>true</Server>
<ExternalConnection>true</ExternalConnection>
<ClientOrdinaryApplication>false</ClientOrdinaryApplication>
<ServerCall>true</ServerCall>
<Privileged>false</Privileged>
<ReturnValuesReuse>DontUse</ReturnValuesReuse>
</Properties>
</CommonModule>
</MetaDataObject>
@@ -0,0 +1,5 @@
// CommonModule1 — shared helpers called from other modules.
Процедура ВывестиСообщение(Текст) Экспорт
Сообщить(Текст);
КонецПроцедуры
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.20">
<CommonModule uuid="814448ec-aec2-49d6-8d8a-79f49628493c">
<Properties>
<Name>ОбщийМодуль2</Name>
<Synonym/>
<Comment/>
<Global>false</Global>
<ClientManagedApplication>false</ClientManagedApplication>
<Server>true</Server>
<ExternalConnection>true</ExternalConnection>
<ClientOrdinaryApplication>false</ClientOrdinaryApplication>
<ServerCall>true</ServerCall>
<Privileged>false</Privileged>
<ReturnValuesReuse>DontUse</ReturnValuesReuse>
</Properties>
</CommonModule>
</MetaDataObject>
@@ -0,0 +1,5 @@
// CommonModule2 — exercises a cross-module call into CommonModule1.
Процедура ПозватьМодуль1() Экспорт
ОбщийМодуль1.ВывестиСообщение("Hello from CommonModule2");
КонецПроцедуры
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<ConfigDumpInfo xmlns="http://v8.1c.ru/8.3/xcf/dumpinfo" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" format="Hierarchical" version="2.20">
<ConfigVersions>
<Metadata name="CommonModule.ОбщийМодуль1" id="e63ad502-3c78-4345-97f7-b72ac549ddf2" configVersion="c6c95de4be61d045b57c6f7286f1d04d00000000"/>
<Metadata name="CommonModule.ОбщийМодуль2" id="814448ec-aec2-49d6-8d8a-79f49628493c" configVersion="30bb706dbfabfa4b940bb6064997d47c00000000"/>
<Metadata name="CommonModule.ОбщийМодуль2.Module" id="814448ec-aec2-49d6-8d8a-79f49628493c.0" configVersion="3d251b408205a64daf243988c6afcd4c00000000"/>
<Metadata name="Configuration.Конфигурация" id="77dbe7df-5681-44a5-bb9e-bb8cb297021a" configVersion="8caf1d64045c77468d007c890e712a8100000000"/>
<Metadata name="Language.Русский" id="e707122a-91c0-40f4-ae5a-46cbcd3ff534" configVersion="358e84df1ac94b4ba768fc864e827eb400000000"/>
</ConfigVersions>
</ConfigDumpInfo>
@@ -0,0 +1,252 @@
<?xml version="1.0" encoding="UTF-8"?>
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.20">
<Configuration uuid="77dbe7df-5681-44a5-bb9e-bb8cb297021a">
<InternalInfo>
<xr:ContainedObject>
<xr:ClassId>9cd510cd-abfc-11d4-9434-004095e12fc7</xr:ClassId>
<xr:ObjectId>f2bbb9be-74c9-40ac-a945-4b03d2624a80</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>9fcd25a0-4822-11d4-9414-008048da11f9</xr:ClassId>
<xr:ObjectId>74c2c4b9-ca2e-4c29-9ac1-c9a686dffd48</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>e3687481-0a87-462c-a166-9f34594f9bba</xr:ClassId>
<xr:ObjectId>e6d84959-ef42-417d-b585-89b178e2ebe4</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>9de14907-ec23-4a07-96f0-85521cb6b53b</xr:ClassId>
<xr:ObjectId>bb344884-c3a2-4197-ad5d-7e6c391de24f</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>51f2d5d8-ea4d-4064-8892-82951750031e</xr:ClassId>
<xr:ObjectId>4c2a3610-e746-47e5-a127-f6ef59fba16d</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>e68182ea-4237-4383-967f-90c1e3370bc7</xr:ClassId>
<xr:ObjectId>f94ef4a2-9b6b-4ef7-8fc1-b69a351798cd</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>fb282519-d103-4dd3-bc12-cb271d631dfc</xr:ClassId>
<xr:ObjectId>d1f9e184-8cf0-47a9-a487-22516bb66c8c</xr:ObjectId>
</xr:ContainedObject>
</InternalInfo>
<Properties>
<Name>Конфигурация</Name>
<Synonym/>
<Comment/>
<NamePrefix/>
<ConfigurationExtensionCompatibilityMode>Version8_3_27</ConfigurationExtensionCompatibilityMode>
<DefaultRunMode>ManagedApplication</DefaultRunMode>
<UsePurposes>
<v8:Value xsi:type="app:ApplicationUsePurpose">PlatformApplication</v8:Value>
</UsePurposes>
<ScriptVariant>Russian</ScriptVariant>
<DefaultRoles/>
<Vendor/>
<Version/>
<UpdateCatalogAddress/>
<IncludeHelpInContents>false</IncludeHelpInContents>
<UseManagedFormInOrdinaryApplication>false</UseManagedFormInOrdinaryApplication>
<UseOrdinaryFormInManagedApplication>false</UseOrdinaryFormInManagedApplication>
<AdditionalFullTextSearchDictionaries/>
<CommonSettingsStorage/>
<ReportsUserSettingsStorage/>
<ReportsVariantsStorage/>
<FormDataSettingsStorage/>
<DynamicListsUserSettingsStorage/>
<URLExternalDataStorage/>
<Content/>
<DefaultReportForm/>
<DefaultReportVariantForm/>
<DefaultReportSettingsForm/>
<DefaultReportAppearanceTemplate/>
<DefaultDynamicListSettingsForm/>
<DefaultSearchForm/>
<DefaultDataHistoryChangeHistoryForm/>
<DefaultDataHistoryVersionDataForm/>
<DefaultDataHistoryVersionDifferencesForm/>
<DefaultCollaborationSystemUsersChoiceForm/>
<RequiredMobileApplicationPermissions/>
<UsedMobileApplicationFunctionalities>
<app:functionality>
<app:functionality>Biometrics</app:functionality>
<app:use>true</app:use>
</app:functionality>
<app:functionality>
<app:functionality>Location</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>BackgroundLocation</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>BluetoothPrinters</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>WiFiPrinters</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>Contacts</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>Calendars</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>PushNotifications</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>LocalNotifications</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>InAppPurchases</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>PersonalComputerFileExchange</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>Ads</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>NumberDialing</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>CallProcessing</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>CallLog</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>AutoSendSMS</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>ReceiveSMS</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>SMSLog</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>Camera</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>Microphone</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>MusicLibrary</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>PictureAndVideoLibraries</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>AudioPlaybackAndVibration</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>BackgroundAudioPlaybackAndVibration</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>InstallPackages</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>OSBackup</app:functionality>
<app:use>true</app:use>
</app:functionality>
<app:functionality>
<app:functionality>ApplicationUsageStatistics</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>BarcodeScanning</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>BackgroundAudioRecording</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>AllFilesAccess</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>Videoconferences</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>NFC</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>DocumentScanning</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>SpeechToText</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>Geofences</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>IncomingShareRequests</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>AllIncomingShareRequestsTypesProcessing</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>TextToSpeech</app:functionality>
<app:use>false</app:use>
</app:functionality>
</UsedMobileApplicationFunctionalities>
<StandaloneConfigurationRestrictionRoles/>
<MobileApplicationURLs/>
<AllowedIncomingShareRequestTypes/>
<MainClientApplicationWindowMode>Normal</MainClientApplicationWindowMode>
<DefaultInterface/>
<DefaultStyle/>
<DefaultLanguage>Language.Русский</DefaultLanguage>
<BriefInformation/>
<DetailedInformation/>
<Copyright/>
<VendorInformationAddress/>
<ConfigurationInformationAddress/>
<DataLockControlMode>Managed</DataLockControlMode>
<ObjectAutonumerationMode>NotAutoFree</ObjectAutonumerationMode>
<ModalityUseMode>DontUse</ModalityUseMode>
<SynchronousPlatformExtensionAndAddInCallUseMode>DontUse</SynchronousPlatformExtensionAndAddInCallUseMode>
<InterfaceCompatibilityMode>Taxi</InterfaceCompatibilityMode>
<DatabaseTablespacesUseMode>DontUse</DatabaseTablespacesUseMode>
<CompatibilityMode>Version8_3_27</CompatibilityMode>
<DefaultConstantsForm/>
</Properties>
<ChildObjects>
<Language>Русский</Language>
<CommonModule>ОбщийМодуль1</CommonModule>
<CommonModule>ОбщийМодуль2</CommonModule>
</ChildObjects>
</Configuration>
</MetaDataObject>
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.20">
<Language uuid="e707122a-91c0-40f4-ae5a-46cbcd3ff534">
<Properties>
<Name>Русский</Name>
<Synonym>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Русский</v8:content>
</v8:item>
</Synonym>
<Comment/>
<LanguageCode>ru</LanguageCode>
</Properties>
</Language>
</MetaDataObject>
View File
Whitespace-only changes.
+269
View File
@@ -0,0 +1,269 @@
import os
from pathlib import Path
from unittest import mock
import pytest
from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language
from solidlsp.ls_utils import SymbolUtils
from solidlsp.settings import SolidLSPSettings
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
@pytest.mark.bsl
class TestBSLLanguageServer:
@pytest.mark.parametrize("language_server", [Language.BSL], indirect=True)
@pytest.mark.parametrize("repo_path", [Language.BSL], indirect=True)
def test_ls_is_running(self, language_server: SolidLanguageServer, repo_path: Path) -> None:
"""Language server starts and attaches to the test repository."""
assert language_server.is_running()
assert Path(language_server.language_server.repository_root_path).resolve() == repo_path.resolve()
@pytest.mark.parametrize("language_server", [Language.BSL], indirect=True)
def test_find_symbol(self, language_server: SolidLanguageServer) -> None:
symbols = language_server.request_full_symbol_tree()
assert SymbolUtils.symbol_tree_contains_name(symbols, "ВывестиСообщение"), "ВывестиСообщение not found in symbol tree"
assert SymbolUtils.symbol_tree_contains_name(symbols, "ПолучитьПриветствие"), "ПолучитьПриветствие not found in symbol tree"
assert SymbolUtils.symbol_tree_contains_name(symbols, "Инициализировать"), "Инициализировать not found in symbol tree"
@pytest.mark.parametrize("language_server", [Language.BSL], indirect=True)
def test_document_symbols(self, language_server: SolidLanguageServer) -> None:
doc_symbols = language_server.request_document_symbols("CommonModule.bsl")
all_symbols, _ = doc_symbols.get_all_symbols_and_roots()
names = [s.get("name") for s in all_symbols if s.get("name")]
assert "ВывестиСообщение" in names, f"ВывестиСообщение not found in CommonModule.bsl symbols. Found: {names}"
assert "ПолучитьПриветствие" in names, f"ПолучитьПриветствие not found in CommonModule.bsl symbols. Found: {names}"
assert "ВызватьПриветствие" in names, f"ВызватьПриветствие not found in CommonModule.bsl symbols. Found: {names}"
@pytest.mark.parametrize("language_server", [Language.BSL], indirect=True)
def test_full_symbol_tree_within_file(self, language_server: SolidLanguageServer) -> None:
"""Scoping the full-tree request to a single file returns that file's symbols."""
symbols = language_server.request_full_symbol_tree(within_relative_path="ObjectModule.bsl")
assert SymbolUtils.symbol_tree_contains_name(symbols, "Инициализировать"), (
"Инициализировать not found in ObjectModule.bsl symbol tree"
)
assert SymbolUtils.symbol_tree_contains_name(symbols, "ПолучитьСостояние"), (
"ПолучитьСостояние not found in ObjectModule.bsl symbol tree"
)
@pytest.mark.parametrize("language_server", [Language.BSL], indirect=True)
def test_find_references_within_file(self, language_server: SolidLanguageServer) -> None:
# CommonModule.bsl (0-indexed):
# line 2: Процедура ВывестиСообщение(Текст) Экспорт <- declaration (name starts at col 10)
# line 13: ВывестиСообщение(Сообщение); <- internal call
# This test asserts within-file resolution on a bare ``.bsl`` file (no surrounding
# 1C project metadata). Cross-module resolution is exercised separately via the
# dedicated ``src/`` fixture in ``test_find_references_across_files`` below.
refs = language_server.request_references("CommonModule.bsl", line=2, column=10)
assert refs, "Expected at least one reference to ВывестиСообщение"
file_names = [ref.get("relativePath", "") for ref in refs]
assert any("CommonModule.bsl" in f for f in file_names), f"Expected self-reference in CommonModule.bsl, got: {file_names}"
# the internal call is on line 13
matching_lines = [ref["range"]["start"]["line"] for ref in refs if "CommonModule.bsl" in ref.get("relativePath", "")]
assert 12 in matching_lines, f"Expected a reference on line 12 (0-indexed), got lines: {matching_lines}"
@pytest.mark.parametrize("language_server", [Language.BSL], indirect=True)
def test_find_references_to_function_within_file(self, language_server: SolidLanguageServer) -> None:
# CommonModule.bsl (0-indexed):
# line 6: Функция ПолучитьПриветствие(Имя) Экспорт <- declaration (name starts at col 8)
# line 12: Сообщение = ПолучитьПриветствие("Мир"); <- internal call at col 16
refs = language_server.request_references("CommonModule.bsl", line=6, column=8)
assert refs, "Expected at least one reference to ПолучитьПриветствие"
matching_lines = [ref["range"]["start"]["line"] for ref in refs if "CommonModule.bsl" in ref.get("relativePath", "")]
assert 11 in matching_lines, f"Expected a reference on line 11 (0-indexed), got lines: {matching_lines}"
# --- Cross-file reference resolution ---
#
# bsl-language-server links common modules across files only when the fixture ships
# proper 1C Configuration metadata (``Configuration.xml`` + per-module ``.mdo`` /
# ``Ext/Module.bsl`` layout). The ``src/`` subtree under the BSL test repo provides
# a minimal-but-complete Designer-format dump for exactly this purpose. The two
# tests below request references and the go-to-definition from the cross-module
# call site ``ОбщийМодуль1.ВывестиСообщение(...)`` in ``ОбщийМодуль2``.
_CROSS_REF_MODULE1 = os.path.join("src", "CommonModules", "ОбщийМодуль1", "Ext", "Module.bsl")
_CROSS_REF_MODULE2 = os.path.join("src", "CommonModules", "ОбщийМодуль2", "Ext", "Module.bsl")
@pytest.mark.parametrize("language_server", [Language.BSL], indirect=True)
def test_find_references_across_files(self, language_server: SolidLanguageServer) -> None:
"""``request_references`` from the declaration must include the cross-module call-site."""
# ОбщийМодуль1 / Ext / Module.bsl (0-indexed):
# line 2: Процедура ВывестиСообщение(Текст) Экспорт <- declaration (name at col 10)
# ОбщийМодуль2 / Ext / Module.bsl (0-indexed):
# line 3: ОбщийМодуль1.ВывестиСообщение("Hello from CommonModule2"); <- cross-module call (col 17)
refs = language_server.request_references(self._CROSS_REF_MODULE1, line=2, column=10)
assert refs, "Expected at least one reference to ВывестиСообщение across modules"
call_site_paths = [ref.get("relativePath", "") for ref in refs if "ОбщийМодуль2" in ref.get("relativePath", "")]
assert call_site_paths, (
f"Expected a cross-module reference from ОбщийМодуль2, got relativePaths: {[ref.get('relativePath', '') for ref in refs]}"
)
call_site_lines = [ref["range"]["start"]["line"] for ref in refs if "ОбщийМодуль2" in ref.get("relativePath", "")]
assert 3 in call_site_lines, f"Expected a reference at ОбщийМодуль2/Module.bsl line 3, got lines: {call_site_lines}"
@pytest.mark.parametrize("language_server", [Language.BSL], indirect=True)
def test_find_definition_across_files(self, language_server: SolidLanguageServer) -> None:
"""``request_definition`` from a cross-module call-site must resolve to the other module."""
# cursor on "ВывестиСообщение" inside
# ОбщийМодуль1.ВывестиСообщение("Hello from CommonModule2"); (line 3, col 17 — 0-indexed)
definitions = language_server.request_definition(self._CROSS_REF_MODULE2, line=3, column=17)
assert definitions, "Expected a cross-module definition for ВывестиСообщение"
target_paths = [d.get("relativePath", "") for d in definitions]
assert any("ОбщийМодуль1" in p for p in target_paths), f"Expected definition to resolve to ОбщийМодуль1, got: {target_paths}"
target_lines = [d["range"]["start"]["line"] for d in definitions if "ОбщийМодуль1" in d.get("relativePath", "")]
assert 2 in target_lines, f"Expected the definition to point at ОбщийМодуль1/Module.bsl line 2, got: {target_lines}"
@pytest.mark.parametrize("language_server", [Language.BSL], indirect=True)
def test_bare_symbol_names(self, language_server: SolidLanguageServer) -> None:
all_symbols = request_all_symbols(language_server)
malformed = [s for s in all_symbols if has_malformed_name(s)]
if malformed:
pytest.fail(
f"Found malformed symbols: {[format_symbol_for_assert(s) for s in malformed]}",
pytrace=False,
)
# ---------------------------------------------------------------------------
# Unit tests — no language server needed, always run regardless of Java
# ---------------------------------------------------------------------------
def test_bsl_filename_matcher() -> None:
matcher = Language.BSL.get_source_fn_matcher()
assert matcher.is_relevant_filename("module.bsl")
assert matcher.is_relevant_filename("script.os")
assert not matcher.is_relevant_filename("module.py")
def test_bsl_enum_registration() -> None:
assert Language.BSL.value == "bsl"
assert Language.BSL.get_ls_class().__name__ == "BSLLanguageServer"
def test_bsl_dependency_provider_default_version() -> None:
"""DependencyProvider resolves the default version to the expected versioned JAR path."""
from solidlsp.language_servers.bsl_language_server import (
DEFAULT_BSL_LS_VERSION,
BSLLanguageServer,
)
settings = SolidLSPSettings()
provider = BSLLanguageServer.DependencyProvider(
settings.get_ls_specific_settings(Language.BSL),
"/tmp/ls_resources",
)
expected_version = DEFAULT_BSL_LS_VERSION
expected_jar_dir = os.path.join("/tmp/ls_resources", f"bsl-ls-{expected_version}")
expected_jar_path = os.path.join(expected_jar_dir, f"bsl-language-server-{expected_version}-exec.jar")
# pretend JAR is already on disk so no download is attempted
with mock.patch("os.path.exists", return_value=True):
jar_path = provider._get_or_install_core_dependency()
assert jar_path == expected_jar_path
def test_bsl_dependency_provider_custom_version_no_sha() -> None:
"""User-overridden versions must install without SHA256 verification."""
from solidlsp.language_servers.bsl_language_server import BSLLanguageServer
from solidlsp.language_servers.common import RuntimeDependencyCollection
settings = SolidLSPSettings()
settings.ls_specific_settings[Language.BSL] = {"bsl_ls_version": "0.28.0"}
provider = BSLLanguageServer.DependencyProvider(
settings.get_ls_specific_settings(Language.BSL),
"/tmp/ls_resources",
)
custom_version = "0.28.0"
expected_jar_dir = os.path.join("/tmp/ls_resources", f"bsl-ls-{custom_version}")
expected_jar_path = os.path.join(expected_jar_dir, f"bsl-language-server-{custom_version}-exec.jar")
installed_deps = []
def fake_install(self_inner, install_dir):
installed_deps.extend(self_inner.get_dependencies_for_current_platform())
os.makedirs(install_dir, exist_ok=True)
open(expected_jar_path, "w").close()
with mock.patch.object(RuntimeDependencyCollection, "install", fake_install):
jar_path = provider._get_or_install_core_dependency()
assert jar_path == expected_jar_path
assert len(installed_deps) == 1
assert installed_deps[0].sha256 is None, "SHA256 must be None for user-overridden version"
if os.path.exists(expected_jar_path):
os.remove(expected_jar_path)
if os.path.exists(expected_jar_dir):
os.rmdir(expected_jar_dir)
def test_bsl_launch_command_uses_ls_path_without_download() -> None:
"""
When ``ls_path`` is set, the public launch-command flow must return the user's JAR
unchanged and must NOT invoke the download / install path. This covers the real
code path used at runtime (``create_launch_command``), not just private helpers.
"""
from solidlsp.language_servers import bsl_language_server
from solidlsp.language_servers.bsl_language_server import BSLLanguageServer
settings = SolidLSPSettings()
settings.ls_specific_settings[Language.BSL] = {"ls_path": "/custom/path/bsl-language-server.jar"}
provider = BSLLanguageServer.DependencyProvider(
settings.get_ls_specific_settings(Language.BSL),
"/tmp/ls_resources",
)
with (
mock.patch.object(bsl_language_server.shutil, "which", return_value="/usr/bin/java"),
mock.patch.object(bsl_language_server, "_get_java_major_version", return_value=21),
mock.patch.object(
BSLLanguageServer.DependencyProvider,
"_get_or_install_core_dependency",
side_effect=AssertionError("must not be called when ls_path is provided"),
) as install_mock,
):
cmd = provider.create_launch_command()
assert cmd == ["java", "-jar", "/custom/path/bsl-language-server.jar"]
install_mock.assert_not_called()
def test_bsl_launch_command_requires_java() -> None:
"""Launch command construction must fail fast when Java is missing."""
from solidlsp.language_servers import bsl_language_server
from solidlsp.language_servers.bsl_language_server import BSLLanguageServer
settings = SolidLSPSettings()
settings.ls_specific_settings[Language.BSL] = {"ls_path": "/custom/path/bsl-language-server.jar"}
provider = BSLLanguageServer.DependencyProvider(
settings.get_ls_specific_settings(Language.BSL),
"/tmp/ls_resources",
)
with mock.patch.object(bsl_language_server.shutil, "which", return_value=None):
with pytest.raises(RuntimeError, match="not found on PATH"):
provider.create_launch_command()
def test_bsl_launch_command_rejects_old_java() -> None:
"""Java older than the minimum supported major version must be rejected up front."""
from solidlsp.language_servers import bsl_language_server
from solidlsp.language_servers.bsl_language_server import BSL_LS_MIN_JAVA_VERSION, BSLLanguageServer
settings = SolidLSPSettings()
settings.ls_specific_settings[Language.BSL] = {"ls_path": "/custom/path/bsl-language-server.jar"}
provider = BSLLanguageServer.DependencyProvider(
settings.get_ls_specific_settings(Language.BSL),
"/tmp/ls_resources",
)
with (
mock.patch.object(bsl_language_server.shutil, "which", return_value="/usr/bin/java"),
mock.patch.object(bsl_language_server, "_get_java_major_version", return_value=BSL_LS_MIN_JAVA_VERSION - 1),
):
with pytest.raises(RuntimeError, match=f"Java {BSL_LS_MIN_JAVA_VERSION}\\+"):
provider.create_launch_command()
+21
View File
@@ -0,0 +1,21 @@
import shutil
import pytest
from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language
from test.solidlsp.util.diagnostics import assert_file_diagnostics
@pytest.mark.bsl
@pytest.mark.skipif(shutil.which("java") is None, reason="Java 21+ is required for BSL LSP")
class TestBSLDiagnostics:
@pytest.mark.parametrize("language_server", [Language.BSL], indirect=True)
def test_file_diagnostics(self, language_server: SolidLanguageServer) -> None:
"""bsl-language-server must flag the unterminated string literal in the fixture."""
assert_file_diagnostics(
language_server,
"diagnostics_sample.bsl",
(),
min_count=1,
)