Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-rope for openSUSE:Factory 
checked in at 2022-02-21 17:46:32
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-rope (Old)
 and      /work/SRC/openSUSE:Factory/.python-rope.new.1958 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-rope"

Mon Feb 21 17:46:32 2022 rev:28 rq:956183 version:0.22.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-rope/python-rope.changes  2021-10-26 
20:15:01.822052170 +0200
+++ /work/SRC/openSUSE:Factory/.python-rope.new.1958/python-rope.changes        
2022-02-21 17:47:36.779612116 +0100
@@ -1,0 +2,9 @@
+Sun Feb 20 21:01:54 UTC 2022 - Dirk M??ller <[email protected]>
+
+- update to 0.22.0:
+  * #443 Implement `yield from` syntax support to patchedast.py
+  * #445, #446 Improve empty tuple and handling of parentheses
+  around tuple
+  * #270, #432 Fix rename import statement with dots and as keyword
+
+-------------------------------------------------------------------

Old:
----
  rope-0.21.0.tar.gz

New:
----
  rope-0.22.0.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-rope.spec ++++++
--- /var/tmp/diff_new_pack.GEtg9P/_old  2022-02-21 17:47:37.351612287 +0100
+++ /var/tmp/diff_new_pack.GEtg9P/_new  2022-02-21 17:47:37.355612288 +0100
@@ -1,7 +1,7 @@
 #
 # spec file for package python-rope
 #
-# Copyright (c) 2021 SUSE LLC
+# Copyright (c) 2022 SUSE LLC
 #
 # All modifications and additions to the file contributed by third parties
 # remain the property of their copyright owners, unless otherwise agreed
@@ -19,7 +19,7 @@
 %define upname rope
 %{?!python_module:%define python_module() python-%{**} python3-%{**}}
 Name:           python-rope
-Version:        0.21.0
+Version:        0.22.0
 Release:        0
 Summary:        A python refactoring library
 License:        LGPL-3.0-or-later

++++++ rope-0.21.0.tar.gz -> rope-0.22.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rope-0.21.0/CHANGELOG.md new/rope-0.22.0/CHANGELOG.md
--- old/rope-0.21.0/CHANGELOG.md        2021-10-18 12:06:02.000000000 +0200
+++ new/rope-0.22.0/CHANGELOG.md        2021-11-22 16:05:08.000000000 +0100
@@ -1,9 +1,26 @@
 # **Upcoming release**
 
-TODO
+## Syntax support
+
+- #443 Implement `yield from` syntax support to patchedast.py
+
+## Bug fixes
+
+- #445, #446 Improve empty tuple and handling of parentheses around tuple
+- #270, #432 Fix rename import statement with dots and as keyword (@climbus)
+
+# Release 0.21.1
+
+Date: 2021-11-11
+
+## Bug fixes
+
+- #441. Start publishing wheel packages to allow offline installs
 
 # Release 0.21.0
 
+Date: 2021-10-18
+
 ## Syntax support
 
 - #392, #316 Handle `global` keyword when extracting method (@climbus)
@@ -28,14 +45,14 @@
 
 ## New feature
 
-- #434 Move read() to FileSystemCommands
+- #434 Move read() to FileSystemCommands (@lieryan)
 
 ## Misc
 
 - #410 Setup all-contributors bot (@lieryan)
 - #404 Blacken source code, rope now follows black code style (@climbus)
-- #399 Add Github Actions to enforce black code style
-- #403 Remove plain 'unittest' only runner
+- #399 Add Github Actions to enforce black code style (@lieryan)
+- #403 Remove plain 'unittest' only runner (@lieryan)
 
 
 # Release 0.20.1
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rope-0.21.0/PKG-INFO new/rope-0.22.0/PKG-INFO
--- old/rope-0.21.0/PKG-INFO    2021-10-18 12:10:41.121533400 +0200
+++ new/rope-0.22.0/PKG-INFO    2021-11-22 16:08:12.375390500 +0100
@@ -1,34 +1,11 @@
 Metadata-Version: 2.1
 Name: rope
-Version: 0.21.0
+Version: 0.22.0
 Summary: a python refactoring library...
 Home-page: https://github.com/python-rope/rope
 Author: Ali Gholami Rudi
 Author-email: [email protected]
 License: LGPL-3.0-or-later
-Description: 
-        
-        .. _GitHub python-rope / rope: https://github.com/python-rope/rope
-        
-        
-        
=========================================================================
-         rope -- the world's most advanced open source Python refactoring 
library
-        
=========================================================================
-        
-        
-        Overview
-        ========
-        
-        `Rope`_ is the world's most advanced open source Python refactoring 
library
-        (yes, I totally stole that tagline from Postgres).
-        
-        .. _`rope`: https://github.com/python-rope/rope
-        
-        
-        Most Python syntax from Python 2.7 up to Python 3.9 is supported. 
Please file bugs and contribute
-        patches if you encounter gaps.
-        
-        
 Platform: UNKNOWN
 Classifier: Development Status :: 4 - Beta
 Classifier: Operating System :: OS Independent
