Skip to content

Commit 2caa826

Browse files
committed
implement reporting unused keywords
1 parent 21b78a0 commit 2caa826

File tree

8 files changed

+207
-58
lines changed

8 files changed

+207
-58
lines changed

robotcode/language_server/common/lsp_types.py

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1045,7 +1045,7 @@ class LinkedEditingRangeOptions(WorkDoneProgressOptions):
10451045

10461046
@dataclass(repr=False)
10471047
class LinkedEditingRangeRegistrationOptions(
1048-
TextDocumentRegistrationOptions, LinkedEditingRangeOptions, StaticRegistrationOptions
1048+
LinkedEditingRangeOptions, StaticRegistrationOptions, TextDocumentRegistrationOptions
10491049
):
10501050
pass
10511051

@@ -1068,7 +1068,7 @@ class RenameOptions(WorkDoneProgressOptions):
10681068

10691069

10701070
@dataclass(repr=False)
1071-
class RenameRegistrationOptions(TextDocumentRegistrationOptions, RenameOptions, StaticRegistrationOptions):
1071+
class RenameRegistrationOptions(RenameOptions, StaticRegistrationOptions, TextDocumentRegistrationOptions):
10721072
pass
10731073

10741074

@@ -1078,7 +1078,24 @@ class InlineValueOptions(WorkDoneProgressOptions):
10781078

10791079

10801080
@dataclass(repr=False)
1081-
class InlineValueRegistrationOptions(InlineValueOptions, TextDocumentRegistrationOptions, StaticRegistrationOptions):
1081+
class InlineValueRegistrationOptions(TextDocumentRegistrationOptions, StaticRegistrationOptions, InlineValueOptions):
1082+
pass
1083+
1084+
1085+
@dataclass(repr=False)
1086+
class _DiagnosticOptions(Model):
1087+
inter_file_dependencies: bool
1088+
workspace_diagnostics: bool
1089+
identifier: Optional[str] = None
1090+
1091+
1092+
@dataclass(repr=False)
1093+
class DiagnosticOptions(WorkDoneProgressOptions, _DiagnosticOptions):
1094+
pass
1095+
1096+
1097+
@dataclass(repr=False)
1098+
class DiagnosticRegistrationOptions(TextDocumentRegistrationOptions, StaticRegistrationOptions, DiagnosticOptions):
10821099
pass
10831100

10841101

@@ -1115,6 +1132,9 @@ class ServerCapabilities(Model):
11151132

11161133
# TODO typeHierarchyProvider?: boolean | TypeHierarchyOptions | TypeHierarchyRegistrationOptions;
11171134
inline_value_provider: Union[bool, InlineValueOptions, InlineValueRegistrationOptions, None] = None
1135+
# TODO inlayHintProvider?: boolean | InlayHintOptions | InlayHintRegistrationOptions;
1136+
diagnostic_provider: Union[DiagnosticOptions, DiagnosticRegistrationOptions, None] = None
1137+
11181138
workspace_symbol_provider: Union[bool, WorkspaceSymbolOptions, None] = None
11191139
workspace: Optional[ServerCapabilitiesWorkspace] = None
11201140
experimental: Optional[Any] = None

robotcode/language_server/common/parts/diagnostics.py

Lines changed: 47 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@
77
from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, cast
88

