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 2021-04-19 21:05:50 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-rope (Old) and /work/SRC/openSUSE:Factory/.python-rope.new.12324 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-rope" Mon Apr 19 21:05:50 2021 rev:24 rq:886499 version:0.19.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-rope/python-rope.changes 2021-04-17 00:01:52.825604976 +0200 +++ /work/SRC/openSUSE:Factory/.python-rope.new.12324/python-rope.changes 2021-04-19 21:06:08.084041550 +0200 @@ -1,0 +2,33 @@ +Sun Apr 18 20:56:15 UTC 2021 - Matej Cepl <mc...@suse.com> + +- Update to 0.19.0: + - fixes #337 + - Fix AttributeError lineno + - Python 3.9 ast changes + - create_generate with goal_resource param + - Fix relative import offset calculation + - Fix missinge lineno attribute for AssignedName ast node + - Added _NamedExpr into `patchedast.py` + - Add support for the walrus operator. + - fix test case name for `test_ann_assign_node_without_target` + - Returned _AnnAssign and checked for support assignment without value + - fixed version restriction in tests for NamedExpr + - Removed AnnAssign, added NeamedExpr, testa are made + - Added _AnnAsign into `patchedast.py` + - Extract augmented assignment + - Fix handling of dict rename in Python 2.x + - Improve handling of generalized dict unpacking during dict rename + - Add expected failure test for comprehension variable scopes + - Implement basic scoping and rename for set and dict comprehension + - Visit subexpressions of comprehensions to collect names for scopes + - Implement rename of inline assignment expression + - Implement basic scoping and renaming of list and generator + comprehension loop variables + - Implement f-string extract refactoring + - Refactor consume_joined_string and also fix missing + ast.JoinedStr/FormattedValue in older python + - Fix some f-string corner cases + - Implement PEP-448 generalized dict-unpacking +- Removed upstreamed rope-pr333-py39.patch. + +------------------------------------------------------------------- Old: ---- rope-0.18.0.tar.gz rope-pr333-py39.patch New: ---- rope-0.19.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-rope.spec ++++++ --- /var/tmp/diff_new_pack.SDAZjv/_old 2021-04-19 21:06:08.580042293 +0200 +++ /var/tmp/diff_new_pack.SDAZjv/_new 2021-04-19 21:06:08.584042299 +0200 @@ -19,15 +19,13 @@ %define upname rope %{?!python_module:%define python_module() python-%{**} python3-%{**}} Name: python-rope -Version: 0.18.0 +Version: 0.19.0 Release: 0 Summary: A python refactoring library License: LGPL-3.0-or-later Group: Development/Languages/Python URL: https://github.com/python-rope/rope Source: https://files.pythonhosted.org/packages/source/r/rope/rope-%{version}.tar.gz -# PATCH-FIX-UPSTREAM rope-pr333-py39.patch gh#python-rope/rope#333 -Patch1: rope-pr333-py39.patch BuildRequires: %{python_module pytest} BuildRequires: %{python_module setuptools} BuildRequires: fdupes ++++++ rope-0.18.0.tar.gz -> rope-0.19.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rope-0.18.0/PKG-INFO new/rope-0.19.0/PKG-INFO --- old/rope-0.18.0/PKG-INFO 2020-10-07 18:08:40.468835800 +0200 +++ new/rope-0.19.0/PKG-INFO 2021-04-18 22:17:27.277263200 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: rope -Version: 0.18.0 +Version: 0.19.0 Summary: a python refactoring library... Home-page: https://github.com/python-rope/rope Author: Ali Gholami Rudi @@ -51,5 +51,6 @@ Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 Classifier: Topic :: Software Development Provides-Extra: dev diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rope-0.18.0/rope/__init__.py new/rope-0.19.0/rope/__init__.py --- old/rope-0.18.0/rope/__init__.py 2020-10-07 18:08:26.000000000 +0200 +++ new/rope-0.19.0/rope/__init__.py 2021-04-18 22:17:05.000000000 +0200 @@ -1,9 +1,9 @@ """rope, a python refactoring library""" INFO = __doc__ -VERSION = '0.18.0' +VERSION = '0.19.0' COPYRIGHT = """\ -Copyright (C) 2019-2020 Matej Cepl +Copyright (C) 2019-2021 Matej Cepl Copyright (C) 2015-2018 Nicholas Smith Copyright (C) 2014-2015 Matej Cepl Copyright (C) 2006-2012 Ali Gholami Rudi diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rope-0.18.0/rope/base/codeanalyze.py new/rope-0.19.0/rope/base/codeanalyze.py --- old/rope-0.18.0/rope/base/codeanalyze.py 2020-10-06 19:52:52.000000000 +0200 +++ new/rope-0.19.0/rope/base/codeanalyze.py 2021-02-03 12:02:18.000000000 +0100 @@ -143,7 +143,7 @@ if token in ["'''", '"""', "'", '"']: if not self.in_string: self.in_string = token - elif self.in_string == token: + elif self.in_string == token or (self.in_string in ['"', "'"] and token == 3*self.in_string): self.in_string = '' if self.in_string: continue diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rope-0.18.0/rope/base/evaluate.py new/rope-0.19.0/rope/base/evaluate.py --- old/rope-0.18.0/rope/base/evaluate.py 2020-04-27 11:29:13.000000000 +0200 +++ new/rope-0.19.0/rope/base/evaluate.py 2021-04-18 22:14:45.000000000 +0200 @@ -1,3 +1,5 @@ +from operator import itemgetter + import rope.base.builtins import rope.base.pynames import rope.base.pyobjects @@ -232,9 +234,12 @@ def _Dict(self, node): keys = None values = None - if node.keys: - keys = self._get_object_for_node(node.keys[0]) - values = self._get_object_for_node(node.values[0]) + if node.keys and node.keys[0]: + keys, values = next(iter(filter(itemgetter(0), zip(node.keys, node.values))), (None, None)) + if keys: + keys = self._get_object_for_node(keys) + if values: + values = self._get_object_for_node(values) self.result = rope.base.pynames.UnboundName( pyobject=rope.base.builtins.get_dict(keys, values)) @@ -302,6 +307,9 @@ elif isinstance(node.slice, ast.Slice): self._call_function(node.value, '__getitem__', [node.slice]) + elif isinstance(node.slice, ast.expr): + self._call_function(node.value, '__getitem__', + [node.value]) def _Slice(self, node): self.result = self._get_builtin_name('slice') diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rope-0.18.0/rope/base/oi/soa.py new/rope-0.19.0/rope/base/oi/soa.py --- old/rope-0.18.0/rope/base/oi/soa.py 2020-01-30 17:02:09.000000000 +0100 +++ new/rope-0.19.0/rope/base/oi/soa.py 2021-04-18 22:14:45.000000000 +0200 @@ -126,7 +126,7 @@ for subscript, levels in nodes: instance = evaluate.eval_node(self.scope, subscript.value) args_pynames = [evaluate.eval_node(self.scope, - subscript.slice.value)] + subscript.slice)] value = rope.base.oi.soi._infer_assignment( rope.base.pynames.AssignmentValue(node.value, levels, type_hint=type_hint), @@ -149,5 +149,5 @@ def _added(self, node, levels): if isinstance(node, rope.base.ast.Subscript) and \ - isinstance(node.slice, rope.base.ast.Index): + isinstance(node.slice, (rope.base.ast.Index, rope.base.ast.expr)): self.nodes.append((node, levels)) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rope-0.18.0/rope/base/pynamesdef.py new/rope-0.19.0/rope/base/pynamesdef.py --- old/rope-0.18.0/rope/base/pynamesdef.py 2016-02-17 18:18:23.000000000 +0100 +++ new/rope-0.19.0/rope/base/pynamesdef.py 2021-04-18 22:14:45.000000000 +0200 @@ -24,7 +24,10 @@ def get_definition_location(self): """Returns a (module, lineno) tuple""" if self.lineno is None and self.assignments: - self.lineno = self.assignments[0].get_lineno() + try: + self.lineno = self.assignments[0].get_lineno() + except AttributeError: + pass return (self.module, self.lineno) def invalidate(self): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rope-0.18.0/rope/base/pyobjectsdef.py new/rope-0.19.0/rope/base/pyobjectsdef.py --- old/rope-0.18.0/rope/base/pyobjectsdef.py 2020-10-06 19:52:52.000000000 +0200 +++ new/rope-0.19.0/rope/base/pyobjectsdef.py 2021-02-03 12:02:18.000000000 +0100 @@ -321,6 +321,37 @@ pass +class _ExpressionVisitor(object): + def __init__(self, scope_visitor): + self.scope_visitor = scope_visitor + + def _assigned(self, name, assignment=None): + self.scope_visitor._assigned(name, assignment) + + def _GeneratorExp(self, node): + for child in ['elt', 'key', 'value']: + if hasattr(node, child): + ast.walk(getattr(node, child), self) + for comp in node.generators: + ast.walk(comp.target, _AssignVisitor(self)) + ast.walk(comp, self) + for if_ in comp.ifs: + ast.walk(if_, self) + + def _ListComp(self, node): + self._GeneratorExp(node) + + def _SetComp(self, node): + self._GeneratorExp(node) + + def _DictComp(self, node): + self._GeneratorExp(node) + + def _NamedExpr(self, node): + ast.walk(node.target, _AssignVisitor(self)) + ast.walk(node.value, self) + + class _AssignVisitor(object): def __init__(self, scope_visitor): @@ -331,6 +362,7 @@ self.assigned_ast = node.value for child_node in node.targets: ast.walk(child_node, self) + ast.walk(node.value, _ExpressionVisitor(self.scope_visitor)) def _assigned(self, name, assignment=None): self.scope_visitor._assigned(name, assignment) @@ -359,7 +391,7 @@ pass -class _ScopeVisitor(object): +class _ScopeVisitor(_ExpressionVisitor): def __init__(self, pycore, owner_object): self.pycore = pycore diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rope-0.18.0/rope/base/worder.py new/rope-0.19.0/rope/base/worder.py --- old/rope-0.18.0/rope/base/worder.py 2020-10-07 17:19:26.000000000 +0200 +++ new/rope-0.19.0/rope/base/worder.py 2021-04-18 22:14:45.000000000 +0200 @@ -225,6 +225,14 @@ prev = self._find_last_non_space_char(offset - 1) if offset <= 0 or self.code[prev] != '.': break + + # Check if relative import + # XXX: Looks like a hack... + prev_word_end = self._find_last_non_space_char(prev - 1) + if self.code[prev_word_end-3:prev_word_end+1] == "from": + offset = prev + break + offset = self._find_primary_without_dot_start(prev - 1) if not self._is_id_char(offset): break diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rope-0.18.0/rope/contrib/generate.py new/rope-0.19.0/rope/contrib/generate.py --- old/rope-0.18.0/rope/contrib/generate.py 2020-01-30 17:02:09.000000000 +0100 +++ new/rope-0.19.0/rope/contrib/generate.py 2021-04-18 22:14:45.000000000 +0200 @@ -5,7 +5,7 @@ from rope.refactor import sourceutils, importutils, functionutils, suites -def create_generate(kind, project, resource, offset): +def create_generate(kind, project, resource, offset, goal_resource=None): """A factory for creating `Generate` objects `kind` can be 'variable', 'function', 'class', 'module' or @@ -13,7 +13,7 @@ """ generate = eval('Generate' + kind.title()) - return generate(project, resource, offset) + return generate(project, resource, offset, goal_resource=goal_resource) def create_module(project, name, sourcefolder=None): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rope-0.18.0/rope/refactor/extract.py new/rope-0.19.0/rope/refactor/extract.py --- old/rope-0.18.0/rope/refactor/extract.py 2020-01-30 17:02:09.000000000 +0100 +++ new/rope-0.19.0/rope/refactor/extract.py 2021-02-03 12:02:18.000000000 +0100 @@ -547,7 +547,7 @@ # if not make_global, do not pass any global names; they are # all visible. if self.info.global_ and not self.info.make_global: - return () + return list(self.info_collector.read & self.info_collector.postread & self.info_collector.written) if not self.info.one_line: result = (self.info_collector.prewritten & self.info_collector.read) @@ -662,6 +662,11 @@ for child in node.targets: ast.walk(child, self) + def _AugAssign(self, node): + ast.walk(node.value, self) + self._read_variable(node.target.id, node.target.lineno) + self._written_variable(node.target.id, node.target.lineno) + def _ClassDef(self, node): self._written_variable(node.name, node.lineno) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rope-0.18.0/rope/refactor/patchedast.py new/rope-0.19.0/rope/refactor/patchedast.py --- old/rope-0.18.0/rope/refactor/patchedast.py 2020-10-06 19:52:52.000000000 +0200 +++ new/rope-0.19.0/rope/refactor/patchedast.py 2021-04-18 22:15:06.000000000 +0200 @@ -125,7 +125,10 @@ # semicolon in except region = self.source.consume_except_as_or_semicolon() else: - region = self.source.consume(child) + if hasattr(ast, 'JoinedStr') and isinstance(node, (ast.JoinedStr, ast.FormattedValue)): + region = self.source.consume_joined_string(child) + else: + region = self.source.consume(child) child = self.source[region[0]:region[1]] token_start = region[0] if not first_token: @@ -255,6 +258,13 @@ children.extend(['=', node.value]) self._handle(node, children) + def _AnnAssign(self, node): + children = [node.target, ':', node.annotation] + if node.value is not None: + children.append('=') + children.append(node.value) + self._handle(node, children) + def _Repr(self, node): self._handle(node, ['`', node.value, '`']) @@ -361,6 +371,46 @@ def _Bytes(self, node): self._handle(node, [self.String]) + def _JoinedStr(self, node): + def start_quote_char(): + possible_quotes = [(self.source.source.find(q, start, end), q) for q in QUOTE_CHARS] + quote_pos, quote_char = min((pos, q) for pos, q in possible_quotes if pos != -1) + return self.source[start:quote_pos + len(quote_char)] + + def end_quote_char(): + possible_quotes = [(self.source.source.rfind(q, start, end), q) for q in reversed(QUOTE_CHARS)] + _, quote_pos, quote_char = max((len(q), pos, q) for pos, q in possible_quotes if pos != -1) + return self.source[end - len(quote_char):end] + + QUOTE_CHARS = ['"""', "'''", '"', "'"] + offset = self.source.offset + start, end = self.source.consume_string( + end=self._find_next_statement_start(), + ) + self.source.offset = offset + + children = [] + children.append(start_quote_char()) + for part in node.values: + if isinstance(part, ast.FormattedValue): + children.append(part) + children.append(end_quote_char()) + self._handle(node, children) + + def _FormattedValue(self, node): + children = [] + children.append('{') + children.append(node.value) + if node.format_spec: + children.append(':') + for val in node.format_spec.values: + if isinstance(val, ast.FormattedValue): + children.append(val.value) + else: + children.append(val.s) + children.append('}') + self._handle(node, children) + def _Continue(self, node): self._handle(node, ['continue']) @@ -369,7 +419,11 @@ children.append('{') if node.keys: for index, (key, value) in enumerate(zip(node.keys, node.values)): - children.extend([key, ':', value]) + if key is None: + # PEP-448 dict unpacking: {a: b, **unpack} + children.extend(['**', value]) + else: + children.extend([key, ':', value]) if index < len(node.keys) - 1: children.append(',') children.append('}') @@ -381,6 +435,10 @@ def _Expr(self, node): self._handle(node, [node.value]) + def _NamedExpr(self, node): + children = [node.target, ':=', node.value] + self._handle(node, children) + def _Exec(self, node): children = [] children.extend(['exec', node.body]) @@ -764,11 +822,11 @@ self.source = source self.offset = 0 - def consume(self, token): + def consume(self, token, skip_comment=True): try: while True: new_offset = self.source.index(token, self.offset) - if self._good_token(token, new_offset): + if self._good_token(token, new_offset) or not skip_comment: break else: self._skip_comment() @@ -779,9 +837,16 @@ self.offset = new_offset + len(token) return (new_offset, self.offset) + def consume_joined_string(self, token): + new_offset = self.source.index(token, self.offset) + self.offset = new_offset + len(token) + return (new_offset, self.offset) + def consume_string(self, end=None): if _Source._string_pattern is None: - original = codeanalyze.get_string_pattern() + string_pattern = codeanalyze.get_string_pattern() + formatted_string_pattern = codeanalyze.get_formatted_string_pattern() + original = r'(?:%s)|(?:%s)' % (string_pattern, formatted_string_pattern) pattern = r'(%s)((\s|\\\n|#[^\n]*\n)*(%s))*' % \ (original, original) _Source._string_pattern = re.compile(pattern) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rope-0.18.0/rope.egg-info/PKG-INFO new/rope-0.19.0/rope.egg-info/PKG-INFO --- old/rope-0.18.0/rope.egg-info/PKG-INFO 2020-10-07 18:08:40.000000000 +0200 +++ new/rope-0.19.0/rope.egg-info/PKG-INFO 2021-04-18 22:17:27.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: rope -Version: 0.18.0 +Version: 0.19.0 Summary: a python refactoring library... Home-page: https://github.com/python-rope/rope Author: Ali Gholami Rudi @@ -51,5 +51,6 @@ Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 Classifier: Topic :: Software Development Provides-Extra: dev diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rope-0.18.0/ropetest/codeanalyzetest.py new/rope-0.19.0/ropetest/codeanalyzetest.py --- old/rope-0.18.0/ropetest/codeanalyzetest.py 2020-10-07 17:19:26.000000000 +0200 +++ new/rope-0.19.0/ropetest/codeanalyzetest.py 2021-04-18 22:14:45.000000000 +0200 @@ -1,3 +1,4 @@ +from textwrap import dedent try: import unittest2 as unittest except ImportError: @@ -156,6 +157,11 @@ self.assertEqual('a_func ( "(" ) . an_attr', self._find_primary(code, 26)) + def test_relative_import(self): + code = "from .module import smt" + self.assertEqual('.module', + self._find_primary(code, 5)) + def test_functions_on_ending_parens(self): code = 'A()' self.assertEqual('A()', self._find_primary(code, 2)) @@ -600,12 +606,12 @@ def test_generating_line_starts2(self): code = 'a = 1\na = 2\n\na = \\ 3\n' - line_finder = LogicalLineFinder(SourceLinesAdapter(code)) + line_finder = self._logical_finder(code) self.assertEqual([2, 4], list(line_finder.generate_starts(2))) def test_generating_line_starts3(self): code = 'a = 1\na = 2\n\na = \\ 3\n' - line_finder = LogicalLineFinder(SourceLinesAdapter(code)) + line_finder = self._logical_finder(code) self.assertEqual([2], list(line_finder.generate_starts(2, 3))) def test_generating_line_starts_for_multi_line_statements(self): @@ -619,6 +625,21 @@ line_finder = self._logical_finder(code) self.assertEqual([4, 5], list(line_finder.generate_starts(4))) + def test_false_triple_quoted_string(self): + code = dedent("""\ + def foo(): + a = 0 + p = 'foo''' + + def bar(): + a = 1 + a += 1 + """) + line_finder = self._logical_finder(code) + self.assertEqual([1, 2, 3, 5, 6, 7], list(line_finder.generate_starts())) + self.assertEqual((3, 3), line_finder.logical_line_in(3)) + self.assertEqual([5, 6, 7], list(line_finder.generate_starts(4))) + class TokenizerLogicalLineFinderTest(LogicalLineFinderTest): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rope-0.18.0/ropetest/contrib/codeassisttest.py new/rope-0.19.0/ropetest/contrib/codeassisttest.py --- old/rope-0.18.0/ropetest/contrib/codeassisttest.py 2020-10-06 19:52:52.000000000 +0200 +++ new/rope-0.19.0/ropetest/contrib/codeassisttest.py 2021-02-03 12:02:18.000000000 +0100 @@ -1,6 +1,7 @@ # coding: utf-8 import os.path +from textwrap import dedent try: import unittest2 as unittest except ImportError: @@ -472,6 +473,19 @@ result = get_definition_location(self.project, code, len(code) - 3) self.assertEqual((None, 3), result) + def test_get_definition_location_false_triple_quoted_string(self): + code = dedent('''\ + def foo(): + a = 0 + p = "foo""" + + def bar(): + a = 1 + a += 1 + ''') + result = get_definition_location(self.project, code, code.index("a += 1")) + self.assertEqual((None, 6), result) + def test_code_assists_in_parens(self): code = 'def a_func(a_var):\n pass\na_var = 10\na_func(a_' result = self._assist(code) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rope-0.18.0/ropetest/contrib/generatetest.py new/rope-0.19.0/ropetest/contrib/generatetest.py --- old/rope-0.18.0/ropetest/contrib/generatetest.py 2020-01-30 17:02:09.000000000 +0100 +++ new/rope-0.19.0/ropetest/contrib/generatetest.py 2021-04-18 22:14:45.000000000 +0200 @@ -284,6 +284,32 @@ ' if 1:\n g()\n', self.mod.read()) + def test_create_generate_class_with_goal_resource(self): + code = 'c = C()\n' + self.mod.write(code) + + result = generate.create_generate( + "class", + self.project, + self.mod, + code.index("C"), + goal_resource=self.mod2) + + self.assertTrue(isinstance(result, generate.GenerateClass)) + self.assertEqual(result.goal_resource, self.mod2) + + def test_create_generate_class_without_goal_resource(self): + code = 'c = C()\n' + self.mod.write(code) + + result = generate.create_generate( + "class", + self.project, + self.mod, + code.index("C")) + + self.assertTrue(isinstance(result, generate.GenerateClass)) + self.assertIsNone(result.goal_resource) if __name__ == '__main__': unittest.main() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rope-0.18.0/ropetest/pyscopestest.py new/rope-0.19.0/ropetest/pyscopestest.py --- old/rope-0.18.0/ropetest/pyscopestest.py 2020-01-30 17:02:09.000000000 +0100 +++ new/rope-0.19.0/ropetest/pyscopestest.py 2021-02-03 12:02:18.000000000 +0100 @@ -44,6 +44,71 @@ scope.get_scopes()[0]['SampleClass']. get_object().get_type()) + def test_list_comprehension_scope_inside_assignment(self): + scope = libutils.get_string_scope( + self.project, 'a_var = [b_var + d_var for b_var, c_var in e_var]\n') + self.assertEqual( + list(sorted(scope.get_defined_names())), + ['a_var', 'b_var', 'c_var'], + ) + + def test_list_comprehension_scope(self): + scope = libutils.get_string_scope( + self.project, '[b_var + d_var for b_var, c_var in e_var]\n') + self.assertEqual( + list(sorted(scope.get_defined_names())), + ['b_var', 'c_var'], + ) + + def test_set_comprehension_scope(self): + scope = libutils.get_string_scope( + self.project, '{b_var + d_var for b_var, c_var in e_var}\n') + self.assertEqual( + list(sorted(scope.get_defined_names())), + ['b_var', 'c_var'], + ) + + def test_generator_comprehension_scope(self): + scope = libutils.get_string_scope( + self.project, '(b_var + d_var for b_var, c_var in e_var)\n') + self.assertEqual( + list(sorted(scope.get_defined_names())), + ['b_var', 'c_var'], + ) + + def test_dict_comprehension_scope(self): + scope = libutils.get_string_scope( + self.project, '{b_var: d_var for b_var, c_var in e_var}\n') + self.assertEqual( + list(sorted(scope.get_defined_names())), + ['b_var', 'c_var'], + ) + + @testutils.only_for_versions_higher('3.8') + def test_inline_assignment_in_comprehensions(self): + scope = libutils.get_string_scope( + self.project, '''[ + (a_var := b_var + (f_var := g_var)) + for b_var in [(j_var := i_var) + for i_var in c_var] if a_var + (h_var := d_var) + ]''') + self.assertEqual( + list(sorted(scope.get_defined_names())), + ['a_var', 'b_var', 'f_var', 'h_var', 'i_var', 'j_var'], + ) + + def test_nested_comprehension(self): + scope = libutils.get_string_scope( + self.project, '''[ + b_var + d_var for b_var, c_var in [ + e_var for e_var in f_var + ] + ]\n''') + self.assertEqual( + list(sorted(scope.get_defined_names())), + ['b_var', 'c_var', 'e_var'], + ) + def test_simple_class_scope(self): scope = libutils.get_string_scope( self.project, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rope-0.18.0/ropetest/refactor/extracttest.py new/rope-0.19.0/ropetest/refactor/extracttest.py --- old/rope-0.18.0/ropetest/refactor/extracttest.py 2020-01-30 17:02:09.000000000 +0100 +++ new/rope-0.19.0/ropetest/refactor/extracttest.py 2021-02-03 12:02:18.000000000 +0100 @@ -1,3 +1,4 @@ +from textwrap import dedent try: import unittest2 as unittest except ImportError: @@ -65,6 +66,26 @@ "def extracted():\n print('one')\n\nprint('hey')\n" self.assertEqual(expected, refactored) + @testutils.only_for('3.5') + def test_extract_function_containing_dict_generalized_unpacking(self): + code = dedent('''\ + def a_func(dict1): + dict2 = {} + a_var = {a: b, **dict1, **dict2} + ''') + start = code.index('{a') + end = code.index('2}') + len('2}') + refactored = self.do_extract_method(code, start, end, 'extracted') + expected = dedent('''\ + def a_func(dict1): + dict2 = {} + a_var = extracted(dict1, dict2) + + def extracted(dict1, dict2): + return {a: b, **dict1, **dict2} + ''') + self.assertEqual(expected, refactored) + def test_simple_extract_function_with_parameter(self): code = "def a_func():\n a_var = 10\n print(a_var)\n" start, end = self._convert_line_range_to_offset(code, 3, 3) @@ -409,6 +430,20 @@ expected = 'new_var = 10 + 20\na_var = new_var\n' self.assertEqual(expected, refactored) + @testutils.only_for_versions_higher('3.6') + def test_extract_variable_f_string(self): + code = dedent('''\ + foo(f"abc {a_var} def", 10) + ''') + start = code.index('f"') + end = code.index('def"') + 4 + refactored = self.do_extract_variable(code, start, end, 'new_var') + expected = dedent('''\ + new_var = f"abc {a_var} def" + foo(new_var, 10) + ''') + self.assertEqual(expected, refactored) + def test_extract_variable_multiple_lines(self): code = 'a = 1\nb = 2\n' start = code.index('1') @@ -544,6 +579,87 @@ expected = '\n\ndef new_f():\n print(1)\n\nnew_f()\n' self.assertEqual(expected, refactored) + @testutils.only_for_versions_higher('3.6') + def test_extract_method_f_string_extract_method(self): + code = dedent('''\ + def func(a_var): + foo(f"abc {a_var}", 10) + ''') + start = code.index('f"') + end = code.index('}"') + 2 + refactored = self.do_extract_method(code, start, end, 'new_func') + expected = dedent('''\ + def func(a_var): + foo(new_func(a_var), 10) + + def new_func(a_var): + return f"abc {a_var}" + ''') + self.assertEqual(expected, refactored) + + @testutils.only_for_versions_higher('3.6') + def test_extract_method_f_string_extract_method_complex_expression(self): + code = dedent('''\ + def func(a_var): + b_var = int + c_var = 10 + fill = 10 + foo(f"abc {a_var + f'{b_var(a_var)}':{fill}16}" f"{c_var}", 10) + ''') + start = code.index('f"') + end = code.index('c_var}"') + 7 + refactored = self.do_extract_method(code, start, end, 'new_func') + expected = dedent('''\ + def func(a_var): + b_var = int + c_var = 10 + fill = 10 + foo(new_func(a_var, b_var, c_var, fill), 10) + + def new_func(a_var, b_var, c_var, fill): + return f"abc {a_var + f'{b_var(a_var)}':{fill}16}" f"{c_var}" + ''') + self.assertEqual(expected, refactored) + + @testutils.only_for_versions_higher('3.6') + def test_extract_method_f_string_false_comment(self): + code = dedent('''\ + def func(a_var): + foo(f"abc {a_var} # ", 10) + ''') + start = code.index('f"') + end = code.index('# "') + 3 + refactored = self.do_extract_method(code, start, end, 'new_func') + expected = dedent('''\ + def func(a_var): + foo(new_func(a_var), 10) + + def new_func(a_var): + return f"abc {a_var} # " + ''') + self.assertEqual(expected, refactored) + + @unittest.expectedFailure + @testutils.only_for_versions_higher('3.6') + def test_extract_method_f_string_false_format_value_in_regular_string(self): + code = dedent('''\ + def func(a_var): + b_var = 1 + foo(f"abc {a_var} " "{b_var}" f"{b_var} def", 10) + ''') + start = code.index('f"') + end = code.index('def"') + 4 + refactored = self.do_extract_method(code, start, end, 'new_func') + expected = dedent('''\ + def func(a_var): + b_var = 1 + foo(new_func(a_var, b_var), 10) + + def new_func(a_var, b_var): + return f"abc {a_var} " "{b_var}" f"{b_var} def" + ''') + self.assertEqual(expected, refactored) + def test_variable_writes_in_the_same_line_as_variable_read(self): code = 'a = 1\na = 1 + a\n' start = code.index('\n') + 1 @@ -554,12 +670,60 @@ self.assertEqual(expected, refactored) def test_variable_writes_in_the_same_line_as_variable_read2(self): - code = 'a = 1\na += 1\n' + code = dedent('''\ + a = 1 + a += 1 + ''') start = code.index('\n') + 1 end = len(code) refactored = self.do_extract_method(code, start, end, 'new_f', global_=True) - expected = 'a = 1\n\ndef new_f():\n a += 1\n\nnew_f()\n' + expected = dedent('''\ + a = 1 + + def new_f(a): + a += 1 + + new_f(a) + ''') + self.assertEqual(expected, refactored) + + def test_variable_writes_in_the_same_line_as_variable_read3(self): + code = dedent('''\ + a = 1 + a += 1 + print(a) + ''') + start, end = self._convert_line_range_to_offset(code, 2, 2) + refactored = self.do_extract_method(code, start, end, 'new_f') + expected = dedent('''\ + a = 1 + + def new_f(a): + a += 1 + return a + + a = new_f(a) + print(a) + ''') + self.assertEqual(expected, refactored) + + def test_variable_writes_only(self): + code = dedent('''\ + i = 1 + print(i) + ''') + start, end = self._convert_line_range_to_offset(code, 1, 1) + refactored = self.do_extract_method(code, start, end, 'new_f') + expected = dedent('''\ + + def new_f(): + i = 1 + return i + + i = new_f() + print(i) + ''') self.assertEqual(expected, refactored) def test_variable_and_similar_expressions(self): @@ -1022,5 +1186,50 @@ end = len(code) - 1 with self.assertRaises(rope.base.exceptions.RefactoringError): self.do_extract_method(code, start, end, 'new_func') + + def test_extract_function_with_inline_assignment_in_method(self): + code = dedent('''\ + def foo(): + i = 1 + i += 1 + print(i) + ''') + start, end = self._convert_line_range_to_offset(code, 3, 3) + refactored = self.do_extract_method(code, start, end, 'new_func') + expected = dedent('''\ + def foo(): + i = 1 + i = new_func(i) + print(i) + + def new_func(i): + i += 1 + return i + ''') + self.assertEqual(expected, refactored) + + @testutils.only_for_versions_higher('3.8') + def test_extract_function_with_inline_assignment_in_condition(self): + code = dedent('''\ + def foo(a): + if i := a == 5: + i += 1 + print(i) + ''') + start, end = self._convert_line_range_to_offset(code, 2, 3) + refactored = self.do_extract_method(code, start, end, 'new_func') + expected = dedent('''\ + def foo(a): + i = new_func(a) + print(i) + + def new_func(a): + if i := a == 5: + i += 1 + return i + ''') + self.assertEqual(expected, refactored) + + if __name__ == '__main__': unittest.main() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rope-0.18.0/ropetest/refactor/patchedasttest.py new/rope-0.19.0/ropetest/refactor/patchedasttest.py --- old/rope-0.18.0/ropetest/refactor/patchedasttest.py 2020-10-06 19:52:52.000000000 +0200 +++ new/rope-0.19.0/ropetest/refactor/patchedasttest.py 2021-04-18 22:14:45.000000000 +0200 @@ -120,6 +120,26 @@ checker.check_children( 'Assign', ['Name', ' ', '=', ' ', 'Num']) + @testutils.only_for_versions_higher('3.6') + def test_ann_assign_node_without_target(self): + source = 'a: List[int]\n' + ast_frag = patchedast.get_patched_ast(source, True) + checker = _ResultChecker(self, ast_frag) + start = source.index('a') # noqa + checker.check_region('AnnAssign', 0, len(source) - 1) + checker.check_children( + 'AnnAssign', ['Name', '', ':', ' ', 'Subscript']) + + @testutils.only_for_versions_higher('3.6') + def test_ann_assign_node_with_target(self): + source = 'a: int = 10\n' + ast_frag = patchedast.get_patched_ast(source, True) + checker = _ResultChecker(self, ast_frag) + start = source.index('a') # noqa + checker.check_region('AnnAssign', 0, len(source) - 1) + checker.check_children( + 'AnnAssign', ['Name', '', ':', ' ', 'Name', ' ', '=', ' ', 'Num']) + def test_add_node(self): source = '1 + 2\n' ast_frag = patchedast.get_patched_ast(source, True) @@ -227,6 +247,63 @@ checker = _ResultChecker(self, ast_frag) checker.check_children('Module', ['', 'Expr', '\n', 'Expr', '\n']) + def test_handling_raw_strings(self): + source = 'r"abc"\n' + ast_frag = patchedast.get_patched_ast(source, True) + checker = _ResultChecker(self, ast_frag) + checker.check_children( + 'Str', ['r"abc"']) + + @testutils.only_for_versions_higher('3.6') + def test_handling_format_strings_basic(self): + source = '1 + f"abc{a}"\n' + ast_frag = patchedast.get_patched_ast(source, True) + checker = _ResultChecker(self, ast_frag) + checker.check_children( + 'JoinedStr', ['f"', 'abc', 'FormattedValue', '', '"']) + checker.check_children( + 'FormattedValue', ['{', '', 'Name', '', '}']) + + @testutils.only_for_versions_higher('3.6') + def test_handling_format_strings_with_implicit_join(self): + source = '''"1" + rf'abc{a}' f"""xxx{b} """\n''' + ast_frag = patchedast.get_patched_ast(source, True) + checker = _ResultChecker(self, ast_frag) + checker.check_children( + 'JoinedStr', ["rf'", 'abc', 'FormattedValue', '\' f"""xxx', 'FormattedValue', ' ', '"""']) + checker.check_children( + 'FormattedValue', ['{', '', 'Name', '', '}']) + + @testutils.only_for_versions_higher('3.6') + def test_handling_format_strings_with_format_spec(self): + source = 'f"abc{a:01}"\n' + ast_frag = patchedast.get_patched_ast(source, True) + checker = _ResultChecker(self, ast_frag) + checker.check_children( + 'JoinedStr', ['f"', 'abc', 'FormattedValue', '', '"']) + checker.check_children( + 'FormattedValue', ['{', '', 'Name', '', ':', '', '01', '', '}']) + + @testutils.only_for_versions_higher('3.6') + def test_handling_format_strings_with_inner_format_spec(self): + source = 'f"abc{a:{length}01}"\n' + ast_frag = patchedast.get_patched_ast(source, True) + checker = _ResultChecker(self, ast_frag) + checker.check_children( + 'JoinedStr', ['f"', 'abc', 'FormattedValue', '', '"']) + checker.check_children( + 'FormattedValue', ['{', '', 'Name', '', ':', '{', 'Name', '}', '01', '', '}']) + + @testutils.only_for_versions_higher('3.6') + def test_handling_format_strings_with_expression(self): + source = 'f"abc{a + b}"\n' + ast_frag = patchedast.get_patched_ast(source, True) + checker = _ResultChecker(self, ast_frag) + checker.check_children( + 'JoinedStr', ['f"', 'abc', 'FormattedValue', '', '"']) + checker.check_children( + 'FormattedValue', ['{', '', 'BinOp', '', '}']) + @testutils.only_for_versions_lower('3') def test_long_integer_literals(self): source = "0x1L + a" @@ -499,6 +576,16 @@ 'Dict', ['{', '', 'Num', '', ':', ' ', 'Num', '', ',', ' ', 'Num', '', ':', ' ', 'Num', '', '}']) + @testutils.only_for('3.5') + def test_dict_node_with_unpacking(self): + source = '{**dict1, **dict2}\n' + ast_frag = patchedast.get_patched_ast(source, True) + checker = _ResultChecker(self, ast_frag) + checker.check_region('Dict', 0, len(source) - 1) + checker.check_children( + 'Dict', ['{', '', '**', '', 'Name', '', ',', + ' ', '**', '', 'Name', '', '}']) + def test_div_node(self): source = '1 / 2\n' ast_frag = patchedast.get_patched_ast(source, True) @@ -534,6 +621,15 @@ ':', '\n ', 'Pass', '\n', 'else', '', ':', '\n ', 'Pass']) + @testutils.only_for_versions_higher('3.8') + def test_named_expr_node(self): + source = 'if a := 10 == 10:\n pass\n' + ast_frag = patchedast.get_patched_ast(source, True) + checker = _ResultChecker(self, ast_frag) + start = source.index('a') + checker.check_region('NamedExpr', start, start + 13) + checker.check_children('NamedExpr', ['Name', ' ', ':=', ' ', 'Compare']) + def test_normal_from_node(self): source = 'from x import y\n' ast_frag = patchedast.get_patched_ast(source, True) @@ -742,8 +838,12 @@ source = 'x = xs[0,:]\n' ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) - checker.check_region('ExtSlice', 7, len(source) - 2) - checker.check_children('ExtSlice', ['Index', '', ',', '', 'Slice']) + if sys.version_info >= (3, 9): + checker.check_region('Tuple', 7, len(source) - 2) + checker.check_children('Tuple', ['Num', '', ',', '', 'Slice']) + else: + checker.check_region('ExtSlice', 7, len(source) - 2) + checker.check_children('ExtSlice', ['Index', '', ',', '', 'Slice']) def test_simple_module_node(self): source = 'pass\n' @@ -837,9 +937,13 @@ source = 'a[1]\n' ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) - checker.check_children( - 'Subscript', ['Name', '', '[', '', 'Index', '', ']']) - checker.check_children('Index', ['Num']) + if sys.version_info >= (3, 9): + checker.check_children( + 'Subscript', ['Name', '', '[', '', 'Num', '', ']']) + else: + checker.check_children( + 'Subscript', ['Name', '', '[', '', 'Index', '', ']']) + checker.check_children('Index', ['Num']) def test_tuple_node(self): source = '(1, 2)\n' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rope-0.18.0/ropetest/refactor/renametest.py new/rope-0.19.0/ropetest/refactor/renametest.py --- old/rope-0.18.0/ropetest/refactor/renametest.py 2020-01-30 17:02:09.000000000 +0100 +++ new/rope-0.19.0/ropetest/refactor/renametest.py 2021-02-03 12:02:18.000000000 +0100 @@ -1,4 +1,5 @@ import sys +from textwrap import dedent try: import unittest2 as unittest except ImportError: @@ -88,6 +89,60 @@ 'def a_func(new_param):\n a = new_param\na_func(1)\n', refactored) + def test_renaming_comprehension_loop_variables(self): + code = '[b_var for b_var, c_var in d_var if b_var == c_var]' + refactored = self._local_rename(code, code.index('b_var') + 1, + 'new_var') + self.assertEqual( + '[new_var for new_var, c_var in d_var if new_var == c_var]', + refactored) + + def test_renaming_list_comprehension_loop_variables_in_assignment(self): + code = 'a_var = [b_var for b_var, c_var in d_var if b_var == c_var]' + refactored = self._local_rename(code, code.index('b_var') + 1, + 'new_var') + self.assertEqual( + 'a_var = [new_var for new_var, c_var in d_var if new_var == c_var]', + refactored) + + def test_renaming_generator_comprehension_loop_variables(self): + code = 'a_var = (b_var for b_var, c_var in d_var if b_var == c_var)' + refactored = self._local_rename(code, code.index('b_var') + 1, + 'new_var') + self.assertEqual( + 'a_var = (new_var for new_var, c_var in d_var if new_var == c_var)', + refactored) + + @unittest.expectedFailure + def test_renaming_comprehension_loop_variables_scope(self): + # FIXME: variable scoping for comprehensions is incorrect, we currently + # don't create a scope for comprehension + code = dedent('''\ + [b_var for b_var, c_var in d_var if b_var == c_var] + b_var = 10 + ''') + refactored = self._local_rename(code, code.index('b_var') + 1, + 'new_var') + self.assertEqual( + '[new_var for new_var, c_var in d_var if new_var == c_var]\nb_var = 10\n', + refactored) + + @testutils.only_for_versions_higher('3.8') + def test_renaming_inline_assignment(self): + code = dedent('''\ + while a_var := next(foo): + print(a_var) + ''') + refactored = self._local_rename(code, code.index('a_var') + 1, + 'new_var') + self.assertEqual( + dedent('''\ + while new_var := next(foo): + print(new_var) + '''), + refactored, + ) + def test_renaming_arguments_for_normal_args_changing_calls(self): code = 'def a_func(p1=None, p2=None):\n pass\na_func(p2=1)\n' refactored = self._local_rename(code, code.index('p2') + 1, 'p3') @@ -627,6 +682,26 @@ 'new_var') self.assertEqual(code.replace('a_var', 'new_var', 2), refactored) + @testutils.only_for_versions_higher('3.5') + def test_renaming_in_generalized_dict_unpacking(self): + code = dedent('''\ + a_var = {**{'stuff': 'can'}, **{'stuff': 'crayon'}} + + if "stuff" in a_var: + print("ya") + ''') + mod1 = testutils.create_module(self.project, 'mod1') + mod1.write(code) + refactored = self._local_rename(code, code.index('a_var') + 1, + 'new_var') + expected = dedent('''\ + new_var = {**{'stuff': 'can'}, **{'stuff': 'crayon'}} + + if "stuff" in new_var: + print("ya") + ''') + self.assertEqual(expected, refactored) + def test_dos_line_ending_and_renaming(self): code = '\r\na = 1\r\n\r\nprint(2 + a + 2)\r\n' offset = code.replace('\r\n', '\n').rindex('a') diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rope-0.18.0/setup.py new/rope-0.19.0/setup.py --- old/rope-0.18.0/setup.py 2020-10-07 18:08:16.000000000 +0200 +++ new/rope-0.19.0/setup.py 2021-04-18 22:15:53.000000000 +0200 @@ -25,6 +25,7 @@ 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', 'Topic :: Software Development']