Skip to content

Commit

Permalink
Add node_ancestors method (#1169)
Browse files Browse the repository at this point in the history
I've been thinking about ways to get rid of unbounded `while` loops in
the Pylint codebase. A common use is to loop over a node's ancestors.
The `node_ancestors` method centralizes this logic in one place so
that those `while` loops can be rewritten as `for`.

A few uses are made of this new method. It only comes up a few places
in Astroid, but there are many more in Pylint.
  • Loading branch information
nickdrozd committed Sep 13, 2021
1 parent 8fbef58 commit 9322fc3
Show file tree
Hide file tree
Showing 3 changed files with 13 additions and 12 deletions.
2 changes: 2 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ Release date: TBA

Closes PyCQA/pylint#3887

* Add ``node_ancestors`` method to ``NodeNG`` for obtaining the ancestors of nodes.

What's New in astroid 2.7.4?
============================
Release date: TBA
Expand Down
12 changes: 3 additions & 9 deletions astroid/nodes/node_classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,17 +109,14 @@ def are_exclusive(stmt1, stmt2, exceptions: Optional[typing.List[str]] = None) -
# index stmt1's parents
stmt1_parents = {}
children = {}
node = stmt1.parent
previous = stmt1
while node:
for node in stmt1.node_ancestors():
stmt1_parents[node] = 1
children[node] = previous
previous = node
node = node.parent
# climb among stmt2's parents until we find a common parent
node = stmt2.parent
previous = stmt2
while node:
for node in stmt2.node_ancestors():
if node in stmt1_parents:
# if the common parent is a If or TryExcept statement, look if
# nodes are in exclusive branches
Expand Down Expand Up @@ -162,7 +159,6 @@ def are_exclusive(stmt1, stmt2, exceptions: Optional[typing.List[str]] = None) -
return previous is not children[node]
return False
previous = node
node = node.parent
return False


Expand Down Expand Up @@ -4719,9 +4715,7 @@ def const_factory(value):

def is_from_decorator(node):
"""Return True if the given node is the child of a decorator"""
parent = node.parent
while parent is not None:
for parent in node.node_ancestors():
if isinstance(parent, Decorators):
return True
parent = parent.parent
return False
11 changes: 8 additions & 3 deletions astroid/nodes/node_ng.py
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,13 @@ def last_child(self) -> Optional["NodeNG"]:
return attr
return None

def node_ancestors(self) -> Iterator["NodeNG"]:
"""Yield parent, grandparent, etc until there are no more."""
parent = self.parent
while parent is not None:
yield parent
parent = parent.parent

def parent_of(self, node):
"""Check if this node is the parent of the given node.
Expand All @@ -218,11 +225,9 @@ def parent_of(self, node):
False otherwise.
:rtype: bool
"""
parent = node.parent
while parent is not None:
for parent in node.node_ancestors():
if self is parent:
return True
parent = parent.parent
return False

def statement(self):
Expand Down

0 comments on commit 9322fc3

Please sign in to comment.