Skip to content

Commit 7a163bd

Browse files
committed
implement references for keywords, close #13, correct analyzing "run keywords"
1 parent fd48124 commit 7a163bd

File tree

3 files changed

+152
-52
lines changed

3 files changed

+152
-52
lines changed

robotcode/language_server/robotframework/diagnostics/namespace.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -611,9 +611,9 @@ async def _analyse_run_keyword(
611611
await self._analyze_keyword_call(argument_tokens[1].value, node, argument_tokens[1], argument_tokens[2:])
612612
return argument_tokens[2:]
613613
elif keyword_doc.is_run_keywords():
614-
614+
has_and = False
615615
while argument_tokens:
616-
# TODO: Parse "run keywords" with arguments using upper case AND
616+
617617
t = argument_tokens[0]
618618
argument_tokens = argument_tokens[1:]
619619
if t.value == "AND":
@@ -635,6 +635,10 @@ async def _analyse_run_keyword(
635635
if and_token is not None:
636636
args = argument_tokens[: argument_tokens.index(and_token)]
637637
argument_tokens = argument_tokens[argument_tokens.index(and_token) + 1 :]
638+
has_and = True
639+
elif has_and:
640+
args = argument_tokens
641+
argument_tokens = []
638642

639643
await self._analyze_keyword_call(t.value, node, t, args)
640644

robotcode/language_server/robotframework/parts/model_helper.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,20 +38,31 @@ async def get_run_keyword_keyworddoc_and_token_from_position(
3838
return result, argument_tokens[2:]
3939

4040
elif keyword_doc.is_run_keywords():
41+
has_and = False
4142
while argument_tokens:
4243
t = argument_tokens[0]
4344
argument_tokens = argument_tokens[1:]
4445
if t.value == "AND":
4546
continue
4647

47-
if is_not_variable_token(t) and position.is_in_range(range_from_token(t)):
48-
result = await self.get_keyworddoc_and_token_from_position(t.value, t, [], namespace, position)
48+
and_token = next((e for e in argument_tokens if e.value == "AND"), None)
49+
if and_token is not None:
50+
args = argument_tokens[: argument_tokens.index(and_token)]
51+
has_and = True
52+
else:
53+
if has_and:
54+
args = argument_tokens
55+
else:
56+
args = []
4957

50-
return result, argument_tokens
58+
result = await self.get_keyworddoc_and_token_from_position(t.value, t, args, namespace, position)
59+
if result is not None and result[0] is not None:
60+
return result, []
5161

52-
and_token = next((e for e in argument_tokens if e.value == "AND"), None)
5362
if and_token is not None:
5463
argument_tokens = argument_tokens[argument_tokens.index(and_token) + 1 :]
64+
elif has_and:
65+
argument_tokens = []
5566

5667
return None, []
5768
elif keyword_doc.is_run_keyword_if() and len(argument_tokens) > 1 and is_not_variable_token(argument_tokens[1]):

robotcode/language_server/robotframework/parts/references.py

Lines changed: 131 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from typing import (
77
TYPE_CHECKING,
88
Any,
9+
AsyncGenerator,
910
Awaitable,
1011
Callable,
1112
Iterator,
@@ -25,12 +26,21 @@
2526
from ...common.lsp_types import Location, Position, ReferenceContext
2627
from ...common.text_document import TextDocument
2728
from ..configuration import WorkspaceConfig
28-
from ..diagnostics.library_doc import KeywordDoc, KeywordMatcher, LibraryDoc
29+
from ..diagnostics.library_doc import (
30+
ALL_RUN_KEYWORDS_MATCHERS,
31+
RESOURCE_FILE_EXTENSION,
32+
ROBOT_FILE_EXTENSION,
33+
KeywordDoc,
34+
KeywordMatcher,
35+
LibraryDoc,
36+
)
37+
from ..diagnostics.namespace import Namespace
2938
from ..utils.ast import (
3039
HasTokens,
3140
Token,
3241
get_nodes_at_position,
3342
get_tokens_at_position,
43+
is_not_variable_token,
3444
range_from_token,
3545
range_from_token_or_node,
3646
tokenize_variables,
@@ -289,13 +299,6 @@ async def _find_keyword_references_in_file(
289299
file: Path,
290300
cancel_token: CancelationToken,
291301
) -> List[Location]:
292-
from robot.parsing.lexer.tokens import Token as RobotToken
293-
from robot.parsing.model.statements import (
294-
Fixture,
295-
KeywordCall,
296-
Template,
297-
TestTemplate,
298-
)
299302

300303
doc = self.parent.robot_workspace.get_or_open_document(file, "robotframework")
301304
namespace = await self.parent.documents_cache.get_namespace(doc, cancelation_token=cancel_token)
@@ -310,47 +313,129 @@ async def _find_keyword_references_in_file(
310313
):
311314
return []
312315

313-
libraries_matchers = (await namespace.get_libraries_matchers()).keys()
314-
resources_matchers = (await namespace.get_resources_matchers()).keys()
316+
return await asyncio.get_running_loop().run_in_executor(
317+
None, asyncio.run, self._find_keyword_references_in_namespace(namespace, kw_doc, cancel_token)
318+
)
319+
320+
async def _find_keyword_references_in_namespace(
321+
self, namespace: Namespace, kw_doc: KeywordDoc, cancel_token: CancelationToken
322+
) -> List[Location]:
323+
from robot.parsing.lexer.tokens import Token as RobotToken
324+
from robot.parsing.model.statements import (
325+
Fixture,
326+
KeywordCall,
327+
Template,
328+
TestTemplate,
329+
)
330+
331+
result: List[Location] = []
332+
333+
async for node in iter_nodes(namespace.model):
334+
cancel_token.throw_if_canceled()
335+
336+
kw_token: Optional[Token] = None
337+
arguments: Optional[List[Token]] = None
315338

316-
async def _run() -> List[Location]:
339+
if isinstance(node, KeywordCall):
340+
kw_token = node.get_token(RobotToken.KEYWORD)
341+
arguments = list(node.get_tokens(RobotToken.ARGUMENT) or [])
342+
elif isinstance(node, Fixture):
343+
kw_token = node.get_token(RobotToken.NAME)
344+
arguments = list(node.get_tokens(RobotToken.ARGUMENT) or [])
345+
elif isinstance(node, (Template, TestTemplate)):
346+
kw_token = node.get_token(RobotToken.NAME)
347+
arguments = list(node.get_tokens(RobotToken.ARGUMENT) or [])
348+
349+
async for location in self.get_keyword_references_from_tokens(namespace, kw_doc, node, kw_token, arguments):
350+
result.append(location)
351+
352+
return result
353+
354+
async def get_keyword_references_from_tokens(
355+
self,
356+
namespace: Namespace,
357+
kw_doc: KeywordDoc,
358+
node: ast.AST,
359+
kw_token: Optional[Token],
360+
arguments: Optional[List[Token]],
361+
) -> AsyncGenerator[Location, None]:
362+
if kw_token is not None and is_not_variable_token(kw_token):
363+
kw: Optional[KeywordDoc] = None
317364
kw_matcher = KeywordMatcher(kw_doc.name)
318365

319-
result: List[Location] = []
320-
321-
async for node in iter_nodes(namespace.model):
322-
cancel_token.throw_if_canceled()
323-
324-
kw: Optional[KeywordDoc] = None
325-
kw_token: Optional[Token] = None
326-
327-
if isinstance(node, KeywordCall):
328-
kw_token = node.get_token(RobotToken.KEYWORD)
329-
elif isinstance(node, Fixture):
330-
kw_token = node.get_token(RobotToken.NAME)
331-
elif isinstance(node, (Template, TestTemplate)):
332-
kw_token = node.get_token(RobotToken.NAME)
333-
334-
if kw_token is not None:
335-
for lib, name in self._yield_owner_and_kw_names(kw_token.value):
336-
if lib is not None:
337-
lib_matcher = KeywordMatcher(lib)
338-
if lib_matcher not in libraries_matchers and lib_matcher not in resources_matchers:
339-
continue
340-
341-
if kw_matcher == name:
342-
kw = await namespace.find_keyword(str(kw_token.value))
343-
344-
if kw is not None and kw == kw_doc:
345-
result.append(
346-
Location(
347-
str(Uri.from_path(namespace.source).normalized()),
348-
range=range_from_token_or_node(node, kw_token),
349-
)
350-
)
351-
return result
366+
for lib, name in self._yield_owner_and_kw_names(kw_token.value):
367+
if lib is not None:
368+
lib_matcher = KeywordMatcher(lib)
369+
if (
370+
lib_matcher not in (await namespace.get_libraries_matchers()).keys()
371+
and lib_matcher not in (await namespace.get_resources_matchers()).keys()
372+
):
373+
continue
374+
375+
if name is not None:
376+
name_matcher = KeywordMatcher(name)
377+
if kw_matcher == name_matcher:
378+
kw = await namespace.find_keyword(str(kw_token.value))
379+
380+
if kw is not None and kw == kw_doc:
381+
yield Location(
382+
str(Uri.from_path(namespace.source).normalized()),
383+
range=range_from_token_or_node(node, kw_token),
384+
)
385+
386+
if name_matcher in ALL_RUN_KEYWORDS_MATCHERS and arguments:
387+
async for location in self.get_keyword_references_from_any_run_keyword(
388+
namespace, kw_doc, node, kw_token, arguments
389+
):
390+
yield location
391+
392+
async def get_keyword_references_from_any_run_keyword(
393+
self,
394+
namespace: Namespace,
395+
kw_doc: KeywordDoc,
396+
node: ast.AST,
397+
kw_token: Token,
398+
arguments: List[Token],
399+
) -> AsyncGenerator[Location, None]:
400+
401+
if kw_token is None or is_not_variable_token(kw_token):
402+
return
403+
404+
kw = await namespace.find_keyword(str(kw_token.value))
405+
406+
if kw is None or not kw.is_any_run_keyword():
407+
return
408+
409+
if kw.is_run_keyword() and len(arguments) > 0 and is_not_variable_token(arguments[0]):
410+
async for e in self.get_keyword_references_from_tokens(
411+
namespace, kw_doc, node, arguments[0], arguments[1:]
412+
):
413+
yield e
414+
elif kw.is_run_keyword_with_condition() and len(arguments) > 1 and is_not_variable_token(arguments[1]):
415+
async for e in self.get_keyword_references_from_tokens(
416+
namespace, kw_doc, node, arguments[1], arguments[2:]
417+
):
418+
yield e
419+
elif kw.is_run_keywords():
420+
421+
while arguments:
422+
423+
t = arguments[0]
424+
arguments = arguments[1:]
425+
if t.value == "AND":
426+
continue
427+
428+
if not is_not_variable_token(t):
429+
continue
430+
431+
and_token = next((e for e in arguments if e.value == "AND"), None)
432+
args = []
433+
if and_token is not None:
434+
args = arguments[: arguments.index(and_token)]
435+
arguments = arguments[arguments.index(and_token) + 1 :]
352436

353-
return await asyncio.get_running_loop().run_in_executor(None, asyncio.run, _run())
437+
async for e in self.get_keyword_references_from_tokens(namespace, kw_doc, node, t, args):
438+
yield e
354439

355440
async def _find_keyword_references(self, document: TextDocument, kw_doc: KeywordDoc) -> List[Location]:
356441
folder = self.parent.workspace.get_workspace_folder(document.uri)
@@ -391,7 +476,7 @@ async def _find_keyword_references(self, document: TextDocument, kw_doc: Keyword
391476

392477
async for f in iter_files(
393478
folder.uri.to_path(),
394-
("**/*.{robot,resource}"),
479+
(f"**/*.{{{ROBOT_FILE_EXTENSION[1:]},{RESOURCE_FILE_EXTENSION[1:]}}}"),
395480
ignore_patterns=config.exclude_patterns or [], # type: ignore
396481
absolute=True,
397482
):

0 commit comments

Comments
 (0)