Skip to content
This repository was archived by the owner on Jan 15, 2025. It is now read-only.

Commit d3d42f8

Browse files
committed
exhaustive deps are off by default
idom has an upcoming change that will allow it to infer hook dependencies automatically
1 parent ce1ead2 commit d3d42f8

File tree

7 files changed

+106
-33
lines changed

7 files changed

+106
-33
lines changed

README.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,44 @@ Run the tests
2222
```bash
2323
tox
2424
```
25+
26+
# Errors
27+
28+
`ROH2**` errors can be enabled with the `--exhaustive-hook-deps` flag or setting
29+
`exhaustive_hook_deps = True` in your `flake8` config.
30+
31+
<table>
32+
<tr>
33+
<th>Code</th>
34+
<th>Message</th>
35+
</tr>
36+
<tr>
37+
<td>ROH100</td>
38+
<td>Hook is defined as a closure</td>
39+
</tr>
40+
<tr>
41+
<td>ROH101</td>
42+
<td>Hook was used outside component or hook definition</td>
43+
</tr>
44+
<tr>
45+
<td>ROH102</td>
46+
<td>Hook was used inside a conditional or loop statement</td>
47+
</tr>
48+
<tr>
49+
<td>ROH200</td>
50+
<td>
51+
A hook's dependency is not destructured - dependencies should be refered to
52+
directly, not via an attribute or key of an object
53+
</td>
54+
</tr>
55+
<tr>
56+
<td>ROH201</td>
57+
<td>Hook dependency args should be a literal list, tuple or None</td>
58+
</tr>
59+
<tr>
60+
<td>ROH202</td>
61+
<td>
62+
Hook dependency is not specified
63+
</td>
64+
</tr>
65+
</table>

flake8_idom_hooks/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,6 @@
1010
__version__ = "0.0.0"
1111

1212
from .flake8_plugin import Plugin
13+
from .run import run_checks
1314

14-
__all__ = ["Plugin"]
15+
__all__ = ["Plugin", "run_checks"]

