Skip to content

Commit

Permalink
fix(analyzer): correct resolving variables declared with the new VAR …
Browse files Browse the repository at this point in the history
…statement and a scope
  • Loading branch information
d-biehl committed Jan 26, 2024
1 parent f33c80a commit a259ec6
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@
from robotcode.language_server.robotframework.configuration import AnalysisConfig
from robotcode.robot.diagnostics.entities import (
ArgumentDefinition,
CommandLineVariableDefinition,
EnvironmentVariableDefinition,
GlobalVariableDefinition,
LibraryArgumentDefinition,
)
from robotcode.robot.diagnostics.namespace import Namespace
Expand Down Expand Up @@ -362,7 +362,7 @@ def _collect_unused_variable_references(self, document: TextDocument) -> Diagnos
check_current_task_canceled()

if isinstance(
var, (LibraryArgumentDefinition, EnvironmentVariableDefinition, CommandLineVariableDefinition)
var, (LibraryArgumentDefinition, EnvironmentVariableDefinition, GlobalVariableDefinition)
):
continue

Expand Down
24 changes: 22 additions & 2 deletions packages/robot/src/robotcode/robot/diagnostics/entities.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,9 @@ def __repr__(self) -> str:
class VariableDefinitionType(Enum):
VARIABLE = "suite variable"
LOCAL_VARIABLE = "local variable"
TEST_VARIABLE = "test variable"
ARGUMENT = "argument"
GLOBAL_VARIABLE = "global variable"
COMMAND_LINE_VARIABLE = "global variable [command line]"
BUILTIN_VARIABLE = "builtin variable"
IMPORTED_VARIABLE = "suite variable [imported]"
Expand Down Expand Up @@ -218,6 +220,15 @@ def range(self) -> Range:
)


@dataclass
class TestVariableDefinition(VariableDefinition):
type: VariableDefinitionType = VariableDefinitionType.TEST_VARIABLE

@single_call
def __hash__(self) -> int:
return hash((type(self), self.name, self.type, self.range, self.source))


@dataclass
class LocalVariableDefinition(VariableDefinition):
type: VariableDefinitionType = VariableDefinitionType.LOCAL_VARIABLE
Expand All @@ -227,6 +238,15 @@ def __hash__(self) -> int:
return hash((type(self), self.name, self.type, self.range, self.source))


@dataclass
class GlobalVariableDefinition(VariableDefinition):
type: VariableDefinitionType = VariableDefinitionType.GLOBAL_VARIABLE

@single_call
def __hash__(self) -> int:
return hash((type(self), self.name, self.type, self.range, self.source))


@dataclass
class BuiltInVariableDefinition(VariableDefinition):
type: VariableDefinitionType = VariableDefinitionType.BUILTIN_VARIABLE
Expand All @@ -238,13 +258,13 @@ def __hash__(self) -> int:


@dataclass
class CommandLineVariableDefinition(VariableDefinition):
class CommandLineVariableDefinition(GlobalVariableDefinition):
type: VariableDefinitionType = VariableDefinitionType.COMMAND_LINE_VARIABLE
resolvable: bool = True

@single_call
def __hash__(self) -> int:
return hash((type(self), self.name, self.type))
return hash((type(self), self.name, self.type, self.range, self.source))


@dataclass
Expand Down
40 changes: 37 additions & 3 deletions packages/robot/src/robotcode/robot/diagnostics/namespace.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
Set,
Tuple,
Union,
cast,
)

from robot.errors import VariableError
Expand Down Expand Up @@ -75,13 +76,15 @@
BuiltInVariableDefinition,
CommandLineVariableDefinition,
EnvironmentVariableDefinition,
GlobalVariableDefinition,
Import,
InvalidVariableError,
LibraryEntry,
LibraryImport,
LocalVariableDefinition,
ResourceEntry,
ResourceImport,
TestVariableDefinition,
VariableDefinition,
VariableMatcher,
VariablesEntry,
Expand Down Expand Up @@ -182,18 +185,21 @@ class BlockVariableVisitor(Visitor):
def __init__(
self,
library_doc: LibraryDoc,
global_variables: List[VariableDefinition],
source: str,
position: Optional[Position] = None,
in_args: bool = True,
) -> None:
super().__init__()
self.library_doc = library_doc
self.global_variables = global_variables
self.source = source
self.position = position
self.in_args = in_args

