Skip to content

Commit 62f9544

Browse files
committed
fix(analyse): correct handling of keyword arguments in all other keyword settings, regardless of the order of the settings.
closes #240
1 parent 81070ce commit 62f9544

File tree

677 files changed

+36108
-59
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

677 files changed

+36108
-59
lines changed

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,7 @@ def iter_expression_variables_from_token(
258258
nodes: Optional[List[ast.AST]],
259259
position: Optional[Position] = None,
260260
skip_commandline_variables: bool = False,
261+
skip_local_variables: bool = False,
261262
return_not_found: bool = False,
262263
) -> Iterator[Tuple[Token, VariableDefinition]]:
263264
variable_started = False
@@ -270,6 +271,7 @@ def iter_expression_variables_from_token(
270271
nodes,
271272
position,
272273
skip_commandline_variables=skip_commandline_variables,
274+
skip_local_variables=skip_local_variables,
273275
ignore_error=True,
274276
)
275277
sub_token = Token(
@@ -376,6 +378,7 @@ def iter_variables_from_token(
376378
nodes: Optional[List[ast.AST]],
377379
position: Optional[Position] = None,
378380
skip_commandline_variables: bool = False,
381+
skip_local_variables: bool = False,
379382
return_not_found: bool = False,
380383
) -> Iterator[Tuple[Token, VariableDefinition]]:
381384
def is_number(name: str) -> bool:
@@ -405,6 +408,7 @@ def iter_token(
405408
nodes,
406409
position,
407410
skip_commandline_variables=skip_commandline_variables,
411+
skip_local_variables=skip_local_variables,
408412
return_not_found=return_not_found,
409413
):
410414
yield v
@@ -461,6 +465,7 @@ def iter_token(
461465
nodes,
462466
position,
463467
skip_commandline_variables=skip_commandline_variables,
468+
skip_local_variables=skip_local_variables,
464469
ignore_error=True,
465470
)
466471
if var is not None:
@@ -485,6 +490,7 @@ def iter_token(
485490
nodes,
486491
position,
487492
skip_commandline_variables=skip_commandline_variables,
493+
skip_local_variables=skip_local_variables,
488494
ignore_error=True,
489495
)
490496
sub_sub_token = Token(

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

Lines changed: 96 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,8 @@
2626
from robot.errors import VariableError
2727
from robot.libraries import STDLIBS
2828
from robot.parsing.lexer.tokens import Token
29-
from robot.parsing.model.blocks import (
30-
Keyword,
31-
SettingSection,
32-
TestCase,
33-
VariableSection,
34-
)
35-
from robot.parsing.model.statements import Arguments, Statement
29+
from robot.parsing.model.blocks import Keyword, SettingSection, TestCase, VariableSection
30+
from robot.parsing.model.statements import Arguments, Fixture, Statement, Timeout
3631
from robot.parsing.model.statements import LibraryImport as RobotLibraryImport
3732
from robot.parsing.model.statements import ResourceImport as RobotResourceImport
3833
from robot.parsing.model.statements import (
@@ -182,7 +177,7 @@ def visit_Variable(self, node: Statement) -> None: # noqa: N802
182177
)
183178

184179

185-
class BlockVariableVisitor(Visitor):
180+
class VariableVisitorBase(Visitor):
186181
def __init__(
187182
self,
188183
namespace: "Namespace",
@@ -198,7 +193,79 @@ def __init__(
198193

199194
self._results: Dict[str, VariableDefinition] = {}
200195
self.current_kw_doc: Optional[KeywordDoc] = None
196+
self.current_kw: Optional[Keyword] = None
197+
198+
def get_variable_token(self, token: Token) -> Optional[Token]:
199+
return next(
200+
(
201+
v
202+
for v in itertools.dropwhile(
203+
lambda t: t.type in Token.NON_DATA_TOKENS,
204+
tokenize_variables(token, ignore_errors=True, extra_types={Token.VARIABLE}),
205+
)
206+
if v.type == Token.VARIABLE
207+
),
208+
None,
209+
)
210+
211+
212+
class ArgumentVisitor(VariableVisitorBase):
213+
def __init__(
214+
self,
215+
namespace: "Namespace",
216+
nodes: Optional[List[ast.AST]],
217+
position: Optional[Position],
218+
in_args: bool,
219+
current_kw_doc: Optional[KeywordDoc],
220+
) -> None:
221+
super().__init__(namespace, nodes, position, in_args)
222+
223+
self.current_kw_doc: Optional[KeywordDoc] = current_kw_doc
224+
225+
def get(self, model: ast.AST) -> Dict[str, VariableDefinition]:
226+
self._results = {}
227+
228+
self.visit(model)
229+
230+
return self._results
231+
232+
def visit_Arguments(self, node: Statement) -> None: # noqa: N802
233+
args: List[str] = []
234+
235+
arguments = node.get_tokens(Token.ARGUMENT)
236+
237+
for argument_token in arguments:
238+
try:
239+
argument = self.get_variable_token(argument_token)
240+
241+
if argument is not None and argument.value != "@{}":
242+
if (
243+
self.in_args
244+
and self.position is not None
245+
and self.position in range_from_token(argument_token)
246+
and self.position > range_from_token(argument).end
247+
):
248+
break
249+
250+
if argument.value not in args:
251+
args.append(argument.value)
252+
arg_def = ArgumentDefinition(
253+
name=argument.value,
254+
name_token=strip_variable_token(argument),
255+
line_no=argument.lineno,
256+
col_offset=argument.col_offset,
257+
end_line_no=argument.lineno,
258+
end_col_offset=argument.end_col_offset,
259+
source=self.namespace.source,
260+
keyword_doc=self.current_kw_doc,
261+
)
262+
self._results[argument.value] = arg_def
263+
264+
except VariableError:
265+
pass
201266

267+
268+
class OnlyArgumentsVisitor(VariableVisitorBase):
202269
def get(self, model: ast.AST) -> List[VariableDefinition]:
203270
self._results = {}
204271

@@ -211,9 +278,11 @@ def visit(self, node: ast.AST) -> None:
211278
super().visit(node)
212279

213280
def visit_Keyword(self, node: ast.AST) -> None: # noqa: N802
281+
self.current_kw = cast(Keyword, node)
214282
try:
215283
self.generic_visit(node)
216284
finally:
285+
self.current_kw = None
217286
self.current_kw_doc = None
218287

219288
def visit_KeywordName(self, node: Statement) -> None: # noqa: N802
@@ -248,53 +317,15 @@ def visit_KeywordName(self, node: Statement) -> None: # noqa: N802
248317
keyword_doc=self.current_kw_doc,
249318
)
250319

251-
def get_variable_token(self, token: Token) -> Optional[Token]:
252-
return next(
253-
(
254-
v
255-
for v in itertools.dropwhile(
256-
lambda t: t.type in Token.NON_DATA_TOKENS,
257-
tokenize_variables(token, ignore_errors=True, extra_types={Token.VARIABLE}),
258-
)
259-
if v.type == Token.VARIABLE
260-
),
261-
None,
262-
)
263-
264-
def visit_Arguments(self, node: Statement) -> None: # noqa: N802
265-
args: List[str] = []
320+
if self.current_kw is not None:
321+
args = ArgumentVisitor(
322+
self.namespace, self.nodes, self.position, self.in_args, self.current_kw_doc
323+
).get(self.current_kw)
324+
if args:
325+
self._results.update(args)
266326

267-
arguments = node.get_tokens(Token.ARGUMENT)
268327

269-
for argument_token in arguments:
270-
try:
271-
argument = self.get_variable_token(argument_token)
272-
273-
if argument is not None and argument.value != "@{}":
274-
if (
275-
self.in_args
276-
and self.position is not None
277-
and self.position in range_from_token(argument_token)
278-
and self.position > range_from_token(argument).end
279-
):
280-
break
281-
282-
if argument.value not in args:
283-
args.append(argument.value)
284-
arg_def = ArgumentDefinition(
285-
name=argument.value,
286-
name_token=strip_variable_token(argument),
287-
line_no=argument.lineno,
288-
col_offset=argument.col_offset,
289-
end_line_no=argument.lineno,
290-
end_col_offset=argument.end_col_offset,
291-
source=self.namespace.source,
292-
keyword_doc=self.current_kw_doc,
293-
)
294-
self._results[argument.value] = arg_def
295-
296-
except VariableError:
297-
pass
328+
class BlockVariableVisitor(OnlyArgumentsVisitor):
298329

299330
def visit_ExceptHeader(self, node: Statement) -> None: # noqa: N802
300331
variables = node.get_tokens(Token.VARIABLE)[:1]
@@ -990,10 +1021,12 @@ def yield_variables(
9901021
nodes: Optional[List[ast.AST]] = None,
9911022
position: Optional[Position] = None,
9921023
skip_commandline_variables: bool = False,
1024+
skip_local_variables: bool = False,
9931025
) -> Iterator[Tuple[VariableMatcher, VariableDefinition]]:
9941026
yielded: Dict[VariableMatcher, VariableDefinition] = {}
9951027

9961028
test_or_keyword = None
1029+
test_or_keyword_nodes = None
9971030

9981031
if nodes:
9991032
test_or_keyword_nodes = list(
@@ -1004,18 +1037,23 @@ def yield_variables(
10041037
)
10051038
test_or_keyword = test_or_keyword_nodes[0] if test_or_keyword_nodes else None
10061039

1040+
in_args = isinstance(test_or_keyword_nodes[-1], Arguments) if test_or_keyword_nodes else False
1041+
only_args = (
1042+
isinstance(test_or_keyword_nodes[-1], (Arguments, Fixture, Timeout)) if test_or_keyword_nodes else False
1043+
)
1044+
10071045
for var in chain(
10081046
*[
10091047
(
10101048
(
1011-
BlockVariableVisitor(
1049+
(OnlyArgumentsVisitor if only_args else BlockVariableVisitor)(
10121050
self,
10131051
nodes,
10141052
position,
1015-
isinstance(test_or_keyword_nodes[-1], Arguments) if nodes else False,
1053+
in_args,
10161054
).get(test_or_keyword)
10171055
)
1018-
if test_or_keyword is not None
1056+
if test_or_keyword is not None and not skip_local_variables
10191057
else []
10201058
)
10211059
],
@@ -1081,6 +1119,7 @@ def find_variable(
10811119
nodes: Optional[List[ast.AST]] = None,
10821120
position: Optional[Position] = None,
10831121
skip_commandline_variables: bool = False,
1122+
skip_local_variables: bool = False,
10841123
ignore_error: bool = False,
10851124
) -> Optional[VariableDefinition]:
10861125
self.ensure_initialized()
@@ -1105,6 +1144,7 @@ def find_variable(
11051144
nodes,
11061145
position,
11071146
skip_commandline_variables=skip_commandline_variables,
1147+
skip_local_variables=skip_local_variables,
11081148
):
11091149
if matcher == m:
11101150
return v

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

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,14 @@
1212
from robot.parsing.model.blocks import Keyword, TestCase
1313
from robot.parsing.model.statements import (
1414
Arguments,
15+
DefaultTags,
1516
DocumentationOrMetadata,
1617
Fixture,
1718
KeywordCall,
1819
LibraryImport,
1920
ResourceImport,
2021
Statement,
22+
Tags,
2123
Template,
2224
TemplateArguments,
2325
TestCaseName,
@@ -70,9 +72,35 @@
7072

7173
if get_robot_version() < (7, 0):
7274
from robot.variables.search import VariableIterator
75+
76+
VARIABLE_NOT_FOUND_HINT_TYPES: Tuple[Any, ...] = (
77+
DocumentationOrMetadata,
78+
TestCaseName,
79+
Tags,
80+
robot.parsing.model.statements.ForceTags,
81+
DefaultTags,
82+
)
83+
84+
IN_SETTING_TYPES: Tuple[Any, ...] = (
85+
DocumentationOrMetadata,
86+
Tags,
87+
robot.parsing.model.statements.ForceTags,
88+
DefaultTags,
89+
Template,
90+
)
7391
else:
7492
from robot.variables.search import VariableMatches
7593

94+
VARIABLE_NOT_FOUND_HINT_TYPES = (
95+
DocumentationOrMetadata,
96+
TestCaseName,
97+
Tags,
98+
robot.parsing.model.statements.TestTags,
99+
DefaultTags,
100+
)
101+
102+
IN_SETTING_TYPES = (DocumentationOrMetadata, Tags, robot.parsing.model.statements.TestTags, DefaultTags, Template)
103+
76104

77105
@dataclass
78106
class AnalyzerResult:
@@ -266,10 +294,10 @@ def visit(self, node: ast.AST) -> None:
266294

267295
self.node_stack.append(node)
268296
try:
297+
in_setting = isinstance(node, IN_SETTING_TYPES)
298+
269299
severity = (
270-
DiagnosticSeverity.HINT
271-
if isinstance(node, (DocumentationOrMetadata, TestCaseName))
272-
else DiagnosticSeverity.ERROR
300+
DiagnosticSeverity.HINT if isinstance(node, VARIABLE_NOT_FOUND_HINT_TYPES) else DiagnosticSeverity.ERROR
273301
)
274302

275303
if isinstance(node, KeywordCall) and node.keyword:
@@ -295,6 +323,7 @@ def visit(self, node: ast.AST) -> None:
295323
self.node_stack,
296324
range_from_token(token).start,
297325
skip_commandline_variables=False,
326+
skip_local_variables=in_setting,
298327
return_not_found=True,
299328
):
300329
if isinstance(var, VariableNotFoundDefinition):
@@ -380,6 +409,7 @@ def visit(self, node: ast.AST) -> None:
380409
self.node_stack,
381410
range_from_token(token).start,
382411
skip_commandline_variables=False,
412+
skip_local_variables=in_setting,
383413
return_not_found=True,
384414
):
385415
if isinstance(var, VariableNotFoundDefinition):

0 commit comments

Comments
 (0)