diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml
index aed77355..58ba0032 100644
--- a/.github/workflows/pytest.yml
+++ b/.github/workflows/pytest.yml
@@ -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:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6ed7dde3..bffc202c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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).
diff --git a/README.md b/README.md
index 160dc8b4..9fe71225 100644
--- a/README.md
+++ b/README.md
@@ -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
diff --git a/docs/01-about/020_programming-languages.md b/docs/01-about/020_programming-languages.md
index 22fa1f66..95601e85 100644
--- a/docs/01-about/020_programming-languages.md
+++ b/docs/01-about/020_programming-languages.md
@@ -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)
diff --git a/docs/02-usage/050_configuration.md b/docs/02-usage/050_configuration.md
index e600d1a4..ddc095b1 100644
--- a/docs/02-usage/050_configuration.md
+++ b/docs/02-usage/050_configuration.md
@@ -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.
diff --git a/pyproject.toml b/pyproject.toml
index 278d3c3a..56a58d95 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -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)",
diff --git a/src/solidlsp/language_servers/bsl_language_server.py b/src/solidlsp/language_servers/bsl_language_server.py
new file mode 100644
index 00000000..98aaa0e1
--- /dev/null
+++ b/src/solidlsp/language_servers/bsl_language_server.py
@@ -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()
diff --git a/src/solidlsp/ls_config.py b/src/solidlsp/ls_config.py
index 34fdf0fb..0817c3a3 100644
--- a/src/solidlsp/ls_config.py
+++ b/src/solidlsp/ls_config.py
@@ -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
diff --git a/test/conftest.py b/test/conftest.py
index ab93ff5f..95ef22cd 100644
--- a/test/conftest.py
+++ b/test/conftest.py
@@ -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
diff --git a/test/resources/repos/bsl/test_repo/.bsl-language-server.json b/test/resources/repos/bsl/test_repo/.bsl-language-server.json
new file mode 100644
index 00000000..45c1c84c
--- /dev/null
+++ b/test/resources/repos/bsl/test_repo/.bsl-language-server.json
@@ -0,0 +1,5 @@
+{
+ "language": "ru",
+ "diagnosticLanguage": "ru",
+ "computeDiagnostics": "onSave"
+}
\ No newline at end of file
diff --git a/test/resources/repos/bsl/test_repo/.gitkeep b/test/resources/repos/bsl/test_repo/.gitkeep
new file mode 100644
index 00000000..e69de29b
diff --git a/test/resources/repos/bsl/test_repo/CommonModule.bsl b/test/resources/repos/bsl/test_repo/CommonModule.bsl
new file mode 100644
index 00000000..ce5b6b9e
--- /dev/null
+++ b/test/resources/repos/bsl/test_repo/CommonModule.bsl
@@ -0,0 +1,14 @@
+// CommonModule.bsl - common module with reusable procedures
+
+Процедура ВывестиСообщение(Текст) Экспорт
+ Сообщить(Текст);
+КонецПроцедуры
+
+Функция ПолучитьПриветствие(Имя) Экспорт
+ Возврат "Привет, " + Имя + "!";
+КонецФункции
+
+Процедура ВызватьПриветствие() Экспорт
+ Сообщение = ПолучитьПриветствие("Мир");
+ ВывестиСообщение(Сообщение);
+КонецПроцедуры
diff --git a/test/resources/repos/bsl/test_repo/ObjectModule.bsl b/test/resources/repos/bsl/test_repo/ObjectModule.bsl
new file mode 100644
index 00000000..88cc6938
--- /dev/null
+++ b/test/resources/repos/bsl/test_repo/ObjectModule.bsl
@@ -0,0 +1,12 @@
+// ObjectModule.bsl - object module example
+
+Перем ВнутреннееСостояние;
+
+Процедура Инициализировать() Экспорт
+ ВнутреннееСостояние = "ready";
+ ВывестиСообщение("Объект инициализирован");
+КонецПроцедуры
+
+Функция ПолучитьСостояние() Экспорт
+ Возврат ВнутреннееСостояние;
+КонецФункции
diff --git a/test/resources/repos/bsl/test_repo/diagnostics_sample.bsl b/test/resources/repos/bsl/test_repo/diagnostics_sample.bsl
new file mode 100644
index 00000000..3d0ba312
--- /dev/null
+++ b/test/resources/repos/bsl/test_repo/diagnostics_sample.bsl
@@ -0,0 +1,6 @@
+// diagnostics_sample.bsl — намеренно сломанный файл для проверки диагностик BSL LS.
+// Содержит синтаксическую ошибку (незакрытая строка), которую bsl-language-server гарантированно флагнет.
+
+Процедура СломаннаяПроцедура() Экспорт
+ Текст = "незакрытая строка;
+КонецПроцедуры
diff --git a/test/resources/repos/bsl/test_repo/src/CommonModules/ОбщийМодуль1.xml b/test/resources/repos/bsl/test_repo/src/CommonModules/ОбщийМодуль1.xml
new file mode 100644
index 00000000..cc693c58
--- /dev/null
+++ b/test/resources/repos/bsl/test_repo/src/CommonModules/ОбщийМодуль1.xml
@@ -0,0 +1,18 @@
+
+
+
+
+ ОбщийМодуль1
+
+
+ false
+ false
+ true
+ true
+ false
+ true
+ false
+ DontUse
+
+
+
\ No newline at end of file
diff --git a/test/resources/repos/bsl/test_repo/src/CommonModules/ОбщийМодуль1/Ext/Module.bsl b/test/resources/repos/bsl/test_repo/src/CommonModules/ОбщийМодуль1/Ext/Module.bsl
new file mode 100644
index 00000000..c17ba301
--- /dev/null
+++ b/test/resources/repos/bsl/test_repo/src/CommonModules/ОбщийМодуль1/Ext/Module.bsl
@@ -0,0 +1,5 @@
+// CommonModule1 — shared helpers called from other modules.
+
+Процедура ВывестиСообщение(Текст) Экспорт
+ Сообщить(Текст);
+КонецПроцедуры
diff --git a/test/resources/repos/bsl/test_repo/src/CommonModules/ОбщийМодуль2.xml b/test/resources/repos/bsl/test_repo/src/CommonModules/ОбщийМодуль2.xml
new file mode 100644
index 00000000..6c539843
--- /dev/null
+++ b/test/resources/repos/bsl/test_repo/src/CommonModules/ОбщийМодуль2.xml
@@ -0,0 +1,18 @@
+
+
+
+
+ ОбщийМодуль2
+
+
+ false
+ false
+ true
+ true
+ false
+ true
+ false
+ DontUse
+
+
+
\ No newline at end of file
diff --git a/test/resources/repos/bsl/test_repo/src/CommonModules/ОбщийМодуль2/Ext/Module.bsl b/test/resources/repos/bsl/test_repo/src/CommonModules/ОбщийМодуль2/Ext/Module.bsl
new file mode 100644
index 00000000..923fb56d
--- /dev/null
+++ b/test/resources/repos/bsl/test_repo/src/CommonModules/ОбщийМодуль2/Ext/Module.bsl
@@ -0,0 +1,5 @@
+// CommonModule2 — exercises a cross-module call into CommonModule1.
+
+Процедура ПозватьМодуль1() Экспорт
+ ОбщийМодуль1.ВывестиСообщение("Hello from CommonModule2");
+КонецПроцедуры
diff --git a/test/resources/repos/bsl/test_repo/src/ConfigDumpInfo.xml b/test/resources/repos/bsl/test_repo/src/ConfigDumpInfo.xml
new file mode 100644
index 00000000..899c546c
--- /dev/null
+++ b/test/resources/repos/bsl/test_repo/src/ConfigDumpInfo.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/test/resources/repos/bsl/test_repo/src/Configuration.xml b/test/resources/repos/bsl/test_repo/src/Configuration.xml
new file mode 100644
index 00000000..0ce60408
--- /dev/null
+++ b/test/resources/repos/bsl/test_repo/src/Configuration.xml
@@ -0,0 +1,252 @@
+
+
+
+
+
+ 9cd510cd-abfc-11d4-9434-004095e12fc7
+ f2bbb9be-74c9-40ac-a945-4b03d2624a80
+
+
+ 9fcd25a0-4822-11d4-9414-008048da11f9
+ 74c2c4b9-ca2e-4c29-9ac1-c9a686dffd48
+
+
+ e3687481-0a87-462c-a166-9f34594f9bba
+ e6d84959-ef42-417d-b585-89b178e2ebe4
+
+
+ 9de14907-ec23-4a07-96f0-85521cb6b53b
+ bb344884-c3a2-4197-ad5d-7e6c391de24f
+
+
+ 51f2d5d8-ea4d-4064-8892-82951750031e
+ 4c2a3610-e746-47e5-a127-f6ef59fba16d
+
+
+ e68182ea-4237-4383-967f-90c1e3370bc7
+ f94ef4a2-9b6b-4ef7-8fc1-b69a351798cd
+
+
+ fb282519-d103-4dd3-bc12-cb271d631dfc
+ d1f9e184-8cf0-47a9-a487-22516bb66c8c
+
+
+
+ Конфигурация
+
+
+
+ Version8_3_27
+ ManagedApplication
+
+ PlatformApplication
+
+ Russian
+
+
+
+
+ false
+ false
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Biometrics
+ true
+
+
+ Location
+ false
+
+
+ BackgroundLocation
+ false
+
+
+ BluetoothPrinters
+ false
+
+
+ WiFiPrinters
+ false
+
+
+ Contacts
+ false
+
+
+ Calendars
+ false
+
+
+ PushNotifications
+ false
+
+
+ LocalNotifications
+ false
+
+
+ InAppPurchases
+ false
+
+
+ PersonalComputerFileExchange
+ false
+
+
+ Ads
+ false
+
+
+ NumberDialing
+ false
+
+
+ CallProcessing
+ false
+
+
+ CallLog
+ false
+
+
+ AutoSendSMS
+ false
+
+
+ ReceiveSMS
+ false
+
+
+ SMSLog
+ false
+
+
+ Camera
+ false
+
+
+ Microphone
+ false
+
+
+ MusicLibrary
+ false
+
+
+ PictureAndVideoLibraries
+ false
+
+
+ AudioPlaybackAndVibration
+ false
+
+
+ BackgroundAudioPlaybackAndVibration
+ false
+
+
+ InstallPackages
+ false
+
+
+ OSBackup
+ true
+
+
+ ApplicationUsageStatistics
+ false
+
+
+ BarcodeScanning
+ false
+
+
+ BackgroundAudioRecording
+ false
+
+
+ AllFilesAccess
+ false
+
+
+ Videoconferences
+ false
+
+
+ NFC
+ false
+
+
+ DocumentScanning
+ false
+
+
+ SpeechToText
+ false
+
+
+ Geofences
+ false
+
+
+ IncomingShareRequests
+ false
+
+
+ AllIncomingShareRequestsTypesProcessing
+ false
+
+
+ TextToSpeech
+ false
+
+
+
+
+
+ Normal
+
+
+ Language.Русский
+
+
+
+
+
+ Managed
+ NotAutoFree
+ DontUse
+ DontUse
+ Taxi
+ DontUse
+ Version8_3_27
+
+
+
+ Русский
+ ОбщийМодуль1
+ ОбщийМодуль2
+
+
+
\ No newline at end of file
diff --git a/test/resources/repos/bsl/test_repo/src/Languages/Русский.xml b/test/resources/repos/bsl/test_repo/src/Languages/Русский.xml
new file mode 100644
index 00000000..6665f2c9
--- /dev/null
+++ b/test/resources/repos/bsl/test_repo/src/Languages/Русский.xml
@@ -0,0 +1,16 @@
+
+
+
+
+ Русский
+
+
+ ru
+ Русский
+
+
+
+ ru
+
+
+
\ No newline at end of file
diff --git a/test/solidlsp/bsl/__init__.py b/test/solidlsp/bsl/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/test/solidlsp/bsl/test_bsl_basic.py b/test/solidlsp/bsl/test_bsl_basic.py
new file mode 100644
index 00000000..b58ab984
--- /dev/null
+++ b/test/solidlsp/bsl/test_bsl_basic.py
@@ -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()
diff --git a/test/solidlsp/bsl/test_bsl_diagnostics.py b/test/solidlsp/bsl/test_bsl_diagnostics.py
new file mode 100644
index 00000000..7974b4a6
--- /dev/null
+++ b/test/solidlsp/bsl/test_bsl_diagnostics.py
@@ -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,
+ )