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
Austin Morton

💻 Tobias Krabel
Tobias Krabel

💻 Bago Amirbekian
Bago Amirbekian

💻 + Ray Myers
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("""\