Fixed diagnostics files uri handling, added low-level tests

This commit is contained in:
Michael Panchenko authored and Dominik Jain committed 2026-04-29 23:52:26 +02:00
1 parent d198d67a9f
commit bd7e64e85a
91 files changed
+958 -72

No files matched your search

+3 -1
View File
@@ -79,6 +79,8 @@ downloads/
eggs/ eggs/
.eggs/ .eggs/
lib/ lib/
!test/resources/repos/dart/test_repo/lib/
!test/resources/repos/dart/test_repo/lib/diagnostics_sample.dart
lib64/ lib64/
parts/ parts/
sdist/ sdist/
@@ -271,4 +273,4 @@ cabal.project.local*
zz-misc/ zz-misc/
vue-implementation/ vue-implementation/
news/news.json news/news.json
+1
View File
@@ -28,6 +28,7 @@ project_name: "serena"
languages: languages:
- python - python
- typescript - typescript
- clojure
# whether to use project's .gitignore files to ignore files # whether to use project's .gitignore files to ignore files
ignore_all_files_in_gitignore: true ignore_all_files_in_gitignore: true
+4 -2
View File
@@ -16,6 +16,7 @@ from serena.tools import (
JetBrainsFindSymbolTool, JetBrainsFindSymbolTool,
JetBrainsGetSymbolsOverviewTool, JetBrainsGetSymbolsOverviewTool,
JetBrainsInlineSymbol, JetBrainsInlineSymbol,
JetBrainsRunInspectionsTool,
JetBrainsSafeDeleteTool, JetBrainsSafeDeleteTool,
SearchForPatternTool, SearchForPatternTool,
) )
@@ -35,12 +36,13 @@ if __name__ == "__main__":
overview_tool = agent.get_tool(JetBrainsGetSymbolsOverviewTool) overview_tool = agent.get_tool(JetBrainsGetSymbolsOverviewTool)
safe_delete_tool = agent.get_tool(JetBrainsSafeDeleteTool) safe_delete_tool = agent.get_tool(JetBrainsSafeDeleteTool)
inline_symbol = agent.get_tool(JetBrainsInlineSymbol) inline_symbol = agent.get_tool(JetBrainsInlineSymbol)
diagnostics_in_file_tool = agent.get_tool(GetDiagnosticsForFileTool)
jb_inspections_tool = agent.get_tool(JetBrainsRunInspectionsTool)
result = agent.execute_task( result = agent.execute_task(
lambda: overview_tool.apply( lambda: overview_tool.apply(
# name_path_pattern="SerenaAgent", # name_path_pattern="SerenaAgent",
relative_path="src/serena/agent.py", relative_path="test/resources/repos/clojure/test_repo/src/test_app/diagnostics_sample.clj",
depth=2,
# keep_definition=True, # keep_definition=True,
) )
) )
@@ -689,7 +689,7 @@ class JetBrainsPluginClient(ToStringMixin):
request_data["startLine"] = start_line request_data["startLine"] = start_line
if end_line is not None: if end_line is not None:
request_data["endLine"] = end_line request_data["endLine"] = end_line
return cast(jb.RunInspectionsResponse, self._make_request("POST", "/runInspections", request_data)) return cast(jb.RunInspectionsResponse, self._make_request("POST", "/runInspectionsOnFile", request_data))
def list_inspections( def list_inspections(
self, self,
@@ -32,9 +32,10 @@ class ElmLanguageServer(SolidLanguageServer):
Creates an ElmLanguageServer instance. This class is not meant to be instantiated directly. Use LanguageServer.create() instead. Creates an ElmLanguageServer instance. This class is not meant to be instantiated directly. Use LanguageServer.create() instead.
""" """
elm_lsp_executable_path = self._setup_runtime_dependencies(config, solidlsp_settings) elm_lsp_executable_path = self._setup_runtime_dependencies(config, solidlsp_settings)
managed_bin_dir = os.path.join(self.ls_resources_dir(solidlsp_settings), "elm-lsp", "node_modules", ".bin")
# Resolve ELM_HOME to absolute path if it's set to a relative path # Resolve ELM_HOME to absolute path if it's set to a relative path
env = {} env = {"PATH": managed_bin_dir + os.pathsep + os.environ.get("PATH", "")}
elm_home = os.environ.get("ELM_HOME") elm_home = os.environ.get("ELM_HOME")
if elm_home: if elm_home:
if not os.path.isabs(elm_home): if not os.path.isabs(elm_home):
@@ -65,9 +66,15 @@ class ElmLanguageServer(SolidLanguageServer):
""" """
Setup runtime dependencies for Elm Language Server and return the command to start the server. Setup runtime dependencies for Elm Language Server and return the command to start the server.
""" """
# Check if elm-language-server is already installed globally elm_config = solidlsp_settings.get_ls_specific_settings(Language.ELM)
elm_language_server_version = elm_config.get("elm_language_server_version", "2.8.0")
elm_compiler_version = elm_config.get("elm_compiler_version", "0.19.1-6")
npm_registry = elm_config.get("npm_registry")
# Check if elm-language-server and elm are already installed globally
system_elm_ls = shutil.which("elm-language-server") system_elm_ls = shutil.which("elm-language-server")
if system_elm_ls: system_elm = shutil.which("elm")
if system_elm_ls and system_elm:
log.info(f"Found system-installed elm-language-server at {system_elm_ls}") log.info(f"Found system-installed elm-language-server at {system_elm_ls}")
return [system_elm_ls, "--stdio"] return [system_elm_ls, "--stdio"]
@@ -76,10 +83,6 @@ class ElmLanguageServer(SolidLanguageServer):
assert is_node_installed, "node is not installed or isn't in PATH. Please install NodeJS and try again." assert is_node_installed, "node is not installed or isn't in PATH. Please install NodeJS and try again."
is_npm_installed = shutil.which("npm") is not None is_npm_installed = shutil.which("npm") is not None
assert is_npm_installed, "npm is not installed or isn't in PATH. Please install npm and try again." assert is_npm_installed, "npm is not installed or isn't in PATH. Please install npm and try again."
elm_config = solidlsp_settings.get_ls_specific_settings(Language.ELM)
elm_language_server_version = elm_config.get("elm_language_server_version", "2.8.0")
npm_registry = elm_config.get("npm_registry")
deps = RuntimeDependencyCollection( deps = RuntimeDependencyCollection(
[ [
RuntimeDependency( RuntimeDependency(
@@ -88,6 +91,12 @@ class ElmLanguageServer(SolidLanguageServer):
command=build_npm_install_command("@elm-tooling/elm-language-server", elm_language_server_version, npm_registry), command=build_npm_install_command("@elm-tooling/elm-language-server", elm_language_server_version, npm_registry),
platform_id="any", platform_id="any",
), ),
RuntimeDependency(
id="elm",
description="Elm compiler package",
command=build_npm_install_command("elm", elm_compiler_version, npm_registry),
platform_id="any",
),
] ]
) )
@@ -98,6 +107,10 @@ class ElmLanguageServer(SolidLanguageServer):
log.info(f"Elm Language Server executable not found at {elm_ls_executable_path}. Installing...") log.info(f"Elm Language Server executable not found at {elm_ls_executable_path}. Installing...")
with LogTime("Installation of Elm language server dependencies", logger=log): with LogTime("Installation of Elm language server dependencies", logger=log):
deps.install(elm_ls_dir) deps.install(elm_ls_dir)
elif not system_elm:
log.info("Elm compiler not found on PATH. Installing a managed Elm compiler...")
with LogTime("Installation of Elm compiler dependency", logger=log):
deps.install(elm_ls_dir)
if not os.path.exists(elm_ls_executable_path): if not os.path.exists(elm_ls_executable_path):
raise FileNotFoundError( raise FileNotFoundError(
@@ -128,6 +141,7 @@ class ElmLanguageServer(SolidLanguageServer):
"hover": {"dynamicRegistration": True, "contentFormat": ["markdown", "plaintext"]}, "hover": {"dynamicRegistration": True, "contentFormat": ["markdown", "plaintext"]},
"codeAction": {"dynamicRegistration": True}, "codeAction": {"dynamicRegistration": True},
"rename": {"dynamicRegistration": True}, "rename": {"dynamicRegistration": True},
"publishDiagnostics": {"relatedInformation": True},
}, },
"workspace": { "workspace": {
"workspaceFolders": True, "workspaceFolders": True,
@@ -220,6 +220,7 @@ class FortranLanguageServer(SolidLanguageServer):
"formatting": {"dynamicRegistration": True}, "formatting": {"dynamicRegistration": True},
"rangeFormatting": {"dynamicRegistration": True}, "rangeFormatting": {"dynamicRegistration": True},
"codeAction": {"dynamicRegistration": True}, "codeAction": {"dynamicRegistration": True},
"publishDiagnostics": {"relatedInformation": True},
}, },
"workspace": { "workspace": {
"workspaceFolders": True, "workspaceFolders": True,
@@ -124,6 +124,7 @@ class Lean4LanguageServer(SolidLanguageServer):
"documentationFormat": ["markdown", "plaintext"], "documentationFormat": ["markdown", "plaintext"],
}, },
}, },
"publishDiagnostics": {"relatedInformation": True},
}, },
"workspace": { "workspace": {
"workspaceFolders": True, "workspaceFolders": True,
@@ -777,6 +777,14 @@ class PascalLanguageServer(SolidLanguageServer):
if value: if value:
initialization_options[var] = value initialization_options[var] = value
initialization_options.update(
{
"checkSyntax": True,
"publishDiagnostics": True,
"showSyntaxErrors": True,
}
)
initialize_params = { initialize_params = {
"locale": "en", "locale": "en",
"capabilities": { "capabilities": {
@@ -831,6 +839,7 @@ class PascalLanguageServer(SolidLanguageServer):
}, },
"formatting": {"dynamicRegistration": True}, "formatting": {"dynamicRegistration": True},
"rangeFormatting": {"dynamicRegistration": True}, "rangeFormatting": {"dynamicRegistration": True},
"publishDiagnostics": {"relatedInformation": True},
}, },
"workspace": { "workspace": {
"workspaceFolders": True, "workspaceFolders": True,
@@ -148,6 +148,7 @@ class SolidityLanguageServer(SolidLanguageServer):
"dynamicRegistration": True, "dynamicRegistration": True,
"contentFormat": ["markdown", "plaintext"], # type: ignore[list-item] "contentFormat": ["markdown", "plaintext"], # type: ignore[list-item]
}, },
"publishDiagnostics": {"relatedInformation": True},
}, },
"workspace": { "workspace": {
"workspaceFolders": True, "workspaceFolders": True,
@@ -89,7 +89,10 @@ class TypeScriptLanguageServer(SolidLanguageServer):
self.server_ready = threading.Event() self.server_ready = threading.Event()
self.initialize_searcher_command_available = threading.Event() self.initialize_searcher_command_available = threading.Event()
# Progress tracking for $/progress notifications (project indexing, etc.) # tracking asynchronous diagnostics publication
self._published_diagnostics_timeout = 5.0
# tracking project indexing progress
self._progress_lock = threading.Lock() self._progress_lock = threading.Lock()
self._active_progress_tokens: set[str] = set() self._active_progress_tokens: set[str] = set()
self._indexing_complete = threading.Event() self._indexing_complete = threading.Event()
@@ -243,9 +246,11 @@ class TypeScriptLanguageServer(SolidLanguageServer):
"signatureHelp": {"dynamicRegistration": True}, "signatureHelp": {"dynamicRegistration": True},
"codeAction": {"dynamicRegistration": True}, "codeAction": {"dynamicRegistration": True},
"rename": {"dynamicRegistration": True, "prepareSupport": True}, "rename": {"dynamicRegistration": True, "prepareSupport": True},
"publishDiagnostics": {"relatedInformation": True},
}, },
"workspace": { "workspace": {
"workspaceFolders": True, "workspaceFolders": True,
"configuration": True,
"didChangeConfiguration": {"dynamicRegistration": True}, "didChangeConfiguration": {"dynamicRegistration": True},
"symbol": {"dynamicRegistration": True}, "symbol": {"dynamicRegistration": True},
}, },
@@ -293,6 +298,10 @@ class TypeScriptLanguageServer(SolidLanguageServer):
def execute_client_command_handler(params: dict) -> list: def execute_client_command_handler(params: dict) -> list:
return [] return []
def configuration_handler(params: dict) -> list:
items = params.get("items", [])
return [{} for _ in items]
def do_nothing(params: dict) -> None: def do_nothing(params: dict) -> None:
return return
@@ -352,6 +361,7 @@ class TypeScriptLanguageServer(SolidLanguageServer):
self._indexing_complete.set() self._indexing_complete.set()
self.server.on_request("client/registerCapability", register_capability_handler) self.server.on_request("client/registerCapability", register_capability_handler)
self.server.on_request("workspace/configuration", configuration_handler)
self.server.on_notification("window/logMessage", window_log_message) self.server.on_notification("window/logMessage", window_log_message)
self.server.on_request("workspace/executeClientCommand", execute_client_command_handler) self.server.on_request("workspace/executeClientCommand", execute_client_command_handler)
self.server.on_request("window/workDoneProgress/create", work_done_progress_create) self.server.on_request("window/workDoneProgress/create", work_done_progress_create)
@@ -397,6 +407,20 @@ class TypeScriptLanguageServer(SolidLanguageServer):
self.INDEXING_PROGRESS_TIMEOUT, self.INDEXING_PROGRESS_TIMEOUT,
) )
@override
def _get_published_diagnostics_uri(self, request_uri: str) -> str:
if os.name != "nt" or not request_uri.startswith("file:///"):
return request_uri
path_part = request_uri[len("file:///") :]
if len(path_part) >= 2 and path_part[0].isalpha() and path_part[1] == ":":
return f"file:///{path_part[0].lower()}%3A{path_part[2:]}"
return request_uri
@override
def _get_published_diagnostics_wait_timeout(self, pull_diagnostics_failed: bool) -> float:
return self._published_diagnostics_timeout
@override @override
def _get_wait_time_for_cross_file_referencing(self) -> float: def _get_wait_time_for_cross_file_referencing(self) -> float:
return 2 return 2
@@ -426,6 +426,18 @@ class VueLanguageServer(SolidLanguageServer):
with self._ts_server.open_file(relative_file_path): with self._ts_server.open_file(relative_file_path):
return self._ts_server.request_rename_symbol_edit(relative_file_path, line, column, new_name) return self._ts_server.request_rename_symbol_edit(relative_file_path, line, column, new_name)
@override
def request_text_document_diagnostics(
self,
relative_file_path: str,
start_line: int = 0,
end_line: int = -1,
min_severity: int = 4,
) -> list[ls_types.Diagnostic]:
self._ensure_ls_operational()
assert self._ts_server is not None
return self._ts_server.request_text_document_diagnostics(relative_file_path, start_line, end_line, min_severity)
def _forward_edit_to_ts_server_if_needed(self, relative_file_path: str, edit_fn: Callable[[], object]) -> None: def _forward_edit_to_ts_server_if_needed(self, relative_file_path: str, edit_fn: Callable[[], object]) -> None:
""" """
Calls ``edit_fn`` on the TypeScript server if the file is open there. Calls ``edit_fn`` on the TypeScript server if the file is open there.
@@ -598,6 +610,7 @@ class VueLanguageServer(SolidLanguageServer):
"signatureHelp": {"dynamicRegistration": True}, "signatureHelp": {"dynamicRegistration": True},
"codeAction": {"dynamicRegistration": True}, "codeAction": {"dynamicRegistration": True},
"rename": {"dynamicRegistration": True, "prepareSupport": True}, "rename": {"dynamicRegistration": True, "prepareSupport": True},
"publishDiagnostics": {"relatedInformation": True},
}, },
"workspace": { "workspace": {
"workspaceFolders": True, "workspaceFolders": True,
+1
View File
@@ -132,6 +132,7 @@ class ZigLanguageServer(SolidLanguageServer):
"dynamicRegistration": True, "dynamicRegistration": True,
"contentFormat": ["markdown", "plaintext"], "contentFormat": ["markdown", "plaintext"],
}, },
"publishDiagnostics": {"relatedInformation": True},
}, },
"workspace": { "workspace": {
"workspaceFolders": True, "workspaceFolders": True,
+67 -12
View File
@@ -13,7 +13,7 @@ from collections.abc import Hashable, Iterator
from contextlib import contextmanager from contextlib import contextmanager
from copy import copy from copy import copy
from pathlib import Path, PurePath from pathlib import Path, PurePath
from time import perf_counter, sleep from time import monotonic, perf_counter, sleep
from typing import Any, Self, Union, cast from typing import Any, Self, Union, cast
import pathspec import pathspec
@@ -754,21 +754,77 @@ class SolidLanguageServer(ABC):
:return: the published diagnostics, or ``None`` if no diagnostics are available. :return: the published diagnostics, or ``None`` if no diagnostics are available.
""" """
uri = self._validate_text_document_diagnostics_request(relative_file_path, start_line, end_line, min_severity) uri = self._validate_text_document_diagnostics_request(relative_file_path, start_line, end_line, min_severity)
published_uri = self._get_published_diagnostics_uri(uri)
diagnostics: list[ls_types.Diagnostic] | None = None diagnostics: list[ls_types.Diagnostic] | None = None
# keeping the document open # keeping the document open
with self.open_file(relative_file_path): with self.open_file(relative_file_path):
diagnostics = self._wait_for_published_diagnostics(uri=uri, after_generation=after_generation, timeout=timeout) diagnostics = self._wait_for_relevant_published_diagnostics(
uri=published_uri,
# falling back to cached diagnostics after_generation=after_generation,
if diagnostics is None and allow_cached: timeout=timeout,
diagnostics = self._get_cached_published_diagnostics(uri) allow_cached=allow_cached,
)
if diagnostics is None: if diagnostics is None:
return None return None
return self._filter_diagnostics(diagnostics, start_line, end_line, min_severity) return self._filter_diagnostics(diagnostics, start_line, end_line, min_severity)
def _get_published_diagnostics_uri(self, request_uri: str) -> str:
"""
Gets the URI under which published diagnostics should be looked up.
"""
return request_uri
def _get_published_diagnostics_wait_timeout(self, pull_diagnostics_failed: bool) -> float:
"""
Gets the timeout for waiting on published diagnostics after a diagnostics request.
"""
return 2.5
def _accept_published_diagnostics(self, diagnostics: list[ls_types.Diagnostic]) -> bool:
"""
Determines whether a published diagnostics payload should satisfy the current wait.
"""
return bool(diagnostics)
def _wait_for_relevant_published_diagnostics(
self,
uri: str,
after_generation: int,
timeout: float,
allow_cached: bool = True,
) -> list[ls_types.Diagnostic] | None:
"""
Waits for a published diagnostics payload that is relevant for the current request.
"""
deadline = monotonic() + timeout
current_after_generation = after_generation
while True:
remaining_timeout = deadline - monotonic()
if remaining_timeout <= 0:
break
diagnostics = self._wait_for_published_diagnostics(
uri=uri,
after_generation=current_after_generation,
timeout=remaining_timeout,
)
if diagnostics is None:
break
if self._accept_published_diagnostics(diagnostics):
return diagnostics
current_after_generation = self._get_published_diagnostics_generation(uri)
if allow_cached:
diagnostics = self._get_cached_published_diagnostics(uri)
if diagnostics is not None and self._accept_published_diagnostics(diagnostics):
return diagnostics
return None
def request_text_document_diagnostics( def request_text_document_diagnostics(
self, self,
relative_file_path: str, relative_file_path: str,
@@ -789,7 +845,8 @@ class SolidLanguageServer(ABC):
:return: A list of diagnostics for the file :return: A list of diagnostics for the file
""" """
uri = self._validate_text_document_diagnostics_request(relative_file_path, start_line, end_line, min_severity) uri = self._validate_text_document_diagnostics_request(relative_file_path, start_line, end_line, min_severity)
diagnostics_before_request = self._get_published_diagnostics_generation(uri) published_uri = self._get_published_diagnostics_uri(uri)
diagnostics_before_request = self._get_published_diagnostics_generation(published_uri)
ret: list[ls_types.Diagnostic] | None = None ret: list[ls_types.Diagnostic] | None = None
pull_diagnostics_failed = False pull_diagnostics_failed = False
@@ -825,13 +882,11 @@ class SolidLanguageServer(ABC):
ret.append(ls_types.Diagnostic(**new_item)) ret.append(ls_types.Diagnostic(**new_item))
if not ret: if not ret:
published_diagnostics = self._wait_for_published_diagnostics( published_diagnostics = self._wait_for_relevant_published_diagnostics(
uri=uri, uri=published_uri,
after_generation=diagnostics_before_request, after_generation=diagnostics_before_request,
timeout=2.5 if pull_diagnostics_failed else 0.5, timeout=self._get_published_diagnostics_wait_timeout(pull_diagnostics_failed),
) )
if published_diagnostics is None:
published_diagnostics = self._get_cached_published_diagnostics(uri)
if published_diagnostics is not None: if published_diagnostics is not None:
ret = published_diagnostics ret = published_diagnostics
@@ -0,0 +1,4 @@
#!/usr/bin/env bash
if then
echo "$missingGreeting"
fi
@@ -0,0 +1,5 @@
def broken_factory : String
missingGreeting
end
broken_consumer = missingConsumerValue
@@ -0,0 +1,3 @@
String brokenFactory() {
return 'broken'
}
@@ -0,0 +1,10 @@
defmodule DiagnosticsSample do
def broken_factory do
missing_greeting
end
def broken_consumer do
value = broken_factory()
{value, missing_consumer_value}
end
end
@@ -0,0 +1,5 @@
module DiagnosticsSample exposing (brokenFactory)
brokenFactory : String
brokenFactory =
"unterminated
@@ -0,0 +1,5 @@
-module(diagnostics_sample).
-export([broken_factory/0, broken_consumer/0]).
broken_factory() -> MissingGreeting.
broken_consumer() -> MissingConsumerValue.
@@ -0,0 +1,5 @@
program diagnostics_sample
implicit none
integer :: duplicated_value
real :: duplicated_value
end program diagnostics_sample
@@ -0,0 +1,13 @@
package com.example
class DiagnosticsSample {
static String brokenFactory() {
missingGreeting
}
static void brokenConsumer() {
def value = brokenFactory()
println(value)
println(missingConsumerValue)
}
}
@@ -0,0 +1,7 @@
module DiagnosticsSample where
brokenFactory :: String
brokenFactory = missingGreeting
brokenConsumer :: String
brokenConsumer = missingConsumerValue
@@ -0,0 +1,10 @@
class DiagnosticsSample {
static function brokenFactory():String {
return missingGreeting;
}
static function brokenConsumer():Void {
trace(brokenFactory());
trace(missingConsumerValue);
}
}
@@ -0,0 +1,3 @@
float4 main() : SV_Target {
return missingGreeting;
}
@@ -0,0 +1,4 @@
{
"name": "diagnostics",
"value":
}
@@ -0,0 +1,3 @@
function broken_factory(
value = 1
end
@@ -1,6 +1 @@
def brokenFactory : Nat := def brokenFactory : Nat :=
missingGreeting
def brokenConsumer : Nat :=
let value := brokenFactory
value + missingConsumerValue
@@ -0,0 +1,3 @@
function value = diagnostics_sample()
value = missingGreeting;
end
@@ -0,0 +1,4 @@
{
greeting = missingGreeting;
consumer = missingConsumerValue;
}
@@ -0,0 +1 @@
let broken_factory (
@@ -1,3 +1,4 @@
(library (library
(public_name test_repo) (public_name test_repo)
(name test_repo)) (name test_repo)
(modules test_repo))
@@ -0,0 +1,4 @@
program DiagnosticsSample;
begin
writeln(
end.
@@ -0,0 +1,5 @@
use strict;
use warnings;
print $missingGreeting;
print $missingConsumerValue;
@@ -1,9 +1,4 @@
function Invoke-BrokenFactory { function Invoke-BrokenFactory {
Invoke-MissingGreeting if ($true) {
} Write-Output "broken"
function Invoke-BrokenConsumer {
$value = Invoke-BrokenFactory
Write-Output $value
Invoke-MissingConsumerValue
} }
@@ -0,0 +1,9 @@
package com.example
object DiagnosticsSample {
def brokenFactory(): String = missingGreeting
def brokenConsumer(): Unit = {
println(brokenFactory())
println(missingConsumerValue)
}
}
@@ -0,0 +1,7 @@
pragma solidity ^0.8.0;
contract DiagnosticsSample {
function brokenFactory() pure returns (string memory) {
return "broken";
}
}
@@ -11,6 +11,7 @@ let package = Package(
targets: [ targets: [
.target( .target(
name: "test_repo", name: "test_repo",
dependencies: []), dependencies: [],
path: "src"),
] ]
) )
@@ -0,0 +1,3 @@
func brokenFactory() -> String {
return
}
@@ -0,0 +1,5 @@
module diagnostics_sample;
initial begin
if (
end
endmodule
@@ -0,0 +1,4 @@
resource "aws_instance" "broken" {
ami =
instance_type = "t2.micro"
}
@@ -0,0 +1,2 @@
[diagnostics]
value =
@@ -0,0 +1,3 @@
<script setup lang="ts">
const value: string =
</script>
@@ -0,0 +1,5 @@
name: diagnostics
value:
- good
-
broken: [
+1 -1
View File
@@ -28,4 +28,4 @@ pub fn build(b: *std.Build) void {
const run_lib_tests = b.addRunArtifact(lib_tests); const run_lib_tests = b.addRunArtifact(lib_tests);
const test_step = b.step("test", "Run unit tests"); const test_step = b.step("test", "Run unit tests");
test_step.dependOn(&run_lib_tests.step); test_step.dependOn(&run_lib_tests.step);
} }
@@ -0,0 +1,3 @@
pub fn brokenFactory() void {
const value =
}
@@ -29,4 +29,4 @@ pub fn main() !void {
pub fn greeting(name: []const u8) []const u8 { pub fn greeting(name: []const u8) []const u8 {
return std.fmt.allocPrint(std.heap.page_allocator, "Hello, {s}!", .{name}) catch "Hello!"; return std.fmt.allocPrint(std.heap.page_allocator, "Hello, {s}!", .{name}) catch "Hello!";
} }
@@ -68,13 +68,13 @@
# name: test_delete_symbol[test_case1] # name: test_delete_symbol[test_case1]
''' '''
import { ConsoleGreeter, Greeter } from "./formatters"; import { ConsoleGreeter, Greeter } from "./formatters";
export function helperFunction() { export function helperFunction() {
const demo = new DemoClass(42); const demo = new DemoClass(42);
demo.printValue(); demo.printValue();
const greeter: Greeter = new ConsoleGreeter(); const greeter: Greeter = new ConsoleGreeter();
console.log(greeter.formatGreeting("World")); console.log(greeter.formatGreeting("World"));
} }
@@ -521,7 +521,7 @@
# name: test_insert_in_rel_to_symbol[test_case1-after] # name: test_insert_in_rel_to_symbol[test_case1-after]
''' '''
import { ConsoleGreeter, Greeter } from "./formatters"; import { ConsoleGreeter, Greeter } from "./formatters";
export class DemoClass { export class DemoClass {
value: number; value: number;
constructor(value: number) { constructor(value: number) {
@@ -539,7 +539,7 @@
export function helperFunction() { export function helperFunction() {
const demo = new DemoClass(42); const demo = new DemoClass(42);
demo.printValue(); demo.printValue();
const greeter: Greeter = new ConsoleGreeter(); const greeter: Greeter = new ConsoleGreeter();
console.log(greeter.formatGreeting("World")); console.log(greeter.formatGreeting("World"));
} }
@@ -555,7 +555,7 @@
# name: test_insert_in_rel_to_symbol[test_case1-before] # name: test_insert_in_rel_to_symbol[test_case1-before]
''' '''
import { ConsoleGreeter, Greeter } from "./formatters"; import { ConsoleGreeter, Greeter } from "./formatters";
function newFunctionAfterClass(): void { function newFunctionAfterClass(): void {
console.log("This function is after DemoClass."); console.log("This function is after DemoClass.");
} }
@@ -573,7 +573,7 @@
export function helperFunction() { export function helperFunction() {
const demo = new DemoClass(42); const demo = new DemoClass(42);
demo.printValue(); demo.printValue();
const greeter: Greeter = new ConsoleGreeter(); const greeter: Greeter = new ConsoleGreeter();
console.log(greeter.formatGreeting("World")); console.log(greeter.formatGreeting("World"));
} }
@@ -589,7 +589,7 @@
# name: test_insert_in_rel_to_symbol[test_case2-after] # name: test_insert_in_rel_to_symbol[test_case2-after]
''' '''
import { ConsoleGreeter, Greeter } from "./formatters"; import { ConsoleGreeter, Greeter } from "./formatters";
export class DemoClass { export class DemoClass {
value: number; value: number;
constructor(value: number) { constructor(value: number) {
@@ -603,7 +603,7 @@
export function helperFunction() { export function helperFunction() {
const demo = new DemoClass(42); const demo = new DemoClass(42);
demo.printValue(); demo.printValue();
const greeter: Greeter = new ConsoleGreeter(); const greeter: Greeter = new ConsoleGreeter();
console.log(greeter.formatGreeting("World")); console.log(greeter.formatGreeting("World"));
} }
@@ -623,7 +623,7 @@
# name: test_insert_in_rel_to_symbol[test_case2-before] # name: test_insert_in_rel_to_symbol[test_case2-before]
''' '''
import { ConsoleGreeter, Greeter } from "./formatters"; import { ConsoleGreeter, Greeter } from "./formatters";
export class DemoClass { export class DemoClass {
value: number; value: number;
constructor(value: number) { constructor(value: number) {
@@ -641,7 +641,7 @@
export function helperFunction() { export function helperFunction() {
const demo = new DemoClass(42); const demo = new DemoClass(42);
demo.printValue(); demo.printValue();
const greeter: Greeter = new ConsoleGreeter(); const greeter: Greeter = new ConsoleGreeter();
console.log(greeter.formatGreeting("World")); console.log(greeter.formatGreeting("World"));
} }
@@ -1712,7 +1712,7 @@
# name: test_replace_body[test_case1] # name: test_replace_body[test_case1]
''' '''
import { ConsoleGreeter, Greeter } from "./formatters"; import { ConsoleGreeter, Greeter } from "./formatters";
export class DemoClass { export class DemoClass {
value: number; value: number;
constructor(value: number) { constructor(value: number) {
@@ -1727,7 +1727,7 @@
export function helperFunction() { export function helperFunction() {
const demo = new DemoClass(42); const demo = new DemoClass(42);
demo.printValue(); demo.printValue();
const greeter: Greeter = new ConsoleGreeter(); const greeter: Greeter = new ConsoleGreeter();
console.log(greeter.formatGreeting("World")); console.log(greeter.formatGreeting("World"));
} }
@@ -0,0 +1,17 @@
import pytest
from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language
from test.solidlsp.util.diagnostics import assert_file_diagnostics
@pytest.mark.bash
class TestBashDiagnostics:
@pytest.mark.parametrize("language_server", [Language.BASH], indirect=True)
def test_file_diagnostics(self, language_server: SolidLanguageServer) -> None:
assert_file_diagnostics(
language_server,
"diagnostics_sample.sh",
(),
min_count=1,
)
+2 -2
View File
@@ -13,8 +13,8 @@ def _test_clojure_cli() -> bool:
CLI_FAIL = _test_clojure_cli() CLI_FAIL = _test_clojure_cli()
TEST_APP_PATH = Path("src") / "test_app" TEST_APP_PATH = Path("src") / "test_app"
CORE_PATH = str(TEST_APP_PATH / "core.clj").replace("\\", "/") CORE_PATH = str(TEST_APP_PATH / "core.clj")
UTILS_PATH = str(TEST_APP_PATH / "utils.clj").replace("\\", "/") UTILS_PATH = str(TEST_APP_PATH / "utils.clj")
def is_clojure_cli_available() -> bool: def is_clojure_cli_available() -> bool:
@@ -0,0 +1,19 @@
import os
import pytest
from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language
from test.solidlsp.util.diagnostics import assert_file_diagnostics
@pytest.mark.clojure
class TestClojureDiagnostics:
@pytest.mark.parametrize("language_server", [Language.CLOJURE], indirect=True)
def test_file_diagnostics(self, language_server: SolidLanguageServer) -> None:
assert_file_diagnostics(
language_server,
os.path.join("src", "test_app", "diagnostics_sample.clj"),
("missing-greeting", "missing-consumer-value"),
min_count=2,
)
+9
View File
@@ -5,6 +5,12 @@ from solidlsp.ls_types import SymbolKind, UnifiedSymbolInformation
PYTHON_BACKEND_LANGUAGES = [Language.PYTHON, Language.PYTHON_TY] PYTHON_BACKEND_LANGUAGES = [Language.PYTHON, Language.PYTHON_TY]
def is_diagnostics_test_file(relative_path: str) -> bool:
normalized_path = relative_path.replace("\\", "/")
filename = normalized_path.rsplit("/", 1)[-1].lower()
return filename.startswith(("diagnosticssample.", "diagnostics_sample."))
def has_malformed_name( def has_malformed_name(
symbol: UnifiedSymbolInformation, symbol: UnifiedSymbolInformation,
whitespace_allowed: bool = False, whitespace_allowed: bool = False,
@@ -36,6 +42,9 @@ def request_all_symbols(language_server: SolidLanguageServer) -> list[UnifiedSym
result: list[UnifiedSymbolInformation] = [] result: list[UnifiedSymbolInformation] = []
def visit(symbol: UnifiedSymbolInformation) -> None: def visit(symbol: UnifiedSymbolInformation) -> None:
relative_path = symbol.get("location", {}).get("relativePath", "")
if relative_path and is_diagnostics_test_file(relative_path):
return
result.append(symbol) result.append(symbol)
for child in symbol.get("children", []): for child in symbol.get("children", []):
visit(child) visit(child)
+17
View File
@@ -0,0 +1,17 @@
import pytest
from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language
from test.solidlsp.util.diagnostics import assert_file_diagnostics
@pytest.mark.cpp
class TestCppDiagnostics:
@pytest.mark.parametrize("language_server", [Language.CPP], indirect=True)
def test_file_diagnostics(self, language_server: SolidLanguageServer) -> None:
assert_file_diagnostics(
language_server,
"diagnostics_sample.cpp",
(),
min_count=1,
)
@@ -0,0 +1,23 @@
import shutil
import pytest
from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language
from test.solidlsp.util.diagnostics import assert_file_diagnostics
pytestmark = [
pytest.mark.crystal,
pytest.mark.skipif(shutil.which("crystalline") is None, reason="Crystalline is not installed"),
]
class TestCrystalDiagnostics:
@pytest.mark.parametrize("language_server", [Language.CRYSTAL], indirect=True)
def test_file_diagnostics(self, language_server: SolidLanguageServer) -> None:
assert_file_diagnostics(
language_server,
"src/diagnostics_sample.cr",
(),
min_count=1,
)
@@ -0,0 +1,17 @@
import pytest
from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language
from test.solidlsp.util.diagnostics import assert_file_diagnostics
@pytest.mark.csharp
class TestCsharpDiagnostics:
@pytest.mark.parametrize("language_server", [Language.CSHARP], indirect=True)
def test_file_diagnostics(self, language_server: SolidLanguageServer) -> None:
assert_file_diagnostics(
language_server,
"DiagnosticsSample.cs",
(),
min_count=1,
)
@@ -0,0 +1,17 @@
import pytest
from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language
from test.solidlsp.util.diagnostics import assert_file_diagnostics
@pytest.mark.dart
class TestDartDiagnostics:
@pytest.mark.parametrize("language_server", [Language.DART], indirect=True)
def test_file_diagnostics(self, language_server: SolidLanguageServer) -> None:
assert_file_diagnostics(
language_server,
"lib/diagnostics_sample.dart",
(),
min_count=1,
)
+10
View File
@@ -13,6 +13,7 @@ from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language from solidlsp.ls_config import Language
from solidlsp.ls_types import SymbolKind from solidlsp.ls_types import SymbolKind
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
from test.solidlsp.util.diagnostics import assert_file_diagnostics
from . import EXPERT_UNAVAILABLE, EXPERT_UNAVAILABLE_REASON from . import EXPERT_UNAVAILABLE, EXPERT_UNAVAILABLE_REASON
@@ -138,3 +139,12 @@ class TestElixirBasic:
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}", f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
pytrace=False, pytrace=False,
) )
@pytest.mark.parametrize("language_server", [Language.ELIXIR], indirect=True)
def test_file_diagnostics(self, language_server: SolidLanguageServer) -> None:
assert_file_diagnostics(
language_server,
"lib/diagnostics_sample.ex",
(),
min_count=1,
)
+20
View File
@@ -0,0 +1,20 @@
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.elm
@pytest.mark.skipif(shutil.which("node") is None or shutil.which("npm") is None, reason="Elm diagnostics require Node.js and npm")
class TestElmDiagnostics:
@pytest.mark.parametrize("language_server", [Language.ELM], indirect=True)
def test_file_diagnostics(self, language_server: SolidLanguageServer) -> None:
assert_file_diagnostics(
language_server,
"DiagnosticsSample.elm",
(),
min_count=1,
)
+10
View File
@@ -10,6 +10,7 @@ import pytest
from solidlsp import SolidLanguageServer from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language from solidlsp.ls_config import Language
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
from test.solidlsp.util.diagnostics import assert_file_diagnostics
from . import ERLANG_LS_UNAVAILABLE, ERLANG_LS_UNAVAILABLE_REASON from . import ERLANG_LS_UNAVAILABLE, ERLANG_LS_UNAVAILABLE_REASON
@@ -55,3 +56,12 @@ class TestErlangLanguageServerBasics:
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}", f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
pytrace=False, pytrace=False,
) )
@pytest.mark.parametrize("language_server", [Language.ERLANG], indirect=True)
def test_file_diagnostics(self, language_server: SolidLanguageServer) -> None:
assert_file_diagnostics(
language_server,
"src/diagnostics_sample.erl",
(),
min_count=1,
)
@@ -0,0 +1,17 @@
import pytest
from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language
from test.solidlsp.util.diagnostics import assert_file_diagnostics
@pytest.mark.fortran
class TestFortranDiagnostics:
@pytest.mark.parametrize("language_server", [Language.FORTRAN], indirect=True)
def test_file_diagnostics(self, language_server: SolidLanguageServer) -> None:
assert_file_diagnostics(
language_server,
"diagnostics_sample.f90",
(),
min_count=1,
)
+8 -15
View File
@@ -9,6 +9,7 @@ from solidlsp.ls_config import Language
from solidlsp.ls_utils import SymbolUtils from solidlsp.ls_utils import SymbolUtils
from test.conftest import find_identifier_position, get_repo_path, is_ci, language_has_verified_implementation_support from test.conftest import find_identifier_position, get_repo_path, is_ci, language_has_verified_implementation_support
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
from test.solidlsp.util.diagnostics import assert_file_diagnostics
# Currently, most F# tests fail, there seems to be a regression or instability. # Currently, most F# tests fail, there seems to be a regression or instability.
@@ -196,21 +197,13 @@ class TestFSharpLanguageServer:
assert isinstance(result["value"], list), "Completions should be a list" assert isinstance(result["value"], list), "Completions should be a list"
@pytest.mark.parametrize("language_server", [Language.FSHARP], indirect=True) @pytest.mark.parametrize("language_server", [Language.FSHARP], indirect=True)
def test_diagnostics(self, language_server: SolidLanguageServer) -> None: def test_file_diagnostics(self, language_server: SolidLanguageServer) -> None:
"""Test getting diagnostics (errors, warnings) from F# files.""" assert_file_diagnostics(
file_path = os.path.join("Program.fs") language_server,
"DiagnosticsSample.fs",
# FsAutoComplete uses publishDiagnostics notifications instead of textDocument/diagnostic requests (),
# So we'll test that the language server can handle files without crashing min_count=1,
# In real usage, diagnostics would come through the publishDiagnostics notification handler )
# Test that we can at least work with the file (open/close cycle)
with language_server.open_file(file_path) as _:
# If we can open and close the file without errors, basic diagnostics support is working
pass
# This is a successful test - FsAutoComplete is working with F# files
assert True, "F# language server can handle files successfully"
@pytest.mark.parametrize("language_server", [Language.FSHARP], indirect=True) @pytest.mark.parametrize("language_server", [Language.FSHARP], indirect=True)
def test_bare_symbol_names(self, language_server) -> None: def test_bare_symbol_names(self, language_server) -> None:
+17
View File
@@ -0,0 +1,17 @@
import pytest
from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language
from test.solidlsp.util.diagnostics import assert_file_diagnostics
@pytest.mark.go
class TestGoDiagnostics:
@pytest.mark.parametrize("language_server", [Language.GO], indirect=True)
def test_file_diagnostics(self, language_server: SolidLanguageServer) -> None:
assert_file_diagnostics(
language_server,
"diagnostics_sample.go",
("missingGreeting", "missingConsumerValue"),
min_count=2,
)
+10
View File
@@ -9,6 +9,7 @@ from solidlsp.ls_config import Language, LanguageServerConfig
from solidlsp.ls_utils import SymbolUtils from solidlsp.ls_utils import SymbolUtils
from solidlsp.settings import SolidLSPSettings from solidlsp.settings import SolidLSPSettings
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
from test.solidlsp.util.diagnostics import assert_file_diagnostics
@pytest.mark.groovy @pytest.mark.groovy
@@ -117,3 +118,12 @@ class TestGroovyLanguageServer:
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}", f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
pytrace=False, pytrace=False,
) )
def test_file_diagnostics(self) -> None:
assert self.language_server is not None
assert_file_diagnostics(
self.language_server,
"src/main/groovy/com/example/DiagnosticsSample.groovy",
(),
min_count=1,
)
@@ -21,6 +21,7 @@ from solidlsp.ls import SolidLanguageServer
from solidlsp.ls_config import Language from solidlsp.ls_config import Language
from solidlsp.ls_types import SymbolKind from solidlsp.ls_types import SymbolKind
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
from test.solidlsp.util.diagnostics import assert_file_diagnostics
@pytest.mark.haskell @pytest.mark.haskell
@@ -240,3 +241,12 @@ class TestHaskellLanguageServer:
for sym in malformed_symbols for sym in malformed_symbols
] ]
pytest.fail(f"Found malformed symbols: {diagnostics}", pytrace=False) pytest.fail(f"Found malformed symbols: {diagnostics}", pytrace=False)
@pytest.mark.parametrize("language_server", [Language.HASKELL], indirect=True)
def test_file_diagnostics(self, language_server: SolidLanguageServer) -> None:
assert_file_diagnostics(
language_server,
"src/DiagnosticsSample.hs",
(),
min_count=1,
)
@@ -0,0 +1,17 @@
import pytest
from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language
from test.solidlsp.util.diagnostics import assert_file_diagnostics
@pytest.mark.haxe
class TestHaxeDiagnostics:
@pytest.mark.parametrize("language_server", [Language.HAXE], indirect=True)
def test_file_diagnostics(self, language_server: SolidLanguageServer) -> None:
assert_file_diagnostics(
language_server,
"src/DiagnosticsSample.hx",
(),
min_count=1,
)
@@ -0,0 +1,17 @@
import pytest
from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language
from test.solidlsp.util.diagnostics import assert_file_diagnostics
@pytest.mark.hlsl
class TestHlslDiagnostics:
@pytest.mark.parametrize("language_server", [Language.HLSL], indirect=True)
def test_file_diagnostics(self, language_server: SolidLanguageServer) -> None:
assert_file_diagnostics(
language_server,
"diagnostics_sample.hlsl",
(),
min_count=1,
)
@@ -0,0 +1,17 @@
import pytest
from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language
from test.solidlsp.util.diagnostics import assert_file_diagnostics
@pytest.mark.java
class TestJavaDiagnostics:
@pytest.mark.parametrize("language_server", [Language.JAVA], indirect=True)
def test_file_diagnostics(self, language_server: SolidLanguageServer) -> None:
assert_file_diagnostics(
language_server,
"src/main/java/test_repo/DiagnosticsSample.java",
(),
min_count=1,
)
@@ -0,0 +1,17 @@
import pytest
from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language
from test.solidlsp.util.diagnostics import assert_file_diagnostics
@pytest.mark.json
class TestJsonDiagnostics:
@pytest.mark.parametrize("language_server", [Language.JSON], indirect=True)
def test_file_diagnostics(self, language_server: SolidLanguageServer) -> None:
assert_file_diagnostics(
language_server,
"diagnostics_sample.json",
(),
min_count=1,
)
+10
View File
@@ -6,6 +6,7 @@ from solidlsp.ls import SolidLanguageServer
from solidlsp.ls_config import Language from solidlsp.ls_config import Language
from test.conftest import is_ci from test.conftest import is_ci
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
from test.solidlsp.util.diagnostics import assert_file_diagnostics
@pytest.mark.skipif(shutil.which("julia") is None and not is_ci, reason="Julia is not available") @pytest.mark.skipif(shutil.which("julia") is None and not is_ci, reason="Julia is not available")
@@ -66,3 +67,12 @@ class TestJuliaLanguageServer:
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}", f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
pytrace=False, pytrace=False,
) )
@pytest.mark.parametrize("language_server", [Language.JULIA], indirect=True)
def test_file_diagnostics(self, language_server: SolidLanguageServer) -> None:
assert_file_diagnostics(
language_server,
"diagnostics_sample.jl",
(),
min_count=1,
)
@@ -0,0 +1,17 @@
import pytest
from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language
from test.solidlsp.util.diagnostics import assert_file_diagnostics
@pytest.mark.kotlin
class TestKotlinDiagnostics:
@pytest.mark.parametrize("language_server", [Language.KOTLIN], indirect=True)
def test_file_diagnostics(self, language_server: SolidLanguageServer) -> None:
assert_file_diagnostics(
language_server,
"src/main/kotlin/test_repo/DiagnosticsSample.kt",
(),
min_count=1,
)
@@ -0,0 +1,17 @@
import pytest
from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language
from test.solidlsp.util.diagnostics import assert_file_diagnostics
@pytest.mark.lean4
class TestLean4Diagnostics:
@pytest.mark.parametrize("language_server", [Language.LEAN4], indirect=True)
def test_file_diagnostics(self, language_server: SolidLanguageServer) -> None:
assert_file_diagnostics(
language_server,
"DiagnosticsSample.lean",
(),
min_count=1,
)
+10
View File
@@ -17,6 +17,7 @@ import pytest
from solidlsp import SolidLanguageServer from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language from solidlsp.ls_config import Language
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
from test.solidlsp.util.diagnostics import assert_file_diagnostics
# Skip all tests if MATLAB is not available # Skip all tests if MATLAB is not available
pytestmark = pytest.mark.matlab pytestmark = pytest.mark.matlab
@@ -129,3 +130,12 @@ class TestMatlabLanguageServerReferences:
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}", f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
pytrace=False, pytrace=False,
) )
@pytest.mark.parametrize("language_server", [Language.MATLAB], indirect=True)
def test_file_diagnostics(self, language_server: SolidLanguageServer) -> None:
assert_file_diagnostics(
language_server,
"diagnostics_sample.m",
(),
min_count=1,
)
+10
View File
@@ -13,6 +13,7 @@ from solidlsp.ls_config import Language
from solidlsp.ls_types import SymbolKind from solidlsp.ls_types import SymbolKind
from test.conftest import is_ci from test.conftest import is_ci
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
from test.solidlsp.util.diagnostics import assert_file_diagnostics
# Skip all Nix tests on Windows as Nix doesn't support Windows # Skip all Nix tests on Windows as Nix doesn't support Windows
pytestmark = pytest.mark.skipif(platform.system() == "Windows", reason="Nix and nil are not available on Windows") pytestmark = pytest.mark.skipif(platform.system() == "Windows", reason="Nix and nil are not available on Windows")
@@ -250,3 +251,12 @@ class TestNixLanguageServer:
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}", f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
pytrace=False, pytrace=False,
) )
@pytest.mark.parametrize("language_server", [Language.NIX], indirect=True)
def test_file_diagnostics(self, language_server: SolidLanguageServer) -> None:
assert_file_diagnostics(
language_server,
"diagnostics_sample.nix",
(),
min_count=1,
)
@@ -0,0 +1,17 @@
import pytest
from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language
from test.solidlsp.util.diagnostics import assert_file_diagnostics
@pytest.mark.ocaml
class TestOcamlDiagnostics:
@pytest.mark.parametrize("language_server", [Language.OCAML], indirect=True)
def test_file_diagnostics(self, language_server: SolidLanguageServer) -> None:
assert_file_diagnostics(
language_server,
"lib/diagnostics_sample.ml",
(),
min_count=1,
)
@@ -0,0 +1,25 @@
import shutil
import pytest
from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language
from test.conftest import language_tests_enabled
from test.solidlsp.util.diagnostics import assert_file_diagnostics
pytestmark = [
pytest.mark.pascal,
pytest.mark.skipif(not language_tests_enabled(Language.PASCAL), reason="Pascal tests are disabled"),
pytest.mark.skipif(shutil.which("fpc") is None, reason="Pascal diagnostics require the Free Pascal compiler"),
]
class TestPascalDiagnostics:
@pytest.mark.parametrize("language_server", [Language.PASCAL], indirect=True)
def test_file_diagnostics(self, language_server: SolidLanguageServer) -> None:
assert_file_diagnostics(
language_server,
"diagnostics_sample.pas",
(),
min_count=1,
)
+10
View File
@@ -6,6 +6,7 @@ import pytest
from solidlsp import SolidLanguageServer from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language from solidlsp.ls_config import Language
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
from test.solidlsp.util.diagnostics import assert_file_diagnostics
@pytest.mark.perl @pytest.mark.perl
@@ -88,3 +89,12 @@ class TestPerlLanguageServer:
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}", f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
pytrace=False, pytrace=False,
) )
@pytest.mark.parametrize("language_server", [Language.PERL], indirect=True)
def test_file_diagnostics(self, language_server: SolidLanguageServer) -> None:
assert_file_diagnostics(
language_server,
"diagnostics_sample.pl",
(),
min_count=1,
)
+17
View File
@@ -0,0 +1,17 @@
import pytest
from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language
from test.solidlsp.util.diagnostics import assert_file_diagnostics
@pytest.mark.php
class TestPhpDiagnostics:
@pytest.mark.parametrize("language_server", [Language.PHP], indirect=True)
def test_file_diagnostics(self, language_server: SolidLanguageServer) -> None:
assert_file_diagnostics(
language_server,
"diagnostics_sample.php",
(),
min_count=1,
)
@@ -0,0 +1,17 @@
import pytest
from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language
from test.solidlsp.util.diagnostics import assert_file_diagnostics
@pytest.mark.powershell
class TestPowershellDiagnostics:
@pytest.mark.parametrize("language_server", [Language.POWERSHELL], indirect=True)
def test_file_diagnostics(self, language_server: SolidLanguageServer) -> None:
assert_file_diagnostics(
language_server,
"diagnostics_sample.ps1",
(),
min_count=1,
)
@@ -0,0 +1,19 @@
import os
import pytest
from solidlsp import SolidLanguageServer
from test.conftest import PYTHON_LANGUAGE_BACKENDS
from test.solidlsp.util.diagnostics import assert_file_diagnostics
@pytest.mark.python
class TestPythonDiagnostics:
@pytest.mark.parametrize("language_server", PYTHON_LANGUAGE_BACKENDS, indirect=True)
def test_file_diagnostics(self, language_server: SolidLanguageServer) -> None:
assert_file_diagnostics(
language_server,
os.path.join("test_repo", "diagnostics_sample.py"),
("missing_user", "undefined_name"),
min_count=2,
)
+1 -1
View File
@@ -90,7 +90,7 @@ class TestRustLanguageServer:
all_symbols = request_all_symbols(language_server) all_symbols = request_all_symbols(language_server)
malformed_symbols = [] malformed_symbols = []
for s in all_symbols: for s in all_symbols:
if has_malformed_name(s): if has_malformed_name(s, whitespace_allowed=True):
malformed_symbols.append(s) malformed_symbols.append(s)
if malformed_symbols: if malformed_symbols:
pytest.fail( pytest.fail(
@@ -0,0 +1,17 @@
import pytest
from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language
from test.solidlsp.util.diagnostics import assert_file_diagnostics
@pytest.mark.rust
class TestRustDiagnostics:
@pytest.mark.parametrize("language_server", [Language.RUST], indirect=True)
def test_file_diagnostics(self, language_server: SolidLanguageServer) -> None:
assert_file_diagnostics(
language_server,
"src/diagnostics_sample.rs",
(),
min_count=1,
)
@@ -6,6 +6,7 @@ import pytest
from solidlsp.language_servers.scala_language_server import ScalaLanguageServer from solidlsp.language_servers.scala_language_server import ScalaLanguageServer
from solidlsp.ls_config import Language, LanguageServerConfig from solidlsp.ls_config import Language, LanguageServerConfig
from solidlsp.settings import SolidLSPSettings from solidlsp.settings import SolidLSPSettings
from test.solidlsp.util.diagnostics import assert_file_diagnostics
pytest.skip("Scala must be compiled for these tests to run through, which is a huge hassle", allow_module_level=True) pytest.skip("Scala must be compiled for these tests to run through, which is a huge hassle", allow_module_level=True)
@@ -62,3 +63,12 @@ def test_scala_find_definition_and_references_across_files(scala_ls):
assert first_def["range"]["start"]["character"] == 6 assert first_def["range"]["start"]["character"] == 6
assert first_def["range"]["end"]["line"] == 7 assert first_def["range"]["end"]["line"] == 7
assert first_def["range"]["end"]["character"] == 14 assert first_def["range"]["end"]["character"] == 14
def test_file_diagnostics(scala_ls) -> None:
assert_file_diagnostics(
scala_ls,
"src/main/scala/com/example/DiagnosticsSample.scala",
(),
min_count=1,
)
@@ -0,0 +1,24 @@
import sys
import pytest
from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language
from test.solidlsp.util.diagnostics import assert_file_diagnostics
@pytest.mark.solidity
class TestSolidityDiagnostics:
@pytest.mark.xfail(
sys.platform == "darwin",
reason="Unknown — passes on Ubuntu but consistently fails on macOS CI; root cause not yet identified.",
strict=False,
)
@pytest.mark.parametrize("language_server", [Language.SOLIDITY], indirect=True)
def test_file_diagnostics(self, language_server: SolidLanguageServer) -> None:
assert_file_diagnostics(
language_server,
"contracts/DiagnosticsSample.sol",
(),
min_count=1,
)
+10
View File
@@ -16,6 +16,7 @@ from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language from solidlsp.ls_config import Language
from test.conftest import is_ci from test.conftest import is_ci
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
from test.solidlsp.util.diagnostics import assert_file_diagnostics
# Skip Swift tests on Windows due to complex GitHub Actions configuration # Skip Swift tests on Windows due to complex GitHub Actions configuration
WINDOWS_SKIP = platform.system() == "Windows" WINDOWS_SKIP = platform.system() == "Windows"
@@ -238,3 +239,12 @@ class TestSwiftProjectBasics:
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}", f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
pytrace=False, pytrace=False,
) )
@pytest.mark.parametrize("language_server", [Language.SWIFT], indirect=True)
def test_file_diagnostics(self, language_server: SolidLanguageServer) -> None:
assert_file_diagnostics(
language_server,
"src/diagnostics_sample.swift",
(),
min_count=1,
)
@@ -0,0 +1,17 @@
import pytest
from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language
from test.solidlsp.util.diagnostics import assert_file_diagnostics
@pytest.mark.systemverilog
class TestSystemverilogDiagnostics:
@pytest.mark.parametrize("language_server", [Language.SYSTEMVERILOG], indirect=True)
def test_file_diagnostics(self, language_server: SolidLanguageServer) -> None:
assert_file_diagnostics(
language_server,
"diagnostics_sample.sv",
(),
min_count=1,
)
@@ -0,0 +1,17 @@
import pytest
from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language
from test.solidlsp.util.diagnostics import assert_file_diagnostics
@pytest.mark.terraform
class TestTerraformDiagnostics:
@pytest.mark.parametrize("language_server", [Language.TERRAFORM], indirect=True)
def test_file_diagnostics(self, language_server: SolidLanguageServer) -> None:
assert_file_diagnostics(
language_server,
"diagnostics_sample.tf",
(),
min_count=1,
)
@@ -0,0 +1,17 @@
import pytest
from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language
from test.solidlsp.util.diagnostics import assert_file_diagnostics
@pytest.mark.toml
class TestTomlDiagnostics:
@pytest.mark.parametrize("language_server", [Language.TOML], indirect=True)
def test_file_diagnostics(self, language_server: SolidLanguageServer) -> None:
assert_file_diagnostics(
language_server,
"diagnostics_sample.toml",
(),
min_count=1,
)
@@ -0,0 +1,17 @@
import pytest
from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language
from test.solidlsp.util.diagnostics import assert_file_diagnostics
@pytest.mark.typescript
class TestTypeScriptDiagnostics:
@pytest.mark.parametrize("language_server", [Language.TYPESCRIPT], indirect=True)
def test_file_diagnostics(self, language_server: SolidLanguageServer) -> None:
assert_file_diagnostics(
language_server,
"diagnostics_sample.ts",
("missingGreeting", "missingConsumerValue"),
min_count=2,
)
+19
View File
@@ -0,0 +1,19 @@
from collections.abc import Sequence
from solidlsp import SolidLanguageServer
def assert_file_diagnostics(
language_server: SolidLanguageServer,
relative_file_path: str,
expected_message_fragments: Sequence[str],
min_count: int = 1,
) -> None:
diagnostics = language_server.request_text_document_diagnostics(relative_file_path, min_severity=1)
assert isinstance(diagnostics, list), diagnostics
assert len(diagnostics) >= min_count, diagnostics
diagnostic_messages = [diagnostic["message"] for diagnostic in diagnostics]
for fragment in expected_message_fragments:
assert any(fragment in message for message in diagnostic_messages), diagnostic_messages
+17
View File
@@ -0,0 +1,17 @@
import pytest
from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language
from test.solidlsp.util.diagnostics import assert_file_diagnostics
@pytest.mark.vue
class TestVueDiagnostics:
@pytest.mark.parametrize("language_server", [Language.VUE], indirect=True)
def test_file_diagnostics(self, language_server: SolidLanguageServer) -> None:
assert_file_diagnostics(
language_server,
"src/DiagnosticsSample.vue",
(),
min_count=1,
)
@@ -0,0 +1,17 @@
import pytest
from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language
from test.solidlsp.util.diagnostics import assert_file_diagnostics
@pytest.mark.yaml
class TestYamlDiagnostics:
@pytest.mark.parametrize("language_server", [Language.YAML], indirect=True)
def test_file_diagnostics(self, language_server: SolidLanguageServer) -> None:
assert_file_diagnostics(
language_server,
"diagnostics_sample.yaml",
(),
min_count=1,
)
+10
View File
@@ -14,6 +14,7 @@ from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language from solidlsp.ls_config import Language
from solidlsp.ls_types import SymbolKind from solidlsp.ls_types import SymbolKind
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
from test.solidlsp.util.diagnostics import assert_file_diagnostics
@pytest.mark.zig @pytest.mark.zig
@@ -355,3 +356,12 @@ class TestZigLanguageServer:
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}", f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
pytrace=False, pytrace=False,
) )
@pytest.mark.parametrize("language_server", [Language.ZIG], indirect=True)
def test_file_diagnostics(self, language_server: SolidLanguageServer) -> None:
assert_file_diagnostics(
language_server,
"src/diagnostics_sample.zig",
(),
min_count=1,
)