Skip to content

Commit

Permalink
Use AST visitor for extract incomplete block check
Browse files Browse the repository at this point in the history
  • Loading branch information
raymyers committed Jan 7, 2024
1 parent 239eff7 commit 0acd85f
Show file tree
Hide file tree
Showing 3 changed files with 34 additions and 72 deletions.
59 changes: 34 additions & 25 deletions rope/refactor/extract.py
Original file line number Diff line number Diff line change
Expand Up @@ -499,34 +499,15 @@ def multi_line_conditions(self, info):
raise RefactoringError(
"Extracted piece should contain complete statements."
)

if self._is_region_incomplete_block(info):
unbalanced_region_finder = _UnbalancedRegionFinder(
info.region_lines[0], info.region_lines[1]
)
unbalanced_region_finder.visit(info.pymodule.ast_node)
if unbalanced_region_finder.error:
raise RefactoringError(
"Extracted piece cannot contain the start of a block without the end"
"Extracted piece cannot contain the start of a block without the end."
)

def _is_region_incomplete_block(self, info):
"""
Is end more indented than start, and does that level continue outside the region?
If so, this is an incomplete block that cannot be extracted.
"""

def get_effective_indent(lines, line):
if found_line := sourceutils.find_nonblank_line(lines, line):
return sourceutils.get_indents(info.pymodule.lines, found_line)
return None

start_line = info.region_lines[0]
end_line = info.region_lines[1]
start_indent = get_effective_indent(info.pymodule.lines, start_line)
end_indent = get_effective_indent(info.pymodule.lines, end_line)
end_next_indent = get_effective_indent(info.pymodule.lines, end_line + 1)
return (
end_next_indent is not None
and start_indent < end_indent
and end_next_indent >= end_indent
)

def _is_region_on_a_word(self, info):
if (
info.region[0] > 0
Expand Down Expand Up @@ -1122,6 +1103,34 @@ def _ClassDef(self, node):
pass


class _UnbalancedRegionFinder(_BaseErrorFinder):
"""
Flag an error if we are including the start of a block without the end.
We detect this by ensuring there is no AST node that starts inside the
selected range but ends outside of it.
"""

def __init__(self, line_start: int, line_end: int):
self.error = False
self.line_start = line_start
self.line_end = line_end

def generic_visit(self, node: ast.AST):
if not hasattr(node, "end_lineno"):
super().generic_visit(node) # Visit children
return
ends_before_range_starts = node.end_lineno < self.line_start
starts_after_range_ends = node.lineno > self.line_end
if ends_before_range_starts or starts_after_range_ends:
return # Don't visit children
starts_on_or_after_range_start = node.lineno >= self.line_start
ends_after_range_ends = node.end_lineno > self.line_end
if starts_on_or_after_range_start and ends_after_range_ends:
self.error = True
return # Don't visit children
super().generic_visit(node) # Visit children


class _GlobalFinder(ast.RopeNodeVisitor):
def __init__(self):
self.globals_ = OrderedSet()
Expand Down
14 changes: 0 additions & 14 deletions rope/refactor/sourceutils.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from rope.base import codeanalyze
from typing import Optional


def get_indents(lines, lineno):
Expand Down Expand Up @@ -92,16 +91,3 @@ def get_body_region(defined):

def get_indent(project):
return project.prefs.get("indent_size", 4)


def find_nonblank_line(
lines, start_line: int, skip_comments: bool = True
) -> Optional[int]:
"""Return index of first non-blank line starting with start_line, None if not found"""
next_line = start_line
while next_line < lines.length():
line_code = lines.get_line(next_line).strip()
if line_code and (not skip_comments or not line_code.startswith("#")):
return next_line
next_line = next_line + 1
return None
33 changes: 0 additions & 33 deletions ropetest/refactor/extracttest.py
Original file line number Diff line number Diff line change
Expand Up @@ -1208,39 +1208,6 @@ def test_raising_exception_on_incomplete_block_5(self):
with self.assertRaises(rope.base.exceptions.RefactoringError):
self.do_extract_method(code, start, end, "new_func")

def test_no_incomplete_error_for_weird_indentation(self):
code = dedent("""\
def foo():
if foo:
s = \"""
blah blah
blah
\"""
print(
a, b, c
)
""")
start = code.index("s =") + 3
after_first_triple_quote = code.index('"""') + 3
end = code.index('"""', after_first_triple_quote) + 3
self.do_extract_method(code, start, end, "new_func")

def test_no_incomplete_error_for_weird_indentation2(self):
code = dedent("""\
def foo():
print(
a, [
3,
4
],
c
)
""")
start = code.index("[")
end = code.index(']') + 1
print(code[start:end])
self.do_extract_method(code, start, end, "new_func")

def test_extract_method_and_extra_blank_lines(self):
code = dedent("""\
Expand Down

0 comments on commit 0acd85f

Please sign in to comment.