@@ -51,3 +28,29 @@
 Classifier: Topic :: Software Development
 Description-Content-Type: text/x-rst
 Provides-Extra: dev
+License-File: COPYING
+
+
+
+.. _GitHub python-rope / rope: https://github.com/python-rope/rope
+
+
+=========================================================================
+ rope -- the world's most advanced open source Python refactoring library
+=========================================================================
+
+
+Overview
+========
+
+`Rope`_ is the world's most advanced open source Python refactoring library
+(yes, I totally stole that tagline from Postgres).
+
+.. _`rope`: https://github.com/python-rope/rope
+
+
+Most Python syntax from Python 2.7 up to Python 3.9 is supported. Please file 
bugs and contribute
+patches if you encounter gaps.
+
+
+
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rope-0.21.0/docs/release-process.rst 
new/rope-0.22.0/docs/release-process.rst
--- old/rope-0.21.0/docs/release-process.rst    2021-10-18 12:10:18.000000000 
+0200
+++ new/rope-0.22.0/docs/release-process.rst    2021-11-11 14:22:16.000000000 
+0100
@@ -2,6 +2,6 @@
 2. Update CHANGELOG.md
 3. Increment version number in ``rope/__init__.py``
 4. Tag the release with the tag annotation containing the release information, 
e.g. ``git tag -s 0.21.0; git push origin 0.21.0``
-5. ``python3 setup.py sdist``
-6. ``twine upload -s dist/rope-$VERSION.tar.gz*``
+5. ``python3 -m build``
+6. ``twine upload -s dist/rope-$VERSION.{tar.gz,whl}``
 7. Publish to Discussions Announcement
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rope-0.21.0/rope/.ropeproject/config.py 
new/rope-0.22.0/rope/.ropeproject/config.py
--- old/rope-0.21.0/rope/.ropeproject/config.py 1970-01-01 01:00:00.000000000 
+0100
+++ new/rope-0.22.0/rope/.ropeproject/config.py 2021-11-11 14:51:05.000000000 
+0100
@@ -0,0 +1,125 @@
+# The default ``config.py``
+# flake8: noqa
+
+
+def set_prefs(prefs):
+    """This function is called before opening the project"""
+
+    # Specify which files and folders to ignore in the project.
+    # Changes to ignored resources are not added to the history and
+    # VCSs.  Also they are not returned in `Project.get_files()`.
+    # Note that ``?`` and ``*`` match all characters but slashes.
+    # '*.pyc': matches 'test.pyc' and 'pkg/test.pyc'
+    # 'mod*.pyc': matches 'test/mod1.pyc' but not 'mod/1.pyc'
+    # '.svn': matches 'pkg/.svn' and all of its children
+    # 'build/*.o': matches 'build/lib.o' but not 'build/sub/lib.o'
+    # 'build//*.o': matches 'build/lib.o' and 'build/sub/lib.o'
+    prefs["ignored_resources"] = [
+        "*.pyc",
+        "*~",
+        ".ropeproject",
+        ".hg",
+        ".svn",
+        "_svn",
+        ".git",
+        ".tox",
+        ".venv",
+        "venv",
+    ]
+
+    # Specifies which files should be considered python files.  It is
+    # useful when you have scripts inside your project.  Only files
+    # ending with ``.py`` are considered to be python files by
+    # default.
+    # prefs['python_files'] = ['*.py']
+
+    # Custom source folders:  By default rope searches the project
+    # for finding source folders (folders that should be searched
+    # for finding modules).  You can add paths to that list.  Note
+    # that rope guesses project source folders correctly most of the
+    # time; use this if you have any problems.
+    # The folders should be relative to project root and use '/' for
+    # separating folders regardless of the platform rope is running on.
+    # 'src/my_source_folder' for instance.
+    # prefs.add('source_folders', 'src')
+
+    # You can extend python path for looking up modules
+    # prefs.add('python_path', '~/python/')
+
+    # Should rope save object information or not.
+    prefs["save_objectdb"] = True
+    prefs["compress_objectdb"] = False
+
+    # If `True`, rope analyzes each module when it is being saved.
+    prefs["automatic_soa"] = True
+    # The depth of calls to follow in static object analysis
+    prefs["soa_followed_calls"] = 0
+
+    # If `False` when running modules or unit tests "dynamic object
+    # analysis" is turned off.  This makes them much faster.
+    prefs["perform_doa"] = True
+
+    # Rope can check the validity of its object DB when running.
+    prefs["validate_objectdb"] = True
+
+    # How many undos to hold?
+    prefs["max_history_items"] = 32
+
+    # Shows whether to save history across sessions.
+    prefs["save_history"] = True
+    prefs["compress_history"] = False
+
+    # Set the number spaces used for indenting.  According to
+    # :PEP:`8`, it is best to use 4 spaces.  Since most of rope's
+    # unit-tests use 4 spaces it is more reliable, too.
+    prefs["indent_size"] = 4
+
+    # Builtin and c-extension modules that are allowed to be imported
+    # and inspected by rope.
+    prefs["extension_modules"] = []
+
+    # Add all standard c-extensions to extension_modules list.
+    prefs["import_dynload_stdmods"] = True
+
+    # If `True` modules with syntax errors are considered to be empty.
+    # The default value is `False`; When `False` syntax errors raise
+    # `rope.base.exceptions.ModuleSyntaxError` exception.
+    prefs["ignore_syntax_errors"] = False
+
+    # If `True`, rope ignores unresolvable imports.  Otherwise, they
+    # appear in the importing namespace.
+    prefs["ignore_bad_imports"] = False
+
+    # If `True`, rope will insert new module imports as
+    # `from <package> import <module>` by default.
+    prefs["prefer_module_from_imports"] = False
+
+    # If `True`, rope will transform a comma list of imports into
+    # multiple separate import statements when organizing
+    # imports.
+    prefs["split_imports"] = False
+
+    # If `True`, rope will remove all top-level import statements and
+    # reinsert them at the top of the module when making changes.
+    prefs["pull_imports_to_top"] = True
+
+    # If `True`, rope will sort imports alphabetically by module name instead
+    # of alphabetically by import statement, with from imports after normal
+    # imports.
+    prefs["sort_imports_alphabetically"] = False
+
+    # Location of implementation of
+    # rope.base.oi.type_hinting.interfaces.ITypeHintingFactory In general
+    # case, you don't have to change this value, unless you're an rope expert.
+    # Change this value to inject you own implementations of interfaces
+    # listed in module rope.base.oi.type_hinting.providers.interfaces
+    # For example, you can add you own providers for Django Models, or disable
+    # the search type-hinting in a class hierarchy, etc.
+    prefs[
+        "type_hinting_factory"
+    ] = "rope.base.oi.type_hinting.factory.default_type_hinting_factory"
+
+
+def project_opened(project):
+    """This function is called after opening the project"""
+    # Do whatever you like here!
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rope-0.21.0/rope/__init__.py 
new/rope-0.22.0/rope/__init__.py
--- old/rope-0.21.0/rope/__init__.py    2021-10-18 12:07:35.000000000 +0200
+++ new/rope-0.22.0/rope/__init__.py    2021-11-22 16:05:38.000000000 +0100
@@ -1,7 +1,7 @@
 """rope, a python refactoring library"""
 
 INFO = __doc__
