Rename Refactoring
Relevant source files
The following files were used as context for generating this wiki page:
Rename Refactoring is a core feature of the rope library designed to safely change the identifiers of various Python entities across a project. It supports renaming classes, functions, modules, packages, methods, variables, and keyword arguments while ensuring that all occurrences—including those in different modules—are updated correctly. Sources: rope/refactor/rename.py:18-24
The system is built to handle complex Python scoping rules, differentiating between local variables and global identifiers, and can optionally include occurrences within documentation, such as comments and strings. It also integrates with other refactoring tools like Move Refactoring and Inline Refactoring to manage code restructuring. Sources: rope/refactor/rename.py:26-50, rope/refactor/inline.py:270-280
Architecture and Components
The rename refactoring logic is primarily encapsulated in the Rename class, which manages the lifecycle of a rename operation from data collection to change generation.
Key Classes
| Class | Description |
|---|---|
Rename | The main refactoring class. It evaluates the target identifier at a given offset and generates a ChangeSet. |
ChangeOccurrences | A specialized class for changing name occurrences within a specific scope without renaming the underlying entity (e.g., changing module usage in one file). |
occurrence.Finder | A utility (used by Rename) to find all valid occurrences of a PyName across specified resources. |
Sources: rope/refactor/rename.py:18-24, rope/refactor/rename.py:148-158, rope/refactor/rename.py:104-113
The Refactoring Process
The refactoring follows a standardized four-step workflow common to all rope refactorings:
- Construction: The
Renameobject is initialized with a project, resource, and offset. Sources: rope/refactor/init.py:8-10 - Evaluation: The tool provides info like the current name via
get_old_name(). Sources: rope/refactor/init.py:14-16 - Change Generation:
get_changes(new_name, ...)is called to calculate necessary modifications. Sources: rope/refactor/init.py:20-22 - Execution: The project performs the returned
ChangeSetusingproject.do(changes). Sources: rope/refactor/init.py:26-28
The following diagram illustrates the flow of data when performing a rename:
Sources: rope/refactor/rename.py:30-145
Logic and Implementation Details
Target Identification
The Rename class identifies the target to be renamed based on the offset provided. If the offset is None, the resource (module or package) itself is the target. If an offset is provided, rope evaluates the location to find a PyName. Sources: rope/refactor/rename.py:30-58
# From rope/refactor/rename.py:35-43
if offset is not None:
self.old_name = worder.get_name_at(self.resource, offset)
this_pymodule = self.project.get_pymodule(self.resource)
self.old_instance, self.old_pyname = evaluate.eval_location2(
this_pymodule, offset
)
if self.old_pyname is None:
raise exceptions.RefactoringError(...)
Occurrence Finding and Filtering
The refactoring uses an occurrence.Finder to locate identifiers. It applies several filters:
- Hierarchy: When
in_hierarchyis True and the target is a method, it renames matching methods in parent and child classes. Sources: rope/refactor/rename.py:108-111 - Scope: Local variables are only renamed within their defining function. Sources: rope/refactor/rename.py:99-100
- Unsure Matches: A callback function can be provided to resolve ambiguous occurrences. Sources: rope/refactor/rename.py:84-89
- Documentation: If
docsis enabled, the finder looks inside comments and string literals. Sources: rope/refactor/rename.py:81-83
Module and Package Renaming
If the target is a module or package, the refactoring adds a MoveResource to the ChangeSet. This handles the physical renaming of the file or directory on the filesystem in addition to updating import statements. Sources: rope/refactor/rename.py:116-120, rope/refactor/rename.py:137-145
Configuration and Parameters
The get_changes method accepts several parameters to control the refactoring behavior:
| Parameter | Type | Default | Description |
|---|---|---|---|
new_name | str | Mandatory | The new identifier name. Cannot be a Python keyword. |
resources | list | None | A list of files to search. If None, searches all Python files in the project. |
in_hierarchy | bool | False | Forces renaming across class hierarchies for methods. |
docs | bool | False | Includes occurrences in comments and strings. |
unsure | func | None | A function that returns True/False for ambiguous occurrences. |
Sources: rope/refactor/rename.py:65-95
Validation
The system validates the new_name to ensure it is not a Python keyword (e.g., class, def). If a keyword is provided, a RefactoringError is raised. Sources: rope/refactor/rename.py:122-128, ropetest/refactor/renametest.py:883-885
Multi-Project Renaming
The library supports renaming identifiers across multiple projects using multiproject.MultiProjectRefactoring. This is useful when a library in one project is consumed by another project, and a rename in the library must propagate to the consumer. Sources: ropetest/refactor/multiprojecttest.py:23-45
Usage Example
A typical programmatic usage of Rename Refactoring:
# From ropetest/refactor/renametest.py:33-35
def _rename(self, resource, offset, new_name, **kwds):
changes = Rename(self.project, resource, offset).get_changes(new_name, **kwds)
self.project.do(changes)
Conclusion
Rename Refactoring in rope provides a robust mechanism for maintaining code consistency. By integrating occurrence finding with Python-aware scope analysis and filesystem management for modules, it automates the tedious and error-prone process of identifier updates. Its extensibility through parameters like in_hierarchy and docs allows it to handle both simple local variable renames and complex library-wide API changes. Sources: rope/refactor/rename.py:18-24, rope/refactor/rename.py:104-120