Skip to content

Commit c65e98d

Browse files
committed
fix(langserver): correct handling of imports with the same namespace name
hover, semantic hightlightning, references are aware of the current keyword call namespace if given
1 parent 91513c5 commit c65e98d

File tree

8 files changed

+62
-130
lines changed

8 files changed

+62
-130
lines changed

packages/language_server/src/robotcode/language_server/robotframework/diagnostics/analyzer.py

Lines changed: 18 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
Statement,
2626
Token,
2727
is_not_variable_token,
28-
iter_over_keyword_names_and_owners,
2928
range_from_node,
3029
range_from_node_or_token,
3130
range_from_token,
@@ -40,13 +39,12 @@
4039
EnvironmentVariableDefinition,
4140
LibraryEntry,
4241
LocalVariableDefinition,
43-
ResourceEntry,
4442
VariableDefinition,
4543
VariableDefinitionType,
4644
VariableNotFoundDefinition,
4745
)
4846
from .errors import DIAGNOSTICS_SOURCE_NAME, Error
49-
from .library_doc import KeywordDoc, KeywordMatcher, is_embedded_keyword
47+
from .library_doc import KeywordDoc, is_embedded_keyword
5048
from .model_helper import ModelHelperMixin
5149
from .namespace import (
5250
KeywordFinder,
@@ -70,8 +68,6 @@ def __init__(
7068
namespace: Namespace,
7169
finder: KeywordFinder,
7270
ignored_lines: List[int],
73-
libraries_matchers: Dict[KeywordMatcher, LibraryEntry],
74-
resources_matchers: Dict[KeywordMatcher, ResourceEntry],
7571
) -> None:
7672
from robot.parsing.model.statements import Template, TestTemplate
7773

@@ -81,8 +77,6 @@ def __init__(
8177
self.namespace = namespace
8278
self.finder = finder
8379
self._ignored_lines = ignored_lines
84-
self.libraries_matchers = libraries_matchers
85-
self.resources_matchers = resources_matchers
8680

8781
self.current_testcase_or_keyword_name: Optional[str] = None
8882
self.test_template: Optional[TestTemplate] = None
@@ -404,26 +398,16 @@ async def _analyze_keyword_call(
404398
result = self.finder.find_keyword(keyword, raise_keyword_error=False)
405399

406400
if keyword is not None:
407-
for lib, name in iter_over_keyword_names_and_owners(keyword):
408-
if (
409-
lib is not None
410-
and not any(k for k in self.libraries_matchers.keys() if k == lib)
411-
and not any(k for k in self.resources_matchers.keys() if k == lib)
412-
):
413-
continue
414-
415-
lib_entry, kw_namespace = await self.get_namespace_info_from_keyword(
416-
self.namespace, keyword_token, self.libraries_matchers, self.resources_matchers
417-
)
418-
if lib_entry and kw_namespace:
419-
r = range_from_token(keyword_token)
420-
lib_range = r
421-
r.end.character = r.start.character + len(kw_namespace)
422-
kw_range.start.character = r.end.character + 1
423-
lib_range.end.character = kw_range.start.character - 1
401+
lib_entry, kw_namespace = await self.get_namespace_info_from_keyword_token(
402+
self.namespace, keyword_token
403+
)
424404

425-
if result is not None and result.parent == lib_entry.library_doc:
426-
break
405+
if lib_entry and kw_namespace:
406+
r = range_from_token(keyword_token)
407+
lib_range = r
408+
r.end.character = r.start.character + len(kw_namespace)
409+
kw_range.start.character = r.end.character + 1
410+
lib_range.end.character = kw_range.start.character - 1
427411

428412
if (
429413
result is not None
@@ -437,7 +421,14 @@ async def _analyze_keyword_call(
437421

438422
if kw_namespace and lib_entry is not None and lib_range is not None:
439423
if self.namespace.document is not None:
440-
self._namespace_references[lib_entry].add(Location(self.namespace.document.document_uri, lib_range))
424+
entries = [lib_entry]
425+
if self.finder.multiple_keywords_result is not None:
426+
entries = next(
427+
(v for k, v in (await self.namespace.get_namespaces()).items() if k == kw_namespace),
428+
entries,
429+
)
430+
for entry in entries:
431+
self._namespace_references[entry].add(Location(self.namespace.document.document_uri, lib_range))
441432

442433
if not ignore_errors_if_contains_variables or is_not_variable_token(keyword_token):
443434
for e in self.finder.diagnostics:

packages/language_server/src/robotcode/language_server/robotframework/diagnostics/library_doc.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,9 @@ def embedded_arguments(self) -> Any:
185185

186186
def __eq__(self, o: Any) -> bool:
187187
if isinstance(o, KeywordMatcher):
188+
if self._is_namespace != o._is_namespace:
189+
return False
190+
188191
if not self.embedded_arguments:
189192
return self.normalized_name == o.normalized_name
190193

@@ -206,7 +209,7 @@ def __hash__(self) -> int:
206209
return hash(
207210
(self.embedded_arguments.name, tuple(self.embedded_arguments.args))
208211
if self.embedded_arguments
209-
else (self.normalized_name,)
212+
else (self.normalized_name, self._is_namespace),
210213
)
211214

212215
def __str__(self) -> str:

packages/language_server/src/robotcode/language_server/robotframework/diagnostics/model_helper.py

Lines changed: 11 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
from typing import (
99
Any,
1010
AsyncIterator,
11-
Dict,
1211
Iterator,
1312
List,
1413
Optional,
@@ -21,15 +20,13 @@
2120
from robotcode.core.lsp.types import Position
2221
from robotcode.language_server.robotframework.diagnostics.entities import (
2322
LibraryEntry,
24-
ResourceEntry,
2523
VariableDefinition,
2624
VariableNotFoundDefinition,
2725
)
2826
from robotcode.language_server.robotframework.diagnostics.library_doc import (
2927
ArgumentInfo,
3028
KeywordArgumentKind,
3129
KeywordDoc,
32-
KeywordMatcher,
3330
LibraryDoc,
3431
)
3532
from robotcode.language_server.robotframework.diagnostics.namespace import (
@@ -214,32 +211,24 @@ async def get_keyworddoc_and_token_from_position(
214211

215212
return None
216213

217-
async def get_namespace_info_from_keyword(
218-
self,
219-
namespace: Namespace,
220-
keyword_token: Token,
221-
libraries_matchers: Optional[Dict[KeywordMatcher, LibraryEntry]] = None,
222-
resources_matchers: Optional[Dict[KeywordMatcher, ResourceEntry]] = None,
214+
@classmethod
215+
async def get_namespace_info_from_keyword_token(
216+
cls, namespace: Namespace, keyword_token: Token
223217
) -> Tuple[Optional[LibraryEntry], Optional[str]]:
224218
lib_entry: Optional[LibraryEntry] = None
225-
226219
kw_namespace: Optional[str] = None
227220

228-
if libraries_matchers is None:
229-
libraries_matchers = await namespace.get_libraries_matchers()
230-
if resources_matchers is None:
231-
resources_matchers = await namespace.get_resources_matchers()
232-
233-
for lib, _ in iter_over_keyword_names_and_owners(keyword_token.value):
221+
for lib, keyword in iter_over_keyword_names_and_owners(keyword_token.value):
234222
if lib is not None:
235-
lib_entry = next((v for k, v in libraries_matchers.items() if k == lib), None)
236-
if lib_entry is not None:
237-
kw_namespace = lib
238-
break
239-
lib_entry = next((v for k, v in resources_matchers.items() if k == lib), None)
240-
if lib_entry is not None:
223+
lib_entries = next((v for k, v in (await namespace.get_namespaces()).items() if k == lib), None)
224+
if lib_entries is not None:
241225
kw_namespace = lib
226+
lib_entry = next(
227+
(v for v in lib_entries if keyword in v.library_doc.keywords),
228+
lib_entries[0] if lib_entries else None,
229+
)
242230
break
231+
243232
return lib_entry, kw_namespace
244233

245234
__match_extended = re.compile(

packages/language_server/src/robotcode/language_server/robotframework/diagnostics/namespace.py

Lines changed: 12 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import re
99
import time
1010
import weakref
11-
from collections import OrderedDict
11+
from collections import OrderedDict, defaultdict
1212
from itertools import chain
1313
from pathlib import Path
1414
from typing import (
@@ -539,6 +539,7 @@ def __init__(
539539
self.workspace_languages = workspace_languages
540540

541541
self._libraries: OrderedDict[str, LibraryEntry] = OrderedDict()
542+
self._namespaces: Optional[Dict[KeywordMatcher, List[LibraryEntry]]] = None
542543
self._libraries_matchers: Optional[Dict[KeywordMatcher, LibraryEntry]] = None
543544
self._resources: OrderedDict[str, ResourceEntry] = OrderedDict()
544545
self._resources_matchers: Optional[Dict[KeywordMatcher, ResourceEntry]] = None
@@ -662,6 +663,7 @@ async def is_initialized(self) -> bool:
662663
async def _invalidate(self) -> None:
663664
self._initialized = False
664665

666+
self._namespaces = None
665667
self._libraries = OrderedDict()
666668
self._libraries_matchers = None
667669
self._resources = OrderedDict()
@@ -732,33 +734,26 @@ async def get_import_entries(self) -> OrderedDict[Import, LibraryEntry]:
732734

733735
return self._import_entries
734736

735-
@_logger.call
736737
async def get_libraries(self) -> OrderedDict[str, LibraryEntry]:
737738
await self.ensure_initialized()
738739

739740
return self._libraries
740741

741-
async def get_libraries_matchers(self) -> Dict[KeywordMatcher, LibraryEntry]:
742-
if self._libraries_matchers is None:
743-
self._libraries_matchers = {
744-
KeywordMatcher(v.alias or v.name or v.import_name, is_namespace=True): v
745-
for v in (await self.get_libraries()).values()
746-
}
747-
return self._libraries_matchers
742+
async def get_namespaces(self) -> Dict[KeywordMatcher, List[LibraryEntry]]:
743+
if self._namespaces is None:
744+
self._namespaces = defaultdict(list)
745+
746+
for v in (await self.get_libraries()).values():
747+
self._namespaces[KeywordMatcher(v.alias or v.name or v.import_name, is_namespace=True)].append(v)
748+
for v in (await self.get_resources()).values():
749+
self._namespaces[KeywordMatcher(v.alias or v.name or v.import_name, is_namespace=True)].append(v)
750+
return self._namespaces
748751

749752
async def get_resources(self) -> OrderedDict[str, ResourceEntry]:
750753
await self.ensure_initialized()
751754

752755
return self._resources
753756

754-
async def get_resources_matchers(self) -> Dict[KeywordMatcher, ResourceEntry]:
755-
if self._resources_matchers is None:
756-
self._resources_matchers = {
757-
KeywordMatcher(v.alias or v.name or v.import_name, is_namespace=True): v
758-
for v in (await self.get_resources()).values()
759-
}
760-
return self._resources_matchers
761-
762757
async def get_imported_variables(self) -> OrderedDict[str, VariablesEntry]:
763758
await self.ensure_initialized()
764759

@@ -1579,8 +1574,6 @@ async def _analyze(self) -> None:
15791574
self,
15801575
await self.create_finder(),
15811576
self.get_ignored_lines(self.document) if self.document is not None else [],
1582-
await self.get_libraries_matchers(),
1583-
await self.get_resources_matchers(),
15841577
).run()
15851578

15861579
self._diagnostics += result.diagnostics

packages/language_server/src/robotcode/language_server/robotframework/parts/code_action_quick_fixes.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ async def code_action_create_keyword(
147147
if bdd_token is not None and token is not None:
148148
keyword_token = token
149149

150-
lib_entry, kw_namespace = await self.get_namespace_info_from_keyword(namespace, keyword_token)
150+
lib_entry, kw_namespace = await self.get_namespace_info_from_keyword_token(namespace, keyword_token)
151151

152152
if lib_entry is not None and lib_entry.library_doc.type == "LIBRARY":
153153
disabled = CodeActionDisabledType("Keyword is from a library")
@@ -208,7 +208,7 @@ async def resolve_code_action_create_keyword(
208208
if bdd_token is not None and token is not None:
209209
keyword_token = token
210210

211-
lib_entry, kw_namespace = await self.get_namespace_info_from_keyword(namespace, keyword_token)
211+
lib_entry, kw_namespace = await self.get_namespace_info_from_keyword_token(namespace, keyword_token)
212212

213213
if lib_entry is not None and lib_entry.library_doc.type == "LIBRARY":
214214
return None

packages/language_server/src/robotcode/language_server/robotframework/parts/inlay_hint.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ async def _get_inlay_hint(
172172
)
173173

174174
if keyword_token is not None and config.namespaces:
175-
lib_entry, kw_namespace = await self.get_namespace_info_from_keyword(namespace, keyword_token)
175+
lib_entry, kw_namespace = await self.get_namespace_info_from_keyword_token(namespace, keyword_token)
176176
if lib_entry is None and kw_namespace is None:
177177
if kw_doc.libtype == "LIBRARY":
178178
lib = next(

packages/language_server/src/robotcode/language_server/robotframework/parts/rename.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,7 @@ async def _find_KeywordCall( # noqa: N802
337337
):
338338
keyword_token = self.strip_bdd_prefix(namespace, keyword_token)
339339

340-
lib_entry, kw_namespace = await self.get_namespace_info_from_keyword(namespace, keyword_token)
340+
lib_entry, kw_namespace = await self.get_namespace_info_from_keyword_token(namespace, keyword_token)
341341

342342
kw_range = range_from_token(keyword_token)
343343

@@ -451,7 +451,7 @@ async def _find_Fixture( # noqa: N802
451451
):
452452
keyword_token = self.strip_bdd_prefix(namespace, keyword_token)
453453

454-
lib_entry, kw_namespace = await self.get_namespace_info_from_keyword(namespace, keyword_token)
454+
lib_entry, kw_namespace = await self.get_namespace_info_from_keyword_token(namespace, keyword_token)
455455

456456
kw_range = range_from_token(keyword_token)
457457

@@ -510,7 +510,7 @@ async def _find_Template_or_TestTemplate( # noqa: N802
510510
(
511511
lib_entry,
512512
kw_namespace,
513-
) = await self.get_namespace_info_from_keyword(namespace, keyword_token)
513+
) = await self.get_namespace_info_from_keyword_token(namespace, keyword_token)
514514

515515
kw_range = range_from_token(keyword_token)
516516

0 commit comments

Comments
 (0)