-VERSION = "0.21.0"
+VERSION = "0.22.0"
 COPYRIGHT = """\
 Copyright (C) 2021-2021 Lie Ryan
 Copyright (C) 2019-2021 Matej Cepl
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rope-0.21.0/rope/base/worder.py 
new/rope-0.22.0/rope/base/worder.py
--- old/rope-0.21.0/rope/base/worder.py 2021-09-29 00:07:33.000000000 +0200
+++ new/rope-0.22.0/rope/base/worder.py 2021-11-21 14:25:04.000000000 +0100
@@ -3,6 +3,8 @@
 
 import rope.base.simplify
 
+MINIMAL_LEN_FOR_AS = 5
+
 
 def get_name_at(resource, offset):
     source_code = resource.read()
@@ -15,6 +17,12 @@
 
     Note that in these methods, offset should be the index of the
     character not the index of the character after it.
+
+    Some of the methods here doesn't exactly do what their name might lead you
+    to think they do, these probably should be fixed. Refer to
+    ropetest/codeanalyzetest.py for what these methods returns. Note that
+    codeanalyzetest.py documents the current behavior, rather than what they
+    should've been.
     """
 
     def __init__(self, code, handle_ignores=False):
@@ -370,13 +378,24 @@
         ):
             return False
         try:
-            end = self._find_word_end(offset)
+            end = self._find_import_main_part_end(offset)
+            if not self._has_enough_len_for_as(end):
+                return False
             as_end = min(self._find_word_end(end + 1), len(self.code))
             as_start = self._find_word_start(as_end)
             return self.code[as_start : as_end + 1] == "as"
         except ValueError:
             return False
 
+    def _has_enough_len_for_as(self, end):
+        return len(self.code) > end + MINIMAL_LEN_FOR_AS
+
+    def _find_import_main_part_end(self, offset):
+        end = self._find_word_end(offset)
+        while len(self.code) > end + 2 and self.code[end + 1] == ".":
+            end = self._find_word_end(end + 2)
+        return end
+
     def is_a_name_after_from_import(self, offset):
         try:
             if len(self.code) > offset and self.code[offset] == "\n":
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rope-0.21.0/rope/refactor/move.py 
new/rope-0.22.0/rope/refactor/move.py
--- old/rope-0.21.0/rope/refactor/move.py       2021-10-09 15:13:14.000000000 
+0200
+++ new/rope-0.22.0/rope/refactor/move.py       2021-11-21 14:25:04.000000000 
+0100
@@ -234,7 +234,7 @@
         self.old_pyname = evaluate.eval_location(this_pymodule, offset)
         if self.old_pyname is None:
             raise exceptions.RefactoringError(
-                "Move refactoring should be performed on a " 
"class/function/variable."
+                "Move refactoring should be performed on a 
class/function/variable."
             )
         if self._is_variable(self.old_pyname):
             self.old_name = worder.get_name_at(resource, offset)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rope-0.21.0/rope/refactor/patchedast.py 
