Skip to content

Commit b183775

Browse files
authored
Fix unused-variable not handled in TRY branches (#1549)
1 parent f0d390f commit b183775

File tree

10 files changed

+160
-21
lines changed

10 files changed

+160
-21
lines changed

docs/releasenotes/changelog.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Release notes
22

3+
## 7.0.0
4+
5+
### Fixes
6+
7+
- Fix ``unused-variable`` and ``variable-overwritten-before-usage`` rules not reporting violations in ``TRY`` blocks ([issue #1548](https://github.com/MarketSquare/robotframework-robocop/issues/1548))
8+
39
## 6.13.0
410

511
### Features

src/robocop/linter/rules/misc.py

Lines changed: 44 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1140,6 +1140,7 @@ class CachedVariable:
11401140
name: str
11411141
token: Token
11421142
is_used: bool
1143+
current_scopy_only: bool = False
11431144

11441145

11451146
class SectionVariablesCollector(ast.NodeVisitor):
@@ -1306,8 +1307,8 @@ def visit_IfBranch(self, node) -> None: # noqa: N802
13061307

13071308
def add_variables_from_if_to_scope(self, if_variables: dict[str, CachedVariable]) -> None:
13081309
"""
1309-
Add all variables in given IF branch to common scope. If variable is used already in the branch, if it will
1310-
also be mark as used.
1310+
Add all variables in the given IF branch to a common scope. If a variable is used already in the branch, it
1311+
will also be marked as used.
13111312
"""
13121313
if not self.variables:
13131314
self.variables.append(if_variables)
@@ -1338,11 +1339,11 @@ def clear_variables_after_loop(self) -> None:
13381339

13391340
def revisit_variables_used_in_loop(self) -> None:
13401341
"""
1341-
Due to recursive nature of the loops, we need to revisit variables used in the loop again in case
1342-
variable defined in the further part of the loop was used.
1342+
Due to the recursive nature of the loops, we need to revisit variables used in the loop again in case
1343+
the variable defined in the further part of the loop was used.
13431344
1344-
In case of nested FOR/WHILE loops we're storing variables in separate stacks, that are merged until we reach
1345-
outer END.
1345+
In case of nested FOR/WHILE loops, we're storing variables in separate stacks that are merged until we reach
1346+
the outer END.
13461347
13471348
For example:
13481349
@@ -1400,26 +1401,50 @@ def try_assign(try_node) -> str:
14001401
def visit_Try(self, node): # noqa: N802
14011402
if node.errors or node.header.errors:
14021403
return
1403-
for token in node.header.get_tokens(Token.ARGUMENT, Token.OPTION):
1404-
self.find_not_nested_variable(token.value, is_var=False)
1404+
# first gather variables from the TRY node
14051405
self.variables.append({})
1406-
if self.try_assign(node) is not None:
1407-
error_var = node.header.get_token(Token.VARIABLE)
1408-
if error_var is not None:
1409-
self.handle_assign_variable(error_var, ignore_var_conversion=False)
14101406
for item in node.body:
14111407
self.visit(item)
1412-
self.variables.pop()
1413-
if node.next:
1414-
self.visit_Try(node.next)
1408+
try_variables = self.variables.pop()
1409+
branch_variables = []
1410+
try_branch = node.next
1411+
while try_branch:
1412+
self.variables.append({})
1413+
# variables in EXCEPT ${error_pattern}
1414+
for token in try_branch.header.get_tokens(Token.ARGUMENT, Token.OPTION):
1415+
self.find_not_nested_variable(token.value, is_var=False)
1416+
# except AS ${err}
1417+
if self.try_assign(try_branch) is not None:
1418+
error_var = try_branch.header.get_token(Token.VARIABLE)
1419+
if error_var is not None:
1420+
self.handle_assign_variable(error_var, ignore_var_conversion=False)
1421+
for variable in self.variables[-1].values():
1422+
variable.current_scopy_only = True
1423+
# visit body of branch
1424+
for item in try_branch.body:
1425+
self.visit(item)
1426+
branch_variables.append(self.variables.pop())
1427+
try_branch = try_branch.next
1428+
for branch in branch_variables:
1429+
for name, variable in branch.items():
1430+
if variable.current_scopy_only:
1431+
if not variable.is_used:
1432+
self.report_arg_or_var_rule(self.unused_variable, variable.token, variable.name)
1433+
elif name not in try_variables:
1434+
try_variables[name] = variable
1435+
else:
1436+
try_variables[name].is_used = try_variables[name].is_used and variable.is_used
1437+
if not variable.is_used:
1438+
try_variables[name].token = variable.token
1439+
self.add_variables_from_if_to_scope(try_variables)
14151440

14161441
def visit_Group(self, node): # noqa: N802
14171442
for token in node.header.get_tokens(Token.ARGUMENT):
14181443
self.find_not_nested_variable(token.value, is_var=False)
14191444
self.generic_visit(node)
14201445

14211446
def visit_KeywordCall(self, node) -> None: # noqa: N802
1422-
for token in node.get_tokens(Token.ARGUMENT, Token.KEYWORD): # argument can be used in keyword name
1447+
for token in node.get_tokens(Token.ARGUMENT, Token.KEYWORD): # argument can be used in the keyword name
14231448
self.find_not_nested_variable(token.value, is_var=False)
14241449
for token in node.get_tokens(Token.ASSIGN): # we first check args, then assign for used and then overwritten
14251450
self.handle_assign_variable(token)
@@ -1462,7 +1487,7 @@ def handle_assign_variable(self, token, ignore_var_conversion: bool = True) -> N
14621487
variable_scope = self.variables[-1]
14631488
if normalized in variable_scope:
14641489
is_used = variable_scope[normalized].is_used
1465-
if not variable_scope[normalized].is_used and not self.ignore_overwriting:
1490+
if not is_used and not self.ignore_overwriting:
14661491
self.report_arg_or_var_rule(
14671492
self.variable_overwritten_before_usage,
14681493
variable_scope[normalized].token,
@@ -1486,12 +1511,12 @@ def find_not_nested_variable(self, value, is_var) -> None:
14861511
Find and process not nested variable.
14871512
14881513
Search `value` string until there is ${variable} without other variables inside. Unescaped escaped syntax
1489-
($var or \\${var}). If variable does exist in assign variables or arguments, it is removed to denote it was
1514+
($var or \\${var}). If a variable does exist in assign variables or arguments, it is removed to denote it was
14901515
used.
14911516
"""
14921517
try:
14931518
variables = list(VariableMatches(value))
1494-
except VariableError: # for example ${variable which wasn't closed properly
1519+
except VariableError: # for example, ${variable which wasn't closed properly
14951520
return
14961521
if not variables:
14971522
if is_var:

tests/linter/rules/variables/unused_variable/expected_extended.txt

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,46 @@ test.robot:193:9 VAR02 Variable '${var2}' is assigned but not used
176176
194 | END
177177
|
178178

179+
test.robot:244:9 VAR02 Variable '${var1}' is assigned but not used
180+
|
181+
242 | Log ${var_used_only_before} Keyword
182+
243 | TRY
183+
244 | ${var1} Keyword
184+
| ^^^^^^^ VAR02
185+
245 | ${var_used_outside1} Keyword
186+
246 | ${var_used_only_before} Keyword
187+
|
188+
189+
test.robot:246:9 VAR02 Variable '${var_used_only_before}' is assigned but not used
190+
|
191+
244 | ${var1} Keyword
192+
245 | ${var_used_outside1} Keyword
193+
246 | ${var_used_only_before} Keyword
194+
| ^^^^^^^^^^^^^^^^^^^^^^^ VAR02
195+
247 | EXCEPT Error message* AS ${var2}
196+
248 | ${var3} Keyword
197+
|
198+
199+
test.robot:247:36 VAR02 Variable '${var2}' is assigned but not used
200+
|
201+
245 | ${var_used_outside1} Keyword
202+
246 | ${var_used_only_before} Keyword
203+
247 | EXCEPT Error message* AS ${var2}
204+
| ^^^^^^^ VAR02
205+
248 | ${var3} Keyword
206+
249 | ${var_used_outside2} Keyword
207+
|
208+
209+
test.robot:248:9 VAR02 Variable '${var3}' is assigned but not used
210+
|
211+
246 | ${var_used_only_before} Keyword
212+
247 | EXCEPT Error message* AS ${var2}
213+
248 | ${var3} Keyword
214+
| ^^^^^^^ VAR02
215+
249 | ${var_used_outside2} Keyword
216+
250 | END
217+
|
218+
179219
unused_section_vars.robot:21:1 VAR02 Variable '${GLOBAL_NOT_USED}' is assigned but not used
180220
|
181221
19 | *** Variables ***

tests/linter/rules/variables/unused_variable/expected_output_rf3.txt renamed to tests/linter/rules/variables/unused_variable/expected_output_after_rf4.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,20 @@ test.robot:19:15:19:22 [I] VAR02 Variable '${var2}' is assigned but not used
33
test.robot:23:5:23:11 [I] VAR02 Variable '${var}' is assigned but not used
44
test.robot:31:5:31:14 [I] VAR02 Variable '${assign}' is assigned but not used
55
test.robot:39:9:39:20 [I] VAR02 Variable '${not_used}' is assigned but not used
6+
test.robot:42:9:42:26 [I] VAR02 Variable '${used_in_branch}' is assigned but not used
67
test.robot:44:9:44:32 [I] VAR02 Variable '${not_used_from_branch}' is assigned but not used
78
test.robot:56:13:56:30 [I] VAR02 Variable '${nested_define3}' is assigned but not used
89
test.robot:66:12:66:18 [I] VAR02 Variable '${var}' is assigned but not used
910
test.robot:92:12:92:23 [I] VAR02 Variable '${category}' is assigned but not used
1011
test.robot:140:5:140:11 [I] VAR02 Variable '${var}' is assigned but not used
1112
test.robot:143:5:143:14 [I] VAR02 Variable '${assign}' is assigned but not used
13+
test.robot:163:9:163:15 [I] VAR02 Variable '${var}' is assigned but not used
14+
test.robot:170:9:170:15 [I] VAR02 Variable '${var}' is assigned but not used
15+
test.robot:179:9:179:16 [I] VAR02 Variable '${var2}' is assigned but not used
16+
test.robot:184:9:184:15 [I] VAR02 Variable '${var}' is assigned but not used
17+
test.robot:193:9:193:16 [I] VAR02 Variable '${var2}' is assigned but not used
18+
test.robot:244:9:244:16 [I] VAR02 Variable '${var1}' is assigned but not used
19+
test.robot:246:9:246:32 [I] VAR02 Variable '${var_used_only_before}' is assigned but not used
20+
test.robot:247:36:247:43 [I] VAR02 Variable '${var2}' is assigned but not used
21+
test.robot:248:9:248:16 [I] VAR02 Variable '${var3}' is assigned but not used
1222
unused_section_vars.robot:21:1:21:19 [I] VAR02 Variable '${GLOBAL_NOT_USED}' is assigned but not used

tests/linter/rules/variables/unused_variable/expected_output_after_var.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,8 @@ test.robot:170:9:170:15 [I] VAR02 Variable '${var}' is assigned but not used
1717
test.robot:179:9:179:16 [I] VAR02 Variable '${var2}' is assigned but not used
1818
test.robot:184:9:184:15 [I] VAR02 Variable '${var}' is assigned but not used
1919
test.robot:193:9:193:16 [I] VAR02 Variable '${var2}' is assigned but not used
20+
test.robot:244:9:244:16 [I] VAR02 Variable '${var1}' is assigned but not used
21+
test.robot:246:9:246:32 [I] VAR02 Variable '${var_used_only_before}' is assigned but not used
22+
test.robot:247:36:247:43 [I] VAR02 Variable '${var2}' is assigned but not used
23+
test.robot:248:9:248:16 [I] VAR02 Variable '${var3}' is assigned but not used
2024
unused_section_vars.robot:21:1:21:19 [I] VAR02 Variable '${GLOBAL_NOT_USED}' is assigned but not used

tests/linter/rules/variables/unused_variable/expected_output_pre_var.txt renamed to tests/linter/rules/variables/unused_variable/expected_output_rf4.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,7 @@ test.robot:170:9:170:15 [I] VAR02 Variable '${var}' is assigned but not used
1515
test.robot:179:9:179:16 [I] VAR02 Variable '${var2}' is assigned but not used
1616
test.robot:184:9:184:15 [I] VAR02 Variable '${var}' is assigned but not used
1717
test.robot:193:9:193:16 [I] VAR02 Variable '${var2}' is assigned but not used
18+
test.robot:244:9:244:16 [I] VAR02 Variable '${var1}' is assigned but not used
19+
test.robot:246:9:246:32 [I] VAR02 Variable '${var_used_only_before}' is assigned but not used
20+
test.robot:248:9:248:16 [I] VAR02 Variable '${var3}' is assigned but not used
1821
unused_section_vars.robot:21:1:21:19 [I] VAR02 Variable '${GLOBAL_NOT_USED}' is assigned but not used

tests/linter/rules/variables/unused_variable/test.robot

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,3 +237,21 @@ Used in EXCEPT branch
237237
EXCEPT ${var2}
238238
No Operation
239239
END
240+
241+
Except with AS
242+
Log ${var_used_only_before} Keyword
243+
TRY
244+
${var1} Keyword
245+
${var_used_outside1} Keyword
246+
${var_used_only_before} Keyword
247+
EXCEPT Error message* AS ${var2}
248+
${var3} Keyword
249+
${var_used_outside2} Keyword
250+
END
251+
Log ${var_used_outside1} Keyword
252+
Log ${var_used_outside2} Keyword
253+
TRY
254+
No Operation
255+
EXCEPT Error message* AS ${var2}
256+
Log ${var2}
257+
END

tests/linter/rules/variables/unused_variable/test_rule.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,17 @@ def test_extended(self):
2121
def test_rule_pre_var(self):
2222
self.check_rule(
2323
src_files=["test.robot", "unused_section_vars.robot"],
24-
expected_file="expected_output_pre_var.txt",
24+
expected_file="expected_output_after_rf4.txt",
2525
issue_format="end_col",
26-
test_on_version=">=4;<7",
26+
test_on_version=">=5;<7",
27+
)
28+
29+
def test_rule_pre_var_pre_try(self):
30+
self.check_rule(
31+
src_files=["test.robot", "unused_section_vars.robot"],
32+
expected_file="expected_output_rf4.txt",
33+
issue_format="end_col",
34+
test_on_version="==4.*",
2735
)
2836

2937
def test_rule_on_loops(self):

tests/linter/rules/variables/variable_overwritten_before_usage/test_rule.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,6 @@ def test_variable_type_conversion(self):
3636
expected_file="variable_type_conversion_expected.txt",
3737
test_on_version=">=7.3",
3838
)
39+
40+
def test_try_except(self):
41+
self.check_rule(src_files=["try_except.robot"], expected_file=None, test_on_version=">=5")
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
*** Keywords ***
2+
TRY EXCEPT example
3+
${initial} Keyword
4+
TRY
5+
${initial} Keyword
6+
${initial_from_try} Keyword
7+
${initial_from_try2} Keyword
8+
EXCEPT Error message* AS ${error}
9+
${initial_from_except} Keyword
10+
${initial_from_except2} Keyword
11+
FINALLY
12+
${initial_from_finally} Keyword
13+
${initial_from_finally2} Keyword
14+
ELSE
15+
${initial_from_else} Keyword
16+
${initial_from_else2} Keyword
17+
END
18+
${initial_from_try} Keyword
19+
${initial_from_except} Keyword
20+
${initial_from_finally} Keyword
21+
${initial_from_else} Keyword
22+
${error} Keyword

0 commit comments

Comments
 (0)