Skip to content

Commit a259ec6

Browse files
committed
fix(analyzer): correct resolving variables declared with the new VAR statement and a scope
1 parent f33c80a commit a259ec6

File tree

4 files changed

+113
-9
lines changed

4 files changed

+113
-9
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@
2020
from robotcode.language_server.robotframework.configuration import AnalysisConfig
2121
from robotcode.robot.diagnostics.entities import (
2222
ArgumentDefinition,
23-
CommandLineVariableDefinition,
2423
EnvironmentVariableDefinition,
24+
GlobalVariableDefinition,
2525
LibraryArgumentDefinition,
2626
)
2727
from robotcode.robot.diagnostics.namespace import Namespace
@@ -362,7 +362,7 @@ def _collect_unused_variable_references(self, document: TextDocument) -> Diagnos
362362
check_current_task_canceled()
363363

364364
if isinstance(
365-
var, (LibraryArgumentDefinition, EnvironmentVariableDefinition, CommandLineVariableDefinition)
365+
var, (LibraryArgumentDefinition, EnvironmentVariableDefinition, GlobalVariableDefinition)
366366
):
367367
continue
368368

packages/robot/src/robotcode/robot/diagnostics/entities.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,9 @@ def __repr__(self) -> str:
173173
class VariableDefinitionType(Enum):
174174
VARIABLE = "suite variable"
175175
LOCAL_VARIABLE = "local variable"
176+
TEST_VARIABLE = "test variable"
176177
ARGUMENT = "argument"
178+
GLOBAL_VARIABLE = "global variable"
177179
COMMAND_LINE_VARIABLE = "global variable [command line]"
178180
BUILTIN_VARIABLE = "builtin variable"
179181
IMPORTED_VARIABLE = "suite variable [imported]"
@@ -218,6 +220,15 @@ def range(self) -> Range:
218220
)
219221

220222

223+
@dataclass
224+
class TestVariableDefinition(VariableDefinition):
225+
type: VariableDefinitionType = VariableDefinitionType.TEST_VARIABLE
226+
227+
@single_call
228+
def __hash__(self) -> int:
229+
return hash((type(self), self.name, self.type, self.range, self.source))
230+
231+
221232
@dataclass
222233
class LocalVariableDefinition(VariableDefinition):
223234
type: VariableDefinitionType = VariableDefinitionType.LOCAL_VARIABLE
@@ -227,6 +238,15 @@ def __hash__(self) -> int:
227238
return hash((type(self), self.name, self.type, self.range, self.source))
228239

229240

241+
@dataclass
242+
class GlobalVariableDefinition(VariableDefinition):
243+
type: VariableDefinitionType = VariableDefinitionType.GLOBAL_VARIABLE
244+
245+
@single_call
246+
def __hash__(self) -> int:
247+
return hash((type(self), self.name, self.type, self.range, self.source))
248+
249+
230250
@dataclass
231251
class BuiltInVariableDefinition(VariableDefinition):
232252
type: VariableDefinitionType = VariableDefinitionType.BUILTIN_VARIABLE
@@ -238,13 +258,13 @@ def __hash__(self) -> int:
238258

239259

240260
@dataclass
241-
class CommandLineVariableDefinition(VariableDefinition):
261+
class CommandLineVariableDefinition(GlobalVariableDefinition):
242262
type: VariableDefinitionType = VariableDefinitionType.COMMAND_LINE_VARIABLE
243263
resolvable: bool = True
244264

245265
@single_call
246266
def __hash__(self) -> int:
247-
return hash((type(self), self.name, self.type))
267+
return hash((type(self), self.name, self.type, self.range, self.source))
248268

249269

250270
@dataclass

packages/robot/src/robotcode/robot/diagnostics/namespace.py

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
Set,
2121
Tuple,
2222
Union,
23+
cast,
2324
)
2425

2526
from robot.errors import VariableError
@@ -75,13 +76,15 @@
7576
BuiltInVariableDefinition,
7677
CommandLineVariableDefinition,
7778
EnvironmentVariableDefinition,
79+
GlobalVariableDefinition,
7880
Import,
7981
InvalidVariableError,
8082
LibraryEntry,
8183
LibraryImport,
8284
LocalVariableDefinition,
8385
ResourceEntry,
8486
ResourceImport,
87+
TestVariableDefinition,
8588
VariableDefinition,
8689
VariableMatcher,
8790
VariablesEntry,
@@ -182,18 +185,21 @@ class BlockVariableVisitor(Visitor):
182185
def __init__(
183186
self,
184187
library_doc: LibraryDoc,
188+
global_variables: List[VariableDefinition],
185189
source: str,
186190
position: Optional[Position] = None,
187191
in_args: bool = True,
188192
) -> None:
189193
super().__init__()
190194
self.library_doc = library_doc
195+
self.global_variables = global_variables
191196
self.source = source
192197
self.position = position
193198
self.in_args = in_args
194199

195200
self._results: Dict[str, VariableDefinition] = {}
196201
self.current_kw_doc: Optional[KeywordDoc] = None
202+
self._var_statements_vars: List[VariableDefinition] = []
197203