new/rope-0.22.0/rope/refactor/patchedast.py
--- old/rope-0.21.0/rope/refactor/patchedast.py 2021-10-18 11:33:57.000000000 
+0200
+++ new/rope-0.22.0/rope/refactor/patchedast.py 2021-11-22 04:56:26.000000000 
+0100
@@ -82,6 +82,7 @@
     exec_close_paren_or_space = object()
     exec_in_or_comma = object()
     with_or_comma_context_manager = object()
+    empty_tuple = object()
 
     def __call__(self, node):
         method = getattr(self, "_" + node.__class__.__name__, None)
@@ -128,6 +129,8 @@
                     )
                 elif child is self.Number:
                     region = self.source.consume_number()
+                elif child == self.empty_tuple:
+                    region = self.source.consume_empty_tuple()
                 elif child == "!=":
                     # INFO: This has been added to handle deprecated ``<>``
                     region = self.source.consume_not_equal()
@@ -844,7 +847,7 @@
         if node.elts:
             self._handle(node, self._child_nodes(node.elts, ","), 
eat_parens=True)
         else:
-            self._handle(node, ["(", ")"])
+            self._handle(node, [self.empty_tuple])
 
     def _UnaryOp(self, node):
         children = self._get_op(node.op)
@@ -863,6 +866,10 @@
             children.append(node.value)
         self._handle(node, children)
 
+    def _YieldFrom(self, node):
+        children = ["yield", "from", node.value]
+        self._handle(node, children)
+
     def _While(self, node):
         children = ["while", node.test, ":"]
         children.extend(node.body)
@@ -946,6 +953,9 @@
         repattern = _Source._number_pattern
         return self._consume_pattern(repattern)
 
+    def consume_empty_tuple(self):
+        return self._consume_pattern(re.compile(r"\(\s*\)"))
+
     def consume_not_equal(self):
         if _Source._not_equals_pattern is None:
             _Source._not_equals_pattern = re.compile(r"<>|!=")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rope-0.21.0/rope.egg-info/PKG-INFO 
new/rope-0.22.0/rope.egg-info/PKG-INFO
--- old/rope-0.21.0/rope.egg-info/PKG-INFO      2021-10-18 12:10:40.000000000 
+0200
+++ new/rope-0.22.0/rope.egg-info/PKG-INFO      2021-11-22 16:08:12.000000000 
+0100
@@ -1,34 +1,11 @@
 Metadata-Version: 2.1
 Name: rope
-Version: 0.21.0
+Version: 0.22.0
 Summary: a python refactoring library...
 Home-page: https://github.com/python-rope/rope
 Author: Ali Gholami Rudi
 Author-email: [email protected]
 License: LGPL-3.0-or-later
-Description: 
-        
-        .. _GitHub python-rope / rope: https://github.com/python-rope/rope
-        
-        
-        
=========================================================================
-         rope -- the world's most advanced open source Python refactoring 
library
-        
=========================================================================
-        
-        
-        Overview
-        ========
-        
-        `Rope`_ is the world's most advanced open source Python refactoring 
library
-        (yes, I totally stole that tagline from Postgres).
-        
-        .. _`rope`: https://github.com/python-rope/rope
-        
-        
-        Most Python syntax from Python 2.7 up to Python 3.9 is supported. 
Please file bugs and contribute
-        patches if you encounter gaps.
-        
-        
 Platform: UNKNOWN
 Classifier: Development Status :: 4 - Beta
 Classifier: Operating System :: OS Independent
@@ -51,3 +28,29 @@
 Classifier: Topic :: Software Development
 Description-Content-Type: text/x-rst
 Provides-Extra: dev
+License-File: COPYING
+
+
+
+.. _GitHub python-rope / rope: https://github.com/python-rope/rope
+
+
+=========================================================================
+ rope -- the world's most advanced open source Python refactoring library
+=========================================================================
+
+
+Overview
+========
+
+`Rope`_ is the world's most advanced open source Python refactoring library
+(yes, I totally stole that tagline from Postgres).
+
+.. _`rope`: https://github.com/python-rope/rope
+
+
+Most Python syntax from Python 2.7 up to Python 3.9 is supported. Please file 
bugs and contribute
+patches if you encounter gaps.
+
+
+
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rope-0.21.0/rope.egg-info/SOURCES.txt 
new/rope-0.22.0/rope.egg-info/SOURCES.txt
--- old/rope-0.21.0/rope.egg-info/SOURCES.txt   2021-10-18 12:10:40.000000000 
+0200
+++ new/rope-0.22.0/rope.egg-info/SOURCES.txt   2021-11-22 16:08:12.000000000 
+0100
@@ -16,6 +16,7 @@
 rope.egg-info/dependency_links.txt
 rope.egg-info/requires.txt
 rope.egg-info/top_level.txt
