Skip to content

Commit fe47c9a

Browse files
committed
add 'max-returns' linter check, closes #4
1 parent f2f5701 commit fe47c9a

File tree

4 files changed

+140
-3
lines changed

4 files changed

+140
-3
lines changed

gdtoolkit/common/ast.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,43 @@
55
from .utils import find_name_token_among_children, find_tree_among_children
66

77

8+
# pylint: disable=too-few-public-methods
9+
class Statement:
10+
"""Abstract representation of statement"""
11+
12+
def __init__(self, node: Tree):
13+
self.lark_node = node
14+
self.kind = node.data
15+
self.sub_statements = [] # type: List[Statement]
16+
self.all_sub_statements = [] # type: List[Statement]
17+
18+
self._load_sub_statements()
19+
20+
def _load_sub_statements(self):
21+
if self.kind == "class_def":
22+
pass
23+
elif self.kind == "property_body_def":
24+
pass
25+
elif self.kind in ["func_def", "static_func_def"]:
26+
self.sub_statements = [Statement(n) for n in self.lark_node.children[1:]]
27+
elif self.kind == "if_stmt":
28+
for branch in self.lark_node.children:
29+
if branch.data in ["if_branch", "elif_branch"]:
30+
self.sub_statements += [Statement(n) for n in branch.children[1:]]
31+
else:
32+
self.sub_statements += [Statement(n) for n in branch.children]
33+
elif self.kind == "while_stmt":
34+
pass
35+
elif self.kind == "for_stmt":
36+
pass
37+
elif self.kind == "match_stmt":
38+
pass
39+
for sub_statement in self.sub_statements:
40+
self.all_sub_statements += [
41+
sub_statement
42+
] + sub_statement.all_sub_statements
43+
44+
845
# pylint: disable=too-few-public-methods
946
class Parameter:
1047
"""Abstract representation of function parameter"""
@@ -13,6 +50,7 @@ def __init__(self, node: Tree):
1350
self.name = node.children[0].value
1451

1552

53+
# TODO: inherit from statement
1654
# pylint: disable=too-few-public-methods
1755
class Function:
1856
"""Abstract representation of function"""
@@ -34,6 +72,8 @@ def _load_data_from_func_def(self, func_def: Tree) -> None:
3472
for c in func_args.children # type: ignore
3573
if c.data != "trailing_comma"
3674
]
75+
slf = Statement(self.lark_node)
76+
self.all_statements = [slf] + slf.all_sub_statements
3777

3878

3979
# pylint: disable=too-few-public-methods

gdtoolkit/linter/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@
7373
# useless-super-delegation
7474
# design checks
7575
# max-locals
76-
# max-returns
76+
"max-returns": 6,
7777
# max-branches
7878
# max-statements
7979
# max-attributes

gdtoolkit/linter/design_checks.py

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ def lint(parse_tree: Tree, config: MappingProxyType) -> List[Problem]:
1717
"max-public-methods",
1818
partial(_max_public_methods_check, config["max-public-methods"]),
1919
),
20+
(
21+
"max-returns",
22+
partial(_max_returns_check, config["max-returns"]),
23+
),
2024
(
2125
"function-arguments-number",
2226
partial(_function_args_num_check, config["function-arguments-number"]),
@@ -33,13 +37,12 @@ def lint(parse_tree: Tree, config: MappingProxyType) -> List[Problem]:
3337
def _function_args_num_check(threshold: int, ast: AbstractSyntaxTree) -> List[Problem]:
3438
problems = []
3539
for function in ast.all_functions:
36-
func_name = function.name
3740
if len(function.parameters) > threshold:
3841
problems.append(
3942
Problem(
4043
name="function-arguments-number",
4144
description='Function "{}" has more than {} arguments'.format(
42-
func_name, threshold
45+
function.name, threshold
4346
),
4447
line=function.lark_node.line,
4548
column=function.lark_node.column,
@@ -71,3 +74,25 @@ def _max_public_methods_check(threshold: int, ast: AbstractSyntaxTree) -> List[P
7174
)
7275
)
7376
return problems
77+
78+
79+
def _max_returns_check(threshold: int, ast: AbstractSyntaxTree) -> List[Problem]:
80+
problems = []
81+
for function in ast.all_functions:
82+
returns = [
83+
statement
84+
for statement in function.all_statements
85+
if statement.kind == "return_stmt"
86+
]
87+
if len(returns) > threshold:
88+
problems.append(
89+
Problem(
90+
name="max-returns",
91+
description='Function "{}" has more than {} arguments'.format(
92+
function.name, threshold
93+
),
94+
line=returns[-1].lark_node.line,
95+
column=returns[-1].lark_node.column,
96+
)
97+
)
98+
return problems

tests/linter/test_design_checks.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,3 +141,75 @@ def test_max_public_methods_ok(code):
141141
])
142142
def test_max_public_methods_nok(code):
143143
simple_nok_check(code, 'max-public-methods', line=1)
144+
145+
146+
@pytest.mark.parametrize('code', [
147+
"""
148+
func foo():
149+
if 1 > 2:
150+
return 1
151+
elif 1:
152+
return 1
153+
elif 1:
154+
return 1
155+
elif 1:
156+
return 1
157+
elif 1:
158+
return 1
159+
elif 1:
160+
return 1
161+
""",
162+
"""
163+
func foo():
164+
if 1 > 2:
165+
if 1:
166+
if 1:
167+
if 1:
168+
return 1
169+
return 1
170+
return 1
171+
elif 1:
172+
return 1
173+
return 1
174+
""",
175+
])
176+
def test_max_returns_ok(code):
177+
simple_ok_check(code, disable=["no-elif-return"])
178+
179+
180+
@pytest.mark.parametrize('code', [
181+
"""
182+
func foo():
183+
if 1 > 2:
184+
return 1
185+
elif 1:
186+
return 1
187+
elif 1:
188+
return 1
189+
elif 1:
190+
return 1
191+
elif 1:
192+
return 1
193+
elif 1:
194+
return 1
195+
return 1
196+
""",
197+
"""
198+
func foo():
199+
if 1 > 2:
200+
if 1:
201+
if 1:
202+
if 1:
203+
return 1
204+
return 1
205+
return 1
206+
elif 1:
207+
return 1
208+
return 1
209+
return 1
210+
211+
return 1
212+
""",
213+
])
214+
def test_max_returns_nok(code):
215+
simple_nok_check(code, 'max-returns', line=15, disable=['no-elif-return'])

0 commit comments

Comments
 (0)