Skip to content

Commit 41156fb

Browse files
nisedodguidoclaude
authored
Migrate to pygls v2 (drop v1 support) (#25)
- Update pygls dependency to >=2.0.0 - Update imports from pygls.server to pygls.lsp.server - Update method calls to use v2 params-based API: - show_message() -> window_show_message(ShowMessageParams) - show_message_log() -> window_log_message(LogMessageParams) - publish_diagnostics() -> text_document_publish_diagnostics(PublishDiagnosticsParams) - Update tests for new API Co-authored-by: Dan Guido <[email protected]> Co-authored-by: Claude Opus 4.5 <[email protected]>
1 parent 78ba80b commit 41156fb

File tree

8 files changed

+51
-49
lines changed

8 files changed

+51
-49
lines changed

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ readme = "README.md"
1010
dependencies = [
1111
"slither-analyzer>=0.10.2",
1212
"semantic-version>=2.10.0",
13-
"pygls>=1.3.0",
13+
"pygls>=2.0.0",
1414
]
1515
classifiers = [
1616
"License :: OSI Approved :: GNU Affero General Public License v3",

slither_lsp/app/feature_analyses/slither_diagnostics.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -114,9 +114,7 @@ def update(
114114

115115
# Loop for each diagnostic and broadcast all of them.
116116
for diagnostic_params in self.diagnostics.values():
117-
self.context.publish_diagnostics(
118-
diagnostic_params.uri, diagnostics=diagnostic_params.diagnostics
119-
)
117+
self.context.text_document_publish_diagnostics(diagnostic_params)
120118

121119
def _clear_single(self, file_uri: str, clear_from_lookup: bool = False) -> None:
122120
"""
@@ -129,7 +127,9 @@ def _clear_single(self, file_uri: str, clear_from_lookup: bool = False) -> None:
129127
:return: None
130128
"""
131129
# Send empty diagnostics for this file to the client.
132-
self.context.publish_diagnostics(file_uri, diagnostics=[])
130+
self.context.text_document_publish_diagnostics(
131+
lsp.PublishDiagnosticsParams(uri=file_uri, diagnostics=[])
132+
)
133133

134134
# Optionally clear this item from the diagnostic lookup
135135
if clear_from_lookup:

slither_lsp/app/logging/lsp_handler.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from logging import DEBUG, ERROR, FATAL, INFO, NOTSET, WARNING, Handler, LogRecord
22

3-
from lsprotocol.types import MessageType
4-
from pygls.server import LanguageServer
3+
from lsprotocol.types import LogMessageParams, MessageType
4+
from pygls.lsp.server import LanguageServer
55

66
_level_to_type = {
77
FATAL: MessageType.Error,
@@ -25,4 +25,4 @@ def __init__(self, server: LanguageServer):
2525
def emit(self, record: LogRecord):
2626
msg = self.format(record)
2727
msg_type = _level_to_type[record.levelno]
28-
self.server.show_message_log(msg, msg_type=msg_type)
28+
self.server.window_log_message(LogMessageParams(type=msg_type, message=msg))

slither_lsp/app/request_handlers/analysis/get_detector_list.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from pygls.server import LanguageServer
1+
from pygls.lsp.server import LanguageServer
22
from slither.__main__ import get_detectors_and_printers, output_detectors_json
33

44

slither_lsp/app/request_handlers/analysis/get_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from importlib.metadata import version as pkg_version
22

3-
from pygls.server import LanguageServer
3+
from pygls.lsp.server import LanguageServer
44

55

66
def get_version(ls: LanguageServer, params):

slither_lsp/app/request_handlers/compilation/get_command_line_args.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from argparse import ArgumentParser
22

33
from crytic_compile.cryticparser.cryticparser import init as crytic_parser_init
4-
from pygls.server import LanguageServer
4+
from pygls.lsp.server import LanguageServer
55

66

77
def get_command_line_args(ls: LanguageServer, params):

slither_lsp/app/slither_server.py

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88
import lsprotocol.types as lsp
99
from crytic_compile.crytic_compile import CryticCompile
1010
from pygls.lsp import METHOD_TO_OPTIONS
11+
from pygls.lsp.server import LanguageServer
1112
from pygls.protocol import LanguageServerProtocol
12-
from pygls.server import LanguageServer
1313
from slither import Slither
1414
from slither.__main__ import (
1515
_process as process_detectors_and_printers,
@@ -98,7 +98,9 @@ def on_initialize(ls: SlitherServer, params):
9898

9999
@self.feature(lsp.INITIALIZED)
100100
def on_initialized(ls: SlitherServer, params):
101-
ls.show_message("slither-lsp initialized", lsp.MessageType.Debug)
101+
ls.window_show_message(
102+
lsp.ShowMessageParams(type=lsp.MessageType.Debug, message="slither-lsp initialized")
103+
)
102104

103105
@self.thread()
104106
@self.feature(lsp.WORKSPACE_DID_CHANGE_WORKSPACE_FOLDERS)
@@ -160,9 +162,11 @@ def _on_analyze(self, params: AnalysisRequestParams):
160162
path = uri_to_fs_path(uri)
161163
workspace_name = split(path)[1]
162164
if self.workspace_in_progress[uri].locked():
163-
self.show_message(
164-
f"Analysis for {workspace_name} is already in progress",
165-
lsp.MessageType.Warning,
165+
self.window_show_message(
166+
lsp.ShowMessageParams(
167+
type=lsp.MessageType.Warning,
168+
message=f"Analysis for {workspace_name} is already in progress",
169+
)
166170
)
167171
continue
168172
self.queue_compile_workspace(uri)
@@ -177,9 +181,11 @@ def queue_compile_workspace(self, uri: str):
177181
def do_compile():
178182
detector_classes, _ = get_detectors_and_printers()
179183
with self.workspace_in_progress[uri]:
180-
self.show_message(
181-
f"Compilation for {workspace_name} has started",
182-
lsp.MessageType.Info,
184+
self.window_show_message(
185+
lsp.ShowMessageParams(
186+
type=lsp.MessageType.Info,
187+
message=f"Compilation for {workspace_name} has started",
188+
)
183189
)
184190
try:
185191
compilation = CryticCompile(path)
@@ -197,9 +203,11 @@ def do_compile():
197203
detector_results = None
198204
analyzed_successfully = True
199205
analysis_error = None
200-
self.show_message(
201-
f"Compilation for {workspace_name} has completed successfully",
202-
lsp.MessageType.Info,
206+
self.window_show_message(
207+
lsp.ShowMessageParams(
208+
type=lsp.MessageType.Info,
209+
message=f"Compilation for {workspace_name} has completed successfully",
210+
)
203211
)
204212
except Exception as err:
205213
# If we encounter an error, set our status.
@@ -208,9 +216,9 @@ def do_compile():
208216
analyzed_successfully = False
209217
analysis_error = err
210218
detector_results = None
211-
self.show_message(
212-
f"Compilation for {workspace_name} has failed. See log for details.",
213-
lsp.MessageType.Info,
219+
msg = f"Compilation for {workspace_name} has failed. See log for details."
220+
self.window_show_message(
221+
lsp.ShowMessageParams(type=lsp.MessageType.Info, message=msg)
214222
)
215223
self._logger.log(logging.ERROR, "Compiling %s has failed: %s", path, err)
216224

tests/integration/test_slither_diagnostics.py

Lines changed: 18 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
def make_mock_server():
1818
"""Create a mock SlitherServer for testing."""
1919
server = MagicMock()
20-
server.publish_diagnostics = MagicMock()
20+
server.text_document_publish_diagnostics = MagicMock()
2121
return server
2222

2323

@@ -85,10 +85,11 @@ def test_publishes_diagnostics_for_detector_results(self):
8585
diagnostics.update([analysis], settings)
8686

8787
# Should publish diagnostics
88-
server.publish_diagnostics.assert_called()
89-
call_args = server.publish_diagnostics.call_args
90-
assert "test.sol" in call_args[0][0] # URI contains filename
91-
assert len(call_args[1]["diagnostics"]) == 1
88+
server.text_document_publish_diagnostics.assert_called()
89+
call_args = server.text_document_publish_diagnostics.call_args
90+
params = call_args[0][0] # PublishDiagnosticsParams object
91+
assert "test.sol" in params.uri # URI contains filename
92+
assert len(params.diagnostics) == 1
9293

9394
def test_filters_hidden_checks(self):
9495
server = make_mock_server()
@@ -110,10 +111,8 @@ def test_filters_hidden_checks(self):
110111
diagnostics.update([analysis], settings)
111112

112113
# Should not publish any diagnostics (check is hidden)
113-
# publish_diagnostics is called but with empty list
114-
if server.publish_diagnostics.called:
115-
call_args = server.publish_diagnostics.call_args
116-
assert len(call_args[1]["diagnostics"]) == 0
114+
# text_document_publish_diagnostics is not called when there are no diagnostics
115+
assert not server.text_document_publish_diagnostics.called
117116

118117
def test_disabled_detectors_produces_no_diagnostics(self):
119118
server = make_mock_server()
@@ -132,14 +131,7 @@ def test_disabled_detectors_produces_no_diagnostics(self):
132131
diagnostics.update([analysis], settings)
133132

134133
# Should not produce any diagnostics when disabled
135-
if server.publish_diagnostics.called:
136-
call_args = server.publish_diagnostics.call_args
137-
# Either not called or called with empty diagnostics
138-
assert (
139-
not call_args
140-
or "diagnostics" not in call_args[1]
141-
or len(call_args[1].get("diagnostics", [])) == 0
142-
)
134+
assert not server.text_document_publish_diagnostics.called
143135

144136
def test_skips_results_without_source_mapping(self):
145137
server = make_mock_server()
@@ -201,8 +193,9 @@ def test_diagnostic_message_format(self):
201193

202194
diagnostics.update([analysis], settings)
203195

204-
call_args = server.publish_diagnostics.call_args
205-
diag = call_args[1]["diagnostics"][0]
196+
call_args = server.text_document_publish_diagnostics.call_args
197+
params = call_args[0][0] # PublishDiagnosticsParams object
198+
diag = params.diagnostics[0]
206199
assert "[MEDIUM]" in diag.message
207200
assert "Low-level call" in diag.message
208201
assert diag.code == "unchecked-lowlevel"
@@ -226,8 +219,9 @@ def test_diagnostic_range_calculation(self):
226219

227220
diagnostics.update([analysis], settings)
228221

229-
call_args = server.publish_diagnostics.call_args
230-
diag = call_args[1]["diagnostics"][0]
222+
call_args = server.text_document_publish_diagnostics.call_args
223+
params = call_args[0][0] # PublishDiagnosticsParams object
224+
diag = params.diagnostics[0]
231225

232226
# Range should be 0-indexed
233227
assert diag.range.start.line == 9 # Line 10 -> 9
@@ -259,7 +253,7 @@ def test_multiple_files_get_separate_diagnostics(self):
259253
diagnostics.update([analysis], settings)
260254

261255
# Should be called twice (once per file)
262-
assert server.publish_diagnostics.call_count == 2
256+
assert server.text_document_publish_diagnostics.call_count == 2
263257

264258
def test_clears_old_diagnostics_on_update(self):
265259
server = make_mock_server()
@@ -277,10 +271,10 @@ def test_clears_old_diagnostics_on_update(self):
277271
diagnostics_obj.update([make_analysis_result([result])], settings)
278272

279273
# Reset mock
280-
server.publish_diagnostics.reset_mock()
274+
server.text_document_publish_diagnostics.reset_mock()
281275

282276
# Second update with no results
283277
diagnostics_obj.update([make_analysis_result([])], settings)
284278

285279
# Should clear diagnostics for the file that no longer has issues
286-
server.publish_diagnostics.assert_called()
280+
server.text_document_publish_diagnostics.assert_called()

0 commit comments

Comments
 (0)