diff --git a/.all-contributorsrc b/.all-contributorsrc
index c20f45d2e..285a2a785 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -546,6 +546,15 @@
"contributions": [
"code"
]
+ },
+ {
+ "login": "raymyers",
+ "name": "Ray Myers",
+ "avatar_url": "https://avatars.githubusercontent.com/u/3324?v=4",
+ "profile": "http://mender.ai",
+ "contributions": [
+ "code"
+ ]
}
],
"projectName": "rope",
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ab59f07b4..7dff4e180 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,9 +1,14 @@
# **Upcoming release**
+
- #516 Autoimport Now automatically detects project dependencies and can read TOML configuration
+
+# Release 1.12.0
+
- #733 skip directories with perm error when building autoimport index (@MrBago)
- #722, #723 Remove site-packages from packages search tree (@tkrabel)
- #738 Implement os.PathLike on Resource (@lieryan)
- #739, #736 Ensure autoimport requests uses indexes (@lieryan)
+- #734, #735 raise exception when extracting the start of a block without the end
# Release 1.11.0
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
index 044b952f1..3a689621e 100644
--- a/CONTRIBUTORS.md
+++ b/CONTRIBUTORS.md
@@ -82,6 +82,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
 Austin Morton 💻 |
 Tobias Krabel 💻 |
 Bago Amirbekian 💻 |
+  Ray Myers 💻 |
diff --git a/docs/release-process.rst b/docs/release-process.rst
index 0f05bc979..d73d69b1e 100644
--- a/docs/release-process.rst
+++ b/docs/release-process.rst
@@ -12,14 +12,15 @@ Release
1. Ensure tickets assigned to Milestones are up to date
2. Update ``CHANGELOG.md``
-3. Increment version number in ``pyproject.toml``
-4. `git commit && git push`
-5. Tag the release with the tag annotation containing the release information,
+3. Close milestone
+4. Increment version number in ``pyproject.toml``
+5. `git commit && git push`
+6. Tag the release with the tag annotation containing the release information,
``python bin/tag-release.py``
-6. ``python3 -m build``
-7. ``twine upload dist/rope-$VERSION.{tar.gz,whl}``
-8. Publish to Discussions Announcement
-9. Close milestone
+7. ``python3 -m build``
+8. ``twine upload dist/rope-$VERSION.{tar.gz,whl}``
+9. Publish to Discussions Announcement
+10. Create Github Release
Release Schedule
diff --git a/pyproject.toml b/pyproject.toml
index f35459cfd..0df77c259 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -21,7 +21,7 @@ classifiers = [
'Programming Language :: Python :: 3.12',
'Topic :: Software Development',
]
-version = '1.11.0'
+version = '1.12.0'
dependencies = ['pytoolconfig[global] >= 1.2.2']
[[project.authors]]
diff --git a/rope/refactor/extract.py b/rope/refactor/extract.py
index c16c7b0c1..1b1659fad 100644
--- a/rope/refactor/extract.py
+++ b/rope/refactor/extract.py
@@ -444,8 +444,10 @@ def __call__(self, info):
def base_conditions(self, info):
if info.region[1] > info.scope_region[1]:
raise RefactoringError("Bad region selected for extract method")
+
end_line = info.region_lines[1]
end_scope = info.global_scope.get_inner_scope_for_line(end_line)
+
if end_scope != info.scope and end_scope.get_end() != end_line:
raise RefactoringError("Bad region selected for extract method")
try:
@@ -497,6 +499,14 @@ def multi_line_conditions(self, info):
raise RefactoringError(
"Extracted piece should contain complete statements."
)
+ 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."
+ )
def _is_region_on_a_word(self, info):
if (
@@ -1093,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()
diff --git a/ropetest/refactor/extracttest.py b/ropetest/refactor/extracttest.py
index ed4054c06..e8c2fc206 100644
--- a/ropetest/refactor/extracttest.py
+++ b/ropetest/refactor/extracttest.py
@@ -1149,6 +1149,64 @@ def xxx_test_raising_exception_on_function_parens(self):
end = code.rindex(")") + 1
with self.assertRaises(rope.base.exceptions.RefactoringError):
self.do_extract_method(code, start, end, "new_func")
+
+ def test_raising_exception_on_incomplete_block(self):
+ code = dedent("""\
+ if True:
+ a = 1
+ b = 2
+ """)
+ start = code.index("if")
+ end = code.index("1") + 1
+ with self.assertRaises(rope.base.exceptions.RefactoringError):
+ self.do_extract_method(code, start, end, "new_func")
+
+ def test_raising_exception_on_incomplete_block_2(self):
+ code = dedent("""\
+ if True:
+ a = 1
+ #
+ b = 2
+ """)
+ start = code.index("if")
+ end = code.index("1") + 1
+ with self.assertRaises(rope.base.exceptions.RefactoringError):
+ self.do_extract_method(code, start, end, "new_func")
+
+ def test_raising_exception_on_incomplete_block_3(self):
+ code = dedent("""\
+ if True:
+ a = 1
+
+ b = 2
+ """)
+ start = code.index("if")
+ end = code.index("1") + 1
+ with self.assertRaises(rope.base.exceptions.RefactoringError):
+ self.do_extract_method(code, start, end, "new_func")
+
+ def test_raising_exception_on_incomplete_block_4(self):
+ code = dedent("""\
+ #
+ if True:
+ a = 1
+ b = 2
+ """)
+ start = code.index("#")
+ end = code.index("1") + 1
+ with self.assertRaises(rope.base.exceptions.RefactoringError):
+ self.do_extract_method(code, start, end, "new_func")
+
+ def test_raising_exception_on_incomplete_block_5(self):
+ code = dedent("""\
+ if True:
+ if 0:
+ a = 1
+ """)
+ start = code.index("if")
+ end = code.index("0:") + 2
+ with self.assertRaises(rope.base.exceptions.RefactoringError):
+ self.do_extract_method(code, start, end, "new_func")
def test_extract_method_and_extra_blank_lines(self):
code = dedent("""\