Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PR: Add comments & docstrings related to call_for_nodes #642

Merged
merged 11 commits into from
Jan 4, 2023
13 changes: 12 additions & 1 deletion rope/base/ast.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,18 @@ def parse(source, filename="<string>", *args, **kwargs): # type: ignore


def call_for_nodes(node, callback):
"""If callback returns `True` the child nodes are skipped"""
"""
Pre-order depth-first traversal of AST nodes, calling `callback(node)` for
each node visited.

When each node is visited, `callback(node)` will be called with the visited
`node`, then its children node will be visited.

If `callback(node)` returns `True` for a node, then the descendants of that
node will not be visited.

See _ResultChecker._find_node for an example.
"""
result = callback(node)
if not result:
for child in ast.iter_child_nodes(node):
Expand Down
2 changes: 2 additions & 0 deletions rope/refactor/similarfinder.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,8 @@ def __init__(self, body, pattern, does_match):
def find_matches(self):
if self.matches is None:
self.matches = []
# _check_nodes always returns None, so
# call_for_nodes traverses self.body's entire tree.
ast.call_for_nodes(self.body, self._check_node)
return self.matches

Expand Down
26 changes: 26 additions & 0 deletions ropetest/refactor/patchedasttest.py
Original file line number Diff line number Diff line change
Expand Up @@ -1488,6 +1488,32 @@ def check_region(self, text, start, end):
self.test_case.assertEqual((start, end), node.region)

def _find_node(self, text):
"""
Find the node in `self.ast` whose type is named in `text`.

:param text: ast node name

Generally, the test should only have a single matching node, as it make
the test much harder to understand when there may be multiple matches.

If `self.ast` contains more than one nodes that matches `text`, then
the **outer-most last match** takes precedence.

For example, given that we are looking for `ast.Call` node:

checker._find_node("Call")

and given that `self.ast` is the AST for this code:

func_a(1, func_b(2, 3)) + func_c(4, func_d(5, 6))

the outer-most last match would be the ast node representing this bit:

func_c(4, func_d(5, 6))

Note that the order of traversal is based on the order of ast nodes,
which usually, but not always, match textual order.
"""
goal = text
if not isinstance(text, (tuple, list)):
goal = [text]
Expand Down