99
from ....utils.async_tools import (
10+
Event,
1011
Lock,
1112
async_event,
13+
async_tasking_event,
1214
async_tasking_event_iterator,
1315
check_canceled,
1416
create_sub_task,
@@ -147,6 +149,8 @@ def __init__(self, protocol: LanguageServerProtocol) -> None:
147149
self.parent.documents.did_close.add(self.on_did_close)
148150
self.parent.documents.did_save.add(self.on_did_save)
149151

152+
self.workspace_diagnostics_running = Event()
153+
150154
@async_tasking_event_iterator
151155
async def collect(sender, document: TextDocument) -> DiagnosticsResult: # NOSONAR
152156
...
@@ -155,6 +159,10 @@ async def collect(sender, document: TextDocument) -> DiagnosticsResult: # NOSON
155159
async def collect_workspace_documents(sender) -> List[WorkspaceDocumentsResult]: # NOSONAR
156160
...
157161

162+
@async_tasking_event
163+
async def workspace_diagnostics_stopped(sender) -> None: # NOSONAR
164+
...
165+
158166
@_logger.call
159167
async def on_connection_lost(self, sender: Any, exc: Optional[BaseException]) -> None:
160168
await self._cancel_all_tasks()
@@ -203,6 +211,8 @@ async def on_did_append_document(self, sender: Any, document: TextDocument) -> N
203211
@language_id("robotframework")
204212
@_logger.call
205213
async def on_did_open(self, sender: Any, document: TextDocument) -> None:
214+
await self.workspace_diagnostics_running.wait()
215+
206216
await self.create_publish_document_diagnostics_task(document)
207217

208218
@language_id("robotframework")
@@ -300,7 +310,8 @@ async def start_publish_document_diagnostics_task(
300310
)
301311

302312
@_logger.call
303-
async def publish_document_diagnostics(self, document_uri: DocumentUri) -> None:
313+
async def publish_document_diagnostics(self, document_uri: DocumentUri, threaded: bool = True) -> None:
314+
304315
document = await self.parent.documents.get(document_uri)
305316
if document is None:
306317
return
@@ -314,6 +325,7 @@ async def publish_document_diagnostics(self, document_uri: DocumentUri) -> None:
314325
document,
315326
callback_filter=language_id_filter(document),
316327
return_exceptions=True,
328+
threaded=threaded,
317329
):
318330
await check_canceled()
319331

@@ -327,8 +339,7 @@ async def publish_document_diagnostics(self, document_uri: DocumentUri) -> None:
327339
diagnostics[result.key] = result.diagnostics if result.diagnostics else []
328340
collected_keys.append(result.key)
329341