self._results: Dict[str, VariableDefinition] = {}
self.current_kw_doc: Optional[KeywordDoc] = None
self._var_statements_vars: List[VariableDefinition] = []

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

def visit_Var(self, node: Statement) -> None: # noqa: N802
from robot.parsing.model.statements import Var

variable = node.get_token(Token.VARIABLE)
if variable is None:
return
try:
if not is_variable(variable.value):
var_name = variable.value
if var_name.endswith("="):
var_name = var_name[:-1].rstrip()

if not is_variable(var_name):
return

self._results[variable.value] = LocalVariableDefinition(
name=variable.value,
scope = cast(Var, node).scope

if scope in ("SUITE",):
var_type = VariableDefinition
elif scope in ("TEST", "TASK"):
var_type = TestVariableDefinition
elif scope in ("GLOBAL",):
var_type = GlobalVariableDefinition
else:
var_type = LocalVariableDefinition

var = var_type(
name=var_name,
name_token=strip_variable_token(variable),
line_no=variable.lineno,
col_offset=variable.col_offset,
Expand All @@ -401,6 +424,16 @@ def visit_Var(self, node: Statement) -> None: # noqa: N802
source=self.source,
)

self._var_statements_vars.append(var)

if var_name not in self._results or type(self._results[var_name]) != type(var):
if isinstance(var, LocalVariableDefinition) or not any(
l for l in self.global_variables if l.matcher == var.matcher
):
self._results[var_name] = var
else:
self._results.pop(var_name, None)

except VariableError:
pass

Expand Down Expand Up @@ -928,6 +961,7 @@ def yield_variables(
(
BlockVariableVisitor(
self.get_library_doc(),
self.get_global_variables(),
self.source,
position,
isinstance(test_or_keyword_nodes[-1], Arguments) if nodes else False,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@
import os
from collections import defaultdict
from dataclasses import dataclass
from typing import Any, Dict, Iterator, List, Optional, Set, Tuple, Union
from typing import Any, Dict, Iterator, List, Optional, Set, Tuple, Type, Union

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

def visit_Var(self, node: Statement) -> None: # noqa: N802
name_token = node.get_token(Token.VARIABLE)
if name_token is None:
return

name = name_token.value

if name is not None:
match = search_variable(name, ignore_errors=True)
if not match.is_assign(allow_assign_mark=True):
return

if name.endswith("="):
name = name[:-1].rstrip()

r = range_from_token(
strip_variable_token(
Token(
name_token.type,
name,
name_token.lineno,
name_token.col_offset,
name_token.error,
)
)
)
# r.start.character = 0
# r.end.character = 0

var_def = self.namespace.find_variable(
name,
skip_commandline_variables=False,
nodes=self.node_stack,
position=range_from_token(node.get_token(Token.VAR)).start,
ignore_error=True,
)
if var_def is not None:
if var_def.name_range != r:
if self.namespace.document is not None:
self._variable_references[var_def].add(Location(self.namespace.document.document_uri, r))
else:
if self.namespace.document is not None:
self._variable_references[var_def] = set()

def generic_visit(self, node: ast.AST) -> None:
check_current_task_canceled()

super().generic_visit(node)

if get_robot_version() < (7, 0):
variable_statements: Tuple[Type[Any], ...] = (Variable,)
else:
variable_statements = (Variable, robot.parsing.model.statements.Var)

def visit(self, node: ast.AST) -> None:
check_current_task_canceled()

Expand All @@ -231,7 +281,7 @@ def visit(self, node: ast.AST) -> None:
for token1 in (
t
for t in node.tokens
if not (isinstance(node, Variable) and t.type == Token.VARIABLE)
if not (isinstance(node, self.variable_statements) and t.type == Token.VARIABLE)
and t.error is None
and contains_variable(t.value, "$@&%")
):
Expand Down

0 comments on commit a259ec6

Please sign in to comment.