Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/library.rst
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,12 @@ the changes it made. Calculating those changes can be time consuming.
See the `rope.base.taskhandle.TaskHandle`_ section for measuring its
progress or interrupting it.

To offer a similar-code extraction only when it finds other matches, call
``extractor.get_similar_occurrences()`` before asking the user to confirm
that option. It returns the number of regions similar extraction would
process, without creating a ``Change``. In the usual case, a result greater
than one means that similar extraction would affect additional regions.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than adding an API that's specific to just extract-similar refactoring, I was actually thinking that this should be more of a general-purpose API to ask the refactoring class to compute a preview object.

An API that could be used, for example, for rename/extract similar operation to preview how many files/occurrences would be affected, listing valid target scopes where extract operation would or could place the extracted method/variable, previewing the function arguments of extract method, how many occurrences would be affected by inline refactoring, how many things organise import would remove/move, etc, without fully calculating the changeset. Integrations could display these info in the UI so users can make more informed decision before performing them, or they could use it to enable/disable actions like similar that doesn't always make sense.

The availability of what info is available is of course going to be specific to each operation, but the API for requesting these preview information objects should have a somewhat consistent contract between different refactoring operations.

I think the implementation here for get_similar_occurrences() looks fine but the API should be left undocumented and considered private for now until we can sketch out the API contract in a separate ticket. I'd approve the PR if we leave out the documentation for now.


Previewing And Performing Changes
---------------------------------
Expand Down
33 changes: 26 additions & 7 deletions rope/refactor/extract.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,21 +68,36 @@ def get_changes(self, extracted_name, similar=False, global_=False, kind=None):
"""
extracted_name, kind = self._get_kind_from_name(extracted_name, kind)

info = self._create_info(extracted_name, similar, global_, kind)
new_contents = _ExtractPerformer(info).extract()
changes = ChangeSet(f"Extract {info.kind} <{extracted_name}>")
changes.add_change(ChangeContents(self.resource, new_contents))
return changes

def get_similar_occurrences(self, global_=False, kind=None):
"""Return the number of regions similar extraction would affect.

This checks the same extraction constraints and match rules as
:meth:`get_changes` without creating changes. ``global_`` and ``kind``
have the same meaning as in :meth:`get_changes`.
"""
info = self._create_info("", similar=True, global_=global_, kind=kind)
return len(_ExtractPerformer(info)._collect_matches().matches)

def _create_info(self, extracted_name, similar, global_, kind):
kind = self._get_kind(kind)
info = _ExtractInfo(
self.project,
self.resource,
self.start_offset,
self.end_offset,
extracted_name,
variable=self._get_kind(kind) == "variable",
variable=kind == "variable",
similar=similar,
make_global=global_,
)
info.kind = self._get_kind(kind)
new_contents = _ExtractPerformer(info).extract()
changes = ChangeSet(f"Extract {info.kind} <{extracted_name}>")
changes.add_change(ChangeContents(self.resource, new_contents))
return changes
info.kind = kind
return info

def _get_kind_from_name(self, extracted_name, kind):
for sign, selected_kind in self.kind_prefixes.items():
Expand Down Expand Up @@ -308,10 +323,14 @@ def _replace_occurrences(self, content, extract_info):
region = match.get_region()
content.add_change(region[0], region[1], replacement.substitute(mapping))

def _collect_info(self):
def _collect_matches(self):
extract_collector = _ExtractCollector(self.info)
self._find_definition(extract_collector)
self._find_matches(extract_collector)
return extract_collector

def _collect_info(self):
extract_collector = self._collect_matches()
self._find_definition_location(extract_collector)
return extract_collector

Expand Down
35 changes: 35 additions & 0 deletions ropetest/refactor/extracttest.py
Original file line number Diff line number Diff line change
Expand Up @@ -1439,6 +1439,41 @@ def test_definition_should_appear_before_the_first_use(self):
""")
self.assertEqual(expected, refactored)

def test_get_similar_occurrences_for_variable(self):
code = dedent("""\
a = 1
b = 1
""")
testmod = testutils.create_module(self.project, "testmod")
testmod.write(code)
start = code.index("1")
extractor = extract.ExtractVariable(self.project, testmod, start, start + 1)

self.assertEqual(2, extractor.get_similar_occurrences())

def test_get_similar_occurrences_for_method(self):
code = dedent("""\
a = 1
b = 1
""")
testmod = testutils.create_module(self.project, "testmod")
testmod.write(code)
start = code.index("1")
extractor = extract.ExtractMethod(self.project, testmod, start, start + 1)

self.assertEqual(2, extractor.get_similar_occurrences())

def test_get_similar_occurrences_with_no_other_matches(self):
code = dedent("""\
a = 1
""")
testmod = testutils.create_module(self.project, "testmod")
testmod.write(code)
start = code.index("1")
extractor = extract.ExtractMethod(self.project, testmod, start, start + 1)

self.assertEqual(1, extractor.get_similar_occurrences())

def test_extract_method_and_similar_expressions(self):
code = dedent("""\
a = 1
Expand Down
Loading