330-
asyncio.get_event_loop().call_soon(
331-
self.parent.send_notification,
342+
self.parent.send_notification(
332343
"textDocument/publishDiagnostics",
333344
PublishDiagnosticsParams(
334345
uri=document.document_uri,
@@ -345,32 +356,36 @@ async def publish_document_diagnostics(self, document_uri: DocumentUri) -> None:
345356
@_logger.call
346357
async def publish_workspace_diagnostics(self) -> None:
347358
canceled = False
348-
349-
async for result_any in self.collect_workspace_documents(
350-
self,
351-
return_exceptions=True,
352-
):
353-
await check_canceled()
354-
if canceled:
355-
break
356-
357-
if isinstance(result_any, BaseException):
358-
if not isinstance(result_any, asyncio.CancelledError):
359-
self._logger.exception(result_any, exc_info=result_any)
360-
continue
361-
362-
result = cast(List[WorkspaceDocumentsResult], result_any)
363-
async with self.parent.window.progress(
364-
"Analyze workspace",
365-
cancellable=True,
366-
current=0,
367-
max=len(result),
368-
) as progress:
369-
for i, v in enumerate(result):
370-
if progress.is_canceled:
371-
canceled = True
372-
break
373-
374-
progress.report(f"Analyze {v.name}" if v.name else None, cancellable=False, current=i + 1)
375-
376-
await self.publish_document_diagnostics(str(v.document.uri))
359+
self.workspace_diagnostics_running.clear()
360+
try:
361+
async for result_any in self.collect_workspace_documents(
362+
self,
363+
return_exceptions=True,
364+
):
365+
await check_canceled()
366+
if canceled:
367+
break
368+
369+
if isinstance(result_any, BaseException):
370+
if not isinstance(result_any, asyncio.CancelledError):
371+
self._logger.exception(result_any, exc_info=result_any)
372+
continue
373+
374+
result = cast(List[WorkspaceDocumentsResult], result_any)
375+
async with self.parent.window.progress(
376+
"Analyze workspace",
377+
cancellable=True,
378+
current=0,
379+
max=len(result),
380+
) as progress:
381+
for i, v in enumerate(result):
382+
if progress.is_canceled:
383+
canceled = True
384+
break
385+
386+
progress.report(f"Analyze {v.name}" if v.name else None, cancellable=False, current=i + 1)
387+
388+
await self.publish_document_diagnostics(str(v.document.uri))
389+
finally:
390+
self.workspace_diagnostics_running.set()
391+
await self.workspace_diagnostics_stopped(self)

robotcode/language_server/common/parts/documents.py

Lines changed: 45 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,12 @@
1616
)
1717

1818
from ....jsonrpc2.protocol import JsonRPCException, rpc_method
19-
from ....utils.async_tools import Lock, async_event, async_tasking_event
19+
from ....utils.async_tools import (
20+
Lock,
21+
async_event,
22+
async_tasking_event,
23+
create_sub_task,
24+
)
2025
from ....utils.logging import LoggingDescriptor
2126
from ....utils.uri import Uri
2227
from ..decorators import language_id_filter
@@ -91,20 +96,28 @@ async def _file_watcher(self, sender: Any, changes: List[FileEvent]) -> None:
9196

9297
for change in to_change.values():
9398
if change.type == FileChangeType.CREATED:
94-
await self.did_create_uri(self, change.uri)
99+
create_sub_task(self.did_create_uri(self, change.uri), loop=self.parent.loop)
95100

96101
document = self._documents.get(DocumentUri(Uri(change.uri).normalized()), None)
97102
if document is not None and change.type == FileChangeType.CREATED:
98-
await self.did_create(self, document, callback_filter=language_id_filter(document))
103+
create_sub_task(
104+
self.did_create(self, document, callback_filter=language_id_filter(document)), loop=self.parent.loop
105+
)
99106
elif document is not None and not document.opened_in_editor:
100107
if change.type == FileChangeType.DELETED:
101108
await self.close_document(document, True)
102-
await self.did_close(self, document, callback_filter=language_id_filter(document))
109+
create_sub_task(
110+
self.did_close(self, document, callback_filter=language_id_filter(document)),
111+
loop=self.parent.loop,
112+
)
103113
elif change.type == FileChangeType.CHANGED:
104114
await document.apply_full_change(
105115
None, await self.read_document_text(document.uri, language_id_filter(document)), save=True
106116
)
107-
await self.did_change(self, document, callback_filter=language_id_filter(document))
117+
create_sub_task(
118+
self.did_change(self, document, callback_filter=language_id_filter(document)),
119+
loop=self.parent.loop,
120+
)
108121

109122
async def read_document_text(self, uri: Uri, language_id: Union[str, Callable[[Any], bool]]) -> str:
110123
for e in await self.on_read_document_text(
@@ -212,7 +225,10 @@ async def append_document(
212225

213226
self._documents[document_uri] = document
214227

215-
await self.did_append_document(self, document, callback_filter=language_id_filter(document))
228+
create_sub_task(
229+
self.did_append_document(self, document, callback_filter=language_id_filter(document)),
230+
loop=self.parent.loop,
231+
)
216232

217233
return document
218234

@@ -245,10 +261,15 @@ async def _text_document_did_open(self, text_document: TextDocumentItem, *args:
245261

246262
document.opened_in_editor = True
247263

248-
await self.did_open(self, document, callback_filter=language_id_filter(document))
264+
create_sub_task(
265+
self.did_open(self, document, callback_filter=language_id_filter(document)), loop=self.parent.loop
266+
)
249267

250268
if text_changed:
251-
await self.did_change(self, document, callback_filter=language_id_filter(document))
269+
create_sub_task(
270+
self.did_change(self, document, callback_filter=language_id_filter(document)),
271+
loop=self.parent.loop,
272+
)
252273

253274
@rpc_method(name="textDocument/didClose", param_type=DidCloseTextDocumentParams)
254275
@_logger.call
@@ -260,8 +281,9 @@ async def _text_document_did_close(self, text_document: TextDocumentIdentifier,
260281
document.opened_in_editor = False
261282

262283
await self.close_document(document)
263-
264-
await self.did_close(self, document, callback_filter=language_id_filter(document))
284+
create_sub_task(
285+
self.did_close(self, document, callback_filter=language_id_filter(document)), loop=self.parent.loop
286+
)
265287

266288
@_logger.call
267289
async def close_document(self, document: TextDocument, real_close: bool = False) -> None:
@@ -274,7 +296,9 @@ async def close_document(self, document: TextDocument, real_close: bool = False)
274296
else:
275297
document._version = None
276298
if await document.revert(None):
277-
await self.did_change(self, document, callback_filter=language_id_filter(document))
299+
create_sub_task(
300+
self.did_change(self, document, callback_filter=language_id_filter(document)), loop=self.parent.loop
301+
)
278302

279303
@rpc_method(name="textDocument/willSave", param_type=WillSaveTextDocumentParams)
280304
@_logger.call
@@ -299,9 +323,14 @@ async def _text_document_did_save(
299323
text_changed = await document.text() != normalized_text
300324
if text_changed:
301325
await document.save(None, text)
302-
await self.did_change(self, document, callback_filter=language_id_filter(document))
326+
create_sub_task(
327+
self.did_change(self, document, callback_filter=language_id_filter(document)),
328+
loop=self.parent.loop,
329+
)
303330

304-
await self.did_save(self, document, callback_filter=language_id_filter(document))
331+
create_sub_task(
332+
self.did_save(self, document, callback_filter=language_id_filter(document)), loop=self.parent.loop
333+
)
305334

306335
@rpc_method(name="textDocument/willSaveWaitUntil", param_type=WillSaveTextDocumentParams)
307336
@_logger.call
@@ -352,4 +381,6 @@ async def _text_document_did_change(
352381
f"for document {text_document.uri}."
353382
)
354383

355-
await self.did_change(self, document, callback_filter=language_id_filter(document))
384+
create_sub_task(
385+
self.did_change(self, document, callback_filter=language_id_filter(document)), loop=self.parent.loop
386+
)

robotcode/language_server/robotframework/parts/codelens.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,16 @@ def __init__(self, parent: RobotLanguageServerProtocol) -> None:
2929
parent.code_lens.collect.add(self.collect)
3030
parent.code_lens.resolve.add(self.resolve)
3131
parent.robot_references.cache_cleared.add(self.robot_references_cache_cleared)
32+
parent.diagnostics.workspace_diagnostics_stopped.add(self.diagnostics_workspace_diagnostics_stopped)
3233

3334
self._running_task: Set[Tuple[TextDocument, KeywordDoc]] = set()
3435

3536
async def robot_references_cache_cleared(self, sender: Any) -> None: # NOSONAR
3637
await self.parent.code_lens.refresh()
3738

39+
async def diagnostics_workspace_diagnostics_stopped(self, sender: Any) -> None: # NOSONAR
40+
await self.parent.code_lens.refresh()
41+
3842
@language_id("robotframework")
3943
@threaded()
4044
async def collect(self, sender: Any, document: TextDocument) -> Optional[List[CodeLens]]:
@@ -109,7 +113,7 @@ async def resolve(self, sender: Any, code_lens: CodeLens) -> Optional[CodeLens]:
109113
name = code_lens.data["name"]
110114
line = code_lens.data["line"]
111115

112-
if self.parent.robot_workspace.workspace_loaded.is_set():
116+
if self.parent.diagnostics.workspace_diagnostics_running.is_set():
113117
kw_doc = await self.get_keyword_definition_at_line(namespace, name, line)
114118

115119
if kw_doc is not None and not kw_doc.is_error_handler:

0 commit comments

Comments
 (0)