198204
def get(self, model: ast.AST) -> List[VariableDefinition]:
199205
self._results = {}
@@ -384,15 +390,32 @@ def visit_ForHeader(self, node: Statement) -> None: # noqa: N802
384390
)
385391

386392
def visit_Var(self, node: Statement) -> None: # noqa: N802
393+
from robot.parsing.model.statements import Var
394+
387395
variable = node.get_token(Token.VARIABLE)
388396
if variable is None:
389397
return
390398
try:
391-
if not is_variable(variable.value):
399+
var_name = variable.value
400+
if var_name.endswith("="):
401+
var_name = var_name[:-1].rstrip()
402+
403+
if not is_variable(var_name):
392404
return
393405

394-
self._results[variable.value] = LocalVariableDefinition(
395-
name=variable.value,
406+
scope = cast(Var, node).scope
407+
408+
if scope in ("SUITE",):
409+
var_type = VariableDefinition
410+
elif scope in ("TEST", "TASK"):
411+
var_type = TestVariableDefinition
412+
elif scope in ("GLOBAL",):
413+
var_type = GlobalVariableDefinition
414+
else:
415+
var_type = LocalVariableDefinition
416+
417+
var = var_type(
418+
name=var_name,
396419
name_token=strip_variable_token(variable),
397420
line_no=variable.lineno,
398421
col_offset=variable.col_offset,
@@ -401,6 +424,16 @@ def visit_Var(self, node: Statement) -> None: # noqa: N802
401424
source=self.source,
402425
)
403426

427+
self._var_statements_vars.append(var)
428+
429+
if var_name not in self._results or type(self._results[var_name]) != type(var):
430+
if isinstance(var, LocalVariableDefinition) or not any(
431+
l for l in self.global_variables if l.matcher == var.matcher
432+
):
433+
self._results[var_name] = var
434+
else:
435+
self._results.pop(var_name, None)
436+
404437
except VariableError:
405438
pass
406439

@@ -928,6 +961,7 @@ def yield_variables(
928961
(
929962
BlockVariableVisitor(
930963
self.get_library_doc(),
964+
self.get_global_variables(),
931965
self.source,
932966
position,
933967
isinstance(test_or_keyword_nodes[-1], Arguments) if nodes else False,

packages/robot/src/robotcode/robot/diagnostics/namespace_analyzer.py

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@
55
import os
66
from collections import defaultdict
77
from dataclasses import dataclass
8-
from typing import Any, Dict, Iterator, List, Optional, Set, Tuple, Union
8+
from typing import Any, Dict, Iterator, List, Optional, Set, Tuple, Type, Union
99

10+
import robot.parsing.model.statements
1011
from robot.parsing.lexer.tokens import Token
1112
from robot.parsing.model.blocks import Keyword, TestCase
1213
from robot.parsing.model.statements import (
@@ -206,11 +207,60 @@ def visit_Variable(self, node: Variable) -> None: # noqa: N802
206207
if var_def not in self._variable_references:
207208
self._variable_references[var_def] = set()
208209

210+
def visit_Var(self, node: Statement) -> None: # noqa: N802
211+
name_token = node.get_token(Token.VARIABLE)
212+
if name_token is None:
213+
return
214+
215+
name = name_token.value
216+
217+
if name is not None:
218+
match = search_variable(name, ignore_errors=True)
219+
if not match.is_assign(allow_assign_mark=True):
220+
return
221+
222+
if name.endswith("="):
223+
name = name[:-1].rstrip()
224+
225+
r = range_from_token(
226+
strip_variable_token(
227+
Token(
228+
name_token.type,
229+
name,
230+
name_token.lineno,
231+
name_token.col_offset,
232+
name_token.error,
233+
)
234+
)
235+
)
236+
# r.start.character = 0
237+
# r.end.character = 0
238+
239+
var_def = self.namespace.find_variable(
240+
name,
241+
skip_commandline_variables=False,
242+
nodes=self.node_stack,
243+
position=range_from_token(node.get_token(Token.VAR)).start,
244+
ignore_error=True,
245+
)
246+
if var_def is not None:
247+
if var_def.name_range != r:
248+
if self.namespace.document is not None:
249+
self._variable_references[var_def].add(Location(self.namespace.document.document_uri, r))
250+
else:
251+
if self.namespace.document is not None:
252+
self._variable_references[var_def] = set()
253+
209254
def generic_visit(self, node: ast.AST) -> None:
210255
check_current_task_canceled()
211256

212257
super().generic_visit(node)
213258

259+
if get_robot_version() < (7, 0):
260+
variable_statements: Tuple[Type[Any], ...] = (Variable,)
261+
else:
262+
variable_statements = (Variable, robot.parsing.model.statements.Var)
263+
214264
def visit(self, node: ast.AST) -> None:
215265
check_current_task_canceled()
216266

@@ -231,7 +281,7 @@ def visit(self, node: ast.AST) -> None:
231281
for token1 in (
232282
t
233283
for t in node.tokens
234-
if not (isinstance(node, Variable) and t.type == Token.VARIABLE)
284+
if not (isinstance(node, self.variable_statements) and t.type == Token.VARIABLE)
235285
and t.error is None
236286
and contains_variable(t.value, "$@&%")
237287
):

0 commit comments

Comments
 (0)