Skip to content

Commit 0acd85f

Browse files
committed
Use AST visitor for extract incomplete block check
1 parent 239eff7 commit 0acd85f

3 files changed

Lines changed: 34 additions & 72 deletions

File tree

‎rope/refactor/extract.py‎

Lines changed: 34 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -499,34 +499,15 @@ def multi_line_conditions(self, info):
499499
raise RefactoringError(
500500
"Extracted piece should contain complete statements."
501501
)
502-
503-
if self._is_region_incomplete_block(info):
502+
unbalanced_region_finder = _UnbalancedRegionFinder(
503+
info.region_lines[0], info.region_lines[1]
504+
)
505+
unbalanced_region_finder.visit(info.pymodule.ast_node)
506+
if unbalanced_region_finder.error:
504507
raise RefactoringError(
505-
"Extracted piece cannot contain the start of a block without the end"
508+
"Extracted piece cannot contain the start of a block without the end."
506509
)
507510

508-
def _is_region_incomplete_block(self, info):
509-
"""
510-
Is end more indented than start, and does that level continue outside the region?
511-
If so, this is an incomplete block that cannot be extracted.
512-
"""
513-
514-
def get_effective_indent(lines, line):
515-
if found_line := sourceutils.find_nonblank_line(lines, line):
516-
return sourceutils.get_indents(info.pymodule.lines, found_line)
517-
return None
518-
519-
start_line = info.region_lines[0]
520-
end_line = info.region_lines[1]
521-
start_indent = get_effective_indent(info.pymodule.lines, start_line)
522-
end_indent = get_effective_indent(info.pymodule.lines, end_line)
523-
end_next_indent = get_effective_indent(info.pymodule.lines, end_line + 1)
524-
return (
525-
end_next_indent is not None
526-
and start_indent < end_indent
527-
and end_next_indent >= end_indent
528-
)
529-
530511
def _is_region_on_a_word(self, info):
531512
if (
532513
info.region[0] > 0
@@ -1122,6 +1103,34 @@ def _ClassDef(self, node):
11221103
pass
11231104

11241105

1106+
class _UnbalancedRegionFinder(_BaseErrorFinder):
1107+
"""
1108+
Flag an error if we are including the start of a block without the end.
1109+
We detect this by ensuring there is no AST node that starts inside the
1110+
selected range but ends outside of it.
1111+
"""
1112+
1113+
def __init__(self, line_start: int, line_end: int):
1114+
self.error = False
1115+
self.line_start = line_start
1116+
self.line_end = line_end
1117+
1118+
def generic_visit(self, node: ast.AST):
1119+
if not hasattr(node, "end_lineno"):
1120+
super().generic_visit(node) # Visit children
1121+
return
1122+
ends_before_range_starts = node.end_lineno < self.line_start
1123+
starts_after_range_ends = node.lineno > self.line_end
1124+
if ends_before_range_starts or starts_after_range_ends:
1125+
return # Don't visit children
1126+
starts_on_or_after_range_start = node.lineno >= self.line_start
1127+
ends_after_range_ends = node.end_lineno > self.line_end
1128+
if starts_on_or_after_range_start and ends_after_range_ends:
1129+
self.error = True
1130+
return # Don't visit children
1131+
super().generic_visit(node) # Visit children
1132+
1133+
11251134
class _GlobalFinder(ast.RopeNodeVisitor):
11261135
def __init__(self):
11271136
self.globals_ = OrderedSet()

‎rope/refactor/sourceutils.py‎

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
from rope.base import codeanalyze
2-
from typing import Optional
32

43

54
def get_indents(lines, lineno):
@@ -92,16 +91,3 @@ def get_body_region(defined):
9291

9392
def get_indent(project):
9493
return project.prefs.get("indent_size", 4)
95-
96-
97-
def find_nonblank_line(
98-
lines, start_line: int, skip_comments: bool = True
99-
) -> Optional[int]:
100-
"""Return index of first non-blank line starting with start_line, None if not found"""
101-
next_line = start_line
102-
while next_line < lines.length():
103-
line_code = lines.get_line(next_line).strip()
104-
if line_code and (not skip_comments or not line_code.startswith("#")):
105-
return next_line
106-
next_line = next_line + 1
107-
return None

‎ropetest/refactor/extracttest.py‎

Lines changed: 0 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1208,39 +1208,6 @@ def test_raising_exception_on_incomplete_block_5(self):
12081208
with self.assertRaises(rope.base.exceptions.RefactoringError):
12091209
self.do_extract_method(code, start, end, "new_func")
12101210

1211-
def test_no_incomplete_error_for_weird_indentation(self):
1212-
code = dedent("""\
1213-
def foo():
1214-
if foo:
1215-
s = \"""
1216-
blah blah
1217-
blah
1218-
\"""
1219-
print(
1220-
a, b, c
1221-
)
1222-
""")
1223-
start = code.index("s =") + 3
1224-
after_first_triple_quote = code.index('"""') + 3
1225-
end = code.index('"""', after_first_triple_quote) + 3
1226-
self.do_extract_method(code, start, end, "new_func")
1227-
1228-
def test_no_incomplete_error_for_weird_indentation2(self):
1229-
code = dedent("""\
1230-
def foo():
1231-
print(
1232-
a, [
1233-
3,
1234-
4
1235-
],
1236-
c
1237-
)
1238-
""")
1239-
start = code.index("[")
1240-
end = code.index(']') + 1
1241-
print(code[start:end])
1242-
self.do_extract_method(code, start, end, "new_func")
1243-
12441211
def test_extract_method_and_extra_blank_lines(self):
12451212
code = dedent("""\
12461213

0 commit comments

Comments
 (0)