+rope/.ropeproject/config.py
 rope/base/__init__.py
 rope/base/arguments.py
 rope/base/ast.py
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rope-0.21.0/rope.egg-info/requires.txt 
new/rope-0.22.0/rope.egg-info/requires.txt
--- old/rope-0.21.0/rope.egg-info/requires.txt  2021-10-18 12:10:40.000000000 
+0200
+++ new/rope-0.22.0/rope.egg-info/requires.txt  2021-11-22 16:08:12.000000000 
+0100
@@ -1,4 +1,5 @@
 
 [dev]
+build
 pytest
 pytest-timeout
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rope-0.21.0/ropetest/codeanalyzetest.py 
new/rope-0.22.0/ropetest/codeanalyzetest.py
--- old/rope-0.21.0/ropetest/codeanalyzetest.py 2021-10-09 17:08:05.000000000 
+0200
+++ new/rope-0.22.0/ropetest/codeanalyzetest.py 2021-11-21 14:25:04.000000000 
+0100
@@ -13,12 +13,6 @@
 
 
 class SourceLinesAdapterTest(unittest.TestCase):
-    def setUp(self):
-        super(SourceLinesAdapterTest, self).setUp()
-
-    def tearDown(self):
-        super(SourceLinesAdapterTest, self).tearDown()
-
     def test_source_lines_simple(self):
         to_lines = SourceLinesAdapter("line1\nline2\n")
         self.assertEqual("line1", to_lines.get_line(1))
@@ -51,17 +45,79 @@
 
 
 class WordRangeFinderTest(unittest.TestCase):
-    def setUp(self):
-        super(WordRangeFinderTest, self).setUp()
-
-    def tearDown(self):
-        super(WordRangeFinderTest, self).tearDown()
-
     def _find_primary(self, code, offset):
         word_finder = worder.Worder(code)
         result = word_finder.get_primary_at(offset)
         return result
 
