55import weakref
66from collections import OrderedDict
77from dataclasses import dataclass , field
8+ from enum import Enum
89from pathlib import Path
910from typing import (
1011 Any ,
3839from .imports_manager import ImportsManager
3940from .library_doc import (
4041 BUILTIN_LIBRARY_NAME ,
42+ BUILTIN_VARIABLES ,
4143 DEFAULT_LIBRARIES ,
4244 KeywordDoc ,
4345 KeywordMatcher ,
@@ -61,15 +63,19 @@ class ImportError(DiagnosticsError):
6163
6264
6365@dataclass
64- class Import :
65- name : Optional [str ]
66- name_token : Optional [Token ]
66+ class SourceEntity :
6767 line_no : int
6868 col_offset : int
6969 end_line_no : int
7070 end_col_offset : int
7171 source : str
7272
73+
74+ @dataclass
75+ class Import (SourceEntity ):
76+ name : Optional [str ]
77+ name_token : Optional [Token ]
78+
7379 def range (self ) -> Range :
7480 return Range (
7581 start = Position (
@@ -114,14 +120,55 @@ def __hash__(self) -> int:
114120class VariablesImport (Import ):
115121 args : Tuple [str , ...] = ()
116122
123+ def __hash__ (self ) -> int :
124+ return hash (
125+ (
126+ type (self ),
127+ self .name ,
128+ self .args ,
129+ )
130+ )
131+
132+
133+ class VariableDefinitionType (Enum ):
134+ VARIABLE = "Variable"
135+ ARGUMENT = "Argument"
136+ BUILTIN_VARIABLE = "Variable (Builtin)"
137+
138+
139+ @dataclass
140+ class VariableDefinition (SourceEntity ):
141+ name : Optional [str ]
142+ name_token : Optional [Token ]
143+ type : VariableDefinitionType = VariableDefinitionType .VARIABLE
144+
145+ def __hash__ (self ) -> int :
146+ return hash ((type (self ), self .name , self .type ))
147+
148+
149+ @dataclass
150+ class BuiltInVariableDefinition (VariableDefinition ):
151+ type : VariableDefinitionType = VariableDefinitionType .BUILTIN_VARIABLE
152+
153+ def __hash__ (self ) -> int :
154+ return hash ((type (self ), self .name , self .type ))
155+
156+
157+ @dataclass
158+ class ArgumentDefinition (VariableDefinition ):
159+ type : VariableDefinitionType = VariableDefinitionType .ARGUMENT
160+
161+ def __hash__ (self ) -> int :
162+ return hash ((type (self ), self .name , self .type ))
163+
117164
118165class NameSpaceError (Exception ):
119166 pass
120167
121168
122169class VariablesVisitor (AsyncVisitor ):
123- async def get (self , source : str , model : ast .AST ) -> List [str ]:
124- self ._results : List [str ] = []
170+ async def get (self , source : str , model : ast .AST ) -> List [VariableDefinition ]:
171+ self ._results : List [VariableDefinition ] = []
125172 self .source = source
126173 await self .visit (model )
127174 return self ._results
@@ -133,11 +180,58 @@ async def visit_Section(self, node: ast.AST) -> None: # noqa: N802
133180 await self .generic_visit (node )
134181
135182 async def visit_Variable (self , node : ast .AST ) -> None : # noqa: N802
183+ from robot .parsing .lexer .tokens import Token
136184 from robot .parsing .model .statements import Variable
137185
138186 n = cast (Variable , node )
187+ name = n .get_value (Token .VARIABLE )
139188 if n .name :
140- self ._results .append (n .name )
189+ self ._results .append (
190+ VariableDefinition (
191+ name = n .name ,
192+ name_token = name if name is not None else None ,
193+ line_no = node .lineno ,
194+ col_offset = node .col_offset ,
195+ end_line_no = node .end_lineno if node .end_lineno is not None else - 1 ,
196+ end_col_offset = node .end_col_offset if node .end_col_offset is not None else - 1 ,
197+ source = self .source ,
198+ )
199+ )
200+
201+
202+ class ArgumentsVisitor (AsyncVisitor ):
203+ async def get (self , source : str , model : ast .AST ) -> List [VariableDefinition ]:
204+ self ._results : List [VariableDefinition ] = []
205+ self .source = source
206+ await self .visit (model )
207+ return self ._results
208+
209+ async def visit_Section (self , node : ast .AST ) -> None : # noqa: N802
210+ from robot .parsing .model .blocks import VariableSection
211+
212+ if isinstance (node , VariableSection ):
213+ await self .generic_visit (node )
214+
215+ async def visit_Arguments (self , node : ast .AST ) -> None : # noqa: N802
216+ from robot .parsing .lexer .tokens import Token as RobotToken
217+ from robot .parsing .model .statements import Arguments
218+ from robot .variables .search import is_variable
219+
220+ n = cast (Arguments , node )
221+ arguments = n .get_tokens (RobotToken .ARGUMENT )
222+ for argument in (cast (RobotToken , e ) for e in arguments ):
223+ if is_variable (argument .value ):
224+ self ._results .append (
225+ ArgumentDefinition (
226+ name = argument .value ,
227+ name_token = argument ,
228+ line_no = argument .lineno ,
229+ col_offset = argument .col_offset ,
230+ end_line_no = argument .lineno if argument .lineno is not None else - 1 ,
231+ end_col_offset = argument .end_col_offset if argument .end_col_offset is not None else - 1 ,
232+ source = self .source ,
233+ )
234+ )
141235
142236
143237class ImportVisitor (AsyncVisitor ):
@@ -590,18 +684,14 @@ def __str__(self) -> str:
590684@dataclass
591685class ResourceEntry (LibraryEntry ):
592686 imports : List [Import ] = field (default_factory = lambda : [])
593- variables : List [str ] = field (default_factory = lambda : [])
687+ variables : List [VariableDefinition ] = field (default_factory = lambda : [])
594688
595689
596690@dataclass
597691class VariablesEntry (LibraryEntry ):
598692 pass
599693
600694
601- IMPORTS_KEY = object ()
602- REFERENCED_DOCUMENTS_KEY = object ()
603-
604-
605695class Namespace :
606696 _logger = LoggingDescriptor ()
607697
@@ -632,7 +722,8 @@ def __init__(
632722 self ._analyze_lock = asyncio .Lock ()
633723 self ._library_doc : Optional [LibraryDoc ] = None
634724 self ._imports : Optional [List [Import ]] = None
635- self ._own_variables : Optional [List [str ]] = None
725+ self ._own_variables : Optional [List [VariableDefinition ]] = None
726+ self ._variables_definitions : Optional [Dict [str , VariableDefinition ]] = None
636727 self ._diagnostics : List [Diagnostic ] = []
637728
638729 self ._keywords : Optional [List [KeywordDoc ]] = None
@@ -732,12 +823,42 @@ async def get_imports(self) -> List[Import]:
732823
733824 return self ._imports
734825
735- async def get_own_variables (self ) -> List [str ]:
826+ async def get_own_variables (self ) -> List [VariableDefinition ]:
736827 if self ._own_variables is None :
737828 self ._own_variables = await VariablesVisitor ().get (self .source , self .model )
738829
739830 return self ._own_variables
740831
832+ _builtin_variables : Optional [List [BuiltInVariableDefinition ]] = None
833+
834+ @classmethod
835+ def get_builtin_variables (cls ) -> List [BuiltInVariableDefinition ]:
836+ if cls ._builtin_variables is None :
837+ cls ._builtin_variables = [BuiltInVariableDefinition (0 , 0 , 0 , 0 , "" , n , None ) for n in BUILTIN_VARIABLES ]
838+
839+ return cls ._builtin_variables
840+
841+ async def get_variables (self , nodes : Optional [List [ast .AST ]] = None ) -> Dict [str , VariableDefinition ]:
842+ from robot .parsing .model .blocks import Keyword
843+
844+ await self ._ensure_initialized ()
845+
846+ if self ._variables_definitions is None :
847+ result : Dict [str , VariableDefinition ] = {}
848+
849+ async for var in async_chain (
850+ * [await ArgumentsVisitor ().get (self .source , n ) for n in nodes or [] if isinstance (n , Keyword )],
851+ (e for e in await self .get_own_variables ()),
852+ * (e .variables for e in self ._resources .values ()),
853+ (e for e in self .get_builtin_variables ()),
854+ ):
855+ if var .name is not None and var .name not in result .keys ():
856+ result [var .name ] = var
857+
858+ self ._variables_definitions = result
859+
860+ return self ._variables_definitions
861+
741862 async def _import_imports (self , imports : Iterable [Import ], base_dir : str , * , top_level : bool = False ) -> None :
742863 async def _import (value : Import ) -> Optional [LibraryEntry ]:
743864 result : Optional [LibraryEntry ] = None
0 commit comments