22
33import ast
44import asyncio
5+ import itertools
56import weakref
67from collections import OrderedDict
78from dataclasses import dataclass , field
2021 Tuple ,
2122 cast ,
2223)
23- import itertools
2424
2525from ....utils .async_itertools import async_chain
2626from ....utils .logging import LoggingDescriptor
3535 Position ,
3636 Range ,
3737)
38- from ..utils .ast import Token , is_non_variable_token , range_from_token_or_node
38+ from ..utils .ast import (
39+ Token ,
40+ is_non_variable_token ,
41+ range_from_token_or_node ,
42+ tokenize_variables ,
43+ )
3944from ..utils .async_ast import AsyncVisitor
4045from .imports_manager import ImportsManager
4146from .library_doc import (
@@ -220,6 +225,46 @@ async def get(self, source: str, model: ast.AST) -> List[VariableDefinition]:
220225 await self .visit (model )
221226 return self ._results
222227
228+ async def visit_KeywordName (self , node : ast .AST ) -> None : # noqa: N802
229+ from robot .parsing .lexer .tokens import Token as RobotToken
230+ from robot .parsing .model .statements import KeywordName
231+ from robot .variables .search import VariableSearcher
232+
233+ n = cast (KeywordName , node )
234+ name_token = cast (Token , n .get_token (RobotToken .KEYWORD_NAME ))
235+
236+ if name_token is not None and name_token .value :
237+ for a in filter (
238+ lambda e : e .type == RobotToken .VARIABLE ,
239+ tokenize_variables (name_token , identifiers = "$" , ignore_errors = True ),
240+ ):
241+ if a .value :
242+ searcher = VariableSearcher ("$" , ignore_errors = True )
243+ match = searcher .search (a .value )
244+ if match .base is None :
245+ continue
246+ name = f"{ match .identifier } {{{ match .base .split (':' , 1 )[0 ]} }}"
247+
248+ self ._results .append (
249+ ArgumentDefinition (
250+ name = name ,
251+ name_token = a ,
252+ line_no = a .lineno ,
253+ col_offset = node .col_offset ,
254+ end_line_no = node .end_lineno
255+ if node .end_lineno is not None
256+ else a .lineno
257+ if a .lineno is not None
258+ else - 1 ,
259+ end_col_offset = node .end_col_offset
260+ if node .end_col_offset is not None
261+ else a .end_col_offset
262+ if name_token .end_col_offset is not None
263+ else - 1 ,
264+ source = self .source ,
265+ )
266+ )
267+
223268 async def visit_Arguments (self , node : ast .AST ) -> None : # noqa: N802
224269 from robot .errors import VariableError
225270 from robot .parsing .lexer .tokens import Token as RobotToken
@@ -781,7 +826,7 @@ def __init__(
781826 self ._libraries : OrderedDict [str , LibraryEntry ] = OrderedDict ()
782827 self ._resources : OrderedDict [str , ResourceEntry ] = OrderedDict ()
783828 self ._variables : OrderedDict [str , VariablesEntry ] = OrderedDict ()
784- self ._initialzed = False
829+ self ._initialized = False
785830 self ._initialize_lock = asyncio .Lock ()
786831 self ._analyzed = False
787832 self ._analyze_lock = asyncio .Lock ()
@@ -791,6 +836,7 @@ def __init__(
791836 self ._diagnostics : List [Diagnostic ] = []
792837
793838 self ._keywords : Optional [List [KeywordDoc ]] = None
839+ self ._loop = asyncio .get_event_loop ()
794840
795841 # TODO: how to get the search order from model
796842 self .search_order : Tuple [str , ...] = ()
@@ -814,21 +860,21 @@ async def resources_changed(self, sender: Any, params: List[LibraryDoc]) -> None
814860
815861 @_logger .call
816862 async def get_diagnostisc (self ) -> List [Diagnostic ]:
817- await self ._ensure_initialized ()
863+ await self .ensure_initialized ()
818864
819865 await self ._analyze ()
820866
821867 return self ._diagnostics
822868
823869 @_logger .call
824870 async def get_libraries (self ) -> OrderedDict [str , LibraryEntry ]:
825- await self ._ensure_initialized ()
871+ await self .ensure_initialized ()
826872
827873 return self ._libraries
828874
829875 @_logger .call
830876 async def get_resources (self ) -> OrderedDict [str , ResourceEntry ]:
831- await self ._ensure_initialized ()
877+ await self .ensure_initialized ()
832878
833879 return self ._resources
834880
@@ -856,9 +902,9 @@ async def get_library_doc(self) -> LibraryDoc:
856902 return self ._library_doc
857903
858904 @_logger .call
859- async def _ensure_initialized (self ) -> None :
905+ async def ensure_initialized (self ) -> bool :
860906 async with self ._initialize_lock :
861- if not self ._initialzed :
907+ if not self ._initialized :
862908 imports = await self .get_imports ()
863909
864910 if self .document is not None :
@@ -879,7 +925,12 @@ async def _ensure_initialized(self) -> None:
879925 await self ._import_default_libraries ()
880926 await self ._import_imports (imports , str (Path (self .source ).parent ), top_level = True )
881927
882- self ._initialzed = True
928+ self ._initialized = True
929+ return self ._initialized
930+
931+ @property
932+ def initialized (self ) -> bool :
933+ return self ._initialized
883934
884935 async def get_imports (self ) -> List [Import ]:
885936 if self ._imports is None :
@@ -905,7 +956,7 @@ def get_builtin_variables(cls) -> List[BuiltInVariableDefinition]:
905956 async def get_variables (self , nodes : Optional [List [ast .AST ]] = None ) -> Dict [VariableMatcher , VariableDefinition ]:
906957 from robot .parsing .model .blocks import Keyword , TestCase
907958
908- await self ._ensure_initialized ()
959+ await self .ensure_initialized ()
909960
910961 result : Dict [VariableMatcher , VariableDefinition ] = {}
911962
@@ -1246,7 +1297,7 @@ async def _get_resource_entry(self, name: str, base_dir: str, sentinel: Any = No
12461297
12471298 @_logger .call
12481299 async def get_keywords (self ) -> List [KeywordDoc ]:
1249- await self ._ensure_initialized ()
1300+ await self .ensure_initialized ()
12501301
12511302 if self ._keywords is None :
12521303 result : Dict [KeywordMatcher , KeywordDoc ] = {}
@@ -1296,10 +1347,13 @@ async def _analyze(self) -> None:
12961347 self ._analyzed = True
12971348
12981349 async def find_keyword (self , name : Optional [str ]) -> Optional [KeywordDoc ]:
1299- await self ._ensure_initialized ()
1350+ await self .ensure_initialized ()
13001351
13011352 return await KeywordFinder (self ).find_keyword (name )
13021353
1354+ async def find_keyword_threadsafe (self , name : Optional [str ]) -> Optional [KeywordDoc ]:
1355+ return await asyncio .wrap_future (asyncio .run_coroutine_threadsafe (self .find_keyword (name ), self ._loop ))
1356+
13031357
13041358class DiagnosticsEntry (NamedTuple ):
13051359 message : str
0 commit comments