+    def _annotated_code(self, annotated_code):
+        """
+        Split annotated code into raw code and annotation.
+
+        Odd lines in `annotated_code` is the actual Python code.
+
+        Even lines in `annotated_code` are single-char annotation for the
+        previous line.
+
+        The annotation may contain one extra character which annotates the
+        newline/end of line character.
+        """
+        code_lines = annotated_code.splitlines()[::2]
+        annotations_lines = annotated_code.splitlines()[1::2]
+        if len(annotations_lines) < len(code_lines):
+            annotations_lines.append("")
+        for idx, (line, line_ann) in enumerate(zip(code_lines, 
annotations_lines)):
+            newline_ann_char = 1  # for annotation of the end of line character
+            self.assertLessEqual(
+                len(line_ann),
+                len(line) + newline_ann_char,
+                msg="Extra character in annotations",
+            )
+            line_ann = line_ann.rstrip()
+            line_ann += " " * (len(line) - len(line_ann))
+            if len(line_ann) != len(line) + newline_ann_char:
+                line_ann += " "
+            self.assertEqual(len(line_ann), len(line) + newline_ann_char)
+            annotations_lines[idx] = line_ann
+        code, annotations = "\n".join(code_lines), "\n".join(annotations_lines)
+        if code[-1] != "\n":
+            annotations = annotations[:-1]
+        self.assertEqual(len(code) + code.count("\n"), len(annotations))
+        return code, annotations
+
+    def _make_offset_annotation(self, code, func):
+        """
+        Create annotation by calling `func(offset)` for every offset in `code`.
+
+        For example, when the annotated code looks like so:
+
+            import a.b.c.d
+                   ++++++++
+
+        This means that `func(offset)` returns True whenever offset points to
+        the 'a.b.c.d' part and returns False everywhere else.
+        """
+
+        def _annotation_char(offset):
+            ann_char = "+" if func(offset) else " "
+            if code[offset] == "\n":
+                ann_char = ann_char + "\n"
+            return ann_char
+
+        return "".join([_annotation_char(offset) for offset in 
range(len(code))])
+
+    def assert_equal_annotation(self, code, expected, actual):
+        if expected != actual:
+            msg = ["Annotation does not match:\n"]
+            for line, line_exp, line_actual in zip(
+                code.splitlines(), expected.splitlines(), actual.splitlines()
+            ):
+                msg.append("  " + line + "\n")
+                if line_exp != line_actual:
+                    msg.append("e " + line_exp + "\n")
+                    msg.append("a " + line_actual + "\n")
+            self.fail("".join(msg))
+
     def test_keyword_before_parens(self):
         code = dedent("""\
             if (a_var).an_attr:
@@ -224,7 +280,33 @@
         code = '"" + # var2.\n  var3'
         self.assertEqual("var3", self._find_primary(code, 21))
 
-    def test_import_statement_finding(self):
+    def test_is_import_statement(self):
+        code, annotations = self._annotated_code(annotated_code=dedent("""\
+            import a.b.c.d
+                   ++++++++
+            from a.b import c
+
+            import a.b.c.d as d
+                   +++++++++++++
+            from a.b import c as e
+
+            from a.b import (
+
+                abc
+
+            )
+
+            result = a.b.c.d.f()
+
+        """))
+        word_finder = worder.Worder(code)
+        self.assert_equal_annotation(
+            code,
+            annotations,
+            self._make_offset_annotation(code, 
word_finder.is_import_statement),
+        )
+
+    def test_is_import_statement_finding(self):
         code = dedent("""\
             import mod
             a_var = 10
@@ -233,7 +315,7 @@
         self.assertTrue(word_finder.is_import_statement(code.index("mod") + 1))
         self.assertFalse(word_finder.is_import_statement(code.index("a_var") + 
1))
 
-    def test_import_statement_finding2(self):
+    def test_is_import_statement_finding2(self):
         code = dedent("""\
             import a.b.c.d
             result = a.b.c.d.f()
@@ -308,6 +390,120 @@
         word_finder = worder.Worder(code)
         self.assertTrue(word_finder.is_assigned_here(0))
 
+    def test_is_from_statement(self):
+        code, annotations = self._annotated_code(annotated_code=dedent("""\
+            import a.b.c.d
+
+            from a.b import c
+                 +++++++++++++
+            import a.b.c.d as d
+
+            from a.b import c as e
+                 ++++++++++++++++++
+            from a.b import (
+                 +++++++++++++
+                abc
+            ++++++++
+            )
+            ++
+            result = a.b.c.d.f()
+
+        """))
+        word_finder = worder.Worder(code)
+        self.assert_equal_annotation(
+            code,
+            annotations,
+            self._make_offset_annotation(code, word_finder.is_from_statement),
+        )
+
+    def test_is_from_statement_module(self):
+        code, annotations = self._annotated_code(annotated_code=dedent("""\
+            import a.b.c.d
+
+            from a.b import c
+                +++++
+            import a.b.c.d as d
+
+            from a.b import c as e
+                +++++
+            from a.b import (
+                +++++
+                abc
+
+            )
+
+            result = a.b.c.d.f()
+
+        """))
+        word_finder = worder.Worder(code)
+        self.assert_equal_annotation(
+            code,
+            annotations,
+            self._make_offset_annotation(code, 
word_finder.is_from_statement_module),
+        )
+
+    def test_is_import_statement_aliased_module(self):
+        code, annotations = self._annotated_code(annotated_code=dedent("""\
+            import a.b.c.d
+
+            from a.b import c
+
+            import a.b.c.d as d
+                   +++++++
+            from a.b import c as e
+
+            from a.b import (
+
+                abc
+
+            )
+
+            import mod1, \\
+
+                mod2 as c, mod3, mod4 as d
+               +++++            +++++
+            result = a.b.c.d.f()
+
+        """))
+        word_finder = worder.Worder(code)
+        self.assert_equal_annotation(
+            code,
+            annotations,
+            self._make_offset_annotation(
+                code, word_finder.is_import_statement_aliased_module
+            ),
+        )
+
+    def test_is_from_aliased(self):
+        code, annotations = self._annotated_code(annotated_code=dedent("""\
+            import a.b.c.d
+
+            from a.b import c
+
+            import a.b.c.d as d
+
+            from a.b import c as e
+                           ++
+            from a.b import (
+
+                abc
+
+            )
+
+            from a.b import mod1, \\
+
+                mod2 as c, mod3, mod4 as d
+               +++++            +++++
+            result = a.b.c.d.f()
+
+        """))
+        word_finder = worder.Worder(code)
+        self.assert_equal_annotation(
+            code,
+            annotations,
+            self._make_offset_annotation(code, word_finder.is_from_aliased),
+        )
+
     def test_is_from_with_from_import_and_multiline_parens(self):
         code = "from mod import \\\n  (f,\n  g, h)\n"
         word_finder = worder.Worder(code)
@@ -318,7 +514,31 @@
         word_finder = worder.Worder(code)
         self.assertTrue(word_finder.is_from_statement(code.rindex("g")))
 
-    def test_one_letter_function_keyword_arguments(self):
+    def test_is_function_keyword_parameter(self):
+        code, annotations = self._annotated_code(annotated_code=dedent("""\
+            func(param=1)
+                ++++++
+            func(
+
+                param=1
+               ++++++
+            )
+
+            def func(param=1):
+                    ++++++
+                pass
+
+        """))
+        word_finder = worder.Worder(code)
+        self.assert_equal_annotation(
+            code,
+            annotations,
+            self._make_offset_annotation(
+                code, word_finder.is_function_keyword_parameter
+            ),
+        )
+
+    def test_one_letter_is_function_keyword_parameter(self):
         code = "f(p=1)\n"
         word_finder = worder.Worder(code)
         index = code.rindex("p")
@@ -362,7 +582,20 @@
             code.index("("), finder.find_parens_start_from_inside(len(code) - 
1)
         )
 
-    def test_is_on_function_keyword(self):
+    def test_is_on_function_call_keyword(self):
+        code, annotations = self._annotated_code(annotated_code=dedent("""\
+            myfunc(va
+                  +++
+        """))
+
+        finder = worder.Worder(code)
+        self.assert_equal_annotation(
+            code,
+            annotations,
+            self._make_offset_annotation(code, 
finder.is_on_function_call_keyword),
+        )
+
+    def test_is_on_function_keyword_partial(self):
         code = "myfunc(va"
         finder = worder.Worder(code)
         self.assertTrue(finder.is_on_function_call_keyword(len(code) - 1))
@@ -474,12 +707,10 @@
     def test_modules_after_from_statements(self):
         root_folder = self.project.root
         mod = testutils.create_module(self.project, "mod", root_folder)
-        mod.write(
-            dedent("""\
-                def a_func():
-                    pass
-            """)
-        )
+        mod.write(dedent("""\
+            def a_func():
+                pass
+        """))
         code = "from mod import a_func\n"
         scope = libutils.get_string_scope(self.project, code)
         name_finder = rope.base.evaluate.ScopeNameFinder(scope.pyobject)
@@ -489,12 +720,10 @@
 
     def test_renaming_functions_with_from_import_and_parens(self):
         mod1 = testutils.create_module(self.project, "mod1")
-        mod1.write(
-            dedent("""\
-                def afunc():
-                    pass
-            """)
-        )
+        mod1.write(dedent("""\
+            def afunc():
+                pass
+        """))
         code = dedent("""\
             from mod1 import (
                 afunc as func)
@@ -512,12 +741,10 @@
         pkg2 = testutils.create_package(self.project, "pkg2", pkg1)
         mod1 = testutils.create_module(self.project, "mod1", pkg1)
         mod2 = testutils.create_module(self.project, "mod2", pkg2)
-        mod1.write(
-            dedent("""\
-                def a_func():
-                    pass
-            """)
-        )
+        mod1.write(dedent("""\
+            def a_func():
+                pass
+        """))
         code = "from ..mod1 import a_func\n"
         mod2.write(code)
         mod2_scope = self.project.get_pymodule(mod2).get_scope()
@@ -597,12 +824,6 @@
 
 
 class LogicalLineFinderTest(unittest.TestCase):
-    def setUp(self):
-        super(LogicalLineFinderTest, self).setUp()
-
-    def tearDown(self):
-        super(LogicalLineFinderTest, self).tearDown()
-
     def _logical_finder(self, code):
         return LogicalLineFinder(SourceLinesAdapter(code))
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rope-0.21.0/ropetest/refactor/patchedasttest.py 
new/rope-0.22.0/ropetest/refactor/patchedasttest.py
--- old/rope-0.21.0/ropetest/refactor/patchedasttest.py 2021-10-18 
11:33:57.000000000 +0200
+++ new/rope-0.22.0/ropetest/refactor/patchedasttest.py 2021-11-21 
15:46:44.000000000 +0100
@@ -1046,6 +1046,30 @@
         checker = _ResultChecker(self, ast_frag)
         checker.check_children("Tuple", ["Num", "", ",", " ", "Num"])
 
+    def test_tuple_with_complex_parentheses1(self):
+        source = "a = ( # (he\n ((((), None))))\n"
+        ast_frag = patchedast.get_patched_ast(source, True)
+        checker = _ResultChecker(self, ast_frag)
+        checker.check_children(
+            "Tuple", ["(", "", "Tuple", "", ",", " ", NameConstant, "", ")"]
+        )
+
+    def test_tuple_with_complex_parentheses2(self):
+        source = "a = ( # (he\n ((((('a')), ('b')))))\n"
+        ast_frag = patchedast.get_patched_ast(source, True)
+        checker = _ResultChecker(self, ast_frag)
+        checker.check_children(
+            "Tuple", ["(", "", "((", "Str", "))", ",", " (", "Str", ")", "", 
")"]
+        )
+
+    def test_tuple_with_complex_parentheses3(self):
+        source = "a = ((), (([],), []),)"
+        ast_frag = patchedast.get_patched_ast(source, True)
+        checker = _ResultChecker(self, ast_frag)
+        checker.check_children(
+            "Tuple", ["(", "", "Tuple", "", ",", " ", "Tuple", ",", ")"]
+        )
+
     def test_one_item_tuple_node(self):
         source = "(1,)\n"
         ast_frag = patchedast.get_patched_ast(source, True)
@@ -1056,7 +1080,23 @@
         source = "()\n"
         ast_frag = patchedast.get_patched_ast(source, True)
         checker = _ResultChecker(self, ast_frag)
-        checker.check_children("Tuple", ["(", "", ")"])
+        checker.check_children("Tuple", ["()"])
+
+    def test_empty_tuple_node2(self):
+        source = "a = ((), None)\n"
+        ast_frag = patchedast.get_patched_ast(source, True)
+        checker = _ResultChecker(self, ast_frag)
+        checker.check_children(
+            "Tuple", ["(", "", "Tuple", "", ",", " ", NameConstant, "", ")"]
+        )
+
+    def test_empty_tuple_node3(self):
+        source = "a = (), None\n"
+        ast_frag = patchedast.get_patched_ast(source, True)
+        checker = _ResultChecker(self, ast_frag)
+        checker.check_children(
+            "Tuple", ["Tuple", "", ",", " ", NameConstant]
+        )
 
     def test_yield_node(self):
         source = dedent("""\
@@ -1067,6 +1107,16 @@
         checker = _ResultChecker(self, ast_frag)
         checker.check_children("Yield", ["yield", " ", NameConstant])
 
+    @testutils.only_for_versions_higher("3.3")
+    def test_yield_from_node(self):
+        source = dedent("""\
+            def f(lst):
+                yield from lst
+        """)
+        ast_frag = patchedast.get_patched_ast(source, True)
+        checker = _ResultChecker(self, ast_frag)
+        checker.check_children("YieldFrom", ["yield", " ", "from", " ", 
"Name"])
+
     def test_while_node(self):
         source = dedent("""\
             while True:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rope-0.21.0/ropetest/refactor/renametest.py 
new/rope-0.22.0/ropetest/refactor/renametest.py
--- old/rope-0.21.0/ropetest/refactor/renametest.py     2021-10-09 
17:08:05.000000000 +0200
+++ new/rope-0.22.0/ropetest/refactor/renametest.py     2021-11-14 
17:35:04.000000000 +0100
@@ -1510,6 +1510,42 @@
         """)
         self.assertEqual(refactored, expected)
 
+    def test_renaming_modules_aliased_with_dots(self):
+        pkg = testutils.create_package(self.project, "json")
+        mod1 = testutils.create_module(self.project, "utils", pkg)
+
+        mod2 = testutils.create_module(self.project, "mod2")
+        mod2.write(
+            dedent(
+                """\
+                import json.utils as stdlib_json_utils
+            """
+            )
+        )
+        self._rename(pkg, None, "new_json")
+        self.assertTrue(
+            not mod1.exists() and self.project.find_module("new_json.utils") 
is not None
+        )
+        self.assertEqual("import new_json.utils as stdlib_json_utils\n", 
mod2.read())
+
+    def test_renaming_modules_aliased_many_dots(self):
+        pkg = testutils.create_package(self.project, "json")
+        mod1 = testutils.create_module(self.project, "utils", pkg)
+
+        mod2 = testutils.create_module(self.project, "mod2")
+        mod2.write(
+            dedent(
+                """\
+                import json.utils.a as stdlib_json_utils
+            """
+            )
+        )
+        self._rename(pkg, None, "new_json")
+        self.assertTrue(
+            not mod1.exists() and self.project.find_module("new_json.utils") 
is not None
+        )
+        self.assertEqual("import new_json.utils.a as stdlib_json_utils\n", 
mod2.read())
+
 
 class ChangeOccurrencesTest(unittest.TestCase):
     def setUp(self):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rope-0.21.0/ropetest/refactor/restructuretest.py 
new/rope-0.22.0/ropetest/refactor/restructuretest.py
--- old/rope-0.21.0/ropetest/refactor/restructuretest.py        2021-10-09 
14:03:59.000000000 +0200
+++ new/rope-0.22.0/ropetest/refactor/restructuretest.py        2021-11-21 
14:25:04.000000000 +0100
@@ -265,3 +265,28 @@
         refactoring = restructure.Restructure(self.project, "${a}", "${a}")
         self.project.do(refactoring.get_changes())
         self.assertEqual(mod_text, self.mod.read())
+
+    @testutils.only_for_versions_higher("3.3")
+    def test_yield_from(self):
+        mod_text = dedent("""\
+            def f(lst):
+                yield from lst
+        """)
+        self.mod.write(mod_text)
+        refactoring = restructure.Restructure(
+            self.project,
+            "yield from ${a}",
+            dedent("""\
+                for it in ${a}:
+                   yield it"""),
+        )
+        self.project.do(refactoring.get_changes())
+        self.assertEqual(
+            dedent("""\
+            def f(lst):
+                for it in lst:
+                   yield it
+            """),
+            self.mod.read(),
+        )
+
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rope-0.21.0/setup.py new/rope-0.22.0/setup.py
--- old/rope-0.21.0/setup.py    2021-10-09 14:03:59.000000000 +0200
+++ new/rope-0.22.0/setup.py    2021-11-11 14:22:16.000000000 +0100
@@ -73,6 +73,7 @@
     classifiers=classifiers,
     extras_require={
         "dev": [
+            "build",
             "pytest",
             "pytest-timeout",
         ]

Reply via email to