flake8_idom_hooks/exhaustive_deps.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ def _get_dependency_names_from_expression(
131131
# own linter doesn't do this we'll just take the easy route for now:
132132
# https://github.com/facebook/react/issues/16265
133133
self._save_error(
134-
201,
134+
200,
135135
elt,
136136
(
137137
f"dependency arg of {hook_name!r} is not destructured - "
@@ -144,14 +144,16 @@ def _get_dependency_names_from_expression(
144144
isinstance(dependency_expr, ast.Constant) and dependency_expr.value is None
145145
):
146146
self._save_error(
147-
202,
147+
201,
148148
dependency_expr,
149149
(
150150
f"dependency args of {hook_name!r} should be a literal list, tuple, "
151151
f"or None - not expression type {type(dependency_expr).__name__!r}"
152152
),
153153
)
154154
return None
155+
else:
156+
return set()
155157

156158

157159
class _MissingNameFinder(ErrorVisitor):
@@ -178,7 +180,7 @@ def visit_Name(self, node: ast.Name) -> None:
178180
self.used_deps.add(node_id)
179181
else:
180182
self._save_error(
181-
203,
183+
202,
182184
node,
183185
(
184186
f"dependency {node_id!r} of function {self._func_name!r} "

flake8_idom_hooks/flake8_plugin.py

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,38 @@
1+
from __future__ import annotations
2+
13
import ast
4+
from argparse import Namespace
25

3-
from typing import List, Tuple, Type
6+
from flake8.options.manager import OptionManager
47

58
from flake8_idom_hooks import __version__
6-
7-
from .utils import ErrorVisitor
8-
from .rules_of_hooks import RulesOfHooksVisitor
9-
from .exhaustive_deps import ExhaustiveDepsVisitor
9+
from flake8_idom_hooks.run import run_checks
1010

1111

1212
class Plugin:
1313

1414
name = __name__
1515
version = __version__
1616

17-
_visitor_types: List[Type[ErrorVisitor]] = [
18-
RulesOfHooksVisitor,
19-
ExhaustiveDepsVisitor,
20-
]
17+
@classmethod
18+
def add_options(cls, option_manager: OptionManager) -> None:
19+
option_manager.add_option(
20+
"--exhaustive-hook-deps",
21+
action="store_true",
22+
default=False,
23+
dest="exhaustive_hook_deps",
24+
parse_from_config=True,
25+
)
26+
27+
@classmethod
28+
def parse_options(cls, options: Namespace) -> None:
29+
cls.options = options
2130

2231
def __init__(self, tree: ast.Module) -> None:
2332
self._tree = tree
2433

25-
def run(self) -> List[Tuple[int, int, str, Type["Plugin"]]]:
26-
errors = []
27-
for vtype in self._visitor_types:
28-
visitor = vtype()
29-
visitor.visit(self._tree)
30-
errors.extend(visitor.errors)
31-
return [(line, col, msg, self.__class__) for line, col, msg in errors]
34+
def run(self) -> list[tuple[int, int, str, type[Plugin]]]:
35+
return [
36+
error + (self.__class__,)
37+
for error in run_checks(self._tree, self.options.exhaustive_hook_deps)
38+
]

flake8_idom_hooks/run.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
from __future__ import annotations
2+
3+
import ast
4+
5+
from .exhaustive_deps import ExhaustiveDepsVisitor
6+
from .rules_of_hooks import RulesOfHooksVisitor
7+
8+
9+
def run_checks(
10+
tree: ast.Module,
11+
exhaustive_hook_deps: bool,
12+
) -> list[tuple[int, int, str]]:
13+
visitor_types = [RulesOfHooksVisitor]
14+
if exhaustive_hook_deps:
15+
visitor_types.append(ExhaustiveDepsVisitor)
16+
17+
errors: list[tuple[int, int, str]] = []
18+
for vtype in visitor_types:
19+
visitor = vtype()
20+
visitor.visit(tree)
21+
errors.extend(visitor.errors)
22+
23+
return errors

tests/hook_usage_test_cases.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ def CheckEffects():
116116

117117
use_effect(
118118
lambda: (
119-
# error: ROH203 dependency 'x' of function 'lambda' is not specified in declaration of 'use_effect'
119+
# error: ROH202 dependency 'x' of function 'lambda' is not specified in declaration of 'use_effect'
120120
x
121121
+ y
122122
),
@@ -125,41 +125,41 @@ def CheckEffects():
125125

126126
use_effect(
127127
lambda: (
128-
# error: ROH203 dependency 'x' of function 'lambda' is not specified in declaration of 'use_effect'
128+
# error: ROH202 dependency 'x' of function 'lambda' is not specified in declaration of 'use_effect'
129129
x
130130
)
131131
)
132132

133133
use_effect(
134134
lambda: (
135-
# error: ROH203 dependency 'x' of function 'lambda' is not specified in declaration of 'use_effect'
135+
# error: ROH202 dependency 'x' of function 'lambda' is not specified in declaration of 'use_effect'
136136
x.y
137137
),
138138
[
139-
# error: ROH201 dependency arg of 'use_effect' is not destructured - dependencies should be refered to directly, not via an attribute or key of an object
139+
# error: ROH200 dependency arg of 'use_effect' is not destructured - dependencies should be refered to directly, not via an attribute or key of an object
140140
x.y
141141
],
142142
)
143143

144144
module.use_effect(
145145
lambda: (
146-
# error: ROH203 dependency 'x' of function 'lambda' is not specified in declaration of 'use_effect'
146+
# error: ROH202 dependency 'x' of function 'lambda' is not specified in declaration of 'use_effect'
147147
x
148148
),
149149
[],
150150
)
151151

152152
use_effect(
153153
lambda: (
154-
# error: ROH203 dependency 'x' of function 'lambda' is not specified in declaration of 'use_effect'
154+
# error: ROH202 dependency 'x' of function 'lambda' is not specified in declaration of 'use_effect'
155155
x
156156
),
157157
args=[],
158158
)
159159

160160
use_effect(
161161
function=lambda: (
162-
# error: ROH203 dependency 'x' of function 'lambda' is not specified in declaration of 'use_effect'
162+
# error: ROH202 dependency 'x' of function 'lambda' is not specified in declaration of 'use_effect'
163163
x
164164
),
165165
args=[],
@@ -171,7 +171,7 @@ def my_effect():
171171

172172
@use_effect(args=[])
173173
def my_effect():
174-
# error: ROH203 dependency 'x' of function 'my_effect' is not specified in declaration of 'use_effect'
174+
# error: ROH202 dependency 'x' of function 'my_effect' is not specified in declaration of 'use_effect'
175175
x
176176

177177
@use_effect(args=[])
@@ -182,7 +182,7 @@ def my_effect(*args, **kwargs):
182182

183183
@module.use_effect(args=[])
184184
def my_effect():
185-
# error: ROH203 dependency 'x' of function 'my_effect' is not specified in declaration of 'use_effect'
185+
# error: ROH202 dependency 'x' of function 'my_effect' is not specified in declaration of 'use_effect'
186186
x
187187

188188
@not_a_decorator_we_care_about
@@ -200,7 +200,7 @@ def impropper_usage_of_effect_as_decorator():
200200

201201
use_effect(
202202
lambda: None,
203-
# error: ROH202 dependency args of 'use_effect' should be a literal list, tuple, or None - not expression type 'Name'
203+
# error: ROH201 dependency args of 'use_effect' should be a literal list, tuple, or None - not expression type 'Name'
204204
not_a_list_or_tuple,
205205
)
206206

tests/test_flake8_idom_hooks.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import ast
22
from pathlib import Path
33

4-
from flake8_idom_hooks import Plugin
4+
from flake8_idom_hooks import run_checks
55

66

77
def test_flake8_idom_hooks():
@@ -20,5 +20,4 @@ def test_flake8_idom_hooks():
2020
col_offset = len(line) - len(lstrip_line)
2121
message = line.replace("# error:", "", 1).strip()
2222
expected_errors.add((lineno, col_offset, message))
23-
actual_errors = Plugin(tree).run()
24-
assert {(ln, col, msg) for ln, col, msg, p_type in actual_errors} == expected_errors
23+
assert set(run_checks(tree, exhaustive_hook_deps=True)) == expected_errors

0 commit comments

Comments
 (0)