././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1705553002.7139843 rope-1.12.0/0000775000175000017500000000000014552126153012476 5ustar00lieryanlieryan././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1705552213.0 rope-1.12.0/CHANGELOG.md0000664000175000017500000002507114552124525014315 0ustar00lieryanlieryan# **Upcoming release** - ... # Release 1.12.0 - #733 skip directories with perm error when building autoimport index (@MrBago) - #722, #723 Remove site-packages from packages search tree (@tkrabel) - #738 Implement os.PathLike on Resource (@lieryan) - #739, #736 Ensure autoimport requests uses indexes (@lieryan) - #734, #735 raise exception when extracting the start of a block without the end # Release 1.11.0 - #710, #561 Implement `except*` syntax (@lieryan) - #711 allow building documentation without having rope module installed (@kloczek) - #719 Allows the in-memory db to be shared across threads (@tkrabel) - #720 create one sqlite3.Connection per thread using a thread local (@tkrabel) - #715 change AutoImport's `get_modules` to be case sensitive (@bagel897) # Release 1.10.0 - #708, #709 Add support for Python 3.12 (@lieryan) # Release 1.9.0 - #624, #693 Implement `nonlocal` keyword (@lieryan) - #697, #565 Automatically purge autoimport.db when there is schema change # Release 1.8.0 - #650 Install pre-commit hooks on rope repository (@lieryan) - #655 Remove unused __init__() methods (@edreamleo, @lieryan) - #656 Reformat using black 23.1.0 (@edreamleo) - #674 Fix/supress all mypy complaints (@edreamleo) - #680 Remove a do-nothing statement in soi._handle_first_parameter (@edreamleo) - #687, #688 Fix autoimport not scanning packages recursively (@lieryan) # Release 1.7.0 ## Feature - #548 Implement MoveGlobal using string as destination module names (@lieryan) ## Bug - #627 Fix parsing of octal literal (@lieryan) - #643, #435 Fix fstrings with mismatched parens (@apmorton) - #646 Fix renaming kwargs when refactoring from imports (@apmorton) - #648 Remove __init__ from import statement when using sqlite autoimport (@bagel897) ## Improvements - rope.contrib.generate improvements - #640 Remove unnecessary eval in generate.py (@edreamleo) - #641 Add type annotations for rope.contrib.generate.create_generate() (@edreamleo) - call_for_nodes() improvements - #634 Remove call_for_nodes(recursive) argument (@edreamleo) - #642 Add comments & docstrings related to call_for_nodes (@edreamleo, @lieryan) - Data storage improvements - #604 Fix test that sometimes leaves files behind in the current working directory (@lieryan) - #606 Deprecate compress_objectdb and compress_history (@lieryan) - #607 Remove importing from legacy files with `.pickle` suffix (@lieryan) - #611 Implement JSON DataFile serialization (@lieryan) - #630 SQLite models improvements (@lieryan) - #631 Implement version hash (@lieryan) ## Tech Debt - #594 Tidy up patchedast (@Alex-CodeLab) - #595 Global default DEFAULT_TASK_HANDLE (@Alex-CodeLab) - #609, #610, #612, #613 Fix pyflakes issues (@edreamleo) - #615 Remove 'unicode' from builtins dict (@edreamleo) - #616, #621 Remove `file` builtins (@edreamleo) - #618 Separate pynames and pynamesdef and remove star-import (@edreamleo, @lieryan) - #620 Remove unused import in occurrences.py (@edreamleo) - #625 Remove support for deprecated ast nodes (@lieryan) ## Tests/Dev - #626 Install pre-commit hooks on rope repository (@lieryan) - #628 Add isort to pre-commit (@lieryan) - #638 Add a function to identify ast Constant nodes more granularly (@lieryan) ## Docs - #636 Update readme to reflect 1.0 has been released. (@maxnoe) # Release 1.6.0 ## New features & Enhancements - #559, #560 Improve handling of whitespace in import and from-import statements (@lieryan) - #566, #567, #597 Fix variables in kwonlyargs and posonlyargs not being correctly passed to extracted methods (@lieryan) ## Unit Test - #589, #596 Fix issue with `sample_project()` creating directories where it shouldn't when running tests (@lieryan) - #547 Add config file for linters - #593 Remove `only_for` decorator for all python versions less than 3.7 (@edreamleo) ## Tech Debt - Code quality - #546 Remove unused vars in test (@lieryan, @edreamleo) - #551, #552 Numerous flake8 linter complaints (@edreamleo) - #558 Fix typos (@kianmeng) - #583, #584 More consistent import style (@edreamleo) - Python 2-related tech debt - #533 Refactoring to Remove usage of unicode type (@lieryan) - #549, #553 Remove rope.base.utils.pycompat (@dreamleo) - #555 Fix some python2-isms (@lieryan) - Rope's AST Wrapper - #536, #578 walk does not return a value (@edreamleo) - #537, #538 Remove special case code from walk (@edreamleo) - #581 Remove functions in rope.base.ast that has functionally identical implementation in stdlib's ast (@lieryan, @edreamleo) - #582 Refactoring rope.base.ast and remove rope.base.astutils (@lieryan, @edreamleo) - pynames and pyobjects - #569, #572 rename pynames to pynamesdef in pyobjectsdef.ph (@edreamleo) # Release 1.5.1 - #531 Add alternative way to retrieve version number from pyproject.toml # Release 1.5.0 Date: 2022-11-23 - #492 Feat: Global configuration support (@bagel897) - #519 Move pytest to pyproject.toml (@gliptak, @bagel897) - #509 Fix read/write analysis of the left-hand side of an augmented assignment (@lieryan) - #522 Implement patchedast parsing of MatchMapping (@lieryan) - #514 Fix inlining dictionary with inline comment (@lieryan) # Release 1.4.0 Date: 2022-10-22 ## Bug fixes - #506, #507 Fix issue with parsing function call args list - #411, #505 Fix extracting generator without parens - #18, #510 When the function is a builtin function, the call parameter's name was sometimes incorrectly identified as an AssignedName. This led to rename refactoring incorrectly renaming these parameters. # Release 1.3.0 Date: 2022-07-29 ## Bug fixes - #496, #497 Add MatMul operator to patchedast - #495 Fix autoimport collection for compiled modules ## Improvement - #501, #502 Autoimport improvements # Release 1.2.0 Date: 2022-04-22 ## New feature - #473 Pyproject.toml support (@bageljrkhanofemus) - #489 Rope now publishes documentations to rope.readthedocs.org (@bageljrkhanofemus) - #490 Migrate from setup.py to pyproject.toml (@bageljrkhanofemus) ## Improvement - #479 Add ABC and type hints for TaskHandle and JobSet (@bageljrkhanofemus) - #486 Drop Python 2 support (@bageljrkhanofemus, @lieryan) - #487 Improved value inference of __all__ declaration (@lieryan) - #424 Add some basic __repr__ to make it easier for debugging (@lieryan) # Release 1.1.1 ## Bug fixes - #476 Fix rope.contrib.autoimport package missing from release (@bageljrkhanofemus) # Release 1.1.0 Date: 2022-05-25 ## New feature - #464 Add new autoimport implementation that uses a sqllite3 database, cache all available modules quickly, search for names and produce import statements, sort import statements. (@bageljrkhanofemus) ## Bug fixes - #419 Fix bug while moving decorated function (@dryobates) - #439 Fix bug while moving decorated class (@dryobates) - #461 Fix bug while extracting method with list comprehension in class method (@dryobates) - #440 Fix bug while inlining function with type hints in signature (@dryobates) ## Deprecation - The pickle-based autoimport implementation is still the default, but will be deprecated sometime in the future. # Release 1.0.0 Date: 2022-04-08 ## Syntax support - #400 Drop Python 2.7 support ## Bug fixes - #459 Fix bug while extracting method with augmented assignment to subscript in try block (@dryobates) # Release 0.23.0 ## Syntax support - #451, $456 Implement structural pattern matching (PEP634) (@lieryan) - #458 Improve the heuristic for joining lines when extracting one line expression (@lieryan) ## Bug fixes - #134, #453 Preserve newline format when writing files (@lieryan) - #457 Fix extract info collection for list comprehension with multiple targets (@lieryan) ## Documentation - #455 Fix typo (@Jasha10) # Release 0.22.0 Date: 2021-11-23 ## 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) ## Misc - #447 Add Python 3.10 to tests # 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) - context manager: - #387, #433 Implement extract refactoring for code containing `async with` (@lieryan) - #398, #104 Fix parsing of nested `with` statement/context manager (@climbus) - list/set/dict/generator comprehension scope issues: - #422 Added scopes for comprehension expressions as part of #293 (@climbus) - #426, #429 Added support for checking scopes by offset as part of #293 (@climbus) - #293, #430 Fix renaming global var affects list comprehension (@climbus) - #395, #315 Reuse of variable in comprehensions confuses method extraction (@climbus) - #436 Fix error `TypeError: 'PyDefinedObject' object is not subscriptable` (@lieryan) - f-string: - #303, #420 Fix inlining into f-string containing quote characters (@lieryan) - inline assignment/walrus operator: - #423 Fix `AttributeError: '_ExpressionVisitor' object has no attribute 'defineds'` (@lieryan) ## Bug fixes - #391, #376 Fix improper replacement when extracting attribute access expression with `similar=True` (@climbus) - #396 Fix improper replacement when extracting index access expression with `similar=True` (@lieryan) ## New feature - #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 (@lieryan) - #403 Remove plain 'unittest' only runner (@lieryan) # Release 0.20.1 Date: 2021-09-18 ## Bug fixes - Fix caller of `_namedexpr_last()` throwing exception due to returning unexpected list instead of boolean # Release 0.20.0 Date: 2021-09-18 ## New feature - #377 Added the ability to extract method to @staticmethod/@classmethod (@climbus) - #374 Changed Organize import to keep variables listed in `__all__` - Change default .ropeproject/config.py to ignore code in folders named .venv and venv (@0x1e02) ## Syntax support - #372 Add extract method refactoring of code containing `exec` (@ceridwen) - #389 Add extract method refactoring of code containing `async def`, `async for`, and `await` - #365, #386 Support extract method of expressions containing inline assignment (walrus operator) ## Bug fixes - #380 Fix list of variables that are returned and/or turned into argument when extracting method in a loop # Previous releases [Changelog from pre-0.10.0](https://github.com/python-rope/rope/blob/595af418e7e7e844dcce600778e1c650c2fc0ba1/docs/done.rst). ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/COPYING0000664000175000017500000001674414512700666013550 0ustar00lieryanlieryan GNU LESSER GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below. 0. Additional Definitions. As used herein, "this License" refers to version 3 of the GNU Lesser General Public License, and the "GNU GPL" refers to version 3 of the GNU General Public License. "The Library" refers to a covered work governed by this License, other than an Application or a Combined Work as defined below. An "Application" is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library. A "Combined Work" is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the "Linked Version". The "Minimal Corresponding Source" for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version. The "Corresponding Application Code" for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work. 1. Exception to Section 3 of the GNU GPL. You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. 2. Conveying Modified Versions. If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version: a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy. 3. Object Code Incorporating Material from Library Header Files. The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following: a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the object code with a copy of the GNU GPL and this license document. 4. Combined Works. You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following: a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the Combined Work with a copy of the GNU GPL and this license document. c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. d) Do one of the following: 0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source. 1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version. e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.) 5. Combined Libraries. You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License. b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 6. Revised Versions of the GNU Lesser General Public License. The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation. If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/MANIFEST.in0000664000175000017500000000022614512700666014237 0ustar00lieryanlieryaninclude README.rst COPYING setup.py MANIFEST.in CHANGELOG.md recursive-include rope *.py recursive-include docs *.rst recursive-include ropetest *.py ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1705553002.7139843 rope-1.12.0/PKG-INFO0000644000175000017500000001431314552126153013573 0ustar00lieryanlieryanMetadata-Version: 2.1 Name: rope Version: 1.12.0 Summary: a python refactoring library... Author-email: Ali Gholami Rudi Maintainer-email: Lie Ryan License: LGPL-3.0-or-later Project-URL: Source, https://github.com/python-rope/rope Project-URL: Documentation, https://rope.readthedocs.io/ Classifier: Development Status :: 4 - Beta Classifier: Operating System :: OS Independent Classifier: Environment :: X11 Applications Classifier: Environment :: Win32 (MS Windows) Classifier: Environment :: MacOS X Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+) Classifier: Natural Language :: English Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3.12 Classifier: Topic :: Software Development Requires-Python: >=3.8 Description-Content-Type: text/x-rst License-File: COPYING Requires-Dist: pytoolconfig[global]>=1.2.2 Provides-Extra: doc Requires-Dist: pytoolconfig[doc]; extra == "doc" Requires-Dist: sphinx>=4.5.0; extra == "doc" Requires-Dist: sphinx-autodoc-typehints>=1.18.1; extra == "doc" Requires-Dist: sphinx-rtd-theme>=1.0.0; extra == "doc" Provides-Extra: dev Requires-Dist: pytest>=7.0.1; extra == "dev" Requires-Dist: pytest-timeout>=2.1.0; extra == "dev" Requires-Dist: build>=0.7.0; extra == "dev" Requires-Dist: pre-commit>=2.20.0; extra == "dev" Provides-Extra: release Requires-Dist: toml>=0.10.2; extra == "release" Requires-Dist: twine>=4.0.2; extra == "release" Requires-Dist: pip-tools>=6.12.1; extra == "release" .. _GitHub python-rope / rope: https://github.com/python-rope/rope ========================================================================= rope -- the world's most advanced open source Python refactoring library ========================================================================= |Build status badge| |Latest version badge| |Download count badge| |ReadTheDocs status badge| .. |Build status badge| image:: https://github.com/python-rope/rope/actions/workflows/main.yml/badge.svg :target: https://github.com/python-rope/rope/actions/workflows/main.yml :alt: Build Status .. |Latest version badge| image:: https://badge.fury.io/py/rope.svg :target: https://badge.fury.io/py/rope :alt: Latest version .. |Download count badge| image:: https://img.shields.io/pypi/dm/rope.svg :alt: Download count .. |ReadTheDocs status badge| image:: https://readthedocs.org/projects/rope/badge/?version=latest :target: https://rope.readthedocs.io/en/latest/?badge=latest :alt: Documentation Status 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 up to Python 3.10 is supported. Please file bugs and contribute patches if you encounter gaps. Since version 1.0.0, rope no longer support running on Python 2. If you need Python 2 support, then check out the `python2` branch or the 0.x.x releases. Getting Started =============== * `Documentation `_ * `How to use Rope in my IDE or Text editor? `_ * `Configuration `_ * `List of features `_ * `Overview of some of rope's features `_ * `Using as a library `_ * `Contributing `_ Why use Rope? ============= - Rope aims to provide powerful and safe refactoring - Rope is light on dependency, Rope only depends on Python itself - Unlike PyRight or PyLance, Rope does not depend on Node.js - Unlike PyLance or PyCharm, Rope is open source. - Unlike PyRight and PyLance, Rope is written in Python itself, so if you experience problems, you would be able to debug and hack it yourself in a language that you are already familiar with - In comparison to Jedi, Rope is focused on refactoring. While Jedi provides some basic refactoring capabilities, Rope supports many more advanced refactoring operations and options that Jedi does not. Bug Reports =========== Send your bug reports and feature requests at `python-rope's issue tracker`_ in GitHub. .. _`python-rope's issue tracker`: https://github.com/python-rope/rope/issues Maintainers =========== Current active maintainer of Rope is Lie Ryan (`@lieryan`_). Special Thanks ============== Many thanks the following people: - Ali Gholami Rudi (`@aligrudi`_) for initially creating the initial Rope project and most of Rope's code - Matej Cepl (`@mcepl`_) as former long-time Rope maintainer - Nick Smith (`@soupytwist`_) as former Rope maintainer - `all of our current and former contributors`_ - `all authors of editor integrations`_ - all maintainers of distro/package managers .. _`@aligrudi`: https://github.com/aligrudi .. _`@soupytwist`: https://github.com/soupytwist .. _`@lieryan`: https://github.com/lieryan .. _`@mcepl`: https://github.com/mcepl .. _`all of our current and former contributors`: https://github.com/python-rope/rope/blob/master/CONTRIBUTORS.md .. _`all authors of editor integrations`: https://github.com/python-rope/rope/wiki/How-to-use-Rope-in-my-IDE-or-Text-editor%3F Packaging Status ================ .. image:: https://repology.org/badge/vertical-allrepos/python:rope.svg?exclude_unsupported=1 :target: https://repology.org/project/python:rope/versions :alt: Packaging status .. image:: https://repology.org/badge/vertical-allrepos/rope.svg?exclude_unsupported=1 :target: https://repology.org/project/rope/versions :alt: Packaging status License ======= This program is under the terms of LGPL v3+ (GNU Lesser General Public License). Have a look at `COPYING`_ for more information. .. _`COPYING`: COPYING ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/README.rst0000664000175000017500000001057614512700666014201 0ustar00lieryanlieryan .. _GitHub python-rope / rope: https://github.com/python-rope/rope ========================================================================= rope -- the world's most advanced open source Python refactoring library ========================================================================= |Build status badge| |Latest version badge| |Download count badge| |ReadTheDocs status badge| .. |Build status badge| image:: https://github.com/python-rope/rope/actions/workflows/main.yml/badge.svg :target: https://github.com/python-rope/rope/actions/workflows/main.yml :alt: Build Status .. |Latest version badge| image:: https://badge.fury.io/py/rope.svg :target: https://badge.fury.io/py/rope :alt: Latest version .. |Download count badge| image:: https://img.shields.io/pypi/dm/rope.svg :alt: Download count .. |ReadTheDocs status badge| image:: https://readthedocs.org/projects/rope/badge/?version=latest :target: https://rope.readthedocs.io/en/latest/?badge=latest :alt: Documentation Status 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 up to Python 3.10 is supported. Please file bugs and contribute patches if you encounter gaps. Since version 1.0.0, rope no longer support running on Python 2. If you need Python 2 support, then check out the `python2` branch or the 0.x.x releases. Getting Started =============== * `Documentation `_ * `How to use Rope in my IDE or Text editor? `_ * `Configuration `_ * `List of features `_ * `Overview of some of rope's features `_ * `Using as a library `_ * `Contributing `_ Why use Rope? ============= - Rope aims to provide powerful and safe refactoring - Rope is light on dependency, Rope only depends on Python itself - Unlike PyRight or PyLance, Rope does not depend on Node.js - Unlike PyLance or PyCharm, Rope is open source. - Unlike PyRight and PyLance, Rope is written in Python itself, so if you experience problems, you would be able to debug and hack it yourself in a language that you are already familiar with - In comparison to Jedi, Rope is focused on refactoring. While Jedi provides some basic refactoring capabilities, Rope supports many more advanced refactoring operations and options that Jedi does not. Bug Reports =========== Send your bug reports and feature requests at `python-rope's issue tracker`_ in GitHub. .. _`python-rope's issue tracker`: https://github.com/python-rope/rope/issues Maintainers =========== Current active maintainer of Rope is Lie Ryan (`@lieryan`_). Special Thanks ============== Many thanks the following people: - Ali Gholami Rudi (`@aligrudi`_) for initially creating the initial Rope project and most of Rope's code - Matej Cepl (`@mcepl`_) as former long-time Rope maintainer - Nick Smith (`@soupytwist`_) as former Rope maintainer - `all of our current and former contributors`_ - `all authors of editor integrations`_ - all maintainers of distro/package managers .. _`@aligrudi`: https://github.com/aligrudi .. _`@soupytwist`: https://github.com/soupytwist .. _`@lieryan`: https://github.com/lieryan .. _`@mcepl`: https://github.com/mcepl .. _`all of our current and former contributors`: https://github.com/python-rope/rope/blob/master/CONTRIBUTORS.md .. _`all authors of editor integrations`: https://github.com/python-rope/rope/wiki/How-to-use-Rope-in-my-IDE-or-Text-editor%3F Packaging Status ================ .. image:: https://repology.org/badge/vertical-allrepos/python:rope.svg?exclude_unsupported=1 :target: https://repology.org/project/python:rope/versions :alt: Packaging status .. image:: https://repology.org/badge/vertical-allrepos/rope.svg?exclude_unsupported=1 :target: https://repology.org/project/rope/versions :alt: Packaging status License ======= This program is under the terms of LGPL v3+ (GNU Lesser General Public License). Have a look at `COPYING`_ for more information. .. _`COPYING`: COPYING ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1705553002.6699822 rope-1.12.0/docs/0000775000175000017500000000000014552126153013426 5ustar00lieryanlieryan././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/docs/configuration.rst0000664000175000017500000000326714512700666017042 0ustar00lieryanlieryanConfiguration ============= Rope supports the following configuration formats 1. pyproject.toml 2. config.py 3. pytool.toml pyproject.toml -------------- Will be used if [tool.rope] is configured. .. code-block:: toml [tool.rope] split_imports = true config.py --------- You can also configure rope via a config.py in the ropefolder directory. It will be used if ``[tool.rope]`` is not present in ``pyproject.toml`` or ``pyproject.toml`` isn't present and config.py is present. .. code-block:: python3 def set_prefs(prefs): prefs["ignored_resources"] = [ "*.pyc", "*~", ".ropeproject", ".hg", ".svn", "_svn", ".git", ".tox", ".venv", "venv", ] Additionally, you can run an executable function at startup of rope. .. code-block:: python3 def project_opened(project): """This function is called after opening the project""" # Do whatever you like here! pytool.toml ----------- If neither a config.py or a pyproject.toml is present, rope will use a pytool.toml. It follows the exact same syntax of the pyproject.toml. - Mac OS X: ``~/Library/Application Support/pytool.toml.`` - Unix: ``~/.config/pytool.toml``` or in $XDG_CONFIG_HOME, if defined - Windows: ``C:\Users\\AppData\Local\pytool.toml`` Options ------- .. autopytoolconfigtable:: rope.base.prefs.Prefs Old Configuration File ---------------------- This is a sample config.py. While this config.py works and all options here should be supported, the above documentation reflects the latest version of rope. .. literalinclude:: default_config.py :language: python3 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1705551350.0 rope-1.12.0/docs/contributing.rst0000664000175000017500000001131414552122766016675 0ustar00lieryanlieryan====================== Contributing to Rope ====================== Getting Involved! ================= Rope's main goal is being a good refactoring tool for python. It also provides some IDE helpers. If you would like to contribute, you're welcome to! How to Help Rope? ================= Rope's development happens in `python-rope's Github`_. Use `python-rope's Github Issue Tracker`_ to discuss development-related issues: * Send bug reports and request features * Submit patches for bugs or new features Use `python-rope's Github Discussion`_ for other discussions, such as: * Help using rope * Help integrating rope with text editors/tools * Discuss your ideas * Engage with the rope community .. _`python-rope's Github`: https://github.com/python-rope/rope .. _`python-rope's Github Issue Tracker`: https://github.com/python-rope/rope/issues .. _`python-rope's Github Discussion`: https://github.com/python-rope/rope/discussions Wish List ========= You are welcome to make pull requests in `python-rope's Github Issue Tracker`_. Here is a list of suggestions. Issues ------ If this is your first time contributing in rope and you don't know where to start, tickets labeled `good first issue`_ is a good place start. The `unresolved issues list`_ in Github is the latest todo list. There is also a rather outdated list in :ref:`dev/issues:Rope Issues`. There is a section called "unresolved issues"; it contains almost every kind of task. This file will need some cleanup, thoughts, and discussions. Pickup whichever you are most interested in. If you have ideas or questions about them, don't hesitate to create a Github ticket for it. .. _`good first issue`: https://github.com/python-rope/rope/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22 .. _`unresolved issues list`: https://github.com/python-rope/rope/issues Write Text Editors or IDE plugins for Rope ------------------------------------------ See pylsp-rope_, ropemacs_, ropevim_, eric4_. .. _pylsp-rope: https://github.com/python-rope/pylsp-rope/ .. _ropemacs: https://github.com/python-rope/ropemacs/ .. _ropevim: https://github.com/python-rope/ropevim/ .. _eric4: http://eric-ide.python-projects.org/ Rope Structure ============== Rope package structure: * `rope.base`: the base part of rope * `rope.refactor`: refactorings and tools used in them * `rope.contrib`: IDE helpers Have a look at ``__init__.py`` of these packages or :ref:`library:Using Rope As A Library` for more information. There's also some really good `tour of Rope's codebase`_ by Austin Bingham (author of `Traad`_). The first 10 minutes of the video talked about Rope in general, the rest are more specific to Traad. .. _tour of Rope's codebase: https://youtu.be/NvV5OrVk24c .. _traad: https://github.com/abingham/traad/ Source Repository ================= Rope uses GitHub_. The repository exists at `python-rope/rope`_. Setting up for local development ================================ #. Clone repository: ``git clone https://github.com/python-rope/rope.git`` #. Create a virtualenv: ``python -m venv rope-venv`` #. Activate the virtualenv #. Install the project into the venv: ``pip install -e '.[doc,dev]'`` Submitting pull requests ======================== Pull requests are welcome. Follow the instructions on GitHub_ on how to setup Git and fork the `python-rope/rope`_ repository. Once your changes are ready, send a `pull request`_ for review. Programming Style ----------------- * Follow `black codestyle`_ * Follow :PEP:`8`. * Use four spaces for indentation. * Include good unit-tests when appropriate. * Rope test suite should pass after patching. .. _`black codestyle`: https://github.com/psf/black Testing ------- Rope uses `pytest`_. To run the test:: pytest -v Many of rope's tests are still written using ``unittest.TestCase`` style, but running the test suite using vanilla ``unittest`` is no longer supported. Make sure to have complete test suite passing and add new tests for the changes you are providing with each new submission. All required packages for development could be installed with:: pip install -e ".[dev]" .. _GitHub: http://github.com/ .. _`python-rope/rope`: https://github.com/python-rope/rope .. _`pull request`: https://help.github.com/articles/using-pull-requests .. _`pytest`: https://pytest.org/ .. _gha-cache-key: Updating gha-cache-key.txt -------------------------- ``gha-cache-key.txt`` file is used as cache-key for Github Action to cache pip packages. Refer to `PR #650`_ to see how it works. .. _`PR #650`: https://github.com/python-rope/rope/pull/650 To re-generate the cache key, run this command: .. code-block:: sh $ pip-compile --extra dev --generate-hashes -o gha-cache-key.txt $ git add gha-cache-key.txt $ git commit ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1705553002.6699822 rope-1.12.0/docs/dev/0000775000175000017500000000000014552126153014204 5ustar00lieryanlieryan././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/docs/dev/issues.rst0000664000175000017500000000750214512700666016260 0ustar00lieryanlieryan============= Rope Issues ============= NOTE: this file is severely outdated and is no longer actively used by rope to keep track of issues. It is kept only for historical purpose. Use the `Issue Tracker`_ instead .. _`Issue Tracker`: https://github.com/python-rope/rope/issues Unresolved Issues ================= * purging out less accurate callinfos when better ones appear? * using properties without calling its get? * global variable inlines * transform and extension modules * merging extract and usefunction * caching instances of PyObject * moving a group of elements together * temps might be read after body in usefunction or extract * usefunction and function returns * usefunction on methods * extracted functions should be inserted before using class bodies * adding "referenced later" wildcard argument to restructurings? * adding "change references" wildcard argument to restructurings? * ideas for more custom wildcards * custom wildcards and recursive patterns * custom restructuring wildcard patterns and replacements * not reimporting back imports after moving * importing compressed objectdb/history data? * not applying all commenting mechanisms always in codeassist * fixing try blocks before current line in code_assist * better tests for patchedast * import actions with more that one phase and filtering problems * handle long imports should work on filtered imports unconditionally? * extracting subexpressions; look at `extracttest` for more info * unignored files that are not under version control * inline fails when there is an arg mismatch * evaluate function parameter defaults in staticoi? * saving diffs instead of old contents in ChangeContents? * handling tuple parameters * extract class * analyzing function decorators * generate ... and implicit interfaces * generate method and class hierarchies * lambdas as functions; consider their parameters * renaming similarly named variables * handling the return type of ``yield`` keyword * not writing unchanged objectdb and history? To Be Reviewed ============== * review patchedast; make it faster * lots of estimations in codeanalyze in WordRangeFinder * review objectdb modules * how concluded data are held for star imports Insert Before In Restructurings =============================== Consider a restructuring like this:: pattern: ${a} if ${b} else ${c} goal: replacement before: if ${b}:\n replacement = ${a}\nelse:\n replacement = ${c} Memory Management ================= These are the places in which rope spends most of the memory it consumes: * PyCore: for storing PyModules * ObjectInfo: for storing object information * History: for storing changes We should measure the amount of memory each of them use to make decisions. Custom Restructuring Wildcards ============================== There is a need to add more custom wildcards in restructuring patterns. But adding all such needs to `similarfinder` module makes it really complex. So I think adding the ability to extend them is useful. Sometimes wildcards can be customized. For instance one might want to match the function calls only if ``p1`` is passed in the arguments. They can be specified in wildcard arguments. Since matched wildcards can appear in the goal pattern, each wildcard should have a corresponding replacement wildcard. Each replacement might be customized in each place it appears; for instance ``${mycall:-p1}`` might mean to remove ``p1`` argument. Wildcard Format --------------- All wildcards should appear as ``${name}``. The type of wildcards and their parameters can be specified using the ``args`` argument of ``Restructuring()``. Ideas: * Maybe we can put checks inside args, too:: pattern: ${project:type=rope.base.project.Project}.pycore But what should be done when a variable appears twice:: pattern: ${a:type=__builtin__.int} + ${a} Examples -------- .. ... ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/docs/index.rst0000664000175000017500000000101614512700666015270 0ustar00lieryanlieryan.. rope documentation master file, created by sphinx-quickstart on Sat May 21 18:16:44 2022. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. Welcome to rope's documentation! ================================ .. toctree:: :maxdepth: 2 :caption: Contents: overview rope library configuration contributing release-process dev/issues Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350120.0 rope-1.12.0/docs/library.rst0000664000175000017500000006777314512700750015645 0ustar00lieryanlieryan========================= Using Rope As A Library ========================= If you need other features, send a feature request. Have a look at :ref:`contributing:Contributing to Rope`. .. contents:: Table of Contents Quick Start =========== This section will help you get started as soon as possible. Making A Project ---------------- The first thing you should do is make a project: .. code-block:: python import rope.base.project myproject = rope.base.project.Project('/path/to/myproject') It's good to know that: * A project is a folder in the file-system. * It can contain anything. * Rope searches for python modules and packages inside a project when needed. * Refactorings only change files and folders inside the project that has been passed to them. * Out of project modules that are imported from a module inside a project are handled but never changed by refactorings. * Rope makes a rope folder inside projects. By default the name of this folder is ``.ropeproject``, but that can be changed using the constructor's ``ropefolder`` parameter. Passing ``None`` prevents rope from making this folder. * Rope uses the ``.ropeproject`` folder for things like saving object information and loading project configurations. * Project preferences can be configured by passing options to the constructor or in ``.ropeproject/config.py``. See the default ``config.py``, ``rope.base.default_config`` module, for more information. * All configurations that are available in the ``config.py`` file can be specified as keyword parameters to the ``Project`` constructor. These parameters override the ones in the ``config.py`` file. * Each project has a set of ignored resource patterns. You can use it to tell rope to ignore files and folders matching certain patterns. * The ``.ropeproject`` folder can be safely copied in other clones of a project if you don't want to lose your objectdb and history. Library Utilities ----------------- The `rope.base.libutils`_ module provides tools that make using rope as a library easier. We'll talk more about this module later. What Are These `Resource`\s? ---------------------------- In rope, files and folders in a project are accessed through ``rope.base.resources.Resource`` objects. It has two subclasses ``File`` and ``Folder``. What we care about is that refactorings and ``Change``\s (we'll talk about them later) use resources. There are two options for creating a ``Resource`` for a path in a project. The first approach uses the ``Project.get_resource()`` method. .. code-block:: python from rope.base import project myresource = myproject.get_resource('/path/to/resource') However, it's preferable to use the ``libutils.path_to_resource()`` function, because it's more flexible and offers a unified way to create resources. It takes a ``project`` and ``path`` as parameters with an optional ``type``. The ``type`` parameter, with values ``file`` or ``folder``, can create a resource for an object that doesn't exist yet. .. code-block:: python from rope.base import libutils myresource = libutils.path_to_resource(myproject, '/path/to/resource') Consider we have a resource. How can we know anything about it? The answer is to use its ``path`` and ``real_path`` attributes. ``Resource.real_path`` is the absolute path of the resource in the file-system. The ``Resource.path`` attribute contains the address of a resource relative to the project's root. Performing Refactorings ----------------------- As a short example of performing refactorings, we'll show how to extract a variable from a file. First we need the ``Resource`` object that points to a file in a project: .. code-block:: python resource = libutils.path_to_resource(myproject, '/path/to/my/module.py') Now we can make our Refactoring class: .. code-block:: python from rope.refactor.extract import ExtractVariable extractor = ExtractVariable(myproject, resource, start, end) Where ``start`` and ``end`` are the offsets of the region to extract in ``resource``. Be careful when calculating the offsets. DOS line-endings and multi-byte characters are considered to be one character. This is actually easier for IDEs, since most GUI libraries handle those cases for you. Next, the IDE ask the user to configure refactoring options, like specifying the name of the extracted variable. After that, we can calculate the changes: .. code-block:: python changes = extractor.get_changes('extracted_variable') Each refactoring returns a ``rope.base.change.Change`` object that holds 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. Previewing And Performing Changes --------------------------------- As mentioned in the last section each refactoring returns a ``rope.base.change.Change`` object. Now how can we know what it contains and how to perform it? *Previewing*: You can use ``changes.get_description()`` to get a preview. It is useful when you don't care much about the format. Otherwise you can use the ``changes`` object directly. See the documentation in ``rope.base.change`` module. *Performing*: The easiest way for performing the refactoring is to use the `Project.do()`_ method: .. code-block:: python myproject.do(changes) If you want to perform the changes yourself, you have two options. Note that the main reason for performing the changes manually is handling version control systems that are not supported by rope. 1. The first approach is to use `rope.base.fscommands`_ (see `Writing A FileSystemCommands`_). The changes can be performed as before using `Project.do()`_. 2. The second approach is to perform the changes manually based on the returned ``changes`` object (again see the documentation in ``rope.base.change`` module). If this approach is used you cannot undo the refactoring using ``project.history.undo()``. *Updating Open Buffers In IDEs*: Usually editors need to reload the files changed by rope. You can use ``Change.get_changed_resources()`` to get the list of resources that need to be reloaded. Validating The Project ---------------------- When using rope as a library, you probably change the files in it in parallel (for example in IDEs). To force rope to invalidate cached information about resources that have been removed or changed outside rope, you should call the `Project.validate()`_ method. You can pass a resource to this method. For example: .. code-block:: python myproject.validate(resource) This validates all files and directories in resource. Call this function every time you want use rope (i.e., before performing refactorings). Performing Static Object Analysis --------------------------------- One of the greatest strengths of rope is its Static Object Analysis (SOA). It analyzes function calls and assignments to collect the types of objects passed to the function. Rope uses the collected data to infer the type of function parameters, return values, and the objects stored in built-in containers. The function ``rope.base.libutils.analyze_modules()`` performs SOA on all modules in the project. It is recommended that you call this function occasionally, and especially before performing large refactorings. Note that analyzing all modules of a project may take a long time. If you have ``automatic_soa`` set, which instructs rope to analyze the changed scopes of modules, then you should report the changes by calling ``rope.base.libutils.report_change()`` when saving files, as follows: .. code-block:: python # Save the new contents. old_contents = resource.read() resource.write(new_contents) # Inform rope about the change. libutils.report_change(myproject, path, old_contents) Note, however, that the use of ``automatic_soa`` is discouraged, because it may slow down saving considerably. Closing The Project ------------------- `Project.close()`_ closes a project's open resources. Always call this function when you don't need a project anymore: .. code-block:: python myproject.close() ``rope.base.libutils`` ====================== The ``rope.base.libutils`` module contains functions that make life easier for building refactoring tools. In some cases, the functions offer a unified way to access or create objects. You're encouraged to use ``rope.base.libutils`` functions whenever possible, because the APIs here may not be as volatile as class methods. ``libutils.analyze_module()`` ------------------------------ Perform static object analysis on a Python file in the project. Note, this may be a very time consuming task. .. code-block:: python libutils.analyze_module(myproject, resource) ``libutils.analyze_modules()`` ------------------------------ Perform static object analysis on all Python files in the project. Note that it might take a long time to finish. .. code-block:: python libutils.analyze_modules(myproject) ``libutils.get_string_module()`` -------------------------------- Returns a ``rope.base.pyobjects.PyModule`` object for the code string. An optional ``resource`` argument can be specified for the resource this code is associated with. If ``force_errors`` is ``True``, then ``rope.base.exceptions.ModuleSyntaxError`` is raised when the code has syntax errors. Otherwise, syntax errors are silently ignored. Note that ``force_errors`` overrides the ``ignore_syntax_errors`` project configuration flag. .. code-block:: python pymodule = libutils.get_string_module(myproject, source) ``libutils.get_string_scope()`` ------------------------------- Get the ``rope.base.pyscopes.GlobalScope`` object for the code string. This is the outermost scope of the code encompassing the whole module. .. code-block:: python scope = libutils.get_string_scope(myproject, source) ``libutils.is_python_file()`` ----------------------------- Returns ``True`` if the resource is a Python file. .. code-block:: python libutils.is_python_file(myproject, resource) ``libutils.modname()`` ---------------------- Retrieves the dotted path string to the module that contains that given resource. .. code-block:: python # If resource is 'path/to/resource.py' relative to the project's root # directory, this returns the string: 'path.to.resource'. module_name = libutils.modname(resource) ``libutils.path_relative_to_project_root()`` -------------------------------------------- Retrieve the path relative to the project's root directory. .. code-block:: python # Get the path relative to the project's root directory. relpath = libutils.relative(myproject.address, path) ``libutils.path_to_resource()`` ------------------------------- Get the resource --- a file or folder --- at the given path. An optional ``type`` argument can be used if the resource doesn't yet exist. The values for ``type`` are the strings ``'file'`` or ``'folder'``. .. code-block:: python # Resource for an existing file. myfile = libutils.path_to_resource(myproject, '/path/to/file.py') # Resource for a non-existing folder. new_folder = libutils.path_to_resource(myproject, '/path/to/folder', type='folder') ``rope.base.project.Project`` ============================= You can create a project by: .. code-block:: python project = Project(root_address) Where the ``root_address`` is the root folder of your project. A project has some useful attributes. ``Project.address`` is the address of the root folder of a project. ``Project.root`` is a ``Folder`` object that points to that folder. `Project.do()` -------------- Used to commit changes returned by refactorings: .. code-block:: python project.do(changes) `Project.history` ----------------- A ``rope.base.history.History`` object. You can use its ``undo`` and ``redo`` methods for undoing or redoing changes. Note that you can use this only if you have committed your changes using rope. `Project.validate()` -------------------- When using rope as a library, you will probably change the files in that project in parallel (for example in IDEs). To force rope to validate cached information about resources that have been removed or changed outside rope, you should call ``Project.validate()``. You should pass a resource to this method. For example: .. code-block:: python project.validate(project.root) This validates all files and directories in the project and clears the cache of all recorded changes. `Project.close()` ----------------- Closes a project's open resources. Always call this function when you don't need a project anymore. Currently it closes the files used for storing object information and project history. Because some parts of these files are in memory for efficiency, not closing a project might put them in an inconsistent state. `rope.base.fscommands` ====================== The ``rope.base.fscommands`` module implements basic file system operations that rope needs to perform. The main reason for the existence of this module is supporting version control systems. Have a look at ``FileSystemCommands`` and ``SubversionCommands`` in the same module. If you need other version control systems you can write a new class that provides this interface. ``rope.base.project.Project`` accepts an ``fscommands`` argument. You can use this argument to force rope to use your new class. ``.ropeproject`` Folder ======================= Since version ``0.5``, rope makes a ``.ropeproject`` folder in the project by default for saving project configurations and data. The name of this folder is passed to the constructor if you want to change that. You can force rope not to make such a folder by passing ``None``. If such a folder exists, rope loads the ``config.py`` file in that folder. It might also use it for storing object information and history. `rope.base.pycore.PyCore` ========================= Provides useful methods for managing python modules and packages. Each project has a ``PyCore`` that can be accessed using the ``Project.pycore`` attribute. ``PyCore.run_module()`` runs a resource. When running, it collects type information to do dynamic object inference. For this reason modules run much slower. Also ``Pycore.analyze_module()`` collects object information for a module. The collected information can be used to enhance rope's static object inference. `rope.base.taskhandle.TaskHandle` ================================= A TaskHandle can be used for stopping and monitoring the progress of time consuming tasks, like some refactorings. The ``Project.do()`` and ``Refactoring.get_changes()`` methods for most refactorings take a keyword parameter called ``task_handle``. You can pass a ``TaskHandle`` object to them. A ``TaskHandle`` can be used for interrupting or observing a task. Always pass ``task_handle`` as keyword argument. It will always be the last argument, and new arguments of the refactoring are added before it. A task might consist of a few ``JobSet``\s. Each ``JobSet`` performs a few jobs. For instance calculating the changes for renaming a method in a class hierarchy has two job sets: one to find the classes for constructing the class hierarchy and another to change the occurrences. The ``TaskHandle.current_jobset()`` returns the most recent ``JobSet`` or ``None`` if none has been started. You can use the methods of ``JobSet`` for obtaining information about the current job. So you might want to do something like: .. code-block:: python import rope.base.taskhandle handle = rope.base.taskhandle.TaskHandle("Test Task") def update_progress(): jobset = handle.current_jobsets() if jobset: text = '' # getting current job set name if jobset.get_name() is not None: text += jobset.get_name() # getting active job name if jobset.get_active_job_name() is not None: text += ' : ' + jobset.get_active_job_name() # adding done percent percent = jobset.get_percent_done() if percent is not None: text += ' ... %s percent done' % percent print(text) handle.add_observer(update_progress) changes = renamer.get_changes('new_name', task_handle=handle) Also you can use something like this for stopping the task: .. code-block:: python def stop(): handle.stop() After calling ``stop()``, the thread that is executing the task will be interrupted by a ``rope.base.exceptions.InterruptedTaskError`` exception. Refactorings ============ Have a look at ``rope.refactor`` package and its sub-modules. For example for performing a move refactoring you can create a ``Move`` object like this: .. code-block:: python mover = Move(project, resource, offset) Where ``resource`` and ``offset`` is the location to perform the refactoring. Then you can commit the changes by it using the ``get_changes()`` method: .. code-block:: python project.do(mover.get_changes(destination)) Where the ``destination`` module/package is the destination resource for move refactoring. Other refactorings classes have a similar interface. List Of Refactorings -------------------- Here is the list of refactorings rope provides. (Note that this list might be out of date.) For more information about these refactoring see pydocs in their modules and the unit-tests in the ``ropetest/refactor/`` folder. * ``rope.refactor.rename``: Rename something in the project. See the example below. * ``rope.refactor.move``: Move a python element in the project. * ``rope.refactor.restructure``: Restructure code. See the example below. * ``rope.refactor.extract``: Extract methods/variables. * ``rope.refactor.inline``: Inline occurrences of a method/variable/parameter. * ``rope.refactor.usefunction``: Try to use a function wherever possible. * ``rope.refactor.method_object``: Transform a function or a method to a method object. * ``rope.refactor.change_signature``: Change the signature of a function/method. * ``rope.refactor.introduce_factory``: Introduce a factory for a class and changes all constructors to use it. * ``rope.refactor.introduce_parameter``: Introduce a parameter in a function. * ``rope.refactor.encapsulate_field``: Generate a getter/setter for a field and changes its occurrences to use them. * ``rope.refactor.localtofield``: Change a local variable to field. * ``rope.refactor.topackage``: Transform a module to a package with the same name. * ``rope.refactor.importutils``: Perform actions like organize imports. Refactoring Resources Parameter ------------------------------- Some refactorings, restructure and find occurrences accept an argument called ``resources``. If it is a list of `File`\s, all other resources in the project are ignored and the refactoring only analyzes them. If it is ``None`` all python modules in the project will be analyzed. Using this parameter, IDEs can let the user limit the files on which a refactoring should be applied. Examples ======== Rename ------ Using rename refactoring: .. code-block:: python # Creating a project >>> from rope.base.project import Project >>> project = Project('.') # Working with files to create a module >>> mod1 = project.root.create_file('mod1.py') >>> mod1.write('a_var = 10\n') # Alternatively you can use `generate` module. # Creating modules and packages using `generate` module >>> from rope.contrib import generate >>> pkg = generate.create_package(project, 'pkg') >>> mod2 = generate.create_module(project, 'mod2', pkg) >>> mod2.write('import mod1\nprint(mod1.a_var)\n') # We can use `Project.find_module` for finding modules, too >>> assert mod2 == project.find_module('pkg.mod2') # Performing rename refactoring on `mod1.a_var` >>> from rope.refactor.rename import Rename >>> changes = Rename(project, mod1, 1).get_changes('new_var') >>> project.do(changes) >>> mod1.read() u'new_var = 10\n' >>> mod2.read() u'import mod1\nprint(mod1.new_var)\n' # Undoing rename refactoring >>> project.history.undo() ... >>> mod1.read() u'a_var = 10\n' >>> mod2.read() u'import mod1\nprint(mod1.a_var)\n' # Cleaning up >>> pkg.remove() >>> mod1.remove() >>> project.close() Restructuring ------------- The example for replacing occurrences of our ``pow`` function to ``**`` operator (see ref:`overview:Restructurings`): .. code-block:: python # Setting up the project >>> from rope.base.project import Project >>> project = Project('.') >>> mod1 = project.root.create_file('mod1.py') >>> mod1.write('def pow(x, y):\n result = 1\n' ... ' for i in range(y):\n result *= x\n' ... ' return result\n') >>> mod2 = project.root.create_file('mod2.py') >>> mod2.write('import mod1\nprint(mod1.pow(2, 3))\n') >>> from rope.refactor import restructure >>> pattern = '${pow_func}(${param1}, ${param2})' >>> goal = '${param1} ** ${param2}' >>> args = {'pow_func': 'name=mod1.pow'} >>> restructuring = restructure.Restructure(project, pattern, goal, args) >>> project.do(restructuring.get_changes()) >>> mod2.read() u'import mod1\nprint(2 ** 3)\n' # Cleaning up >>> mod1.remove() >>> mod2.remove() >>> project.close() See code documentation and test suites for more information. Other Topics ============ Writing A `FileSystemCommands` ------------------------------ The ``get_changes()`` method of refactoring classes return a ``rope.base.change.Change`` object. You perform these changes by calling ``Project.do()``. But as explained above some IDEs need to perform the changes themselves. Every change to the file-system in rope is committed using an object that provides a ``rope.base.fscommands.FileSystemCommands`` interface. As explained above in `rope.base.fscommands`_ section, rope uses this interface to handle different VCSs. You can implement your own fscommands object: .. code-block:: python class MyFileSystemCommands(object): def create_file(self, path): """Create a new file""" # ... def create_folder(self, path): """Create a new folder""" # ... def move(self, path, new_location): """Move resource at `path` to `new_location`""" # ... def remove(self, path): """Remove resource""" # ... def write(self, path, data): """Write `data` to file at `path`""" # ... def read(self, path): """Read `data` from file at `path`""" # ... And you can create a project like this: .. code-block:: python my_fscommands = MyFileSystemCommands() project = rope.base.project.Project('~/myproject', fscommands=my_fscommands) `rope.contrib.codeassist` ------------------------- The ``rope.contrib`` package contains modules that use rope base parts and provide useful features. ``rope.contrib.codeassist`` module can be used in IDEs: .. code-block:: python from rope.ide import codeassist # Get the proposals; you might want to pass a Resource proposals = codeassist.code_assist(project, source_code, offset) # Sorting proposals; for changing the order see pydoc proposals = codeassist.sorted_proposals(proposals) # Where to insert the completions starting_offset = codeassist.starting_offset(source_code, offset) # Applying a proposal proposal = proposals[x] replacement = proposal.name new_source_code = (source_code[:starting_offset] + replacement + source_code[offset:]) ``maxfixes`` parameter of ``code_assist`` decides how many syntax errors to fix. The default value is one. For instance: .. code-block:: python def f(): g(my^ myvariable = None def g(p): invalid syntax ... will report ``myvariable``, only if ``maxfixes`` is greater than 1. ``later_locals``, if ``True``, forces rope to propose names that are defined later in current scope. It is ``True`` by default. For instance: .. code-block:: python def f(): my^ myvariable = None will not report ``myvariable``, if ``later_locals`` is ``False``. See pydocs and source code for more information (other functions in this module might be interesting, too; like ``get_doc``, ``get_definition_location``). `rope.contrib.findit` --------------------- ``findit`` module provides ``find_occurrences()`` for finding occurrences of a name. Also the ``find_implementations()`` function finds the places in which a method is overridden. `rope.contrib.autoimport` ------------------------- This module can be used to find the modules that provide a name. IDEs can use this module to auto-import names. ``AutoImport.get_modules()`` returns the list of modules with the given global name. ``AutoImport.import_assist()`` tries to find the modules that have a global name that starts with the given prefix. There are currently two implementations of autoimport in rope, a deprecated implementation that uses pickle-based storage (rope.contrib.autoimport.pickle.AutoImport) and a new, experimental one that uses sqlite3 database (rope.contrib.autoimport.sqlite.AutoImport). New and existing integrations should migrate to the sqlite3 storage as the pickle-based autoimport will be removed in the future. `rope.contrib.autoimport.sqlite` -------------------------------- Currently, the sqlite3-based only stores autoimport cache in an in-memory sqlite3 database, you can make it write the import cache to persistent storage by passing memory=False to AutoImport constructor. This default will change in the future, if you want to always store the autoimport cache in-memory, then you need to explicitly pass memory=True. It must be closed when done with the ``AutoImport.close()`` method. AutoImport can search for a name from both modules and statements you can import from them. .. code-block:: python from rope.base.project import Project from rope.contrib.autoimport import AutoImport project = Project("/path/to/project") autoimport = AutoImport(project, memory=False) autoimport.generate_resource_cache() # Generates a cache of the local modules, from the project you're working on autoimport.generate_modules_cache() # Generates a cache of external modules print(autoimport.search("Dict")) autoimport.close() project.close() It provides two new search methods: - search_full() - returns a list of mostly unsorted tuples. This has itemkind and source information. - search() - simpler wrapper around search_full with a basic sorting algorithm Cross-Project Refactorings -------------------------- ``rope.refactor.multiproject`` can be used to perform a refactoring across multiple projects. Usually refactorings have a main project. That is the project that contains the definition of the changing python name. Other projects depend on the main one, and the uses of the changed name in them should be updated. Each refactoring changes only one project (the project passed to its constructor). But we can use the ``MultiProjectRefactoring`` proxy to perform a refactoring on other projects, too. First we need to create a multi-project refactoring constructor. As an example consider that we want to perform a rename refactoring: .. code-block:: python from rope.refactor import multiproject, rename CrossRename = multiproject.MultiProjectRefactoring(rename.Rename, projects) Here ``projects`` is the list of dependent projects. It does not include the main project. The first argument is the refactoring class (such as ``Rename``) or factory function (like ``create_move``). Next we can construct the refactoring: .. code-block:: python renamer = CrossRename(project, resource, offset) We create the rename refactoring as we do for normal refactoings. Note that ``project`` is the main project. As mentioned above, other projects use the main project. Rope automatically adds the main project to the python path of other projects. Finally we can calculate the changes. But instead of calling ``get_changes()`` (which returns main project changes only), we can call ``get_all_changes()`` with the same arguments. It returns a list of ``(project, changes)`` tuples. You can perform them manually by calling ``project.do(changes)`` for each tuple, or use ``multiproject.perform()``: .. code-block:: python project_and_changes = renamer.get_all_changes('newname') multiproject.perform(project_and_changes) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/docs/overview.rst0000664000175000017500000007445014512700666016043 0ustar00lieryanlieryan=============== Rope Overview =============== The purpose of this file is to give an overview of some of rope's features. It is incomplete. And some of the features shown here are old and do not show what rope can do in extremes. So if you really want to feel the power of rope try its features and see its unit tests. This file is more suitable for the users. Developers who plan to use rope as a library might find :ref:`library:Using Rope As A Library` more useful. .. contents:: Table of Contents ``.ropeproject`` Folder ======================= Rope uses a folder inside projects for holding project configuration and data. Its default name is ``.ropeproject``, but it can be changed (you can even tell rope not to create this folder). Currently it is used for things such as: * There is a ``config.py`` file in this folder in which you can change project configurations. Have look at the default ``config.py`` file (is created when it does not exist) for more information. * It can be used for saving project history, so that the next time you open the project you can undo past changes. * It can be used for saving object information to help rope object inference. * It can be used for saving global names cache which is used in auto-import. You can change what to save and what not to in the ``config.py`` file. Key bindings ============ Rope is a library that is used in many IDE and Text Editors to perform refactoring on Python code. This page documents the details of the refactoring operations but you would need consult the documentation for your IDE/Text Editor client integration for the specific key bindings that are used by those IDE/Text Editors. Refactorings ============ This section shows some random refactorings that you can perform using rope. Renaming Attributes ------------------- Consider we have: .. code-block:: python class AClass(object): def __init__(self): self.an_attr = 1 def a_method(self, arg): print(self.an_attr, arg) a_var = AClass() a_var.a_method(a_var.an_attr) After renaming ``an_attr`` to ``new_attr`` and ``a_method`` to ``new_method`` we'll have: .. code-block:: python class AClass(object): def __init__(self): self.new_attr = 1 def new_method(self, arg): print(self.new_attr, arg) a_var = AClass() a_var.new_method(a_var.new_attr) Renaming Function Keyword Parameters ------------------------------------ On: .. code-block:: python def a_func(a_param): print(a_param) a_func(a_param=10) a_func(10) performing rename refactoring on any occurrence of ``a_param`` will result in: .. code-block:: python def a_func(new_param): print(new_param) a_func(new_param=10) a_func(10) Renaming modules ---------------- Consider the project tree is something like:: root/ mod1.py mod2.py ``mod1.py`` contains: .. code-block:: python import mod2 from mod2 import AClass mod2.a_func() a_var = AClass() After performing rename refactoring one of the ``mod2`` occurrences in `mod1` we'll get: .. code-block:: python import newmod from newmod import AClass newmod.a_func() a_var = AClass() and the new project tree would be:: root/ mod1.py newmod.py Renaming Occurrences In Strings And Comments -------------------------------------------- You can tell rope to rename all occurrences of a name in comments and strings. This can be done by passing ``docs=True`` to `Rename.get_changes()` method. Rope renames names in comments and strings only where the name is visible. For example in: .. code-block:: python def f(): a_var = 1 # INFO: I'm printing `a_var` print('a_var = %s' % a_var) # f prints a_var after we rename the `a_var` local variable in `f()` to `new_var` we would get: .. code-block:: python def f(): new_var = 1 # INFO: I'm printing `new_var` print('new_var = %s' % new_var) # f prints a_var This makes it safe to assume that this option does not perform wrong renames most of the time. This also changes occurrences inside evaluated strings: .. code-block:: python def func(): print('func() called') eval('func()') After renaming ``func`` to ``newfunc`` we should have: .. code-block:: python def newfunc(): print('newfunc() called') eval('newfunc()') Rename When Unsure ------------------ This option tells rope to rename when it doesn't know whether it is an exact match or not. For example after renaming `C.a_func` when the 'rename when unsure' option is set in: .. code-block:: python class C(object): def a_func(self): pass def a_func(arg): arg.a_func() C().a_func() we would have: .. code-block:: python class C(object): def new_func(self): pass def a_func(arg): arg.new_func() C().new_func() Note that the global ``a_func`` was not renamed because we are sure that it is not a match. But when using this option there might be some unexpected renames. So only use this option when the name is almost unique and is not defined in other places. Move Method Refactoring ----------------------- It happens when you perform move refactoring on a method of a class. In this refactoring, a method of a class is moved to the class of one of its attributes. The old method will call the new method. If you want to change all of the occurrences of the old method to use the new method you can inline it afterwards. For instance if you perform move method on ``a_method`` in: .. code-block:: python class A(object): pass class B(object): def __init__(self): self.attr = A() def a_method(self): pass b = B() b.a_method() You will be asked for the destination field and the name of the new method. If you use ``attr`` and ``new_method`` in these fields and press enter, you'll have: .. code-block:: python class A(object): def new_method(self): pass class B(object): def __init__(self): self.attr = A() def a_method(self): return self.attr.new_method() b = B() b.a_method() Now if you want to change the occurrences of ``B.a_method()`` to use ``A.new_method()``, you can inline ``B.a_method()``: .. code-block:: python class A(object): def new_method(self): pass class B(object): def __init__(self): self.attr = A() b = B() b.attr.new_method() Moving Fields ------------- Rope does not have a separate refactoring for moving fields. Rope's refactorings are very flexible, though. You can use the rename refactoring to move fields. For instance: .. code-block:: python class A(object): pass class B(object): def __init__(self): self.a = A() self.attr = 1 b = B() print(b.attr) consider we want to move ``attr`` to ``A``. We can do that by renaming ``attr`` to ``a.attr``: .. code-block:: python class A(object): pass class B(object): def __init__(self): self.a = A() self.a.attr = 1 b = B() print(b.a.attr) You can move the definition of ``attr`` manually. Moving Global Classes/Functions/Variables ----------------------------------------- You can move global classes/function/variables to another module by using the Move refactoring on a global object: For instance, in this refactoring, if you are moving ``twice()`` to ``pkg1.mod2``: .. code-block:: python # pkg1/mod1.py def twice(a): return a * 2 print(twice(4)) .. code-block:: python # pkg1/mod3.py import pkg1.mod1 pkg1.mod1.twice(13) When asked for the destination module, put in ``pkg1.mod2``. Rope will update all the imports. .. code-block:: python # pkg1/mod1.py import pkg1.mod2 print(pkg1.mod2.twice(4)) .. code-block:: python # pkg1/mod2.py def twice(a): return a * 2 .. code-block:: python # pkg1/mod3.py import pkg1.mod2 pkg1.mod2.twice(13) Extract Method -------------- In these examples ``${region_start}`` and ``${region_end}`` show the selected region for extraction: .. code-block:: python def a_func(): a = 1 b = 2 * a c = ${region_start}a * 2 + b * 3${region_end} After performing extract method we'll have: .. code-block:: python def a_func(): a = 1 b = 2 * a c = new_func(a, b) def new_func(a, b): return a * 2 + b * 3 For multi-line extractions if we have: .. code-block:: python def a_func(): a = 1 ${region_start}b = 2 * a c = a * 2 + b * 3${region_end} print(b, c) After performing extract method we'll have: .. code-block:: python def a_func(): a = 1 b, c = new_func(a) print(b, c) def new_func(a): b = 2 * a c = a * 2 + b * 3 return b, c Extracting Similar Expressions/Statements ----------------------------------------- When performing extract method or local variable refactorings you can tell rope to extract similar expressions/statements. For instance in: .. code-block:: python if True: x = 2 * 3 else: x = 2 * 3 + 1 Extracting ``2 * 3`` will result in: .. code-block:: python six = 2 * 3 if True: x = six else: x = six + 1 Extract Regular Method into staticmethod/classmethod ---------------------------------------------------- If you prefix the extracted method name with `@` or `$`, the generated method will be created as a `classmethod` and `staticmethod` respectively. For instance in: .. code-block:: python class A(object): def f(self, a): b = a * 2 if you select ``a * 2`` for method extraction and name the method ``@new_method``, you'll get: .. code-block:: python class A(object): def f(self, a): b = A.twice(a) @classmethod def new_method(cls, a): return a * 2 Similarly, you can prefix the name with `$` to create a staticmethod instead. Extract Method In staticmethods/classmethods -------------------------------------------- The extract method refactoring has been enhanced to handle static and class methods better. For instance in: .. code-block:: python class A(object): @staticmethod def f(a): b = a * 2 if you extract ``a * 2`` as a method you'll get: .. code-block:: python class A(object): @staticmethod def f(a): b = A.twice(a) @staticmethod def twice(a): return a * 2 Inline Method Refactoring ------------------------- Inline method refactoring can add imports when necessary. For instance consider ``mod1.py`` is: .. code-block:: python import sys class C(object): pass def do_something(): print(sys.version) return C() and ``mod2.py`` is: .. code-block:: python import mod1 c = mod1.do_something() After inlining ``do_something``, ``mod2.py`` would be: .. code-block:: python import mod1 import sys print(sys.version) c = mod1.C() Rope can inline methods, too: .. code-block:: python class C(object): var = 1 def f(self, p): result = self.var + pn return result c = C() x = c.f(1) After inlining ``C.f()``, we'll have: .. code-block:: python class C(object): var = 1 c = C() result = c.var + pn x = result As another example we will inline a ``classmethod``: .. code-block:: python class C(object): @classmethod def say_hello(cls, name): return 'Saying hello to %s from %s' % (name, cls.__name__) hello = C.say_hello('Rope') Inlining ``say_hello`` will result in: .. code-block:: python class C(object): pass hello = 'Saying hello to %s from %s' % ('Rope', C.__name__) Inlining Parameters ------------------- ``rope.refactor.inline.create_inline()`` creates an ``InlineParameter`` object when performed on a parameter. It passes the default value of the parameter wherever its function is called without passing it. For instance in: .. code-block:: python def f(p1=1, p2=1): pass f(3) f() f(3, 4) after inlining p2 parameter will have: .. code-block:: python def f(p1=1, p2=2): pass f(3, 2) f(p2=2) f(3, 4) Use Function Refactoring ------------------------ It tries to find the places in which a function can be used and changes the code to call it instead. For instance if mod1 is: .. code-block:: python def square(p): return p ** 2 my_var = 3 ** 2 and mod2 is: .. code-block:: python another_var = 4 ** 2 if we perform "use function" on square function, mod1 will be: .. code-block:: python def square(p): return p ** 2 my_var = square(3) and mod2 will be: .. code-block:: python import mod1 another_var = mod1.square(4) Automatic Default Insertion In Change Signature ----------------------------------------------- The ``rope.refactor.change_signature.ArgumentReorderer`` signature changer takes a parameter called ``autodef``. If not ``None``, its value is used whenever rope needs to insert a default for a parameter (that happens when an argument without default is moved after another that has a default value). For instance in: .. code-block:: python def f(p1, p2=2): pass if we reorder using: .. code-block:: python changers = [ArgumentReorderer([1, 0], autodef='1')] will result in: .. code-block:: python def f(p2=2, p1=1): pass Sorting Imports --------------- Organize imports sorts imports, too. It does that according to :PEP:`8`:: [__future__ imports] [standard imports] [third-party imports] [project imports] [the rest of module] Handling Long Imports --------------------- ``Handle long imports`` command tries to make long imports look better by transforming ``import pkg1.pkg2.pkg3.pkg4.mod1`` to ``from pkg1.pkg2.pkg3.pkg4 import mod1``. Long imports can be identified either by having lots of dots or being very long. The default configuration considers imported modules with more than 2 dots or with more than 27 characters to be long. Stoppable Refactorings ---------------------- Some refactorings might take a long time to finish (based on the size of your project). The ``get_changes()`` method of these refactorings take a parameter called ``task_handle``. If you want to monitor or stop these refactoring you can pass a ``rope.refactor.taskhandle.TaskHandle`` to this method. See ``rope.refactor.taskhandle`` module for more information. Basic Implicit Interfaces ------------------------- Implicit interfaces are the interfaces that you don't explicitly define; But you expect a group of classes to have some common attributes. These interfaces are very common in dynamic languages; Since we only have implementation inheritance and not interface inheritance. For instance: .. code-block:: python class A(object): def count(self): pass class B(object): def count(self): pass def count_for(arg): return arg.count() count_for(A()) count_for(B()) Here we know that there is an implicit interface defined by the function ``count_for`` that provides ``count()``. Here when we rename ``A.count()`` we expect ``B.count()`` to be renamed, too. Currently rope supports a basic form of implicit interfaces. When you try to rename an attribute of a parameter, rope renames that attribute for all objects that have been passed to that function in different call sites. That is renaming the occurrence of ``count`` in ``count_for`` function to ``newcount`` will result in: .. code-block:: python class A(object): def newcount(self): pass class B(object): def newcount(self): pass def count_for(arg): return arg.newcount() count_for(A()) count_for(B()) This also works for change method signature. Note that this feature relies on rope's object analysis mechanisms to find out the parameters that are passed to a function. Restructurings -------------- ``rope.refactor.restructure`` can be used for performing restructurings. A restructuring is a program transformation; not as well defined as other refactorings like rename. In this section, we'll see some examples. After this example you might like to have a look at: * ``rope.refactor.restructure`` for more examples and features not described here like adding imports to changed modules. * ``rope.refactor.wildcards`` for an overview of the arguments the default wildcard supports. Finally, restructurings can be improved in many ways (for instance adding new wildcards). You might like to discuss your ideas in the `Github Discussion`_. .. _`Github Discussion`: https://github.com/python-rope/rope/discussions Example 1 ''''''''' In its basic form we have a pattern and a goal. Consider we were not aware of the ``**`` operator and wrote our own: .. code-block:: python def pow(x, y): result = 1 for i in range(y): result *= x return result print(pow(2, 3)) Now that we know ``**`` exists we want to use it wherever ``pow`` is used (there might be hundreds of them!). We can use a pattern like:: pattern: pow(${param1}, ${param2}) Goal can be something like:: goal: ${param1} ** ${param2} Note that ``${...}`` can be used to match expressions. By default every expression at that point will match. You can use the matched names in goal and they will be replaced with the string that was matched in each occurrence. So the outcome of our restructuring will be: .. code-block:: python def pow(x, y): result = 1 for i in range(y): result *= x return result print(2 ** 3) It seems to be working but what if ``pow`` is imported in some module or we have some other function defined in some other module that uses the same name and we don't want to change it. Wildcard arguments come to rescue. Wildcard arguments is a mapping; Its keys are wildcard names that appear in the pattern (the names inside ``${...}``). The values are the parameters that are passed to wildcard matchers. The arguments a wildcard takes is based on its type. For checking the type of a wildcard, we can pass ``type=value`` as an argument; ``value`` should be resolved to a python variable (or reference). For instance for specifying ``pow`` in this example we can use ``mod.pow``. As you see, this string should start from module name. For referencing python builtin types and functions you can use ``__builtin__`` module (for instance ``__builtin__.int``). For solving the mentioned problem, we change our ``pattern``. But ``goal`` remains the same:: pattern: ${pow_func}(${param1}, ${param2}) goal: ${param1} ** ${param2} Consider the name of the module containing our ``pow`` function is ``mod``. ``args`` can be:: pow_func: name=mod.pow If we need to pass more arguments to a wildcard matcher we can use ``,`` to separate them. Such as ``name: type=mod.MyClass,exact``. This restructuring handles aliases like in: .. code-block:: python mypow = pow result = mypow(2, 3) Transforms into: .. code-block:: python mypow = pow result = 2 ** 3 If we want to ignore aliases we can pass ``exact`` as another wildcard argument:: pattern: ${pow}(${param1}, ${param2}) goal: ${param1} ** ${param2} args: pow: name=mod.pow, exact ``${name}``, by default, matches every expression at that point; if ``exact`` argument is passed to a wildcard only the specified name will match (for instance, if ``exact`` is specified , ``${name}`` matches ``name`` and ``x.name`` but not ``var`` nor ``(1 + 2)`` while a normal ``${name}`` can match all of them). For performing this refactoring using rope library see :ref:`library:Restructuring`. Example 2 ''''''''' As another example consider: .. code-block:: python class A(object): def f(self, p1, p2): print(p1) print(p2) a = A() a.f(1, 2) Later we decide that ``A.f()`` is doing too much and we want to divide it to ``A.f1()`` and ``A.f2()``: .. code-block:: python class A(object): def f(self, p1, p2): print(p1) print(p2) def f1(self, p): print(p) def f2(self, p): print(p) a = A() a.f(1, 2) But who's going to fix all those nasty occurrences (actually this situation can be handled using inline method refactoring but this is just an example; consider inline refactoring is not implemented yet!). Restructurings come to rescue:: pattern: ${inst}.f(${p1}, ${p2}) goal: ${inst}.f1(${p1}) ${inst}.f2(${p2}) args: inst: type=mod.A After performing we will have: .. code-block:: python class A(object): def f(self, p1, p2): print(p1) print(p2) def f1(self, p): print(p) def f2(self, p): print(p) a = A() a.f1(1) a.f2(2) Example 3 ''''''''' If you like to replace every occurrences of ``x.set(y)`` with ``x = y`` when x is an instance of ``mod.A`` in: .. code-block:: python from mod import A a = A() b = A() a.set(b) We can perform a restructuring with these information:: pattern: ${x}.set(${y}) goal: ${x} = ${y} args: x: type=mod.A After performing the above restructuring we'll have: .. code-block:: python from mod import A a = A() b = A() a = b Note that ``mod.py`` contains something like: .. code-block:: python class A(object): def set(self, arg): pass Issues '''''' Pattern names can appear only at the start of an expression. For instance ``var.${name}`` is invalid. These situations can usually be fixed by specifying good checks, for example on the type of `var` and using a ``${var}.name``. Object Inference ================ This section is a bit out of date. Static object inference can do more than described here (see unittests). Hope to update this someday! Static Object Inference ----------------------- .. code-block:: python class AClass(object): def __init__(self): self.an_attr = 1 def call_a_func(self): return a_func() def a_func(): return AClass() a_var = a_func() #a_var.${codeassist} another_var = a_var #another_var.${codeassist} #another_var.call_a_func().${codeassist} Basic support for builtin types: .. code-block:: python a_list = [AClass(), AClass()] for x in a_list: pass #x.${codeassist} #a_list.pop().${codeassist} a_dict = ['text': AClass()] for key, value in a_dict.items(): pass #key.${codeassist} #value.${codeassist} Enhanced static returned object inference: .. code-block:: python class C(object): def c_func(self): return [''] def a_func(arg): return arg.c_func() a_var = a_func(C()) Here rope knows that the type of a_var is a ``list`` that holds ``str``\s. Supporting generator functions: .. code-block:: python class C(object): pass def a_generator(): yield C() for c in a_generator(): a_var = c Here the objects ``a_var`` and ``c`` hold are known. Rope collects different types of data during SOA, like per name data for builtin container types: .. code-block:: python l1 = [C()] var1 = l1.pop() l2 = [] l2.append(C()) var2 = l2.pop() Here rope can easily infer the type of ``var1``. But for knowing the type of ``var2``, it needs to analyze the items inserted into ``l2`` which might happen in other modules. Rope can do that by running SOA on that module. You might be wondering is there any reason for using DOA instead of SOA. The answer is that DOA might be more accurate and handles complex and dynamic situations. For example in: .. code-block:: python def f(arg): return eval(arg) a_var = f('C') SOA can no way conclude the object ``a_var`` holds but it is really trivial for DOA. What's more SOA only analyzes calls in one module while DOA analyzes any call that happens when running a module. That is, for achieving the same result as DOA you might need to run SOA on more than one module and more than once (not considering dynamic situations.) One advantage of SOA is that it is much faster than DOA. Dynamic Object Analysis ----------------------- ``PyCore.run_module()`` runs a module and collects object information if ``perform_doa`` project config is set. Since as the program runs rope gathers type information, the program runs much slower. After the program is run, you can get better code assists and some of the refactorings perform much better. ``mod1.py``: .. code-block:: python def f1(param): pass #param.${codeassist} #f2(param).${codeassist} def f2(param): #param.${codeassist} return param Using code assist in specified places does not give any information and there is actually no information about the return type of ``f2`` or ``param`` parameter of ``f1``. ``mod2.py``: .. code-block:: python import mod1 class A(object): def a_method(self): pass a_var = A() mod1.f1(a_var) Retry those code assists after performing DOA on ``mod2`` module. Builtin Container Types ''''''''''''''''''''''' Builtin types can be handled in a limited way, too: .. code-block:: python class A(object): def a_method(self): pass def f1(): result = [] result.append(A()) return result returned = f() #returned[0].${codeassist} Test the the proposed completions after running this module. Guessing Function Returned Value Based On Parameters ---------------------------------------------------- ``mod1.py``: .. code-block:: python class C1(object): def c1_func(self): pass class C2(object): def c2_func(self): pass def func(arg): if isinstance(arg, C1): return C2() else: return C1() func(C1()) func(C2()) After running ``mod1`` either SOA or DOA on this module you can test: ``mod2.py``: .. code-block:: python import mod1 arg = mod1.C1() a_var = mod1.func(arg) a_var.${codeassist} mod1.func(mod1.C2()).${codeassist} Automatic SOA ------------- When turned on, it analyzes the changed scopes of a file when saving for obtaining object information; So this might make saving files a bit more time consuming. By default, this feature is turned on, but you can turn it off by editing your project ``config.py`` file, though that is not recommended. Validating Object DB -------------------- Since files on disk change over time project objectdb might hold invalid information. Currently there is a basic incremental objectdb validation that can be used to remove or fix out of date information. Rope uses this feature by default but you can disable it by editing ``config.py``. Type Hinting ------------ Currently supported type hinting for: - function parameter type, using function doctring (:type or @type) - function return type, using function doctring (:rtype or @rtype) - class attribute type, using class docstring (:type or @type). Attribute should by set to None or NotImplemented in class. - any assignment, using type comments of PEP 0484 (in limited form). If rope cannot detect the type of a function argument correctly (due to the dynamic nature of Python), you can help it by hinting the type using one of the following docstring syntax styles. **Sphinx style** http://sphinx-doc.org/domains.html#info-field-lists :: def myfunction(node, foo): """Do something with a ``node``. :type node: ProgramNode :param str foo: foo parameter description """ node.| # complete here **Epydoc** http://epydoc.sourceforge.net/manual-fields.html :: def myfunction(node): """Do something with a ``node``. @type node: ProgramNode """ node.| # complete here **Numpydoc** https://github.com/numpy/numpy/blob/master/doc/HOWTO_DOCUMENT.rst.txt In order to support the numpydoc format, you need to install the `numpydoc `__ package. :: def foo(var1, var2, long_var_name='hi'): r"""A one-line summary that does not use variable names or the function name. ... Parameters ---------- var1 : array_like Array_like means all those objects -- lists, nested lists, etc. -- that can be converted to an array. We can also refer to variables like `var1`. var2 : int The type above can either refer to an actual Python type (e.g. ``int``), or describe the type of the variable in more detail, e.g. ``(N,) ndarray`` or ``array_like``. long_variable_name : {'hi', 'ho'}, optional Choices in brackets, default first when optional. ... """ var2.| # complete here **PEP 0484** https://www.python.org/dev/peps/pep-0484/#type-comments :: class Sample(object): def __init__(self): self.x = None # type: random.Random self.x.| # complete here Supported syntax of type hinting '''''''''''''''''''''''''''''''' Currently rope supports the following syntax of type-hinting. Parametrized objects: - Foo - foo.bar.Baz - list[Foo] or list[foo.bar.Baz] etc. - set[Foo] - tuple[Foo] - dict[Foo, Bar] - collections.Iterable[Foo] - collections.Iterator[Foo] Nested expressions also allowed: - collections.Iterable[list[Foo]] TODO: Callable objects: - (Foo, Bar) -> Baz Multiple interfaces implementation: - Foo | Bar 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 using ``source_folders`` project config. Note that rope guesses project source folders correctly most of the time. You can also extend python path using ``python_path`` config. Version Control Systems Support =============================== When performing refactorings some files might need to be moved (when renaming a module) or new files might be created. When using a VCS, rope detects and uses it to perform file system actions. Currently Mercurial_, GIT_, Darcs_ and SVN (using pysvn_ library) are supported. They are selected based on dot files in project root directory. For instance, Mercurial will be used if `mercurial` module is available and there is a ``.hg`` folder in project root. Rope assumes either all files are under version control in a project or there is no version control at all. Also don't forget to commit your changes yourself, rope doesn't do that. Adding support for other VCSs is easy; have a look at :ref:`library:Writing A \`FileSystemCommands\``. .. _pysvn: http://pysvn.tigris.org .. _Mercurial: http://selenic.com/mercurial .. _GIT: http://git.or.cz .. _darcs: http://darcs.net ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1705552762.0 rope-1.12.0/docs/release-process.rst0000664000175000017500000000174714552125572017271 0ustar00lieryanlieryanRelease Process =============== Pre-Release ----------- 1. Update :ref:`gha-cache-key.txt `: ``pip-compile --extra dev --generate-hashes -o gha-cache-key.txt --resolver=backtracking`` Release ------- 1. Ensure tickets assigned to Milestones are up to date 2. Update ``CHANGELOG.md`` 3. Close milestone 4. Increment version number in ``pyproject.toml`` 5. `git commit && git push` 6. Tag the release with the tag annotation containing the release information, ``python bin/tag-release.py`` 7. ``python3 -m build`` 8. ``twine upload dist/rope-$VERSION.{tar.gz,whl}`` 9. Publish to Discussions Announcement 10. Create Github Release Release Schedule ================ Rope has a release schedule once a month, usually sometime close to the 15th of each month. However, this schedule is not a guaranteed date, if there is a particularly urgent change or if there's not enough pull requests for the month, there may be additional releases or the release window may be skipped. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/docs/rope.rst0000664000175000017500000000266314512700666015137 0ustar00lieryanlieryanFeatures ======== Features implemented so far: * Refactorings * Rename everything! * Extract method/local variable * Move class/function/module/package/method * Inline method/local variable/parameter * Restructuring (like converting ``${a}.f(${b})`` to ``${b}.g(${a})`` where ``a: type=mymod.A``) * Introduce factory * Change method signature * Transform module to package * Encapsulate field * Replace method with method object * And a few others * Refactoring Features * Extracting similar statements in extract refactorings * Fixing imports when needed * Previewing refactorings * Undo/redo refactorings * Stopping refactorings * Cross-project refactorings * Basic implicit interfaces handling in rename and change signature * Mercurial_, GIT_, Darcs_ and SVN (pysvn_ library) support in refactorings * IDE helpers * Auto-completion * Definition location * Get pydoc * Find occurrences * Organize imports (remove unused and duplicate imports and sort them) * Generating python elements * Object Inference * Static and dynamic object analysis * Handling built-in container types * Saving object information on disk and validating them * Type hints using docstring or type comments PEP 0484 For more information see :ref:`overview:Rope Overview`. .. _pysvn: http://pysvn.tigris.org .. _Mercurial: http://selenic.com/mercurial .. _GIT: http://git.or.cz .. _darcs: http://darcs.net ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1705552925.0 rope-1.12.0/pyproject.toml0000664000175000017500000000444214552126035015415 0ustar00lieryanlieryan[project] name = 'rope' description = 'a python refactoring library...' readme = 'README.rst' requires-python = '>=3.8' classifiers = [ 'Development Status :: 4 - Beta', 'Operating System :: OS Independent', 'Environment :: X11 Applications', 'Environment :: Win32 (MS Windows)', 'Environment :: MacOS X', 'Intended Audience :: Developers', 'License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)', 'Natural Language :: English', 'Programming Language :: Python', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', 'Programming Language :: Python :: 3.12', 'Topic :: Software Development', ] version = '1.12.0' dependencies = ['pytoolconfig[global] >= 1.2.2'] [[project.authors]] name = 'Ali Gholami Rudi' email = 'aligrudi@users.sourceforge.net' [[project.maintainers]] name = 'Lie Ryan' email = 'lieryan.24@proton.me' [project.license] text = 'LGPL-3.0-or-later' [project.urls] Source = 'https://github.com/python-rope/rope' Documentation = 'https://rope.readthedocs.io/' [project.optional-dependencies] doc = [ 'pytoolconfig[doc]', "sphinx>=4.5.0", "sphinx-autodoc-typehints>=1.18.1", "sphinx-rtd-theme>=1.0.0", ] dev = [ 'pytest>=7.0.1', 'pytest-timeout>=2.1.0', 'build>=0.7.0', 'pre-commit>=2.20.0', ] release = [ 'toml>=0.10.2', 'twine>=4.0.2', 'pip-tools>=6.12.1', ] [tool.setuptools] packages = [ 'rope', 'rope.base', 'rope.base.oi', 'rope.base.oi.type_hinting', 'rope.base.oi.type_hinting.providers', 'rope.base.oi.type_hinting.resolvers', 'rope.base.utils', 'rope.contrib', 'rope.contrib.autoimport', 'rope.refactor', 'rope.refactor.importutils', ] [tool.black] target-version = [ 'py36', 'py37', 'py38', 'py39', ] include = 'rope/.*\.pyi?$' force-exclude = 'ropetest|rope/base/prefs.py' [tool.isort] profile = "black" [tool.pytest.ini_options] python_files = [ "*test.py", "__init__.py", ] markers = [ "time_limit: sets a maximum amount of time the test can run", ] [build-system] requires = [ 'setuptools', ] build-backend = 'setuptools.build_meta' ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1705553002.6699822 rope-1.12.0/rope/0000775000175000017500000000000014552126153013443 5ustar00lieryanlieryan././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1699196919.0 rope-1.12.0/rope/__init__.py0000664000175000017500000000260514521727767015575 0ustar00lieryanlieryan"""rope, a python refactoring library""" import importlib.metadata try: VERSION = importlib.metadata.version("rope") except importlib.metadata.PackageNotFoundError: def get_fallback_version(): import pathlib import re pyproject = ( pathlib.Path(__file__).resolve().parent.parent / "pyproject.toml" ).read_text() version = re.search("version.*=.*'(.*)'", pyproject) return version.group(1) if version else None VERSION = get_fallback_version() INFO = __doc__ COPYRIGHT = """\ Copyright (C) 2021-2023 Lie Ryan 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 Copyright (C) 2009-2012 Anton Gritsay This program is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program. If not, see .""" ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1705553002.6779826 rope-1.12.0/rope/base/0000775000175000017500000000000014552126153014355 5ustar00lieryanlieryan././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/rope/base/__init__.py0000664000175000017500000000024114512700666016466 0ustar00lieryanlieryan"""Base rope package This package contains rope core modules that are used by other modules and packages. """ __all__ = ["project", "libutils", "exceptions"] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/rope/base/arguments.py0000664000175000017500000000632314512700666016743 0ustar00lieryanlieryanimport rope.base.evaluate from rope.base import ast class Arguments: """A class for evaluating parameters passed to a function You can use the `create_arguments` factory. It handles implicit first arguments. """ def __init__(self, args, scope): self.args = args self.scope = scope self.instance = None def get_arguments(self, parameters): result = [] for pyname in self.get_pynames(parameters): if pyname is None: result.append(None) else: result.append(pyname.get_object()) return result def get_pynames(self, parameters): result = [None] * max(len(parameters), len(self.args)) for index, arg in enumerate(self.args): if isinstance(arg, ast.keyword) and arg.arg in parameters: result[parameters.index(arg.arg)] = self._evaluate(arg.value) else: result[index] = self._evaluate(arg) return result def get_instance_pyname(self): if self.args: return self._evaluate(self.args[0]) def _evaluate(self, ast_node): return rope.base.evaluate.eval_node(self.scope, ast_node) def create_arguments(primary, pyfunction, call_node, scope): """A factory for creating `Arguments`""" args = list(call_node.args) args.extend(call_node.keywords) called = call_node.func # XXX: Handle constructors if _is_method_call(primary, pyfunction) and isinstance(called, ast.Attribute): args.insert(0, called.value) return Arguments(args, scope) class ObjectArguments: def __init__(self, pynames): self.pynames = pynames def get_arguments(self, parameters): result = [] for pyname in self.pynames: if pyname is None: result.append(None) else: result.append(pyname.get_object()) return result def get_pynames(self, parameters): return self.pynames def get_instance_pyname(self): return self.pynames[0] class MixedArguments: def __init__(self, pyname, arguments, scope): """`arguments` is an instance of `Arguments`""" self.pyname = pyname self.args = arguments def get_pynames(self, parameters): return [self.pyname] + self.args.get_pynames(parameters[1:]) def get_arguments(self, parameters): result = [] for pyname in self.get_pynames(parameters): if pyname is None: result.append(None) else: result.append(pyname.get_object()) return result def get_instance_pyname(self): return self.pyname def _is_method_call(primary, pyfunction): if primary is None: return False pyobject = primary.get_object() if ( isinstance(pyobject.get_type(), rope.base.pyobjects.PyClass) and isinstance(pyfunction, rope.base.pyobjects.PyFunction) and isinstance(pyfunction.parent, rope.base.pyobjects.PyClass) ): return True if isinstance( pyobject.get_type(), rope.base.pyobjects.AbstractClass ) and isinstance(pyfunction, rope.base.builtins.BuiltinFunction): return True return False ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/rope/base/ast.py0000664000175000017500000000473114512700666015526 0ustar00lieryanlieryanimport ast import sys from ast import * # noqa: F401,F403 from rope.base import fscommands try: # Suppress the mypy complaint: Module "ast" has no attribute "_const_node_type_names" from ast import _const_node_type_names # type:ignore except ImportError: # backported from stdlib `ast` assert sys.version_info < (3, 8) _const_node_type_names = { bool: "NameConstant", # should be before int type(None): "NameConstant", int: "Num", float: "Num", complex: "Num", str: "Str", bytes: "Bytes", type(...): "Ellipsis", } def parse(source, filename="", *args, **kwargs): # type: ignore if isinstance(source, str): source = fscommands.unicode_to_file_data(source) if b"\r" in source: source = source.replace(b"\r\n", b"\n").replace(b"\r", b"\n") if not source.endswith(b"\n"): source += b"\n" try: return ast.parse(source, filename=filename, *args, **kwargs) except (TypeError, ValueError) as e: error = SyntaxError() error.lineno = 1 error.filename = filename error.msg = str(e) raise error def call_for_nodes(node, callback): """ Pre-order depth-first traversal of AST nodes, calling `callback(node)` for each node visited. When each node is visited, `callback(node)` will be called with the visited `node`, then its children node will be visited. If `callback(node)` returns `True` for a node, then the descendants of that node will not be visited. See _ResultChecker._find_node for an example. """ result = callback(node) if not result: for child in ast.iter_child_nodes(node): call_for_nodes(child, callback) class RopeNodeVisitor(ast.NodeVisitor): def visit(self, node): """Modified from ast.NodeVisitor to match rope's existing Visitor implementation""" method = "_" + node.__class__.__name__ visitor = getattr(self, method, self.generic_visit) return visitor(node) def get_const_subtype_name(node): """Get pre-3.8 ast node name""" # fmt: off assert sys.version_info >= (3, 8), "This should only be called in Python 3.8 and above" # fmt: on assert isinstance(node, ast.Constant) return _const_node_type_names[type(node.value)] def get_node_type_name(node): return ( get_const_subtype_name(node) if isinstance(node, ast.Constant) else node.__class__.__name__ ) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/rope/base/builtins.py0000664000175000017500000006322714512700666016575 0ustar00lieryanlieryan"""This module tries to support builtin types and functions.""" import inspect import io import rope.base.evaluate from rope.base import arguments, ast, pynames, pyobjects, utils class BuiltinModule(pyobjects.AbstractModule): def __init__(self, name, pycore=None, initial={}): super().__init__() self.name = name self.pycore = pycore self.initial = initial parent = None def get_attributes(self): return self.attributes def get_doc(self): if self.module: return self.module.__doc__ def get_name(self): return self.name.split(".")[-1] @property @utils.saveit def attributes(self): result = _object_attributes(self.module, self) result.update(self.initial) if self.pycore is not None: submodules = self.pycore._builtin_submodules(self.name) for name, module in submodules.items(): result[name] = rope.base.builtins.BuiltinName(module) return result @property @utils.saveit def module(self): try: result = __import__(self.name) for token in self.name.split(".")[1:]: result = getattr(result, token, None) return result except ImportError: return class _BuiltinElement: def __init__(self, builtin, parent=None): self.builtin = builtin self._parent = parent def get_doc(self): if self.builtin: return getattr(self.builtin, "__doc__", None) def get_name(self): if self.builtin: return getattr(self.builtin, "__name__", None) @property def parent(self): if self._parent is None: return builtins return self._parent class BuiltinClass(_BuiltinElement, pyobjects.AbstractClass): def __init__(self, builtin, attributes, parent=None): _BuiltinElement.__init__(self, builtin, parent) pyobjects.AbstractClass.__init__(self) self.initial = attributes @utils.saveit def get_attributes(self): result = _object_attributes(self.builtin, self) result.update(self.initial) return result def get_module(self): return builtins class BuiltinFunction(_BuiltinElement, pyobjects.AbstractFunction): def __init__( self, returned=None, function=None, builtin=None, argnames=[], parent=None ): _BuiltinElement.__init__(self, builtin, parent) pyobjects.AbstractFunction.__init__(self) self.argnames = argnames self.returned = returned self.function = function def get_returned_object(self, args): if self.function is not None: return self.function(_CallContext(self.argnames, args)) else: return self.returned def get_param_names(self, special_args=True): return self.argnames class BuiltinUnknown(_BuiltinElement, pyobjects.PyObject): def __init__(self, builtin): super().__init__(pyobjects.get_unknown()) self.builtin = builtin self.type = pyobjects.get_unknown() def get_name(self): return getattr(type(self.builtin), "__name__", None) @utils.saveit def get_attributes(self): return _object_attributes(self.builtin, self) def _object_attributes(obj, parent): attributes = {} for name in dir(obj): if name == "None": continue try: child = getattr(obj, name) except AttributeError: # descriptors are allowed to raise AttributeError # even if they are in dir() continue pyobject = None if inspect.isclass(child): pyobject = BuiltinClass(child, {}, parent=parent) elif inspect.isroutine(child): pyobject = BuiltinFunction(builtin=child, parent=parent) else: pyobject = BuiltinUnknown(builtin=child) attributes[name] = BuiltinName(pyobject) return attributes def _create_builtin_type_getter(cls): def _get_builtin(*args): if not hasattr(cls, "_generated"): cls._generated = {} if args not in cls._generated: cls._generated[args] = cls(*args) return cls._generated[args] return _get_builtin def _create_builtin_getter(cls): type_getter = _create_builtin_type_getter(cls) def _get_builtin(*args): return pyobjects.PyObject(type_getter(*args)) return _get_builtin class _CallContext: def __init__(self, argnames, args): self.argnames = argnames self.args = args def _get_scope_and_pyname(self, pyname): if pyname is not None and isinstance(pyname, pynames.AssignedName): pymodule, lineno = pyname.get_definition_location() if pymodule is None: return None, None if lineno is None: lineno = 1 scope = pymodule.get_scope().get_inner_scope_for_line(lineno) name = None while name is None and scope is not None: for current in scope.get_names(): if scope[current] is pyname: name = current break else: scope = scope.parent return scope, name return None, None def get_argument(self, name): if self.args: args = self.args.get_arguments(self.argnames) return args[self.argnames.index(name)] def get_pyname(self, name): if self.args: args = self.args.get_pynames(self.argnames) if name in self.argnames: return args[self.argnames.index(name)] def get_arguments(self, argnames): if self.args: return self.args.get_arguments(argnames) def get_pynames(self, argnames): if self.args: return self.args.get_pynames(argnames) def get_per_name(self): if self.args is None: return None pyname = self.args.get_instance_pyname() scope, name = self._get_scope_and_pyname(pyname) if name is not None: pymodule = pyname.get_definition_location()[0] return pymodule.pycore.object_info.get_per_name(scope, name) return None def save_per_name(self, value): if self.args is None: return None pyname = self.args.get_instance_pyname() scope, name = self._get_scope_and_pyname(pyname) if name is not None: pymodule = pyname.get_definition_location()[0] pymodule.pycore.object_info.save_per_name(scope, name, value) class _AttributeCollector: def __init__(self, type): self.attributes = {} self.type = type def __call__( self, name, returned=None, function=None, argnames=["self"], check_existence=True, parent=None, ): try: builtin = getattr(self.type, name) except AttributeError: if check_existence: raise builtin = None self.attributes[name] = BuiltinName( BuiltinFunction( returned=returned, function=function, argnames=argnames, builtin=builtin, parent=parent, ) ) def __setitem__(self, name, value): self.attributes[name] = value class List(BuiltinClass): def __init__(self, holding=None): self.holding = holding collector = _AttributeCollector(list) collector("__iter__", function=self._iterator_get, parent=self) collector("__new__", function=self._new_list, parent=self) # Adding methods collector( "append", function=self._list_add, argnames=["self", "value"], parent=self ) collector( "__setitem__", function=self._list_add, argnames=["self", "index", "value"], parent=self, ) collector( "insert", function=self._list_add, argnames=["self", "index", "value"], parent=self, ) collector( "extend", function=self._self_set, argnames=["self", "iterable"], parent=self, ) # Getting methods collector("__getitem__", function=self._list_get, parent=self) collector("pop", function=self._list_get, parent=self) try: collector("__getslice__", function=self._list_get) except AttributeError: pass super().__init__(list, collector.attributes) def _new_list(self, args): return _create_builtin(args, get_list) def _list_add(self, context): if self.holding is not None: return holding = context.get_argument("value") if holding is not None and holding != pyobjects.get_unknown(): context.save_per_name(holding) def _self_set(self, context): if self.holding is not None: return iterable = context.get_pyname("iterable") holding = _infer_sequence_for_pyname(iterable) if holding is not None and holding != pyobjects.get_unknown(): context.save_per_name(holding) def _list_get(self, context): if self.holding is not None: args = context.get_arguments(["self", "key"]) if ( len(args) > 1 and args[1] is not None and args[1].get_type() == builtins["slice"].get_object() ): return get_list(self.holding) return self.holding return context.get_per_name() def _iterator_get(self, context): return get_iterator(self._list_get(context)) def _self_get(self, context): return get_list(self._list_get(context)) get_list = _create_builtin_getter(List) get_list_type = _create_builtin_type_getter(List) class Dict(BuiltinClass): def __init__(self, keys=None, values=None): self.keys = keys self.values = values collector = _AttributeCollector(dict) collector("__new__", function=self._new_dict, parent=self) collector("__setitem__", function=self._dict_add, parent=self) collector("popitem", function=self._item_get, parent=self) collector("pop", function=self._value_get, parent=self) collector("get", function=self._key_get, parent=self) collector("keys", function=self._key_list, parent=self) collector("values", function=self._value_list, parent=self) collector("items", function=self._item_list, parent=self) collector("copy", function=self._self_get, parent=self) collector("__getitem__", function=self._value_get, parent=self) collector("__iter__", function=self._key_iter, parent=self) collector("update", function=self._self_set, parent=self) super().__init__(dict, collector.attributes) def _new_dict(self, args): def do_create(holding=None): if holding is None: return get_dict() type = holding.get_type() if isinstance(type, Tuple) and len(type.get_holding_objects()) == 2: return get_dict(*type.get_holding_objects()) return _create_builtin(args, do_create) def _dict_add(self, context): if self.keys is not None: return key, value = context.get_arguments(["self", "key", "value"])[1:] if key is not None and key != pyobjects.get_unknown(): context.save_per_name(get_tuple(key, value)) def _item_get(self, context): if self.keys is not None: return get_tuple(self.keys, self.values) item = context.get_per_name() if item is None or not isinstance(item.get_type(), Tuple): return get_tuple(self.keys, self.values) return item def _value_get(self, context): item = self._item_get(context).get_type() return item.get_holding_objects()[1] def _key_get(self, context): item = self._item_get(context).get_type() return item.get_holding_objects()[0] def _value_list(self, context): return get_list(self._value_get(context)) def _key_list(self, context): return get_list(self._key_get(context)) def _item_list(self, context): return get_list(self._item_get(context)) def _value_iter(self, context): return get_iterator(self._value_get(context)) def _key_iter(self, context): return get_iterator(self._key_get(context)) def _item_iter(self, context): return get_iterator(self._item_get(context)) def _self_get(self, context): item = self._item_get(context).get_type() key, value = item.get_holding_objects()[:2] return get_dict(key, value) def _self_set(self, context): if self.keys is not None: return new_dict = context.get_pynames(["self", "d"])[1] if new_dict and isinstance(new_dict.get_object().get_type(), Dict): args = arguments.ObjectArguments([new_dict]) items = ( new_dict.get_object()["popitem"].get_object().get_returned_object(args) ) context.save_per_name(items) else: holding = _infer_sequence_for_pyname(new_dict) if holding is not None and isinstance(holding.get_type(), Tuple): context.save_per_name(holding) get_dict = _create_builtin_getter(Dict) get_dict_type = _create_builtin_type_getter(Dict) class Tuple(BuiltinClass): def __init__(self, *objects): self.objects = objects first = None if objects: first = objects[0] attributes = { "__getitem__": BuiltinName( BuiltinFunction(first) ), # TODO: add slice support "__getslice__": BuiltinName(BuiltinFunction(pyobjects.PyObject(self))), "__new__": BuiltinName(BuiltinFunction(function=self._new_tuple)), "__iter__": BuiltinName(BuiltinFunction(get_iterator(first))), } super().__init__(tuple, attributes) def get_holding_objects(self): return self.objects def _new_tuple(self, args): return _create_builtin(args, get_tuple) get_tuple = _create_builtin_getter(Tuple) get_tuple_type = _create_builtin_type_getter(Tuple) class Set(BuiltinClass): def __init__(self, holding=None): self.holding = holding collector = _AttributeCollector(set) collector("__new__", function=self._new_set) self_methods = [ "copy", "difference", "intersection", "symmetric_difference", "union", ] for method in self_methods: collector(method, function=self._self_get, parent=self) collector("add", function=self._set_add, parent=self) collector("update", function=self._self_set, parent=self) collector("update", function=self._self_set, parent=self) collector("symmetric_difference_update", function=self._self_set, parent=self) collector("difference_update", function=self._self_set, parent=self) collector("pop", function=self._set_get, parent=self) collector("__iter__", function=self._iterator_get, parent=self) super().__init__(set, collector.attributes) def _new_set(self, args): return _create_builtin(args, get_set) def _set_add(self, context): if self.holding is not None: return holding = context.get_arguments(["self", "value"])[1] if holding is not None and holding != pyobjects.get_unknown(): context.save_per_name(holding) def _self_set(self, context): if self.holding is not None: return iterable = context.get_pyname("iterable") holding = _infer_sequence_for_pyname(iterable) if holding is not None and holding != pyobjects.get_unknown(): context.save_per_name(holding) def _set_get(self, context): if self.holding is not None: return self.holding return context.get_per_name() def _iterator_get(self, context): return get_iterator(self._set_get(context)) def _self_get(self, context): return get_list(self._set_get(context)) get_set = _create_builtin_getter(Set) get_set_type = _create_builtin_type_getter(Set) class Str(BuiltinClass): def __init__(self): self_object = pyobjects.PyObject(self) collector = _AttributeCollector(str) collector("__iter__", get_iterator(self_object), check_existence=False) self_methods = [ "__getitem__", "capitalize", "center", "encode", "expandtabs", "join", "ljust", "lower", "lstrip", "replace", "rjust", "rstrip", "strip", "swapcase", "title", "translate", "upper", "zfill", ] for method in self_methods: collector(method, self_object, parent=self) py2_self_methods = ["__getslice__", "decode"] for method in py2_self_methods: try: collector(method, self_object) except AttributeError: pass for method in ["rsplit", "split", "splitlines"]: collector(method, get_list(self_object), parent=self) super().__init__(str, collector.attributes) def get_doc(self): return str.__doc__ get_str = _create_builtin_getter(Str) get_str_type = _create_builtin_type_getter(Str) class BuiltinName(pynames.PyName): def __init__(self, pyobject): self.pyobject = pyobject def get_object(self): return self.pyobject def get_definition_location(self): return (None, None) class Iterator(pyobjects.AbstractClass): def __init__(self, holding=None): super().__init__() self.holding = holding self.attributes = { "next": BuiltinName(BuiltinFunction(self.holding)), "__iter__": BuiltinName(BuiltinFunction(self)), } def get_attributes(self): return self.attributes def get_returned_object(self, args): return self.holding get_iterator = _create_builtin_getter(Iterator) class Generator(pyobjects.AbstractClass): def __init__(self, holding=None): super().__init__() self.holding = holding self.attributes = { "next": BuiltinName(BuiltinFunction(self.holding)), "__iter__": BuiltinName(BuiltinFunction(get_iterator(self.holding))), "close": BuiltinName(BuiltinFunction()), "send": BuiltinName(BuiltinFunction()), "throw": BuiltinName(BuiltinFunction()), } def get_attributes(self): return self.attributes def get_returned_object(self, args): return self.holding get_generator = _create_builtin_getter(Generator) class File(BuiltinClass): def __init__(self, filename=None, mode="r", *args): self.filename = filename self.mode = mode self.args = args str_object = get_str() str_list = get_list(get_str()) attributes = {} def add(name, returned=None, function=None): builtin = getattr(io.TextIOBase, name, None) attributes[name] = BuiltinName( BuiltinFunction(returned=returned, function=function, builtin=builtin) ) add("__iter__", get_iterator(str_object)) add("__enter__", returned=pyobjects.PyObject(self)) for method in ["next", "read", "readline", "readlines"]: add(method, str_list) for method in [ "close", "flush", "lineno", "isatty", "seek", "tell", "truncate", "write", "writelines", ]: add(method) super().__init__(open, attributes) get_file = _create_builtin_getter(File) get_file_type = _create_builtin_type_getter(File) class Property(BuiltinClass): def __init__(self, fget=None, fset=None, fdel=None, fdoc=None): self._fget = fget self._fdoc = fdoc attributes = { "fget": BuiltinName(BuiltinFunction()), "fset": BuiltinName(pynames.UnboundName()), "fdel": BuiltinName(pynames.UnboundName()), "__new__": BuiltinName(BuiltinFunction(function=_property_function)), } super().__init__(property, attributes) def get_property_object(self, args): if isinstance(self._fget, pyobjects.AbstractFunction): return self._fget.get_returned_object(args) def _property_function(args): parameters = args.get_arguments(["fget", "fset", "fdel", "fdoc"]) return pyobjects.PyObject(Property(parameters[0])) class Lambda(pyobjects.AbstractFunction): def __init__(self, node, scope): super().__init__() self.node = node self.arguments = node.args self.scope = scope def get_returned_object(self, args): result = rope.base.evaluate.eval_node(self.scope, self.node.body) if result is not None: return result.get_object() else: return pyobjects.get_unknown() def get_module(self): return self.parent.get_module() def get_scope(self): return self.scope def get_kind(self): return "lambda" def get_ast(self): return self.node def get_attributes(self): return {} def get_name(self): return "lambda" def get_param_names(self, special_args=True): result = [node.arg for node in self.arguments.args if isinstance(node, ast.arg)] if self.arguments.vararg: result.append("*" + self.arguments.vararg.arg) if self.arguments.kwarg: result.append("**" + self.arguments.kwarg.arg) return result @property def parent(self): return self.scope.pyobject class BuiltinObject(BuiltinClass): def __init__(self): super().__init__(object, {}) class BuiltinType(BuiltinClass): def __init__(self): super().__init__(type, {}) def _infer_sequence_for_pyname(pyname): if pyname is None: return None seq = pyname.get_object() args = arguments.ObjectArguments([pyname]) if "__iter__" in seq: obj = seq["__iter__"].get_object() if not isinstance(obj, pyobjects.AbstractFunction): return None iter = obj.get_returned_object(args) if iter is not None and "next" in iter: holding = iter["next"].get_object().get_returned_object(args) return holding def _create_builtin(args, creator): passed = args.get_pynames(["sequence"])[0] if passed is None: holding = None else: holding = _infer_sequence_for_pyname(passed) if holding is not None: return creator(holding) else: return creator() def _open_function(args): return _create_builtin(args, get_file) def _range_function(args): return get_list() def _reversed_function(args): return _create_builtin(args, get_iterator) def _sorted_function(args): return _create_builtin(args, get_list) def _super_function(args): passed_class, passed_self = args.get_arguments(["type", "self"]) if passed_self is None: return passed_class else: # pyclass = passed_self.get_type() pyclass = passed_class if isinstance(pyclass, pyobjects.AbstractClass): supers = pyclass.get_superclasses() if supers: return pyobjects.PyObject(supers[0]) return passed_self def _zip_function(args): args = args.get_pynames(["sequence"]) objects = [] for seq in args: if seq is None: holding = None else: holding = _infer_sequence_for_pyname(seq) objects.append(holding) tuple = get_tuple(*objects) return get_list(tuple) def _enumerate_function(args): passed = args.get_pynames(["sequence"])[0] if passed is None: holding = None else: holding = _infer_sequence_for_pyname(passed) tuple = get_tuple(None, holding) return get_iterator(tuple) def _iter_function(args): passed = args.get_pynames(["sequence"])[0] if passed is None: holding = None else: holding = _infer_sequence_for_pyname(passed) return get_iterator(holding) def _input_function(args): return get_str() _initial_builtins = { "list": BuiltinName(get_list_type()), "dict": BuiltinName(get_dict_type()), "tuple": BuiltinName(get_tuple_type()), "set": BuiltinName(get_set_type()), "str": BuiltinName(get_str_type()), "open": BuiltinName(BuiltinFunction(function=_open_function, builtin=open)), "range": BuiltinName(BuiltinFunction(function=_range_function, builtin=range)), "reversed": BuiltinName( BuiltinFunction(function=_reversed_function, builtin=reversed) ), "sorted": BuiltinName(BuiltinFunction(function=_sorted_function, builtin=sorted)), "super": BuiltinName(BuiltinFunction(function=_super_function, builtin=super)), "property": BuiltinName( BuiltinFunction(function=_property_function, builtin=property) ), "zip": BuiltinName(BuiltinFunction(function=_zip_function, builtin=zip)), "enumerate": BuiltinName( BuiltinFunction(function=_enumerate_function, builtin=enumerate) ), "object": BuiltinName(BuiltinObject()), "type": BuiltinName(BuiltinType()), "iter": BuiltinName(BuiltinFunction(function=_iter_function, builtin=iter)), "input": BuiltinName(BuiltinFunction(function=_input_function, builtin=input)), } builtins = BuiltinModule("builtins", initial=_initial_builtins) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/rope/base/change.py0000664000175000017500000003255314512700666016167 0ustar00lieryanlieryanimport datetime import difflib import os import time from typing import Union import rope.base.fscommands from rope.base import exceptions, taskhandle, utils from rope.base.fscommands import FileContent class Change: """The base class for changes Rope refactorings return `Change` objects. They can be previewed, committed or undone. """ def do(self, job_set=None): """Perform the change .. note:: Do use this directly. Use `Project.do()` instead. """ def undo(self, job_set=None): """Perform the change .. note:: Do use this directly. Use `History.undo()` instead. """ def get_description(self): """Return the description of this change This can be used for previewing the changes. """ return str(self) def get_changed_resources(self): """Return the list of resources that will be changed""" return [] @property @utils.saveit def _operations(self): return _ResourceOperations(self.resource.project) class ChangeSet(Change): """A collection of `Change` objects This class holds a collection of changes. This class provides these fields: * `changes`: the list of changes * `description`: the goal of these changes """ def __init__(self, description, timestamp=None): self.changes = [] self.description = description self.time = timestamp def do(self, job_set=taskhandle.DEFAULT_JOB_SET): try: done = [] for change in self.changes: change.do(job_set) done.append(change) self.time = time.time() except Exception: for change in done: change.undo() raise def undo(self, job_set=taskhandle.DEFAULT_JOB_SET): try: done = [] for change in reversed(self.changes): change.undo(job_set) done.append(change) except Exception: for change in done: change.do() raise def add_change(self, change): self.changes.append(change) def get_description(self): result = [str(self) + ":\n\n\n"] for change in self.changes: result.append(change.get_description()) result.append("\n") return "".join(result) def __str__(self): if self.time is not None: date = datetime.datetime.fromtimestamp(self.time) if date.date() == datetime.date.today(): string_date = "today" elif date.date() == (datetime.date.today() - datetime.timedelta(1)): string_date = "yesterday" elif date.year == datetime.date.today().year: string_date = date.strftime("%b %d") else: string_date = date.strftime("%d %b, %Y") string_time = date.strftime("%H:%M:%S") string_time = f"{string_date} {string_time} " return self.description + " - " + string_time return self.description def get_changed_resources(self): result = set() for change in self.changes: result.update(change.get_changed_resources()) return result def _handle_job_set(function): """A decorator for handling `taskhandle.JobSet` A decorator for handling `taskhandle.JobSet` for `do` and `undo` methods of `Change`. """ def call(self, job_set=taskhandle.DEFAULT_JOB_SET): job_set.started_job(str(self)) function(self) job_set.finished_job() return call class ChangeContents(Change): """A class to change the contents of a file Fields: * `resource`: The `rope.base.resources.File` to change * `new_contents`: What to write in the file """ def __init__(self, resource, new_contents, old_contents=None): self.resource = resource # IDEA: Only saving diffs; possible problems when undo/redoing self.new_contents = new_contents self.old_contents = old_contents @_handle_job_set def do(self): if self.old_contents is None: self.old_contents = self.resource.read() self._operations.write_file(self.resource, self.new_contents) @_handle_job_set def undo(self): if self.old_contents is None: raise exceptions.HistoryError("Undoing a change that is not performed yet!") self._operations.write_file(self.resource, self.old_contents) def __str__(self): return "Change <%s>" % self.resource.path def get_description(self): new = self.new_contents old = self.old_contents if old is None: if self.resource.exists(): old = self.resource.read() else: old = "" result = difflib.unified_diff( old.splitlines(True), new.splitlines(True), "a/" + self.resource.path, "b/" + self.resource.path, ) return "".join(list(result)) def get_changed_resources(self): return [self.resource] class MoveResource(Change): """Move a resource to a new location Fields: * `resource`: The `rope.base.resources.Resource` to move * `new_resource`: The destination for move; It is the moved resource not the folder containing that resource. """ def __init__(self, resource, new_location, exact=False): self.project = resource.project self.resource = resource if not exact: new_location = _get_destination_for_move(resource, new_location) if resource.is_folder(): self.new_resource = self.project.get_folder(new_location) else: self.new_resource = self.project.get_file(new_location) @_handle_job_set def do(self): self._operations.move(self.resource, self.new_resource) @_handle_job_set def undo(self): self._operations.move(self.new_resource, self.resource) def __str__(self): return "Move <%s>" % self.resource.path def get_description(self): return "rename from {}\nrename to {}".format( self.resource.path, self.new_resource.path, ) def get_changed_resources(self): return [self.resource, self.new_resource] class CreateResource(Change): """A class to create a resource Fields: * `resource`: The resource to create """ def __init__(self, resource): self.resource = resource @_handle_job_set def do(self): self._operations.create(self.resource) @_handle_job_set def undo(self): self._operations.remove(self.resource) def __str__(self): return "Create Resource <%s>" % (self.resource.path) def get_description(self): return "new file %s" % (self.resource.path) def get_changed_resources(self): return [self.resource] def _get_child_path(self, parent, name): if parent.path == "": return name else: return parent.path + "/" + name class CreateFolder(CreateResource): """A class to create a folder See docs for `CreateResource`. """ def __init__(self, parent, name): resource = parent.project.get_folder(self._get_child_path(parent, name)) super().__init__(resource) class CreateFile(CreateResource): """A class to create a file See docs for `CreateResource`. """ def __init__(self, parent, name): resource = parent.project.get_file(self._get_child_path(parent, name)) super().__init__(resource) class RemoveResource(Change): """A class to remove a resource Fields: * `resource`: The resource to be removed """ def __init__(self, resource): self.resource = resource @_handle_job_set def do(self): self._operations.remove(self.resource) # TODO: Undoing remove operations @_handle_job_set def undo(self): raise NotImplementedError("Undoing `RemoveResource` is not implemented yet.") def __str__(self): return "Remove <%s>" % (self.resource.path) def get_changed_resources(self): return [self.resource] def count_changes(change): """Counts the number of basic changes a `Change` will make""" if isinstance(change, ChangeSet): result = 0 for child in change.changes: result += count_changes(child) return result return 1 def create_job_set(task_handle, change): return task_handle.create_jobset(str(change), count_changes(change)) class _ResourceOperations: def __init__(self, project): self.project = project self.fscommands = project.fscommands self.direct_commands = rope.base.fscommands.FileSystemCommands() def _get_fscommands(self, resource): if self.project.is_ignored(resource): return self.direct_commands return self.fscommands def write_file(self, resource, contents: Union[str, FileContent]): data: FileContent if not isinstance(contents, bytes): data = rope.base.fscommands.unicode_to_file_data( contents, newlines=resource.newlines, ) else: data = contents fscommands = self._get_fscommands(resource) fscommands.write(resource.real_path, data) for observer in list(self.project.observers): observer.resource_changed(resource) def move(self, resource, new_resource): fscommands = self._get_fscommands(resource) fscommands.move(resource.real_path, new_resource.real_path) for observer in list(self.project.observers): observer.resource_moved(resource, new_resource) def create(self, resource): if resource.is_folder(): self._create_resource(resource.path, kind="folder") else: self._create_resource(resource.path) for observer in list(self.project.observers): observer.resource_created(resource) def remove(self, resource): fscommands = self._get_fscommands(resource) fscommands.remove(resource.real_path) for observer in list(self.project.observers): observer.resource_removed(resource) def _create_resource(self, file_name, kind="file"): resource_path = self.project._get_resource_path(file_name) if os.path.exists(resource_path): raise exceptions.RopeError("Resource <%s> already exists" % resource_path) resource = self.project.get_file(file_name) if not resource.parent.exists(): raise exceptions.ResourceNotFoundError( "Parent folder of <%s> does not exist" % resource.path ) fscommands = self._get_fscommands(resource) try: if kind == "file": fscommands.create_file(resource_path) else: fscommands.create_folder(resource_path) except OSError as e: raise exceptions.RopeError(e) def _get_destination_for_move(resource, destination): dest_path = resource.project._get_resource_path(destination) if os.path.isdir(dest_path): if destination != "": return destination + "/" + resource.name else: return resource.name return destination class ChangeToData: def convertChangeSet(self, change): description = change.description changes = [self(child) for child in change.changes] return (description, changes, change.time) def convertChangeContents(self, change): return (change.resource.path, change.new_contents, change.old_contents) def convertMoveResource(self, change): return (change.resource.path, change.new_resource.path) def convertCreateResource(self, change): return (change.resource.path, change.resource.is_folder()) def convertRemoveResource(self, change): return (change.resource.path, change.resource.is_folder()) def __call__(self, change): change_type = type(change) if change_type in (CreateFolder, CreateFile): change_type = CreateResource method = getattr(self, "convert" + change_type.__name__) return (change_type.__name__, method(change)) class DataToChange: def __init__(self, project): self.project = project def makeChangeSet(self, description, changes, time=None): result = ChangeSet(description, time) for child in changes: result.add_change(self(child)) return result def makeChangeContents(self, path, new_contents, old_contents): resource = self.project.get_file(path) return ChangeContents(resource, new_contents, old_contents) def makeMoveResource(self, old_path, new_path): resource = self.project.get_file(old_path) return MoveResource(resource, new_path, exact=True) def makeCreateResource(self, path, is_folder): if is_folder: resource = self.project.get_folder(path) else: resource = self.project.get_file(path) return CreateResource(resource) def makeRemoveResource(self, path, is_folder): if is_folder: resource = self.project.get_folder(path) else: resource = self.project.get_file(path) return RemoveResource(resource) def __call__(self, data): method = getattr(self, "make" + data[0]) return method(*data[1]) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/rope/base/codeanalyze.py0000664000175000017500000002677614512700666017252 0ustar00lieryanlieryanimport bisect import re import token import tokenize class ChangeCollector: def __init__(self, text): self.text = text self.changes = [] def add_change(self, start, end, new_text=None): if new_text is None: new_text = self.text[start:end] self.changes.append((start, end, new_text)) def get_changed(self): if not self.changes: return None self.changes.sort(key=lambda x: x[:2]) pieces = [] last_changed = 0 for change in self.changes: start, end, text = change pieces.append(self.text[last_changed:start] + text) last_changed = end if last_changed < len(self.text): pieces.append(self.text[last_changed:]) result = "".join(pieces) if result != self.text: return result class SourceLinesAdapter: """Adapts source to Lines interface Note: The creation of this class is expensive. """ def __init__(self, source_code): self.code = source_code self.starts = None self._initialize_line_starts() def _initialize_line_starts(self): self.starts = [] self.starts.append(0) try: i = 0 while True: i = self.code.index("\n", i) + 1 self.starts.append(i) except ValueError: pass self.starts.append(len(self.code) + 1) def get_line(self, lineno): return self.code[self.starts[lineno - 1] : self.starts[lineno] - 1] def length(self): return len(self.starts) - 1 def get_line_number(self, offset): return bisect.bisect(self.starts, offset) def get_line_start(self, lineno): return self.starts[lineno - 1] def get_line_end(self, lineno): return self.starts[lineno] - 1 class ArrayLinesAdapter: def __init__(self, lines): self.lines = lines def get_line(self, line_number): return self.lines[line_number - 1] def length(self): return len(self.lines) class LinesToReadline: def __init__(self, lines, start): self.lines = lines self.current = start def readline(self): if self.current <= self.lines.length(): self.current += 1 return self.lines.get_line(self.current - 1) + "\n" return "" def __call__(self): return self.readline() class _CustomGenerator: def __init__(self, lines): self.lines = lines self.in_string = "" self.open_count = 0 self.continuation = False def __call__(self): size = self.lines.length() result = [] i = 1 while i <= size: while i <= size and not self.lines.get_line(i).strip(): i += 1 if i <= size: start = i while True: line = self.lines.get_line(i) self._analyze_line(line) if ( not (self.continuation or self.open_count or self.in_string) or i == size ): break i += 1 result.append((start, i)) i += 1 return result # Matches all backslashes before the token, to detect escaped quotes _main_tokens = re.compile(r'(\\*)((\'\'\'|"""|\'|")|#|\[|\]|\{|\}|\(|\))') def _analyze_line(self, line): token = None for match in self._main_tokens.finditer(line): prefix = match.group(1) token = match.group(2) # Skip any tokens which are escaped if len(prefix) % 2 == 1: continue if token in ["'''", '"""', "'", '"']: if not self.in_string: 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 if token == "#": break if token in "([{": self.open_count += 1 elif token in ")]}": self.open_count -= 1 if line and token != "#" and line.endswith("\\"): self.continuation = True else: self.continuation = False def custom_generator(lines): return _CustomGenerator(lines)() class LogicalLineFinder: def __init__(self, lines): self.lines = lines def logical_line_in(self, line_number): indents = count_line_indents(self.lines.get_line(line_number)) tries = 0 while True: block_start = get_block_start(self.lines, line_number, indents) try: return self._block_logical_line(block_start, line_number) except IndentationError as e: tries += 1 if tries == 5: raise e lineno = e.lineno + block_start - 1 indents = count_line_indents(self.lines.get_line(lineno)) def generate_starts(self, start_line=1, end_line=None): for start, end in self.generate_regions(start_line, end_line): yield start def generate_regions(self, start_line=1, end_line=None): # XXX: `block_start` should be at a better position! block_start = 1 readline = LinesToReadline(self.lines, block_start) try: for start, end in self._logical_lines(readline): real_start = start + block_start - 1 real_start = self._first_non_blank(real_start) if end_line is not None and real_start >= end_line: break real_end = end + block_start - 1 if real_start >= start_line: yield (real_start, real_end) except tokenize.TokenError: pass def _block_logical_line(self, block_start, line_number): readline = LinesToReadline(self.lines, block_start) shifted = line_number - block_start + 1 region = self._calculate_logical(readline, shifted) start = self._first_non_blank(region[0] + block_start - 1) if region[1] is None: end = self.lines.length() else: end = region[1] + block_start - 1 return start, end def _calculate_logical(self, readline, line_number): last_end = 1 try: for start, end in self._logical_lines(readline): if line_number <= end: return (start, end) last_end = end + 1 except tokenize.TokenError as e: current = e.args[1][0] return (last_end, max(last_end, current - 1)) return (last_end, None) def _logical_lines(self, readline): last_end = 1 for current_token in tokenize.generate_tokens(readline): current = current_token[2][0] if current_token[0] == token.NEWLINE: yield (last_end, current) last_end = current + 1 def _first_non_blank(self, line_number): current = line_number while current < self.lines.length(): line = self.lines.get_line(current).strip() if line and not line.startswith("#"): return current current += 1 return current def tokenizer_generator(lines): return LogicalLineFinder(lines).generate_regions() class CachingLogicalLineFinder: def __init__(self, lines, generate=custom_generator): self.lines = lines self._generate = generate _starts = None @property def starts(self): if self._starts is None: self._init_logicals() return self._starts _ends = None @property def ends(self): if self._ends is None: self._init_logicals() return self._ends def _init_logicals(self): """Should initialize _starts and _ends attributes""" size = self.lines.length() + 1 self._starts = [None] * size self._ends = [None] * size for start, end in self._generate(self.lines): self._starts[start] = True self._ends[end] = True def logical_line_in(self, line_number): start = line_number while start > 0 and not self.starts[start]: start -= 1 if start == 0: try: start = self.starts.index(True, line_number) except ValueError: return (line_number, line_number) return (start, self.ends.index(True, start)) def generate_starts(self, start_line=1, end_line=None): if end_line is None: end_line = self.lines.length() for index in range(start_line, end_line): if self.starts[index]: yield index def get_block_start(lines, lineno, maximum_indents=80): """Approximate block start""" pattern = get_block_start_patterns() for i in range(lineno, 0, -1): match = pattern.search(lines.get_line(i)) if ( match is not None and count_line_indents(lines.get_line(i)) <= maximum_indents ): striped = match.string.lstrip() # Maybe we're in a list comprehension or generator expression if i > 1 and striped.startswith("if") or striped.startswith("for"): bracs = 0 for j in range(i, min(i + 5, lines.length() + 1)): for c in lines.get_line(j): if c == "#": break if c in "[(": bracs += 1 if c in ")]": bracs -= 1 if bracs < 0: break if bracs < 0: break if bracs < 0: continue return i return 1 _block_start_pattern = None def get_block_start_patterns(): global _block_start_pattern if not _block_start_pattern: pattern = ( "^\\s*(((def|class|if|elif|except|for|while|with)\\s)|" "((try|else|finally|except)\\s*:))" ) _block_start_pattern = re.compile(pattern, re.M) return _block_start_pattern def count_line_indents(line): indents = 0 for char in line: if char == " ": indents += 1 elif char == "\t": indents += 8 else: return indents return 0 def get_string_pattern_with_prefix(prefix, prefix_group_name=None): longstr = r'"""(\\.|"(?!"")|\\\n|[^"\\])*"""' shortstr = r'"(\\.|\\\n|[^"\\\n])*"' if prefix_group_name is not None: pattern = "(?P<%s>%%s)(%%s)" % prefix_group_name else: pattern = "%s(%s)" return pattern % ( prefix, "|".join( [ longstr, longstr.replace('"', "'"), shortstr, shortstr.replace('"', "'"), ] ), ) def get_string_pattern(): prefix = r"(? Tuple[Optional[rope.base.pynames.PyName], Optional[rope.base.pynames.PyName]]: lineno = self.lines.get_line_number(offset) holding_scope = self.module_scope.get_inner_scope_for_offset(offset) # function keyword parameter if self.worder.is_function_keyword_parameter(offset): keyword_name = self.worder.get_word_at(offset) pyobject = self.get_enclosing_function(offset) if isinstance(pyobject, pyobjectsdef.PyFunction): parameter_name = pyobject.get_parameters().get(keyword_name, None) return (None, parameter_name) elif isinstance(pyobject, pyobjects.AbstractFunction): parameter_name = rope.base.pynames.ParameterName() return (None, parameter_name) # class body if self._is_defined_in_class_body(holding_scope, offset, lineno): class_scope = holding_scope if lineno == holding_scope.get_start(): class_scope = holding_scope.parent name = self.worder.get_primary_at(offset).strip() try: return (None, class_scope.pyobject[name]) except rope.base.exceptions.AttributeNotFoundError: return (None, None) # function header if self._is_function_name_in_function_header(holding_scope, offset, lineno): name = self.worder.get_primary_at(offset).strip() return (None, holding_scope.parent[name]) # module in a from statement or an imported name that is aliased if self.worder.is_from_statement_module( offset ) or self.worder.is_import_statement_aliased_module(offset): module = self.worder.get_primary_at(offset) module_pyname = self._find_module(module) return (None, module_pyname) if self.worder.is_from_aliased(offset): name = self.worder.get_from_aliased(offset) else: name = self.worder.get_primary_at(offset) return eval_str2(holding_scope, name) def get_enclosing_function(self, offset): function_parens = self.worder.find_parens_start_from_inside(offset) try: function_pyname = self.get_pyname_at(function_parens - 1) except BadIdentifierError: function_pyname = None if function_pyname is not None: pyobject = function_pyname.get_object() if isinstance(pyobject, pyobjects.AbstractFunction): return pyobject elif ( isinstance(pyobject, pyobjects.AbstractClass) and "__init__" in pyobject ): return pyobject["__init__"].get_object() elif "__call__" in pyobject: return pyobject["__call__"].get_object() return None def _find_module(self, module_name): dots = 0 while module_name[dots] == ".": dots += 1 return rope.base.pynames.ImportedModule( self.module_scope.pyobject, module_name[dots:], dots ) class StatementEvaluator(ast.RopeNodeVisitor): def __init__(self, scope): self.scope = scope self.result = None self.old_result = None def _Name(self, node): self.result = self.scope.lookup(node.id) def _Attribute(self, node): pyname = eval_node(self.scope, node.value) if pyname is None: pyname = rope.base.pynames.UnboundName() self.old_result = pyname if pyname.get_object() != rope.base.pyobjects.get_unknown(): try: self.result = pyname.get_object()[node.attr] except exceptions.AttributeNotFoundError: self.result = None def _Call(self, node): primary, pyobject = self._get_primary_and_object_for_node(node.func) if pyobject is None: return def _get_returned(pyobject): args = arguments.create_arguments(primary, pyobject, node, self.scope) return pyobject.get_returned_object(args) if isinstance(pyobject, rope.base.pyobjects.AbstractClass): result = None if "__new__" in pyobject: new_function = pyobject["__new__"].get_object() result = _get_returned(new_function) if result is None or result == rope.base.pyobjects.get_unknown(): result = rope.base.pyobjects.PyObject(pyobject) self.result = rope.base.pynames.UnboundName(pyobject=result) return pyfunction = None if isinstance(pyobject, rope.base.pyobjects.AbstractFunction): pyfunction = pyobject elif "__call__" in pyobject: pyfunction = pyobject["__call__"].get_object() if pyfunction is not None: self.result = rope.base.pynames.UnboundName( pyobject=_get_returned(pyfunction) ) def _Str(self, node): self.result = rope.base.pynames.UnboundName( pyobject=rope.base.builtins.get_str() ) def _Num(self, node): type_name = type(node.n).__name__ self.result = self._get_builtin_name(type_name) def _Constant(self, node): type_name = type(node.value).__name__ try: self.result = self._get_builtin_name(type_name) except exceptions.AttributeNotFoundError: # XXX: Right way to fix this is to add missing NoneType to builtins? pass def _get_builtin_name(self, type_name): pytype = rope.base.builtins.builtins[type_name].get_object() return rope.base.pynames.UnboundName(rope.base.pyobjects.PyObject(pytype)) def _BinOp(self, node): self.result = rope.base.pynames.UnboundName( self._get_object_for_node(node.left) ) def _BoolOp(self, node): pyobject = self._get_object_for_node(node.values[0]) if pyobject is None: pyobject = self._get_object_for_node(node.values[1]) self.result = rope.base.pynames.UnboundName(pyobject) def _Repr(self, node): self.result = self._get_builtin_name("str") def _UnaryOp(self, node): self.result = rope.base.pynames.UnboundName( self._get_object_for_node(node.operand) ) def _Compare(self, node): self.result = self._get_builtin_name("bool") def _Dict(self, node): keys = None values = None 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) ) def _List(self, node): holding = None if node.elts: holding = self._get_object_for_node(node.elts[0]) self.result = rope.base.pynames.UnboundName( pyobject=rope.base.builtins.get_list(holding) ) def _ListComp(self, node): pyobject = self._what_does_comprehension_hold(node) self.result = rope.base.pynames.UnboundName( pyobject=rope.base.builtins.get_list(pyobject) ) def _GeneratorExp(self, node): pyobject = self._what_does_comprehension_hold(node) self.result = rope.base.pynames.UnboundName( pyobject=rope.base.builtins.get_iterator(pyobject) ) def _what_does_comprehension_hold(self, node): scope = self._make_comprehension_scope(node) pyname = eval_node(scope, node.elt) return pyname.get_object() if pyname is not None else None def _make_comprehension_scope(self, node): scope = self.scope module = scope.pyobject.get_module() names = {} for comp in node.generators: new_names = _get_evaluated_names( comp.target, comp.iter, module, ".__iter__().next()", node.lineno ) names.update(new_names) return rope.base.pyscopes.TemporaryScope(scope.pycore, scope, names) def _Tuple(self, node): objects = [] if len(node.elts) < 4: for stmt in node.elts: pyobject = self._get_object_for_node(stmt) objects.append(pyobject) else: objects.append(self._get_object_for_node(node.elts[0])) self.result = rope.base.pynames.UnboundName( pyobject=rope.base.builtins.get_tuple(*objects) ) def _get_object_for_node(self, stmt): pyname = eval_node(self.scope, stmt) pyobject = None if pyname is not None: pyobject = pyname.get_object() return pyobject def _get_primary_and_object_for_node(self, stmt): primary, pyname = eval_node2(self.scope, stmt) pyobject = None if pyname is not None: pyobject = pyname.get_object() return primary, pyobject def _Subscript(self, node): if isinstance(node.slice, ast.Index): self._call_function(node.value, "__getitem__", [node.slice.value]) 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") def _call_function(self, node, function_name, other_args=None): pyname = eval_node(self.scope, node) if pyname is not None: pyobject = pyname.get_object() else: return if function_name in pyobject: called = pyobject[function_name].get_object() if not called or not isinstance(called, pyobjects.AbstractFunction): return args = [node] if other_args: args += other_args arguments_ = arguments.Arguments(args, self.scope) self.result = rope.base.pynames.UnboundName( pyobject=called.get_returned_object(arguments_) ) def _Lambda(self, node): self.result = rope.base.pynames.UnboundName( pyobject=rope.base.builtins.Lambda(node, self.scope) ) def _get_evaluated_names(targets, assigned, module, evaluation, lineno): result = {} for name, levels in nameanalyze.get_name_levels(targets): assignment = rope.base.pynames.AssignmentValue(assigned, levels, evaluation) # XXX: this module should not access `rope.base.pynamesdef`! pyname = rope.base.pynamesdef.AssignedName(lineno, module) pyname.assignments.append(assignment) result[name] = pyname return result ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/rope/base/exceptions.py0000664000175000017500000000253214512700666017115 0ustar00lieryanlieryanclass RopeError(Exception): """Base exception for rope""" class ResourceNotFoundError(RopeError): """Resource not found exception""" class RefactoringError(RopeError): """Errors for performing a refactoring""" class InterruptedTaskError(RopeError): """The task has been interrupted""" class HistoryError(RopeError): """Errors for history undo/redo operations""" class ModuleNotFoundError(RopeError): """Module not found exception""" class AttributeNotFoundError(RopeError): """Attribute not found exception""" class NameNotFoundError(RopeError): """Name not found exception""" class BadIdentifierError(RopeError): """The name cannot be resolved""" class ModuleSyntaxError(RopeError): """Module has syntax errors The `filename` and `lineno` fields indicate where the error has occurred. """ def __init__(self, filename, lineno, message): self.filename = filename self.lineno = lineno self.message_ = message super().__init__( f"Syntax error in file <{filename}> line <{lineno}>: {message}" ) class ModuleDecodeError(RopeError): """Cannot decode module""" def __init__(self, filename, message): self.filename = filename self.message_ = message super().__init__(f"Cannot decode file <{filename}>: {message}") ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/rope/base/fscommands.py0000664000175000017500000002044114512700666017065 0ustar00lieryanlieryan"""Project file system commands. This modules implements file system operations used by rope. Different version control systems can be supported by implementing the interface provided by `FileSystemCommands` class. See `SubversionCommands` and `MercurialCommands` for example. """ import os import re import shutil import subprocess import typing FileContent = typing.NewType("FileContent", bytes) def create_fscommands(root): dirlist = os.listdir(root) commands = { ".hg": MercurialCommands, ".svn": SubversionCommands, ".git": GITCommands, "_svn": SubversionCommands, "_darcs": DarcsCommands, } for key in commands: if key in dirlist: try: return commands[key](root) except (ImportError, OSError): pass return FileSystemCommands() class FileSystemCommands: def create_file(self, path): open(path, "w").close() def create_folder(self, path): os.mkdir(path) def move(self, path, new_location): shutil.move(path, new_location) def remove(self, path): if os.path.isfile(path): os.remove(path) else: shutil.rmtree(path) def write(self, path, data): file_ = open(path, "wb") try: file_.write(data) finally: file_.close() def read(self, path): with open(path, "rb") as handle: return handle.read() class SubversionCommands: def __init__(self, *args): self.normal_actions = FileSystemCommands() import pysvn # type:ignore self.client = pysvn.Client() def create_file(self, path): self.normal_actions.create_file(path) self.client.add(path, force=True) def create_folder(self, path): self.normal_actions.create_folder(path) self.client.add(path, force=True) def move(self, path, new_location): self.client.move(path, new_location, force=True) def remove(self, path): self.client.remove(path, force=True) def write(self, path, data): self.normal_actions.write(path, data) def read(self, path): return self.normal_actions.read(path) class MercurialCommands: def __init__(self, root): self.hg = self._import_mercurial() self.normal_actions = FileSystemCommands() try: self.ui = self.hg.ui.ui( verbose=False, debug=False, quiet=True, interactive=False, traceback=False, report_untrusted=False, ) except Exception: self.ui = self.hg.ui.ui() self.ui.setconfig("ui", "interactive", "no") self.ui.setconfig("ui", "debug", "no") self.ui.setconfig("ui", "traceback", "no") self.ui.setconfig("ui", "verbose", "no") self.ui.setconfig("ui", "report_untrusted", "no") self.ui.setconfig("ui", "quiet", "yes") self.repo = self.hg.hg.repository(self.ui, root) def _import_mercurial(self): import mercurial.commands # type:ignore import mercurial.hg # type:ignore import mercurial.ui # type:ignore return mercurial def create_file(self, path): self.normal_actions.create_file(path) self.hg.commands.add(self.ui, self.repo, path) def create_folder(self, path): self.normal_actions.create_folder(path) def move(self, path, new_location): self.hg.commands.rename(self.ui, self.repo, path, new_location, after=False) def remove(self, path): self.hg.commands.remove(self.ui, self.repo, path) def write(self, path, data): self.normal_actions.write(path, data) def read(self, path): return self.normal_actions.read(path) class GITCommands: def __init__(self, root): self.root = root self._do(["version"]) self.normal_actions = FileSystemCommands() def create_file(self, path): self.normal_actions.create_file(path) self._do(["add", self._in_dir(path)]) def create_folder(self, path): self.normal_actions.create_folder(path) def move(self, path, new_location): self._do(["mv", self._in_dir(path), self._in_dir(new_location)]) def remove(self, path): self._do(["rm", self._in_dir(path)]) def write(self, path, data): # XXX: should we use ``git add``? self.normal_actions.write(path, data) def read(self, path): return self.normal_actions.read(path) def _do(self, args): _execute(["git"] + args, cwd=self.root) def _in_dir(self, path): if path.startswith(self.root): return path[len(self.root) + 1 :] return self.root class DarcsCommands: def __init__(self, root): self.root = root self.normal_actions = FileSystemCommands() def create_file(self, path): self.normal_actions.create_file(path) self._do(["add", path]) def create_folder(self, path): self.normal_actions.create_folder(path) self._do(["add", path]) def move(self, path, new_location): self._do(["mv", path, new_location]) def remove(self, path): self.normal_actions.remove(path) def read(self, path): return self.normal_actions.read(path) def write(self, path, data): self.normal_actions.write(path, data) def _do(self, args): _execute(["darcs"] + args, cwd=self.root) def _execute(args, cwd=None): process = subprocess.Popen(args, cwd=cwd, stdout=subprocess.PIPE) process.wait() return process.returncode def unicode_to_file_data(contents: str, encoding=None, newlines=None) -> FileContent: assert isinstance(contents, str) if newlines and newlines != "\n": contents = contents.replace("\n", newlines) if encoding is None: encoding = read_str_coding(contents) if encoding is not None: return FileContent(contents.encode(encoding)) try: return FileContent(contents.encode()) except UnicodeEncodeError: return FileContent(contents.encode("utf-8")) def file_data_to_unicode(data, encoding=None): result = _decode_data(data, encoding) newline = "\n" if "\r\n" in result: result = result.replace("\r\n", "\n") newline = "\r\n" if "\r" in result: result = result.replace("\r", "\n") newline = "\r" return result, newline def _decode_data(data, encoding): if isinstance(data, str): return data if encoding is None: encoding = read_str_coding(data) if encoding is None: # there is no encoding tip, we need to guess. # PEP263 says that "encoding not explicitly defined" means it is ascii, # but we will use utf8 instead since utf8 fully covers ascii and btw is # the only non-latin sane encoding. encoding = "utf-8" try: return data.decode(encoding) except (UnicodeError, LookupError): # fallback to latin1: it should never fail return data.decode("latin1") def read_str_coding(source): # as defined by PEP-263 (https://www.python.org/dev/peps/pep-0263/) CODING_LINE_PATTERN = b"^[ \t\f]*#.*?coding[:=][ \t]*([-_.a-zA-Z0-9]+)" if type(source) == bytes: newline = b"\n" CODING_LINE_PATTERN = re.compile(CODING_LINE_PATTERN) else: newline = "\n" CODING_LINE_PATTERN = re.compile(CODING_LINE_PATTERN.decode("ascii")) for line in source.split(newline, 2)[:2]: if re.match(CODING_LINE_PATTERN, line): return _find_coding(line) else: return def _find_coding(text): if isinstance(text, str): text = text.encode("utf-8") coding = b"coding" to_chr = chr try: start = text.index(coding) + len(coding) if text[start] not in b"=:": return start += 1 while start < len(text) and to_chr(text[start]).isspace(): start += 1 end = start while end < len(text): c = text[end] if not to_chr(c).isalnum() and c not in b"-_": break end += 1 result = text[start:end] if isinstance(result, bytes): result = result.decode("utf-8") return result except ValueError: pass ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/rope/base/history.py0000664000175000017500000002022614512700666016435 0ustar00lieryanlieryanfrom rope.base import change, exceptions, taskhandle, utils class History: """A class that holds project history""" def __init__(self, project, maxundos=None): self.project = project self._undo_list = [] self._redo_list = [] self._maxundos = maxundos self._load_history() self.project.data_files.add_write_hook(self.write) self.current_change = None def _load_history(self): if self.save: result = self.project.data_files.read_data("history") if result is not None: to_change = change.DataToChange(self.project) for data in result[0]: self._undo_list.append(to_change(data)) for data in result[1]: self._redo_list.append(to_change(data)) def do(self, changes, task_handle=taskhandle.DEFAULT_TASK_HANDLE): """Perform the change and add it to the `self.undo_list` Note that uninteresting changes (changes to ignored files) will not be appended to `self.undo_list`. """ try: self.current_change = changes changes.do(change.create_job_set(task_handle, changes)) finally: self.current_change = None if self._is_change_interesting(changes): self.undo_list.append(changes) self._remove_extra_items() del self.redo_list[:] def _remove_extra_items(self): if len(self.undo_list) > self.max_undos: del self.undo_list[0 : len(self.undo_list) - self.max_undos] def _is_change_interesting(self, changes): for resource in changes.get_changed_resources(): if not self.project.is_ignored(resource): return True return False def undo(self, change=None, drop=False, task_handle=taskhandle.DEFAULT_TASK_HANDLE): """Redo done changes from the history When `change` is `None`, the last done change will be undone. If change is not `None` it should be an item from `self.undo_list`; this change and all changes that depend on it will be undone. In both cases the list of undone changes will be returned. If `drop` is `True`, the undone change will not be appended to the redo list. """ if not self._undo_list: raise exceptions.HistoryError("Undo list is empty") if change is None: change = self.undo_list[-1] dependencies = self._find_dependencies(self.undo_list, change) self._move_front(self.undo_list, dependencies) self._perform_undos(len(dependencies), task_handle) result = self.redo_list[-len(dependencies) :] if drop: del self.redo_list[-len(dependencies) :] return result def redo(self, change=None, task_handle=taskhandle.DEFAULT_TASK_HANDLE): """Redo undone changes from the history When `change` is `None`, the last undone change will be redone. If change is not `None` it should be an item from `self.redo_list`; this change and all changes that depend on it will be redone. In both cases the list of redone changes will be returned. """ if not self.redo_list: raise exceptions.HistoryError("Redo list is empty") if change is None: change = self.redo_list[-1] dependencies = self._find_dependencies(self.redo_list, change) self._move_front(self.redo_list, dependencies) self._perform_redos(len(dependencies), task_handle) return self.undo_list[-len(dependencies) :] def _move_front(self, change_list, changes): for change in changes: change_list.remove(change) change_list.append(change) def _find_dependencies(self, change_list, change): index = change_list.index(change) return _FindChangeDependencies(change_list[index:])() def _perform_undos(self, count, task_handle): for i in range(count): self.current_change = self.undo_list[-1] try: job_set = change.create_job_set(task_handle, self.current_change) self.current_change.undo(job_set) finally: self.current_change = None self.redo_list.append(self.undo_list.pop()) def _perform_redos(self, count, task_handle): for i in range(count): self.current_change = self.redo_list[-1] try: job_set = change.create_job_set(task_handle, self.current_change) self.current_change.do(job_set) finally: self.current_change = None self.undo_list.append(self.redo_list.pop()) def contents_before_current_change(self, file): if self.current_change is None: return None result = self._search_for_change_contents([self.current_change], file) if result is not None: return result if file.exists() and not file.is_folder(): return file.read() else: return None def _search_for_change_contents(self, change_list, file): for change_ in reversed(change_list): if isinstance(change_, change.ChangeSet): result = self._search_for_change_contents(change_.changes, file) if result is not None: return result if isinstance(change_, change.ChangeContents) and change_.resource == file: return change_.old_contents def write(self): if self.save: data = [] to_data = change.ChangeToData() self._remove_extra_items() data.append([to_data(change_) for change_ in self.undo_list]) data.append([to_data(change_) for change_ in self.redo_list]) self.project.data_files.write_data("history", data) def get_file_undo_list(self, resource): return [ change for change in self.undo_list if resource in change.get_changed_resources() ] def __str__(self): return "History holds %s changes in memory" % ( len(self.undo_list) + len(self.redo_list) ) undo_list = property(lambda self: self._undo_list) redo_list = property(lambda self: self._redo_list) @property def tobe_undone(self): """The last done change if available, `None` otherwise""" if self.undo_list: return self.undo_list[-1] @property def tobe_redone(self): """The last undone change if available, `None` otherwise""" if self.redo_list: return self.redo_list[-1] @property def max_undos(self): if self._maxundos is None: return self.project.prefs.get("max_history_items", 100) else: return self._maxundos @property def save(self): return self.project.prefs.get("save_history", False) @property @utils.deprecated("compress_history is no longer supported") def compress(self): return False def clear(self): """Forget all undo and redo information""" del self.undo_list[:] del self.redo_list[:] class _FindChangeDependencies: def __init__(self, change_list): self.change = change_list[0] self.change_list = change_list self.changed_resources = set(self.change.get_changed_resources()) def __call__(self): result = [self.change] for change in self.change_list[1:]: if self._depends_on(change, result): result.append(change) self.changed_resources.update(change.get_changed_resources()) return result def _depends_on(self, changes, result): for resource in changes.get_changed_resources(): if resource is None: continue if resource in self.changed_resources: return True for changed in self.changed_resources: if resource.is_folder() and resource.contains(changed): return True if changed.is_folder() and changed.contains(resource): return True return False ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/rope/base/libutils.py0000664000175000017500000000740114512700666016563 0ustar00lieryanlieryan"""A few useful functions for using rope as a library""" import os.path import rope.base.project import rope.base.pycore from rope.base import pyobjectsdef, taskhandle, utils def path_to_resource(project, path, type=None): """Get the resource at path You only need to specify `type` if `path` does not exist. It can be either 'file' or 'folder'. If the type is `None` it is assumed that the resource already exists. Note that this function uses `Project.get_resource()`, `Project.get_file()`, and `Project.get_folder()` methods. """ project_path = path_relative_to_project_root(project, path) if project_path is None: project_path = rope.base.project._realpath(path) project = rope.base.project.get_no_project() if type is None: return project.get_resource(project_path) if type == "file": return project.get_file(project_path) if type == "folder": return project.get_folder(project_path) return None def path_relative_to_project_root(project, path): return relative(project.address, path) @utils.deprecated() def relative(root, path): root = rope.base.project._realpath(root).replace(os.path.sep, "/") path = rope.base.project._realpath(path).replace(os.path.sep, "/") if path == root: return "" if path.startswith(root + "/"): return path[len(root) + 1 :] def report_change(project, path, old_content): """Report that the contents of file at `path` was changed The new contents of file is retrieved by reading the file. """ resource = path_to_resource(project, path) if resource is None: return for observer in list(project.observers): observer.resource_changed(resource) if project.pycore.automatic_soa: rope.base.pycore.perform_soa_on_changed_scopes(project, resource, old_content) def analyze_module(project, resource): """Perform static object analysis on a python file in the project Note that this might be really time consuming. """ project.pycore.analyze_module(resource) def analyze_modules(project, task_handle=taskhandle.DEFAULT_TASK_HANDLE): """Perform static object analysis on all python files in the project Note that this might be really time consuming. """ resources = project.get_python_files() job_set = task_handle.create_jobset("Analyzing Modules", len(resources)) for resource in resources: job_set.started_job(resource.path) analyze_module(project, resource) job_set.finished_job() def get_string_module(project, code, resource=None, force_errors=False): """Returns a `PyObject` object for the given code If `force_errors` is `True`, `exceptions.ModuleSyntaxError` is raised if module has syntax errors. This overrides ``ignore_syntax_errors`` project config. """ return pyobjectsdef.PyModule( project.pycore, code, resource, force_errors=force_errors ) def get_string_scope(project, code, resource=None): """Returns a `Scope` object for the given code""" return get_string_module(project, code, resource).get_scope() def is_python_file(project, resource): return project.pycore.is_python_file(resource) def modname(resource): if resource.is_folder(): module_name = resource.name source_folder = resource.parent elif resource.name == "__init__.py": module_name = resource.parent.name source_folder = resource.parent.parent else: module_name = resource.name[:-3] source_folder = resource.parent while source_folder != source_folder.parent and source_folder.has_child( "__init__.py" ): module_name = source_folder.name + "." + module_name source_folder = source_folder.parent return module_name ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/rope/base/nameanalyze.py0000664000175000017500000000317014512700666017237 0ustar00lieryanlieryanfrom rope.base import ast def get_name_levels(node): """Return a list of ``(name, level)`` tuples for assigned names The `level` is `None` for simple assignments and is a list of numbers for tuple assignments for example in:: a, (b, c) = x The levels for for `a` is ``[0]``, for `b` is ``[1, 0]`` and for `c` is ``[1, 1]``. """ visitor = _NodeNameCollector() visitor.visit(node) return visitor.names class _NodeNameCollector(ast.RopeNodeVisitor): def __init__(self, levels=None): self.names = [] self.levels = levels self.index = 0 def _add_node(self, node): new_levels = [] if self.levels is not None: new_levels = list(self.levels) new_levels.append(self.index) self.index += 1 self._added(node, new_levels) def _added(self, node, levels): if hasattr(node, "id"): self.names.append((node.id, levels)) def _Name(self, node): self._add_node(node) def _ExceptHandler(self, node): self.names.append((node.name, [])) def _Tuple(self, node): new_levels = [] if self.levels is not None: new_levels = list(self.levels) new_levels.append(self.index) self.index += 1 visitor = _NodeNameCollector(new_levels) for child in ast.iter_child_nodes(node): visitor.visit(child) self.names.extend(visitor.names) def _Subscript(self, node): self._add_node(node) def _Attribute(self, node): self._add_node(node) def _Slice(self, node): self._add_node(node) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1705553002.6819828 rope-1.12.0/rope/base/oi/0000775000175000017500000000000014552126153014764 5ustar00lieryanlieryan././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/rope/base/oi/__init__.py0000664000175000017500000000322214512700666017077 0ustar00lieryanlieryan"""Rope object analysis and inference package Rope makes some simplifying assumptions about a python program. It assumes that a program only performs assignments and function calls. Tracking assignments is simple and `PyName` objects handle that. The main problem is function calls. Rope uses these two approaches for obtaining call information: * Static object analysis: `rope.base.pycore.PyCore.analyze_module()` It can analyze modules to obtain information about functions. This is done by analyzing function calls in a module or scope. Currently SOA analyzes the scopes that are changed while saving or when the user asks to analyze a module. That is mainly because static analysis is time-consuming. * Dynamic object analysis: `rope.base.pycore.PyCore.run_module()` When you run a module or your testsuite, when DOA is enabled, it collects information about parameters passed to and objects returned from functions. The main problem with this approach is that it is quite slow; Not when looking up the information but when collecting them. An instance of `rope.base.oi.objectinfo.ObjectInfoManager` can be used for accessing these information. It saves the data in a `rope.base.oi.objectdb.ObjectDB` internally. Now if our objectdb does not know anything about a function and we need the value returned by it, static object inference, SOI, comes into play. It analyzes function body and tries to infer the object that is returned from it (we usually need the returned value for the given parameter objects). Rope might collect and store information for other `PyName`, too. For instance rope stores the object builtin containers hold. """ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/rope/base/oi/doa.py0000664000175000017500000001634514512700666016115 0ustar00lieryanlieryanimport base64 import contextlib import hashlib import hmac try: import cPickle as pickle # type:ignore except ImportError: import pickle # type:ignore import marshal import os import socket import subprocess import sys import tempfile import threading def _compat_compare_digest(a, b): """Implementation of hmac.compare_digest for python < 2.7.7. This function uses an approach designed to prevent timing analysis by avoiding content-based short circuiting behaviour, making it appropriate for cryptography. """ if len(a) != len(b): return False # Computes the bitwise difference of all characters in the two strings # before returning whether or not they are equal. difference = 0 for a_char, b_char in zip(a, b): difference |= ord(a_char) ^ ord(b_char) return difference == 0 try: from hmac import compare_digest except ImportError: compare_digest = _compat_compare_digest class PythonFileRunner: """A class for running python project files""" def __init__( self, pycore, file_, args=None, stdin=None, stdout=None, analyze_data=None ): self.pycore = pycore self.file = file_ self.analyze_data = analyze_data self.observers = [] self.args = args self.stdin = stdin self.stdout = stdout def run(self): """Execute the process""" env = dict(os.environ) file_path = self.file.real_path path_folders = ( self.pycore.project.get_source_folders() + self.pycore.project.get_python_path_folders() ) env["PYTHONPATH"] = os.pathsep.join(folder.real_path for folder in path_folders) runmod_path = self.pycore.project.find_module("rope.base.oi.runmod").real_path self.receiver = None self._init_data_receiving() send_info = "-" if self.receiver: send_info = self.receiver.get_send_info() args = [ sys.executable, runmod_path, send_info, self.pycore.project.address, self.file.real_path, ] if self.analyze_data is None: del args[1:4] if self.args is not None: args.extend(self.args) self.process = subprocess.Popen( executable=sys.executable, args=args, env=env, cwd=os.path.split(file_path)[0], stdin=self.stdin, stdout=self.stdout, stderr=self.stdout, close_fds=os.name != "nt", ) def _init_data_receiving(self): if self.analyze_data is None: return # Disabling FIFO data transfer due to blocking when running # unittests in the GUI. # XXX: Handle FIFO data transfer for `rope.ui.testview` if True or os.name == "nt": self.receiver = _SocketReceiver() else: self.receiver = _FIFOReceiver() self.receiving_thread = threading.Thread(target=self._receive_information) self.receiving_thread.daemon = True self.receiving_thread.start() def _receive_information(self): # temp = open('/dev/shm/info', 'wb') for data in self.receiver.receive_data(): self.analyze_data(data) # temp.write(str(data) + '\n') # temp.close() for observer in self.observers: observer() def wait_process(self): """Wait for the process to finish""" self.process.wait() if self.analyze_data: self.receiving_thread.join() def kill_process(self): """Stop the process""" if self.process.poll() is not None: return with contextlib.suppress(OSError): if hasattr(self.process, "terminate"): self.process.terminate() elif os.name != "nt": os.kill(self.process.pid, 9) else: import ctypes handle = int(self.process._handle) ctypes.windll.kernel32.TerminateProcess(handle, -1) def add_finishing_observer(self, observer): """Notify this observer when execution finishes""" self.observers.append(observer) class _MessageReceiver: def receive_data(self): pass def get_send_info(self): pass class _SocketReceiver(_MessageReceiver): def __init__(self): self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.data_port = 3037 self.key = os.urandom(32) while self.data_port < 4000: try: self.server_socket.bind(("localhost", self.data_port)) break except OSError: self.data_port += 1 self.server_socket.listen(1) def get_send_info(self): return "%d:%s" % (self.data_port, base64.b64encode(self.key).decode("utf-8")) def receive_data(self): conn, addr = self.server_socket.accept() self.server_socket.close() my_file = conn.makefile("rb") while True: # Received messages must meet the following criteria: # 1. Must be contained on a single line. # 2. Must be prefixed with a base64 encoded sha256 message digest # of the base64 encoded pickle data. # 3. Message digest must be computed using the correct key. # # Any messages received that do not meet these criteria will never # be unpickled and will be dropped silently. try: buf = my_file.readline() if len(buf) == 0: break try: digest_end = buf.index(b":") buf_digest = base64.b64decode(buf[:digest_end]) buf_data = buf[digest_end + 1 : -1] decoded_buf_data = base64.b64decode(buf_data) except Exception: # Corrupted data; the payload cannot be trusted and just has # to be dropped. See CVE-2014-3539. continue digest = hmac.new(self.key, buf_data, hashlib.sha256).digest() if not compare_digest(buf_digest, digest): # Signature mismatch; the payload cannot be trusted and just # has to be dropped. See CVE-2014-3539. continue yield pickle.loads(decoded_buf_data) except EOFError: break my_file.close() conn.close() class _FIFOReceiver(_MessageReceiver): def __init__(self): # XXX: this is insecure and might cause race conditions self.file_name = self._get_file_name() os.mkfifo(self.file_name) def _get_file_name(self): prefix = tempfile.gettempdir() + "/__rope_" i = 0 while os.path.exists(prefix + str(i).rjust(4, "0")): i += 1 return prefix + str(i).rjust(4, "0") def get_send_info(self): return self.file_name def receive_data(self): my_file = open(self.file_name, "rb") while True: try: yield marshal.load(my_file) except EOFError: break my_file.close() os.remove(self.file_name) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/rope/base/oi/memorydb.py0000664000175000017500000000656014512700666017166 0ustar00lieryanlieryanfrom rope.base import utils from rope.base.oi import objectdb from rope.base.serializer import json_to_python, python_to_json class MemoryDB(objectdb.FileDict): def __init__(self, project, persist=None): self.project = project self._persist = persist self.files = self self._load_files() self.project.data_files.add_write_hook(self.write) def _load_files(self): self._files = {} if self.persist: result = self.project.data_files.read_data("objectdb") if result is not None: self._files = result def keys(self): return self._files.keys() def __iter__(self): yield from self._files def __len__(self): return len(self._files) def __setitem__(self): raise NotImplementedError() def __contains__(self, key): return key in self._files def __getitem__(self, key): return FileInfo(self._files[key]) def create(self, path): self._files[path] = {} def rename(self, file, newfile): if file not in self._files: return self._files[newfile] = self._files[file] del self[file] def __delitem__(self, file): del self._files[file] def write(self): if self.persist: self.project.data_files.write_data("objectdb", self._files) @property @utils.deprecated("compress_objectdb is no longer supported") def compress(self): return False @property def persist(self): if self._persist is not None: return self._persist else: return self.project.prefs.get("save_objectdb", False) class FileInfo(objectdb.FileInfo): def __init__(self, scopes): self.scopes = scopes def create_scope(self, key): self.scopes[key] = ScopeInfo() def keys(self): return self.scopes.keys() def __contains__(self, key): return key in self.scopes def __getitem__(self, key): return self.scopes[key] def __delitem__(self, key): del self.scopes[key] def __iter__(self): yield from self.scopes def __len__(self): return len(self.scopes) def __setitem__(self): raise NotImplementedError() class ScopeInfo(objectdb.ScopeInfo): def __init__(self): self.call_info = {} self.per_name = {} def get_per_name(self, name): return self.per_name.get(name, None) def save_per_name(self, name, value): self.per_name[name] = value def get_returned(self, parameters): return self.call_info.get(parameters, None) def get_call_infos(self): for args, returned in self.call_info.items(): yield objectdb.CallInfo(args, returned) def add_call(self, parameters, returned): self.call_info[parameters] = returned def __getstate__(self): original_data = (self.call_info, self.per_name) encoded = python_to_json(original_data, version=2) encoded["$"] = "ScopeInfo" return encoded def __setstate__(self, data): if isinstance(data, tuple) and len(data) == 2: # legacy pickle-based serialization self.call_info, self.per_name = data else: # new serialization assert data["$"] == "ScopeInfo" self.call_info, self.per_name = json_to_python(data) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/rope/base/oi/objectdb.py0000664000175000017500000001070314512700666017116 0ustar00lieryanlieryanclass ObjectDB: def __init__(self, db, validation): self.db = db self.validation = validation self.observers = [] self.files = db.files def validate_files(self): for file in list(self.files): if not self.validation.is_file_valid(file): del self.files[file] self._file_removed(file) def validate_file(self, file): if file not in self.files: return for key in list(self.files[file]): if not self.validation.is_scope_valid(file, key): del self.files[file][key] def file_moved(self, file, newfile): if file not in self.files: return self.files.rename(file, newfile) self._file_removed(file) self._file_added(newfile) def get_files(self): return self.files.keys() def get_returned(self, path, key, args): scope_info = self._get_scope_info(path, key, readonly=True) result = scope_info.get_returned(args) if self.validation.is_value_valid(result): return result def get_pername(self, path, key, name): scope_info = self._get_scope_info(path, key, readonly=True) result = scope_info.get_per_name(name) if self.validation.is_value_valid(result): return result def get_callinfos(self, path, key): scope_info = self._get_scope_info(path, key, readonly=True) return scope_info.get_call_infos() def add_callinfo(self, path, key, args, returned): scope_info = self._get_scope_info(path, key, readonly=False) old_returned = scope_info.get_returned(args) if self.validation.is_more_valid(returned, old_returned): scope_info.add_call(args, returned) def add_pername(self, path, key, name, value): scope_info = self._get_scope_info(path, key, readonly=False) old_value = scope_info.get_per_name(name) if self.validation.is_more_valid(value, old_value): scope_info.save_per_name(name, value) def add_file_list_observer(self, observer): self.observers.append(observer) def write(self): self.db.write() def _get_scope_info(self, path, key, readonly=True): if path not in self.files: if readonly: return _NullScopeInfo() self.files.create(path) self._file_added(path) if key not in self.files[path]: if readonly: return _NullScopeInfo() self.files[path].create_scope(key) result = self.files[path][key] if isinstance(result, dict): print(self.files, self.files[path], self.files[path][key]) return result def _file_removed(self, path): for observer in self.observers: observer.removed(path) def _file_added(self, path): for observer in self.observers: observer.added(path) def __str__(self): scope_count = 0 for file_dict in self.files.values(): scope_count += len(file_dict) return "ObjectDB holds {} file and {} scope infos".format( len(self.files), scope_count, ) class _NullScopeInfo: def __init__(self, error_on_write=True): self.error_on_write = error_on_write def get_per_name(self, name): pass def save_per_name(self, name, value): if self.error_on_write: raise NotImplementedError() def get_returned(self, parameters): pass def get_call_infos(self): return [] def add_call(self, parameters, returned): if self.error_on_write: raise NotImplementedError() class FileInfo(dict): def create_scope(self, key): pass class FileDict(dict): def create(self, key): pass def rename(self, key, new_key): pass class ScopeInfo: def get_per_name(self, name): pass def save_per_name(self, name, value): pass def get_returned(self, parameters): pass def get_call_infos(self): pass def add_call(self, parameters, returned): pass class CallInfo: def __init__(self, args, returned): self.args = args self.returned = returned def get_parameters(self): return self.args def get_returned(self): return self.returned class FileListObserver: def added(self, path): pass def removed(self, path): pass ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/rope/base/oi/objectinfo.py0000664000175000017500000002066014512700666017467 0ustar00lieryanlieryanimport warnings from rope.base import exceptions, resourceobserver from rope.base.oi import memorydb, objectdb, transform class ObjectInfoManager: """Stores object information It uses an instance of `objectdb.ObjectDB` for storing information. """ def __init__(self, project): self.project = project self.to_textual = transform.PyObjectToTextual(project) self.to_pyobject = transform.TextualToPyObject(project) self.doi_to_pyobject = transform.DOITextualToPyObject(project) self._init_objectdb() if project.prefs.get("validate_objectdb", False): self._init_validation() def _init_objectdb(self): dbtype = self.project.get_prefs().get("objectdb_type", None) persist = None if dbtype is not None: warnings.warn( '"objectdb_type" project config is deprecated;\n' 'Use "save_objectdb" instead in your project ' 'config file.\n(".ropeproject/config.py" by default)\n', DeprecationWarning, ) if dbtype != "memory" and self.project.ropefolder is not None: persist = True self.validation = TextualValidation(self.to_pyobject) db = memorydb.MemoryDB(self.project, persist=persist) self.objectdb = objectdb.ObjectDB(db, self.validation) def _init_validation(self): self.objectdb.validate_files() observer = resourceobserver.ResourceObserver( changed=self._resource_changed, moved=self._resource_moved, removed=self._resource_moved, ) files = [] for path in self.objectdb.get_files(): resource = self.to_pyobject.path_to_resource(path) if resource is not None and resource.project == self.project: files.append(resource) self.observer = resourceobserver.FilteredResourceObserver(observer, files) self.objectdb.add_file_list_observer(_FileListObserver(self)) self.project.add_observer(self.observer) def _resource_changed(self, resource): try: self.objectdb.validate_file(self.to_textual.resource_to_path(resource)) except exceptions.ModuleSyntaxError: pass def _resource_moved(self, resource, new_resource=None): self.observer.remove_resource(resource) if new_resource is not None: old = self.to_textual.resource_to_path(resource) new = self.to_textual.resource_to_path(new_resource) self.objectdb.file_moved(old, new) self.observer.add_resource(new_resource) def get_returned(self, pyobject, args): result = self.get_exact_returned(pyobject, args) if result is not None: return result path, key = self._get_scope(pyobject) if path is None: return None for call_info in self.objectdb.get_callinfos(path, key): returned = call_info.get_returned() if returned and returned[0] not in ("unknown", "none"): result = returned break if result is None: result = returned if result is not None: return self.to_pyobject(result) def get_exact_returned(self, pyobject, args): path, key = self._get_scope(pyobject) if path is not None: returned = self.objectdb.get_returned( path, key, self._args_to_textual(pyobject, args) ) if returned is not None: return self.to_pyobject(returned) def _args_to_textual(self, pyfunction, args): parameters = list(pyfunction.get_param_names(special_args=False)) arguments = args.get_arguments(parameters)[: len(parameters)] textual_args = tuple(self.to_textual(arg) for arg in arguments) return textual_args def get_parameter_objects(self, pyobject): path, key = self._get_scope(pyobject) if path is None: return None arg_count = len(pyobject.get_param_names(special_args=False)) unknowns = arg_count parameters = [None] * arg_count for call_info in self.objectdb.get_callinfos(path, key): args = call_info.get_parameters() for index, arg in enumerate(args[:arg_count]): old = parameters[index] if self.validation.is_more_valid(arg, old): parameters[index] = arg if self.validation.is_value_valid(arg): unknowns -= 1 if unknowns == 0: break if unknowns < arg_count: return [self.to_pyobject(parameter) for parameter in parameters] def get_passed_objects(self, pyfunction, parameter_index): path, key = self._get_scope(pyfunction) if path is None: return [] result = [] for call_info in self.objectdb.get_callinfos(path, key): args = call_info.get_parameters() if len(args) > parameter_index: parameter = self.to_pyobject(args[parameter_index]) if parameter is not None: result.append(parameter) return result def doa_data_received(self, data): def doi_to_normal(textual): pyobject = self.doi_to_pyobject(textual) return self.to_textual(pyobject) function = doi_to_normal(data[0]) args = tuple(doi_to_normal(textual) for textual in data[1]) returned = doi_to_normal(data[2]) if function[0] == "defined" and len(function) == 3: self._save_data(function, args, returned) def function_called(self, pyfunction, params, returned=None): function_text = self.to_textual(pyfunction) params_text = tuple(self.to_textual(param) for param in params) returned_text = ("unknown",) if returned is not None: returned_text = self.to_textual(returned) self._save_data(function_text, params_text, returned_text) def save_per_name(self, scope, name, data): path, key = self._get_scope(scope.pyobject) if path is not None: self.objectdb.add_pername(path, key, name, self.to_textual(data)) def get_per_name(self, scope, name): path, key = self._get_scope(scope.pyobject) if path is not None: result = self.objectdb.get_pername(path, key, name) if result is not None: return self.to_pyobject(result) def _save_data(self, function, args, returned=("unknown",)): self.objectdb.add_callinfo(function[1], function[2], args, returned) def _get_scope(self, pyobject): resource = pyobject.get_module().get_resource() if resource is None: return None, None textual = self.to_textual(pyobject) if textual[0] == "defined": path = textual[1] if len(textual) == 3: key = textual[2] else: key = "" return path, key return None, None def sync(self): self.objectdb.sync() def __str__(self): return str(self.objectdb) class TextualValidation: def __init__(self, to_pyobject): self.to_pyobject = to_pyobject def is_value_valid(self, value): # ???: Should none and unknown be considered valid? if value is None or value[0] in ("none", "unknown"): return False return self.to_pyobject(value) is not None def is_more_valid(self, new, old): if old is None: return True return new[0] not in ("unknown", "none") def is_file_valid(self, path): return self.to_pyobject.path_to_resource(path) is not None def is_scope_valid(self, path, key): if key == "": textual = ("defined", path) else: textual = ("defined", path, key) return self.to_pyobject(textual) is not None class _FileListObserver: def __init__(self, object_info): self.object_info = object_info self.observer = self.object_info.observer self.to_pyobject = self.object_info.to_pyobject def removed(self, path): resource = self.to_pyobject.path_to_resource(path) if resource is not None: self.observer.remove_resource(resource) def added(self, path): resource = self.to_pyobject.path_to_resource(path) if resource is not None: self.observer.add_resource(resource) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/rope/base/oi/runmod.py0000664000175000017500000002052414512700666016650 0ustar00lieryanlieryandef __rope_start_everything(): import os import socket import sys try: import cPickle as pickle # type:ignore except ImportError: import pickle import base64 import hashlib import hmac import inspect import marshal import threading import types class _MessageSender: def send_data(self, data): pass class _SocketSender(_MessageSender): def __init__(self, port, key): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(("127.0.0.1", port)) self.my_file = s.makefile("wb") self.key = base64.b64decode(key) def send_data(self, data): if not self.my_file.closed: pickled_data = base64.b64encode( pickle.dumps(data, pickle.HIGHEST_PROTOCOL) ) dgst = hmac.new(self.key, pickled_data, hashlib.sha256).digest() self.my_file.write(base64.b64encode(dgst) + b":" + pickled_data + b"\n") def close(self): self.my_file.close() class _FileSender(_MessageSender): def __init__(self, file_name): self.my_file = open(file_name, "wb") def send_data(self, data): if not self.my_file.closed: marshal.dump(data, self.my_file) def close(self): self.my_file.close() def _cached(func): cache = {} def newfunc(self, arg): if arg in cache: return cache[arg] result = func(self, arg) cache[arg] = result return result return newfunc class _FunctionCallDataSender: def __init__(self, send_info, project_root): self.project_root = project_root if send_info[0].isdigit(): port, key = send_info.split(":", 1) self.sender = _SocketSender(int(port), key) else: self.sender = _FileSender(send_info) def global_trace(frame, event, arg): # HACK: Ignoring out->in calls # This might lose some information if self._is_an_interesting_call(frame): return self.on_function_call sys.settrace(global_trace) threading.settrace(global_trace) def on_function_call(self, frame, event, arg): if event != "return": return args = [] returned = ("unknown",) code = frame.f_code for argname in code.co_varnames[: code.co_argcount]: try: argvalue = self._object_to_persisted_form(frame.f_locals[argname]) args.append(argvalue) except (TypeError, AttributeError): args.append(("unknown",)) try: returned = self._object_to_persisted_form(arg) except (TypeError, AttributeError): pass try: data = ( self._object_to_persisted_form(frame.f_code), tuple(args), returned, ) self.sender.send_data(data) except TypeError: pass return self.on_function_call def _is_an_interesting_call(self, frame): # if frame.f_code.co_name in ['?', '']: # return False # return not frame.f_back or # not self._is_code_inside_project(frame.f_back.f_code) if not self._is_code_inside_project(frame.f_code) and ( not frame.f_back or not self._is_code_inside_project(frame.f_back.f_code) ): return False return True def _is_code_inside_project(self, code): source = self._path(code.co_filename) return ( source is not None and os.path.exists(source) and _realpath(source).startswith(self.project_root) ) @_cached def _get_persisted_code(self, object_): source = self._path(object_.co_filename) if not os.path.exists(source): raise TypeError("no source") return ("defined", _realpath(source), str(object_.co_firstlineno)) @_cached def _get_persisted_class(self, object_): try: return ( "defined", _realpath(inspect.getsourcefile(object_)), object_.__name__, ) except (TypeError, AttributeError): return ("unknown",) def _get_persisted_builtin(self, object_): if isinstance(object_, str): return ("builtin", "str") if isinstance(object_, list): holding = None if len(object_) > 0: holding = object_[0] return ("builtin", "list", self._object_to_persisted_form(holding)) if isinstance(object_, dict): keys = None values = None if len(object_) > 0: # @todo - fix it properly, why is __locals__ being # duplicated ? keys = [key for key in object_.keys() if key != "__locals__"][0] values = object_[keys] return ( "builtin", "dict", self._object_to_persisted_form(keys), self._object_to_persisted_form(values), ) if isinstance(object_, tuple): objects = [] if len(object_) < 3: for holding in object_: objects.append(self._object_to_persisted_form(holding)) else: objects.append(self._object_to_persisted_form(object_[0])) return tuple(["builtin", "tuple"] + objects) if isinstance(object_, set): holding = None if len(object_) > 0: for o in object_: holding = o break return ("builtin", "set", self._object_to_persisted_form(holding)) return ("unknown",) def _object_to_persisted_form(self, object_): if object_ is None: return ("none",) if isinstance(object_, types.CodeType): return self._get_persisted_code(object_) if isinstance(object_, types.FunctionType): return self._get_persisted_code(object_.__code__) if isinstance(object_, types.MethodType): return self._get_persisted_code(object_.__func__.__code__) if isinstance(object_, types.ModuleType): return self._get_persisted_module(object_) if isinstance(object_, (str, list, dict, tuple, set)): return self._get_persisted_builtin(object_) if isinstance(object_, type): return self._get_persisted_class(object_) return ("instance", self._get_persisted_class(type(object_))) @_cached def _get_persisted_module(self, object_): path = self._path(object_.__file__) if path and os.path.exists(path): return ("defined", _realpath(path)) return ("unknown",) def _path(self, path): if path.endswith(".pyc"): path = path[:-1] if path.endswith(".py"): return path def close(self): self.sender.close() sys.settrace(None) def _realpath(path): return os.path.realpath(os.path.abspath(os.path.expanduser(path))) send_info = sys.argv[1] project_root = sys.argv[2] file_to_run = sys.argv[3] run_globals = globals() run_globals.update( {"__name__": "__main__", "__builtins__": __builtins__, "__file__": file_to_run} ) if send_info != "-": data_sender = _FunctionCallDataSender(send_info, project_root) del sys.argv[1:4] with open(file_to_run) as f: code = compile(f.read(), file_to_run, "exec") exec(code, run_globals) if send_info != "-": data_sender.close() if __name__ == "__main__": __rope_start_everything() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/rope/base/oi/soa.py0000664000175000017500000001334614512700666016132 0ustar00lieryanlieryanimport rope.base.ast import rope.base.oi.soi import rope.base.pynames from rope.base import arguments, evaluate, nameanalyze, pyobjects def analyze_module(pycore, pymodule, should_analyze, search_subscopes, followed_calls): """Analyze `pymodule` for static object inference Analyzes scopes for collecting object information. The analysis starts from inner scopes. """ _analyze_node(pycore, pymodule, should_analyze, search_subscopes, followed_calls) def _analyze_node(pycore, pydefined, should_analyze, search_subscopes, followed_calls): if search_subscopes(pydefined): for scope in pydefined.get_scope().get_scopes(): _analyze_node( pycore, scope.pyobject, should_analyze, search_subscopes, followed_calls ) if should_analyze(pydefined): new_followed_calls = max(0, followed_calls - 1) return_true = lambda pydefined: True return_false = lambda pydefined: False def _follow(pyfunction): _analyze_node( pycore, pyfunction, return_true, return_false, new_followed_calls ) visitor = SOAVisitor(pycore, pydefined, _follow if followed_calls else None) for child in rope.base.ast.iter_child_nodes(pydefined.get_ast()): visitor.visit(child) class SOAVisitor(rope.base.ast.RopeNodeVisitor): def __init__(self, pycore, pydefined, follow_callback=None): self.pycore = pycore self.pymodule = pydefined.get_module() self.scope = pydefined.get_scope() self.follow = follow_callback def _FunctionDef(self, node): pass def _ClassDef(self, node): pass def _Call(self, node): for child in rope.base.ast.iter_child_nodes(node): self.visit(child) primary, pyname = evaluate.eval_node2(self.scope, node.func) if pyname is None: return pyfunction = pyname.get_object() if isinstance(pyfunction, pyobjects.AbstractFunction): args = arguments.create_arguments(primary, pyfunction, node, self.scope) elif isinstance(pyfunction, pyobjects.PyClass): pyclass = pyfunction if "__init__" in pyfunction: pyfunction = pyfunction["__init__"].get_object() pyname = rope.base.pynames.UnboundName(pyobjects.PyObject(pyclass)) args = self._args_with_self(primary, pyname, pyfunction, node) elif "__call__" in pyfunction: pyfunction = pyfunction["__call__"].get_object() args = self._args_with_self(primary, pyname, pyfunction, node) else: return self._call(pyfunction, args) def _args_with_self(self, primary, self_pyname, pyfunction, node): base_args = arguments.create_arguments(primary, pyfunction, node, self.scope) return arguments.MixedArguments(self_pyname, base_args, self.scope) def _call(self, pyfunction, args): if isinstance(pyfunction, pyobjects.PyFunction): if self.follow is not None: before = self._parameter_objects(pyfunction) self.pycore.object_info.function_called( pyfunction, args.get_arguments(pyfunction.get_param_names()) ) pyfunction._set_parameter_pyobjects(None) if self.follow is not None: after = self._parameter_objects(pyfunction) if after != before: self.follow(pyfunction) # XXX: Maybe we should not call every builtin function if isinstance(pyfunction, rope.base.builtins.BuiltinFunction): pyfunction.get_returned_object(args) def _parameter_objects(self, pyfunction): return [ pyfunction.get_parameter(i) for i in range(len(pyfunction.get_param_names(False))) ] def _AnnAssign(self, node): for child in rope.base.ast.iter_child_nodes(node): self.visit(child) visitor = _SOAAssignVisitor() nodes = [] visitor.visit(node.target) nodes.extend(visitor.nodes) self._evaluate_assign_value(node, nodes, type_hint=node.annotation) def _Assign(self, node): for child in rope.base.ast.iter_child_nodes(node): self.visit(child) visitor = _SOAAssignVisitor() nodes = [] for child in node.targets: visitor.visit(child) nodes.extend(visitor.nodes) self._evaluate_assign_value(node, nodes) def _evaluate_assign_value(self, node, nodes, type_hint=False): for subscript, levels in nodes: instance = evaluate.eval_node(self.scope, subscript.value) args_pynames = [evaluate.eval_node(self.scope, subscript.slice)] value = rope.base.oi.soi._infer_assignment( rope.base.pynames.AssignmentValue( node.value, levels, type_hint=type_hint ), self.pymodule, ) args_pynames.append(rope.base.pynames.UnboundName(value)) if instance is not None and value is not None: pyobject = instance.get_object() if "__setitem__" in pyobject: pyfunction = pyobject["__setitem__"].get_object() args = arguments.ObjectArguments([instance] + args_pynames) self._call(pyfunction, args) # IDEA: handle `__setslice__`, too class _SOAAssignVisitor(nameanalyze._NodeNameCollector): def __init__(self): super().__init__() self.nodes = [] def _added(self, node, levels): if isinstance(node, rope.base.ast.Subscript) and isinstance( node.slice, (rope.base.ast.Index, rope.base.ast.expr) ): self.nodes.append((node, levels)) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/rope/base/oi/soi.py0000664000175000017500000001704714512700666016144 0ustar00lieryanlieryan"""A module for inferring objects For more information see the documentation in `rope.base.oi` package. """ import rope.base.builtins # Use full qualification for clarity. from rope.base import arguments, evaluate, pynames, pyobjects, utils from rope.base.oi.type_hinting.factory import get_type_hinting_factory _ignore_inferred = utils.ignore_exception(pyobjects.IsBeingInferredError) @_ignore_inferred def infer_returned_object(pyfunction, args): """Infer the `PyObject` this `PyFunction` returns after calling""" object_info = pyfunction.pycore.object_info result = object_info.get_exact_returned(pyfunction, args) if result is not None: return result result = _infer_returned(pyfunction, args) if result is not None: if args and pyfunction.get_module().get_resource() is not None: params = args.get_arguments(pyfunction.get_param_names(special_args=False)) object_info.function_called(pyfunction, params, result) return result result = object_info.get_returned(pyfunction, args) if result is not None: return result hint_return = get_type_hinting_factory( pyfunction.pycore.project ).make_return_provider() type_ = hint_return(pyfunction) if type_ is not None: return pyobjects.PyObject(type_) @_ignore_inferred def infer_parameter_objects(pyfunction): """Infer the `PyObject` of parameters of this `PyFunction`""" object_info = pyfunction.pycore.object_info result = object_info.get_parameter_objects(pyfunction) if result is None: result = _parameter_objects(pyfunction) _handle_first_parameter(pyfunction, result) return result def _handle_first_parameter(pyobject, parameters): kind = pyobject.get_kind() if not parameters: if not pyobject.get_param_names(special_args=False): return parameters.append(pyobjects.get_unknown()) if kind == "method": parameters[0] = pyobjects.PyObject(pyobject.parent) if kind == "classmethod": parameters[0] = pyobject.parent @_ignore_inferred def infer_assigned_object(pyname): if not pyname.assignments: return for assignment in reversed(pyname.assignments): result = _infer_assignment(assignment, pyname.module) if ( isinstance(result, rope.base.builtins.BuiltinUnknown) and result.get_name() == "NotImplementedType" ): break elif result == pyobjects.get_unknown(): break elif result is not None: return result hint_assignment = get_type_hinting_factory( pyname.module.pycore.project ).make_assignment_provider() hinting_result = hint_assignment(pyname) if hinting_result is not None: return pyobjects.PyObject(hinting_result) return result def get_passed_objects(pyfunction, parameter_index): object_info = pyfunction.pycore.object_info result = object_info.get_passed_objects(pyfunction, parameter_index) if not result: statically_inferred = _parameter_objects(pyfunction) if len(statically_inferred) > parameter_index: result.append(statically_inferred[parameter_index]) return result def _infer_returned(pyobject, args): if args: # HACK: Setting parameter objects manually # This is not thread safe and might cause problems if `args` # does not come from a good call site pyobject.get_scope().invalidate_data() pyobject._set_parameter_pyobjects( args.get_arguments(pyobject.get_param_names(special_args=False)) ) scope = pyobject.get_scope() if not scope._get_returned_asts(): return maxtries = 3 for returned_node in reversed(scope._get_returned_asts()[-maxtries:]): try: resulting_pyname = evaluate.eval_node(scope, returned_node) if resulting_pyname is None: continue pyobject = resulting_pyname.get_object() if pyobject == pyobjects.get_unknown(): continue if not scope._is_generator(): return pyobject else: return rope.base.builtins.get_generator(pyobject) except pyobjects.IsBeingInferredError: pass def _parameter_objects(pyobject): result = [] params = pyobject.get_param_names(special_args=False) hint_param = get_type_hinting_factory(pyobject.pycore.project).make_param_provider() for name in params: type_ = hint_param(pyobject, name) if type_ is not None: result.append(pyobjects.PyObject(type_)) else: result.append(pyobjects.get_unknown()) return result # handling `rope.base.pynames.AssignmentValue` @_ignore_inferred def _infer_assignment(assignment, pymodule): result = _follow_pyname(assignment, pymodule) if result is None: return None pyname, pyobject = result pyobject = _follow_evaluations(assignment, pyname, pyobject) if pyobject is None: return None return _follow_levels(assignment, pyobject) def _follow_levels(assignment, pyobject): for index in assignment.levels: if isinstance(pyobject.get_type(), rope.base.builtins.Tuple): holdings = pyobject.get_type().get_holding_objects() if holdings: pyobject = holdings[min(len(holdings) - 1, index)] else: pyobject = None elif isinstance(pyobject.get_type(), rope.base.builtins.List): pyobject = pyobject.get_type().holding else: pyobject = None if pyobject is None: break return pyobject @_ignore_inferred def _follow_pyname(assignment, pymodule, lineno=None): assign_node = assignment.type_hint or assignment.ast_node if lineno is None: lineno = _get_lineno_for_node(assign_node) holding_scope = pymodule.get_scope().get_inner_scope_for_line(lineno) pyname = evaluate.eval_node(holding_scope, assign_node) if pyname is not None: result = pyname.get_object() if ( isinstance(result.get_type(), rope.base.builtins.Property) and holding_scope.get_kind() == "Class" ): arg = pynames.UnboundName(pyobjects.PyObject(holding_scope.pyobject)) return pyname, result.get_type().get_property_object( arguments.ObjectArguments([arg]) ) return pyname, result @_ignore_inferred def _follow_evaluations(assignment, pyname, pyobject): new_pyname = pyname tokens = assignment.evaluation.split(".") for token in tokens: call = token.endswith("()") if call: token = token[:-2] if token: pyname = new_pyname new_pyname = _get_attribute(pyobject, token) if new_pyname is not None: pyobject = new_pyname.get_object() if pyobject is not None and call: if isinstance(pyobject, pyobjects.AbstractFunction): args = arguments.ObjectArguments([pyname]) pyobject = pyobject.get_returned_object(args) else: pyobject = None if pyobject is None: break if pyobject is not None and assignment.assign_type: return pyobjects.PyObject(pyobject) return pyobject def _get_lineno_for_node(assign_node): if hasattr(assign_node, "lineno") and assign_node.lineno is not None: return assign_node.lineno return 1 def _get_attribute(pyobject, name): if pyobject is not None and name in pyobject: return pyobject[name] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/rope/base/oi/transform.py0000664000175000017500000002314514512700666017361 0ustar00lieryanlieryan"""Provides classes for persisting `PyObject`""" import os import re import rope.base.builtins # Use full qualification for clarity. from rope.base import exceptions class PyObjectToTextual: """For transforming `PyObject` to textual form This can be used for storing `PyObjects` in files. Use `TextualToPyObject` for converting back. """ def __init__(self, project): self.project = project def transform(self, pyobject): """Transform a `PyObject` to textual form""" if pyobject is None: return ("none",) object_type = type(pyobject) try: method = getattr(self, object_type.__name__ + "_to_textual") return method(pyobject) except AttributeError: return ("unknown",) def __call__(self, pyobject): return self.transform(pyobject) def PyObject_to_textual(self, pyobject): if isinstance(pyobject.get_type(), rope.base.pyobjects.AbstractClass): result = self.transform(pyobject.get_type()) if result[0] == "defined": return ("instance", result) return result return ("unknown",) def PyFunction_to_textual(self, pyobject): return self._defined_to_textual(pyobject) def PyClass_to_textual(self, pyobject): return self._defined_to_textual(pyobject) def _defined_to_textual(self, pyobject): address = [] while pyobject.parent is not None: address.insert(0, pyobject.get_name()) pyobject = pyobject.parent return ( "defined", self._get_pymodule_path(pyobject.get_module()), ".".join(address), ) def PyModule_to_textual(self, pyobject): return ("defined", self._get_pymodule_path(pyobject)) def PyPackage_to_textual(self, pyobject): return ("defined", self._get_pymodule_path(pyobject)) def List_to_textual(self, pyobject): return ("builtin", "list", self.transform(pyobject.holding)) def Dict_to_textual(self, pyobject): return ( "builtin", "dict", self.transform(pyobject.keys), self.transform(pyobject.values), ) def Tuple_to_textual(self, pyobject): objects = [ self.transform(holding) for holding in pyobject.get_holding_objects() ] return tuple(["builtin", "tuple"] + objects) def Set_to_textual(self, pyobject): return ("builtin", "set", self.transform(pyobject.holding)) def Iterator_to_textual(self, pyobject): return ("builtin", "iter", self.transform(pyobject.holding)) def Generator_to_textual(self, pyobject): return ("builtin", "generator", self.transform(pyobject.holding)) def Str_to_textual(self, pyobject): return ("builtin", "str") def File_to_textual(self, pyobject): return ("builtin", "file") def BuiltinFunction_to_textual(self, pyobject): return ("builtin", "function", pyobject.get_name()) def _get_pymodule_path(self, pymodule): return self.resource_to_path(pymodule.get_resource()) def resource_to_path(self, resource): if resource.project == self.project: return resource.path else: return resource.real_path class TextualToPyObject: """For transforming textual form to `PyObject`""" def __init__(self, project, allow_in_project_absolutes=False): self.project = project def __call__(self, textual): return self.transform(textual) def transform(self, textual): """Transform an object from textual form to `PyObject`""" if textual is None: return None type = textual[0] try: method = getattr(self, type + "_to_pyobject") return method(textual) except AttributeError: return None def builtin_to_pyobject(self, textual): method = getattr(self, "builtin_%s_to_pyobject" % textual[1], None) if method is not None: return method(textual) def builtin_str_to_pyobject(self, textual): return rope.base.builtins.get_str() def builtin_list_to_pyobject(self, textual): holding = self.transform(textual[2]) return rope.base.builtins.get_list(holding) def builtin_dict_to_pyobject(self, textual): keys = self.transform(textual[2]) values = self.transform(textual[3]) return rope.base.builtins.get_dict(keys, values) def builtin_tuple_to_pyobject(self, textual): objects = [self.transform(holding) for holding in textual[2:]] return rope.base.builtins.get_tuple(*objects) def builtin_set_to_pyobject(self, textual): holding = self.transform(textual[2]) return rope.base.builtins.get_set(holding) def builtin_iter_to_pyobject(self, textual): holding = self.transform(textual[2]) return rope.base.builtins.get_iterator(holding) def builtin_generator_to_pyobject(self, textual): holding = self.transform(textual[2]) return rope.base.builtins.get_generator(holding) def builtin_file_to_pyobject(self, textual): return rope.base.builtins.get_file() def builtin_function_to_pyobject(self, textual): if textual[2] in rope.base.builtins.builtins: return rope.base.builtins.builtins[textual[2]].get_object() def unknown_to_pyobject(self, textual): return None def none_to_pyobject(self, textual): return None def _module_to_pyobject(self, textual): path = textual[1] return self._get_pymodule(path) def _hierarchical_defined_to_pyobject(self, textual): path = textual[1] names = textual[2].split(".") pymodule = self._get_pymodule(path) pyobject = pymodule for name in names: if pyobject is None: return None if isinstance(pyobject, rope.base.pyobjects.PyDefinedObject): try: pyobject = pyobject.get_scope()[name].get_object() except exceptions.NameNotFoundError: return None else: return None return pyobject def defined_to_pyobject(self, textual): if len(textual) == 2 or textual[2] == "": return self._module_to_pyobject(textual) else: return self._hierarchical_defined_to_pyobject(textual) def instance_to_pyobject(self, textual): type = self.transform(textual[1]) if type is not None: return rope.base.pyobjects.PyObject(type) def _get_pymodule(self, path): resource = self.path_to_resource(path) if resource is not None: return self.project.get_pymodule(resource) def path_to_resource(self, path): try: root = self.project.address if not os.path.isabs(path): return self.project.get_resource(path) if path == root or path.startswith(root + os.sep): # INFO: This is a project file; should not be absolute return None import rope.base.project return rope.base.project.get_no_project().get_resource(path) except exceptions.ResourceNotFoundError: return None class DOITextualToPyObject(TextualToPyObject): """For transforming textual form to `PyObject` The textual form DOI uses is different from rope's standard textual form. The reason is that we cannot find the needed information by analyzing live objects. This class can be used to transform DOI textual form to `PyObject` and later we can convert it to standard textual form using `TextualToPyObject` class. """ def _function_to_pyobject(self, textual): path = textual[1] lineno = int(textual[2]) pymodule = self._get_pymodule(path) if pymodule is not None: scope = pymodule.get_scope() inner_scope = scope.get_inner_scope_for_line(lineno) return inner_scope.pyobject def _class_to_pyobject(self, textual): path, name = textual[1:] pymodule = self._get_pymodule(path) if pymodule is None: return None module_scope = pymodule.get_scope() suspected = None if name in module_scope.get_names(): suspected = module_scope[name].get_object() if suspected is not None and isinstance(suspected, rope.base.pyobjects.PyClass): return suspected else: lineno = self._find_occurrence(name, pymodule.get_resource().read()) if lineno is not None: inner_scope = module_scope.get_inner_scope_for_line(lineno) return inner_scope.pyobject def defined_to_pyobject(self, textual): if len(textual) == 2: return self._module_to_pyobject(textual) else: if textual[2].isdigit(): result = self._function_to_pyobject(textual) else: result = self._class_to_pyobject(textual) if not isinstance(result, rope.base.pyobjects.PyModule): return result def _find_occurrence(self, name, source): pattern = re.compile(r"^\s*class\s*" + name + r"\b") lines = source.split("\n") for i in range(len(lines)): if pattern.match(lines[i]): return i + 1 def path_to_resource(self, path): import rope.base.libutils relpath = rope.base.libutils.path_relative_to_project_root(self.project, path) if relpath is not None: path = relpath return super().path_to_resource(path) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1705553002.6819828 rope-1.12.0/rope/base/oi/type_hinting/0000775000175000017500000000000014552126153017465 5ustar00lieryanlieryan././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/rope/base/oi/type_hinting/__init__.py0000664000175000017500000000000014512700666021567 0ustar00lieryanlieryan././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/rope/base/oi/type_hinting/evaluate.py0000664000175000017500000002154114512700666021653 0ustar00lieryanlieryan# Based on super lightweight Simple Top-Down Parser from http://effbot.org/zone/simple-top-down-parsing.htm # and https://bitbucket.org/emacsway/sqlbuilder/src/default/sqlbuilder/smartsql/contrib/evaluate.py import re from rope.base import utils as base_utils from rope.base.oi.type_hinting import utils class SymbolBase: name = None # node/token type name def __init__(self): self.value = None # used by name and literals self.first = None self.second = None self.third = None # used by tree nodes def nud(self, parser): raise SyntaxError("Syntax error (%r)." % self.name) def led(self, left, parser): raise SyntaxError("Unknown operator (%r)." % self.name) def evaluate(self, pyobject): raise NotImplementedError(self.name, self) def __repr__(self): if self.name == "(name)": return f"({self.name[1:-1]} {self.value})" out = [repr(self.name), self.first, self.second, self.third] out = [str(i) for i in out if i] return "(" + " ".join(out) + ")" class SymbolTable: def multi(func): def _inner(self, names, *a, **kw): for name in names.split(): func(self, name, *a, **kw) return _inner def __init__(self): self.symbol_table = {} def get(self, name, default=None): return self.symbol_table.get(name, default) def __getitem__(self, name): return self.symbol_table[name] def __iter__(self): return iter(self.symbol_table) def symbol(self, name, bp=0): try: s = self.symbol_table[name] except KeyError: class S(SymbolBase): pass s = S s.__name__ = "symbol-" + name # for debugging s.name = name s.lbp = bp self.symbol_table[name] = s else: s.lbp = max(bp, s.lbp) return s @multi # type:ignore def infix(self, name, bp): symbol = self.symbol(name, bp) @method(symbol) def led(self, left, parser): self.first = left self.second = parser.expression(bp) return self @multi # type:ignore def infix_r(self, name, bp): symbol = self.symbol(name, bp) @method(symbol) def led(self, left, parser): self.first = left self.second = parser.expression(bp - 0.1) return self def ternary(self, name, name2, bp): symbol = self.symbol(name, bp) symbol2 = self.symbol(name2) @method(symbol) def led(self, left, parser): self.first = left self.second = parser.expression(symbol2.lbp) parser.advance(symbol2.name) self.third = parser.expression(symbol2.lbp + 0.1) return self @multi # type:ignore def prefix(self, name, bp): # type:ignore symbol = self.symbol(name, bp) @method(symbol) def nud(self, parser): self.first = parser.expression(bp) return self @multi # type:ignore def postfix(self, name, bp): symbol = self.symbol(name, bp) @method(symbol) def led(self, left, parser): self.first = left return self multi = staticmethod(multi) # Just for code checker symbol_table = SymbolTable() class Lexer: _token_pattern = re.compile( r""" \s* (?: ( [,()\[\]|] | -> | (?<=\s)(?:or)\b ) # operator | ([a-zA-Z](?:\w|\.)*) # name ) """, re.U | re.S | re.X, ) def __init__(self, symbol_table): self.symbol_table = symbol_table def tokenize(self, program): for name, value in self._tokenize_expr(program): symbol = symbol_table.get(value) if symbol: s = symbol() elif name == "(name)": symbol = symbol_table[name] s = symbol() s.value = value else: raise SyntaxError( "Unknown operator ({}). Possible operators are {!r}".format( value, list(self.symbol_table) ) ) yield s def _tokenize_expr(self, program): if isinstance(program, bytes): program = program.decode("utf-8") # import pprint; pprint.pprint(self._token_pattern.findall(program)) for operator, name in self._token_pattern.findall(program): if operator: yield "(operator)", operator elif name: yield "(name)", name else: raise SyntaxError yield "(end)", "(end)" class Parser: token = None next = None def __init__(self, lexer): self.lexer = lexer def parse(self, program): generator = self.lexer.tokenize(program) self.next = generator.__next__ self.token = self.next() return self.expression() def expression(self, rbp=0): t = self.token self.token = self.next() left = t.nud(self) while rbp < self.token.lbp: t = self.token self.token = self.next() left = t.led(left, self) return left def advance(self, name=None): if name and self.token.name != name: raise SyntaxError(f"Expected {name!r} but found {self.token.name!r}") self.token = self.next() def method(s): assert issubclass(s, SymbolBase) def bind(fn): setattr(s, fn.__name__, fn) return fn return bind symbol, infix, infix_r, prefix, postfix, ternary = ( symbol_table.symbol, symbol_table.infix, symbol_table.infix_r, symbol_table.prefix, symbol_table.postfix, symbol_table.ternary, ) symbol("(", 270) symbol(")") symbol("[", 250) # Parameters symbol("]") symbol("->", 230) infix("|", 170) infix("or", 170) symbol(",") symbol("(name)") symbol("(end)") @method(symbol("(name)")) # type:ignore def nud(self, parser): return self @method(symbol("(name)")) # type:ignore def evaluate(self, pyobject): return utils.resolve_type(self.value, pyobject) # Parametrized objects @method(symbol("[")) # type:ignore def led(self, left, parser): self.first = left self.second = [] if parser.token.name != "]": while 1: if parser.token.name == "]": break self.second.append(parser.expression()) if parser.token.name != ",": break parser.advance(",") parser.advance("]") return self @method(symbol("[")) # type:ignore def evaluate(self, pyobject): return utils.parametrize_type( self.first.evaluate(pyobject), *[i.evaluate(pyobject) for i in self.second] ) # Anonymous Function Calls @method(symbol("(")) # type:ignore def nud(self, parser): self.second = [] if parser.token.name != ")": while 1: self.second.append(parser.expression()) if parser.token.name != ",": break parser.advance(",") parser.advance(")") parser.advance("->") self.third = parser.expression(symbol("->").lbp + 0.1) return self # Function Calls @method(symbol("(")) # type:ignore def led(self, left, parser): self.first = left self.second = [] if parser.token.name != ")": while 1: self.second.append(parser.expression()) if parser.token.name != ",": break parser.advance(",") parser.advance(")") parser.advance("->") self.third = parser.expression(symbol("->").lbp + 0.1) return self @method(symbol("(")) # type:ignore def evaluate(self, pyobject): # TODO: Implement me raise NotImplementedError @method(symbol("or")) # type:ignore @method(symbol("|")) def evaluate(self, pyobject): # TODO: Implement me raise NotImplementedError class Compiler: parser_factory = Parser lexer_factory = Lexer symbol_table = symbol_table def _make_parser(self): return self.parser_factory(self.lexer_factory(self.symbol_table)) @base_utils.cached(500) def __call__(self, program): """ :type program: str :rtype: rope.base.oi.type_hinting.evaluate.SymbolBase """ return self._make_parser().parse(program) compile = Compiler() class Evaluator: compile = compile def __call__(self, program, pyobject): """Evaluates the program string or AST :type program: str or rope.base.oi.type_hinting.evaluate.SymbolBase :rtype: rope.base.pyobjects.PyDefinedObject | rope.base.pyobjects.PyObject or None """ ast = self.compile(program) if isinstance(program, str) else program return ast.evaluate(pyobject) evaluate = Evaluator() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/rope/base/oi/type_hinting/factory.py0000664000175000017500000000525014512700666021513 0ustar00lieryanlieryanfrom rope.base import utils from rope.base.oi.type_hinting import interfaces from rope.base.oi.type_hinting.providers import ( composite, docstrings, inheritance, numpydocstrings, pep0484_type_comments, ) from rope.base.oi.type_hinting.resolvers import composite as composite_resolvers from rope.base.oi.type_hinting.resolvers import types class TypeHintingFactory(interfaces.ITypeHintingFactory): @utils.saveit def make_param_provider(self): providers = [ docstrings.ParamProvider( docstrings.DocstringParamParser(), self.make_resolver() ), docstrings.ParamProvider( numpydocstrings.NumPyDocstringParamParser(), self.make_resolver() ), ] return inheritance.ParamProvider(composite.ParamProvider(*providers)) @utils.saveit def make_return_provider(self): providers = [ docstrings.ReturnProvider( docstrings.DocstringReturnParser(), self.make_resolver() ), ] return inheritance.ReturnProvider(composite.ReturnProvider(*providers)) @utils.saveit def make_assignment_provider(self): providers = [ pep0484_type_comments.AssignmentProvider(self.make_resolver()), docstrings.AssignmentProvider( docstrings.DocstringParamParser(), self.make_resolver() ), docstrings.AssignmentProvider( numpydocstrings.NumPyDocstringParamParser(), self.make_resolver() ), ] return inheritance.AssignmentProvider(composite.AssignmentProvider(*providers)) @utils.saveit def make_resolver(self): """ :rtype: rope.base.oi.type_hinting.resolvers.interfaces.IResolver """ resolvers = [ types.Resolver(), ] return composite_resolvers.Resolver(*resolvers) default_type_hinting_factory = TypeHintingFactory() class TypeHintingFactoryAccessor: def __call__(self, project): """ :type project: rope.base.project.Project :rtype: rope.base.oi.type_hinting.interfaces.ITypeHintingFactory """ factory_location = project.get_prefs().get( "type_hinting_factory", "rope.base.oi.type_hinting.factory.default_type_hinting_factory", ) return self._get_factory(factory_location) @utils.cached(10) def _get_factory(self, factory_location): """ :type factory_location: str :rtype: rope.base.oi.type_hinting.interfaces.ITypeHintingFactory """ return utils.resolve(factory_location) get_type_hinting_factory = TypeHintingFactoryAccessor() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/rope/base/oi/type_hinting/interfaces.py0000664000175000017500000000131314512700666022163 0ustar00lieryanlieryanclass ITypeHintingFactory: def make_param_provider(self): """ :rtype: rope.base.oi.type_hinting.providers.interfaces.IParamProvider """ raise NotImplementedError def make_return_provider(self): """ :rtype: rope.base.oi.type_hinting.providers.interfaces.IReturnProvider """ raise NotImplementedError def make_assignment_provider(self): """ :rtype: rope.base.oi.type_hinting.providers.interfaces.IAssignmentProvider """ raise NotImplementedError def make_resolver(self): """ :rtype: rope.base.oi.type_hinting.resolvers.interfaces.IResolver """ raise NotImplementedError ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1705553002.6819828 rope-1.12.0/rope/base/oi/type_hinting/providers/0000775000175000017500000000000014552126153021502 5ustar00lieryanlieryan././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/rope/base/oi/type_hinting/providers/__init__.py0000664000175000017500000000000014512700666023604 0ustar00lieryanlieryan././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/rope/base/oi/type_hinting/providers/composite.py0000664000175000017500000000350014512700666024057 0ustar00lieryanlieryanfrom rope.base.oi.type_hinting.providers import interfaces class ParamProvider(interfaces.IParamProvider): def __init__(self, *delegates): """ :type delegates: list[rope.base.oi.type_hinting.providers.interfaces.IParamProvider] """ self._delegates = delegates def __call__(self, pyfunc, param_name): """ :type pyfunc: rope.base.pyobjectsdef.PyFunction :type param_name: str :rtype: rope.base.pyobjects.PyDefinedObject | rope.base.pyobjects.PyObject or None """ for delegate in self._delegates: result = delegate(pyfunc, param_name) if result: return result class ReturnProvider(interfaces.IReturnProvider): def __init__(self, *delegates): """ :type delegates: list[rope.base.oi.type_hinting.providers.interfaces.IReturnProvider] """ self._delegates = delegates def __call__(self, pyfunc): """ :type pyfunc: rope.base.pyobjectsdef.PyFunction :rtype: rope.base.pyobjects.PyDefinedObject | rope.base.pyobjects.PyObject or None """ for delegate in self._delegates: result = delegate(pyfunc) if result: return result class AssignmentProvider(interfaces.IAssignmentProvider): def __init__(self, *delegates): """ :type delegates: list[rope.base.oi.type_hinting.providers.interfaces.IAssignmentProvider] """ self._delegates = delegates def __call__(self, pyname): """ :type pyname: rope.base.pynamesdef.AssignedName :rtype: rope.base.pyobjects.PyDefinedObject | rope.base.pyobjects.PyObject or None """ for delegate in self._delegates: result = delegate(pyname) if result: return result ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/rope/base/oi/type_hinting/providers/docstrings.py0000664000175000017500000001402414512700666024237 0ustar00lieryanlieryan""" Hinting the type using docstring of class/function. It's an irreplaceable thing if you are using Dependency Injection with passive class: http://www.martinfowler.com/articles/injection.html Some code extracted (or based on code) from: https://github.com/davidhalter/jedi/blob/b489019f5bd5750051122b94cc767df47751ecb7/jedi/evaluate/docstrings.py Thanks to @davidhalter for this utils under MIT License. Similar solutions: - https://www.jetbrains.com/pycharm/help/type-hinting-in-pycharm.html - https://www.python.org/dev/peps/pep-0484/#type-comments - http://www.pydev.org/manual_adv_type_hints.html - https://jedi.readthedocs.org/en/latest/docs/features.html#type-hinting Discussions: - https://groups.google.com/d/topic/rope-dev/JlAzmZ83K1M/discussion - https://groups.google.com/d/topic/rope-dev/LCFNN98vckI/discussion """ import re from rope.base.oi.type_hinting import utils from rope.base.oi.type_hinting.providers import interfaces class ParamProvider(interfaces.IParamProvider): def __init__(self, docstring_parser, resolver): """ :type docstring_parser: rope.base.oi.type_hinting.providers.docstrings.IParamParser :type resolver: rope.base.oi.type_hinting.resolvers.interfaces.IResolver """ self._parse_docstring = docstring_parser self._resolve = resolver def __call__(self, pyfunc, param_name): """ :type pyfunc: rope.base.pyobjectsdef.PyFunction :type param_name: str :rtype: rope.base.pyobjects.PyDefinedObject | rope.base.pyobjects.PyObject or None """ type_strs = self._parse_docstring(pyfunc.get_doc(), param_name) if type_strs: return self._resolve(type_strs[0], pyfunc) class ReturnProvider(interfaces.IReturnProvider): def __init__(self, docstring_parser, resolver): """ :type docstring_parser: rope.base.oi.type_hinting.providers.docstrings.IReturnParser :type resolver: rope.base.oi.type_hinting.resolvers.interfaces.IResolver """ self._parse_docstring = docstring_parser self._resolve = resolver def __call__(self, pyfunc): """ :type pyfunc: rope.base.pyobjectsdef.PyFunction :rtype: rope.base.pyobjects.PyDefinedObject | rope.base.pyobjects.PyObject or None """ type_strs = self._parse_docstring(pyfunc.get_doc()) if type_strs: return self._resolve(type_strs[0], pyfunc) class AssignmentProvider(interfaces.IAssignmentProvider): def __init__(self, docstring_parser, resolver): """ :type docstring_parser: rope.base.oi.type_hinting.providers.docstrings.IParamParser :type resolver: rope.base.oi.type_hinting.resolvers.interfaces.IResolver """ self._parse_docstring = docstring_parser self._resolve = resolver def __call__(self, pyname): """ :type pyname: rope.base.pynamesdef.AssignedName :rtype: rope.base.pyobjects.PyDefinedObject | rope.base.pyobjects.PyObject or None """ try: pyclass, attr_name = utils.get_class_with_attr_name(pyname) except TypeError: return else: type_strs = self._parse_docstring(pyclass.get_doc(), attr_name) if type_strs: return self._resolve(type_strs[0], pyclass) class IParamParser: def __call__(self, docstring, param_name): """ :type docstring: str :type param_name: str """ class IReturnParser: def __call__(self, docstring): """ :type docstring: str """ class DocstringParamParser(IParamParser): DOCSTRING_PARAM_PATTERNS = [ r"\s*:type\s+%s:\s*([^\n]+)", # Sphinx r"\s*:param\s+(\w+)\s+%s:[^\n]+", # Sphinx param with type r"\s*@type\s+%s:\s*([^\n]+)", # Epydoc ] def __init__(self): self._strip_rst_role = RSTRoleStrip() def __call__(self, docstring, param_name): """Search `docstring` for type(-s) of `param_name`. >>> DocstringParamParser()(':type param: int', 'param') ['int'] >>> DocstringParamParser()('@type param: int', 'param') ['int'] >>> DocstringParamParser()(':type param: :class:`threading.Thread`', 'param') ['threading.Thread'] >>> bool(DocstringParamParser()('no document', 'param')) False >>> DocstringParamParser()(':param int param: some description', 'param') ['int'] """ if not docstring: return [] patterns = [ re.compile(p % re.escape(param_name)) for p in self.DOCSTRING_PARAM_PATTERNS ] for pattern in patterns: match = pattern.search(docstring) if match: return [self._strip_rst_role(match.group(1))] return [] class DocstringReturnParser(IReturnParser): DOCSTRING_RETURN_PATTERNS = [ re.compile(r"\s*:rtype:\s*([^\n]+)", re.M), # Sphinx re.compile(r"\s*@rtype:\s*([^\n]+)", re.M), # Epydoc ] def __init__(self): self._strip_rst_role = RSTRoleStrip() def __call__(self, docstring): if not docstring: return [] for p in self.DOCSTRING_RETURN_PATTERNS: match = p.search(docstring) if match: return [self._strip_rst_role(match.group(1))] return [] class RSTRoleStrip: RST_ROLE_PATTERN = re.compile(r":[^`]+:`([^`]+)`") def __call__(self, type_str): """ Strip off the part looks like a ReST role in `type_str`. >>> RSTRoleStrip()(':class:`ClassName`') # strip off :class: 'ClassName' >>> RSTRoleStrip()(':py:obj:`module.Object`') # works with domain 'module.Object' >>> RSTRoleStrip()('ClassName') # do nothing when not ReST role 'ClassName' See also: http://sphinx-doc.org/domains.html#cross-referencing-python-objects """ match = self.RST_ROLE_PATTERN.match(type_str) if match: return match.group(1) else: return type_str ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/rope/base/oi/type_hinting/providers/inheritance.py0000664000175000017500000000410414512700666024347 0ustar00lieryanlieryanfrom rope.base.oi.type_hinting import utils from rope.base.oi.type_hinting.providers import interfaces class ParamProvider(interfaces.IParamProvider): def __init__(self, delegate): """ :type delegate: rope.base.oi.type_hinting.providers.interfaces.IParamProvider """ self._delegate = delegate def __call__(self, pyfunc, param_name): """ :type pyfunc: rope.base.pyobjectsdef.PyFunction :type param_name: str :rtype: rope.base.pyobjects.PyDefinedObject | rope.base.pyobjects.PyObject or None """ superfunc = pyfunc while superfunc: result = self._delegate(superfunc, param_name) if result: return result superfunc = utils.get_super_func(superfunc) class ReturnProvider(interfaces.IReturnProvider): def __init__(self, delegate): """ :type delegate: rope.base.oi.type_hinting.providers.interfaces.IReturnProvider """ self._delegate = delegate def __call__(self, pyfunc): """ :type pyfunc: rope.base.pyobjectsdef.PyFunction :rtype: rope.base.pyobjects.PyDefinedObject | rope.base.pyobjects.PyObject or None """ superfunc = pyfunc while superfunc: result = self._delegate(superfunc) if result: return result superfunc = utils.get_super_func(superfunc) class AssignmentProvider(interfaces.IAssignmentProvider): def __init__(self, delegate): """ :type delegate: rope.base.oi.type_hinting.providers.interfaces.IAssignmentProvider """ self._delegate = delegate def __call__(self, pyname): """ :type pyname: rope.base.pynamesdef.AssignedName :rtype: rope.base.pyobjects.PyDefinedObject | rope.base.pyobjects.PyObject or None """ super_pyname = pyname while super_pyname: result = self._delegate(super_pyname) if result: return result super_pyname = utils.get_super_assignment(super_pyname) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/rope/base/oi/type_hinting/providers/interfaces.py0000664000175000017500000000203714512700666024204 0ustar00lieryanlieryanclass IParamProvider: def __call__(self, pyfunc, param_name): """ :type pyfunc: rope.base.pyobjectsdef.PyFunction :type param_name: str :rtype: rope.base.pyobjects.PyDefinedObject | rope.base.pyobjects.PyObject or None """ raise NotImplementedError class IReturnProvider: """ :type resolve: rope.base.oi.type_hinting.resolvers.interfaces.IResolver """ resolve = None def __call__(self, pyfunc): """ :type pyfunc: rope.base.pyobjectsdef.PyFunction :rtype: rope.base.pyobjects.PyDefinedObject | rope.base.pyobjects.PyObject or None """ raise NotImplementedError class IAssignmentProvider: """ :type resolve: rope.base.oi.type_hinting.resolvers.interfaces.IResolver """ resolve = None def __call__(self, pyname): """ :type pyname: rope.base.pynamesdef.AssignedName :rtype: rope.base.pyobjects.PyDefinedObject | rope.base.pyobjects.PyObject or None """ raise NotImplementedError ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/rope/base/oi/type_hinting/providers/numpydocstrings.py0000664000175000017500000000261314512700666025331 0ustar00lieryanlieryan""" Some code extracted (or based on code) from: https://github.com/davidhalter/jedi/blob/b489019f5bd5750051122b94cc767df47751ecb7/jedi/evaluate/docstrings.py Thanks to @davidhalter for this utils under MIT License. """ import re from rope.base.ast import literal_eval from rope.base.oi.type_hinting.providers import docstrings try: from numpydoc.docscrape import NumpyDocString # type:ignore except ImportError: NumpyDocString = None class NumPyDocstringParamParser(docstrings.IParamParser): def __call__(self, docstring, param_name): """Search `docstring` (in numpydoc format) for type(-s) of `param_name`.""" if not docstring: return [] params = NumpyDocString(docstring)._parsed_data["Parameters"] for p_name, p_type, p_descr in params: if p_name == param_name: m = re.match("([^,]+(,[^,]+)*?)(,[ ]*optional)?$", p_type) if m: p_type = m.group(1) if p_type.startswith("{"): types = {type(x).__name__ for x in literal_eval(p_type)} return list(types) else: return [p_type] return [] class _DummyParamParser(docstrings.IParamParser): def __call__(self, docstring, param_name): return [] if not NumpyDocString: NumPyDocstringParamParser = _DummyParamParser # type:ignore ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/rope/base/oi/type_hinting/providers/pep0484_type_comments.py0000664000175000017500000000276514512700666026143 0ustar00lieryanlieryanimport re from rope.base.oi.type_hinting import utils from rope.base.oi.type_hinting.providers import interfaces class AssignmentProvider(interfaces.IAssignmentProvider): def __init__(self, resolver): """ :type resolver: rope.base.oi.type_hinting.resolvers.interfaces.IResolver """ self._resolve = resolver PEP0484_TYPE_COMMENT_PATTERNS = (re.compile(r"type:\s*([^\n]+)"),) def __call__(self, pyname): """ :type pyname: rope.base.pynamesdef.AssignedName :rtype: rope.base.pyobjects.PyDefinedObject | rope.base.pyobjects.PyObject or None """ from rope.base.oi.soi import _get_lineno_for_node lineno = _get_lineno_for_node(pyname.assignments[0].ast_node) holding_scope = pyname.module.get_scope().get_inner_scope_for_line(lineno) line = holding_scope._get_global_scope()._scope_finder.lines.get_line(lineno) if "#" in line: type_strs = self._search_type_in_type_comment(line.split("#", 1)[1]) if type_strs: return self._resolve(type_strs[0], holding_scope.pyobject) def _search_type_in_type_comment(self, code): """For more info see: https://www.python.org/dev/peps/pep-0484/#type-comments >>> AssignmentProvider()._search_type_in_type_comment('type: int') ['int'] """ for p in self.PEP0484_TYPE_COMMENT_PATTERNS: match = p.search(code) if match: return [match.group(1)] ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1705553002.685983 rope-1.12.0/rope/base/oi/type_hinting/resolvers/0000775000175000017500000000000014552126153021511 5ustar00lieryanlieryan././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/rope/base/oi/type_hinting/resolvers/__init__.py0000664000175000017500000000000014512700666023613 0ustar00lieryanlieryan././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/rope/base/oi/type_hinting/resolvers/composite.py0000664000175000017500000000141214512700666024066 0ustar00lieryanlieryanfrom rope.base.oi.type_hinting.resolvers import interfaces class Resolver(interfaces.IResolver): def __init__(self, *delegates): """ :type delegates: list[rope.base.oi.type_hinting.resolvers.interfaces.IResolver] """ self._delegates = delegates def __call__(self, hint, pyobject): """ :param hint: For example "List[int]" or "(Foo, Bar) -> Baz" or simple "Foo" :type hint: str :type pyobject: rope.base.pyobjects.PyDefinedObject | rope.base.pyobjects.PyObject :rtype: rope.base.pyobjects.PyDefinedObject | rope.base.pyobjects.PyObject or None """ for delegate in self._delegates: result = delegate(hint, pyobject) if result: return result ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/rope/base/oi/type_hinting/resolvers/interfaces.py0000664000175000017500000000062514512700666024214 0ustar00lieryanlieryanclass IResolver: def __call__(self, hint, pyobject): """ :param hint: For example "List[int]" or "(Foo, Bar) -> Baz" or simple "Foo" :type hint: str :type pyobject: rope.base.pyobjects.PyDefinedObject | rope.base.pyobjects.PyObject :rtype: rope.base.pyobjects.PyDefinedObject | rope.base.pyobjects.PyObject or None """ raise NotImplementedError ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/rope/base/oi/type_hinting/resolvers/types.py0000664000175000017500000000114114512700666023227 0ustar00lieryanlieryanfrom rope.base.oi.type_hinting import evaluate from rope.base.oi.type_hinting.resolvers import interfaces class Resolver(interfaces.IResolver): def __call__(self, hint, pyobject): """ :param hint: For example "List[int]" or "(Foo, Bar) -> Baz" or simple "Foo" :type hint: str :type pyobject: rope.base.pyobjects.PyDefinedObject | rope.base.pyobjects.PyObject :rtype: rope.base.pyobjects.PyDefinedObject | rope.base.pyobjects.PyObject or None """ try: return evaluate.evaluate(hint, pyobject) except Exception: pass ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/rope/base/oi/type_hinting/utils.py0000664000175000017500000001242214512700666021203 0ustar00lieryanlieryanfrom __future__ import annotations import logging from typing import TYPE_CHECKING, Optional, Union import rope.base.utils as base_utils from rope.base import evaluate from rope.base.exceptions import AttributeNotFoundError from rope.base.pyobjects import PyClass, PyDefinedObject, PyFunction, PyObject def get_super_func(pyfunc): if not isinstance(pyfunc.parent, PyClass): return for cls in get_mro(pyfunc.parent)[1:]: try: superfunc = cls.get_attribute(pyfunc.get_name()).get_object() except AttributeNotFoundError: pass else: if isinstance(superfunc, PyFunction): return superfunc def get_super_assignment(pyname): """ :type pyname: rope.base.pynamesdef.AssignedName :type: rope.base.pynamesdef.AssignedName """ try: pyclass, attr_name = get_class_with_attr_name(pyname) except TypeError: return else: for super_pyclass in get_mro(pyclass)[1:]: if attr_name in super_pyclass: return super_pyclass[attr_name] def get_class_with_attr_name(pyname): """ :type pyname: rope.base.pynamesdef.AssignedName :return: rope.base.pyobjectsdef.PyClass, str :rtype: tuple """ lineno = get_lineno_for_node(pyname.assignments[0].ast_node) holding_scope = pyname.module.get_scope().get_inner_scope_for_line(lineno) pyobject = holding_scope.pyobject if isinstance(pyobject, PyClass): pyclass = pyobject elif isinstance(pyobject, PyFunction) and isinstance(pyobject.parent, PyClass): pyclass = pyobject.parent else: return for name, attr in pyclass.get_attributes().items(): if attr is pyname: return (pyclass, name) def get_lineno_for_node(assign_node): if hasattr(assign_node, "lineno") and assign_node.lineno is not None: return assign_node.lineno return 1 def get_mro(pyclass): # FIXME: to use real mro() result class_list = [pyclass] for cls in class_list: for super_cls in cls.get_superclasses(): if isinstance(super_cls, PyClass) and super_cls not in class_list: class_list.append(super_cls) return class_list def resolve_type( type_name: str, pyobject: Union[PyDefinedObject, PyObject], ) -> Optional[Union[PyDefinedObject, PyObject]]: """ Find proper type object from its name. """ deprecated_aliases = {"collections": "collections.abc"} ret_type = None logging.debug("Looking for %s", type_name) if "." not in type_name: try: # XXX: this looks incorrect? It doesn't seem like it would work # correctly if you have a type/class not defined in the # module/global scope ret_type = ( pyobject.get_module().get_scope().get_name(type_name).get_object() ) except AttributeNotFoundError: logging.exception("Cannot resolve type %s", type_name) else: mod_name, attr_name = type_name.rsplit(".", 1) try: mod_finder = evaluate.ScopeNameFinder(pyobject.get_module()) mod = mod_finder._find_module(mod_name).get_object() ret_type = mod.get_attribute(attr_name).get_object() except AttributeNotFoundError: if mod_name in deprecated_aliases: try: logging.debug( "Looking for %s in %s", attr_name, deprecated_aliases[mod_name] ) mod = mod_finder._find_module( deprecated_aliases[mod_name] ).get_object() ret_type = mod.get_attribute(attr_name).get_object() except AttributeNotFoundError: logging.exception( "Cannot resolve type %s in %s", attr_name, dir(mod) ) logging.debug("ret_type = %s", ret_type) return ret_type class ParametrizeType: _supported_mapping = { "builtins.list": "rope.base.builtins.get_list", "builtins.tuple": "rope.base.builtins.get_tuple", "builtins.set": "rope.base.builtins.get_set", "builtins.dict": "rope.base.builtins.get_dict", "_collections_abc.Iterable": "rope.base.builtins.get_iterator", "_collections_abc.Iterator": "rope.base.builtins.get_iterator", "collections.abc.Iterable": "rope.base.builtins.get_iterator", # Python3.3 "collections.abc.Iterator": "rope.base.builtins.get_iterator", # Python3.3 } def __call__(self, pyobject, *args, **kwargs): """ :type pyobject: rope.base.pyobjects.PyObject :rtype: rope.base.pyobjects.PyDefinedObject | rope.base.pyobjects.PyObject or None """ type_factory = self._get_type_factory(pyobject) if type_factory: parametrized_type = type_factory(*args, **kwargs) if parametrized_type: return parametrized_type return pyobject def _get_type_factory(self, pyobject): type_str = "{}.{}".format( pyobject.get_module().get_name(), pyobject.get_name(), ) if type_str in self._supported_mapping: return base_utils.resolve(self._supported_mapping[type_str]) parametrize_type = ParametrizeType() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/rope/base/prefs.py0000664000175000017500000002373314512700666016061 0ustar00lieryanlieryan# mypy reports many problems. # type: ignore """Rope preferences.""" from dataclasses import asdict, dataclass from textwrap import dedent from typing import Any, Callable, Dict, List, Optional, Tuple from packaging.requirements import Requirement from pytoolconfig import PyToolConfig, UniversalKey, field from pytoolconfig.sources import Source from rope.base.resources import Folder @dataclass class Prefs: """Class to store rope preferences.""" ignored_resources: List[str] = field( default_factory=lambda: [ "*.pyc", "*~", ".ropeproject", ".hg", ".svn", "_svn", ".git", ".tox", ".venv", "venv", ".mypy_cache", ".pytest_cache", ], description=dedent(""" 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' """), ) python_files: List[str] = field( default_factory=lambda: ["*.py"], description=dedent(""" 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. """), ) source_folders: List[str] = field( description=dedent(""" 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. """), default_factory=lambda: [], ) python_path: List[str] = field( default_factory=lambda: [], description="You can extend python path for looking up modules.", ) save_objectdb: bool = field( default=False, description="Should rope save object information or not." ) compress_objectdb: bool = field( default=False, description="Deprecated. This has no effect", ) automatic_soa: bool = field( True, "If `True`, rope analyzes each module when it is being saved." ) soa_followed_calls: int = field( default=0, description="The depth of calls to follow in static object analysis" ) perform_doa: bool = field( default=True, description=dedent(""" If `False` when running modules or unit tests 'dynamic object analysis' is turned off. This makes them much faster. """), ) validate_objectdb: bool = field( default=False, description="Rope can check the validity of its object DB when running.", ) max_history_items: int = field(default=32, description="How many undos to hold?") save_history: bool = field( default=True, description="Shows whether to save history across sessions." ) compress_history: bool = field( default=False, description="Deprecated. This has no effect", ) indent_size: int = field( default=4, description=dedent(""" 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. """), ) extension_modules: List[str] = field( default_factory=list, description=""" Builtin and c-extension modules that are allowed to be imported and inspected by rope. """, ) import_dynload_stdmods: bool = field( default=True, description="Add all standard c-extensions to extension_modules list.", ) ignore_syntax_errors: bool = field( default=False, description=dedent(""" 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. """), ) ignore_bad_imports: bool = field( default=False, description=dedent(""" If `True`, rope ignores unresolvable imports. Otherwise, they appear in the importing namespace. """), ) prefer_module_from_imports: bool = field( default=False, description=dedent(""" If `True`, rope will insert new module imports as `from import `by default. """), ) split_imports: bool = field( default=False, description=dedent(""" If `True`, rope will transform a comma list of imports into multiple separate import statements when organizing imports. """), ) pull_imports_to_top: bool = field( default=True, description=dedent(""" If `True`, rope will remove all top-level import statements and reinsert them at the top of the module when making changes. """), ) sort_imports_alphabetically: bool = field( default=False, description=dedent(""" If `True`, rope will sort imports alphabetically by module name instead of alphabetically by import statement, with from imports after normal imports. """), ) type_hinting_factory: str = field( "rope.base.oi.type_hinting.factory.default_type_hinting_factory", description=dedent(""" 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. """), ) project_opened: Optional[Callable] = field( None, description=dedent(""" This function is called after opening the project. Can only be set in config.py. """), ) py_version: Optional[Tuple[int, int]] = field( default=None, description="Minimum python version to target", universal_config=UniversalKey.min_py_version, ) dependencies: Optional[List[Requirement]] = field( default=None, universal_config=UniversalKey.dependencies ) callbacks: Dict[str, Callable[[Any], None]] = field( default_factory=lambda: {}, description=dedent(""" Callbacks run when configuration values are changed. Can only be set in config.py. """), ) def set(self, key: str, value: Any): """Set the value of `key` preference to `value`.""" if key in self.callbacks: self.callbacks[key](value) else: setattr(self, key, value) def add(self, key: str, value: Any): """Add an entry to a list preference Add `value` to the list of entries for the `key` preference. """ if getattr(self, key) is None: self[key] = [] getattr(self, key).append(value) def get(self, key: str, default: Any = None): """Get the value of the key preference""" return getattr(self, key, default) def add_callback(self, key: str, callback: Callable): """Add `key` preference with `callback` function Whenever `key` is set the callback is called with the given `value` as parameter. """ self.callbacks[key] = callback def __setitem__(self, key: str, value: Any): self.set(key, value) def __getitem__(self, key: str): return self.get(key) class _RopeConfigSource(Source): """Custom source for rope config.py files.""" name: str = "config.py" run_globals: Dict def __init__(self, ropefolder: Folder): self.ropefolder = ropefolder self.run_globals = {} def _read(self) -> bool: if self.ropefolder is None or not self.ropefolder.has_child("config.py"): return False config = self.ropefolder.get_child("config.py") self.run_globals.update( { "__name__": "__main__", "__builtins__": __builtins__, "__file__": config.real_path, } ) with open(config.real_path) as f: code = compile(f.read(), config.real_path, "exec") exec(code, self.run_globals) return True def parse(self) -> Optional[Dict]: prefs = Prefs() if not self._read(): return None if "set_prefs" in self.run_globals: self.run_globals["set_prefs"](prefs) if "project_opened" in self.run_globals: prefs["project_opened"] = self.run_globals["project_opened"] return asdict(prefs) def get_config(root: Folder, ropefolder: Folder) -> PyToolConfig: custom_sources = [_RopeConfigSource(ropefolder)] config = PyToolConfig( "rope", root.pathlib, Prefs, custom_sources=custom_sources, bases=[".ropefolder"], recursive=False, global_config=True, ) return config ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/rope/base/project.py0000664000175000017500000003551114512700666016405 0ustar00lieryanlieryanfrom __future__ import annotations import contextlib import json import os import sys import warnings from contextlib import ExitStack from typing import Optional import rope.base.fscommands # Use full qualification for clarity. import rope.base.resourceobserver as resourceobserver from rope.base import exceptions, history, pycore, taskhandle, utils from rope.base.exceptions import ModuleNotFoundError # At present rope.base.prefs starts with `# type:ignore`. # As a result, mypy knows nothing about Prefs and get_config. from rope.base.prefs import Prefs, get_config # type:ignore from rope.base.resources import File, Folder, _ResourceMatcher try: import cPickle as pickle # type:ignore except ImportError: import pickle # type:ignore class _Project: prefs: Prefs def __init__(self, fscommands): self.observers = [] self.fscommands = fscommands self.prefs = Prefs() self.data_files = _DataFiles(self) self._custom_source_folders = [] def get_resource(self, resource_name): """Get a resource in a project. `resource_name` is the path of a resource in a project. It is the path of a resource relative to project root. Project root folder address is an empty string. If the resource does not exist a `exceptions.ResourceNotFound` exception would be raised. Use `get_file()` and `get_folder()` when you need to get nonexistent `Resource`. """ path = self._get_resource_path(resource_name) if not os.path.exists(path): raise exceptions.ResourceNotFoundError( "Resource <%s> does not exist" % resource_name ) elif os.path.isfile(path): return File(self, resource_name) elif os.path.isdir(path): return Folder(self, resource_name) else: raise exceptions.ResourceNotFoundError("Unknown resource " + resource_name) def get_module(self, name, folder=None): """Returns a `PyObject` if the module was found.""" # check if this is a builtin module pymod = self.pycore.builtin_module(name) if pymod is not None: return pymod module = self.find_module(name, folder) if module is None: raise ModuleNotFoundError("Module %s not found" % name) return self.pycore.resource_to_pyobject(module) def get_python_path_folders(self): result = [] for src in self.prefs.get("python_path", []) + sys.path: with contextlib.suppress(exceptions.ResourceNotFoundError): src_folder = get_no_project().get_resource(src) result.append(src_folder) return result # INFO: It was decided not to cache source folders, since: # - Does not take much time when the root folder contains # packages, that is most of the time # - We need a separate resource observer; `self.observer` # does not get notified about module and folder creations def get_source_folders(self): """Returns project source folders""" if self.root is None: return [] result = list(self._custom_source_folders) result.extend(self.pycore._find_source_folders(self.root)) return result def validate(self, folder): """Validate files and folders contained in this folder It validates all of the files and folders contained in this folder if some observers are interested in them. """ for observer in list(self.observers): observer.validate(folder) def add_observer(self, observer): """Register a `ResourceObserver` See `FilteredResourceObserver`. """ self.observers.append(observer) def remove_observer(self, observer): """Remove a registered `ResourceObserver`""" if observer in self.observers: self.observers.remove(observer) def do(self, changes, task_handle=taskhandle.DEFAULT_TASK_HANDLE): """Apply the changes in a `ChangeSet` Most of the time you call this function for committing the changes for a refactoring. """ self.history.do(changes, task_handle=task_handle) def get_pymodule(self, resource, force_errors=False): return self.pycore.resource_to_pyobject(resource, force_errors) def get_pycore(self): return self.pycore def get_file(self, path): """Get the file with `path` (it may not exist)""" return File(self, path) def get_folder(self, path): """Get the folder with `path` (it may not exist)""" return Folder(self, path) def get_prefs(self): return self.prefs def get_relative_module(self, name, folder, level): module = self.find_relative_module(name, folder, level) if module is None: raise ModuleNotFoundError("Module %s not found" % name) return self.pycore.resource_to_pyobject(module) def find_module(self, modname, folder=None) -> Optional[File]: """Returns a resource corresponding to the given module returns None if it can not be found """ for src in self.get_source_folders(): module = _find_module_in_folder(src, modname) if module is not None: return module for src in self.get_python_path_folders(): module = _find_module_in_folder(src, modname) if module is not None: return module if folder is not None: module = _find_module_in_folder(folder, modname) if module is not None: return module return None def find_relative_module(self, modname, folder, level): for i in range(level - 1): folder = folder.parent if modname == "": return folder else: return _find_module_in_folder(folder, modname) def is_ignored(self, resource): return False def _get_resource_path(self, name): pass @property @utils.saveit def history(self): return history.History(self) @property @utils.saveit def pycore(self): return pycore.PyCore(self) def close(self): warnings.warn("Cannot close a NoProject", DeprecationWarning, stacklevel=2) ropefolder = None class Project(_Project): """A Project containing files and folders""" def __init__( self, projectroot, fscommands=None, ropefolder=".ropeproject", **prefs ): """A rope project :parameters: - `projectroot`: The address of the root folder of the project - `fscommands`: Implements the file system operations used by rope; have a look at `rope.base.fscommands` - `ropefolder`: The name of the folder in which rope stores project configurations and data. Pass `None` for not using such a folder at all. - `prefs`: Specify project preferences. These values overwrite config file preferences. """ if projectroot != "/": projectroot = _realpath(projectroot).rstrip("/\\") assert isinstance(projectroot, str) self._address = projectroot self._ropefolder_name = ropefolder if not os.path.exists(self._address): os.mkdir(self._address) elif not os.path.isdir(self._address): raise exceptions.RopeError("Project root exists and" " is not a directory") if fscommands is None: fscommands = rope.base.fscommands.create_fscommands(self._address) super().__init__(fscommands) self.ignored = _ResourceMatcher() self.file_list = _FileListCacher(self) self._init_prefs(prefs) if ropefolder is not None: self.prefs.add("ignored_resources", ropefolder) self._init_source_folders() def __repr__(self): return '<{}.{} "{}">'.format( self.__class__.__module__, self.__class__.__name__, self.address, ) @utils.deprecated("Delete once deprecated functions are gone") def _init_source_folders(self): for path in self.prefs.get("source_folders", []): folder = self.get_resource(path) self._custom_source_folders.append(folder) def get_files(self): return self.file_list.get_files() def get_python_files(self): """Returns all python files available in the project""" return [ resource for resource in self.get_files() if self.pycore.is_python_file(resource) ] def _get_resource_path(self, name): return os.path.join(self._address, *name.split("/")) def _init_ropefolder(self): if self.ropefolder is not None and not self.ropefolder.exists(): self._create_recursively(self.ropefolder) def _create_recursively(self, folder): if folder.parent != self.root and not folder.parent.exists(): self._create_recursively(folder.parent) folder.create() def _init_prefs(self, prefs): config = get_config(self.root, self.ropefolder).parse() self.prefs = config self.prefs.add_callback("ignored_resources", self.ignored.set_patterns) self.ignored.set_patterns(self.prefs.ignored_resources) for key, value in prefs.items(): self.prefs.set(key, value) self._init_other_parts() self._init_ropefolder() if config.project_opened: config.project_opened(self) def _init_other_parts(self): # Forcing the creation of `self.pycore` to register observers self.pycore # pylint: disable=pointless-statement def is_ignored(self, resource): return self.ignored.does_match(resource) def sync(self): """Closes project open resources""" self.close() def close(self): """Closes project open resources""" self.data_files.write() def set(self, key, value): """Set the `key` preference to `value`""" self.prefs.set(key, value) @property def ropefolder(self): if self._ropefolder_name is not None: return self.get_folder(self._ropefolder_name) def validate(self, folder=None): if folder is None: folder = self.root super().validate(folder) root = property(lambda self: self.get_resource("")) address = property(lambda self: self._address) class NoProject(_Project): """A null object for holding out of project files. This class is singleton use `get_no_project` global function """ def __init__(self): fscommands = rope.base.fscommands.FileSystemCommands() super().__init__(fscommands) def _get_resource_path(self, name): real_name = name.replace("/", os.path.sep) return _realpath(real_name) def get_resource(self, name): universal_name = _realpath(name).replace(os.path.sep, "/") return super().get_resource(universal_name) def get_files(self): return [] def get_python_files(self): return [] _no_project = None def get_no_project(): if NoProject._no_project is None: NoProject._no_project = NoProject() return NoProject._no_project class _FileListCacher: def __init__(self, project): self.project = project self.files = None rawobserver = resourceobserver.ResourceObserver( self._changed, self._invalid, self._invalid, self._invalid, self._invalid ) self.project.add_observer(rawobserver) def get_files(self): if self.files is None: self.files = set() self._add_files(self.project.root) return self.files def _add_files(self, folder): for child in folder.get_children(): if child.is_folder(): self._add_files(child) elif not self.project.is_ignored(child): self.files.add(child) def _changed(self, resource): if resource.is_folder(): self.files = None def _invalid(self, resource, new_resource=None): self.files = None class _DataFiles: def __init__(self, project): self.project = project self.hooks = [] def read_data(self, name): if self.project.ropefolder is None: return None file = self._get_file(name) if file.exists(): with open(file.real_path, "rb") as input_file: result = [] try: while True: result.append(pickle.load(input_file)) except EOFError: pass if len(result) == 1: return result[0] if len(result) > 1: return result def write_data(self, name, data): if self.project.ropefolder is not None: file = self._get_file(name) with ExitStack() as cm: output_file = cm.enter_context(open(file.real_path, "wb")) output_file2 = cm.enter_context(open(file.real_path + ".json", "w")) pickle.dump(data, output_file, 2) json.dump(data, output_file2, default=lambda o: o.__getstate__()) def add_write_hook(self, hook): self.hooks.append(hook) def write(self): for hook in self.hooks: hook() def _get_file(self, name): path = self.project.ropefolder.path + "/" + name return self.project.get_file(path) def _realpath(path): """Return the real path of `path` Is equivalent to ``realpath(abspath(expanduser(path)))``. Of the particular notice is the hack dealing with the unfortunate situation of running native-Windows python (os.name == 'nt') inside of Cygwin (abspath starts with '/'), which apparently normal os.path.realpath completely messes up. """ # there is a bug in cygwin for os.path.abspath() for abs paths if sys.platform == "cygwin": if path[1:3] == ":\\": return path elif path[1:3] == ":/": path = "/cygdrive/" + path[0] + path[2:] return os.path.abspath(os.path.expanduser(path)) return os.path.realpath(os.path.abspath(os.path.expanduser(path))) def _find_module_in_folder(folder, modname): module = folder packages = modname.split(".") for pkg in packages[:-1]: if module.is_folder() and module.has_child(pkg): module = module.get_child(pkg) else: return None if module.is_folder(): if ( module.has_child(packages[-1]) and module.get_child(packages[-1]).is_folder() ): return module.get_child(packages[-1]) elif ( module.has_child(packages[-1] + ".py") and not module.get_child(packages[-1] + ".py").is_folder() ): return module.get_child(packages[-1] + ".py") ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/rope/base/pycore.py0000664000175000017500000003100414512700666016231 0ustar00lieryanlieryanimport bisect import contextlib import difflib import warnings import rope.base.libutils import rope.base.oi.doa import rope.base.oi.objectinfo import rope.base.oi.soa import rope.base.resourceobserver import rope.base.resources from rope.base import builtins, exceptions, pyobjectsdef, stdmods, taskhandle, utils class PyCore: def __init__(self, project): self.project = project self._init_resource_observer() self.cache_observers = [] self.module_cache = _ModuleCache(self) self.extension_cache = _ExtensionCache(self) self.object_info = rope.base.oi.objectinfo.ObjectInfoManager(project) self._init_python_files() self._init_automatic_soa() def _init_python_files(self): self.python_matcher = None patterns = self.project.prefs.get("python_files", None) if patterns is not None: self.python_matcher = rope.base.resources._ResourceMatcher() self.python_matcher.set_patterns(patterns) def _init_resource_observer(self): callback = self._invalidate_resource_cache observer = rope.base.resourceobserver.ResourceObserver( changed=callback, moved=callback, removed=callback ) self.observer = rope.base.resourceobserver.FilteredResourceObserver(observer) self.project.add_observer(self.observer) def _init_automatic_soa(self): if not self.automatic_soa: return callback = self._file_changed_for_soa observer = rope.base.resourceobserver.ResourceObserver( changed=callback, moved=callback, removed=callback ) self.project.add_observer(observer) @property def automatic_soa(self): auto_soa = self.project.prefs.get("automatic_soi", None) return self.project.prefs.get("automatic_soa", auto_soa) def _file_changed_for_soa(self, resource, new_resource=None): old_contents = self.project.history.contents_before_current_change(resource) if old_contents is not None: perform_soa_on_changed_scopes(self.project, resource, old_contents) def is_python_file(self, resource): if resource.is_folder(): return False if self.python_matcher is None: return resource.name.endswith(".py") return self.python_matcher.does_match(resource) @utils.deprecated("Use `project.get_module` instead") def get_module(self, name, folder=None): """Returns a `PyObject` if the module was found.""" return self.project.get_module(name, folder) def _builtin_submodules(self, modname): result = {} for extension in self.extension_modules: if extension.startswith(modname + "."): name = extension[len(modname) + 1 :] if "." not in name: result[name] = self.builtin_module(extension) return result def builtin_module(self, name): return self.extension_cache.get_pymodule(name) @utils.deprecated("Use `project.get_relative_module` instead") def get_relative_module(self, name, folder, level): return self.project.get_relative_module(name, folder, level) @utils.deprecated("Use `libutils.get_string_module` instead") def get_string_module(self, code, resource=None, force_errors=False): """Returns a `PyObject` object for the given code If `force_errors` is `True`, `exceptions.ModuleSyntaxError` is raised if module has syntax errors. This overrides ``ignore_syntax_errors`` project config. """ return pyobjectsdef.PyModule(self, code, resource, force_errors=force_errors) @utils.deprecated("Use `libutils.get_string_scope` instead") def get_string_scope(self, code, resource=None): """Returns a `Scope` object for the given code""" return rope.base.libutils.get_string_scope(code, resource) def _invalidate_resource_cache(self, resource, new_resource=None): for observer in self.cache_observers: observer(resource) @utils.deprecated("Use `project.get_python_path_folders` instead") def get_python_path_folders(self): return self.project.get_python_path_folders() @utils.deprecated("Use `project.find_module` instead") def find_module(self, modname, folder=None): """Returns a resource corresponding to the given module returns None if it can not be found """ return self.project.find_module(modname, folder) @utils.deprecated("Use `project.find_relative_module` instead") def find_relative_module(self, modname, folder, level): return self.project.find_relative_module(modname, folder, level) # INFO: It was decided not to cache source folders, since: # - Does not take much time when the root folder contains # packages, that is most of the time # - We need a separate resource observer; `self.observer` # does not get notified about module and folder creations @utils.deprecated("Use `project.get_source_folders` instead") def get_source_folders(self): """Returns project source folders""" return self.project.get_source_folders() def resource_to_pyobject(self, resource, force_errors=False): return self.module_cache.get_pymodule(resource, force_errors) @utils.deprecated("Use `project.get_python_files` instead") def get_python_files(self): """Returns all python files available in the project""" return self.project.get_python_files() def _is_package(self, folder): return ( folder.has_child("__init__.py") and not folder.get_child("__init__.py").is_folder() ) def _find_source_folders(self, folder): for resource in folder.get_folders(): if self._is_package(resource): return [folder] result = [] for resource in folder.get_files(): if resource.name.endswith(".py"): result.append(folder) break for resource in folder.get_folders(): result.extend(self._find_source_folders(resource)) return result def run_module(self, resource, args=None, stdin=None, stdout=None): """Run `resource` module Returns a `rope.base.oi.doa.PythonFileRunner` object for controlling the process. """ perform_doa = self.project.prefs.get("perform_doi", True) perform_doa = self.project.prefs.get("perform_doa", perform_doa) receiver = self.object_info.doa_data_received if not perform_doa: receiver = None runner = rope.base.oi.doa.PythonFileRunner( self, resource, args, stdin, stdout, receiver ) runner.add_finishing_observer(self.module_cache.forget_all_data) runner.run() return runner def analyze_module( self, resource, should_analyze=lambda py: True, search_subscopes=lambda py: True, followed_calls=None, ): """Analyze `resource` module for static object inference This function forces rope to analyze this module to collect information about function calls. `should_analyze` is a function that is called with a `PyDefinedObject` argument. If it returns `True` the element is analyzed. If it is `None` or returns `False` the element is not analyzed. `search_subscopes` is like `should_analyze`; The difference is that if it returns `False` the sub-scopes are all ignored. That is it is assumed that `should_analyze` returns `False` for all of its subscopes. `followed_calls` override the value of ``soa_followed_calls`` project config. """ if followed_calls is None: followed_calls = self.project.prefs.get("soa_followed_calls", 0) pymodule = self.resource_to_pyobject(resource) self.module_cache.forget_all_data() rope.base.oi.soa.analyze_module( self, pymodule, should_analyze, search_subscopes, followed_calls ) def get_classes(self, task_handle=taskhandle.DEFAULT_TASK_HANDLE): warnings.warn( "`PyCore.get_classes()` is deprecated", DeprecationWarning, stacklevel=2 ) return [] def __str__(self): return str(self.module_cache) + str(self.object_info) @utils.deprecated("Use `libutils.modname` instead") def modname(self, resource): return rope.base.libutils.modname(resource) @property @utils.cacheit def extension_modules(self): result = set(self.project.prefs.get("extension_modules", [])) if self.project.prefs.get("import_dynload_stdmods", False): result.update(stdmods.dynload_modules()) return result class _ModuleCache: def __init__(self, pycore): self.pycore = pycore self.module_map = {} self.pycore.cache_observers.append(self._invalidate_resource) self.observer = self.pycore.observer def _invalidate_resource(self, resource): if resource in self.module_map: self.forget_all_data() self.observer.remove_resource(resource) del self.module_map[resource] def get_pymodule(self, resource, force_errors=False): if resource in self.module_map: return self.module_map[resource] if resource.is_folder(): result = pyobjectsdef.PyPackage( self.pycore, resource, force_errors=force_errors, ) else: result = pyobjectsdef.PyModule( self.pycore, resource=resource, force_errors=force_errors, ) if result.has_errors: return result self.module_map[resource] = result self.observer.add_resource(resource) return result def forget_all_data(self): for pymodule in self.module_map.values(): pymodule._forget_concluded_data() def __str__(self): return "PyCore caches %d PyModules\n" % len(self.module_map) class _ExtensionCache: def __init__(self, pycore): self.pycore = pycore self.extensions = {} def get_pymodule(self, name): if name == "__builtin__": return builtins.builtins allowed = self.pycore.extension_modules if name not in self.extensions and name in allowed: self.extensions[name] = builtins.BuiltinModule(name, self.pycore) return self.extensions.get(name) def perform_soa_on_changed_scopes(project, resource, old_contents): pycore = project.pycore if resource.exists() and pycore.is_python_file(resource): with contextlib.suppress(exceptions.ModuleSyntaxError): new_contents = resource.read() # detecting changes in new_contents relative to old_contents detector = _TextChangeDetector(new_contents, old_contents) def search_subscopes(pydefined): scope = pydefined.get_scope() return detector.is_changed(scope.get_start(), scope.get_end()) def should_analyze(pydefined): scope = pydefined.get_scope() start = scope.get_start() end = scope.get_end() return detector.consume_changes(start, end) pycore.analyze_module(resource, should_analyze, search_subscopes) class _TextChangeDetector: def __init__(self, old, new): self.old = old self.new = new self._set_diffs() def _set_diffs(self): differ = difflib.Differ() self.lines = [] lineno = 0 for line in differ.compare( self.old.splitlines(True), self.new.splitlines(True) ): if line.startswith(" "): lineno += 1 elif line.startswith("-"): lineno += 1 self.lines.append(lineno) def is_changed(self, start, end): """Tell whether any of start till end lines have changed The end points are inclusive and indices start from 1. """ left, right = self._get_changed(start, end) return left < right def consume_changes(self, start, end): """Clear the changed status of lines from start till end""" left, right = self._get_changed(start, end) if left < right: del self.lines[left:right] return left < right def _get_changed(self, start, end): left = bisect.bisect_left(self.lines, start) right = bisect.bisect_right(self.lines, end) return left, right ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/rope/base/pynames.py0000664000175000017500000001436614512700666016420 0ustar00lieryanlieryanfrom __future__ import annotations import typing import rope.base.pyobjects from rope.base import exceptions, utils if typing.TYPE_CHECKING: from typing import Union from rope.base import pyobjectsdef class PyName: """References to `PyObject` inside python programs""" def get_object(self): """Return the `PyObject` object referenced by this `PyName`""" def get_definition_location(self): """Return a (module, lineno) tuple""" class DefinedName(PyName): def __init__(self, pyobject): self.pyobject = pyobject def get_object(self): return self.pyobject def get_definition_location(self): lineno = utils.guess_def_lineno( self.pyobject.get_module(), self.pyobject.get_ast() ) return (self.pyobject.get_module(), lineno) class AssignedName(PyName): pass class UnboundName(PyName): def __init__(self, pyobject=None): self.pyobject = pyobject if self.pyobject is None: self.pyobject = rope.base.pyobjects.get_unknown() def get_object(self): return self.pyobject def get_definition_location(self): return (None, None) class AssignmentValue: """An assigned expression""" def __init__( self, ast_node, levels=None, evaluation="", assign_type=False, type_hint=None ): """The `level` is `None` for simple assignments and is a list of numbers for tuple assignments for example in:: a, (b, c) = x The levels for for `a` is ``[0]``, for `b` is ``[1, 0]`` and for `c` is ``[1, 1]``. """ self.ast_node = ast_node if levels is None: self.levels = [] else: self.levels = levels self.evaluation = evaluation self.assign_type = assign_type self.type_hint = type_hint def get_lineno(self): return self.ast_node.lineno class EvaluatedName(PyName): """A name whose object will be evaluated later""" def __init__(self, callback, module=None, lineno=None): self.module = module self.lineno = lineno self.callback = callback self.pyobject = _Inferred(callback, _get_concluded_data(module)) def get_object(self): return self.pyobject.get() def get_definition_location(self): return (self.module, self.lineno) def invalidate(self): """Forget the `PyObject` this `PyName` holds""" self.pyobject.set(None) class ParameterName(PyName): pass class ImportedModule(PyName): def __init__( self, importing_module: Union[ pyobjectsdef.PyModule, pyobjectsdef.PyPackage, ], module_name=None, level=0, resource=None, ): assert ( module_name is not None or resource is not None ), "At least one of module_name or resource must be set" self.importing_module = importing_module self.module_name = module_name self.level = level self.resource = resource self.pymodule = _get_concluded_data(self.importing_module) def _current_folder(self): resource = self.importing_module.get_module().get_resource() if resource is None: return None return resource.parent def _get_pymodule(self): if self.pymodule.get() is None: pycore = self.importing_module.pycore if self.resource is not None: self.pymodule.set(pycore.project.get_pymodule(self.resource)) else: try: if self.level == 0: pymodule = pycore.project.get_module( self.module_name, self._current_folder() ) else: pymodule = pycore.project.get_relative_module( self.module_name, self._current_folder(), self.level ) self.pymodule.set(pymodule) except exceptions.ModuleNotFoundError: pass return self.pymodule.get() def get_object(self): if self._get_pymodule() is None: return rope.base.pyobjects.get_unknown() return self._get_pymodule() def get_definition_location(self): pymodule = self._get_pymodule() if not isinstance(pymodule, rope.base.pyobjects.PyDefinedObject): return (None, None) return (pymodule.get_module(), 1) class ImportedName(PyName): def __init__(self, imported_module, imported_name): self.imported_module = imported_module self.imported_name = imported_name def _get_imported_pyname(self): try: result = self.imported_module.get_object()[self.imported_name] if result != self: return result except exceptions.AttributeNotFoundError: pass return UnboundName() @utils.prevent_recursion(rope.base.pyobjects.get_unknown) def get_object(self): return self._get_imported_pyname().get_object() @utils.prevent_recursion(lambda: (None, None)) def get_definition_location(self): return self._get_imported_pyname().get_definition_location() def _get_concluded_data(module): if module is None: return rope.base.pyobjects._ConcludedData() return module._get_concluded_data() def _circular_inference(): raise rope.base.pyobjects.IsBeingInferredError("Circular Object Inference") class _Inferred: def __init__(self, get_inferred, concluded=None): self.get_inferred = get_inferred self.concluded = concluded if self.concluded is None: self.temp = None @utils.prevent_recursion(_circular_inference) def get(self, *args, **kwds): if self.concluded is None or self.concluded.get() is None: self.set(self.get_inferred(*args, **kwds)) if self._get() is None: self.set(rope.base.pyobjects.get_unknown()) return self._get() def set(self, pyobject): if self.concluded is not None: self.concluded.set(pyobject) self.temp = pyobject def _get(self): if self.concluded is not None: return self.concluded.get() return self.temp ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/rope/base/pynamesdef.py0000664000175000017500000000406214512700666017067 0ustar00lieryanlieryanimport contextlib import rope.base.oi.soi import rope.base.pyobjects from rope.base import pynames, utils class DefinedName(pynames.DefinedName): pass class AssignedName(pynames.AssignedName): def __init__(self, lineno=None, module=None, pyobject=None): self.lineno = lineno self.module = module self.assignments = [] self.pyobject = _Inferred( self._get_inferred, pynames._get_concluded_data(module) ) self.pyobject.set(pyobject) @utils.prevent_recursion(lambda: None) def _get_inferred(self): if self.module is not None: return rope.base.oi.soi.infer_assigned_object(self) def get_object(self): return self.pyobject.get() def get_definition_location(self): """Returns a (module, lineno) tuple""" if self.lineno is None and self.assignments: with contextlib.suppress(AttributeError): self.lineno = self.assignments[0].get_lineno() return (self.module, self.lineno) def invalidate(self): """Forget the `PyObject` this `PyName` holds""" self.pyobject.set(None) class UnboundName(pynames.UnboundName): pass class ParameterName(pynames.ParameterName): def __init__(self, pyfunction, index): self.pyfunction = pyfunction self.index = index def get_object(self): result = self.pyfunction.get_parameter(self.index) if result is None: result = rope.base.pyobjects.get_unknown() return result def get_objects(self): """Returns the list of objects passed as this parameter""" return rope.base.oi.soi.get_passed_objects(self.pyfunction, self.index) def get_definition_location(self): return (self.pyfunction.get_module(), self.pyfunction.get_ast().lineno) class AssignmentValue(pynames.AssignmentValue): pass class EvaluatedName(pynames.EvaluatedName): pass class ImportedModule(pynames.ImportedModule): pass class ImportedName(pynames.ImportedName): pass _Inferred = pynames._Inferred ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1699196919.0 rope-1.12.0/rope/base/pyobjects.py0000664000175000017500000002211614521727767016751 0ustar00lieryanlieryanfrom typing import Optional from rope.base import ast, exceptions, utils class PyObject: def __init__(self, type_): if type_ is None: type_ = self self.type = type_ def get_attributes(self): if self.type is self: return {} return self.type.get_attributes() def get_attribute(self, name): if name not in self.get_attributes(): raise exceptions.AttributeNotFoundError("Attribute %s not found" % name) return self.get_attributes()[name] def get_module(self): return None def get_type(self): return self.type def __getitem__(self, key): """The same as ``get_attribute(key)``""" return self.get_attribute(key) def __contains__(self, key): """The same as ``key in self.get_attributes()``""" return key in self.get_attributes() def __eq__(self, obj): """Check the equality of two `PyObject` Currently it is assumed that instances (the direct instances of `PyObject`, not the instances of its subclasses) are equal if their types are equal. For every other object like defineds or builtins rope assumes objects are reference objects and their identities should match. """ if self.__class__ != obj.__class__: return False if type(self) == PyObject: if self is not self.type: return self.type == obj.type else: return self.type is obj.type return self is obj def __ne__(self, obj): return not self.__eq__(obj) def __hash__(self): """See docs for `__eq__()` method""" if type(self) == PyObject and self != self.type: return hash(self.type) + 1 else: return super().__hash__() def __iter__(self): """The same as ``iter(self.get_attributes())``""" return iter(self.get_attributes()) _types = None _unknown = None @staticmethod def _get_base_type(name): if PyObject._types is None: PyObject._types = {} base_type = PyObject(None) PyObject._types["Type"] = base_type PyObject._types["Module"] = PyObject(base_type) PyObject._types["Function"] = PyObject(base_type) PyObject._types["Unknown"] = PyObject(base_type) return PyObject._types[name] def get_base_type(name): """Return the base type with name `name`. The base types are 'Type', 'Function', 'Module' and 'Unknown'. It was used to check the type of a `PyObject` but currently its use is discouraged. Use classes defined in this module instead. For example instead of ``pyobject.get_type() == get_base_type('Function')`` use ``isinstance(pyobject, AbstractFunction)``. You can use `AbstractClass` for classes, `AbstractFunction` for functions, and `AbstractModule` for modules. You can also use `PyFunction` and `PyClass` for testing if an object is defined somewhere and rope can access its source. These classes provide more methods. """ return PyObject._get_base_type(name) def get_unknown(): """Return a pyobject whose type is unknown Note that two unknown objects are equal. So for example you can write:: if pyname.get_object() == get_unknown(): print('cannot determine what this pyname holds') Rope could have used `None` for indicating unknown objects but we had to check that in many places. So actually this method returns a null object. """ if PyObject._unknown is None: PyObject._unknown = PyObject(get_base_type("Unknown")) return PyObject._unknown class AbstractClass(PyObject): def __init__(self): super().__init__(get_base_type("Type")) def get_name(self): pass def get_doc(self): pass def get_superclasses(self): return [] class AbstractFunction(PyObject): def __init__(self): super().__init__(get_base_type("Function")) def get_name(self): pass def get_doc(self): pass def get_param_names(self, special_args=True): return [] def get_returned_object(self, args): return get_unknown() class AbstractModule(PyObject): def __init__(self, doc=None): super().__init__(get_base_type("Module")) def get_doc(self): pass def get_resource(self): pass class PyDefinedObject: """Python defined names that rope can access their sources""" def __init__(self, pycore, ast_node, parent): self.pycore = pycore self.ast_node = ast_node self.scope = None self.parent = parent self.structural_attributes = None self.concluded_attributes = self.get_module()._get_concluded_data() self.attributes = self.get_module()._get_concluded_data() self.defineds = None def __repr__(self): return '<{}.{} "{}" at {}>'.format( self.__class__.__module__, self.__class__.__name__, self.absolute_name, hex(id(self)), ) @property def absolute_name(self): obj_name = self.get_name() return self.get_module().get_name() + ("::" + obj_name if obj_name else "") visitor_class = None @utils.prevent_recursion(lambda: {}) def _get_structural_attributes(self): if self.structural_attributes is None: self.structural_attributes = self._create_structural_attributes() return self.structural_attributes @utils.prevent_recursion(lambda: {}) def _get_concluded_attributes(self): if self.concluded_attributes.get() is None: self._get_structural_attributes() self.concluded_attributes.set(self._create_concluded_attributes()) return self.concluded_attributes.get() def get_attributes(self): if self.attributes.get() is None: result = dict(self._get_concluded_attributes()) result.update(self._get_structural_attributes()) self.attributes.set(result) return self.attributes.get() def get_attribute(self, name): if name in self._get_structural_attributes(): return self._get_structural_attributes()[name] if name in self._get_concluded_attributes(): return self._get_concluded_attributes()[name] raise exceptions.AttributeNotFoundError("Attribute %s not found" % name) def get_scope(self): if self.scope is None: self.scope = self._create_scope() return self.scope def get_module(self): current_object = self while current_object.parent is not None: current_object = current_object.parent return current_object def get_name(self): return None def get_doc(self) -> Optional[str]: if len(self.get_ast().body) > 0: expr = self.get_ast().body[0] if ( isinstance(expr, ast.Expr) and isinstance(expr.value, ast.Constant) and isinstance(expr.value.value, str) ): return expr.value.value return None def _get_defined_objects(self): if self.defineds is None: self._get_structural_attributes() return self.defineds def _create_structural_attributes(self): if self.visitor_class is None: return {} new_visitor = self.visitor_class(self.pycore, self) for child in ast.iter_child_nodes(self.ast_node): new_visitor.visit(child) self.defineds = new_visitor.defineds return new_visitor.names def _create_concluded_attributes(self): return {} def get_ast(self): return self.ast_node def _create_scope(self): pass class PyFunction(PyDefinedObject, AbstractFunction): pass class PyComprehension(PyDefinedObject, PyObject): pass def get_name(self): return "" class PyClass(PyDefinedObject, AbstractClass): pass class _ConcludedData: def __init__(self): self.data_ = None def set(self, data): self.data_ = data def get(self): return self.data_ data = property(get, set) def _invalidate(self): self.data = None def __str__(self): return "<" + str(self.data) + ">" class _PyModule(PyDefinedObject, AbstractModule): def __init__(self, pycore, ast_node, resource): self.resource = resource self.concluded_data = [] AbstractModule.__init__(self) PyDefinedObject.__init__(self, pycore, ast_node, None) @property def absolute_name(self) -> str: return self.get_name() def _get_concluded_data(self): new_data = _ConcludedData() self.concluded_data.append(new_data) return new_data def _forget_concluded_data(self): for data in self.concluded_data: data._invalidate() def get_resource(self): return self.resource class PyModule(_PyModule): pass class PyPackage(_PyModule): pass class IsBeingInferredError(exceptions.RopeError): pass ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/rope/base/pyobjectsdef.py0000664000175000017500000005403514512700666017422 0ustar00lieryanlieryanimport rope.base.builtins import rope.base.codeanalyze import rope.base.evaluate import rope.base.libutils import rope.base.oi.soi import rope.base.pyscopes from rope.base import ( arguments, ast, exceptions, fscommands, nameanalyze, pynamesdef, pyobjects, utils, ) class PyFunction(pyobjects.PyFunction): def __init__(self, pycore, ast_node, parent): rope.base.pyobjects.AbstractFunction.__init__(self) rope.base.pyobjects.PyDefinedObject.__init__(self, pycore, ast_node, parent) self.arguments = self.ast_node.args self.parameter_pyobjects = pynamesdef._Inferred( self._infer_parameters, self.get_module()._get_concluded_data() ) self.returned = pynamesdef._Inferred(self._infer_returned) self.parameter_pynames = None def _create_structural_attributes(self): return {} def _create_concluded_attributes(self): return {} def _create_scope(self): return rope.base.pyscopes.FunctionScope(self.pycore, self, _FunctionVisitor) def _infer_parameters(self): pyobjects = rope.base.oi.soi.infer_parameter_objects(self) self._handle_special_args(pyobjects) return pyobjects def _infer_returned(self, args=None): return rope.base.oi.soi.infer_returned_object(self, args) def _handle_special_args(self, pyobjects): if len(pyobjects) == len(self.arguments.args): if self.arguments.vararg: pyobjects.append(rope.base.builtins.get_list()) if self.arguments.kwarg: pyobjects.append(rope.base.builtins.get_dict()) def _set_parameter_pyobjects(self, pyobjects): if pyobjects is not None: self._handle_special_args(pyobjects) self.parameter_pyobjects.set(pyobjects) def get_parameters(self): if self.parameter_pynames is None: result = {} for index, name in enumerate(self.get_param_names()): # TODO: handle tuple parameters result[name] = pynamesdef.ParameterName(self, index) self.parameter_pynames = result return self.parameter_pynames def get_parameter(self, index): if index < len(self.parameter_pyobjects.get()): return self.parameter_pyobjects.get()[index] def get_returned_object(self, args): return self.returned.get(args) def get_name(self): return self.get_ast().name def get_param_names(self, special_args=True): # TODO: handle tuple parameters result = [node.arg for node in self.arguments.args if isinstance(node, ast.arg)] if special_args: if self.arguments.vararg: result.append(self.arguments.vararg.arg) if self.arguments.kwarg: result.append(self.arguments.kwarg.arg) return result def get_kind(self): """Get function type It returns one of 'function', 'method', 'staticmethod' or 'classmethod' strs. """ scope = self.parent.get_scope() if isinstance(self.parent, PyClass): for decorator in self.decorators: pyname = rope.base.evaluate.eval_node(scope, decorator) if pyname == rope.base.builtins.builtins["staticmethod"]: return "staticmethod" if pyname == rope.base.builtins.builtins["classmethod"]: return "classmethod" return "method" return "function" @property def decorators(self): try: return self.ast_node.decorator_list except AttributeError: return getattr(self.ast_node, "decorators", None) class PyComprehension(pyobjects.PyComprehension): def __init__(self, pycore, ast_node, parent): self.visitor_class = _ComprehensionVisitor rope.base.pyobjects.PyObject.__init__(self, type_="Comp") rope.base.pyobjects.PyDefinedObject.__init__(self, pycore, ast_node, parent) def _create_scope(self): return rope.base.pyscopes.ComprehensionScope( self.pycore, self, _ComprehensionVisitor ) def get_kind(self): return "Comprehension" class PyClass(pyobjects.PyClass): def __init__(self, pycore, ast_node, parent): self.visitor_class = _ClassVisitor rope.base.pyobjects.AbstractClass.__init__(self) rope.base.pyobjects.PyDefinedObject.__init__(self, pycore, ast_node, parent) self.parent = parent self._superclasses = self.get_module()._get_concluded_data() def get_superclasses(self): if self._superclasses.get() is None: self._superclasses.set(self._get_bases()) return self._superclasses.get() def get_name(self): return self.get_ast().name def _create_concluded_attributes(self): result = {} for base in reversed(self.get_superclasses()): result.update(base.get_attributes()) return result def _get_bases(self): result = [] for base_name in self.ast_node.bases: base = rope.base.evaluate.eval_node(self.parent.get_scope(), base_name) if ( base is not None and base.get_object().get_type() == rope.base.pyobjects.get_base_type("Type") ): result.append(base.get_object()) return result def _create_scope(self): return rope.base.pyscopes.ClassScope(self.pycore, self) class PyModule(pyobjects.PyModule): def __init__(self, pycore, source=None, resource=None, force_errors=False): ignore = pycore.project.prefs.get("ignore_syntax_errors", False) syntax_errors = force_errors or not ignore self.has_errors = False try: source, node = self._init_source(pycore, source, resource) except exceptions.ModuleSyntaxError: self.has_errors = True if syntax_errors: raise else: source = "\n" node = ast.parse("\n") self.source_code = source self.star_imports = [] self.visitor_class = _GlobalVisitor self.coding = fscommands.read_str_coding(self.source_code) super().__init__(pycore, node, resource) def _init_source(self, pycore, source_code, resource): filename = "string" if resource: filename = resource.path try: if source_code is None: source_bytes = resource.read_bytes() source_code, _ = fscommands.file_data_to_unicode(source_bytes) else: if isinstance(source_code, str): source_bytes = fscommands.unicode_to_file_data(source_code) else: source_bytes = source_code ast_node = ast.parse(source_bytes, filename=filename) except SyntaxError as e: raise exceptions.ModuleSyntaxError(filename, e.lineno, e.msg) except UnicodeDecodeError as e: raise exceptions.ModuleSyntaxError(filename, 1, "%s" % (e.reason)) return source_code, ast_node @utils.prevent_recursion(lambda: {}) def _create_concluded_attributes(self): result = {} for star_import in self.star_imports: result.update(star_import.get_names()) return result def _create_scope(self): return rope.base.pyscopes.GlobalScope(self.pycore, self) @property @utils.saveit def lines(self): """A `SourceLinesAdapter`""" return rope.base.codeanalyze.SourceLinesAdapter(self.source_code) @property @utils.saveit def logical_lines(self): """A `LogicalLinesFinder`""" return rope.base.codeanalyze.CachingLogicalLineFinder(self.lines) def get_name(self): return rope.base.libutils.modname(self.resource) if self.resource else "" class PyPackage(pyobjects.PyPackage): def __init__(self, pycore, resource=None, force_errors=False): self.resource = resource init_dot_py = self._get_init_dot_py() if init_dot_py is not None: ast_node = pycore.project.get_pymodule( init_dot_py, force_errors=force_errors ).get_ast() else: ast_node = ast.parse("\n") super().__init__(pycore, ast_node, resource) def _create_structural_attributes(self): result = {} modname = rope.base.libutils.modname(self.resource) extension_submodules = self.pycore._builtin_submodules(modname) for name, module in extension_submodules.items(): result[name] = rope.base.builtins.BuiltinName(module) if self.resource is None: return result for name, resource in self._get_child_resources().items(): result[name] = pynamesdef.ImportedModule(self, resource=resource) return result def _create_concluded_attributes(self): result = {} init_dot_py = self._get_init_dot_py() if init_dot_py: init_object = self.pycore.project.get_pymodule(init_dot_py) result.update(init_object.get_attributes()) return result def _get_child_resources(self): result = {} for child in self.resource.get_children(): if child.is_folder(): result[child.name] = child elif child.name.endswith(".py") and child.name != "__init__.py": name = child.name[:-3] result[name] = child return result def _get_init_dot_py(self): if self.resource is not None and self.resource.has_child("__init__.py"): return self.resource.get_child("__init__.py") else: return None def _create_scope(self): return self.get_module().get_scope() def get_module(self): init_dot_py = self._get_init_dot_py() if init_dot_py: return self.pycore.project.get_pymodule(init_dot_py) return self def get_name(self): return rope.base.libutils.modname(self.resource) if self.resource else "" class _AnnAssignVisitor(ast.RopeNodeVisitor): def __init__(self, scope_visitor): self.scope_visitor = scope_visitor self.assigned_ast = None self.type_hint = None def _AnnAssign(self, node): self.assigned_ast = node.value self.type_hint = node.annotation self.visit(node.target) def _assigned(self, name, assignment=None): self.scope_visitor._assigned(name, assignment) def _Name(self, node): assignment = pynamesdef.AssignmentValue( self.assigned_ast, assign_type=True, type_hint=self.type_hint ) self._assigned(node.id, assignment) def _Tuple(self, node): names = nameanalyze.get_name_levels(node) for name, levels in names: assignment = None if self.assigned_ast is not None: assignment = pynamesdef.AssignmentValue(self.assigned_ast, levels) self._assigned(name, assignment) def _Annotation(self, node): pass def _Attribute(self, node): pass def _Subscript(self, node): pass def _Slice(self, node): pass class _ExpressionVisitor(ast.RopeNodeVisitor): 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): list_comp = PyComprehension( self.scope_visitor.pycore, node, self.scope_visitor.owner_object ) self.scope_visitor.defineds.append(list_comp) def _SetComp(self, node): self._GeneratorExp(node) def _ListComp(self, node): self._GeneratorExp(node) def _DictComp(self, node): self._GeneratorExp(node) def _NamedExpr(self, node): _AssignVisitor(self).visit(node.target) self.visit(node.value) class _AssignVisitor(ast.RopeNodeVisitor): def __init__(self, scope_visitor): self.scope_visitor = scope_visitor self.assigned_ast = None def _Assign(self, node): self.assigned_ast = node.value for child_node in node.targets: self.visit(child_node) _ExpressionVisitor(self.scope_visitor).visit(node.value) def _assigned(self, name, assignment=None): self.scope_visitor._assigned(name, assignment) def _Name(self, node): assignment = None if self.assigned_ast is not None: assignment = pynamesdef.AssignmentValue(self.assigned_ast) self._assigned(node.id, assignment) def _Tuple(self, node): names = nameanalyze.get_name_levels(node) for name, levels in names: assignment = None if self.assigned_ast is not None: assignment = pynamesdef.AssignmentValue(self.assigned_ast, levels) self._assigned(name, assignment) def _Attribute(self, node): pass def _Subscript(self, node): pass def _Slice(self, node): pass class _ScopeVisitor(_ExpressionVisitor): def __init__(self, pycore, owner_object): _ExpressionVisitor.__init__(self, scope_visitor=self) self.pycore = pycore self.owner_object = owner_object self.names = {} self.defineds = [] def get_module(self): if self.owner_object is not None: return self.owner_object.get_module() else: return None def _ClassDef(self, node): pyclass = PyClass(self.pycore, node, self.owner_object) self.names[node.name] = pynamesdef.DefinedName(pyclass) self.defineds.append(pyclass) def _FunctionDef(self, node): pyfunction = PyFunction(self.pycore, node, self.owner_object) for decorator in pyfunction.decorators: if isinstance(decorator, ast.Name) and decorator.id == "property": if isinstance(self, _ClassVisitor): type_ = rope.base.builtins.Property(pyfunction) arg = pynamesdef.UnboundName( rope.base.pyobjects.PyObject(self.owner_object) ) def _eval(type_=type_, arg=arg): return type_.get_property_object( arguments.ObjectArguments([arg]) ) lineno = utils.guess_def_lineno(self.get_module(), node) self.names[node.name] = pynamesdef.EvaluatedName( _eval, module=self.get_module(), lineno=lineno ) break else: self.names[node.name] = pynamesdef.DefinedName(pyfunction) self.defineds.append(pyfunction) def _AsyncFunctionDef(self, node): return self._FunctionDef(node) def _Assign(self, node): _AssignVisitor(self).visit(node) def _AnnAssign(self, node): _AnnAssignVisitor(self).visit(node) def _AugAssign(self, node): pass def _For(self, node): self._update_evaluated(node.target, node.iter, ".__iter__().next()") for child in node.body + node.orelse: self.visit(child) def _AsyncFor(self, node): return self._For(node) def _assigned(self, name, assignment): pyname = self.names.get(name, None) if pyname is None: pyname = pynamesdef.AssignedName(module=self.get_module()) if isinstance(pyname, pynamesdef.AssignedName): if assignment is not None: pyname.assignments.append(assignment) self.names[name] = pyname def _update_evaluated( self, targets, assigned, evaluation="", eval_type=False, type_hint=None ): result = {} if isinstance(targets, str): assignment = pynamesdef.AssignmentValue(assigned, [], evaluation, eval_type) self._assigned(targets, assignment) else: names = nameanalyze.get_name_levels(targets) for name, levels in names: assignment = pynamesdef.AssignmentValue( assigned, levels, evaluation, eval_type ) self._assigned(name, assignment) return result def _With(self, node): for item in node.items: if item.optional_vars: self._update_evaluated( item.optional_vars, item.context_expr, ".__enter__()" ) for child in node.body: self.visit(child) def _AsyncWith(self, node): return self._With(node) def _excepthandler(self, node): node_name_type = str if node.name is not None and isinstance(node.name, node_name_type): type_node = node.type if isinstance(node.type, ast.Tuple) and type_node.elts: type_node = type_node.elts[0] self._update_evaluated(node.name, type_node, eval_type=True) for child in node.body: self.visit(child) def _ExceptHandler(self, node): self._excepthandler(node) def _Import(self, node): for import_pair in node.names: module_name = import_pair.name alias = import_pair.asname first_package = module_name.split(".")[0] if alias is not None: imported = pynamesdef.ImportedModule(self.get_module(), module_name) if not self._is_ignored_import(imported): self.names[alias] = imported else: imported = pynamesdef.ImportedModule(self.get_module(), first_package) if not self._is_ignored_import(imported): self.names[first_package] = imported def _ImportFrom(self, node): level = 0 if node.level: level = node.level imported_module = pynamesdef.ImportedModule( self.get_module(), node.module or "", level, ) if self._is_ignored_import(imported_module): return if len(node.names) == 1 and node.names[0].name == "*": if isinstance(self.owner_object, PyModule): self.owner_object.star_imports.append(StarImport(imported_module)) else: for imported_name in node.names: imported = imported_name.name alias = imported_name.asname if alias is not None: imported = alias self.names[imported] = pynamesdef.ImportedName( imported_module, imported_name.name ) def _is_ignored_import(self, imported_module): if not self.pycore.project.prefs.get("ignore_bad_imports", False): return False return not isinstance( imported_module.get_object(), rope.base.pyobjects.AbstractModule ) def _Global(self, node): module = self.get_module() for name in node.names: if module is not None: try: pyname = module[name] except exceptions.AttributeNotFoundError: pyname = pynamesdef.AssignedName(node.lineno) self.names[name] = pyname class _ComprehensionVisitor(_ScopeVisitor): def _comprehension(self, node): self.visit(node.target) self.visit(node.iter) def _Name(self, node): if isinstance(node.ctx, ast.Store): self.names[node.id] = self._get_pyobject(node) def _get_pyobject(self, node): return pynamesdef.AssignedName(lineno=node.lineno, module=self.get_module()) class _GlobalVisitor(_ScopeVisitor): pass class _ClassVisitor(_ScopeVisitor): def _FunctionDef(self, node): _ScopeVisitor._FunctionDef(self, node) if len(node.args.args) > 0: first = node.args.args[0] new_visitor = None if isinstance(first, ast.arg): new_visitor = _ClassInitVisitor(self, first.arg) if new_visitor is not None: for child in ast.iter_child_nodes(node): new_visitor.visit(child) class _FunctionVisitor(_ScopeVisitor): def __init__(self, pycore, owner_object): super().__init__(pycore, owner_object) self.returned_asts = [] self.generator = False def _Return(self, node): if node.value is not None: self.returned_asts.append(node.value) def _Yield(self, node): if node.value is not None: self.returned_asts.append(node.value) self.generator = True class _ClassInitVisitor(_AssignVisitor): def __init__(self, scope_visitor, self_name): super().__init__(scope_visitor) self.self_name = self_name def _Attribute(self, node): if not isinstance(node.ctx, ast.Store): return if isinstance(node.value, ast.Name) and node.value.id == self.self_name: if node.attr not in self.scope_visitor.names: self.scope_visitor.names[node.attr] = pynamesdef.AssignedName( lineno=node.lineno, module=self.scope_visitor.get_module() ) if self.assigned_ast is not None: pyname = self.scope_visitor.names[node.attr] if isinstance(pyname, pynamesdef.AssignedName): pyname.assignments.append( pynamesdef.AssignmentValue(self.assigned_ast) ) def _Tuple(self, node): if not isinstance(node.ctx, ast.Store): return for child in ast.iter_child_nodes(node): self.visit(child) def _Name(self, node): pass def _FunctionDef(self, node): pass def _ClassDef(self, node): pass def _For(self, node): pass def _With(self, node): pass class StarImport: def __init__(self, imported_module): self.imported_module = imported_module def get_names(self): result = {} imported = self.imported_module.get_object() for name in imported: if not name.startswith("_"): result[name] = pynamesdef.ImportedName(self.imported_module, name) return result ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/rope/base/pyscopes.py0000664000175000017500000002631414512700666016605 0ustar00lieryanlieryanimport rope.base.builtins # Use full qualification for clarity. from rope.base import ast, codeanalyze, exceptions, pynames, utils from rope.refactor import patchedast class Scope: def __init__(self, pycore, pyobject, parent_scope): self.pycore = pycore self.pyobject = pyobject self.parent = parent_scope def get_names(self): """Return the names defined or imported in this scope""" return self.pyobject.get_attributes() def get_defined_names(self): """Return the names defined in this scope""" return self.pyobject._get_structural_attributes() def get_name(self, name): """Return name `PyName` defined in this scope""" if name not in self.get_names(): raise exceptions.NameNotFoundError("name %s not found" % name) return self.get_names()[name] def __getitem__(self, key): """The same as ``get_name(key)``""" return self.get_name(key) def __contains__(self, key): """The same as ``key in self.get_names()``""" return key in self.get_names() @utils.saveit def get_scopes(self): """Return the subscopes of this scope The returned scopes should be sorted by the order they appear. """ return self._create_scopes() def lookup(self, name): if name in self.get_names(): return self.get_names()[name] if self.parent is not None: return self.parent._propagated_lookup(name) return None def get_propagated_names(self): """Return the visible names of this scope Return the names defined in this scope that are visible from scopes containing this scope. This method returns the same dictionary returned by `get_names()` except for `ClassScope` which returns an empty dict. """ return self.get_names() def _propagated_lookup(self, name): if name in self.get_propagated_names(): return self.get_propagated_names()[name] if self.parent is not None: return self.parent._propagated_lookup(name) return None def _create_scopes(self): return [ pydefined.get_scope() for pydefined in self.pyobject._get_defined_objects() ] def _get_global_scope(self): current = self while current.parent is not None: current = current.parent return current def get_start(self): return self.pyobject.get_ast().lineno def get_body_start(self): body = self.pyobject.get_ast().body if body: return body[0].lineno return self.get_start() def get_end(self): pymodule = self._get_global_scope().pyobject return pymodule.logical_lines.logical_line_in(self.logical_end)[1] @utils.saveit def get_logical_end(self): global_scope = self._get_global_scope() return global_scope._scope_finder.find_scope_end(self) start = property(get_start) end = property(get_end) logical_end = property(get_logical_end) def get_kind(self): pass def get_region(self): self._calculate_scope_regions_for_module() node = self.pyobject.get_ast() region = patchedast.node_region(node) return region def _calculate_scope_regions_for_module(self): self._get_global_scope()._calculate_scope_regions() def in_region(self, offset): """Checks if offset is in scope region""" region = self.get_region() return region[0] < offset < region[1] class GlobalScope(Scope): def __init__(self, pycore, module): super().__init__(pycore, module, None) self.names = module._get_concluded_data() def get_start(self): return 1 def get_kind(self): return "Module" def get_name(self, name): try: return self.pyobject[name] except exceptions.AttributeNotFoundError: if name in self.builtin_names: return self.builtin_names[name] raise exceptions.NameNotFoundError("name %s not found" % name) @utils.saveit def _calculate_scope_regions(self): source = self._get_source() patchedast.patch_ast(self.pyobject.get_ast(), source) def _get_source(self): return self.pyobject.source_code def get_names(self): if self.names.get() is None: result = dict(self.builtin_names) result.update(super().get_names()) self.names.set(result) return self.names.get() def get_inner_scope_for_line(self, lineno, indents=None): return self._scope_finder.get_holding_scope(self, lineno, indents) def get_inner_scope_for_offset(self, offset): return self._scope_finder.get_holding_scope_for_offset(self, offset) @property @utils.saveit def _scope_finder(self): return _HoldingScopeFinder(self.pyobject) @property def builtin_names(self): return rope.base.builtins.builtins.get_attributes() class ComprehensionScope(Scope): def __init__(self, pycore, pyobject, visitor): super().__init__(pycore, pyobject, pyobject.parent.get_scope()) self.names = None self.returned_asts = None self.defineds = None self.visitor = visitor def _get_names(self): if self.names is None: self._visit_comprehension() return self.names def get_names(self): return self._get_names() def _visit_comprehension(self): if self.names is None: new_visitor = self.visitor(self.pycore, self.pyobject) for node in ast.iter_child_nodes(self.pyobject.get_ast()): new_visitor.visit(node) self.names = dict(self.parent.get_names()) self.names.update(new_visitor.names) self.defineds = new_visitor.defineds def get_logical_end(self): return self.get_start() logical_end = property(get_logical_end) def get_body_start(self): return self.get_start() class FunctionScope(Scope): def __init__(self, pycore, pyobject, visitor): super().__init__(pycore, pyobject, pyobject.parent.get_scope()) self.names = None self.returned_asts = None self.is_generator = None self.defineds = None self.visitor = visitor def _get_names(self): if self.names is None: self._visit_function() return self.names def _visit_function(self): if self.names is None: new_visitor = self.visitor(self.pycore, self.pyobject) for n in ast.iter_child_nodes(self.pyobject.get_ast()): new_visitor.visit(n) self.names = new_visitor.names self.names.update(self.pyobject.get_parameters()) self.returned_asts = new_visitor.returned_asts self.is_generator = new_visitor.generator self.defineds = new_visitor.defineds def _get_returned_asts(self): if self.names is None: self._visit_function() return self.returned_asts def _is_generator(self): if self.is_generator is None: self._get_returned_asts() return self.is_generator def get_names(self): return self._get_names() def _create_scopes(self): if self.defineds is None: self._visit_function() return [pydefined.get_scope() for pydefined in self.defineds] def get_kind(self): return "Function" def invalidate_data(self): for pyname in self.get_names().values(): if isinstance(pyname, (pynames.AssignedName, pynames.EvaluatedName)): pyname.invalidate() class ClassScope(Scope): def __init__(self, pycore, pyobject): super().__init__(pycore, pyobject, pyobject.parent.get_scope()) def get_kind(self): return "Class" def get_propagated_names(self): return {} class _HoldingScopeFinder: def __init__(self, pymodule): self.pymodule = pymodule def get_indents(self, lineno): return codeanalyze.count_line_indents(self.lines.get_line(lineno)) def _get_scope_indents(self, scope): return self.get_indents(scope.get_start()) def get_holding_scope(self, module_scope, lineno, line_indents=None): if line_indents is None: line_indents = self.get_indents(lineno) current_scope = module_scope new_scope = current_scope while new_scope is not None and ( new_scope.get_kind() == "Module" or self._get_scope_indents(new_scope) <= line_indents ): current_scope = new_scope if ( current_scope.get_start() == lineno and current_scope.get_kind() != "Module" ): return current_scope new_scope = None for scope in current_scope.get_scopes(): if scope.get_start() <= lineno: if lineno <= scope.get_end(): new_scope = scope break else: break return current_scope def _is_empty_line(self, lineno): line = self.lines.get_line(lineno) return line.strip() == "" or line.lstrip().startswith("#") def _get_body_indents(self, scope): return self.get_indents(scope.get_body_start()) @staticmethod def get_holding_scope_for_offset(scope, offset): for inner_scope in scope.get_scopes(): if inner_scope.in_region(offset): return _HoldingScopeFinder.get_holding_scope_for_offset( inner_scope, offset ) return scope def find_scope_end(self, scope): if not scope.parent: return self.lines.length() end = scope.pyobject.get_ast().body[-1].lineno scope_start = self.pymodule.logical_lines.logical_line_in(scope.start) if scope_start[1] >= end: # handling one-liners body_indents = self._get_scope_indents(scope) + 4 else: body_indents = self._get_body_indents(scope) for line_start in self.logical_lines.generate_starts( min(end + 1, self.lines.length()), self.lines.length() + 1 ): if not self._is_empty_line(line_start): if self.get_indents(line_start) < body_indents: return end else: end = line_start return end @property def lines(self): return self.pymodule.lines @property def code(self): return self.pymodule.source_code @property def logical_lines(self): return self.pymodule.logical_lines class TemporaryScope(Scope): """Currently used for list comprehensions and generator expressions These scopes do not appear in the `get_scopes()` method of their parent scopes. """ def __init__(self, pycore, parent_scope, names): super().__init__(pycore, parent_scope.pyobject, parent_scope) self.names = names def get_names(self): return self.names def get_defined_names(self): return self.names def _create_scopes(self): return [] def get_kind(self): return "Temporary" ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/rope/base/resourceobserver.py0000664000175000017500000002406314512700666020336 0ustar00lieryanlieryanimport os class ResourceObserver: """Provides the interface for observing resources `ResourceObserver` can be registered using `Project. add_observer()`. But most of the time `FilteredResourceObserver` should be used. `ResourceObserver` report all changes passed to them and they don't report changes to all resources. For example if a folder is removed, it only calls `removed()` for that folder and not its contents. You can use `FilteredResourceObserver` if you are interested in changes only to a list of resources. And you want changes to be reported on individual resources. """ def __init__( self, changed=None, moved=None, created=None, removed=None, validate=None ): self.changed = changed self.moved = moved self.created = created self.removed = removed self._validate = validate def resource_changed(self, resource): """It is called when the resource changes""" if self.changed is not None: self.changed(resource) def resource_moved(self, resource, new_resource): """It is called when a resource is moved""" if self.moved is not None: self.moved(resource, new_resource) def resource_created(self, resource): """Is called when a new resource is created""" if self.created is not None: self.created(resource) def resource_removed(self, resource): """Is called when a new resource is removed""" if self.removed is not None: self.removed(resource) def validate(self, resource): """Validate the existence of this resource and its children. This function is called when rope need to update its resource cache about the files that might have been changed or removed by other processes. """ if self._validate is not None: self._validate(resource) class FilteredResourceObserver: """A useful decorator for `ResourceObserver` Most resource observers have a list of resources and are interested only in changes to those files. This class satisfies this need. It dispatches resource changed and removed messages. It performs these tasks: * Changes to files and folders are analyzed to check whether any of the interesting resources are changed or not. If they are, it reports these changes to `resource_observer` passed to the constructor. * When a resource is removed it checks whether any of the interesting resources are contained in that folder and reports them to `resource_observer`. * When validating a folder it validates all of the interesting files in that folder. Since most resource observers are interested in a list of resources that change over time, `add_resource` and `remove_resource` might be useful. """ def __init__(self, resource_observer, initial_resources=None, timekeeper=None): self.observer = resource_observer self.resources = {} if timekeeper is not None: self.timekeeper = timekeeper else: self.timekeeper = ChangeIndicator() if initial_resources is not None: for resource in initial_resources: self.add_resource(resource) def add_resource(self, resource): """Add a resource to the list of interesting resources""" if resource.exists(): self.resources[resource] = self.timekeeper.get_indicator(resource) else: self.resources[resource] = None def remove_resource(self, resource): """Add a resource to the list of interesting resources""" if resource in self.resources: del self.resources[resource] def clear_resources(self): """Removes all registered resources""" self.resources.clear() def resource_changed(self, resource): changes = _Changes() self._update_changes_caused_by_changed(changes, resource) self._perform_changes(changes) def _update_changes_caused_by_changed(self, changes, changed): if changed in self.resources: changes.add_changed(changed) if self._is_parent_changed(changed): changes.add_changed(changed.parent) def _update_changes_caused_by_moved(self, changes, resource, new_resource=None): if resource in self.resources: changes.add_removed(resource, new_resource) if new_resource in self.resources: changes.add_created(new_resource) if resource.is_folder(): for file in list(self.resources): if resource.contains(file): new_file = self._calculate_new_resource( resource, new_resource, file ) changes.add_removed(file, new_file) if self._is_parent_changed(resource): changes.add_changed(resource.parent) if new_resource is not None: if self._is_parent_changed(new_resource): changes.add_changed(new_resource.parent) def _is_parent_changed(self, child): return child.parent in self.resources def resource_moved(self, resource, new_resource): changes = _Changes() self._update_changes_caused_by_moved(changes, resource, new_resource) self._perform_changes(changes) def resource_created(self, resource): changes = _Changes() self._update_changes_caused_by_created(changes, resource) self._perform_changes(changes) def _update_changes_caused_by_created(self, changes, resource): if resource in self.resources: changes.add_created(resource) if self._is_parent_changed(resource): changes.add_changed(resource.parent) def resource_removed(self, resource): changes = _Changes() self._update_changes_caused_by_moved(changes, resource) self._perform_changes(changes) def _perform_changes(self, changes): for resource in changes.changes: self.observer.resource_changed(resource) self.resources[resource] = self.timekeeper.get_indicator(resource) for resource, new_resource in changes.moves.items(): self.resources[resource] = None if new_resource is not None: self.observer.resource_moved(resource, new_resource) else: self.observer.resource_removed(resource) for resource in changes.creations: self.observer.resource_created(resource) self.resources[resource] = self.timekeeper.get_indicator(resource) def validate(self, resource): changes = _Changes() for file in self._search_resource_moves(resource): if file in self.resources: self._update_changes_caused_by_moved(changes, file) for file in self._search_resource_changes(resource): if file in self.resources: self._update_changes_caused_by_changed(changes, file) for file in self._search_resource_creations(resource): if file in self.resources: changes.add_created(file) self._perform_changes(changes) def _search_resource_creations(self, resource): creations = set() if ( resource in self.resources and resource.exists() and self.resources[resource] is None ): creations.add(resource) if resource.is_folder(): for file in self.resources: if ( file.exists() and resource.contains(file) and self.resources[file] is None ): creations.add(file) return creations def _search_resource_moves(self, resource): all_moved = set() if resource in self.resources and not resource.exists(): all_moved.add(resource) if resource.is_folder(): for file in self.resources: if resource.contains(file): if not file.exists(): all_moved.add(file) moved = set(all_moved) for folder in [file for file in all_moved if file.is_folder()]: if folder in moved: for file in list(moved): if folder.contains(file): moved.remove(file) return moved def _search_resource_changes(self, resource): changed = set() if resource in self.resources and self._is_changed(resource): changed.add(resource) if resource.is_folder(): for file in self.resources: if file.exists() and resource.contains(file): if self._is_changed(file): changed.add(file) return changed def _is_changed(self, resource): if self.resources[resource] is None: return False return self.resources[resource] != self.timekeeper.get_indicator(resource) def _calculate_new_resource(self, main, new_main, resource): if new_main is None: return None diff = resource.path[len(main.path) :] return resource.project.get_resource(new_main.path + diff) class ChangeIndicator: def get_indicator(self, resource): """Return the modification time and size of a `Resource`.""" path = resource.real_path # on dos, mtime does not change for a folder when files are added if os.name != "posix" and os.path.isdir(path): return ( os.path.getmtime(path), len(os.listdir(path)), os.path.getsize(path), ) return (os.path.getmtime(path), os.path.getsize(path)) class _Changes: def __init__(self): self.changes = set() self.creations = set() self.moves = {} def add_changed(self, resource): self.changes.add(resource) def add_removed(self, resource, new_resource=None): self.moves[resource] = new_resource def add_created(self, resource): self.creations.add(resource) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1705551350.0 rope-1.12.0/rope/base/resources.py0000664000175000017500000001740414552122766016755 0ustar00lieryanlieryan"""Files and folders in a project are represented as resource objects. Files and folders are access through `Resource` objects. `Resource` has two subclasses: `File` and `Folder`. What we care about is that refactorings and `rope.base.change.Change`s use resources. There are two options to create a `Resource` for a path in a project. Note that in these examples `path` is the path to a file or folder relative to the project's root. A project's root folder is represented by an empty string. 1) Use the `rope.base.Project.get_resource()` method. E.g.: myresource = myproject.get_resource(path) 2) Use the `rope.base.libutils` module. `libutils` has a function named `path_to_resource()`. It takes a project and a path: from rope.base import libutils myresource = libutils.path_to_resource(myproject, path) Once we have a `Resource`, we can retrieve information from it, like getting the path relative to the project's root (via `path`), reading from and writing to the resource, moving the resource, etc. """ import os import re import warnings from pathlib import Path from rope.base import change, exceptions, fscommands class Resource(os.PathLike): """Represents files and folders in a project""" def __init__(self, project, path): self.project = project self._path = path def __repr__(self): return '<{}.{} "{}" at {}>'.format( self.__class__.__module__, self.__class__.__name__, self.path, hex(id(self)), ) def __fspath__(self) -> str: """Return the file system path of this resource""" return self.project._get_resource_path(self.path) def move(self, new_location): """Move resource to `new_location`""" self._perform_change( change.MoveResource(self, new_location), f"Moving <{self.path}> to <{new_location}>", ) def remove(self): """Remove resource from the project""" self._perform_change(change.RemoveResource(self), "Removing <%s>" % self.path) def is_dir(self): """Alias for `is_folder()`""" def is_folder(self): """Return True if the resource is a Folder""" def create(self): """Create this resource""" def exists(self): return os.path.exists(self) @property def parent(self): parent = "/".join(self.path.split("/")[0:-1]) return self.project.get_folder(parent) @property def path(self) -> str: """Return the path of this resource relative to the project root The path is the list of parent directories separated by '/' followed by the resource name. """ return self._path @property def name(self) -> str: """Return the name of this resource""" return self.path.split("/")[-1] @property def real_path(self) -> str: return os.fspath(self) @property def pathlib(self) -> Path: """Return the file as a pathlib path.""" return Path(self) def __eq__(self, obj): return self.__class__ == obj.__class__ and self.path == obj.path def __ne__(self, obj): return not self.__eq__(obj) def __hash__(self): return hash(self.path) def _perform_change(self, change_, description): changes = change.ChangeSet(description) changes.add_change(change_) self.project.do(changes) class File(Resource): """Represents a file""" def __init__(self, project, name): self.newlines = None super().__init__(project, name) def read(self): data = self.read_bytes() try: content, self.newlines = fscommands.file_data_to_unicode(data) return content except UnicodeDecodeError as e: raise exceptions.ModuleDecodeError(self.path, e.reason) def read_bytes(self): if not hasattr(self.project.fscommands, "read"): warnings.warn( "FileSystemCommands should implement read() method", DeprecationWarning, stacklevel=2, ) with open(self, "rb") as handle: return handle.read() return self.project.fscommands.read(self.real_path) def write(self, contents): try: if contents == self.read(): return except OSError: pass self._perform_change( change.ChangeContents(self, contents), "Writing file <%s>" % self.path ) def is_folder(self): return False def create(self): self.parent.create_file(self.name) class Folder(Resource): """Represents a folder""" def is_folder(self): return True def get_children(self): """Return the children of this folder""" try: children = os.listdir(self) except OSError: return [] result = [] for name in children: try: child = self.get_child(name) except exceptions.ResourceNotFoundError: continue if not self.project.is_ignored(child): result.append(self.get_child(name)) return result def create_file(self, file_name): self._perform_change( change.CreateFile(self, file_name), "Creating file <%s>" % self._get_child_path(file_name), ) return self.get_child(file_name) def create_folder(self, folder_name): self._perform_change( change.CreateFolder(self, folder_name), "Creating folder <%s>" % self._get_child_path(folder_name), ) return self.get_child(folder_name) def _get_child_path(self, name): if self.path: return self.path + "/" + name else: return name def get_child(self, name): return self.project.get_resource(self._get_child_path(name)) def has_child(self, name): try: self.get_child(name) return True except exceptions.ResourceNotFoundError: return False def get_files(self): return [ resource for resource in self.get_children() if not resource.is_folder() ] def get_folders(self): return [resource for resource in self.get_children() if resource.is_folder()] def contains(self, resource): if self == resource: return False return self.path == "" or resource.path.startswith(self.path + "/") def create(self): self.parent.create_folder(self.name) class _ResourceMatcher: def __init__(self): self.patterns = [] self._compiled_patterns = [] def set_patterns(self, patterns): """Specify which resources to match `patterns` is a `list` of `str` that can contain ``*`` and ``?`` signs for matching resource names. """ self._compiled_patterns = None self.patterns = patterns def _add_pattern(self, pattern): re_pattern = ( pattern.replace(".", "\\.") .replace("*", "[^/]*") .replace("?", "[^/]") .replace("//", "/(.*/)?") ) re_pattern = "^(.*/)?" + re_pattern + "(/.*)?$" self.compiled_patterns.append(re.compile(re_pattern)) def does_match(self, resource): for pattern in self.compiled_patterns: if pattern.match(resource.path): return True path = os.path.join(resource.project.address, *resource.path.split("/")) return os.path.islink(path) @property def compiled_patterns(self): if self._compiled_patterns is None: self._compiled_patterns = [] for pattern in self.patterns: self._add_pattern(pattern) return self._compiled_patterns ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/rope/base/serializer.py0000664000175000017500000001272614512700666017113 0ustar00lieryanlieryan""" This module serves to convert a data structure composed of Python primitives (dict, list, tuple, int, str, None) to JSON-serializable primitives (object, array, number, str, null). A core feature of this serializer is that the produced will round-trip to identical objects when deserialized by the standard library json module. In other words, this property always holds: >>> original_data = ... any JSON ... >>> encoded = python_to_json(original_data) >>> serialized = json.dumps(encoded) >>> decoded = json.loads(serialized) >>> rehydrated_data = json_to_python(decoded) >>> assert rehydrated_data == original_data >>> assert encoded == decoded Couple challenges in straight serialization that this module helps resolve: - json.dumps() maps both Python list and tuple to JSON array. This module provides two variants: - In version=1, this module converts Python list `[1, 2, 3]` as-is and converts Python tuple `(1, 2, 3)` to special object construct `{"$": "t", "items": [1, 2, 3]}` - In version=2, it is the other way around, this module converts Python tuple `(1, 2, 3)` as-is and converts Python list `[1, 2, 3]` to special object construct `{"$": "l", "items": [1, 2, 3]}` - Python dict keys can be a tuple/dict, but JSON Object keys must be strings This module replaces all `dict` keys with `refid` which can be resolved using the `encoded["references"][refid]` lookup table. Except there's a small optimisation, if the dict key is a string that isn't only numeric, which is encoded directly into the object. - Python dict keys cannot be another dict because it is unhashable, therefore there's no encoding for having objects as keys either. - There is currently no support for floating point numbers. Note that `json_to_python` only accepts Python objects that can be the output of `python_to_json`, there is NO guarantee for going the other way around. This may or may not work: >>> python_to_json(json_to_python(original_data)) == original_data """ def python_to_json(o, version=1): if version not in (1, 2): raise ValueError(f"Unexpected version {version}") references = [] result = { "v": version, "data": _py2js(o, references, version=version), "references": references, } if not result["references"]: del result["references"] return result def json_to_python(o): version = o["v"] if version not in (1, 2): raise ValueError(f"Unexpected version {version}") references = o.get("references", {}) data = _js2py(o["data"], references, version) return data def _py2js(o, references, version): if isinstance(o, (str, int)) or o is None: return o elif isinstance(o, tuple): if version == 1: return { "$": "t", "items": [_py2js(item, references, version) for item in o], } else: return [_py2js(item, references, version) for item in o] elif isinstance(o, list): if version == 2: return { "$": "l", "items": [_py2js(item, references, version) for item in o], } else: return [_py2js(item, references, version) for item in o] elif isinstance(o, dict): result = {} for pykey, pyvalue in o.items(): if pykey == "$": raise ValueError('dict cannot contain reserved key "$"') if isinstance(pykey, str) and not pykey.isdigit(): result[pykey] = _py2js(pyvalue, references, version) else: assert isinstance(pykey, (str, int, tuple)) or pykey is None assert not isinstance(pykey, list) refid = len(references) references.append(_py2js(pykey, references, version)) result[str(refid)] = _py2js(pyvalue, references, version) return result raise TypeError(f"Object of type {type(o)} is not allowed {o}") def _js2py(o, references, version): if isinstance(o, (str, int)) or o is None: return o elif isinstance(o, list): if version == 1: return list(_js2py(item, references, version) for item in o) elif version == 2: return tuple(_js2py(item, references, version) for item in o) raise ValueError(f"Unexpected version {version}") elif isinstance(o, dict): result = {} if "$" in o: if o["$"] == "t": assert version == 1 data = o["items"] return tuple(_js2py(item, references, version) for item in data) elif o["$"] == "l": assert version == 2 data = o["items"] return list(_js2py(item, references, version) for item in data) raise TypeError(f'Unrecognized object of type: {o["$"]} {o}') else: for refid, jsvalue in o.items(): assert isinstance(refid, str) if refid.isdigit(): refid = int(refid) assert 0 <= refid < len(references) jskey = references[refid] pyvalue = _js2py(jsvalue, references, version) pykey = _js2py(jskey, references, version) result[pykey] = pyvalue else: result[refid] = _js2py(jsvalue, references, version) return result raise TypeError(f'Object of type "{type(o).__name__}" is not allowed {o}') ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/rope/base/simplify.py0000664000175000017500000000356114512700666016573 0ustar00lieryanlieryan"""A module to ease code analysis This module is here to help source code analysis. """ import re from rope.base import codeanalyze, utils @utils.cached(7) def real_code(source): """Simplify `source` for analysis It replaces: * comments with spaces * strs with a new str filled with spaces * implicit and explicit continuations with spaces * tabs and semicolons with spaces The resulting code is a lot easier to analyze if we are interested only in offsets. """ collector = codeanalyze.ChangeCollector(source) for start, end, matchgroups in ignored_regions(source): if source[start] == "#": replacement = " " * (end - start) elif "f" in matchgroups.get("prefix", "").lower(): replacement = None else: replacement = '"%s"' % (" " * (end - start - 2)) if replacement is not None: collector.add_change(start, end, replacement) source = collector.get_changed() or source collector = codeanalyze.ChangeCollector(source) parens = 0 for match in _parens.finditer(source): i = match.start() c = match.group() if c in "({[": parens += 1 if c in ")}]": parens -= 1 if c == "\n" and parens > 0: collector.add_change(i, i + 1, " ") source = collector.get_changed() or source return source.replace("\\\n", " ").replace("\t", " ").replace(";", "\n") @utils.cached(7) def ignored_regions(source): """Return ignored regions like strings and comments in `source`""" return [ (match.start(), match.end(), match.groupdict()) for match in _str.finditer(source) ] _str = re.compile( "|".join( [ codeanalyze.get_comment_pattern(), codeanalyze.get_any_string_pattern(), ] ) ) _parens = re.compile(r"[\({\[\]}\)\n]") ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/rope/base/stdmods.py0000664000175000017500000000310614512700666016407 0ustar00lieryanlieryanimport inspect import os import sys from rope.base import utils def _stdlib_path(): return os.path.dirname(inspect.getsourcefile(inspect)) @utils.cached(1) def standard_modules(): return python_modules() | dynload_modules() @utils.cached(1) def python_modules(): result = set() lib_path = _stdlib_path() if os.path.exists(lib_path): for name in os.listdir(lib_path): path = os.path.join(lib_path, name) if os.path.isdir(path): if "-" not in name: result.add(name) else: if name.endswith(".py"): result.add(name[:-3]) return result def normalize_so_name(name): """ Handle different types of python installations """ if "cpython" in name: return os.path.splitext(os.path.splitext(name)[0])[0] # XXX: Special handling for Fedora python2 distribution # See: https://github.com/python-rope/rope/issues/211 if name == "timemodule.so": return "time" return os.path.splitext(name)[0] @utils.cached(1) def dynload_modules(): result = set(sys.builtin_module_names) dynload_path = os.path.join(_stdlib_path(), "lib-dynload") if os.path.exists(dynload_path): for name in os.listdir(dynload_path): path = os.path.join(dynload_path, name) if os.path.isfile(path): if name.endswith(".dll"): result.add(normalize_so_name(name)) if name.endswith(".so"): result.add(normalize_so_name(name)) return result ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/rope/base/taskhandle.py0000664000175000017500000001145314512700666017054 0ustar00lieryanlieryanfrom abc import ABC, abstractmethod from typing import Optional, Sequence from rope.base import exceptions, utils class BaseJobSet(ABC): name: str = "" job_name: str = "" @abstractmethod def started_job(self, name: str) -> None: pass @abstractmethod def finished_job(self) -> None: pass @abstractmethod def check_status(self) -> None: pass @utils.deprecated("Just use JobSet.job_name attribute/property instead") def get_active_job_name(self) -> str: pass @abstractmethod def get_percent_done(self) -> Optional[float]: pass @utils.deprecated("Just use JobSet.name attribute/property instead") def get_name(self) -> str: pass @abstractmethod def increment(self) -> None: """ Increment the number of tasks to complete. This is used if the number is not known ahead of time. """ pass class BaseTaskHandle(ABC): @abstractmethod def stop(self) -> None: pass @abstractmethod def current_jobset(self) -> Optional[BaseJobSet]: pass @abstractmethod def add_observer(self) -> None: pass @abstractmethod def is_stopped(self) -> bool: pass @abstractmethod def get_jobsets(self) -> Sequence[BaseJobSet]: pass def create_jobset( self, name: str = "JobSet", count: Optional[int] = None ) -> BaseJobSet: pass def _inform_observers(self) -> None: pass class TaskHandle(BaseTaskHandle): def __init__(self, name="Task", interrupts=True): """Construct a TaskHandle If `interrupts` is `False` the task won't be interrupted by calling `TaskHandle.stop()`. """ self.name = name self.interrupts = interrupts self.stopped = False self.job_sets = [] self.observers = [] def stop(self): """Interrupts the refactoring""" if self.interrupts: self.stopped = True self._inform_observers() def current_jobset(self): """Return the current `JobSet`""" if self.job_sets: return self.job_sets[-1] def add_observer(self, observer): """Register an observer for this task handle The observer is notified whenever the task is stopped or a job gets finished. """ self.observers.append(observer) def is_stopped(self): return self.stopped def get_jobsets(self): return self.job_sets def create_jobset(self, name="JobSet", count=None): result = JobSet(self, name=name, count=count) self.job_sets.append(result) self._inform_observers() return result def _inform_observers(self): for observer in list(self.observers): observer() class JobSet(BaseJobSet): def __init__(self, handle, name, count): self.handle = handle self.name = name self.count = count self.done = 0 self.job_name = None def started_job(self, name): self.check_status() self.job_name = name self.handle._inform_observers() def finished_job(self): self.check_status() self.done += 1 self.handle._inform_observers() self.job_name = None def check_status(self): if self.handle.is_stopped(): raise exceptions.InterruptedTaskError() @utils.deprecated("Just use JobSet.job_name attribute/property instead") def get_active_job_name(self): return self.job_name def get_percent_done(self): if self.count is not None and self.count > 0: percent = self.done * 100 // self.count return min(percent, 100) @utils.deprecated("Just use JobSet.name attribute/property instead") def get_name(self): return self.name def increment(self): self.count += 1 class NullTaskHandle(BaseTaskHandle): def is_stopped(self): return False def stop(self): pass def create_jobset(self, *args, **kwds): return NullJobSet() def get_jobsets(self): return [] def add_observer(self, observer): pass def current_jobset(self) -> None: """Return the current `JobSet`""" return None class NullJobSet(BaseJobSet): def started_job(self, name): pass def finished_job(self): pass def check_status(self): pass @utils.deprecated("Just use JobSet.job_name attribute/property instead") def get_active_job_name(self): pass def get_percent_done(self): pass @utils.deprecated("Just use JobSet.name attribute/property instead") def get_name(self): pass def increment(self): pass DEFAULT_TASK_HANDLE = NullTaskHandle() DEFAULT_JOB_SET = NullJobSet() ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1705553002.685983 rope-1.12.0/rope/base/utils/0000775000175000017500000000000014552126153015515 5ustar00lieryanlieryan././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/rope/base/utils/__init__.py0000664000175000017500000000755514512700666017645 0ustar00lieryanlieryanimport sys import warnings def saveit(func): """A decorator that caches the return value of a function""" name = "_" + func.__name__ def _wrapper(self, *args, **kwds): if not hasattr(self, name): setattr(self, name, func(self, *args, **kwds)) return getattr(self, name) return _wrapper cacheit = saveit def prevent_recursion(default): """A decorator that returns the return value of `default` in recursions""" def decorator(func): name = "_calling_%s_" % func.__name__ def newfunc(self, *args, **kwds): if getattr(self, name, False): return default() setattr(self, name, True) try: return func(self, *args, **kwds) finally: setattr(self, name, False) return newfunc return decorator def ignore_exception(exception_class): """A decorator that ignores `exception_class` exceptions""" def _decorator(func): def newfunc(*args, **kwds): try: return func(*args, **kwds) except exception_class: pass return newfunc return _decorator def deprecated(message=None): """A decorator for deprecated functions""" def _decorator(func, message=message): if message is None: message = "%s is deprecated" % func.__name__ def newfunc(*args, **kwds): warnings.warn(message, DeprecationWarning, stacklevel=2) return func(*args, **kwds) return newfunc return _decorator def cached(size): """A caching decorator based on parameter objects""" def decorator(func): cached_func = _Cached(func, size) return lambda *a, **kw: cached_func(*a, **kw) return decorator class _Cached: def __init__(self, func, count): self.func = func self.cache = [] self.count = count def __call__(self, *args, **kwds): key = (args, kwds) for cached_key, cached_result in self.cache: if cached_key == key: return cached_result result = self.func(*args, **kwds) self.cache.append((key, result)) if len(self.cache) > self.count: del self.cache[0] return result def resolve(str_or_obj): """Returns object from string""" if not isinstance(str_or_obj, str): return str_or_obj if "." not in str_or_obj: str_or_obj += "." mod_name, obj_name = str_or_obj.rsplit(".", 1) __import__(mod_name) mod = sys.modules[mod_name] return getattr(mod, obj_name) if obj_name else mod def guess_def_lineno(module, node): """Find the line number for a function or class definition. `node` may be either an ast.FunctionDef, ast.AsyncFunctionDef, or ast.ClassDef Python 3.8 simply provides this to us, but in earlier versions the ast node.lineno points to the first decorator rather than the actual definition, so we try our best to find where the definitions are. This is to workaround bpo-33211 (https://bugs.python.org/issue33211) """ def is_inline_body(): # class Foo(object): # def inline_body(): pass # ^ ^--- body_col_offset # `--- indent_col_offset # def not_inline_body(): # pass # ^--- body_col_offset == indent_col_offset line = module.lines.get_line(node.body[0].lineno) indent_col_offset = len(line) - len(line.lstrip()) body_col_offset = node.body[0].col_offset return indent_col_offset < body_col_offset if sys.version_info >= (3, 8) or not hasattr(node, "body"): return node.lineno possible_def_line = ( node.body[0].lineno if is_inline_body() else node.body[0].lineno - 1 ) return module.logical_lines.logical_line_in(possible_def_line)[0] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/rope/base/utils/datastructures.py0000664000175000017500000000344014512700666021150 0ustar00lieryanlieryan# this snippet was taken from this link # http://code.activestate.com/recipes/576694/ from collections.abc import MutableSet class OrderedSet(MutableSet): def __init__(self, iterable=None): self.end = end = [] end += [None, end, end] # sentinel # node for doubly linked list self.map = {} # key --> [key, prev, next] if iterable is not None: self |= iterable def __len__(self): return len(self.map) def __contains__(self, key): return key in self.map def add(self, key): if key not in self.map: end = self.end curr = end[1] curr[2] = end[1] = self.map[key] = [key, curr, end] def intersection(self, set_b): return OrderedSet([item for item in self if item in set_b]) def discard(self, key): if key in self.map: key, prev, next = self.map.pop(key) prev[2] = next next[1] = prev def __iter__(self): end = self.end curr = end[2] while curr is not end: yield curr[0] curr = curr[2] def __reversed__(self): end = self.end curr = end[1] while curr is not end: yield curr[0] curr = curr[1] def pop(self, last=True): if not self: raise KeyError("set is empty") key = self.end[1][0] if last else self.end[2][0] self.discard(key) return key def __repr__(self): if not self: return f"{self.__class__.__name__}()" return f"{self.__class__.__name__}({list(self)!r})" def __eq__(self, other): if isinstance(other, OrderedSet): return len(self) == len(other) and list(self) == list(other) return set(self) == set(other) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/rope/base/versioning.py0000664000175000017500000000257514512700666017126 0ustar00lieryanlieryanimport hashlib import importlib.util import json from typing import Dict import rope.base.project def get_version_hash_data(project: rope.base.project.Project) -> Dict[str, str]: version_hash_data = dict( version_data=f"{rope.VERSION}", prefs_data=_get_prefs_data(project), schema_file_content=_get_file_content("rope.contrib.autoimport.models"), ) return version_hash_data def calculate_version_hash(project: rope.base.project.Project) -> str: def _merge(hasher, name: str, serialized_data: str): hashed_data = hashlib.sha256(serialized_data.encode("utf-8")).hexdigest() hasher.update(hashed_data.encode("ascii")) hasher = hashlib.sha256() for name, data in get_version_hash_data(project).items(): _merge(hasher, name, data) return hasher.hexdigest() def _get_prefs_data(project) -> str: prefs_data = dict(vars(project.prefs)) del prefs_data["project_opened"] del prefs_data["callbacks"] del prefs_data["dependencies"] return json.dumps(prefs_data, sort_keys=True, indent=2) def _get_file_content(module_name: str) -> str: models_module = importlib.util.find_spec(module_name) assert models_module and models_module.loader assert isinstance(models_module.loader, importlib.machinery.SourceFileLoader) src = models_module.loader.get_source(module_name) assert src return src ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/rope/base/worder.py0000664000175000017500000005362014512700666016242 0ustar00lieryanlieryanimport bisect import keyword import rope.base.simplify MINIMAL_LEN_FOR_AS = 5 def get_name_at(resource, offset): source_code = resource.read() word_finder = Worder(source_code) return word_finder.get_word_at(offset) class Worder: """A class for finding boundaries of words and expressions 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): simplified = rope.base.simplify.real_code(code) self.code_finder = _RealFinder(simplified, code) self.handle_ignores = handle_ignores self.code = code def _init_ignores(self): ignores = rope.base.simplify.ignored_regions(self.code) self.dumb_finder = _RealFinder(self.code, self.code) self.starts = [ignored[0] for ignored in ignores] self.ends = [ignored[1] for ignored in ignores] def _context_call(self, name, offset): if self.handle_ignores: if not hasattr(self, "starts"): self._init_ignores() start = bisect.bisect(self.starts, offset) if start > 0 and offset < self.ends[start - 1]: return getattr(self.dumb_finder, name)(offset) return getattr(self.code_finder, name)(offset) def get_primary_at(self, offset): return self._context_call("get_primary_at", offset) def get_word_at(self, offset): return self._context_call("get_word_at", offset) def get_primary_range(self, offset): return self._context_call("get_primary_range", offset) def get_splitted_primary_before(self, offset): return self._context_call("get_splitted_primary_before", offset) def get_word_range(self, offset): return self._context_call("get_word_range", offset) def is_function_keyword_parameter(self, offset): return self.code_finder.is_function_keyword_parameter(offset) def is_a_class_or_function_name_in_header(self, offset): return self.code_finder.is_a_class_or_function_name_in_header(offset) def is_from_statement_module(self, offset): return self.code_finder.is_from_statement_module(offset) def is_from_aliased(self, offset): return self.code_finder.is_from_aliased(offset) def is_import_statement_aliased_module(self, offset): return self.code_finder.is_import_statement_aliased_module(offset) def find_parens_start_from_inside(self, offset): return self.code_finder.find_parens_start_from_inside(offset) def is_a_name_after_from_import(self, offset): return self.code_finder.is_a_name_after_from_import(offset) def is_from_statement(self, offset): return self.code_finder.is_from_statement(offset) def get_from_aliased(self, offset): return self.code_finder.get_from_aliased(offset) def is_import_statement(self, offset): return self.code_finder.is_import_statement(offset) def is_assigned_here(self, offset): return self.code_finder.is_assigned_here(offset) def is_a_function_being_called(self, offset): return self.code_finder.is_a_function_being_called(offset) def get_word_parens_range(self, offset): return self.code_finder.get_word_parens_range(offset) def is_name_assigned_in_class_body(self, offset): return self.code_finder.is_name_assigned_in_class_body(offset) def is_on_function_call_keyword(self, offset): return self.code_finder.is_on_function_call_keyword(offset) def _find_parens_start(self, offset): return self.code_finder._find_parens_start(offset) def get_parameters(self, first, last): return self.code_finder.get_parameters(first, last) def get_from_module(self, offset): return self.code_finder.get_from_module(offset) def is_assigned_in_a_tuple_assignment(self, offset): return self.code_finder.is_assigned_in_a_tuple_assignment(offset) def get_assignment_type(self, offset): return self.code_finder.get_assignment_type(offset) def get_function_and_args_in_header(self, offset): return self.code_finder.get_function_and_args_in_header(offset) def get_lambda_and_args(self, offset): return self.code_finder.get_lambda_and_args(offset) def find_function_offset(self, offset): return self.code_finder.find_function_offset(offset) class _RealFinder: def __init__(self, code, raw): self.code = code self.raw = raw def _find_word_start(self, offset): current_offset = offset while current_offset >= 0 and self._is_id_char(current_offset): current_offset -= 1 return current_offset + 1 def _find_word_end(self, offset): while offset + 1 < len(self.code) and self._is_id_char(offset + 1): offset += 1 return offset def _find_last_non_space_char(self, offset): while offset >= 0 and self.code[offset].isspace(): if self.code[offset] == "\n": return offset offset -= 1 return max(-1, offset) def get_word_at(self, offset): offset = self._get_fixed_offset(offset) return self.raw[self._find_word_start(offset) : self._find_word_end(offset) + 1] def _get_fixed_offset(self, offset): if offset >= len(self.code): return offset - 1 if not self._is_id_char(offset): if offset > 0 and self._is_id_char(offset - 1): return offset - 1 if offset < len(self.code) - 1 and self._is_id_char(offset + 1): return offset + 1 return offset def _is_id_char(self, offset): return self.code[offset].isalnum() or self.code[offset] == "_" def _find_string_start(self, offset): kind = self.code[offset] try: return self.code.rindex(kind, 0, offset) except ValueError: return 0 def _find_parens_start(self, offset): offset = self._find_last_non_space_char(offset - 1) while offset >= 0 and self.code[offset] not in "[({": if self.code[offset] not in ":,": offset = self._find_primary_start(offset) offset = self._find_last_non_space_char(offset - 1) return offset def _find_atom_start(self, offset): old_offset = offset if self.code[offset] == "\n": return offset + 1 if self.code[offset].isspace(): offset = self._find_last_non_space_char(offset) if self.code[offset] in "'\"": return self._find_string_start(offset) if self.code[offset] in ")]}": return self._find_parens_start(offset) if self._is_id_char(offset): return self._find_word_start(offset) return old_offset def _find_primary_without_dot_start(self, offset): """It tries to find the undotted primary start It is different from `self._get_atom_start()` in that it follows function calls, too; such as in ``f(x)``. """ last_atom = offset offset = self._find_last_non_space_char(last_atom) while offset > 0 and self.code[offset] in ")]": last_atom = self._find_parens_start(offset) offset = self._find_last_non_space_char(last_atom - 1) if offset >= 0 and (self.code[offset] in "\"'})]" or self._is_id_char(offset)): atom_start = self._find_atom_start(offset) if not keyword.iskeyword(self.code[atom_start : offset + 1]) or ( offset + 1 < len(self.code) and self._is_id_char(offset + 1) ): return atom_start return last_atom def _find_primary_start(self, offset): if offset >= len(self.code): offset = len(self.code) - 1 if self.code[offset] != ".": offset = self._find_primary_without_dot_start(offset) else: offset = offset + 1 while offset > 0: 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 return offset def get_primary_at(self, offset): offset = self._get_fixed_offset(offset) start, end = self.get_primary_range(offset) return self.raw[start:end].strip() def get_splitted_primary_before(self, offset): """returns expression, starting, starting_offset This function is used in `rope.codeassist.assist` function. """ if offset == 0: return ("", "", 0) end = offset - 1 word_start = self._find_atom_start(end) real_start = self._find_primary_start(end) if self.code[word_start:offset].strip() == "": word_start = end if self.code[end].isspace(): word_start = end if self.code[real_start:word_start].strip() == "": real_start = word_start if real_start == word_start == end and not self._is_id_char(end): return ("", "", offset) if real_start == word_start: return ("", self.raw[word_start:offset], word_start) else: if self.code[end] == ".": return (self.raw[real_start:end], "", offset) last_dot_position = word_start if self.code[word_start] != ".": last_dot_position = self._find_last_non_space_char(word_start - 1) last_char_position = self._find_last_non_space_char(last_dot_position - 1) if self.code[word_start].isspace(): word_start = offset return ( self.raw[real_start : last_char_position + 1], self.raw[word_start:offset], word_start, ) def _get_line_start(self, offset): try: return self.code.rindex("\n", 0, offset + 1) except ValueError: return 0 def _get_line_end(self, offset): try: return self.code.index("\n", offset) except ValueError: return len(self.code) def is_name_assigned_in_class_body(self, offset): word_start = self._find_word_start(offset - 1) word_end = self._find_word_end(offset) + 1 if "." in self.code[word_start:word_end]: return False line_start = self._get_line_start(word_start) line = self.code[line_start:word_start].strip() return not line and self.get_assignment_type(offset) == "=" def is_a_class_or_function_name_in_header(self, offset): word_start = self._find_word_start(offset - 1) line_start = self._get_line_start(word_start) prev_word = self.code[line_start:word_start].strip() return prev_word in ["def", "class"] def _find_first_non_space_char(self, offset): if offset >= len(self.code): return len(self.code) while offset < len(self.code) and self.code[offset].isspace(): if self.code[offset] == "\n": return offset offset += 1 return offset def is_a_function_being_called(self, offset): word_end = self._find_word_end(offset) + 1 next_char = self._find_first_non_space_char(word_end) return ( next_char < len(self.code) and self.code[next_char] == "(" and not self.is_a_class_or_function_name_in_header(offset) ) def _find_import_end(self, start): return self._get_line_end(start) def is_import_statement(self, offset): try: last_import = self.code.rindex("import ", 0, offset) except ValueError: return False line_start = self._get_line_start(last_import) return ( self._find_import_end(last_import + 7) >= offset and self._find_word_start(line_start) == last_import ) def is_from_statement(self, offset): try: last_from = self.code.rindex("from ", 0, offset) from_import = self.code.index(" import ", last_from) from_names = from_import + 8 except ValueError: return False from_names = self._find_first_non_space_char(from_names) return self._find_import_end(from_names) >= offset def is_from_statement_module(self, offset): if offset >= len(self.code) - 1: return False stmt_start = self._find_primary_start(offset) line_start = self._get_line_start(stmt_start) prev_word = self.code[line_start:stmt_start].strip() return prev_word == "from" def is_import_statement_aliased_module(self, offset): if not self.is_import_statement(offset): return False try: line_start = self._get_line_start(offset) import_idx = self.code.rindex("import", line_start, offset) imported_names = import_idx + 7 except ValueError: return False # Check if the offset is within the imported names if ( imported_names - 1 > offset or self._find_import_end(imported_names) < offset ): return False try: 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": line_start = self._get_line_start(offset - 1) else: line_start = self._get_line_start(offset) last_from = self.code.rindex("from ", line_start, offset) from_import = self.code.index(" import ", last_from) from_names = from_import + 8 except ValueError: return False if from_names - 1 > offset: return False return self._find_import_end(from_names) >= offset def get_from_module(self, offset): try: last_from = self.code.rindex("from ", 0, offset) import_offset = self.code.index(" import ", last_from) end = self._find_last_non_space_char(import_offset) return self.get_primary_at(end) except ValueError: pass def is_from_aliased(self, offset): if not self.is_a_name_after_from_import(offset): return False try: end = self._find_word_end(offset) 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 get_from_aliased(self, offset): try: end = self._find_word_end(offset) as_ = self._find_word_end(end + 1) alias = self._find_word_end(as_ + 1) start = self._find_word_start(alias) return self.raw[start : alias + 1] except ValueError: pass def is_function_keyword_parameter(self, offset): word_end = self._find_word_end(offset) if word_end + 1 == len(self.code): return False next_char = self._find_first_non_space_char(word_end + 1) equals = self.code[next_char : next_char + 2] if equals == "==" or not equals.startswith("="): return False word_start = self._find_word_start(offset) prev_char = self._find_last_non_space_char(word_start - 1) return prev_char - 1 >= 0 and self.code[prev_char] in ",(" def is_on_function_call_keyword(self, offset): stop = self._get_line_start(offset) if self._is_id_char(offset): offset = self._find_word_start(offset) - 1 offset = self._find_last_non_space_char(offset) if offset <= stop or self.code[offset] not in "(,": return False parens_start = self.find_parens_start_from_inside(offset) return stop < parens_start def find_parens_start_from_inside(self, offset): stop = self._get_line_start(offset) while offset > stop: if self.code[offset] == "(": break if self.code[offset] != ",": offset = self._find_primary_start(offset) offset -= 1 return max(stop, offset) def is_assigned_here(self, offset): return self.get_assignment_type(offset) is not None def get_assignment_type(self, offset): # XXX: does not handle tuple assignments word_end = self._find_word_end(offset) next_char = self._find_first_non_space_char(word_end + 1) single = self.code[next_char : next_char + 1] double = self.code[next_char : next_char + 2] triple = self.code[next_char : next_char + 3] if double not in ("==", "<=", ">=", "!="): for op in [single, double, triple]: if op.endswith("="): return op def get_primary_range(self, offset): start = self._find_primary_start(offset) end = self._find_word_end(offset) + 1 return (start, end) def get_word_range(self, offset): offset = max(0, offset) start = self._find_word_start(offset) end = self._find_word_end(offset) + 1 return (start, end) def get_word_parens_range(self, offset, opening="(", closing=")"): end = self._find_word_end(offset) start_parens = self.code.index(opening, end) index = start_parens open_count = 0 while index < len(self.code): if self.code[index] == opening: open_count += 1 if self.code[index] == closing: open_count -= 1 if open_count == 0: return (start_parens, index + 1) index += 1 return (start_parens, index) def get_parameters(self, first, last): keywords = [] args = [] current = self._find_last_non_space_char(last - 1) while current > first: primary_start = current current = self._find_primary_start(current) while current != first and ( self.code[current] not in "=," or self.code[current - 1] in "=!<>" ): current = self._find_last_non_space_char(current - 1) primary = self.raw[current + 1 : primary_start + 1].strip() if self.code[current] == "=": primary_start = current - 1 current -= 1 while current != first and self.code[current] not in ",": current = self._find_last_non_space_char(current - 1) param_name = self.raw[current + 1 : primary_start + 1].strip() keywords.append((self.__strip_type_hint(param_name), primary)) else: args.append(self.__strip_type_hint(primary)) current = self._find_last_non_space_char(current - 1) args.reverse() keywords.reverse() return args, keywords def __strip_type_hint(self, name): return name.split(":", 1)[0] def is_assigned_in_a_tuple_assignment(self, offset): start = self._get_line_start(offset) end = self._get_line_end(offset) primary_start = self._find_primary_start(offset) primary_end = self._find_word_end(offset) prev_char_offset = self._find_last_non_space_char(primary_start - 1) next_char_offset = self._find_first_non_space_char(primary_end + 1) next_char = prev_char = "" if prev_char_offset >= start: prev_char = self.code[prev_char_offset] if next_char_offset < end: next_char = self.code[next_char_offset] try: equals_offset = self.code.index("=", start, end) except ValueError: return False if prev_char not in "(," and next_char not in ",)": return False parens_start = self.find_parens_start_from_inside(offset) # XXX: only handling (x, y) = value return offset < equals_offset and self.code[start:parens_start].strip() == "" def get_function_and_args_in_header(self, offset): offset = self.find_function_offset(offset) lparens, rparens = self.get_word_parens_range(offset) return self.raw[offset : rparens + 1] def find_function_offset(self, offset, definition="def "): while True: offset = self.code.index(definition, offset) if offset == 0 or not self._is_id_char(offset - 1): break offset += 1 def_ = offset + 4 return self._find_first_non_space_char(def_) def get_lambda_and_args(self, offset): offset = self.find_function_offset(offset, definition="lambda ") lparens, rparens = self.get_word_parens_range(offset, opening=" ", closing=":") return self.raw[offset : rparens + 1] ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1705553002.685983 rope-1.12.0/rope/contrib/0000775000175000017500000000000014552126153015103 5ustar00lieryanlieryan././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/rope/contrib/__init__.py0000664000175000017500000000025114512700666017215 0ustar00lieryanlieryan"""rope IDE tools package This package contains modules that can be used in IDEs but do not depend on the UI. So these modules will be used by `rope.ui` modules. """ ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1705553002.6899831 rope-1.12.0/rope/contrib/autoimport/0000775000175000017500000000000014552126153017306 5ustar00lieryanlieryan././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/rope/contrib/autoimport/__init__.py0000664000175000017500000000030414512700666021417 0ustar00lieryanlieryan"""AutoImport module for rope.""" from .pickle import AutoImport as _PickleAutoImport from .sqlite import AutoImport as _SqliteAutoImport AutoImport = _PickleAutoImport __all__ = ["AutoImport"] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/rope/contrib/autoimport/defs.py0000664000175000017500000000461414512700666020611 0ustar00lieryanlieryan"""Definitions of types for the Autoimport program.""" import pathlib from enum import Enum from typing import NamedTuple, Optional class Source(Enum): """Describes the source of the package, for sorting purposes.""" PROJECT = 0 # Obviously any project packages come first MANUAL = 1 # Placeholder since Autoimport classifies manually added modules BUILTIN = 2 STANDARD = 3 # We want to favor standard library items SITE_PACKAGE = 4 UNKNOWN = 5 # modified_time class ModuleInfo(NamedTuple): """Descriptor of information to get names from a module.""" filepath: Optional[pathlib.Path] modname: str underlined: bool process_imports: bool class ModuleFile(ModuleInfo): """Descriptor of information to get names from a file using ast.""" filepath: pathlib.Path modname: str underlined: bool process_imports: bool class ModuleCompiled(ModuleInfo): """Descriptor of information to get names using imports.""" filepath = None modname: str underlined: bool process_imports: bool class PackageType(Enum): """Describes the type of package, to determine how to get the names from it.""" BUILTIN = 0 # No file exists, compiled into python. IE: Sys STANDARD = 1 # Just a folder COMPILED = 2 # .so module SINGLE_FILE = 3 # a .py file class NameType(Enum): """Describes the type of Name for lsp completions. Taken from python lsp server.""" Text = 1 Method = 2 Function = 3 Constructor = 4 Field = 5 Variable = 6 Class = 7 Interface = 8 Module = 9 Property = 10 Unit = 11 Value = 12 Enum = 13 Keyword = 14 Snippet = 15 Color = 16 File = 17 Reference = 18 Folder = 19 EnumMember = 20 Constant = 21 Struct = 22 Event = 23 Operator = 24 TypeParameter = 25 class Package(NamedTuple): """Attributes of a package.""" name: str source: Source path: Optional[pathlib.Path] type: PackageType class Name(NamedTuple): """A Name to be added to the database.""" name: str modname: str package: str source: Source name_type: NameType class PartialName(NamedTuple): """Partial information of a Name.""" name: str name_type: NameType class SearchResult(NamedTuple): """Search Result.""" import_statement: str name: str source: int itemkind: int ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1705551350.0 rope-1.12.0/rope/contrib/autoimport/models.py0000664000175000017500000000771114552122766021157 0ustar00lieryanlieryanfrom abc import ABC, abstractmethod from typing import Dict, List class FinalQuery: def __init__(self, query): self._query = query def __repr__(self): return f'{self.__class__.__name__}("{self._query}")' def explain(self): return FinalQuery("EXPLAIN QUERY PLAN " + self._query) class Query: def __init__(self, query: str, columns: List[str]): self.query = query self.columns = columns def __repr__(self): return f'{self.__class__.__name__}("{self.query}", columns={self.columns})' def select(self, *columns: str): if not (set(columns) <= set(self.columns)): raise ValueError( f"Unknown column names passed: {set(columns) - set(self.columns)}" ) selected_columns = ", ".join(columns) return FinalQuery(f"SELECT {selected_columns} FROM {self.query}") def select_star(self): return FinalQuery(f"SELECT * FROM {self.query}") def where(self, where_clause: str): return Query( f"{self.query} WHERE {where_clause}", columns=self.columns, ) def insert_into(self) -> FinalQuery: columns = ", ".join(self.columns) placeholders = ", ".join(["?"] * len(self.columns)) return FinalQuery( f"INSERT INTO {self.query}({columns}) VALUES ({placeholders})" ) def drop_table(self) -> FinalQuery: return FinalQuery(f"DROP TABLE IF EXISTS {self.query}") def delete_from(self) -> FinalQuery: return FinalQuery(f"DELETE FROM {self.query}") class Model(ABC): @property @abstractmethod def table_name(self) -> str: ... @property @abstractmethod def schema(self) -> Dict[str, str]: ... @classmethod def create_table(cls, connection): metadata_table = [ f"{column_name} {column_type}" for column_name, column_type in cls.schema.items() ] metadata_table_definition = ", ".join(metadata_table) connection.execute( f"CREATE TABLE IF NOT EXISTS {cls.table_name}({metadata_table_definition})" ) class Metadata(Model): table_name = "metadata" schema = { "version_hash": "TEXT", "hash_data": "TEXT", "created_at": "TEXT", } columns = list(schema.keys()) objects = Query(table_name, columns) class Name(Model): table_name = "names" schema = { "name": "TEXT", "module": "TEXT", "package": "TEXT", "source": "INTEGER", "type": "INTEGER", } columns = list(schema.keys()) objects = Query(table_name, columns) @classmethod def create_table(cls, connection): super().create_table(connection) # fmt: off connection.execute("CREATE INDEX IF NOT EXISTS names_name ON names(name)") connection.execute("CREATE INDEX IF NOT EXISTS names_module ON names(module)") connection.execute("CREATE INDEX IF NOT EXISTS names_package ON names(package)") connection.execute("CREATE INDEX IF NOT EXISTS names_name_nocase ON names(name COLLATE NOCASE)") connection.execute("CREATE INDEX IF NOT EXISTS names_module_nocase ON names(module COLLATE NOCASE)") connection.execute("CREATE INDEX IF NOT EXISTS names_package_nocase ON names(package COLLATE NOCASE)") # fmt: on search_submodule_like = objects.where('module LIKE ("%." || ?)') search_module_like = objects.where("module LIKE (?)") import_assist = objects.where("name LIKE (? || '%')") search_by_name_like = objects.where("name LIKE (?)") search_by_name = objects.where("name IS (?)") delete_by_module_name = objects.where("module = ?").delete_from() class Package(Model): table_name = "packages" schema = { "package": "TEXT", "path": "TEXT", } columns = list(schema.keys()) objects = Query(table_name, columns) delete_by_package_name = objects.where("package = ?").delete_from() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/rope/contrib/autoimport/parse.py0000664000175000017500000001223414512700666020777 0ustar00lieryanlieryan""" Functions to find importable names. Can extract names from source code of a python file, .so object, or builtin module. """ import inspect import logging import pathlib from importlib import import_module from typing import Generator, List from rope.base import ast from .defs import ( ModuleCompiled, ModuleFile, ModuleInfo, Name, NameType, Package, PartialName, Source, ) logger = logging.getLogger(__name__) def get_type_ast(node: ast.AST) -> NameType: """Get the lsp type of a node.""" if isinstance(node, ast.ClassDef): return NameType.Class if isinstance(node, ast.FunctionDef): return NameType.Function if isinstance(node, ast.Assign): return NameType.Variable return NameType.Variable # default value def get_names_from_file( module: pathlib.Path, package_name: str = "", underlined: bool = False, process_imports: bool = False, ) -> Generator[PartialName, None, None]: """Get all the names from a given file using ast.""" try: root_node = ast.parse(module.read_bytes()) except SyntaxError as error: print(error) return for node in ast.iter_child_nodes(root_node): if isinstance(node, ast.Assign): for target in node.targets: try: assert isinstance(target, ast.Name) if underlined or not target.id.startswith("_"): yield PartialName( target.id, get_type_ast(node), ) except (AttributeError, AssertionError): # TODO handle tuple assignment pass elif isinstance(node, (ast.FunctionDef, ast.ClassDef)): if underlined or not node.name.startswith("_"): yield PartialName( node.name, get_type_ast(node), ) elif process_imports and isinstance(node, ast.ImportFrom): # When we process imports, we want to include names in it's own package. if node.level == 0: continue if not node.module or package_name is node.module.split(".")[0]: continue for name in node.names: if isinstance(name, ast.alias): if name.asname: real_name = name.asname else: real_name = name.name else: real_name = name if underlined or not real_name.startswith("_"): yield PartialName(real_name, get_type_ast(node)) def get_type_object(imported_object) -> NameType: """Determine the type of an object.""" if inspect.isclass(imported_object): return NameType.Class if inspect.isfunction(imported_object) or inspect.isbuiltin(imported_object): return NameType.Function return NameType.Variable def get_names(module: ModuleInfo, package: Package) -> List[Name]: """Get all names from a module and package.""" if isinstance(module, ModuleCompiled): return list( get_names_from_compiled(package.name, package.source, module.underlined) ) if isinstance(module, ModuleFile): return [ combine(package, module, partial_name) for partial_name in get_names_from_file( module.filepath, package.name, underlined=module.underlined, process_imports=module.process_imports, ) ] return [] def get_names_from_compiled( package: str, source: Source, underlined: bool = False, ) -> Generator[Name, None, None]: """ Get the names from a compiled module. Instead of using ast, it imports the module. Parameters ---------- package : str package to import. Must be in sys.path underlined : bool include underlined names """ # builtins is banned because you never have to import it # python_crun is banned because it crashes python banned = ["builtins", "python_crun"] if package in banned or (package.startswith("_") and not underlined): return # Builtins is redundant since you don't have to import it. if source not in (Source.BUILTIN, Source.STANDARD): return try: module = import_module(str(package)) except ImportError: logger.error(f"{package} could not be imported for autoimport analysis") return else: for name, value in inspect.getmembers(module): if underlined or not name.startswith("_"): if ( inspect.isclass(value) or inspect.isfunction(value) or inspect.isbuiltin(value) ): yield Name( str(name), package, package, source, get_type_object(value) ) def combine(package: Package, module: ModuleFile, name: PartialName) -> Name: """Combine information to form a full name.""" return Name(name.name, module.modname, package.name, package.source, name.name_type) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/rope/contrib/autoimport/pickle.py0000664000175000017500000002031214512700666021130 0ustar00lieryanlieryan""" IMPORTANT: This is a deprecated implementation of autoimport using pickle-based storage. This pickle-based autoimport is provided only for backwards compatibility purpose and will be removed and the sqlite backend will be the new default implementation in the future. If you are still using this module, you should migrate to the new and improved sqlite-based storage backend (rope.contrib.autoimport.sqlite.AutoImport). """ import contextlib import re from rope.base import ( builtins, exceptions, libutils, pynames, pyobjects, resourceobserver, resources, taskhandle, ) from rope.refactor import importutils class AutoImport: """A class for finding the module that provides a name This class maintains a cache of global names in python modules. Note that this cache is not accurate and might be out of date. """ def __init__(self, project, observe=True, underlined=False): """Construct an AutoImport object If `observe` is `True`, listen for project changes and update the cache. If `underlined` is `True`, underlined names are cached, too. """ self.project = project self.underlined = underlined self.names = project.data_files.read_data("globalnames") if self.names is None: self.names = {} project.data_files.add_write_hook(self._write) # XXX: using a filtered observer observer = resourceobserver.ResourceObserver( changed=self._changed, moved=self._moved, removed=self._removed ) if observe: project.add_observer(observer) def import_assist(self, starting): """Return a list of ``(name, module)`` tuples This function tries to find modules that have a global name that starts with `starting`. """ # XXX: breaking if gave up! use generators result = [] for module in self.names: result.extend( (global_name, module) for global_name in self.names[module] if global_name.startswith(starting) ) return result def get_modules(self, name): """Return the list of modules that have global `name`""" return [module for module in self.names if name in self.names[module]] def get_all_names(self): """Return the list of all cached global names""" result = set() for module in self.names: result.update(set(self.names[module])) return result def get_name_locations(self, name): """Return a list of ``(resource, lineno)`` tuples""" result = [] for module in self.names: if name in self.names[module]: with contextlib.suppress(exceptions.ModuleNotFoundError): pymodule = self.project.get_module(module) if name in pymodule: pyname = pymodule[name] module, lineno = pyname.get_definition_location() if module is not None: resource = module.get_module().get_resource() if resource is not None and lineno is not None: result.append((resource, lineno)) return result def generate_cache( self, resources=None, underlined=None, task_handle=taskhandle.DEFAULT_TASK_HANDLE, ): """Generate global name cache for project files If `resources` is a list of `rope.base.resource.File`, only those files are searched; otherwise all python modules in the project are cached. """ if resources is None: resources = self.project.get_python_files() job_set = task_handle.create_jobset( "Generating autoimport cache", len(resources) ) for file in resources: job_set.started_job("Working on <%s>" % file.path) self.update_resource(file, underlined) job_set.finished_job() def generate_modules_cache( self, modules, underlined=None, task_handle=taskhandle.DEFAULT_TASK_HANDLE ): """Generate global name cache for modules listed in `modules`""" job_set = task_handle.create_jobset( "Generating autoimport cache for modules", len(modules) ) for modname in modules: job_set.started_job("Working on <%s>" % modname) if modname.endswith(".*"): mod = self.project.find_module(modname[:-2]) if mod: for sub in submodules(mod): self.update_resource(sub, underlined) else: self.update_module(modname, underlined) job_set.finished_job() def clear_cache(self): """Clear all entries in global-name cache It might be a good idea to use this function before regenerating global names. """ self.names.clear() def find_insertion_line(self, code): """Guess at what line the new import should be inserted""" match = re.search(r"^(def|class)\s+", code) if match is not None: code = code[: match.start()] try: pymodule = libutils.get_string_module(self.project, code) except exceptions.ModuleSyntaxError: return 1 testmodname = "__rope_testmodule_rope" importinfo = importutils.NormalImport(((testmodname, None),)) module_imports = importutils.get_module_imports(self.project, pymodule) module_imports.add_import(importinfo) code = module_imports.get_changed_source() offset = code.index(testmodname) lineno = code.count("\n", 0, offset) + 1 return lineno def update_resource(self, resource, underlined=None): """Update the cache for global names in `resource`""" with contextlib.suppress(exceptions.ModuleSyntaxError): pymodule = self.project.get_pymodule(resource) modname = self._module_name(resource) self._add_names(pymodule, modname, underlined) def update_module(self, modname, underlined=None): """Update the cache for global names in `modname` module `modname` is the name of a module. """ with contextlib.suppress(exceptions.ModuleNotFoundError): pymodule = self.project.get_module(modname) self._add_names(pymodule, modname, underlined) def _module_name(self, resource): return libutils.modname(resource) def _add_names(self, pymodule, modname, underlined): if underlined is None: underlined = self.underlined globals = [] if isinstance(pymodule, pyobjects.PyDefinedObject): attributes = pymodule._get_structural_attributes() else: attributes = pymodule.get_attributes() for name, pyname in attributes.items(): if not underlined and name.startswith("_"): continue if isinstance(pyname, (pynames.AssignedName, pynames.DefinedName)): globals.append(name) if isinstance(pymodule, builtins.BuiltinModule): globals.append(name) self.names[modname] = globals def _write(self): self.project.data_files.write_data("globalnames", self.names) def _changed(self, resource): if not resource.is_folder(): self.update_resource(resource) def _moved(self, resource, newresource): if not resource.is_folder(): modname = self._module_name(resource) if modname in self.names: del self.names[modname] self.update_resource(newresource) def _removed(self, resource): if not resource.is_folder(): modname = self._module_name(resource) if modname in self.names: del self.names[modname] def submodules(mod): if isinstance(mod, resources.File): if mod.name.endswith(".py") and mod.name != "__init__.py": return {mod} return set() if not mod.has_child("__init__.py"): return set() result = {mod} for child in mod.get_children(): result |= submodules(child) return result ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1705551350.0 rope-1.12.0/rope/contrib/autoimport/sqlite.py0000664000175000017500000005626014552122766021200 0ustar00lieryanlieryan"""AutoImport module for rope.""" import contextlib import json from hashlib import sha256 import secrets import re import sqlite3 import sys import warnings from collections import OrderedDict from concurrent.futures import Future, ProcessPoolExecutor, as_completed from datetime import datetime from itertools import chain from pathlib import Path from threading import local from typing import Generator, Iterable, Iterator, List, Optional, Set, Tuple from rope.base import exceptions, libutils, resourceobserver, taskhandle, versioning from rope.base.project import Project from rope.base.resources import Resource from rope.contrib.autoimport import models from rope.contrib.autoimport.defs import ( ModuleFile, Name, NameType, Package, PackageType, SearchResult, Source, ) from rope.contrib.autoimport.parse import get_names from rope.contrib.autoimport.utils import ( get_files, get_modname_from_path, get_package_tuple, sort_and_deduplicate, sort_and_deduplicate_tuple, ) from rope.refactor import importutils def get_future_names( packages: List[Package], underlined: bool, job_set: taskhandle.BaseJobSet ) -> Generator[Future, None, None]: """Get all names as futures.""" with ProcessPoolExecutor() as executor: for package in packages: for module in get_files(package, underlined): job_set.started_job(module.modname) job_set.increment() yield executor.submit(get_names, module, package) def filter_packages( packages: Iterable[Package], underlined: bool, existing: List[str] ) -> Iterable[Package]: """Filter list of packages to parse.""" if underlined: def filter_package(package: Package) -> bool: return package.name not in existing else: def filter_package(package: Package) -> bool: return package.name not in existing and not package.name.startswith("_") return filter(filter_package, packages) _deprecated_default: bool = object() # type: ignore class AutoImport: """A class for finding the module that provides a name. This class maintains a cache of global names in python modules. Note that this cache is not accurate and might be out of date. """ connection: sqlite3.Connection memory: bool project: Project project_package: Package underlined: bool def __init__( self, project: Project, observe: bool = True, underlined: bool = False, memory: bool = _deprecated_default, ): """Construct an AutoImport object. Parameters ___________ project : rope.base.project.Project the project to use for project imports observe : bool if true, listen for project changes and update the cache. underlined : bool If `underlined` is `True`, underlined names are cached, too. memory: If true, don't persist to disk DEPRECATION NOTICE: The default value will change to use an on-disk database by default in the future. If you want to use an in-memory database, you need to pass `memory=True` explicitly: autoimport = AutoImport(..., memory=True) """ self.project = project project_package = get_package_tuple(project.root.pathlib, project) assert project_package is not None assert project_package.path is not None self.project_package = project_package self.underlined = underlined self.memory = memory if memory is _deprecated_default: self.memory = True warnings.warn( "The default value for `AutoImport(memory)` argument will " "change to use an on-disk database by default in the future. " "If you want to use an in-memory database, you need to pass " "`AutoImport(memory=True)` explicitly.", DeprecationWarning, ) self.thread_local = local() self.connection = self.create_database_connection( project=project, memory=memory, ) self._setup_db() if observe: observer = resourceobserver.ResourceObserver( changed=self._changed, moved=self._moved, removed=self._removed ) project.add_observer(observer) @classmethod def create_database_connection( cls, *, project: Optional[Project] = None, memory: bool = False, ) -> sqlite3.Connection: """ Create an sqlite3 connection project : rope.base.project.Project the project to use for project imports memory : bool if true, don't persist to disk """ def calculate_project_hash(data: str) -> str: return sha256(data.encode()).hexdigest() if not memory and project is None: raise Exception("if memory=False, project must be provided") if memory or project is None or project.ropefolder is None: # Allows the in-memory db to be shared across threads # See https://www.sqlite.org/inmemorydb.html project_hash: str if project is None: project_hash = secrets.token_hex() elif project.ropefolder is None: project_hash = calculate_project_hash(project.address) else: project_hash = calculate_project_hash(project.ropefolder.real_path) return sqlite3.connect( f"file:rope-{project_hash}:?mode=memory&cache=shared", uri=True ) else: return sqlite3.connect(project.ropefolder.pathlib / "autoimport.db") @property def connection(self): """ Creates a new connection if called from a new thread. This makes sure AutoImport can be shared across threads. """ if not hasattr(self.thread_local, "connection"): self.thread_local.connection = self.create_database_connection( project=self.project, memory=self.memory, ) return self.thread_local.connection @connection.setter def connection(self, value: sqlite3.Connection): self.thread_local.connection = value def _setup_db(self): models.Metadata.create_table(self.connection) version_hash = list( self._execute(models.Metadata.objects.select("version_hash")) ) current_version_hash = versioning.calculate_version_hash(self.project) if not version_hash or version_hash[0][0] != current_version_hash: self.clear_cache() def import_assist(self, starting: str): """ Find modules that have a global name that starts with `starting`. For a more complete list, use the search or search_full methods. Parameters __________ starting : str what all the names should start with Return __________ Return a list of ``(name, module)`` tuples """ results = self._execute( models.Name.import_assist.select("name", "module", "source"), (starting,) ).fetchall() return sort_and_deduplicate_tuple( results ) # Remove duplicates from multiple occurrences of the same item def search(self, name: str, exact_match: bool = False) -> List[Tuple[str, str]]: """ Search both modules and names for an import string. This is a simple wrapper around search_full with basic sorting based on Source. Returns a sorted list of import statement, modname pairs """ results: List[Tuple[str, str, int]] = [ (statement, import_name, source) for statement, import_name, source, type in self.search_full( name, exact_match ) ] return sort_and_deduplicate_tuple(results) def search_full( self, name: str, exact_match: bool = False, ignored_names: Optional[Set[str]] = None, ) -> Generator[SearchResult, None, None]: """ Search both modules and names for an import string. Parameters __________ name: str Name to search for exact_match: bool If using exact_match, only search for that name. Otherwise, search for any name starting with that name. ignored_names : Set[str] Will ignore any names in this set Return __________ Unsorted Generator of SearchResults. Each is guaranteed to be unique. """ if ignored_names is None: ignored_names = set() results = set(self._search_name(name, exact_match)) results = results.union(self._search_module(name, exact_match)) for result in results: if result.name not in ignored_names: yield result def _search_name( self, name: str, exact_match: bool = False ) -> Generator[SearchResult, None, None]: """ Search both names for available imports. Returns the import statement, import name, source, and type. """ if not exact_match: name = name + "%" # Makes the query a starts_with query for import_name, module, source, name_type in self._execute( models.Name.search_by_name_like.select("name", "module", "source", "type"), (name,), ): yield ( SearchResult( f"from {module} import {import_name}", import_name, source, name_type, ) ) def _search_module( self, name: str, exact_match: bool = False ) -> Generator[SearchResult, None, None]: """ Search both modules for available imports. Returns the import statement, import name, source, and type. """ if not exact_match: name = name + "%" # Makes the query a starts_with query for module, source in self._execute( models.Name.search_submodule_like.select("module", "source"), (name,) ): parts = module.split(".") import_name = parts[-1] remaining = parts[0] for part in parts[1:-1]: remaining += "." remaining += part yield ( SearchResult( f"from {remaining} import {import_name}", import_name, source, NameType.Module.value, ) ) for module, source in self._execute( models.Name.search_module_like.select("module", "source"), (name,) ): if "." in module: continue yield SearchResult( f"import {module}", module, source, NameType.Module.value ) def get_modules(self, name) -> List[str]: """Get the list of modules that have global `name`.""" results = self._execute( models.Name.search_by_name.select("module", "source"), (name,) ).fetchall() return sort_and_deduplicate(results) def get_all_names(self) -> List[str]: """Get the list of all cached global names.""" return self._execute(models.Name.objects.select("name")).fetchall() def _dump_all(self) -> Tuple[List[Name], List[Package]]: """Dump the entire database.""" name_results = self._execute(models.Name.objects.select_star()).fetchall() package_results = self._execute(models.Package.objects.select_star()).fetchall() return name_results, package_results def generate_cache( self, resources: Optional[List[Resource]] = None, underlined: bool = False, task_handle: taskhandle.BaseTaskHandle = taskhandle.DEFAULT_TASK_HANDLE, ): """Generate global name cache for project files. If `resources` is a list of `rope.base.resource.File`, only those files are searched; otherwise all python modules in the project are cached. """ if resources is None: resources = self.project.get_python_files() job_set = task_handle.create_jobset( "Generating autoimport cache", len(resources) ) self._execute( models.Package.delete_by_package_name, (self.project_package.name,) ) futures = [] with ProcessPoolExecutor() as executor: for file in resources: job_set.started_job(f"Working on {file.path}") module = self._resource_to_module(file, underlined) futures.append(executor.submit(get_names, module, self.project_package)) for future in as_completed(futures): self._add_names(future.result()) job_set.finished_job() self.connection.commit() def generate_modules_cache( self, modules: Optional[List[str]] = None, task_handle: taskhandle.BaseTaskHandle = taskhandle.DEFAULT_TASK_HANDLE, single_thread: bool = False, underlined: Optional[bool] = None, ): """ Generate global name cache for external modules listed in `modules`. If no modules are provided, it will generate a cache for every module available. This method searches in your sys.path and configured python folders. Do not use this for generating your own project's internal names, use generate_resource_cache for that instead. """ underlined = self.underlined if underlined is None else underlined packages: List[Package] = ( self._get_available_packages() if modules is None else list(self._get_packages_from_modules(modules)) ) existing = self._get_packages_from_cache() packages = list(filter_packages(packages, underlined, existing)) if not packages: return self._add_packages(packages) job_set = task_handle.create_jobset("Generating autoimport cache", 0) if single_thread: for package in packages: for module in get_files(package, underlined): job_set.started_job(module.modname) for name in get_names(module, package): self._add_name(name) job_set.finished_job() else: for future_name in as_completed( get_future_names(packages, underlined, job_set) ): self._add_names(future_name.result()) job_set.finished_job() self.connection.commit() def _get_packages_from_modules(self, modules: List[str]) -> Iterator[Package]: for modname in modules: package = self._find_package_path(modname) if package is None: continue yield package def update_module(self, module: str): """Update a module in the cache, or add it if it doesn't exist.""" self._del_if_exist(module) self.generate_modules_cache([module]) def close(self): """Close the autoimport database.""" self.connection.commit() self.connection.close() def get_name_locations(self, name): """Return a list of ``(resource, lineno)`` tuples.""" result = [] modules = self._execute( models.Name.search_by_name_like.select("module"), (name,) ).fetchall() for module in modules: with contextlib.suppress(exceptions.ModuleNotFoundError): module_name = module[0] if module_name.startswith(f"{self.project_package.name}."): module_name = ".".join(module_name.split(".")) pymodule = self.project.get_module(module_name) if name in pymodule: pyname = pymodule[name] module, lineno = pyname.get_definition_location() if module is not None: resource = module.get_module().get_resource() if resource is not None and lineno is not None: result.append((resource, lineno)) return result def clear_cache(self): """Clear all entries in global-name cache. It might be a good idea to use this function before regenerating global names. """ with self.connection: self._execute(models.Name.objects.drop_table()) self._execute(models.Package.objects.drop_table()) self._execute(models.Metadata.objects.drop_table()) models.Name.create_table(self.connection) models.Package.create_table(self.connection) models.Metadata.create_table(self.connection) data = ( versioning.calculate_version_hash(self.project), json.dumps(versioning.get_version_hash_data(self.project)), datetime.utcnow().isoformat(), ) assert models.Metadata.columns == [ "version_hash", "hash_data", "created_at", ] self._execute(models.Metadata.objects.insert_into(), data) self.connection.commit() def find_insertion_line(self, code): """Guess at what line the new import should be inserted.""" match = re.search(r"^(def|class)\s+", code) if match is not None: code = code[: match.start()] try: pymodule = libutils.get_string_module(self.project, code) except exceptions.ModuleSyntaxError: return 1 testmodname = "__rope_testmodule_rope" importinfo = importutils.NormalImport(((testmodname, None),)) module_imports = importutils.get_module_imports(self.project, pymodule) module_imports.add_import(importinfo) code = module_imports.get_changed_source() offset = code.index(testmodname) lineno = code.count("\n", 0, offset) + 1 return lineno def update_resource( self, resource: Resource, underlined: bool = False, commit: bool = True ): """Update the cache for global names in `resource`.""" underlined = underlined if underlined else self.underlined module = self._resource_to_module(resource, underlined) self._del_if_exist(module_name=module.modname, commit=False) for name in get_names(module, self.project_package): self._add_name(name) if commit: self.connection.commit() def _changed(self, resource): if not resource.is_folder(): self.update_resource(resource) def _moved(self, resource: Resource, newresource: Resource): if not resource.is_folder(): modname = self._resource_to_module(resource).modname self._del_if_exist(modname) self.update_resource(newresource) def _del_if_exist(self, module_name, commit: bool = True): self._execute(models.Name.delete_by_module_name, (module_name,)) if commit: self.connection.commit() def _get_python_folders(self) -> List[Path]: def filter_folders(folder: Path) -> bool: return folder.is_dir() and folder.as_posix() != "/usr/bin" folders = self.project.get_python_path_folders() folder_paths = filter(filter_folders, map(Path, folders)) return list(OrderedDict.fromkeys(folder_paths)) def _safe_iterdir(self, folder: Path): dirs = folder.iterdir() while True: try: yield next(dirs) except PermissionError: pass except StopIteration: break def _get_available_packages(self) -> List[Package]: packages: List[Package] = [ Package(module, Source.BUILTIN, None, PackageType.BUILTIN) for module in sys.builtin_module_names ] for folder in self._get_python_folders(): for package in self._safe_iterdir(folder): package_tuple = get_package_tuple(package, self.project) if package_tuple is None: continue packages.append(package_tuple) return packages def _add_packages(self, packages: List[Package]): data = [(p.name, str(p.path)) for p in packages] self._executemany(models.Package.objects.insert_into(), data) def _get_packages_from_cache(self) -> List[str]: existing: List[str] = list( chain(*self._execute(models.Package.objects.select_star()).fetchall()) ) existing.append(self.project_package.name) return existing def _removed(self, resource): if not resource.is_folder(): modname = self._resource_to_module(resource).modname self._del_if_exist(modname) def _add_future_names(self, names: Future): self._add_names(names.result()) @staticmethod def _convert_name(name: Name) -> tuple: return ( name.name, name.modname, name.package, name.source.value, name.name_type.value, ) def _add_names(self, names: Iterable[Name]): if names is not None: self._executemany( models.Name.objects.insert_into(), [self._convert_name(name) for name in names], ) def _add_name(self, name: Name): self._execute(models.Name.objects.insert_into(), self._convert_name(name)) def _find_package_path(self, target_name: str) -> Optional[Package]: if target_name in sys.builtin_module_names: return Package(target_name, Source.BUILTIN, None, PackageType.BUILTIN) for folder in self._get_python_folders(): for package in self._safe_iterdir(folder): package_tuple = get_package_tuple(package, self.project) if package_tuple is None: continue name, source, package_path, package_type = package_tuple if name == target_name: return package_tuple return None def _resource_to_module( self, resource: Resource, underlined: bool = False ) -> ModuleFile: assert self.project_package.path underlined = underlined if underlined else self.underlined resource_path: Path = resource.pathlib # The project doesn't need its name added to the path, # since the standard python file layout accounts for that # so we set add_package_name to False resource_modname: str = get_modname_from_path( resource_path, self.project_package.path, add_package_name=False ) return ModuleFile( resource_path, resource_modname, underlined, resource_path.name == "__init__.py", ) def _execute(self, query: models.FinalQuery, *args, **kwargs): assert isinstance(query, models.FinalQuery) return self.connection.execute(query._query, *args, **kwargs) def _executemany(self, query: models.FinalQuery, *args, **kwargs): assert isinstance(query, models.FinalQuery) return self.connection.executemany(query._query, *args, **kwargs) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1705551350.0 rope-1.12.0/rope/contrib/autoimport/utils.py0000664000175000017500000001135514552122766021033 0ustar00lieryanlieryan"""Utility functions for the autoimport code.""" import pathlib import sys from collections import OrderedDict from typing import Generator, List, Optional, Tuple from rope.base.project import Project from .defs import ModuleCompiled, ModuleFile, ModuleInfo, Package, PackageType, Source def get_package_tuple( package_path: pathlib.Path, project: Optional[Project] = None ) -> Optional[Package]: """ Get package name and type from a path. Checks for common issues, such as not being a viable python module Returns None if not a viable package. """ package_name = package_path.name package_type: PackageType if package_name.startswith(".") or package_name in ["__pycache__", "site-packages"]: return None if package_name.endswith((".egg-info", ".dist-info")): return None if package_path.is_file(): if package_name.endswith(".so"): package_name = package_name.split(".")[0] package_type = PackageType.COMPILED elif package_name.endswith(".pyd"): package_name = package_name.split(".")[0] package_type = PackageType.COMPILED elif package_name.endswith(".py"): package_name = package_path.stem package_type = PackageType.SINGLE_FILE else: return None else: package_type = PackageType.STANDARD package_source: Source = get_package_source(package_path, project, package_name) return Package(package_name, package_source, package_path, package_type) def get_package_source( package: pathlib.Path, project: Optional[Project], name: str ) -> Source: """Detect the source of a given package. Rudimentary implementation.""" if name in sys.builtin_module_names: return Source.BUILTIN if project is not None and project.address in str(package): return Source.PROJECT if "site-packages" in package.parts: return Source.SITE_PACKAGE if sys.version_info < (3, 10, 0): if str(package).startswith(sys.prefix): return Source.STANDARD else: if name in sys.stdlib_module_names: return Source.STANDARD return Source.UNKNOWN def get_modname_from_path( modpath: pathlib.Path, package_path: pathlib.Path, add_package_name: bool = True ) -> str: """Get module name from a path in respect to package.""" package_name: str = package_path.stem rel_path_parts = modpath.relative_to(package_path).parts modname = "" if len(rel_path_parts) > 0: for part in rel_path_parts[:-1]: modname += part modname += "." if rel_path_parts[-1] == "__init__.py": modname = modname[:-1] else: modname = modname + modpath.stem if add_package_name: modname = package_name if modname == "" else package_name + "." + modname else: assert modname != "." return modname def sort_and_deduplicate(results: List[Tuple[str, int]]) -> List[str]: """Sort and deduplicate a list of name, source entries.""" results = sorted(results, key=lambda y: y[-1]) results_sorted = [name for name, source in results] return list(OrderedDict.fromkeys(results_sorted)) def sort_and_deduplicate_tuple( results: List[Tuple[str, str, int]] ) -> List[Tuple[str, str]]: """Sort and deduplicate a list of name, module, source entries.""" results = sorted(results, key=lambda y: y[-1]) results_sorted = [result[:-1] for result in results] return list(OrderedDict.fromkeys(results_sorted)) def should_parse(path: pathlib.Path, underlined: bool) -> bool: if underlined: return True return all(not part.startswith("_") for part in path.parts) def get_files( package: Package, underlined: bool = False ) -> Generator[ModuleInfo, None, None]: """Find all files to parse in a given path using __init__.py.""" if package.type in (PackageType.COMPILED, PackageType.BUILTIN): if package.source in (Source.STANDARD, Source.BUILTIN): yield ModuleCompiled(None, package.name, underlined, True) elif package.type == PackageType.SINGLE_FILE: assert package.path assert package.path.suffix == ".py" yield ModuleFile(package.path, package.path.stem, underlined, False) else: assert package.path for file in package.path.glob("**/*.py"): if file.name == "__init__.py": yield ModuleFile( file, get_modname_from_path(file.parent, package.path), underlined, True, ) elif should_parse(file, underlined): yield ModuleFile( file, get_modname_from_path(file, package.path), underlined, False ) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/rope/contrib/changestack.py0000664000175000017500000000250614512700666017736 0ustar00lieryanlieryan"""For performing many refactorings as a single command `changestack` module can be used to perform many refactorings on top of each other as one bigger command. It can be used like:: stack = ChangeStack(project, 'my big command') #.. stack.push(refactoring1.get_changes()) #.. stack.push(refactoring2.get_changes()) #.. stack.push(refactoringX.get_changes()) stack.pop_all() changes = stack.merged() Now `changes` can be previewed or performed as before. """ from rope.base import change class ChangeStack: def __init__(self, project, description="merged changes"): self.project = project self.description = description self.stack = [] def push(self, changes): self.stack.append(changes) self.project.do(changes) def pop_all(self): for i in range(len(self.stack)): self.project.history.undo(drop=True) def merged(self): result = change.ChangeSet(self.description) for changes in self.stack: for c in self._basic_changes(changes): result.add_change(c) return result def _basic_changes(self, changes): if isinstance(changes, change.ChangeSet): for child in changes.changes: yield from self._basic_changes(child) else: yield changes ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/rope/contrib/codeassist.py0000664000175000017500000006355114512700666017633 0ustar00lieryanlieryanimport keyword import sys import warnings from rope.base import ( builtins, evaluate, exceptions, libutils, pynames, pynamesdef, pyobjects, pyobjectsdef, pyscopes, worder, ) from rope.contrib import fixsyntax from rope.refactor import functionutils def code_assist( project, source_code, offset, resource=None, templates=None, maxfixes=1, later_locals=True, ): """Return python code completions as a list of `CodeAssistProposal` `resource` is a `rope.base.resources.Resource` object. If provided, relative imports are handled. `maxfixes` is the maximum number of errors to fix if the code has errors in it. If `later_locals` is `False` names defined in this scope and after this line is ignored. """ if templates is not None: warnings.warn( "Codeassist no longer supports templates", DeprecationWarning, stacklevel=2 ) assist = _PythonCodeAssist( project, source_code, offset, resource=resource, maxfixes=maxfixes, later_locals=later_locals, ) return assist() def starting_offset(source_code, offset): """Return the offset in which the completion should be inserted Usually code assist proposals should be inserted like:: completion = proposal.name result = (source_code[:starting_offset] + completion + source_code[offset:]) Where starting_offset is the offset returned by this function. """ word_finder = worder.Worder(source_code, True) expression, starting, starting_offset = word_finder.get_splitted_primary_before( offset ) return starting_offset def get_doc(project, source_code, offset, resource=None, maxfixes=1): """Get the pydoc""" fixer = fixsyntax.FixSyntax(project, source_code, resource, maxfixes) pyname = fixer.pyname_at(offset) if pyname is None: return None pyobject = pyname.get_object() return PyDocExtractor().get_doc(pyobject) def get_calltip( project, source_code, offset, resource=None, maxfixes=1, ignore_unknown=False, remove_self=False, ): """Get the calltip of a function The format of the returned string is ``module_name.holding_scope_names.function_name(arguments)``. For classes `__init__()` and for normal objects `__call__()` function is used. Note that the offset is on the function itself *not* after the its open parenthesis. (Actually it used to be the other way but it was easily confused when string literals were involved. So I decided it is better for it not to try to be too clever when it cannot be clever enough). You can use a simple search like:: offset = source_code.rindex('(', 0, offset) - 1 to handle simple situations. If `ignore_unknown` is `True`, `None` is returned for functions without source-code like builtins and extensions. If `remove_self` is `True`, the first parameter whose name is self will be removed for methods. """ fixer = fixsyntax.FixSyntax(project, source_code, resource, maxfixes) pyname = fixer.pyname_at(offset) if pyname is None: return None pyobject = pyname.get_object() return PyDocExtractor().get_calltip(pyobject, ignore_unknown, remove_self) def get_definition_location(project, source_code, offset, resource=None, maxfixes=1): """Return the definition location of the python name at `offset` Return a (`rope.base.resources.Resource`, lineno) tuple. If no `resource` is given and the definition is inside the same module, the first element of the returned tuple would be `None`. If the location cannot be determined ``(None, None)`` is returned. """ fixer = fixsyntax.FixSyntax(project, source_code, resource, maxfixes) pyname = fixer.pyname_at(offset) if pyname is not None: module, lineno = pyname.get_definition_location() if module is not None: return module.get_module().get_resource(), lineno return (None, None) def find_occurrences(*args, **kwds): import rope.contrib.findit warnings.warn( "Use `rope.contrib.findit.find_occurrences()` instead", DeprecationWarning, stacklevel=2, ) return rope.contrib.findit.find_occurrences(*args, **kwds) def get_canonical_path(project, resource, offset): """Get the canonical path to an object. Given the offset of the object, this returns a list of (name, name_type) tuples representing the canonical path to the object. For example, the 'x' in the following code: class Foo(object): def bar(self): class Qux(object): def mux(self, x): pass we will return: [('Foo', 'CLASS'), ('bar', 'FUNCTION'), ('Qux', 'CLASS'), ('mux', 'FUNCTION'), ('x', 'PARAMETER')] `resource` is a `rope.base.resources.Resource` object. `offset` is the offset of the pyname you want the path to. """ # Retrieve the PyName. pymod = project.get_pymodule(resource) pyname = evaluate.eval_location(pymod, offset) # Now get the location of the definition and its containing scope. defmod, lineno = pyname.get_definition_location() if not defmod: return None scope = defmod.get_scope().get_inner_scope_for_line(lineno) # Start with the name of the object we're interested in. names = [] if isinstance(pyname, pynamesdef.ParameterName): names = [(worder.get_name_at(pymod.get_resource(), offset), "PARAMETER")] elif isinstance(pyname, pynamesdef.AssignedName): names = [(worder.get_name_at(pymod.get_resource(), offset), "VARIABLE")] # Collect scope names. while scope.parent: if isinstance(scope, pyscopes.FunctionScope): scope_type = "FUNCTION" elif isinstance(scope, pyscopes.ClassScope): scope_type = "CLASS" else: scope_type = None names.append((scope.pyobject.get_name(), scope_type)) scope = scope.parent names.append((defmod.get_resource().real_path, "MODULE")) names.reverse() return names class CompletionProposal: """A completion proposal The `scope` instance variable shows where proposed name came from and can be 'global', 'local', 'builtin', 'attribute', 'keyword', 'imported', 'parameter_keyword'. The `type` instance variable shows the approximate type of the proposed object and can be 'instance', 'class', 'function', 'module', and `None`. All possible relations between proposal's `scope` and `type` are shown in the table below (different scopes in rows and types in columns): | instance | class | function | module | None local | + | + | + | + | global | + | + | + | + | builtin | + | + | + | | attribute | + | + | + | + | imported | + | + | + | + | keyword | | | | | + parameter_keyword | | | | | + """ def __init__(self, name, scope, pyname=None): self.name = name self.pyname = pyname self.scope = self._get_scope(scope) def __str__(self): return f"{self.name} ({self.scope}, {self.type})" def __repr__(self): return str(self) @property def parameters(self): """The names of the parameters the function takes. Returns None if this completion is not a function. """ pyname = self.pyname if isinstance(pyname, pynames.ImportedName): pyname = pyname._get_imported_pyname() if isinstance(pyname, pynames.DefinedName): pyobject = pyname.get_object() if isinstance(pyobject, pyobjects.AbstractFunction): return pyobject.get_param_names() @property def type(self): pyname = self.pyname if isinstance(pyname, builtins.BuiltinName): pyobject = pyname.get_object() if isinstance(pyobject, builtins.BuiltinFunction): return "function" elif isinstance(pyobject, builtins.BuiltinClass): return "class" elif isinstance(pyobject, builtins.BuiltinObject) or isinstance( pyobject, builtins.BuiltinName ): return "instance" elif isinstance(pyname, pynames.ImportedModule): return "module" elif isinstance(pyname, pynames.ImportedName) or isinstance( pyname, pynames.DefinedName ): pyobject = pyname.get_object() if isinstance(pyobject, pyobjects.AbstractFunction): return "function" if isinstance(pyobject, pyobjects.AbstractClass): return "class" return "instance" def _get_scope(self, scope): if isinstance(self.pyname, builtins.BuiltinName): return "builtin" if isinstance(self.pyname, pynames.ImportedModule) or isinstance( self.pyname, pynames.ImportedName ): return "imported" return scope def get_doc(self): """Get the proposed object's docstring. Returns None if it can not be get. """ if not self.pyname: return None pyobject = self.pyname.get_object() if not hasattr(pyobject, "get_doc"): return None return self.pyname.get_object().get_doc() @property def kind(self): warnings.warn( "the proposal's `kind` property is deprecated, " "use `scope` instead" ) return self.scope # leaved for backward compatibility CodeAssistProposal = CompletionProposal class NamedParamProposal(CompletionProposal): """A parameter keyword completion proposal Holds reference to ``_function`` -- the function which parameter ``name`` belongs to. This allows to determine default value for this parameter. """ def __init__(self, name, function): self.argname = name name = "%s=" % name super().__init__(name, "parameter_keyword") self._function = function def get_default(self): """Get a string representation of a param's default value. Returns None if there is no default value for this param. """ definfo = functionutils.DefinitionInfo.read(self._function) for arg, default in definfo.args_with_defaults: if self.argname == arg: return default return None def sorted_proposals(proposals, scopepref=None, typepref=None): """Sort a list of proposals Return a sorted list of the given `CodeAssistProposal`. `scopepref` can be a list of proposal scopes. Defaults to ``['parameter_keyword', 'local', 'global', 'imported', 'attribute', 'builtin', 'keyword']``. `typepref` can be a list of proposal types. Defaults to ``['class', 'function', 'instance', 'module', None]``. (`None` stands for completions with no type like keywords.) """ sorter = _ProposalSorter(proposals, scopepref, typepref) return sorter.get_sorted_proposal_list() def starting_expression(source_code, offset): """Return the expression to complete""" word_finder = worder.Worder(source_code, True) expression, starting, starting_offset = word_finder.get_splitted_primary_before( offset ) if expression: return expression + "." + starting return starting def default_templates(): warnings.warn( "default_templates() is deprecated.", DeprecationWarning, stacklevel=2 ) return {} class _PythonCodeAssist: def __init__( self, project, source_code, offset, resource=None, maxfixes=1, later_locals=True ): self.project = project self.code = source_code self.resource = resource self.maxfixes = maxfixes self.later_locals = later_locals self.word_finder = worder.Worder(source_code, True) ( self.expression, self.starting, self.offset, ) = self.word_finder.get_splitted_primary_before(offset) keywords = keyword.kwlist def _find_starting_offset(self, source_code, offset): current_offset = offset - 1 while current_offset >= 0 and ( source_code[current_offset].isalnum() or source_code[current_offset] in "_" ): current_offset -= 1 return current_offset + 1 def _matching_keywords(self, starting): return [ CompletionProposal(kw, "keyword") for kw in self.keywords if kw.startswith(starting) ] def __call__(self): if self.offset > len(self.code): return [] completions = list(self._code_completions().values()) if self.expression.strip() == "" and self.starting.strip() != "": completions.extend(self._matching_keywords(self.starting)) return completions def _dotted_completions(self, module_scope, holding_scope): result = {} found_pyname = evaluate.eval_str(holding_scope, self.expression) if found_pyname is not None: element = found_pyname.get_object() compl_scope = "attribute" if isinstance(element, (pyobjectsdef.PyModule, pyobjectsdef.PyPackage)): compl_scope = "imported" for name, pyname in element.get_attributes().items(): if name.startswith(self.starting): result[name] = CompletionProposal(name, compl_scope, pyname) return result def _undotted_completions(self, scope, result, lineno=None): if scope.parent is not None: self._undotted_completions(scope.parent, result) if lineno is None: names = scope.get_propagated_names() else: names = scope.get_names() for name, pyname in names.items(): if name.startswith(self.starting): compl_scope = "local" if scope.get_kind() == "Module": compl_scope = "global" if ( lineno is None or self.later_locals or not self._is_defined_after(scope, pyname, lineno) ): result[name] = CompletionProposal(name, compl_scope, pyname) def _from_import_completions(self, pymodule): module_name = self.word_finder.get_from_module(self.offset) if module_name is None: return {} pymodule = self._find_module(pymodule, module_name) result = {} for name in pymodule: if name.startswith(self.starting): result[name] = CompletionProposal( name, scope="global", pyname=pymodule[name] ) return result def _find_module(self, pymodule, module_name): dots = 0 while module_name[dots] == ".": dots += 1 pyname = pynames.ImportedModule(pymodule, module_name[dots:], dots) return pyname.get_object() def _is_defined_after(self, scope, pyname, lineno): location = pyname.get_definition_location() if location is not None and location[1] is not None: if ( location[0] == scope.pyobject.get_module() and lineno <= location[1] <= scope.get_end() ): return True def _code_completions(self): lineno = self.code.count("\n", 0, self.offset) + 1 fixer = fixsyntax.FixSyntax( self.project, self.code, self.resource, self.maxfixes ) pymodule = fixer.get_pymodule() module_scope = pymodule.get_scope() code = pymodule.source_code lines = code.split("\n") result = {} start = fixsyntax._logical_start(lines, lineno) indents = fixsyntax._get_line_indents(lines[start - 1]) inner_scope = module_scope.get_inner_scope_for_line(start, indents) if self.word_finder.is_a_name_after_from_import(self.offset): return self._from_import_completions(pymodule) if self.expression.strip() != "": result.update(self._dotted_completions(module_scope, inner_scope)) else: result.update(self._keyword_parameters(module_scope.pyobject, inner_scope)) self._undotted_completions(inner_scope, result, lineno=lineno) return result def _keyword_parameters(self, pymodule, scope): offset = self.offset if offset == 0: return {} word_finder = worder.Worder(self.code, True) if word_finder.is_on_function_call_keyword(offset - 1): function_parens = word_finder.find_parens_start_from_inside(offset - 1) primary = word_finder.get_primary_at(function_parens - 1) try: function_pyname = evaluate.eval_str(scope, primary) except exceptions.BadIdentifierError: return {} if function_pyname is not None: pyobject = function_pyname.get_object() if isinstance(pyobject, pyobjects.AbstractFunction): pass elif ( isinstance(pyobject, pyobjects.AbstractClass) and "__init__" in pyobject ): pyobject = pyobject["__init__"].get_object() elif "__call__" in pyobject: pyobject = pyobject["__call__"].get_object() if isinstance(pyobject, pyobjects.AbstractFunction): param_names = [] param_names.extend(pyobject.get_param_names(special_args=False)) result = {} for name in param_names: if name.startswith(self.starting): result[name + "="] = NamedParamProposal(name, pyobject) return result return {} class _ProposalSorter: """Sort a list of code assist proposals""" def __init__(self, code_assist_proposals, scopepref=None, typepref=None): self.proposals = code_assist_proposals if scopepref is None: scopepref = [ "parameter_keyword", "local", "global", "imported", "attribute", "builtin", "keyword", ] self.scopepref = scopepref if typepref is None: typepref = ["class", "function", "instance", "module", None] self.typerank = {type: index for index, type in enumerate(typepref)} def get_sorted_proposal_list(self): """Return a list of `CodeAssistProposal`""" proposals = {} for proposal in self.proposals: proposals.setdefault(proposal.scope, []).append(proposal) result = [] for scope in self.scopepref: scope_proposals = proposals.get(scope, []) scope_proposals = [ proposal for proposal in scope_proposals if proposal.type in self.typerank ] scope_proposals.sort(key=self._proposal_key) result.extend(scope_proposals) return result def _proposal_key(self, proposal1): def _underline_count(name): return sum(1 for c in name if c == "_") return ( self.typerank.get(proposal1.type, 100), _underline_count(proposal1.name), proposal1.name, ) # if proposal1.type != proposal2.type: # return cmp(self.typerank.get(proposal1.type, 100), # self.typerank.get(proposal2.type, 100)) # return self._compare_underlined_names(proposal1.name, # proposal2.name) class PyDocExtractor: def get_doc(self, pyobject): if isinstance(pyobject, pyobjects.AbstractFunction): return self._get_function_docstring(pyobject) elif isinstance(pyobject, pyobjects.AbstractClass): return self._get_class_docstring(pyobject) elif isinstance(pyobject, pyobjects.AbstractModule): return self._trim_docstring(pyobject.get_doc()) return None def get_calltip(self, pyobject, ignore_unknown=False, remove_self=False): try: if isinstance(pyobject, pyobjects.AbstractClass): pyobject = pyobject["__init__"].get_object() if not isinstance(pyobject, pyobjects.AbstractFunction): pyobject = pyobject["__call__"].get_object() except exceptions.AttributeNotFoundError: return None if ignore_unknown and not isinstance(pyobject, pyobjects.PyFunction): return if isinstance(pyobject, pyobjects.AbstractFunction): result = self._get_function_signature(pyobject, add_module=True) if remove_self and self._is_method(pyobject): return result.replace("(self)", "()").replace("(self, ", "(") return result def _get_class_docstring(self, pyclass): def _get_class_header(pyclass): class_name = pyclass.get_name() supers = [super.get_name() for super in pyclass.get_superclasses()] super_classes = ", ".join(supers) return f"class {class_name}({super_classes}):\n\n" contents = self._trim_docstring(pyclass.get_doc(), 2) doc = _get_class_header(pyclass) doc += contents if "__init__" in pyclass: init = pyclass["__init__"].get_object() if isinstance(init, pyobjects.AbstractFunction): doc += "\n\n" + self._get_single_function_docstring(init) return doc def _get_function_docstring(self, pyfunction): functions = [pyfunction] if self._is_method(pyfunction): functions.extend( self._get_super_methods(pyfunction.parent, pyfunction.get_name()) ) return "\n\n".join( [self._get_single_function_docstring(function) for function in functions] ) def _is_method(self, pyfunction): return isinstance(pyfunction, pyobjects.PyFunction) and isinstance( pyfunction.parent, pyobjects.PyClass ) def _get_single_function_docstring(self, pyfunction): signature = self._get_function_signature(pyfunction) docs = self._trim_docstring(pyfunction.get_doc(), indents=2) return signature + ":\n\n" + docs def _get_super_methods(self, pyclass, name): result = [] for super_class in pyclass.get_superclasses(): if name in super_class: function = super_class[name].get_object() if isinstance(function, pyobjects.AbstractFunction): result.append(function) result.extend(self._get_super_methods(super_class, name)) return result def _get_function_signature(self, pyfunction, add_module=False): location = self._location(pyfunction, add_module) if isinstance(pyfunction, pyobjects.PyFunction): info = functionutils.DefinitionInfo.read(pyfunction) return location + info.to_string() else: return "{}({})".format( location + pyfunction.get_name(), ", ".join(pyfunction.get_param_names()), ) def _location(self, pyobject, add_module=False): location = [] parent = pyobject.parent while parent and not isinstance(parent, pyobjects.AbstractModule): location.append(parent.get_name()) location.append(".") parent = parent.parent if add_module: if isinstance(pyobject, pyobjects.PyFunction): location.insert(0, self._get_module(pyobject)) if isinstance(parent, builtins.BuiltinModule): location.insert(0, parent.get_name() + ".") return "".join(location) def _get_module(self, pyfunction): module = pyfunction.get_module() if module is not None: resource = module.get_resource() if resource is not None: return libutils.modname(resource) + "." return "" def _trim_docstring(self, docstring, indents=0): """The sample code from :PEP:`257`""" if not docstring: return "" # Convert tabs to spaces (following normal Python rules) # and split into a list of lines: lines = docstring.expandtabs().splitlines() # Determine minimum indentation (first line doesn't count): indent = sys.maxsize for line in lines[1:]: stripped = line.lstrip() if stripped: indent = min(indent, len(line) - len(stripped)) # Remove indentation (first line is special): trimmed = [lines[0].strip()] if indent < sys.maxsize: for line in lines[1:]: trimmed.append(line[indent:].rstrip()) # Strip off trailing and leading blank lines: while trimmed and not trimmed[-1]: trimmed.pop() while trimmed and not trimmed[0]: trimmed.pop(0) # Return a single string: return "\n".join(" " * indents + line for line in trimmed) # Deprecated classes class TemplateProposal(CodeAssistProposal): def __init__(self, name, template): warnings.warn( "TemplateProposal is deprecated.", DeprecationWarning, stacklevel=2 ) super().__init__(name, "template") self.template = template class Template: def __init__(self, template): self.template = template warnings.warn("Template is deprecated.", DeprecationWarning, stacklevel=2) def variables(self): return [] def substitute(self, mapping): return self.template def get_cursor_location(self, mapping): return len(self.template) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/rope/contrib/finderrors.py0000664000175000017500000000556714512700666017652 0ustar00lieryanlieryan"""Finding bad name and attribute accesses `find_errors` function can be used to find possible bad name and attribute accesses. As an example:: errors = find_errors(project, project.get_resource('mod.py')) for error in errors: print('%s: %s' % (error.lineno, error.error)) prints possible errors for ``mod.py`` file. TODO: * use task handles * reporting names at most once * attributes of extension modules that don't appear in extension_modules project config can be ignored * not calling `PyScope.get_inner_scope_for_line()` if it is a bottleneck; needs profiling * not reporting occurrences where rope cannot infer the object * rope saves multiple objects for some of the names in its objectdb use all of them not to give false positives * ... ;-) """ from rope.base import ast, evaluate, pyobjects def find_errors(project, resource): """Find possible bad name and attribute accesses It returns a list of `Error`. """ pymodule = project.get_pymodule(resource) finder = _BadAccessFinder(pymodule) finder.visit(pymodule.get_ast()) return finder.errors class _BadAccessFinder(ast.RopeNodeVisitor): def __init__(self, pymodule): self.pymodule = pymodule self.scope = pymodule.get_scope() self.errors = [] def _Name(self, node): if isinstance(node.ctx, (ast.Store, ast.Param)): return scope = self.scope.get_inner_scope_for_line(node.lineno) pyname = scope.lookup(node.id) if pyname is None: self._add_error(node, "Unresolved variable") elif self._is_defined_after(scope, pyname, node.lineno): self._add_error(node, "Defined later") def _Attribute(self, node): if not isinstance(node.ctx, ast.Store): scope = self.scope.get_inner_scope_for_line(node.lineno) pyname = evaluate.eval_node(scope, node.value) if pyname is not None and pyname.get_object() != pyobjects.get_unknown(): if node.attr not in pyname.get_object(): self._add_error(node, "Unresolved attribute") self.visit(node.value) def _add_error(self, node, msg): if isinstance(node, ast.Attribute): name = node.attr else: name = node.id if name != "None": error = Error(node.lineno, msg + " " + name) self.errors.append(error) def _is_defined_after(self, scope, pyname, lineno): location = pyname.get_definition_location() if location is not None and location[1] is not None: if ( location[0] == self.pymodule and lineno <= location[1] <= scope.get_end() ): return True class Error: def __init__(self, lineno, error): self.lineno = lineno self.error = error def __str__(self): return f"{self.lineno}: {self.error}" ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/rope/contrib/findit.py0000664000175000017500000001060314512700666016735 0ustar00lieryanlieryanfrom rope.base import evaluate, exceptions, pyobjects, taskhandle, worder from rope.contrib import fixsyntax from rope.refactor import occurrences def find_occurrences( project, resource, offset, unsure=False, resources=None, in_hierarchy=False, task_handle=taskhandle.DEFAULT_TASK_HANDLE, ): """Return a list of `Location` If `unsure` is `True`, possible matches are returned, too. You can use `Location.unsure` to see which are unsure occurrences. `resources` can be a list of `rope.base.resource.File` that should be searched for occurrences; if `None` all python files in the project are searched. """ name = worder.get_name_at(resource, offset) this_pymodule = project.get_pymodule(resource) primary, pyname = evaluate.eval_location2(this_pymodule, offset) def is_match(occurrence): return unsure finder = occurrences.create_finder( project, name, pyname, unsure=is_match, in_hierarchy=in_hierarchy, instance=primary, ) if resources is None: resources = project.get_python_files() job_set = task_handle.create_jobset("Finding Occurrences", count=len(resources)) return _find_locations(finder, resources, job_set) def find_implementations( project, resource, offset, resources=None, task_handle=taskhandle.DEFAULT_TASK_HANDLE, ): """Find the places a given method is overridden. Finds the places a method is implemented. Returns a list of `Location`. """ name = worder.get_name_at(resource, offset) this_pymodule = project.get_pymodule(resource) pyname = evaluate.eval_location(this_pymodule, offset) if pyname is not None: pyobject = pyname.get_object() if ( not isinstance(pyobject, pyobjects.PyFunction) or pyobject.get_kind() != "method" ): raise exceptions.BadIdentifierError("Not a method!") else: raise exceptions.BadIdentifierError("Cannot resolve the identifier!") def is_defined(occurrence): if not occurrence.is_defined(): return False def not_self(occurrence): if occurrence.get_pyname().get_object() == pyname.get_object(): return False filters = [is_defined, not_self, occurrences.InHierarchyFilter(pyname, True)] finder = occurrences.Finder(project, name, filters=filters) if resources is None: resources = project.get_python_files() job_set = task_handle.create_jobset("Finding Implementations", count=len(resources)) return _find_locations(finder, resources, job_set) def find_definition(project, code, offset, resource=None, maxfixes=1): """Return the definition location of the python name at `offset` A `Location` object is returned if the definition location can be determined, otherwise ``None`` is returned. """ fixer = fixsyntax.FixSyntax(project, code, resource, maxfixes) pyname = fixer.pyname_at(offset) if pyname is not None: module, lineno = pyname.get_definition_location() name = worder.Worder(code).get_word_at(offset) if lineno is not None: start = module.lines.get_line_start(lineno) def check_offset(occurrence): if occurrence.offset < start: return False pyname_filter = occurrences.PyNameFilter(pyname) finder = occurrences.Finder(project, name, [check_offset, pyname_filter]) for occurrence in finder.find_occurrences(pymodule=module): return Location(occurrence) class Location: def __init__(self, occurrence): self.resource = occurrence.resource self.region = occurrence.get_word_range() self.offset = self.region[0] self.unsure = occurrence.is_unsure() self.lineno = occurrence.lineno def __repr__(self): return '<{}.{} "{}:{} ({}-{})" at {}>'.format( self.__class__.__module__, self.__class__.__name__, self.resource.path, self.lineno, self.region[0], self.region[1], hex(id(self)), ) def _find_locations(finder, resources, job_set): result = [] for resource in resources: job_set.started_job(resource.path) result.extend(map(Location, finder.find_occurrences(resource))) job_set.finished_job() return result ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/rope/contrib/fixmodnames.py0000664000175000017500000000422214512700666017772 0ustar00lieryanlieryan"""Fix the name of modules This module is useful when you want to rename many of the modules in your project. That can happen specially when you want to change their naming style. For instance:: fixer = FixModuleNames(project) changes = fixer.get_changes(fixer=str.lower) project.do(changes) Here it renames all modules and packages to use lower-cased chars. You can tell it to use any other style by using the ``fixer`` argument. """ from rope.base import taskhandle from rope.contrib import changestack from rope.refactor import rename class FixModuleNames: def __init__(self, project): self.project = project def get_changes(self, fixer=str.lower, task_handle=taskhandle.DEFAULT_TASK_HANDLE): """Fix module names `fixer` is a function that takes and returns a `str`. Given the name of a module, it should return the fixed name. """ stack = changestack.ChangeStack(self.project, "Fixing module names") jobset = task_handle.create_jobset( "Fixing module names", self._count_fixes(fixer) + 1 ) try: while True: for resource in self._tobe_fixed(fixer): jobset.started_job(resource.path) renamer = rename.Rename(self.project, resource) changes = renamer.get_changes(fixer(self._name(resource))) stack.push(changes) jobset.finished_job() break else: break finally: jobset.started_job("Reverting to original state") stack.pop_all() jobset.finished_job() return stack.merged() def _count_fixes(self, fixer): return len(list(self._tobe_fixed(fixer))) def _tobe_fixed(self, fixer): for resource in self.project.get_python_files(): modname = self._name(resource) if modname != fixer(modname): yield resource def _name(self, resource): modname = resource.name.rsplit(".", 1)[0] if modname == "__init__": modname = resource.parent.name return modname ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/rope/contrib/fixsyntax.py0000664000175000017500000001517114512700666017522 0ustar00lieryanlieryanfrom rope.base import codeanalyze, evaluate, exceptions, libutils, utils, worder from rope.base.codeanalyze import ArrayLinesAdapter, LogicalLineFinder class FixSyntax: def __init__(self, project, code, resource, maxfixes=1): self.project = project self.code = code self.resource = resource self.maxfixes = maxfixes @utils.saveit def get_pymodule(self): """Get a `PyModule`""" msg = None code = self.code tries = 0 while True: try: if ( tries == 0 and self.resource is not None and self.resource.read() == code ): return self.project.get_pymodule(self.resource, force_errors=True) return libutils.get_string_module( self.project, code, resource=self.resource, force_errors=True ) except exceptions.ModuleSyntaxError as e: if msg is None: msg = f"{e.filename}:{e.lineno} {e.message_}" if tries < self.maxfixes: tries += 1 self.commenter.comment(e.lineno) code = "\n".join(self.commenter.lines) else: raise exceptions.ModuleSyntaxError( e.filename, e.lineno, f"Failed to fix error: {msg}" ) @property @utils.saveit def commenter(self): return _Commenter(self.code) def pyname_at(self, offset): pymodule = self.get_pymodule() def old_pyname(): word_finder = worder.Worder(self.code, True) expression = word_finder.get_primary_at(offset) expression = expression.replace("\\\n", " ").replace("\n", " ") lineno = self.code.count("\n", 0, offset) scope = pymodule.get_scope().get_inner_scope_for_line(lineno) return evaluate.eval_str(scope, expression) new_code = pymodule.source_code def new_pyname(): newoffset = self.commenter.transferred_offset(offset) return evaluate.eval_location(pymodule, newoffset) if new_code.startswith(self.code[: offset + 1]): return new_pyname() result = old_pyname() if result is None: return new_pyname() return result class _Commenter: def __init__(self, code): self.code = code self.lines = self.code.split("\n") self.lines.append("\n") self.origs = list(range(len(self.lines) + 1)) self.diffs = [0] * (len(self.lines) + 1) def comment(self, lineno): start = _logical_start(self.lines, lineno, check_prev=True) - 1 # using self._get_stmt_end() instead of self._get_block_end() # to lower commented lines end = self._get_stmt_end(start) indents = _get_line_indents(self.lines[start]) if 0 < start: last_lineno = self._last_non_blank(start - 1) last_line = self.lines[last_lineno] if last_line.rstrip().endswith(":"): indents = _get_line_indents(last_line) + 4 self._set(start, " " * indents + "pass") for line in range(start + 1, end + 1): self._set(line, self.lines[start]) self._fix_incomplete_try_blocks(lineno, indents) def transferred_offset(self, offset): lineno = self.code.count("\n", 0, offset) diff = sum(self.diffs[:lineno]) return offset + diff def _last_non_blank(self, start): while start > 0 and self.lines[start].strip() == "": start -= 1 return start def _get_block_end(self, lineno): end_line = lineno base_indents = _get_line_indents(self.lines[lineno]) for i in range(lineno + 1, len(self.lines)): if _get_line_indents(self.lines[i]) >= base_indents: end_line = i else: break return end_line def _get_stmt_end(self, lineno): base_indents = _get_line_indents(self.lines[lineno]) for i in range(lineno + 1, len(self.lines)): if _get_line_indents(self.lines[i]) <= base_indents: return i - 1 return lineno def _fix_incomplete_try_blocks(self, lineno, indents): block_start = lineno last_indents = indents while block_start > 0: block_start = ( codeanalyze.get_block_start(ArrayLinesAdapter(self.lines), block_start) - 1 ) if self.lines[block_start].strip().startswith("try:"): indents = _get_line_indents(self.lines[block_start]) if indents > last_indents: continue last_indents = indents block_end = self._find_matching_deindent(block_start) line = self.lines[block_end].strip() if not ( line.startswith("finally:") or line.startswith("except ") or line.startswith("except:") ): self._insert(block_end, " " * indents + "finally:") self._insert(block_end + 1, " " * indents + " pass") def _find_matching_deindent(self, line_number): indents = _get_line_indents(self.lines[line_number]) current_line = line_number + 1 while current_line < len(self.lines): line = self.lines[current_line] if not line.strip().startswith("#") and not line.strip() == "": # HACK: We should have used logical lines here if _get_line_indents(self.lines[current_line]) <= indents: return current_line current_line += 1 return len(self.lines) - 1 def _set(self, lineno, line): self.diffs[self.origs[lineno]] += len(line) - len(self.lines[lineno]) self.lines[lineno] = line def _insert(self, lineno, line): self.diffs[self.origs[lineno]] += len(line) + 1 self.origs.insert(lineno, self.origs[lineno]) self.lines.insert(lineno, line) def _logical_start(lines, lineno, check_prev=False): logical_finder = LogicalLineFinder(ArrayLinesAdapter(lines)) if check_prev: prev = lineno - 1 while prev > 0: start, end = logical_finder.logical_line_in(prev) if end is None or start <= lineno < end: return start if start <= prev: break prev -= 1 return logical_finder.logical_line_in(lineno)[0] def _get_line_indents(line): return codeanalyze.count_line_indents(line) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/rope/contrib/generate.py0000664000175000017500000003477514512700666017272 0ustar00lieryanlieryanfrom __future__ import annotations from typing import TYPE_CHECKING from rope.base import ( change, codeanalyze, evaluate, exceptions, libutils, pynames, pyobjects, worder, ) from rope.refactor import functionutils, importutils, sourceutils, suites if TYPE_CHECKING: from typing import Literal, Optional from rope.base.project import Project from rope.base.resources import Resource GenerateKind = Literal[ "variable", "function", "class", "module", "package", ] def create_generate( kind: GenerateKind, project: Project, resource: Resource, offset: int, goal_resource: Optional[Resource] = None, ): """A factory for creating `Generate` objects Used in https://github.com/python-rope/ropemode but not in Rope itself. """ d = { "class": GenerateClass, "function": GenerateFunction, "module": GenerateModule, "package": GeneratePackage, "variable": GenerateVariable, } generate = d[kind] return generate(project, resource, offset, goal_resource=goal_resource) def create_module(project, name, sourcefolder=None): """Creates a module and returns a `rope.base.resources.File`""" if sourcefolder is None: sourcefolder = project.root packages = name.split(".") parent = sourcefolder for package in packages[:-1]: parent = parent.get_child(package) return parent.create_file(packages[-1] + ".py") def create_package(project, name, sourcefolder=None): """Creates a package and returns a `rope.base.resources.Folder`""" if sourcefolder is None: sourcefolder = project.root packages = name.split(".") parent = sourcefolder for package in packages[:-1]: parent = parent.get_child(package) made_packages = parent.create_folder(packages[-1]) made_packages.create_file("__init__.py") return made_packages class _Generate: def __init__(self, project, resource, offset, goal_resource=None): self.project = project self.resource = resource self.goal_resource = goal_resource self.info = self._generate_info(project, resource, offset) self.name = self.info.get_name() self._check_exceptional_conditions() def _generate_info(self, project, resource, offset): return _GenerationInfo(project.pycore, resource, offset, self.goal_resource) def _check_exceptional_conditions(self): if self.info.element_already_exists(): raise exceptions.RefactoringError( "Element <%s> already exists." % self.name ) if not self.info.primary_is_found(): raise exceptions.RefactoringError( "Cannot determine the scope <%s> should be defined in." % self.name ) def get_changes(self): changes = change.ChangeSet(f"Generate {self._get_element_kind()} <{self.name}>") indents = self.info.get_scope_indents() blanks = self.info.get_blank_lines() base_definition = sourceutils.fix_indentation(self._get_element(), indents) definition = "\n" * blanks[0] + base_definition + "\n" * blanks[1] resource = self.info.get_insertion_resource() start, end = self.info.get_insertion_offsets() collector = codeanalyze.ChangeCollector(resource.read()) collector.add_change(start, end, definition) changes.add_change(change.ChangeContents(resource, collector.get_changed())) if self.goal_resource: relative_import = _add_relative_import_to_module( self.project, self.resource, self.goal_resource, self.name ) changes.add_change(relative_import) return changes def get_location(self): return (self.info.get_insertion_resource(), self.info.get_insertion_lineno()) def _get_element_kind(self): raise NotImplementedError() def _get_element(self): raise NotImplementedError() class GenerateFunction(_Generate): def _generate_info(self, project, resource, offset): return _FunctionGenerationInfo(project.pycore, resource, offset) def _get_element(self): decorator = "" args = [] if self.info.is_static_method(): decorator = "@staticmethod\n" if ( self.info.is_method() or self.info.is_constructor() or self.info.is_instance() ): args.append("self") args.extend(self.info.get_passed_args()) definition = "{}def {}({}):\n pass\n".format( decorator, self.name, ", ".join(args), ) return definition def _get_element_kind(self): return "Function" class GenerateVariable(_Generate): def _get_element(self): return "%s = None\n" % self.name def _get_element_kind(self): return "Variable" class GenerateClass(_Generate): def _get_element(self): return "class %s(object):\n pass\n" % self.name def _get_element_kind(self): return "Class" class GenerateModule(_Generate): def get_changes(self): package = self.info.get_package() changes = change.ChangeSet("Generate Module <%s>" % self.name) new_resource = self.project.get_file(f"{package.path}/{self.name}.py") if new_resource.exists(): raise exceptions.RefactoringError( "Module <%s> already exists" % new_resource.path ) changes.add_change(change.CreateResource(new_resource)) changes.add_change( _add_import_to_module(self.project, self.resource, new_resource) ) return changes def get_location(self): package = self.info.get_package() return (package.get_child("%s.py" % self.name), 1) class GeneratePackage(_Generate): def get_changes(self): package = self.info.get_package() changes = change.ChangeSet("Generate Package <%s>" % self.name) new_resource = self.project.get_folder(f"{package.path}/{self.name}") if new_resource.exists(): raise exceptions.RefactoringError( "Package <%s> already exists" % new_resource.path ) changes.add_change(change.CreateResource(new_resource)) changes.add_change( _add_import_to_module(self.project, self.resource, new_resource) ) child = self.project.get_folder(package.path + "/" + self.name) changes.add_change(change.CreateFile(child, "__init__.py")) return changes def get_location(self): package = self.info.get_package() child = package.get_child(self.name) return (child.get_child("__init__.py"), 1) def _add_import_to_module(project, resource, imported): pymodule = project.get_pymodule(resource) import_tools = importutils.ImportTools(project) module_imports = import_tools.module_imports(pymodule) module_name = libutils.modname(imported) new_import = importutils.NormalImport(((module_name, None),)) module_imports.add_import(new_import) return change.ChangeContents(resource, module_imports.get_changed_source()) def _add_relative_import_to_module(project, resource, imported, name): pymodule = project.get_pymodule(resource) import_tools = importutils.ImportTools(project) module_imports = import_tools.module_imports(pymodule) new_import = import_tools.get_from_import(imported, name) module_imports.add_import(new_import) return change.ChangeContents(resource, module_imports.get_changed_source()) class _GenerationInfo: def __init__(self, pycore, resource, offset, goal_resource=None): self.pycore = pycore self.resource = resource self.offset = offset self.goal_resource = goal_resource self.source_pymodule = self.pycore.project.get_pymodule(resource) finder = evaluate.ScopeNameFinder(self.source_pymodule) self.primary, self.pyname = finder.get_primary_and_pyname_at(offset) self._init_fields() def _init_fields(self): self.source_scope = self._get_source_scope() self.goal_scope = self._get_goal_scope() self.goal_pymodule = self._get_goal_module(self.goal_scope) def _get_goal_scope(self): if self.primary is None: if self.goal_resource: return self.pycore.project.get_pymodule(self.goal_resource).get_scope() else: return self._get_source_scope() pyobject = self.primary.get_object() if isinstance(pyobject, pyobjects.PyDefinedObject): return pyobject.get_scope() elif isinstance(pyobject.get_type(), pyobjects.PyClass): return pyobject.get_type().get_scope() def _get_goal_module(self, scope): if scope is None: return while scope.parent is not None: scope = scope.parent return scope.pyobject def _get_source_scope(self): module_scope = self.source_pymodule.get_scope() lineno = self.source_pymodule.lines.get_line_number(self.offset) return module_scope.get_inner_scope_for_line(lineno) def get_insertion_lineno(self): lines = self.goal_pymodule.lines if self.goal_scope == self.source_scope: line_finder = self.goal_pymodule.logical_lines lineno = lines.get_line_number(self.offset) lineno = line_finder.logical_line_in(lineno)[0] root = suites.ast_suite_tree(self.goal_scope.pyobject.get_ast()) suite = root.find_suite(lineno) indents = sourceutils.get_indents(lines, lineno) while self.get_scope_indents() < indents: lineno = suite.get_start() indents = sourceutils.get_indents(lines, lineno) suite = suite.parent return lineno else: return min(self.goal_scope.get_end() + 1, lines.length()) def get_insertion_resource(self): return self.goal_pymodule.get_resource() def get_insertion_offsets(self): if self.goal_scope.get_kind() == "Class": start, end = sourceutils.get_body_region(self.goal_scope.pyobject) if self.goal_pymodule.source_code[start:end].strip() == "pass": return start, end lines = self.goal_pymodule.lines start = lines.get_line_start(self.get_insertion_lineno()) return (start, start) def get_scope_indents(self): if self.goal_scope.get_kind() == "Module": return 0 return ( sourceutils.get_indents( self.goal_pymodule.lines, self.goal_scope.get_start() ) + 4 ) def get_blank_lines(self): if self.goal_scope.get_kind() == "Module": base_blanks = 2 if self.goal_pymodule.source_code.strip() == "": base_blanks = 0 if self.goal_scope.get_kind() == "Class": base_blanks = 1 if self.goal_scope.get_kind() == "Function": base_blanks = 0 if self.goal_scope == self.source_scope: return (0, base_blanks) return (base_blanks, 0) def get_package(self): primary = self.primary if self.primary is None: return self.pycore.project.get_source_folders()[0] if isinstance(primary.get_object(), pyobjects.PyPackage): return primary.get_object().get_resource() raise exceptions.RefactoringError( "A module/package can be only created in a package." ) def primary_is_found(self): return self.goal_scope is not None def element_already_exists(self): if self.pyname is None or isinstance(self.pyname, pynames.UnboundName): return False return self.get_name() in self.goal_scope.get_defined_names() def get_name(self): return worder.get_name_at(self.resource, self.offset) class _FunctionGenerationInfo(_GenerationInfo): def _get_goal_scope(self): if self.is_constructor(): return self.pyname.get_object().get_scope() if self.is_instance(): return self.pyname.get_object().get_type().get_scope() if self.primary is None: return self._get_source_scope() pyobject = self.primary.get_object() if isinstance(pyobject, pyobjects.PyDefinedObject): return pyobject.get_scope() elif isinstance(pyobject.get_type(), pyobjects.PyClass): return pyobject.get_type().get_scope() def element_already_exists(self): if self.pyname is None or isinstance(self.pyname, pynames.UnboundName): return False return self.get_name() in self.goal_scope.get_defined_names() def is_static_method(self): return self.primary is not None and isinstance( self.primary.get_object(), pyobjects.PyClass ) def is_method(self): return self.primary is not None and isinstance( self.primary.get_object().get_type(), pyobjects.PyClass ) def is_constructor(self): return self.pyname is not None and isinstance( self.pyname.get_object(), pyobjects.PyClass ) def is_instance(self): if self.pyname is None: return False pyobject = self.pyname.get_object() return isinstance(pyobject.get_type(), pyobjects.PyClass) def get_name(self): if self.is_constructor(): return "__init__" if self.is_instance(): return "__call__" return worder.get_name_at(self.resource, self.offset) def get_passed_args(self): result = [] source = self.source_pymodule.source_code finder = worder.Worder(source) if finder.is_a_function_being_called(self.offset): start, end = finder.get_primary_range(self.offset) parens_start, parens_end = finder.get_word_parens_range(end - 1) call = source[start:parens_end] parser = functionutils._FunctionParser(call, False) args, keywords = parser.get_parameters() for arg in args: if self._is_id(arg): result.append(arg) else: result.append("arg%d" % len(result)) for name, value in keywords: result.append(name) return result def _is_id(self, arg): def id_or_underline(c): return c.isalpha() or c == "_" for c in arg: if not id_or_underline(c) and not c.isdigit(): return False return id_or_underline(arg[0]) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1705553002.6939833 rope-1.12.0/rope/refactor/0000775000175000017500000000000014552126153015250 5ustar00lieryanlieryan././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/rope/refactor/__init__.py0000664000175000017500000000411614512700666017366 0ustar00lieryanlieryan"""rope refactor package This package contains modules that perform python refactorings. Refactoring classes perform refactorings in 4 steps: 1. Collect some data for performing the refactoring and use them to construct a refactoring class. Like:: renamer = Rename(project, resource, offset) 2. Some refactorings give you useful information about the refactoring after their construction. Like:: print(renamer.get_old_name()) 3. Give the refactoring class more information about how to perform the refactoring and get the changes this refactoring is going to make. This is done by calling `get_changes` method of the refactoring class. Like:: changes = renamer.get_changes(new_name) 4. You can commit the changes. Like:: project.do(changes) These steps are like the steps IDEs usually do for performing a refactoring. These are the things an IDE does in each step: 1. Construct a refactoring object by giving it information like resource, offset and ... . Some of the refactoring problems (like performing rename refactoring on language keywords) can be reported here. 2. Print some information about the refactoring and ask the user about the information that are necessary for completing the refactoring (like new name). 3. Call the `get_changes` by passing it information asked from the user (if necessary) and get and preview the changes returned by it. 4. perform the refactoring. From ``0.5m5`` release the `get_changes()` method of some time- consuming refactorings take an optional `rope.base.taskhandle. TaskHandle` parameter. You can use this object for stopping or monitoring the progress of refactorings. """ from rope.refactor.importutils import ImportOrganizer # noqa from rope.refactor.topackage import ModuleToPackage # noqa __all__ = [ "rename", "move", "inline", "extract", "restructure", "topackage", "importutils", "usefunction", "change_signature", "encapsulate_field", "introduce_factory", "introduce_parameter", "localtofield", "method_object", "multiproject", ] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/rope/refactor/change_signature.py0000664000175000017500000003217014512700666021136 0ustar00lieryanlieryanimport copy import rope.base.exceptions from rope.base import codeanalyze, evaluate, pyobjects, taskhandle, utils, worder from rope.base.change import ChangeContents, ChangeSet from rope.refactor import functionutils, occurrences class ChangeSignature: def __init__(self, project, resource, offset): self.project = project self.resource = resource self.offset = offset self._set_name_and_pyname() if ( self.pyname is None or self.pyname.get_object() is None or not isinstance(self.pyname.get_object(), pyobjects.PyFunction) ): raise rope.base.exceptions.RefactoringError( "Change method signature should be performed on functions" ) def _set_name_and_pyname(self): self.name = worder.get_name_at(self.resource, self.offset) this_pymodule = self.project.get_pymodule(self.resource) self.primary, self.pyname = evaluate.eval_location2(this_pymodule, self.offset) if self.pyname is None: return pyobject = self.pyname.get_object() if isinstance(pyobject, pyobjects.PyClass) and "__init__" in pyobject: self.pyname = pyobject["__init__"] self.name = "__init__" pyobject = self.pyname.get_object() self.others = None if ( self.name == "__init__" and isinstance(pyobject, pyobjects.PyFunction) and isinstance(pyobject.parent, pyobjects.PyClass) ): pyclass = pyobject.parent self.others = (pyclass.get_name(), pyclass.parent[pyclass.get_name()]) def _change_calls( self, call_changer, in_hierarchy=None, resources=None, handle=taskhandle.DEFAULT_TASK_HANDLE, ): if resources is None: resources = self.project.get_python_files() changes = ChangeSet("Changing signature of <%s>" % self.name) job_set = handle.create_jobset("Collecting Changes", len(resources)) finder = occurrences.create_finder( self.project, self.name, self.pyname, instance=self.primary, in_hierarchy=in_hierarchy and self.is_method(), ) if self.others: name, pyname = self.others constructor_finder = occurrences.create_finder( self.project, name, pyname, only_calls=True ) finder = _MultipleFinders([finder, constructor_finder]) for file in resources: job_set.started_job(file.path) change_calls = _ChangeCallsInModule( self.project, finder, file, call_changer ) changed_file = change_calls.get_changed_module() if changed_file is not None: changes.add_change(ChangeContents(file, changed_file)) job_set.finished_job() return changes def get_args(self): """Get function arguments. Return a list of ``(name, default)`` tuples for all but star and double star arguments. For arguments that don't have a default, `None` will be used. """ return self._definfo().args_with_defaults def is_method(self): pyfunction = self.pyname.get_object() return isinstance(pyfunction.parent, pyobjects.PyClass) @utils.deprecated("Use `ChangeSignature.get_args()` instead") def get_definition_info(self): return self._definfo() def _definfo(self): return functionutils.DefinitionInfo.read(self.pyname.get_object()) @utils.deprecated() def normalize(self): changer = _FunctionChangers( self.pyname.get_object(), self.get_definition_info(), [ArgumentNormalizer()] ) return self._change_calls(changer) @utils.deprecated() def remove(self, index): changer = _FunctionChangers( self.pyname.get_object(), self.get_definition_info(), [ArgumentRemover(index)], ) return self._change_calls(changer) @utils.deprecated() def add(self, index, name, default=None, value=None): changer = _FunctionChangers( self.pyname.get_object(), self.get_definition_info(), [ArgumentAdder(index, name, default, value)], ) return self._change_calls(changer) @utils.deprecated() def inline_default(self, index): changer = _FunctionChangers( self.pyname.get_object(), self.get_definition_info(), [ArgumentDefaultInliner(index)], ) return self._change_calls(changer) @utils.deprecated() def reorder(self, new_ordering): changer = _FunctionChangers( self.pyname.get_object(), self.get_definition_info(), [ArgumentReorderer(new_ordering)], ) return self._change_calls(changer) def get_changes( self, changers, in_hierarchy=False, resources=None, task_handle=taskhandle.DEFAULT_TASK_HANDLE, ): """Get changes caused by this refactoring `changers` is a list of `_ArgumentChanger`. If `in_hierarchy` is `True` the changers are applied to all matching methods in the class hierarchy. `resources` can be a list of `rope.base.resource.File` that should be searched for occurrences; if `None` all python files in the project are searched. """ function_changer = _FunctionChangers( self.pyname.get_object(), self._definfo(), changers ) return self._change_calls( function_changer, in_hierarchy, resources, task_handle ) class _FunctionChangers: def __init__(self, pyfunction, definition_info, changers=None): self.pyfunction = pyfunction self.definition_info = definition_info self.changers = changers self.changed_definition_infos = self._get_changed_definition_infos() def _get_changed_definition_infos(self): definition_info = self.definition_info result = [definition_info] for changer in self.changers: definition_info = copy.deepcopy(definition_info) changer.change_definition_info(definition_info) result.append(definition_info) return result def change_definition(self, call): return self.changed_definition_infos[-1].to_string() def change_call(self, primary, pyname, call): call_info = functionutils.CallInfo.read( primary, pyname, self.definition_info, call ) mapping = functionutils.ArgumentMapping(self.definition_info, call_info) for definition_info, changer in zip( self.changed_definition_infos, self.changers ): changer.change_argument_mapping(definition_info, mapping) return mapping.to_call_info(self.changed_definition_infos[-1]).to_string() class _ArgumentChanger: def change_definition_info(self, definition_info): pass def change_argument_mapping(self, definition_info, argument_mapping): pass class ArgumentNormalizer(_ArgumentChanger): pass class ArgumentRemover(_ArgumentChanger): def __init__(self, index): self.index = index def change_definition_info(self, call_info): if self.index < len(call_info.args_with_defaults): del call_info.args_with_defaults[self.index] elif ( self.index == len(call_info.args_with_defaults) and call_info.args_arg is not None ): call_info.args_arg = None elif ( self.index == len(call_info.args_with_defaults) and call_info.args_arg is None and call_info.keywords_arg is not None ) or ( self.index == len(call_info.args_with_defaults) + 1 and call_info.args_arg is not None and call_info.keywords_arg is not None ): call_info.keywords_arg = None def change_argument_mapping(self, definition_info, mapping): if self.index < len(definition_info.args_with_defaults): name = definition_info.args_with_defaults[0] if name in mapping.param_dict: del mapping.param_dict[name] class ArgumentAdder(_ArgumentChanger): def __init__(self, index, name, default=None, value=None): self.index = index self.name = name self.default = default self.value = value def change_definition_info(self, definition_info): for pair in definition_info.args_with_defaults: if pair[0] == self.name: raise rope.base.exceptions.RefactoringError( "Adding duplicate parameter: <%s>." % self.name ) definition_info.args_with_defaults.insert(self.index, (self.name, self.default)) def change_argument_mapping(self, definition_info, mapping): if self.value is not None: mapping.param_dict[self.name] = self.value class ArgumentDefaultInliner(_ArgumentChanger): def __init__(self, index): self.index = index self.remove = False def change_definition_info(self, definition_info): if self.remove: definition_info.args_with_defaults[self.index] = ( definition_info.args_with_defaults[self.index][0], None, ) def change_argument_mapping(self, definition_info, mapping): default = definition_info.args_with_defaults[self.index][1] name = definition_info.args_with_defaults[self.index][0] if default is not None and name not in mapping.param_dict: mapping.param_dict[name] = default class ArgumentReorderer(_ArgumentChanger): def __init__(self, new_order, autodef=None): """Construct an `ArgumentReorderer` Note that the `new_order` is a list containing the new position of parameters; not the position each parameter is going to be moved to. (changed in ``0.5m4``) For example changing ``f(a, b, c)`` to ``f(c, a, b)`` requires passing ``[2, 0, 1]`` and *not* ``[1, 2, 0]``. The `autodef` (automatic default) argument, forces rope to use it as a default if a default is needed after the change. That happens when an argument without default is moved after another that has a default value. Note that `autodef` should be a string or `None`; the latter disables adding automatic default. """ self.new_order = new_order self.autodef = autodef def change_definition_info(self, definition_info): new_args = list(definition_info.args_with_defaults) for new_index, index in enumerate(self.new_order): new_args[new_index] = definition_info.args_with_defaults[index] seen_default = False for index, (arg, default) in enumerate(list(new_args)): if default is not None: seen_default = True if seen_default and default is None and self.autodef is not None: new_args[index] = (arg, self.autodef) definition_info.args_with_defaults = new_args class _ChangeCallsInModule: def __init__(self, project, occurrence_finder, resource, call_changer): self.project = project self.occurrence_finder = occurrence_finder self.resource = resource self.call_changer = call_changer def get_changed_module(self): word_finder = worder.Worder(self.source) change_collector = codeanalyze.ChangeCollector(self.source) for occurrence in self.occurrence_finder.find_occurrences(self.resource): if not occurrence.is_called() and not occurrence.is_defined(): continue start, end = occurrence.get_primary_range() begin_parens, end_parens = word_finder.get_word_parens_range(end - 1) if occurrence.is_called(): primary, pyname = occurrence.get_primary_and_pyname() changed_call = self.call_changer.change_call( primary, pyname, self.source[start:end_parens] ) else: changed_call = self.call_changer.change_definition( self.source[start:end_parens] ) if changed_call is not None: change_collector.add_change(start, end_parens, changed_call) return change_collector.get_changed() @property @utils.saveit def pymodule(self): return self.project.get_pymodule(self.resource) @property @utils.saveit def source(self): if self.resource is not None: return self.resource.read() else: return self.pymodule.source_code @property @utils.saveit def lines(self): return self.pymodule.lines class _MultipleFinders: def __init__(self, finders): self.finders = finders def find_occurrences(self, resource=None, pymodule=None): all_occurrences = [] for finder in self.finders: all_occurrences.extend(finder.find_occurrences(resource, pymodule)) all_occurrences.sort(key=lambda x: x.get_primary_range()) return all_occurrences ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/rope/refactor/encapsulate_field.py0000664000175000017500000002041314512700666021274 0ustar00lieryanlieryanfrom rope.base import evaluate, exceptions, libutils, pynames, taskhandle, utils, worder from rope.base.change import ChangeContents, ChangeSet from rope.refactor import occurrences, sourceutils class EncapsulateField: def __init__(self, project, resource, offset): self.project = project self.name = worder.get_name_at(resource, offset) this_pymodule = self.project.get_pymodule(resource) self.pyname = evaluate.eval_location(this_pymodule, offset) if not self._is_an_attribute(self.pyname): raise exceptions.RefactoringError( "Encapsulate field should be performed on class attributes." ) self.resource = self.pyname.get_definition_location()[0].get_resource() def get_changes( self, getter=None, setter=None, resources=None, task_handle=taskhandle.DEFAULT_TASK_HANDLE, ): """Get the changes this refactoring makes If `getter` is not `None`, that will be the name of the getter, otherwise ``get_${field_name}`` will be used. The same is true for `setter` and if it is None set_${field_name} is used. `resources` can be a list of `rope.base.resource.File` that the refactoring should be applied on; if `None` all python files in the project are searched. """ if resources is None: resources = self.project.get_python_files() changes = ChangeSet("Encapsulate field <%s>" % self.name) job_set = task_handle.create_jobset("Collecting Changes", len(resources)) if getter is None: getter = "get_" + self.name if setter is None: setter = "set_" + self.name renamer = GetterSetterRenameInModule( self.project, self.name, self.pyname, getter, setter ) for file in resources: job_set.started_job(file.path) if file == self.resource: result = self._change_holding_module(changes, renamer, getter, setter) changes.add_change(ChangeContents(self.resource, result)) else: result = renamer.get_changed_module(file) if result is not None: changes.add_change(ChangeContents(file, result)) job_set.finished_job() return changes def get_field_name(self): """Get the name of the field to be encapsulated""" return self.name def _is_an_attribute(self, pyname): if pyname is not None and isinstance(pyname, pynames.AssignedName): pymodule, lineno = self.pyname.get_definition_location() scope = pymodule.get_scope().get_inner_scope_for_line(lineno) if scope.get_kind() == "Class": return pyname in scope.get_names().values() parent = scope.parent if parent is not None and parent.get_kind() == "Class": return pyname in parent.get_names().values() return False def _get_defining_class_scope(self): defining_scope = self._get_defining_scope() if defining_scope.get_kind() == "Function": defining_scope = defining_scope.parent return defining_scope def _get_defining_scope(self): pymodule, line = self.pyname.get_definition_location() return pymodule.get_scope().get_inner_scope_for_line(line) def _change_holding_module(self, changes, renamer, getter, setter): pymodule = self.project.get_pymodule(self.resource) class_scope = self._get_defining_class_scope() defining_object = self._get_defining_scope().pyobject start, end = sourceutils.get_body_region(defining_object) new_source = renamer.get_changed_module( pymodule=pymodule, skip_start=start, skip_end=end ) if new_source is not None: pymodule = libutils.get_string_module( self.project, new_source, self.resource ) class_scope = pymodule.get_scope().get_inner_scope_for_line( class_scope.get_start() ) indents = sourceutils.get_indent(self.project) * " " getter = f"def {getter}(self):\n{indents}return self.{self.name}" setter = f"def {setter}(self, value):\n{indents}self.{self.name} = value" new_source = sourceutils.add_methods(pymodule, class_scope, [getter, setter]) return new_source class GetterSetterRenameInModule: def __init__(self, project, name, pyname, getter, setter): self.project = project self.name = name self.finder = occurrences.create_finder(project, name, pyname) self.getter = getter self.setter = setter def get_changed_module( self, resource=None, pymodule=None, skip_start=0, skip_end=0 ): change_finder = _FindChangesForModule( self, resource, pymodule, skip_start, skip_end ) return change_finder.get_changed_module() class _FindChangesForModule: def __init__(self, finder, resource, pymodule, skip_start, skip_end): self.project = finder.project self.finder = finder.finder self.getter = finder.getter self.setter = finder.setter self.resource = resource self.pymodule = pymodule self.last_modified = 0 self.last_set = None self.set_index = None self.skip_start = skip_start self.skip_end = skip_end def get_changed_module(self): result = [] for occurrence in self.finder.find_occurrences(self.resource, self.pymodule): start, end = occurrence.get_word_range() if self.skip_start <= start < self.skip_end: continue self._manage_writes(start, result) result.append(self.source[self.last_modified : start]) if self._is_assigned_in_a_tuple_assignment(occurrence): raise exceptions.RefactoringError( "Cannot handle tuple assignments in encapsulate field." ) if occurrence.is_written(): assignment_type = self.worder.get_assignment_type(start) if assignment_type == "=": result.append(self.setter + "(") else: var_name = ( self.source[occurrence.get_primary_range()[0] : start] + self.getter + "()" ) result.append( self.setter + "(" + var_name + " %s " % assignment_type[:-1] ) current_line = self.lines.get_line_number(start) start_line, end_line = self.pymodule.logical_lines.logical_line_in( current_line ) self.last_set = self.lines.get_line_end(end_line) end = self.source.index("=", end) + 1 self.set_index = len(result) else: result.append(self.getter + "()") self.last_modified = end if self.last_modified != 0: self._manage_writes(len(self.source), result) result.append(self.source[self.last_modified :]) return "".join(result) return None def _manage_writes(self, offset, result): if self.last_set is not None and self.last_set <= offset: result.append(self.source[self.last_modified : self.last_set]) set_value = "".join(result[self.set_index :]).strip() del result[self.set_index :] result.append(set_value + ")") self.last_modified = self.last_set self.last_set = None def _is_assigned_in_a_tuple_assignment(self, occurrence): offset = occurrence.get_word_range()[0] return self.worder.is_assigned_in_a_tuple_assignment(offset) @property @utils.saveit def source(self): if self.resource is not None: return self.resource.read() else: return self.pymodule.source_code @property @utils.saveit def lines(self): if self.pymodule is None: self.pymodule = self.project.get_pymodule(self.resource) return self.pymodule.lines @property @utils.saveit def worder(self): return worder.Worder(self.source) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1705551350.0 rope-1.12.0/rope/refactor/extract.py0000664000175000017500000012156014552122766017307 0ustar00lieryanlieryanimport re from contextlib import contextmanager from itertools import chain from typing import Dict from rope.base import ast, codeanalyze from rope.base.change import ChangeContents, ChangeSet from rope.base.exceptions import RefactoringError from rope.base.utils.datastructures import OrderedSet from rope.refactor import patchedast, similarfinder, sourceutils, suites, usefunction # Extract refactoring has lots of special cases. I tried to split it # to smaller parts to make it more manageable: # # _ExtractInfo: holds information about the refactoring; it is passed # to the parts that need to have information about the refactoring # # _ExtractCollector: merely saves all of the information necessary for # performing the refactoring. # # _DefinitionLocationFinder: finds where to insert the definition. # # _ExceptionalConditionChecker: checks for exceptional conditions in # which the refactoring cannot be applied. # # _ExtractMethodParts: generates the pieces of code (like definition) # needed for performing extract method. # # _ExtractVariableParts: like _ExtractMethodParts for variables. # # _ExtractPerformer: Uses above classes to collect refactoring # changes. # # There are a few more helper functions and classes used by above # classes. class _ExtractRefactoring: kind_prefixes: Dict[str, str] = {} def __init__(self, project, resource, start_offset, end_offset, variable=False): self.project = project self.resource = resource self.start_offset = self._fix_start(resource.read(), start_offset) self.end_offset = self._fix_end(resource.read(), end_offset) def _fix_start(self, source, offset): while offset < len(source) and source[offset].isspace(): offset += 1 return offset def _fix_end(self, source, offset): while offset > 0 and source[offset - 1].isspace(): offset -= 1 return offset def get_changes(self, extracted_name, similar=False, global_=False, kind=None): """Get the changes this refactoring makes :parameters: - `extracted_name`: target name, when starts with @ - set kind to classmethod, $ - staticmethod - `similar`: if `True`, similar expressions/statements are also replaced. - `global_`: if `True`, the extracted method/variable will be global. - `kind`: kind of target refactoring to (staticmethod, classmethod) """ extracted_name, kind = self._get_kind_from_name(extracted_name, kind) info = _ExtractInfo( self.project, self.resource, self.start_offset, self.end_offset, extracted_name, variable=self._get_kind(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 def _get_kind_from_name(self, extracted_name, kind): for sign, selected_kind in self.kind_prefixes.items(): if extracted_name.startswith(sign): self._validate_kind_prefix(kind, selected_kind) return extracted_name[1:], selected_kind return extracted_name, kind @staticmethod def _validate_kind_prefix(kind, selected_kind): if kind and kind != selected_kind: raise RefactoringError("Kind and shortcut in name mismatch") @classmethod def _get_kind(cls, kind): raise NotImplementedError(f"You have to subclass {cls}") class ExtractMethod(_ExtractRefactoring): kind = "method" allowed_kinds = ("function", "method", "staticmethod", "classmethod") kind_prefixes = {"@": "classmethod", "$": "staticmethod"} @classmethod def _get_kind(cls, kind): return kind if kind in cls.allowed_kinds else cls.kind class ExtractVariable(_ExtractRefactoring): def __init__(self, *args, **kwds): kwds = dict(kwds) kwds["variable"] = True super().__init__(*args, **kwds) kind = "variable" def _get_kind(cls, kind): return cls.kind class _ExtractInfo: """Holds information about the extract to be performed""" def __init__( self, project, resource, start, end, new_name, variable, similar, make_global ): self.project = project self.resource = resource self.pymodule = project.get_pymodule(resource) self.global_scope = self.pymodule.get_scope() self.source = self.pymodule.source_code self.lines = self.pymodule.lines self.new_name = new_name self.variable = variable self.similar = similar self._init_parts(start, end) self.kind = None self._init_scope() self.make_global = make_global def _init_parts(self, start, end): self.region = ( self._choose_closest_line_end(start), self._choose_closest_line_end(end, end=True), ) start = self.logical_lines.logical_line_in( self.lines.get_line_number(self.region[0]) )[0] end = self.logical_lines.logical_line_in( self.lines.get_line_number(self.region[1]) )[1] self.region_lines = (start, end) self.lines_region = ( self.lines.get_line_start(self.region_lines[0]), self.lines.get_line_end(self.region_lines[1]), ) @property def logical_lines(self): return self.pymodule.logical_lines def _init_scope(self): start_line = self.region_lines[0] scope = self.global_scope.get_inner_scope_for_line(start_line) if scope.get_kind() != "Module" and scope.get_start() == start_line: scope = scope.parent self.scope = scope self.scope_region = self._get_scope_region(self.scope) def _get_scope_region(self, scope): return ( self.lines.get_line_start(scope.get_start()), self.lines.get_line_end(scope.get_end()) + 1, ) def _choose_closest_line_end(self, offset, end=False): lineno = self.lines.get_line_number(offset) line_start = self.lines.get_line_start(lineno) line_end = self.lines.get_line_end(lineno) if self.source[line_start:offset].strip() == "": if end: return line_start - 1 else: return line_start elif self.source[offset:line_end].strip() == "": return min(line_end, len(self.source)) return offset @property def one_line(self): return self.region != self.lines_region and ( self.logical_lines.logical_line_in(self.region_lines[0]) == self.logical_lines.logical_line_in(self.region_lines[1]) ) @property def global_(self): return self.scope.parent is None @property def method(self): return self.scope.parent is not None and self.scope.parent.get_kind() == "Class" @property def indents(self): return sourceutils.get_indents(self.pymodule.lines, self.region_lines[0]) @property def scope_indents(self): if self.global_: return 0 return sourceutils.get_indents(self.pymodule.lines, self.scope.get_start()) @property def extracted(self): return self.source[self.region[0] : self.region[1]] _cached_parsed_extracted = None @property def _parsed_extracted(self): if self._cached_parsed_extracted is None: self._cached_parsed_extracted = _parse_text(self.extracted) return self._cached_parsed_extracted _returned = None @property def returned(self): """Does the extracted piece contain return statement""" if self._returned is None: self._returned = usefunction._returns_last(self._parsed_extracted) return self._returned _returning_named_expr = None @property def returning_named_expr(self): """Does the extracted piece contains named expression/:= operator)""" if self._returning_named_expr is None: self._returning_named_expr = usefunction._namedexpr_last( self._parsed_extracted ) return self._returning_named_expr _returning_generator = None @property def returning_generator_exp(self): """Does the extracted piece contains a generator expression""" if self._returning_generator is None: self._returning_generator = ( isinstance(self._parsed_extracted, ast.Module) and isinstance(self._parsed_extracted.body[0], ast.Expr) and isinstance(self._parsed_extracted.body[0].value, ast.GeneratorExp) ) return self._returning_generator class _ExtractCollector: """Collects information needed for performing the extract""" def __init__(self, info): self.definition = None self.body_pattern = None self.checks = {} self.replacement_pattern = None self.matches = None self.replacements = None self.definition_location = None class _ExtractPerformer: def __init__(self, info): self.info = info _ExceptionalConditionChecker()(self.info) def extract(self): extract_info = self._collect_info() content = codeanalyze.ChangeCollector(self.info.source) definition = extract_info.definition lineno, indents = extract_info.definition_location offset = self.info.lines.get_line_start(lineno) indented = sourceutils.fix_indentation(definition, indents) content.add_change(offset, offset, indented) self._replace_occurrences(content, extract_info) return content.get_changed() def _replace_occurrences(self, content, extract_info): for match in extract_info.matches: replacement = similarfinder.CodeTemplate(extract_info.replacement_pattern) mapping = {} for name in replacement.get_names(): node = match.get_ast(name) if node: start, end = patchedast.node_region(match.get_ast(name)) mapping[name] = self.info.source[start:end] else: mapping[name] = name region = match.get_region() content.add_change(region[0], region[1], replacement.substitute(mapping)) def _collect_info(self): extract_collector = _ExtractCollector(self.info) self._find_definition(extract_collector) self._find_matches(extract_collector) self._find_definition_location(extract_collector) return extract_collector def _find_matches(self, collector): regions = self._where_to_search() finder = similarfinder.SimilarFinder(self.info.pymodule) matches = [] for start, end in regions: region_matches = finder.get_matches( collector.body_pattern, collector.checks, start, end ) # Don't extract overlapping regions last_match_end = -1 for region_match in region_matches: if self.info.one_line and self._is_assignment(region_match): continue start, end = region_match.get_region() if last_match_end < start: matches.append(region_match) last_match_end = end collector.matches = matches @staticmethod def _is_assignment(region_match): return isinstance( region_match.ast, (ast.Attribute, ast.Subscript) ) and isinstance(region_match.ast.ctx, ast.Store) def _where_to_search(self): if self.info.similar: if self.info.make_global or self.info.global_: return [(0, len(self.info.pymodule.source_code))] if self.info.method and not self.info.variable: class_scope = self.info.scope.parent regions = [] method_kind = _get_function_kind(self.info.scope) for scope in class_scope.get_scopes(): if ( method_kind == "method" and _get_function_kind(scope) != "method" ): continue start = self.info.lines.get_line_start(scope.get_start()) end = self.info.lines.get_line_end(scope.get_end()) regions.append((start, end)) return regions else: if self.info.variable: return [self.info.scope_region] else: return [self.info._get_scope_region(self.info.scope.parent)] else: return [self.info.region] def _find_definition_location(self, collector): matched_lines = [] for match in collector.matches: start = self.info.lines.get_line_number(match.get_region()[0]) start_line = self.info.logical_lines.logical_line_in(start)[0] matched_lines.append(start_line) location_finder = _DefinitionLocationFinder(self.info, matched_lines) collector.definition_location = ( location_finder.find_lineno(), location_finder.find_indents(), ) def _find_definition(self, collector): if self.info.variable: parts = _ExtractVariableParts(self.info) else: parts = _ExtractMethodParts(self.info) collector.definition = parts.get_definition() collector.body_pattern = parts.get_body_pattern() collector.replacement_pattern = parts.get_replacement_pattern() collector.checks = parts.get_checks() class _DefinitionLocationFinder: def __init__(self, info, matched_lines): self.info = info self.matched_lines = matched_lines # This only happens when subexpressions cannot be matched if not matched_lines: self.matched_lines.append(self.info.region_lines[0]) def find_lineno(self): if self.info.variable and not self.info.make_global: return self._get_before_line() if self.info.global_: toplevel = self._find_toplevel(self.info.scope) ast = self.info.pymodule.get_ast() newlines = sorted(self.matched_lines + [toplevel.get_end() + 1]) return suites.find_visible(ast, newlines) if self.info.make_global: toplevel = self._find_toplevel(self.info.scope) return toplevel.get_end() + 1 return self._get_after_scope() def _find_toplevel(self, scope): toplevel = scope if toplevel.parent is not None: while toplevel.parent.parent is not None: toplevel = toplevel.parent return toplevel def find_indents(self): if self.info.variable and not self.info.make_global: return sourceutils.get_indents(self.info.lines, self._get_before_line()) else: if self.info.global_ or self.info.make_global: return 0 return self.info.scope_indents def _get_before_line(self): ast = self.info.scope.pyobject.get_ast() return suites.find_visible(ast, self.matched_lines) def _get_after_scope(self): return self.info.scope.get_end() + 1 class _ExceptionalConditionChecker: def __call__(self, info): self.base_conditions(info) if info.one_line: self.one_line_conditions(info) else: self.multi_line_conditions(info) def base_conditions(self, info): if info.region[1] > info.scope_region[1]: raise RefactoringError("Bad region selected for extract method") end_line = info.region_lines[1] end_scope = info.global_scope.get_inner_scope_for_line(end_line) if end_scope != info.scope and end_scope.get_end() != end_line: raise RefactoringError("Bad region selected for extract method") try: extracted = info.extracted if info.one_line: extracted = "(%s)" % extracted if _UnmatchedBreakOrContinueFinder.has_errors(extracted): raise RefactoringError( "A break/continue without having a matching for/while loop." ) except SyntaxError: raise RefactoringError( "Extracted piece should contain complete statements." ) def one_line_conditions(self, info): if self._is_region_on_a_word(info): raise RefactoringError("Should extract complete statements.") if info.variable and not info.one_line: raise RefactoringError("Extract variable should not span multiple lines.") if usefunction._named_expr_count( info._parsed_extracted ) - usefunction._namedexpr_last(info._parsed_extracted): raise RefactoringError( "Extracted piece cannot contain named expression (:= operator)." ) def multi_line_conditions(self, info): node = _parse_text(info.source[info.region[0] : info.region[1]]) count = usefunction._return_count(node) extracted = info.extracted if count > 1: raise RefactoringError( "Extracted piece can have only one return statement." ) if usefunction._yield_count(node): raise RefactoringError("Extracted piece cannot have yield statements.") if not hasattr( ast, "PyCF_ALLOW_TOP_LEVEL_AWAIT" ) and _AsyncStatementFinder.has_errors(extracted): raise RefactoringError( "Extracted piece can only have async/await " "statements if Rope is running on Python " "3.8 or higher" ) if count == 1 and not usefunction._returns_last(node): raise RefactoringError("Return should be the last statement.") if info.region != info.lines_region: raise RefactoringError( "Extracted piece should contain complete statements." ) unbalanced_region_finder = _UnbalancedRegionFinder( info.region_lines[0], info.region_lines[1] ) unbalanced_region_finder.visit(info.pymodule.ast_node) if unbalanced_region_finder.error: raise RefactoringError( "Extracted piece cannot contain the start of a block without the end." ) def _is_region_on_a_word(self, info): if ( info.region[0] > 0 and self._is_on_a_word(info, info.region[0] - 1) or self._is_on_a_word(info, info.region[1] - 1) ): return True def _is_on_a_word(self, info, offset): prev = info.source[offset] if not (prev.isalnum() or prev == "_") or offset + 1 == len(info.source): return False next = info.source[offset + 1] return next.isalnum() or next == "_" class _ExtractMethodParts(ast.RopeNodeVisitor): def __init__(self, info): self.info = info self.info_collector = self._create_info_collector() self.info.kind = self._get_kind_by_scope() self._check_constraints() def _get_kind_by_scope(self): if self._extacting_from_staticmethod(): return "staticmethod" elif self._extracting_from_classmethod(): return "classmethod" return self.info.kind def _check_constraints(self): if self._extracting_staticmethod() or self._extracting_classmethod(): if not self.info.method: raise RefactoringError( "Cannot extract to staticmethod/classmethod outside class" ) def _extacting_from_staticmethod(self): return ( self.info.method and _get_function_kind(self.info.scope) == "staticmethod" ) def _extracting_from_classmethod(self): return self.info.method and _get_function_kind(self.info.scope) == "classmethod" def get_definition(self): if self.info.global_: return "\n%s\n" % self._get_function_definition() else: return "\n%s" % self._get_function_definition() def get_replacement_pattern(self): variables = [] variables.extend(self._find_function_arguments()) variables.extend(self._find_function_returns()) return similarfinder.make_pattern(self._get_call(), variables) def get_body_pattern(self): variables = [] variables.extend(self._find_function_arguments()) variables.extend(self._find_function_returns()) variables.extend(self._find_temps()) return similarfinder.make_pattern(self._get_body(), variables) def _get_body(self): result = sourceutils.fix_indentation(self.info.extracted, 0) if self.info.one_line: result = "(%s)" % result return result def _find_temps(self): return usefunction.find_temps(self.info.project, self._get_body()) def get_checks(self): if self.info.method and not self.info.make_global: if _get_function_kind(self.info.scope) == "method": class_name = similarfinder._pydefined_to_str( self.info.scope.parent.pyobject ) return {self._get_self_name(): "type=" + class_name} return {} def _create_info_collector(self): zero = self.info.scope.get_start() - 1 start_line = self.info.region_lines[0] - zero end_line = self.info.region_lines[1] - zero info_collector = _FunctionInformationCollector( start_line, end_line, self.info.global_ ) body = self.info.source[self.info.scope_region[0] : self.info.scope_region[1]] node = _parse_text(body) info_collector.visit(node) return info_collector def _get_function_definition(self): args = self._find_function_arguments() returns = self._find_function_returns() result = [] self._append_decorators(result) result.append("def %s:\n" % self._get_function_signature(args)) unindented_body = self._get_unindented_function_body(returns) indents = sourceutils.get_indent(self.info.project) function_body = sourceutils.indent_lines(unindented_body, indents) result.append(function_body) definition = "".join(result) return definition + "\n" def _append_decorators(self, result): if self._extracting_staticmethod(): result.append("@staticmethod\n") elif self._extracting_classmethod(): result.append("@classmethod\n") def _extracting_classmethod(self): return self.info.kind == "classmethod" def _extracting_staticmethod(self): return self.info.kind == "staticmethod" def _get_function_signature(self, args): args = list(args) prefix = "" if self._extracting_method() or self._extracting_classmethod(): self_name = self._get_self_name() if self_name is None: raise RefactoringError( "Extracting a method from a function with no self argument." ) if self_name in args: args.remove(self_name) args.insert(0, self_name) return prefix + self.info.new_name + "(%s)" % self._get_comma_form(args) def _extracting_method(self): return not self._extracting_staticmethod() and ( self.info.method and not self.info.make_global and _get_function_kind(self.info.scope) == "method" ) def _get_self_name(self): if self._extracting_classmethod(): return "cls" return self._get_scope_self_name() def _get_scope_self_name(self): if self.info.scope.pyobject.get_kind() == "staticmethod": return param_names = self.info.scope.pyobject.get_param_names() if param_names: return param_names[0] def _get_function_call(self, args): return "{prefix}{name}({args})".format( prefix=self._get_function_call_prefix(args), name=self.info.new_name, args=self._get_comma_form(args), ) def _get_function_call_prefix(self, args): prefix = "" if self.info.method and not self.info.make_global: if self._extracting_staticmethod() or self._extracting_classmethod(): prefix = self.info.scope.parent.pyobject.get_name() + "." else: self_name = self._get_self_name() if self_name in args: args.remove(self_name) prefix = self_name + "." return prefix def _get_comma_form(self, names): return ", ".join(names) def _get_call(self): args = self._find_function_arguments() returns = self._find_function_returns() call_prefix = "" if returns and (not self.info.one_line or self.info.returning_named_expr): assignment_operator = " := " if self.info.one_line else " = " call_prefix = self._get_comma_form(returns) + assignment_operator if self.info.returned: call_prefix = "return " return call_prefix + self._get_function_call(args) def _find_function_arguments(self): # 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 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 result |= ( self.info_collector.prewritten & self.info_collector.postread & (self.info_collector.maybe_written - self.info_collector.written) ) return list(result) start = self.info.region[0] if start == self.info.lines_region[0]: start = start + re.search("\\S", self.info.extracted).start() function_definition = self.info.source[start : self.info.region[1]] read = _VariableReadsAndWritesFinder.find_reads_for_one_liners( function_definition ) return list(self.info_collector.prewritten.intersection(read)) def _find_function_returns(self): if self.info.one_line: written = self.info_collector.written | self.info_collector.maybe_written return list(written & self.info_collector.postread) if self.info.returned: return [] written = self.info_collector.written | self.info_collector.maybe_written return list(written & self.info_collector.postread) def _get_unindented_function_body(self, returns): if self.info.one_line: return self._get_single_expression_function_body() return self._get_multiline_function_body(returns) def _get_multiline_function_body(self, returns): unindented_body = sourceutils.fix_indentation(self.info.extracted, 0) unindented_body = self._insert_globals(unindented_body) unindented_body = self._insert_nonlocals(unindented_body) if returns: unindented_body += "\nreturn %s" % self._get_comma_form(returns) return unindented_body def _get_single_expression_function_body(self): extracted = _get_single_expression_body(self.info.extracted, info=self.info) body = "return " + extracted body = self._insert_globals(body) body = self._insert_nonlocals(body) return body def _insert_globals(self, unindented_body): globals_in_body = self._get_globals_in_body(unindented_body) globals_ = self.info_collector.globals_ & ( self.info_collector.written | self.info_collector.maybe_written ) globals_ = globals_ - globals_in_body if globals_: unindented_body = "global {}\n{}".format( ", ".join(globals_), unindented_body ) return unindented_body def _insert_nonlocals(self, unindented_body): nonlocals_in_body = self._get_nonlocals_in_body(unindented_body) nonlocals_ = self.info_collector.nonlocals_ & ( self.info_collector.written | self.info_collector.maybe_written ) nonlocals_ = nonlocals_ - nonlocals_in_body if nonlocals_: unindented_body = "nonlocal {}\n{}".format( ", ".join(nonlocals_), unindented_body ) return unindented_body @staticmethod def _get_globals_in_body(unindented_body): node = _parse_text(unindented_body) visitor = _GlobalFinder() visitor.visit(node) return visitor.globals_ @staticmethod def _get_nonlocals_in_body(unindented_body): node = _parse_text(unindented_body) visitor = _NonlocalFinder() visitor.visit(node) return visitor.nonlocals_ class _ExtractVariableParts: def __init__(self, info): self.info = info def get_definition(self): extracted = _get_single_expression_body(self.info.extracted, info=self.info) return self.info.new_name + " = " + extracted + "\n" def get_body_pattern(self): return "(%s)" % self.info.extracted.strip() def get_replacement_pattern(self): return self.info.new_name def get_checks(self): return {} class _FunctionInformationCollector(ast.RopeNodeVisitor): def __init__(self, start, end, is_global): self.start = start self.end = end self.is_global = is_global self.prewritten = OrderedSet() self.maybe_written = OrderedSet() self.written = OrderedSet() self.read = OrderedSet() self.postread = OrderedSet() self.postwritten = OrderedSet() self.host_function = True self.conditional = False self.globals_ = OrderedSet() self.nonlocals_ = OrderedSet() self.surrounded_by_loop = 0 self.loop_depth = 0 def _read_variable(self, name, lineno): if self.start <= lineno <= self.end: if name not in self.written: if not self.conditional or name not in self.maybe_written: self.read.add(name) if self.end < lineno: if name not in self.postwritten: self.postread.add(name) def _written_variable(self, name, lineno): if self.start <= lineno <= self.end: if self.conditional: self.maybe_written.add(name) else: self.written.add(name) if self.loop_depth > 0 and name in self.read: self.postread.add(name) if self.start > lineno: self.prewritten.add(name) if self.end < lineno: self.postwritten.add(name) def _FunctionDef(self, node): if not self.is_global and self.host_function: self.host_function = False for name in _get_argnames(node.args): self._written_variable(name, node.lineno) for child in node.body: self.visit(child) else: self._written_variable(node.name, node.lineno) visitor = _VariableReadsAndWritesFinder() for child in node.body: visitor.visit(child) for name in visitor.read - visitor.written: self._read_variable(name, node.lineno) def _Global(self, node): self.globals_.add(*node.names) def _Nonlocal(self, node): self.nonlocals_.add(*node.names) def _AsyncFunctionDef(self, node): self._FunctionDef(node) def _Name(self, node): if isinstance(node.ctx, (ast.Store, ast.AugStore)): self._written_variable(node.id, node.lineno) if not isinstance(node.ctx, ast.Store): self._read_variable(node.id, node.lineno) def _MatchAs(self, node): self._written_variable(node.name, node.lineno) if node.pattern: self.visit(node.pattern) def _Assign(self, node): self.visit(node.value) for child in node.targets: self.visit(child) def _AugAssign(self, node): self.visit(node.value) if isinstance(node.target, ast.Name): target_id = node.target.id self._read_variable(target_id, node.target.lineno) self._written_variable(target_id, node.target.lineno) else: self.visit(node.target) def _ClassDef(self, node): self._written_variable(node.name, node.lineno) def _ListComp(self, node): self._comp_exp(node) def _GeneratorExp(self, node): self._comp_exp(node) def _SetComp(self, node): self._comp_exp(node) def _DictComp(self, node): self._comp_exp(node) def _comp_exp(self, node): read = OrderedSet(self.read) written = OrderedSet(self.written) maybe_written = OrderedSet(self.maybe_written) for child in ast.iter_child_nodes(node): self.visit(child) comp_names = list( chain.from_iterable( self._flatten_nested_tuple_of_names(generator.target) for generator in node.generators ) ) self.read = self.read - comp_names | read self.written = self.written - comp_names | written self.maybe_written = self.maybe_written - comp_names | maybe_written def _flatten_nested_tuple_of_names(self, node): if isinstance(node, ast.Tuple): for elt in node.elts: yield self._flatten_nested_tuple_of_names(elt) elif isinstance(node, ast.Name): yield node.id else: assert False, f"Unexpected node type in list comprehension target: {node!r}" def _If(self, node): self._handle_conditional_node(node) def _While(self, node): with self._handle_loop_context(node): self._handle_conditional_node(node) def _For(self, node): with self._handle_loop_context(node), self._handle_conditional_context(node): # iter has to be checked before the target variables self.visit(node.iter) self.visit(node.target) for child in node.body: self.visit(child) for child in node.orelse: self.visit(child) def _handle_conditional_node(self, node): with self._handle_conditional_context(node): for child in ast.iter_child_nodes(node): self.visit(child) @contextmanager def _handle_conditional_context(self, node): if self.start <= node.lineno <= self.end: self.conditional = True try: yield finally: self.conditional = False @contextmanager def _handle_loop_context(self, node): if node.lineno < self.start: self.loop_depth += 1 try: yield finally: self.loop_depth -= 1 def _get_argnames(arguments): result = [] result.extend(node.arg for node in getattr(arguments, "posonlyargs", [])) result.extend(node.arg for node in arguments.args) if arguments.vararg: result.append(arguments.vararg.arg) if arguments.kwarg: result.append(arguments.kwarg.arg) result.extend(node.arg for node in arguments.kwonlyargs) return result class _VariableReadsAndWritesFinder(ast.RopeNodeVisitor): def __init__(self): self.written = set() self.read = set() def _Name(self, node): if isinstance(node.ctx, (ast.Store, ast.AugStore)): self.written.add(node.id) if not isinstance(node, ast.Store): self.read.add(node.id) def _FunctionDef(self, node): self.written.add(node.name) visitor = _VariableReadsAndWritesFinder() for child in ast.iter_child_nodes(node): visitor.visit(child) self.read.update(visitor.read - visitor.written) def _Class(self, node): self.written.add(node.name) @staticmethod def find_reads_and_writes(code): if code.strip() == "": return set(), set() node = _parse_text(code) visitor = _VariableReadsAndWritesFinder() visitor.visit(node) return visitor.read, visitor.written @staticmethod def find_reads_for_one_liners(code): if code.strip() == "": return set(), set() node = _parse_text(code) visitor = _VariableReadsAndWritesFinder() visitor.visit(node) return visitor.read class _BaseErrorFinder(ast.RopeNodeVisitor): @classmethod def has_errors(cls, code): if code.strip() == "": return False node = _parse_text(code) visitor = cls() visitor.visit(node) return visitor.error class _UnmatchedBreakOrContinueFinder(_BaseErrorFinder): def __init__(self): self.error = False self.loop_count = 0 def _For(self, node): self.loop_encountered(node) def _While(self, node): self.loop_encountered(node) def loop_encountered(self, node): self.loop_count += 1 for child in node.body: self.visit(child) self.loop_count -= 1 if node.orelse: if isinstance(node.orelse, (list, tuple)): for node_ in node.orelse: self.visit(node_) else: self.visit(node.orelse) def _Break(self, node): self.check_loop() def _Continue(self, node): self.check_loop() def check_loop(self): if self.loop_count < 1: self.error = True def _FunctionDef(self, node): pass def _ClassDef(self, node): pass class _AsyncStatementFinder(_BaseErrorFinder): def __init__(self): self.error = False def _AsyncFor(self, node): self.error = True def _AsyncWith(self, node): self.error = True def _FunctionDef(self, node): pass def _ClassDef(self, node): pass class _UnbalancedRegionFinder(_BaseErrorFinder): """ Flag an error if we are including the start of a block without the end. We detect this by ensuring there is no AST node that starts inside the selected range but ends outside of it. """ def __init__(self, line_start: int, line_end: int): self.error = False self.line_start = line_start self.line_end = line_end def generic_visit(self, node: ast.AST): if not hasattr(node, "end_lineno"): super().generic_visit(node) # Visit children return ends_before_range_starts = node.end_lineno < self.line_start starts_after_range_ends = node.lineno > self.line_end if ends_before_range_starts or starts_after_range_ends: return # Don't visit children starts_on_or_after_range_start = node.lineno >= self.line_start ends_after_range_ends = node.end_lineno > self.line_end if starts_on_or_after_range_start and ends_after_range_ends: self.error = True return # Don't visit children super().generic_visit(node) # Visit children class _GlobalFinder(ast.RopeNodeVisitor): def __init__(self): self.globals_ = OrderedSet() def _Global(self, node): self.globals_.add(*node.names) class _NonlocalFinder(ast.RopeNodeVisitor): def __init__(self): self.nonlocals_ = OrderedSet() def _Nonlocal(self, node): self.nonlocals_.add(*node.names) def _get_function_kind(scope): return scope.pyobject.get_kind() def _parse_text(body): body = sourceutils.fix_indentation(body, 0) try: node = ast.parse(body) except SyntaxError: # needed to parse expression containing := operator try: node = ast.parse("(" + body + ")") except SyntaxError: node = ast.parse( "async def __rope_placeholder__():\n" + sourceutils.fix_indentation(body, 4) ) node.body = node.body[0].body return node def _join_lines(code): lines = [] for line in code.splitlines(): if line.endswith("\\"): lines.append(line[:-1].strip()) else: lines.append(line.strip()) return " ".join(lines) def _get_single_expression_body(extracted, info): extracted = sourceutils.fix_indentation(extracted, 0) already_parenthesized = ( extracted.lstrip()[0] in "({[" and extracted.rstrip()[-1] in ")}]" ) large_multiline = extracted.count("\n") >= 2 and already_parenthesized if not large_multiline: extracted = _join_lines(extracted) multiline_expression = "\n" in extracted if ( info.returning_named_expr or info.returning_generator_exp or (multiline_expression and not large_multiline) ): extracted = "(" + extracted + ")" return extracted ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/rope/refactor/functionutils.py0000664000175000017500000002017414512700666020537 0ustar00lieryanlieryanfrom rope.base import pyobjects, worder from rope.base.builtins import Lambda class DefinitionInfo: def __init__( self, function_name, is_method, args_with_defaults, args_arg, keywords_arg ): self.function_name = function_name self.is_method = is_method self.args_with_defaults = args_with_defaults self.args_arg = args_arg self.keywords_arg = keywords_arg def to_string(self): return f"{self.function_name}({self.arguments_to_string()})" def arguments_to_string(self, from_index=0): params = [] for arg, default in self.args_with_defaults: if default is not None: params.append(f"{arg}={default}") else: params.append(arg) if self.args_arg is not None: params.append("*" + self.args_arg) if self.keywords_arg: params.append("**" + self.keywords_arg) return ", ".join(params[from_index:]) @staticmethod def _read(pyfunction, code): kind = pyfunction.get_kind() is_method = kind == "method" is_lambda = kind == "lambda" info = _FunctionParser(code, is_method, is_lambda) args, keywords = info.get_parameters() args_arg = None keywords_arg = None if args and args[-1].startswith("**"): keywords_arg = args[-1][2:] del args[-1] if args and args[-1].startswith("*"): args_arg = args[-1][1:] del args[-1] args_with_defaults = [(name, None) for name in args] args_with_defaults.extend(keywords) return DefinitionInfo( info.get_function_name(), is_method, args_with_defaults, args_arg, keywords_arg, ) @staticmethod def read(pyfunction): pymodule = pyfunction.get_module() word_finder = worder.Worder(pymodule.source_code) lineno = pyfunction.get_ast().lineno start = pymodule.lines.get_line_start(lineno) if isinstance(pyfunction, Lambda): call = word_finder.get_lambda_and_args(start) else: call = word_finder.get_function_and_args_in_header(start) return DefinitionInfo._read(pyfunction, call) class CallInfo: def __init__( self, function_name, args, keywords, args_arg, keywords_arg, implicit_arg, constructor, ): self.function_name = function_name self.args = args self.keywords = keywords self.args_arg = args_arg self.keywords_arg = keywords_arg self.implicit_arg = implicit_arg self.constructor = constructor def to_string(self): function = self.function_name if self.implicit_arg: function = self.args[0] + "." + self.function_name params = [] start = 0 if self.implicit_arg or self.constructor: start = 1 if self.args[start:]: params.extend(self.args[start:]) if self.keywords: params.extend([f"{name}={value}" for name, value in self.keywords]) if self.args_arg is not None: params.append("*" + self.args_arg) if self.keywords_arg: params.append("**" + self.keywords_arg) return "{}({})".format(function, ", ".join(params)) @staticmethod def read(primary, pyname, definition_info, code): is_method_call = CallInfo._is_method_call(primary, pyname) is_constructor = CallInfo._is_class(pyname) is_classmethod = CallInfo._is_classmethod(pyname) info = _FunctionParser(code, is_method_call or is_classmethod) args, keywords = info.get_parameters() args_arg = None keywords_arg = None if args and args[-1].startswith("**"): keywords_arg = args[-1][2:] del args[-1] if args and args[-1].startswith("*"): args_arg = args[-1][1:] del args[-1] if is_constructor: args.insert(0, definition_info.args_with_defaults[0][0]) return CallInfo( info.get_function_name(), args, keywords, args_arg, keywords_arg, is_method_call or is_classmethod, is_constructor, ) @staticmethod def _is_method_call(primary, pyname): return ( primary is not None and isinstance(primary.get_object().get_type(), pyobjects.PyClass) and CallInfo._is_method(pyname) ) @staticmethod def _is_class(pyname): return pyname is not None and isinstance(pyname.get_object(), pyobjects.PyClass) @staticmethod def _is_method(pyname): if pyname is not None and isinstance(pyname.get_object(), pyobjects.PyFunction): return pyname.get_object().get_kind() == "method" return False @staticmethod def _is_classmethod(pyname): if pyname is not None and isinstance(pyname.get_object(), pyobjects.PyFunction): return pyname.get_object().get_kind() == "classmethod" return False class ArgumentMapping: def __init__(self, definition_info, call_info): self.call_info = call_info self.param_dict = {} self.keyword_args = [] self.args_arg = [] for index, value in enumerate(call_info.args): if index < len(definition_info.args_with_defaults): name = definition_info.args_with_defaults[index][0] self.param_dict[name] = value else: self.args_arg.append(value) for name, value in call_info.keywords: index = -1 for pair in definition_info.args_with_defaults: if pair[0] == name: self.param_dict[name] = value break else: self.keyword_args.append((name, value)) def to_call_info(self, definition_info): args = [] keywords = [] for index in range(len(definition_info.args_with_defaults)): name = definition_info.args_with_defaults[index][0] if name in self.param_dict: args.append(self.param_dict[name]) else: for i in range(index, len(definition_info.args_with_defaults)): name = definition_info.args_with_defaults[i][0] if name in self.param_dict: keywords.append((name, self.param_dict[name])) break args.extend(self.args_arg) keywords.extend(self.keyword_args) return CallInfo( self.call_info.function_name, args, keywords, self.call_info.args_arg, self.call_info.keywords_arg, self.call_info.implicit_arg, self.call_info.constructor, ) class _FunctionParser: def __init__(self, call, implicit_arg, is_lambda=False): self.call = call self.implicit_arg = implicit_arg self.word_finder = worder.Worder(self.call) if is_lambda: self.last_parens = self.call.rindex(":") else: self.last_parens = self.call.rindex(")") self.first_parens = self.word_finder._find_parens_start(self.last_parens) def get_parameters(self): args, keywords = self.word_finder.get_parameters( self.first_parens, self.last_parens ) if self.is_called_as_a_method(): instance = self.call[: self.call.rindex(".", 0, self.first_parens)] args.insert(0, instance.strip()) return args, keywords def get_instance(self): if self.is_called_as_a_method(): return self.word_finder.get_primary_at( self.call.rindex(".", 0, self.first_parens) - 1 ) def get_function_name(self): if self.is_called_as_a_method(): return self.word_finder.get_word_at(self.first_parens - 1) else: return self.word_finder.get_primary_at(self.first_parens - 1) def is_called_as_a_method(self): return self.implicit_arg and "." in self.call[: self.first_parens] ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1705553002.6979835 rope-1.12.0/rope/refactor/importutils/0000775000175000017500000000000014552126153017643 5ustar00lieryanlieryan././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/rope/refactor/importutils/__init__.py0000664000175000017500000003205214512700666021761 0ustar00lieryanlieryan"""A package for handling imports This package provides tools for modifying module imports after refactorings or as a separate task. """ import rope.base.codeanalyze import rope.base.evaluate from rope.base import libutils from rope.base.change import ChangeContents, ChangeSet from rope.refactor import occurrences, rename from rope.refactor.importutils import actions, module_imports from rope.refactor.importutils.importinfo import FromImport, NormalImport class ImportOrganizer: """Perform some import-related commands Each method returns a `rope.base.change.Change` object. """ def __init__(self, project): self.project = project self.import_tools = ImportTools(self.project) def organize_imports(self, resource, offset=None): return self._perform_command_on_import_tools( self.import_tools.organize_imports, resource, offset ) def expand_star_imports(self, resource, offset=None): return self._perform_command_on_import_tools( self.import_tools.expand_stars, resource, offset ) def froms_to_imports(self, resource, offset=None): return self._perform_command_on_import_tools( self.import_tools.froms_to_imports, resource, offset ) def relatives_to_absolutes(self, resource, offset=None): return self._perform_command_on_import_tools( self.import_tools.relatives_to_absolutes, resource, offset ) def handle_long_imports(self, resource, offset=None): return self._perform_command_on_import_tools( self.import_tools.handle_long_imports, resource, offset ) def _perform_command_on_import_tools(self, method, resource, offset): pymodule = self.project.get_pymodule(resource) before_performing = pymodule.source_code import_filter = None if offset is not None: import_filter = self._line_filter(pymodule.lines.get_line_number(offset)) result = method(pymodule, import_filter=import_filter) if result is not None and result != before_performing: changes = ChangeSet( method.__name__.replace("_", " ") + " in <%s>" % resource.path ) changes.add_change(ChangeContents(resource, result)) return changes def _line_filter(self, lineno): def import_filter(import_stmt): return import_stmt.start_line <= lineno < import_stmt.end_line return import_filter class ImportTools: def __init__(self, project): self.project = project def get_import(self, resource): """The import statement for `resource`""" module_name = libutils.modname(resource) return NormalImport(((module_name, None),)) def get_from_import(self, resource, name): """The from import statement for `name` in `resource`""" module_name = libutils.modname(resource) names = [] if isinstance(name, list): names = [(imported, None) for imported in name] else: names = [ (name, None), ] return FromImport(module_name, 0, tuple(names)) def module_imports(self, module, imports_filter=None): return module_imports.ModuleImports(self.project, module, imports_filter) def froms_to_imports(self, pymodule, import_filter=None): pymodule = self._clean_up_imports(pymodule, import_filter) module_imports = self.module_imports(pymodule, import_filter) for import_stmt in module_imports.imports: if import_stmt.readonly or not self._is_transformable_to_normal( import_stmt.import_info ): continue pymodule = self._from_to_normal(pymodule, import_stmt) # Adding normal imports in place of froms module_imports = self.module_imports(pymodule, import_filter) for import_stmt in module_imports.imports: if not import_stmt.readonly and self._is_transformable_to_normal( import_stmt.import_info ): import_stmt.import_info = NormalImport( ((import_stmt.import_info.module_name, None),) ) module_imports.remove_duplicates() return module_imports.get_changed_source() def expand_stars(self, pymodule, import_filter=None): module_imports = self.module_imports(pymodule, import_filter) module_imports.expand_stars() return module_imports.get_changed_source() def _from_to_normal(self, pymodule, import_stmt): resource = pymodule.get_resource() from_import = import_stmt.import_info module_name = from_import.module_name for name, alias in from_import.names_and_aliases: imported = name if alias is not None: imported = alias occurrence_finder = occurrences.create_finder( self.project, imported, pymodule[imported], imports=False, keywords=False, ) source = rename.rename_in_module( occurrence_finder, module_name + "." + name, pymodule=pymodule, replace_primary=True, ) if source is not None: pymodule = libutils.get_string_module(self.project, source, resource) return pymodule def _clean_up_imports(self, pymodule, import_filter): resource = pymodule.get_resource() module_with_imports = self.module_imports(pymodule, import_filter) module_with_imports.expand_stars() source = module_with_imports.get_changed_source() if source is not None: pymodule = libutils.get_string_module(self.project, source, resource) source = self.relatives_to_absolutes(pymodule) if source is not None: pymodule = libutils.get_string_module(self.project, source, resource) module_with_imports = self.module_imports(pymodule, import_filter) module_with_imports.remove_duplicates() module_with_imports.remove_unused_imports() source = module_with_imports.get_changed_source() if source is not None: pymodule = libutils.get_string_module(self.project, source, resource) return pymodule def relatives_to_absolutes(self, pymodule, import_filter=None): module_imports = self.module_imports(pymodule, import_filter) to_be_absolute_list = module_imports.get_relative_to_absolute_list() for name, absolute_name in to_be_absolute_list: pymodule = self._rename_in_module(pymodule, name, absolute_name) module_imports = self.module_imports(pymodule, import_filter) module_imports.get_relative_to_absolute_list() source = module_imports.get_changed_source() if source is None: source = pymodule.source_code return source def _is_transformable_to_normal(self, import_info): return isinstance(import_info, FromImport) def organize_imports( self, pymodule, unused=True, duplicates=True, selfs=True, sort=True, import_filter=None, ): if unused or duplicates: module_imports = self.module_imports(pymodule, import_filter) if unused: module_imports.remove_unused_imports() if self.project.prefs.get("split_imports"): module_imports.force_single_imports() if duplicates: module_imports.remove_duplicates() source = module_imports.get_changed_source() if source is not None: pymodule = libutils.get_string_module( self.project, source, pymodule.get_resource() ) if selfs: pymodule = self._remove_self_imports(pymodule, import_filter) if sort: return self.sort_imports(pymodule, import_filter) else: return pymodule.source_code def _remove_self_imports(self, pymodule, import_filter=None): module_imports = self.module_imports(pymodule, import_filter) ( to_be_fixed, to_be_renamed, ) = module_imports.get_self_import_fix_and_rename_list() for name in to_be_fixed: try: pymodule = self._rename_in_module(pymodule, name, "", till_dot=True) except ValueError: # There is a self import with direct access to it return pymodule for name, new_name in to_be_renamed: pymodule = self._rename_in_module(pymodule, name, new_name) module_imports = self.module_imports(pymodule, import_filter) module_imports.get_self_import_fix_and_rename_list() source = module_imports.get_changed_source() if source is not None: pymodule = libutils.get_string_module( self.project, source, pymodule.get_resource() ) return pymodule def _rename_in_module(self, pymodule, name, new_name, till_dot=False): old_name = name.split(".")[-1] old_pyname = rope.base.evaluate.eval_str(pymodule.get_scope(), name) occurrence_finder = occurrences.create_finder( self.project, old_name, old_pyname, imports=False ) changes = rope.base.codeanalyze.ChangeCollector(pymodule.source_code) for occurrence in occurrence_finder.find_occurrences(pymodule=pymodule): start, end = occurrence.get_primary_range() if till_dot: new_end = pymodule.source_code.index(".", end) + 1 space = pymodule.source_code[end : new_end - 1].strip() if not space == "": for c in space: if not c.isspace() and c not in "\\": raise ValueError() end = new_end changes.add_change(start, end, new_name) source = changes.get_changed() if source is not None: pymodule = libutils.get_string_module( self.project, source, pymodule.get_resource() ) return pymodule def sort_imports(self, pymodule, import_filter=None): module_imports = self.module_imports(pymodule, import_filter) module_imports.sort_imports() return module_imports.get_changed_source() def handle_long_imports( self, pymodule, maxdots=2, maxlength=27, import_filter=None ): # IDEA: `maxdots` and `maxlength` can be specified in project config # adding new from imports module_imports = self.module_imports(pymodule, import_filter) to_be_fixed = module_imports.handle_long_imports(maxdots, maxlength) # performing the renaming pymodule = libutils.get_string_module( self.project, module_imports.get_changed_source(), resource=pymodule.get_resource(), ) for name in to_be_fixed: pymodule = self._rename_in_module(pymodule, name, name.split(".")[-1]) # organizing imports return self.organize_imports( pymodule, selfs=False, sort=False, import_filter=import_filter ) def get_imports(project, pydefined): """A shortcut for getting the `ImportInfo` used in a scope""" pymodule = pydefined.get_module() module = module_imports.ModuleImports(project, pymodule) if pymodule == pydefined: return [stmt.import_info for stmt in module.imports] return module.get_used_imports(pydefined) def get_module_imports(project, pymodule): """A shortcut for creating a `module_imports.ModuleImports` object""" return module_imports.ModuleImports(project, pymodule) def add_import(project, pymodule, module_name, name=None): imports = get_module_imports(project, pymodule) candidates = [] names = [] selected_import = None # from mod import name if name is not None: from_import = FromImport(module_name, 0, [(name, None)]) names.append(name) candidates.append(from_import) # from pkg import mod if "." in module_name: pkg, mod = module_name.rsplit(".", 1) from_import = FromImport(pkg, 0, [(mod, None)]) if project.prefs.get("prefer_module_from_imports"): selected_import = from_import candidates.append(from_import) if name: names.append(mod + "." + name) else: names.append(mod) # import mod normal_import = NormalImport([(module_name, None)]) if name: names.append(module_name + "." + name) else: names.append(module_name) candidates.append(normal_import) visitor = actions.AddingVisitor(project, candidates) if selected_import is None: selected_import = normal_import for import_statement in imports.imports: if import_statement.accept(visitor): selected_import = visitor.import_info break imports.add_import(selected_import) imported_name = names[candidates.index(selected_import)] return imports.get_changed_source(), imported_name ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/rope/refactor/importutils/actions.py0000664000175000017500000003365714512700666021676 0ustar00lieryanlieryanfrom rope.base import exceptions, libutils, pyobjects, stdmods from rope.refactor import occurrences from rope.refactor.importutils import importinfo class ImportInfoVisitor: def dispatch(self, import_): try: method_name = "visit" + import_.import_info.__class__.__name__ method = getattr(self, method_name) return method(import_, import_.import_info) except exceptions.ModuleNotFoundError: pass def visitEmptyImport(self, import_stmt, import_info): pass def visitNormalImport(self, import_stmt, import_info): pass def visitFromImport(self, import_stmt, import_info): pass class RelativeToAbsoluteVisitor(ImportInfoVisitor): def __init__(self, project, current_folder): self.to_be_absolute = [] self.project = project self.folder = current_folder self.context = importinfo.ImportContext(project, current_folder) def visitNormalImport(self, import_stmt, import_info): self.to_be_absolute.extend(self._get_relative_to_absolute_list(import_info)) new_pairs = [] for name, alias in import_info.names_and_aliases: resource = self.project.find_module(name, folder=self.folder) if resource is None: new_pairs.append((name, alias)) continue absolute_name = libutils.modname(resource) new_pairs.append((absolute_name, alias)) if not import_info._are_name_and_alias_lists_equal( new_pairs, import_info.names_and_aliases ): import_stmt.import_info = importinfo.NormalImport(new_pairs) def _get_relative_to_absolute_list(self, import_info): result = [] for name, alias in import_info.names_and_aliases: if alias is not None: continue resource = self.project.find_module(name, folder=self.folder) if resource is None: continue absolute_name = libutils.modname(resource) if absolute_name != name: result.append((name, absolute_name)) return result def visitFromImport(self, import_stmt, import_info): resource = import_info.get_imported_resource(self.context) if resource is None: return None absolute_name = libutils.modname(resource) if import_info.module_name != absolute_name: import_stmt.import_info = importinfo.FromImport( absolute_name, 0, import_info.names_and_aliases ) class FilteringVisitor(ImportInfoVisitor): def __init__(self, project, folder, can_select): self.to_be_absolute = [] self.project = project self.can_select = self._transform_can_select(can_select) self.context = importinfo.ImportContext(project, folder) def _transform_can_select(self, can_select): def can_select_name_and_alias(name, alias): imported = name if alias is not None: imported = alias return can_select(imported) return can_select_name_and_alias def visitNormalImport(self, import_stmt, import_info): new_pairs = [ (name, alias) for name, alias in import_info.names_and_aliases if self.can_select(name, alias) ] return importinfo.NormalImport(new_pairs) def visitFromImport(self, import_stmt, import_info): if _is_future(import_info): return import_info new_pairs = [] if import_info.is_star_import(): for name in import_info.get_imported_names(self.context): if self.can_select(name, None): new_pairs.append(import_info.names_and_aliases[0]) break else: for name, alias in import_info.names_and_aliases: if self.can_select(name, alias): new_pairs.append((name, alias)) return importinfo.FromImport( import_info.module_name, import_info.level, new_pairs ) class RemovingVisitor(ImportInfoVisitor): def __init__(self, project, folder, can_select): self.to_be_absolute = [] self.project = project self.filtering = FilteringVisitor(project, folder, can_select) def dispatch(self, import_): result = self.filtering.dispatch(import_) if result is not None: import_.import_info = result class AddingVisitor(ImportInfoVisitor): """A class for adding imports Given a list of `ImportInfo`, it tries to add each import to the module and returns `True` and gives up when an import can be added to older ones. """ def __init__(self, project, import_list): self.project = project self.import_list = import_list self.import_info = None def dispatch(self, import_): for import_info in self.import_list: self.import_info = import_info if ImportInfoVisitor.dispatch(self, import_): return True # TODO: Handle adding relative and absolute imports def visitNormalImport(self, import_stmt, import_info): if not isinstance(self.import_info, import_info.__class__): return False # Adding ``import x`` and ``import x.y`` that results ``import x.y`` if ( len(import_info.names_and_aliases) == len(self.import_info.names_and_aliases) == 1 ): imported1 = import_info.names_and_aliases[0] imported2 = self.import_info.names_and_aliases[0] if imported1[1] == imported2[1] is None: if imported1[0].startswith(imported2[0] + "."): return True if imported2[0].startswith(imported1[0] + "."): import_stmt.import_info = self.import_info return True # Multiple imports using a single import statement is discouraged # so we won't bother adding them. if self.import_info._are_name_and_alias_lists_equal( import_info.names_and_aliases, self.import_info.names_and_aliases ): return True def visitFromImport(self, import_stmt, import_info): if ( isinstance(self.import_info, import_info.__class__) and import_info.module_name == self.import_info.module_name and import_info.level == self.import_info.level ): if import_info.is_star_import(): return True if self.import_info.is_star_import(): import_stmt.import_info = self.import_info return True if self.project.prefs.get("split_imports"): return ( self.import_info.names_and_aliases == import_info.names_and_aliases ) new_pairs = list(import_info.names_and_aliases) for pair in self.import_info.names_and_aliases: if pair not in new_pairs: new_pairs.append(pair) import_stmt.import_info = importinfo.FromImport( import_info.module_name, import_info.level, new_pairs ) return True class ExpandStarsVisitor(ImportInfoVisitor): def __init__(self, project, folder, can_select): self.project = project self.filtering = FilteringVisitor(project, folder, can_select) self.context = importinfo.ImportContext(project, folder) def visitNormalImport(self, import_stmt, import_info): self.filtering.dispatch(import_stmt) def visitFromImport(self, import_stmt, import_info): if import_info.is_star_import(): new_pairs = [ (name, None) for name in import_info.get_imported_names(self.context) ] new_import = importinfo.FromImport( import_info.module_name, import_info.level, new_pairs ) import_stmt.import_info = self.filtering.visitFromImport(None, new_import) else: self.filtering.dispatch(import_stmt) class SelfImportVisitor(ImportInfoVisitor): def __init__(self, project, current_folder, resource): self.project = project self.folder = current_folder self.resource = resource self.to_be_fixed = set() self.to_be_renamed = set() self.context = importinfo.ImportContext(project, current_folder) def visitNormalImport(self, import_stmt, import_info): new_pairs = [] for name, alias in import_info.names_and_aliases: resource = self.project.find_module(name, folder=self.folder) if resource is not None and resource == self.resource: imported = name if alias is not None: imported = alias self.to_be_fixed.add(imported) else: new_pairs.append((name, alias)) if not import_info._are_name_and_alias_lists_equal( new_pairs, import_info.names_and_aliases ): import_stmt.import_info = importinfo.NormalImport(new_pairs) def visitFromImport(self, import_stmt, import_info): resource = import_info.get_imported_resource(self.context) if resource is None: return if resource == self.resource: self._importing_names_from_self(import_info, import_stmt) return pymodule = self.project.get_pymodule(resource) new_pairs = [] for name, alias in import_info.names_and_aliases: try: result = pymodule[name].get_object() if ( isinstance(result, pyobjects.PyModule) and result.get_resource() == self.resource ): imported = name if alias is not None: imported = alias self.to_be_fixed.add(imported) else: new_pairs.append((name, alias)) except exceptions.AttributeNotFoundError: new_pairs.append((name, alias)) if not import_info._are_name_and_alias_lists_equal( new_pairs, import_info.names_and_aliases ): import_stmt.import_info = importinfo.FromImport( import_info.module_name, import_info.level, new_pairs ) def _importing_names_from_self(self, import_info, import_stmt): if not import_info.is_star_import(): for name, alias in import_info.names_and_aliases: if alias is not None: self.to_be_renamed.add((alias, name)) import_stmt.empty_import() class SortingVisitor(ImportInfoVisitor): def __init__(self, project, current_folder): self.project = project self.folder = current_folder self.standard = set() self.third_party = set() self.in_project = set() self.future = set() self.context = importinfo.ImportContext(project, current_folder) def visitNormalImport(self, import_stmt, import_info): if import_info.names_and_aliases: name, alias = import_info.names_and_aliases[0] resource = self.project.find_module(name, folder=self.folder) self._check_imported_resource(import_stmt, resource, name) def visitFromImport(self, import_stmt, import_info): resource = import_info.get_imported_resource(self.context) self._check_imported_resource(import_stmt, resource, import_info.module_name) def _check_imported_resource(self, import_stmt, resource, imported_name): info = import_stmt.import_info if resource is not None and resource.project == self.project: self.in_project.add(import_stmt) elif _is_future(info): self.future.add(import_stmt) elif imported_name.split(".")[0] in stdmods.standard_modules(): self.standard.add(import_stmt) else: self.third_party.add(import_stmt) class LongImportVisitor(ImportInfoVisitor): def __init__(self, current_folder, project, maxdots, maxlength): self.maxdots = maxdots self.maxlength = maxlength self.to_be_renamed = set() self.current_folder = current_folder self.project = project self.new_imports = [] def visitNormalImport(self, import_stmt, import_info): for name, alias in import_info.names_and_aliases: if alias is None and self._is_long(name): self.to_be_renamed.add(name) last_dot = name.rindex(".") from_ = name[:last_dot] imported = name[last_dot + 1 :] self.new_imports.append( importinfo.FromImport(from_, 0, ((imported, None),)) ) def _is_long(self, name): return name.count(".") > self.maxdots or ( "." in name and len(name) > self.maxlength ) class RemovePyNameVisitor(ImportInfoVisitor): def __init__(self, project, pymodule, pyname, folder): self.pymodule = pymodule self.pyname = pyname self.context = importinfo.ImportContext(project, folder) def visitFromImport(self, import_stmt, import_info): new_pairs = [] if not import_info.is_star_import(): for name, alias in import_info.names_and_aliases: try: pyname = self.pymodule[alias or name] if occurrences.same_pyname(self.pyname, pyname): continue except exceptions.AttributeNotFoundError: pass new_pairs.append((name, alias)) return importinfo.FromImport( import_info.module_name, import_info.level, new_pairs ) def dispatch(self, import_): result = ImportInfoVisitor.dispatch(self, import_) if result is not None: import_.import_info = result def _is_future(info): return isinstance(info, importinfo.FromImport) and info.module_name == "__future__" ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/rope/refactor/importutils/importinfo.py0000664000175000017500000001323614512700666022413 0ustar00lieryanlieryanfrom typing import List, Tuple class ImportStatement: """Represent an import in a module `readonly` attribute controls whether this import can be changed by import actions or not. """ def __init__( self, import_info, start_line, end_line, main_statement=None, blank_lines=0 ): self.start_line = start_line self.end_line = end_line self.readonly = False self.main_statement = main_statement self._import_info = None self.import_info = import_info self._is_changed = False self.new_start = None self.blank_lines = blank_lines def _get_import_info(self): return self._import_info def _set_import_info(self, new_import): if ( not self.readonly and new_import is not None and not new_import == self._import_info ): self._is_changed = True self._import_info = new_import import_info = property(_get_import_info, _set_import_info) def get_import_statement(self): if self._is_changed or self.main_statement is None: return self.import_info.get_import_statement() else: return self.main_statement def empty_import(self): self.import_info = ImportInfo.get_empty_import() def move(self, lineno, blank_lines=0): self.new_start = lineno self.blank_lines = blank_lines def get_old_location(self): return self.start_line, self.end_line def get_new_start(self): return self.new_start def is_changed(self): return self._is_changed or ( self.new_start is not None or self.new_start != self.start_line ) def accept(self, visitor): return visitor.dispatch(self) class ImportInfo: def get_imported_primaries(self, context): pass def get_imported_names(self, context): return [ primary.split(".")[0] for primary in self.get_imported_primaries(context) ] def get_import_statement(self): pass def is_empty(self): pass def __hash__(self): return hash(self.get_import_statement()) def _are_name_and_alias_lists_equal(self, list1, list2): if len(list1) != len(list2): return False for pair1, pair2 in zip(list1, list2): if pair1 != pair2: return False return True def __eq__(self, obj): return ( isinstance(obj, self.__class__) and self.get_import_statement() == obj.get_import_statement() ) def __ne__(self, obj): return not self.__eq__(obj) @staticmethod def get_empty_import(): return EmptyImport() class NormalImport(ImportInfo): def __init__(self, names_and_aliases): self.names_and_aliases = names_and_aliases def get_imported_primaries(self, context): result = [] for name, alias in self.names_and_aliases: if alias: result.append(alias) else: result.append(name) return result def get_import_statement(self): result = "import " for name, alias in self.names_and_aliases: result += name if alias: result += " as " + alias result += ", " return result[:-2] def is_empty(self): return len(self.names_and_aliases) == 0 class FromImport(ImportInfo): def __init__(self, module_name, level, names_and_aliases): self.module_name = module_name self.level = level self.names_and_aliases = names_and_aliases def get_imported_primaries(self, context): if self.names_and_aliases[0][0] == "*": module = self.get_imported_module(context) return [name for name in module if not name.startswith("_")] result = [] for name, alias in self.names_and_aliases: if alias: result.append(alias) else: result.append(name) return result def get_imported_resource(self, context): """Get the imported resource Returns `None` if module was not found. """ if self.level == 0: return context.project.find_module(self.module_name, folder=context.folder) else: return context.project.find_relative_module( self.module_name, context.folder, self.level ) def get_imported_module(self, context): """Get the imported `PyModule` Raises `rope.base.exceptions.ModuleNotFoundError` if module could not be found. """ if self.level == 0: return context.project.get_module(self.module_name, context.folder) else: return context.project.get_relative_module( self.module_name, context.folder, self.level ) def get_import_statement(self): result = "from " + "." * self.level + self.module_name + " import " for name, alias in self.names_and_aliases: result += name if alias: result += " as " + alias result += ", " return result[:-2] def is_empty(self): return len(self.names_and_aliases) == 0 def is_star_import(self): return len(self.names_and_aliases) > 0 and self.names_and_aliases[0][0] == "*" class EmptyImport(ImportInfo): names_and_aliases: List[Tuple[str, str]] = [] def is_empty(self): return True def get_imported_primaries(self, context): return [] class ImportContext: def __init__(self, project, folder): self.project = project self.folder = folder ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1699196919.0 rope-1.12.0/rope/refactor/importutils/module_imports.py0000664000175000017500000005275614521727767023314 0ustar00lieryanlieryanfrom typing import List, Union from rope.base import ast, exceptions, pynames, pynamesdef, utils from rope.refactor.importutils import actions, importinfo class ModuleImports: def __init__(self, project, pymodule, import_filter=None): self.project = project self.pymodule = pymodule self.separating_lines = 0 self.filter = import_filter self.sorted = False @property @utils.saveit def imports(self): finder = _GlobalImportFinder(self.pymodule) result = finder.find_import_statements() self.separating_lines = finder.get_separating_line_count() if self.filter is not None: for import_stmt in result: if not self.filter(import_stmt): import_stmt.readonly = True return result def _get_unbound_names(self, defined_pyobject): visitor = _GlobalUnboundNameFinder(self.pymodule, defined_pyobject) visitor.visit(self.pymodule.get_ast()) return visitor.unbound def _get_all_star_list(self, pymodule): def _resolve_name( name: Union[pynamesdef.AssignedName, pynames.ImportedName] ) -> List: while isinstance(name, pynames.ImportedName): try: name = name.imported_module.get_object().get_attribute( name.imported_name, ) except exceptions.AttributeNotFoundError: return [] assert isinstance(name, pynamesdef.AssignedName) return name.assignments result = set() try: all_star_list = pymodule.get_attribute("__all__") except exceptions.AttributeNotFoundError: return result assignments = [ assignment.ast_node for assignment in _resolve_name(all_star_list) ] # FIXME: Need a better way to recursively infer possible values. # Currently pyobjects can recursively infer type, but not values. # Do a very basic 1-level value inference while assignments: assignment = assignments.pop() if isinstance(assignment, ast.List): stack = list(assignment.elts) while stack: el = stack.pop() if isinstance(el, ast.IfExp): stack.append(el.body) stack.append(el.orelse) elif isinstance(el, ast.Starred): assignments.append(el.value) else: if isinstance(el, ast.Constant) and isinstance(el.value, str): result.add(el.value) elif isinstance(el, ast.Name): try: name = pymodule.get_attribute(el.id) except exceptions.AttributeNotFoundError: continue else: for av in _resolve_name(name): if isinstance( av.ast_node, ast.Constant, ) and isinstance( av.ast_node.value, str, ): result.add(av.ast_node.value) elif isinstance(assignment, ast.Name): try: name = pymodule.get_attribute(assignment.id) except exceptions.AttributeNotFoundError: continue else: assignments.extend( assignment.ast_node for assignment in _resolve_name(name) ) elif isinstance(assignment, ast.BinOp): assignments.append(assignment.left) assignments.append(assignment.right) return result def remove_unused_imports(self): can_select = _OneTimeSelector( self._get_unbound_names(self.pymodule) | self._get_all_star_list(self.pymodule) | {"__all__"} ) visitor = actions.RemovingVisitor( self.project, self._current_folder(), can_select ) for import_statement in self.imports: import_statement.accept(visitor) def get_used_imports(self, defined_pyobject): result = [] can_select = _OneTimeSelector(self._get_unbound_names(defined_pyobject)) visitor = actions.FilteringVisitor( self.project, self._current_folder(), can_select ) for import_statement in self.imports: new_import = import_statement.accept(visitor) if new_import is not None and not new_import.is_empty(): result.append(new_import) return result def get_changed_source(self): if not self.project.prefs.get("pull_imports_to_top") and not self.sorted: return "".join(self._rewrite_imports(self.imports)) # Make sure we forward a removed import's preceding blank # lines count to the following import statement. prev_stmt = None for stmt in self.imports: if prev_stmt is not None and prev_stmt.import_info.is_empty(): stmt.blank_lines = max(prev_stmt.blank_lines, stmt.blank_lines) prev_stmt = stmt # The new list of imports. imports = [stmt for stmt in self.imports if not stmt.import_info.is_empty()] after_removing = self._remove_imports(self.imports) first_non_blank = self._first_non_blank_line(after_removing, 0) first_import = self._first_import_line() - 1 result = [] # Writing module docs result.extend(after_removing[first_non_blank:first_import]) # Writing imports sorted_imports = sorted(imports, key=self._get_location) for stmt in sorted_imports: if stmt != sorted_imports[0]: result.append("\n" * stmt.blank_lines) result.append(stmt.get_import_statement() + "\n") if sorted_imports and first_non_blank < len(after_removing): result.append("\n" * self.separating_lines) # Writing the body first_after_imports = self._first_non_blank_line(after_removing, first_import) result.extend(after_removing[first_after_imports:]) return "".join(result) def _get_import_location(self, stmt): start = stmt.get_new_start() if start is None: start = stmt.get_old_location()[0] return start def _get_location(self, stmt): if stmt.get_new_start() is not None: return stmt.get_new_start() else: return stmt.get_old_location()[0] def _remove_imports(self, imports): lines = self.pymodule.source_code.splitlines(True) after_removing = [] first_import_line = self._first_import_line() last_index = 0 for stmt in imports: start, end = stmt.get_old_location() blank_lines = 0 if start != first_import_line: blank_lines = _count_blank_lines( lines.__getitem__, start - 2, last_index - 1, -1 ) after_removing.extend(lines[last_index : start - 1 - blank_lines]) last_index = end - 1 after_removing.extend(lines[last_index:]) return after_removing def _rewrite_imports(self, imports): lines = self.pymodule.source_code.splitlines(True) after_rewriting = [] last_index = 0 for stmt in imports: start, end = stmt.get_old_location() after_rewriting.extend(lines[last_index : start - 1]) if not stmt.import_info.is_empty(): after_rewriting.append(stmt.get_import_statement() + "\n") last_index = end - 1 after_rewriting.extend(lines[last_index:]) return after_rewriting def _first_non_blank_line(self, lines, lineno): return lineno + _count_blank_lines(lines.__getitem__, lineno, len(lines)) def add_import(self, import_info): visitor = actions.AddingVisitor(self.project, [import_info]) for import_statement in self.imports: if import_statement.accept(visitor): break else: lineno = self._get_new_import_lineno() blanks = self._get_new_import_blanks() self.imports.append( importinfo.ImportStatement( import_info, lineno, lineno, blank_lines=blanks ) ) def _get_new_import_blanks(self): return 0 def _get_new_import_lineno(self): if self.imports: return self.imports[-1].end_line return 1 def filter_names(self, can_select): visitor = actions.RemovingVisitor( self.project, self._current_folder(), can_select ) for import_statement in self.imports: import_statement.accept(visitor) def expand_stars(self): can_select = _OneTimeSelector(self._get_unbound_names(self.pymodule)) visitor = actions.ExpandStarsVisitor( self.project, self._current_folder(), can_select ) for import_statement in self.imports: import_statement.accept(visitor) def remove_duplicates(self): added_imports = [] for import_stmt in self.imports: visitor = actions.AddingVisitor(self.project, [import_stmt.import_info]) for added_import in added_imports: if added_import.accept(visitor): import_stmt.empty_import() else: added_imports.append(import_stmt) def force_single_imports(self): """force a single import per statement""" for import_stmt in self.imports[:]: import_info = import_stmt.import_info if import_info.is_empty() or import_stmt.readonly: continue if len(import_info.names_and_aliases) > 1: for name_and_alias in import_info.names_and_aliases: if hasattr(import_info, "module_name"): new_import = importinfo.FromImport( import_info.module_name, import_info.level, [name_and_alias] ) else: new_import = importinfo.NormalImport([name_and_alias]) self.add_import(new_import) import_stmt.empty_import() def get_relative_to_absolute_list(self): visitor = actions.RelativeToAbsoluteVisitor( self.project, self._current_folder() ) for import_stmt in self.imports: if not import_stmt.readonly: import_stmt.accept(visitor) return visitor.to_be_absolute def get_self_import_fix_and_rename_list(self): visitor = actions.SelfImportVisitor( self.project, self._current_folder(), self.pymodule.get_resource() ) for import_stmt in self.imports: if not import_stmt.readonly: import_stmt.accept(visitor) return visitor.to_be_fixed, visitor.to_be_renamed def _current_folder(self): return self.pymodule.get_resource().parent def sort_imports(self): if self.project.prefs.get("sort_imports_alphabetically"): sort_kwargs = dict(key=self._get_import_name) else: sort_kwargs = dict(key=self._key_imports) # IDEA: Sort from import list visitor = actions.SortingVisitor(self.project, self._current_folder()) for import_statement in self.imports: import_statement.accept(visitor) in_projects = sorted(visitor.in_project, **sort_kwargs) third_party = sorted(visitor.third_party, **sort_kwargs) standards = sorted(visitor.standard, **sort_kwargs) future = sorted(visitor.future, **sort_kwargs) last_index = self._first_import_line() last_index = self._move_imports(future, last_index, 0) last_index = self._move_imports(standards, last_index, 1) last_index = self._move_imports(third_party, last_index, 1) last_index = self._move_imports(in_projects, last_index, 1) self.separating_lines = 2 self.sorted = True def _first_import_line(self): nodes = self.pymodule.get_ast().body lineno = 0 if self.pymodule.get_doc() is not None: lineno = 1 if len(nodes) > lineno: if isinstance(nodes[lineno], ast.Import) or isinstance( nodes[lineno], ast.ImportFrom ): return nodes[lineno].lineno first_line = get_first_decorator_or_function_start_line(nodes[lineno]) lineno = self.pymodule.logical_lines.logical_line_in(first_line)[0] else: lineno = self.pymodule.lines.length() return lineno - _count_blank_lines( self.pymodule.lines.get_line, lineno - 1, 1, -1 ) def _get_import_name(self, import_stmt): import_info = import_stmt.import_info if hasattr(import_info, "module_name"): return "{}.{}".format( import_info.module_name, import_info.names_and_aliases[0][0], ) else: return import_info.names_and_aliases[0][0] def _key_imports(self, stm1): str1 = stm1.get_import_statement() return str1.startswith("from "), str1 # str1 = stmt1.get_import_statement() # str2 = stmt2.get_import_statement() # if str1.startswith('from ') and not str2.startswith('from '): # return 1 # if not str1.startswith('from ') and str2.startswith('from '): # return -1 # return cmp(str1, str2) def _move_imports(self, imports, index, blank_lines): if imports: imports[0].move(index, blank_lines) index += 1 if len(imports) > 1: for stmt in imports[1:]: stmt.move(index) index += 1 return index def handle_long_imports(self, maxdots, maxlength): visitor = actions.LongImportVisitor( self._current_folder(), self.project, maxdots, maxlength ) for import_statement in self.imports: if not import_statement.readonly: import_statement.accept(visitor) for import_info in visitor.new_imports: self.add_import(import_info) return visitor.to_be_renamed def remove_pyname(self, pyname): """Removes pyname when imported in ``from mod import x``""" visitor = actions.RemovePyNameVisitor( self.project, self.pymodule, pyname, self._current_folder() ) for import_stmt in self.imports: import_stmt.accept(visitor) def get_first_decorator_or_function_start_line(node): decorators = getattr(node, "decorator_list", []) first_line = min([decorator.lineno for decorator in decorators] + [node.lineno]) return first_line def _count_blank_lines(get_line, start, end, step=1): count = 0 for idx in range(start, end, step): if get_line(idx).strip() == "": count += 1 else: break return count class _OneTimeSelector: def __init__(self, names): self.names = names self.selected_names = set() def __call__(self, imported_primary): if self._can_name_be_added(imported_primary): for name in self._get_dotted_tokens(imported_primary): self.selected_names.add(name) return True return False def _get_dotted_tokens(self, imported_primary): tokens = imported_primary.split(".") for i in range(len(tokens)): yield ".".join(tokens[: i + 1]) def _can_name_be_added(self, imported_primary): for name in self._get_dotted_tokens(imported_primary): if name in self.names and name not in self.selected_names: return True return False class _UnboundNameFinder(ast.RopeNodeVisitor): def __init__(self, pyobject): self.pyobject = pyobject def _visit_child_scope(self, node): pyobject = ( self.pyobject.get_module() .get_scope() .get_inner_scope_for_line(node.lineno) .pyobject ) visitor = _LocalUnboundNameFinder(pyobject, self) for child in ast.iter_child_nodes(node): visitor.visit(child) def _FunctionDef(self, node): self._visit_child_scope(node) def _ClassDef(self, node): self._visit_child_scope(node) def _Name(self, node): if self._get_root()._is_node_interesting(node) and not self.is_bound(node.id): self.add_unbound(node.id) def _Attribute(self, node): result = [] while isinstance(node, ast.Attribute): result.append(node.attr) node = node.value if isinstance(node, ast.Name): result.append(node.id) primary = ".".join(reversed(result)) if self._get_root()._is_node_interesting(node) and not self.is_bound( primary ): self.add_unbound(primary) else: self.visit(node) def _get_root(self): pass def is_bound(self, name, propagated=False): pass def add_unbound(self, name): pass class _GlobalUnboundNameFinder(_UnboundNameFinder): def __init__(self, pymodule, wanted_pyobject): super().__init__(pymodule) self.unbound = set() self.names = set() for name, pyname in pymodule._get_structural_attributes().items(): if not isinstance(pyname, (pynames.ImportedName, pynames.ImportedModule)): self.names.add(name) wanted_scope = wanted_pyobject.get_scope() self.start = wanted_scope.get_start() self.end = wanted_scope.get_end() + 1 def _get_root(self): return self def is_bound(self, primary, propagated=False): name = primary.split(".")[0] return name in self.names def add_unbound(self, name): names = name.split(".") for i in range(len(names)): self.unbound.add(".".join(names[: i + 1])) def _is_node_interesting(self, node): return self.start <= node.lineno < self.end class _LocalUnboundNameFinder(_UnboundNameFinder): def __init__(self, pyobject, parent): super().__init__(pyobject) self.parent = parent def _get_root(self): return self.parent._get_root() def is_bound(self, primary, propagated=False): name = primary.split(".")[0] if propagated: names = self.pyobject.get_scope().get_propagated_names() else: names = self.pyobject.get_scope().get_names() return name in names or self.parent.is_bound(name, propagated=True) def add_unbound(self, name): self.parent.add_unbound(name) class _GlobalImportFinder: def __init__(self, pymodule): self.current_folder = None if pymodule.get_resource(): self.current_folder = pymodule.get_resource().parent self.pymodule = pymodule self.imports = [] self.pymodule = pymodule self.lines = self.pymodule.lines def visit_import(self, node, end_line): start_line = node.lineno import_statement = importinfo.ImportStatement( importinfo.NormalImport(self._get_names(node.names)), start_line, end_line, self._get_text(start_line, end_line), blank_lines=self._count_empty_lines_before(start_line), ) self.imports.append(import_statement) def _count_empty_lines_before(self, lineno): return _count_blank_lines(self.lines.get_line, lineno - 1, 0, -1) def _count_empty_lines_after(self, lineno): return _count_blank_lines(self.lines.get_line, lineno + 1, self.lines.length()) def get_separating_line_count(self): if not self.imports: return 0 return self._count_empty_lines_after(self.imports[-1].end_line - 1) def _get_text(self, start_line, end_line): result = [self.lines.get_line(index) for index in range(start_line, end_line)] return "\n".join(result) def visit_from(self, node, end_line): level = 0 if node.level: level = node.level import_info = importinfo.FromImport( node.module or "", level, self._get_names(node.names), ) start_line = node.lineno self.imports.append( importinfo.ImportStatement( import_info, node.lineno, end_line, self._get_text(start_line, end_line), blank_lines=self._count_empty_lines_before(start_line), ) ) def _get_names(self, alias_names): return [(alias.name, alias.asname) for alias in alias_names] def find_import_statements(self): nodes = self.pymodule.get_ast().body for node in nodes: if isinstance(node, (ast.Import, ast.ImportFrom)): lines = self.pymodule.logical_lines end_line = lines.logical_line_in(node.lineno)[1] + 1 if isinstance(node, ast.Import): self.visit_import(node, end_line) if isinstance(node, ast.ImportFrom): self.visit_from(node, end_line) return self.imports ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/rope/refactor/inline.py0000664000175000017500000006102514512700666017107 0ustar00lieryanlieryan# Known Bugs when inlining a function/method # The values passed to function are inlined using _inlined_variable. # This may cause two problems, illustrated in the examples below # # def foo(var1): # var1 = var1*10 # return var1 # # If a call to foo(20) is inlined, the result of inlined function is 20, # but it should be 200. # # def foo(var1): # var2 = var1*10 # return var2 # # 2- If a call to foo(10+10) is inlined the result of inlined function is 110 # but it should be 200. import re from typing import List import rope.base.builtins # Use fully qualified names for clarity. from rope.base import ( codeanalyze, evaluate, exceptions, libutils, pynames, pyobjects, taskhandle, utils, worder, ) from rope.base.change import ChangeContents, ChangeSet from rope.refactor import ( change_signature, functionutils, importutils, move, occurrences, rename, sourceutils, ) def unique_prefix(): n = 0 while True: yield "__" + str(n) + "__" n += 1 def create_inline(project, resource, offset): """Create a refactoring object for inlining Based on `resource` and `offset` it returns an instance of `InlineMethod`, `InlineVariable` or `InlineParameter`. """ pyname = _get_pyname(project, resource, offset) message = ( "Inline refactoring should be performed on " "a method, local variable or parameter." ) if pyname is None: raise exceptions.RefactoringError(message) if isinstance(pyname, pynames.ImportedName): pyname = pyname._get_imported_pyname() if isinstance(pyname, pynames.AssignedName): return InlineVariable(project, resource, offset) if isinstance(pyname, pynames.ParameterName): return InlineParameter(project, resource, offset) if isinstance(pyname.get_object(), pyobjects.PyFunction): return InlineMethod(project, resource, offset) else: raise exceptions.RefactoringError(message) class _Inliner: def __init__(self, project, resource, offset): self.project = project self.pyname = _get_pyname(project, resource, offset) range_finder = worder.Worder(resource.read(), True) self.region = range_finder.get_primary_range(offset) self.name = range_finder.get_word_at(offset) self.offset = offset self.original = resource def get_changes(self, *args, **kwds): pass def get_kind(self): """Return either 'variable', 'method' or 'parameter'""" class InlineMethod(_Inliner): def __init__(self, *args, **kwds): super().__init__(*args, **kwds) self.pyfunction = self.pyname.get_object() self.pymodule = self.pyfunction.get_module() self.resource = self.pyfunction.get_module().get_resource() self.occurrence_finder = occurrences.create_finder( self.project, self.name, self.pyname ) self.normal_generator = _DefinitionGenerator(self.project, self.pyfunction) self._init_imports() def _init_imports(self): body = sourceutils.get_body(self.pyfunction) body, imports = move.moving_code_with_imports(self.project, self.resource, body) self.imports = imports self.others_generator = _DefinitionGenerator( self.project, self.pyfunction, body=body ) def _get_scope_range(self): scope = self.pyfunction.get_scope() lines = self.pymodule.lines start_line = scope.get_start() if self.pyfunction.decorators: decorators = self.pyfunction.decorators if hasattr(decorators[0], "lineno"): start_line = decorators[0].lineno start_offset = lines.get_line_start(start_line) end_offset = min( lines.get_line_end(scope.end) + 1, len(self.pymodule.source_code) ) return (start_offset, end_offset) def get_changes( self, remove=True, only_current=False, resources=None, task_handle=taskhandle.DEFAULT_TASK_HANDLE, ): """Get the changes this refactoring makes If `remove` is `False` the definition will not be removed. If `only_current` is `True`, the the current occurrence will be inlined, only. """ changes = ChangeSet("Inline method <%s>" % self.name) if resources is None: resources = self.project.get_python_files() if only_current: resources = [self.original] if remove: resources.append(self.resource) job_set = task_handle.create_jobset("Collecting Changes", len(resources)) for file in resources: job_set.started_job(file.path) if file == self.resource: changes.add_change( self._defining_file_changes( changes, remove=remove, only_current=only_current ) ) else: aim = None if only_current and self.original == file: aim = self.offset handle = _InlineFunctionCallsForModuleHandle( self.project, file, self.others_generator, aim ) result = move.ModuleSkipRenamer( self.occurrence_finder, file, handle ).get_changed_module() if result is not None: result = _add_imports(self.project, result, file, self.imports) if remove: result = _remove_from(self.project, self.pyname, result, file) changes.add_change(ChangeContents(file, result)) job_set.finished_job() return changes def _get_removed_range(self): scope = self.pyfunction.get_scope() lines = self.pymodule.lines start, end = self._get_scope_range() end_line = scope.get_end() for i in range(end_line + 1, lines.length()): if lines.get_line(i).strip() == "": end_line = i else: break end = min(lines.get_line_end(end_line) + 1, len(self.pymodule.source_code)) return (start, end) def _defining_file_changes(self, changes, remove, only_current): start_offset, end_offset = self._get_removed_range() aim = None if only_current: if self.resource == self.original: aim = self.offset else: # we don't want to change any of them aim = len(self.resource.read()) + 100 handle = _InlineFunctionCallsForModuleHandle( self.project, self.resource, self.normal_generator, aim_offset=aim ) replacement = None if remove: replacement = self._get_method_replacement() result = move.ModuleSkipRenamer( self.occurrence_finder, self.resource, handle, start_offset, end_offset, replacement, ).get_changed_module() return ChangeContents(self.resource, result) def _get_method_replacement(self): if self._is_the_last_method_of_a_class(): indents = sourceutils.get_indents( self.pymodule.lines, self.pyfunction.get_scope().get_start() ) return " " * indents + "pass\n" return "" def _is_the_last_method_of_a_class(self): pyclass = self.pyfunction.parent if not isinstance(pyclass, pyobjects.PyClass): return False class_start, class_end = sourceutils.get_body_region(pyclass) source = self.pymodule.source_code func_start, func_end = self._get_scope_range() return ( source[class_start:func_start].strip() == "" and source[func_end:class_end].strip() == "" ) def get_kind(self): return "method" class InlineVariable(_Inliner): def __init__(self, *args, **kwds): super().__init__(*args, **kwds) self.pymodule = self.pyname.get_definition_location()[0] self.resource = self.pymodule.get_resource() self._check_exceptional_conditions() self._init_imports() def _check_exceptional_conditions(self): if len(self.pyname.assignments) != 1: raise exceptions.RefactoringError( "Local variable should be assigned once for inlining." ) def get_changes( self, remove=True, only_current=False, resources=None, docs=False, task_handle=taskhandle.DEFAULT_TASK_HANDLE, ): if resources is None: if rename._is_local(self.pyname): resources = [self.resource] else: resources = self.project.get_python_files() if only_current: resources = [self.original] if remove and self.original != self.resource: resources.append(self.resource) changes = ChangeSet("Inline variable <%s>" % self.name) jobset = task_handle.create_jobset("Calculating changes", len(resources)) for resource in resources: jobset.started_job(resource.path) if resource == self.resource: source = self._change_main_module(remove, only_current, docs) changes.add_change(ChangeContents(self.resource, source)) else: result = self._change_module(resource, remove, only_current) if result is not None: result = _add_imports(self.project, result, resource, self.imports) changes.add_change(ChangeContents(resource, result)) jobset.finished_job() return changes def _change_main_module(self, remove, only_current, docs): region = None if only_current and self.original == self.resource: region = self.region return _inline_variable( self.project, self.pymodule, self.pyname, self.name, remove=remove, region=region, docs=docs, ) def _init_imports(self): vardef = _getvardef(self.pymodule, self.pyname) self.imported, self.imports = move.moving_code_with_imports( self.project, self.resource, vardef ) def _change_module(self, resource, remove, only_current): filters = [occurrences.NoImportsFilter(), occurrences.PyNameFilter(self.pyname)] if only_current and resource == self.original: def check_aim(occurrence): start, end = occurrence.get_primary_range() if self.offset < start or end < self.offset: return False filters.insert(0, check_aim) finder = occurrences.Finder(self.project, self.name, filters=filters) changed = rename.rename_in_module( finder, self.imported, resource=resource, replace_primary=True ) if changed and remove: changed = _remove_from(self.project, self.pyname, changed, resource) return changed def get_kind(self): return "variable" class InlineParameter(_Inliner): def __init__(self, *args, **kwds): super().__init__(*args, **kwds) resource, offset = self._function_location() index = self.pyname.index self.changers = [change_signature.ArgumentDefaultInliner(index)] self.signature = change_signature.ChangeSignature( self.project, resource, offset ) def _function_location(self): pymodule, lineno = self.pyname.get_definition_location() resource = pymodule.get_resource() start = pymodule.lines.get_line_start(lineno) word_finder = worder.Worder(pymodule.source_code) offset = word_finder.find_function_offset(start) return resource, offset def get_changes(self, **kwds): """Get the changes needed by this refactoring See `rope.refactor.change_signature.ChangeSignature.get_changes()` for arguments. """ return self.signature.get_changes(self.changers, **kwds) def get_kind(self): return "parameter" def _join_lines(lines: List[str]) -> str: return "\n".join(lines) class _ComplexExpressionVisitor: def __init__(self): self.is_complex_expression = False def _Set(self, node): self.is_complex_expression = True def _List(self, node): self.is_complex_expression = True def _Tuple(self, node): self.is_complex_expression = True def _Dict(self, node): self.is_complex_expression = True class _DefinitionGenerator: unique_prefix = unique_prefix() def __init__(self, project, pyfunction, body=None): self.project = project self.pyfunction = pyfunction self.pymodule = pyfunction.get_module() self.resource = self.pymodule.get_resource() self.definition_info = self._get_definition_info() self.definition_params = self._get_definition_params() self._calculated_definitions = {} if body is not None: self.body = body else: self.body = sourceutils.get_body(self.pyfunction) def _get_definition_info(self): return functionutils.DefinitionInfo.read(self.pyfunction) def _get_definition_params(self): definition_info = self.definition_info paramdict = dict([pair for pair in definition_info.args_with_defaults]) if ( definition_info.args_arg is not None or definition_info.keywords_arg is not None ): raise exceptions.RefactoringError( "Cannot inline functions with list and keyword arguments." ) if self.pyfunction.get_kind() == "classmethod": paramdict[ definition_info.args_with_defaults[0][0] ] = self.pyfunction.parent.get_name() return paramdict def get_function_name(self): return self.pyfunction.get_name() def get_definition(self, primary, pyname, call, host_vars=None, returns=False): # caching already calculated definitions if host_vars is None: host_vars = [] return self._calculate_definition(primary, pyname, call, host_vars, returns) def _calculate_header(self, primary, pyname, call): # A header is created which initializes parameters # to the values passed to the function. call_info = functionutils.CallInfo.read( primary, pyname, self.definition_info, call ) paramdict = self.definition_params mapping = functionutils.ArgumentMapping(self.definition_info, call_info) for param_name, value in mapping.param_dict.items(): paramdict[param_name] = value header = "" to_be_inlined = [] for name, value in paramdict.items(): if name != value and value is not None: header += name + " = " + value.replace("\n", " ") + "\n" to_be_inlined.append(name) return header, to_be_inlined def _calculate_definition(self, primary, pyname, call, host_vars, returns): header, to_be_inlined = self._calculate_header(primary, pyname, call) source = header + self.body mod = libutils.get_string_module(self.project, source) name_dict = mod.get_scope().get_names() all_names = [ x for x in name_dict if not isinstance(name_dict[x], rope.base.builtins.BuiltinName) ] # If there is a name conflict, all variable names # inside the inlined function are renamed if set(all_names).intersection(set(host_vars)): prefix = next(_DefinitionGenerator.unique_prefix) guest = libutils.get_string_module(self.project, source, self.resource) to_be_inlined = [prefix + item for item in to_be_inlined] for item in all_names: pyname = guest[item] occurrence_finder = occurrences.create_finder( self.project, item, pyname ) source = rename.rename_in_module( occurrence_finder, prefix + item, pymodule=guest ) guest = libutils.get_string_module(self.project, source, self.resource) # parameters not reassigned inside the functions are now inlined. for name in to_be_inlined: pymodule = libutils.get_string_module(self.project, source, self.resource) pyname = pymodule[name] source = _inline_variable(self.project, pymodule, pyname, name) return self._replace_returns_with(source, returns) def _replace_returns_with(self, source, returns): result = [] returned = None last_changed = 0 for match in _DefinitionGenerator._get_return_pattern().finditer(source): for key, value in match.groupdict().items(): if value and key == "return": result.append(source[last_changed : match.start("return")]) if returns: self._check_nothing_after_return(source, match.end("return")) beg_idx = match.end("return") returned = _join_lines( source[beg_idx : len(source)].lstrip().splitlines(), ) last_changed = len(source) else: current = match.end("return") while current < len(source) and source[current] in " \t": current += 1 last_changed = current if current == len(source) or source[current] == "\n": result.append("pass") result.append(source[last_changed:]) return "".join(result), returned def _check_nothing_after_return(self, source, offset): lines = codeanalyze.SourceLinesAdapter(source) lineno = lines.get_line_number(offset) logical_lines = codeanalyze.LogicalLineFinder(lines) lineno = logical_lines.logical_line_in(lineno)[1] if source[lines.get_line_end(lineno) : len(source)].strip() != "": raise exceptions.RefactoringError( "Cannot inline functions with statements " + "after return statement." ) @classmethod def _get_return_pattern(cls): if not hasattr(cls, "_return_pattern"): def named_pattern(name, list_): return "(?P<%s>" % name + "|".join(list_) + ")" comment_pattern = named_pattern("comment", [r"#[^\n]*"]) string_pattern = named_pattern("string", [codeanalyze.get_string_pattern()]) return_pattern = r"\b(?Preturn)\b" cls._return_pattern = re.compile( comment_pattern + "|" + string_pattern + "|" + return_pattern ) return cls._return_pattern class _InlineFunctionCallsForModuleHandle: def __init__(self, project, resource, definition_generator, aim_offset=None): """Inlines occurrences If `aim` is not `None` only the occurrences that intersect `aim` offset will be inlined. """ self.project = project self.generator = definition_generator self.resource = resource self.aim = aim_offset def occurred_inside_skip(self, change_collector, occurrence): if not occurrence.is_defined(): raise exceptions.RefactoringError( "Cannot inline functions that reference themselves" ) def occurred_outside_skip(self, change_collector, occurrence): start, end = occurrence.get_primary_range() # we remove out of date imports later if occurrence.is_in_import_statement(): return # the function is referenced outside an import statement if not occurrence.is_called(): raise exceptions.RefactoringError( "Reference to inlining function other than function call" " in " % (self.resource.path, start) ) if self.aim is not None and (self.aim < start or self.aim > end): return end_parens = self._find_end_parens(self.source, end - 1) lineno = self.lines.get_line_number(start) start_line, end_line = self.pymodule.logical_lines.logical_line_in(lineno) line_start = self.lines.get_line_start(start_line) line_end = self.lines.get_line_end(end_line) returns = ( self.source[line_start:start].strip() != "" or self.source[end_parens:line_end].strip() != "" ) indents = sourceutils.get_indents(self.lines, start_line) primary, pyname = occurrence.get_primary_and_pyname() host = self.pymodule scope = host.scope.get_inner_scope_for_line(lineno) definition, returned = self.generator.get_definition( primary, pyname, self.source[start:end_parens], scope.get_names(), returns=returns, ) end = min(line_end + 1, len(self.source)) change_collector.add_change( line_start, end, sourceutils.fix_indentation(definition, indents) ) if returns: name = returned if name is None: name = "None" change_collector.add_change( line_end, end, self.source[line_start:start] + name + self.source[end_parens:end], ) def _find_end_parens(self, source, offset): finder = worder.Worder(source) return finder.get_word_parens_range(offset)[1] @property @utils.saveit def pymodule(self): return self.project.get_pymodule(self.resource) @property @utils.saveit def source(self): if self.resource is not None: return self.resource.read() else: return self.pymodule.source_code @property @utils.saveit def lines(self): return self.pymodule.lines def _inline_variable( project, pymodule, pyname, name, remove=True, region=None, docs=False ): definition = _getvardef(pymodule, pyname) start, end = _assigned_lineno(pymodule, pyname) occurrence_finder = occurrences.create_finder(project, name, pyname, docs=docs) changed_source = rename.rename_in_module( occurrence_finder, definition, pymodule=pymodule, replace_primary=True, writes=False, region=region, ) if changed_source is None: changed_source = pymodule.source_code if remove: lines = codeanalyze.SourceLinesAdapter(changed_source) source = ( changed_source[: lines.get_line_start(start)] + changed_source[lines.get_line_end(end) + 1 :] ) else: source = changed_source return source def _getvardef(pymodule, pyname): assignment = pyname.assignments[0] lines = pymodule.lines start, end = _assigned_lineno(pymodule, pyname) definition_with_assignment = _join_lines( [lines.get_line(n) for n in range(start, end + 1)], ) if assignment.levels: raise exceptions.RefactoringError("Cannot inline tuple assignments.") definition = definition_with_assignment[ definition_with_assignment.index("=") + 1 : ].strip() return definition def _assigned_lineno(pymodule, pyname): definition_line = pyname.assignments[0].ast_node.lineno return pymodule.logical_lines.logical_line_in(definition_line) def _add_imports(project, source, resource, imports): if not imports: return source pymodule = libutils.get_string_module(project, source, resource) module_import = importutils.get_module_imports(project, pymodule) for import_info in imports: module_import.add_import(import_info) source = module_import.get_changed_source() pymodule = libutils.get_string_module(project, source, resource) import_tools = importutils.ImportTools(project) return import_tools.organize_imports(pymodule, unused=False, sort=False) def _get_pyname(project, resource, offset): pymodule = project.get_pymodule(resource) pyname = evaluate.eval_location(pymodule, offset) if isinstance(pyname, pynames.ImportedName): pyname = pyname._get_imported_pyname() return pyname def _remove_from(project, pyname, source, resource): pymodule = libutils.get_string_module(project, source, resource) module_import = importutils.get_module_imports(project, pymodule) module_import.remove_pyname(pyname) return module_import.get_changed_source() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/rope/refactor/introduce_factory.py0000664000175000017500000001343114512700666021352 0ustar00lieryanlieryanfrom rope.base import evaluate, exceptions, libutils, pyobjects, taskhandle from rope.base.change import ChangeContents, ChangeSet from rope.refactor import importutils, occurrences, rename, sourceutils class IntroduceFactory: def __init__(self, project, resource, offset): self.project = project self.offset = offset this_pymodule = self.project.get_pymodule(resource) self.old_pyname = evaluate.eval_location(this_pymodule, offset) if self.old_pyname is None or not isinstance( self.old_pyname.get_object(), pyobjects.PyClass ): raise exceptions.RefactoringError( "Introduce factory should be performed on a class." ) self.old_name = self.old_pyname.get_object().get_name() self.pymodule = self.old_pyname.get_object().get_module() self.resource = self.pymodule.get_resource() def get_changes( self, factory_name, global_factory=False, resources=None, task_handle=taskhandle.DEFAULT_TASK_HANDLE, ): """Get the changes this refactoring makes `factory_name` indicates the name of the factory function to be added. If `global_factory` is `True` the factory will be global otherwise a static method is added to the class. `resources` can be a list of `rope.base.resource.File` that this refactoring should be applied on; if `None` all python files in the project are searched. """ if resources is None: resources = self.project.get_python_files() changes = ChangeSet("Introduce factory method <%s>" % factory_name) job_set = task_handle.create_jobset("Collecting Changes", len(resources)) self._change_module(resources, changes, factory_name, global_factory, job_set) return changes def get_name(self): """Return the name of the class""" return self.old_name def _change_module(self, resources, changes, factory_name, global_, job_set): if global_: replacement = "__rope_factory_%s_" % factory_name else: replacement = self._new_function_name(factory_name, global_) for file_ in resources: job_set.started_job(file_.path) if file_ == self.resource: self._change_resource(changes, factory_name, global_) job_set.finished_job() continue changed_code = self._rename_occurrences(file_, replacement, global_) if changed_code is not None: if global_: new_pymodule = libutils.get_string_module( self.project, changed_code, self.resource ) modname = libutils.modname(self.resource) changed_code, imported = importutils.add_import( self.project, new_pymodule, modname, factory_name ) changed_code = changed_code.replace(replacement, imported) changes.add_change(ChangeContents(file_, changed_code)) job_set.finished_job() def _change_resource(self, changes, factory_name, global_): class_scope = self.old_pyname.get_object().get_scope() source_code = self._rename_occurrences( self.resource, self._new_function_name(factory_name, global_), global_ ) if source_code is None: source_code = self.pymodule.source_code else: self.pymodule = libutils.get_string_module( self.project, source_code, resource=self.resource ) lines = self.pymodule.lines start = self._get_insertion_offset(class_scope, lines) result = source_code[:start] result += self._get_factory_method(lines, class_scope, factory_name, global_) result += source_code[start:] changes.add_change(ChangeContents(self.resource, result)) def _get_insertion_offset(self, class_scope, lines): start_line = class_scope.get_end() if class_scope.get_scopes(): start_line = class_scope.get_scopes()[-1].get_end() start = lines.get_line_end(start_line) + 1 return start def _get_factory_method(self, lines, class_scope, factory_name, global_): unit_indents = " " * sourceutils.get_indent(self.project) if global_: if self._get_scope_indents(lines, class_scope) > 0: raise exceptions.RefactoringError( "Cannot make global factory method for nested classes." ) return "\ndef {}(*args, **kwds):\n{}return {}(*args, **kwds)\n".format( factory_name, unit_indents, self.old_name, ) unindented_factory = ( "@staticmethod\ndef %s(*args, **kwds):\n" % factory_name + f"{unit_indents}return {self.old_name}(*args, **kwds)\n" ) indents = self._get_scope_indents(lines, class_scope) + sourceutils.get_indent( self.project ) return "\n" + sourceutils.indent_lines(unindented_factory, indents) def _get_scope_indents(self, lines, scope): return sourceutils.get_indents(lines, scope.get_start()) def _new_function_name(self, factory_name, global_): if global_: return factory_name else: return self.old_name + "." + factory_name def _rename_occurrences(self, file_, changed_name, global_factory): finder = occurrences.create_finder( self.project, self.old_name, self.old_pyname, only_calls=True ) return rename.rename_in_module( finder, changed_name, resource=file_, replace_primary=global_factory ) IntroduceFactoryRefactoring = IntroduceFactory ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/rope/refactor/introduce_parameter.py0000664000175000017500000000720514512700666021665 0ustar00lieryanlieryanimport rope.base.change # Use fully qualified names for clarity. from rope.base import codeanalyze, evaluate, exceptions, worder from rope.refactor import functionutils, occurrences, sourceutils class IntroduceParameter: """Introduce parameter refactoring This refactoring adds a new parameter to a function and replaces references to an expression in it with the new parameter. The parameter finding part is different from finding similar pieces in extract refactorings. In this refactoring parameters are found based on the object they reference to. For instance in:: class A(object): var = None class B(object): a = A() b = B() a = b.a def f(a): x = b.a.var + a.var using this refactoring on ``a.var`` with ``p`` as the new parameter name, will result in:: def f(p=a.var): x = p + p """ def __init__(self, project, resource, offset): self.project = project self.resource = resource self.offset = offset self.pymodule = self.project.get_pymodule(self.resource) scope = self.pymodule.get_scope().get_inner_scope_for_offset(offset) if scope.get_kind() != "Function": raise exceptions.RefactoringError( "Introduce parameter should be performed inside functions" ) self.pyfunction = scope.pyobject self.name, self.pyname = self._get_name_and_pyname() if self.pyname is None: raise exceptions.RefactoringError( "Cannot find the definition of <%s>" % self.name ) def _get_primary(self): word_finder = worder.Worder(self.resource.read()) return word_finder.get_primary_at(self.offset) def _get_name_and_pyname(self): return ( worder.get_name_at(self.resource, self.offset), evaluate.eval_location(self.pymodule, self.offset), ) def get_changes(self, new_parameter): definition_info = functionutils.DefinitionInfo.read(self.pyfunction) definition_info.args_with_defaults.append((new_parameter, self._get_primary())) collector = codeanalyze.ChangeCollector(self.resource.read()) header_start, header_end = self._get_header_offsets() body_start, body_end = sourceutils.get_body_region(self.pyfunction) collector.add_change(header_start, header_end, definition_info.to_string()) self._change_function_occurrences( collector, body_start, body_end, new_parameter ) changes = rope.base.change.ChangeSet("Introduce parameter <%s>" % new_parameter) change = rope.base.change.ChangeContents(self.resource, collector.get_changed()) changes.add_change(change) return changes def _get_header_offsets(self): lines = self.pymodule.lines start_line = self.pyfunction.get_scope().get_start() end_line = self.pymodule.logical_lines.logical_line_in(start_line)[1] start = lines.get_line_start(start_line) end = lines.get_line_end(end_line) start = self.pymodule.source_code.find("def", start) + 4 end = self.pymodule.source_code.rfind(":", start, end) return start, end def _change_function_occurrences( self, collector, function_start, function_end, new_name ): finder = occurrences.create_finder(self.project, self.name, self.pyname) for occurrence in finder.find_occurrences(resource=self.resource): start, end = occurrence.get_primary_range() if function_start <= start < function_end: collector.add_change(start, end, new_name) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/rope/refactor/localtofield.py0000664000175000017500000000403414512700666020267 0ustar00lieryanlieryanfrom rope.base import evaluate, exceptions, pynames, worder from rope.refactor.rename import Rename class LocalToField: def __init__(self, project, resource, offset): self.project = project self.resource = resource self.offset = offset def get_changes(self): name = worder.get_name_at(self.resource, self.offset) this_pymodule = self.project.get_pymodule(self.resource) pyname = evaluate.eval_location(this_pymodule, self.offset) if not self._is_a_method_local(pyname): raise exceptions.RefactoringError( "Convert local variable to field should be performed on " "a local variable of a method." ) pymodule, lineno = pyname.get_definition_location() function_scope = pymodule.get_scope().get_inner_scope_for_line(lineno) # Not checking redefinition # self._check_redefinition(name, function_scope) new_name = self._get_field_name(function_scope.pyobject, name) changes = Rename(self.project, self.resource, self.offset).get_changes( new_name, resources=[self.resource] ) return changes def _check_redefinition(self, name, function_scope): class_scope = function_scope.parent if name in class_scope.pyobject: raise exceptions.RefactoringError("The field %s already exists" % name) def _get_field_name(self, pyfunction, name): self_name = pyfunction.get_param_names()[0] new_name = self_name + "." + name return new_name def _is_a_method_local(self, pyname): pymodule, lineno = pyname.get_definition_location() holding_scope = pymodule.get_scope().get_inner_scope_for_line(lineno) parent = holding_scope.parent return ( isinstance(pyname, pynames.AssignedName) and pyname in holding_scope.get_names().values() and holding_scope.get_kind() == "Function" and parent is not None and parent.get_kind() == "Class" ) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/rope/refactor/method_object.py0000664000175000017500000000736514512700666020446 0ustar00lieryanlieryanimport warnings from rope.base import change, codeanalyze, evaluate, exceptions, libutils, pyobjects from rope.refactor import occurrences, rename, sourceutils class MethodObject: def __init__(self, project, resource, offset): self.project = project this_pymodule = self.project.get_pymodule(resource) pyname = evaluate.eval_location(this_pymodule, offset) if pyname is None or not isinstance(pyname.get_object(), pyobjects.PyFunction): raise exceptions.RefactoringError( "Replace method with method object refactoring should be " "performed on a function." ) self.pyfunction = pyname.get_object() self.pymodule = self.pyfunction.get_module() self.resource = self.pymodule.get_resource() def get_new_class(self, name): body = sourceutils.fix_indentation( self._get_body(), sourceutils.get_indent(self.project) * 2 ) return "class {}(object):\n\n{}{}def __call__(self):\n{}".format( name, self._get_init(), " " * sourceutils.get_indent(self.project), body, ) def get_changes(self, classname=None, new_class_name=None): if new_class_name is not None: warnings.warn( "new_class_name parameter is deprecated; use classname", DeprecationWarning, stacklevel=2, ) classname = new_class_name collector = codeanalyze.ChangeCollector(self.pymodule.source_code) start, end = sourceutils.get_body_region(self.pyfunction) indents = sourceutils.get_indents( self.pymodule.lines, self.pyfunction.get_scope().get_start() ) + sourceutils.get_indent(self.project) new_contents = " " * indents + "return {}({})()\n".format( classname, ", ".join(self._get_parameter_names()), ) collector.add_change(start, end, new_contents) insertion = self._get_class_insertion_point() collector.add_change( insertion, insertion, "\n\n" + self.get_new_class(classname) ) changes = change.ChangeSet("Replace method with method object refactoring") changes.add_change( change.ChangeContents(self.resource, collector.get_changed()) ) return changes def _get_class_insertion_point(self): current = self.pyfunction while current.parent != self.pymodule: current = current.parent end = self.pymodule.lines.get_line_end(current.get_scope().get_end()) return min(end + 1, len(self.pymodule.source_code)) def _get_body(self): body = sourceutils.get_body(self.pyfunction) for param in self._get_parameter_names(): body = param + " = None\n" + body pymod = libutils.get_string_module(self.project, body, self.resource) pyname = pymod[param] finder = occurrences.create_finder(self.project, param, pyname) result = rename.rename_in_module(finder, "self." + param, pymodule=pymod) body = result[result.index("\n") + 1 :] return body def _get_init(self): params = self._get_parameter_names() indents = " " * sourceutils.get_indent(self.project) if not params: return "" header = indents + "def __init__(self" body = "" for arg in params: new_name = arg if arg == "self": new_name = "host" header += ", %s" % new_name body += indents * 2 + f"self.{arg} = {new_name}\n" header += "):" return f"{header}\n{body}\n" def _get_parameter_names(self): return self.pyfunction.get_param_names() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/rope/refactor/move.py0000664000175000017500000010323214512700666016574 0ustar00lieryanlieryan"""A module containing classes for move refactoring `create_move()` is a factory for creating move refactoring objects based on inputs. """ from __future__ import annotations import typing from typing import Optional, List, Union from rope.base import ( codeanalyze, evaluate, exceptions, libutils, pynames, pyobjects, taskhandle, worder, ) from rope.base.change import ChangeContents, ChangeSet, MoveResource from rope.refactor import functionutils, importutils, occurrences, rename, sourceutils from rope.refactor.importutils.module_imports import ( get_first_decorator_or_function_start_line, ) if typing.TYPE_CHECKING: from rope.base import project, resources def create_move(project, resource, offset=None): """A factory for creating Move objects Based on `resource` and `offset`, return one of `MoveModule`, `MoveGlobal` or `MoveMethod` for performing move refactoring. """ if offset is None: return MoveModule(project, resource) this_pymodule = project.get_pymodule(resource) pyname = evaluate.eval_location(this_pymodule, offset) if pyname is not None: pyobject = pyname.get_object() if isinstance(pyobject, pyobjects.PyModule) or isinstance( pyobject, pyobjects.PyPackage ): return MoveModule(project, pyobject.get_resource()) if isinstance(pyobject, pyobjects.PyFunction) and isinstance( pyobject.parent, pyobjects.PyClass ): return MoveMethod(project, resource, offset) if ( isinstance(pyobject, pyobjects.PyDefinedObject) and isinstance(pyobject.parent, pyobjects.PyModule) or isinstance(pyname, pynames.AssignedName) ): return MoveGlobal(project, resource, offset) raise exceptions.RefactoringError( "Move only works on global classes/functions/variables, modules and methods." ) class MoveMethod: """For moving methods It makes a new method in the destination class and changes the body of the old method to call the new method. You can inline the old method to change all of its occurrences. """ def __init__(self, project, resource, offset): self.project = project this_pymodule = self.project.get_pymodule(resource) pyname = evaluate.eval_location(this_pymodule, offset) self.method_name = worder.get_name_at(resource, offset) self.pyfunction = pyname.get_object() if self.pyfunction.get_kind() != "method": raise exceptions.RefactoringError("Only normal methods can be moved.") def get_changes( self, dest_attr, new_name=None, resources=None, task_handle=taskhandle.DEFAULT_TASK_HANDLE, # FIXME: this is unused ): """Return the changes needed for this refactoring Parameters: - `dest_attr`: the name of the destination attribute - `new_name`: the name of the new method; if `None` uses the old name - `resources` can be a list of `rope.base.resources.File` to apply this refactoring on. If `None`, the restructuring will be applied to all python files. """ changes = ChangeSet("Moving method <%s>" % self.method_name) if resources is None: resources = self.project.get_python_files() if new_name is None: new_name = self.get_method_name() resource1, start1, end1, new_content1 = self._get_changes_made_by_old_class( dest_attr, new_name ) collector1 = codeanalyze.ChangeCollector(resource1.read()) collector1.add_change(start1, end1, new_content1) resource2, start2, end2, new_content2 = self._get_changes_made_by_new_class( dest_attr, new_name ) if resource1 == resource2: collector1.add_change(start2, end2, new_content2) else: collector2 = codeanalyze.ChangeCollector(resource2.read()) collector2.add_change(start2, end2, new_content2) result = collector2.get_changed() import_tools = importutils.ImportTools(self.project) new_imports = self._get_used_imports(import_tools) if new_imports: goal_pymodule = libutils.get_string_module( self.project, result, resource2 ) result = _add_imports_to_module( import_tools, goal_pymodule, new_imports ) if resource2 in resources: changes.add_change(ChangeContents(resource2, result)) if resource1 in resources: changes.add_change(ChangeContents(resource1, collector1.get_changed())) return changes def get_method_name(self): return self.method_name def _get_used_imports(self, import_tools): return importutils.get_imports(self.project, self.pyfunction) def _get_changes_made_by_old_class(self, dest_attr, new_name): pymodule = self.pyfunction.get_module() indents = self._get_scope_indents(self.pyfunction) body = "return self.{}.{}({})\n".format( dest_attr, new_name, self._get_passed_arguments_string(), ) region = sourceutils.get_body_region(self.pyfunction) return ( pymodule.get_resource(), region[0], region[1], sourceutils.fix_indentation(body, indents), ) def _get_scope_indents(self, pyobject): pymodule = pyobject.get_module() return sourceutils.get_indents( pymodule.lines, pyobject.get_scope().get_start() ) + sourceutils.get_indent(self.project) def _get_changes_made_by_new_class(self, dest_attr, new_name): old_pyclass = self.pyfunction.parent if dest_attr not in old_pyclass: raise exceptions.RefactoringError( "Destination attribute <%s> not found" % dest_attr ) pyclass = old_pyclass[dest_attr].get_object().get_type() if not isinstance(pyclass, pyobjects.PyClass): raise exceptions.RefactoringError( "Unknown class type for attribute <%s>" % dest_attr ) pymodule = pyclass.get_module() resource = pyclass.get_module().get_resource() start, end = sourceutils.get_body_region(pyclass) pre_blanks = "\n" if pymodule.source_code[start:end].strip() != "pass": pre_blanks = "\n\n" start = end indents = self._get_scope_indents(pyclass) body = pre_blanks + sourceutils.fix_indentation( self.get_new_method(new_name), indents ) return resource, start, end, body def get_new_method(self, name): return "{}\n{}".format( self._get_new_header(name), sourceutils.fix_indentation( self._get_body(), sourceutils.get_indent(self.project) ), ) def _get_unchanged_body(self): return sourceutils.get_body(self.pyfunction) def _get_body(self, host="host"): self_name = self._get_self_name() body = self_name + " = None\n" + self._get_unchanged_body() pymodule = libutils.get_string_module(self.project, body) finder = occurrences.create_finder(self.project, self_name, pymodule[self_name]) result = rename.rename_in_module(finder, host, pymodule=pymodule) if result is None: result = body return result[result.index("\n") + 1 :] def _get_self_name(self): return self.pyfunction.get_param_names()[0] def _get_new_header(self, name): header = "def %s(self" % name if self._is_host_used(): header += ", host" definition_info = functionutils.DefinitionInfo.read(self.pyfunction) others = definition_info.arguments_to_string(1) if others: header += ", " + others return header + "):" def _get_passed_arguments_string(self): result = "" if self._is_host_used(): result = "self" definition_info = functionutils.DefinitionInfo.read(self.pyfunction) others = definition_info.arguments_to_string(1) if others: if result: result += ", " result += others return result def _is_host_used(self): return self._get_body("__old_self") != self._get_unchanged_body() class MoveGlobal: """For moving global function and classes""" project: project.Project def __init__(self, project, resource, offset): self.project = project this_pymodule = self.project.get_pymodule(resource) 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." ) if self._is_variable(self.old_pyname): self.old_name = worder.get_name_at(resource, offset) pymodule = this_pymodule else: self.old_name = self.old_pyname.get_object().get_name() pymodule = self.old_pyname.get_object().get_module() self._check_exceptional_conditions() self.source = pymodule.get_resource() self.tools = _MoveTools( self.project, self.source, self.old_pyname, self.old_name ) self.import_tools = self.tools.import_tools def _import_filter(self, stmt): module_name = libutils.modname(self.source) if isinstance(stmt.import_info, importutils.NormalImport): # Affect any statement that imports the source module return any( module_name == name for name, alias in stmt.import_info.names_and_aliases ) elif isinstance(stmt.import_info, importutils.FromImport): # Affect statements importing from the source package if "." in module_name: package_name, basename = module_name.rsplit(".", 1) if stmt.import_info.module_name == package_name and any( basename == name for name, alias in stmt.import_info.names_and_aliases ): return True return stmt.import_info.module_name == module_name return False def _check_exceptional_conditions(self): if self._is_variable(self.old_pyname): pymodule = self.old_pyname.get_definition_location()[0] try: pymodule.get_scope().get_name(self.old_name) except exceptions.NameNotFoundError: self._raise_refactoring_error() elif not ( isinstance(self.old_pyname.get_object(), pyobjects.PyDefinedObject) and self._is_global(self.old_pyname.get_object()) ): self._raise_refactoring_error() def _raise_refactoring_error(self): raise exceptions.RefactoringError( "Move refactoring should be performed on a global class, function " "or variable." ) def _is_global(self, pyobject): return pyobject.get_scope().parent == pyobject.get_module().get_scope() def _is_variable(self, pyname): return isinstance(pyname, pynames.AssignedName) def get_changes( self, dest: Optional[Union[str, resources.Resource]], resources: Optional[List[resources.File]] = None, task_handle=taskhandle.DEFAULT_TASK_HANDLE, ): """Return the changes needed for this refactoring Parameters: - `dest`: the Resource or dotted moddule name of the move destination - `resources` can be a list of `rope.base.resources.File` to apply this refactoring on. If `None`, the restructuring will be applied to all python files. """ # To do (in another PR): Create a new var: dest_resource: resource.Reource # Doing so should remove the type:ignore below. if isinstance(dest, str): dest = self.project.find_module(dest) if resources is None: resources = self.project.get_python_files() # The previous guards protect against this mypy complaint: # "Resource" has no attribute "has_child" if dest is None or not dest.exists(): raise exceptions.RefactoringError("Move destination does not exist.") if dest.is_folder() and dest.has_child("__init__.py"): # type:ignore dest = dest.get_child("__init__.py") # type:ignore # The previous guards protect against this mypy complaint: # Item "None" of "Union[str, Resource, None]" has no attribute "is_folder" if dest.is_folder(): # type:ignore raise exceptions.RefactoringError( "Move destination for non-modules should not be folders." ) if self.source == dest: raise exceptions.RefactoringError( "Moving global elements to the same module." ) return self._calculate_changes(dest, resources, task_handle) def _calculate_changes(self, dest, resources, task_handle): changes = ChangeSet("Moving global <%s>" % self.old_name) job_set = task_handle.create_jobset("Collecting Changes", len(resources)) for file_ in resources: job_set.started_job(file_.path) if file_ == self.source: changes.add_change(self._source_module_changes(dest)) elif file_ == dest: changes.add_change(self._dest_module_changes(dest)) elif self.tools.occurs_in_module(resource=file_): pymodule = self.project.get_pymodule(file_) # Changing occurrences placeholder = "__rope_renaming_%s_" % self.old_name source = self.tools.rename_in_module(placeholder, resource=file_) should_import = source is not None # Removing out of date imports pymodule = self.tools.new_pymodule(pymodule, source) source = self.import_tools.organize_imports( pymodule, sort=False, import_filter=self._import_filter ) # Adding new import if should_import: pymodule = self.tools.new_pymodule(pymodule, source) source, imported = importutils.add_import( self.project, pymodule, self._new_modname(dest), self.old_name ) source = source.replace(placeholder, imported) source = self.tools.new_source(pymodule, source) if source != file_.read(): changes.add_change(ChangeContents(file_, source)) job_set.finished_job() return changes def _source_module_changes(self, dest): placeholder = "__rope_moving_%s_" % self.old_name handle = _ChangeMoveOccurrencesHandle(placeholder) occurrence_finder = occurrences.create_finder( self.project, self.old_name, self.old_pyname ) start, end = self._get_moving_region() renamer = ModuleSkipRenamer(occurrence_finder, self.source, handle, start, end) source = renamer.get_changed_module() pymodule = libutils.get_string_module(self.project, source, self.source) source = self.import_tools.organize_imports(pymodule, sort=False) if handle.occurred: pymodule = libutils.get_string_module(self.project, source, self.source) # Adding new import source, imported = importutils.add_import( self.project, pymodule, self._new_modname(dest), self.old_name ) source = source.replace(placeholder, imported) return ChangeContents(self.source, source) def _new_modname(self, dest): return libutils.modname(dest) def _dest_module_changes(self, dest): # Changing occurrences pymodule = self.project.get_pymodule(dest) source = self.tools.rename_in_module(self.old_name, pymodule) pymodule = self.tools.new_pymodule(pymodule, source) moving, imports = self._get_moving_element_with_imports() pymodule, has_changed = self._add_imports2(pymodule, imports) module_with_imports = self.import_tools.module_imports(pymodule) source = pymodule.source_code lineno = 0 if module_with_imports.imports: lineno = module_with_imports.imports[-1].end_line - 1 else: while lineno < pymodule.lines.length() and pymodule.lines.get_line( lineno + 1 ).lstrip().startswith("#"): lineno += 1 if lineno > 0: cut = pymodule.lines.get_line_end(lineno) + 1 result = source[:cut] + "\n\n" + moving + source[cut:] else: result = moving + source # Organizing imports source = result pymodule = libutils.get_string_module(self.project, source, dest) source = self.import_tools.organize_imports(pymodule, sort=False, unused=False) # Remove unused imports of the old module pymodule = libutils.get_string_module(self.project, source, dest) source = self.import_tools.organize_imports( pymodule, sort=False, selfs=False, unused=True, import_filter=self._import_filter, ) return ChangeContents(dest, source) def _get_moving_element_with_imports(self): return moving_code_with_imports( self.project, self.source, self._get_moving_element() ) def _get_module_with_imports(self, source_code, resource): pymodule = libutils.get_string_module(self.project, source_code, resource) return self.import_tools.module_imports(pymodule) def _get_moving_element(self): start, end = self._get_moving_region() moving = self.source.read()[start:end] return moving.rstrip() + "\n" def _get_moving_region(self): pymodule = self.project.get_pymodule(self.source) lines = pymodule.lines if self._is_variable(self.old_pyname): logical_lines = pymodule.logical_lines lineno = logical_lines.logical_line_in( self.old_pyname.get_definition_location()[1] )[0] start = lines.get_line_start(lineno) end_line = logical_lines.logical_line_in(lineno)[1] else: node = self.old_pyname.get_object().ast_node start = lines.get_line_start( get_first_decorator_or_function_start_line(node) ) end_line = self.old_pyname.get_object().get_scope().get_end() # Include comment lines before the definition start_line = lines.get_line_number(start) while start_line > 1 and lines.get_line(start_line - 1).startswith("#"): start_line -= 1 start = lines.get_line_start(start_line) while end_line < lines.length() and lines.get_line(end_line + 1).strip() == "": end_line += 1 end = min(lines.get_line_end(end_line) + 1, len(pymodule.source_code)) return start, end def _add_imports2(self, pymodule, new_imports): source = self.tools.add_imports(pymodule, new_imports) if source is None: return pymodule, False else: resource = pymodule.get_resource() pymodule = libutils.get_string_module(self.project, source, resource) return pymodule, True class MoveModule: """For moving modules and packages""" def __init__(self, project, resource): self.project = project if not resource.is_folder() and resource.name == "__init__.py": resource = resource.parent if resource.is_folder() and not resource.has_child("__init__.py"): raise exceptions.RefactoringError("Cannot move non-package folder.") dummy_pymodule = libutils.get_string_module(self.project, "") self.old_pyname = pynames.ImportedModule(dummy_pymodule, resource=resource) self.source = self.old_pyname.get_object().get_resource() if self.source.is_folder(): self.old_name = self.source.name else: self.old_name = self.source.name[:-3] self.tools = _MoveTools( self.project, self.source, self.old_pyname, self.old_name ) self.import_tools = self.tools.import_tools def get_changes( self, dest, resources=None, task_handle=taskhandle.DEFAULT_TASK_HANDLE ): if resources is None: resources = self.project.get_python_files() if dest is None or not dest.is_folder(): raise exceptions.RefactoringError( "Move destination for modules should be packages." ) return self._calculate_changes(dest, resources, task_handle) def _calculate_changes(self, dest, resources, task_handle): changes = ChangeSet("Moving module <%s>" % self.old_name) job_set = task_handle.create_jobset("Collecting changes", len(resources)) for module in resources: job_set.started_job(module.path) if module == self.source: self._change_moving_module(changes, dest) else: source = self._change_occurrences_in_module(dest, resource=module) if source is not None: changes.add_change(ChangeContents(module, source)) job_set.finished_job() if self.project == self.source.project: changes.add_change(MoveResource(self.source, dest.path)) return changes def _new_modname(self, dest): destname = libutils.modname(dest) if destname: return destname + "." + self.old_name return self.old_name def _new_import(self, dest): return importutils.NormalImport([(self._new_modname(dest), None)]) def _change_moving_module(self, changes, dest): if not self.source.is_folder(): pymodule = self.project.get_pymodule(self.source) source = self.import_tools.relatives_to_absolutes(pymodule) pymodule = self.tools.new_pymodule(pymodule, source) source = self._change_occurrences_in_module(dest, pymodule) source = self.tools.new_source(pymodule, source) if source != self.source.read(): changes.add_change(ChangeContents(self.source, source)) def _change_occurrences_in_module(self, dest, pymodule=None, resource=None): if not self.tools.occurs_in_module(pymodule=pymodule, resource=resource): return if pymodule is None: pymodule = self.project.get_pymodule(resource) new_name = self._new_modname(dest) module_imports = importutils.get_module_imports(self.project, pymodule) changed = False source = None if libutils.modname(dest): changed = self._change_import_statements(dest, new_name, module_imports) if changed: source = module_imports.get_changed_source() source = self.tools.new_source(pymodule, source) pymodule = self.tools.new_pymodule(pymodule, source) new_import = self._new_import(dest) source = self.tools.rename_in_module( new_name, imports=True, pymodule=pymodule, resource=resource if not changed else None, ) should_import = self.tools.occurs_in_module( pymodule=pymodule, resource=resource, imports=False ) pymodule = self.tools.new_pymodule(pymodule, source) source = self.tools.remove_old_imports(pymodule) if should_import: pymodule = self.tools.new_pymodule(pymodule, source) source = self.tools.add_imports(pymodule, [new_import]) source = self.tools.new_source(pymodule, source) if source is not None and source != pymodule.resource.read(): return source return None def _change_import_statements(self, dest, new_name, module_imports): moving_module = self.source parent_module = moving_module.parent changed = False for import_stmt in module_imports.imports: if not any( name_and_alias[0] == self.old_name for name_and_alias in import_stmt.import_info.names_and_aliases ) and not any( name_and_alias[0] == libutils.modname(self.source) for name_and_alias in import_stmt.import_info.names_and_aliases ): continue # Case 1: Look for normal imports of the moving module. if isinstance(import_stmt.import_info, importutils.NormalImport): continue # Case 2: The moving module is from-imported. changed = ( self._handle_moving_in_from_import_stmt( dest, import_stmt, module_imports, parent_module ) or changed ) # Case 3: Names are imported from the moving module. context = importutils.importinfo.ImportContext(self.project, None) if ( not import_stmt.import_info.is_empty() and import_stmt.import_info.get_imported_resource(context) == moving_module ): import_stmt.import_info = importutils.FromImport( new_name, import_stmt.import_info.level, import_stmt.import_info.names_and_aliases, ) changed = True return changed def _handle_moving_in_from_import_stmt( self, dest, import_stmt, module_imports, parent_module ): changed = False context = importutils.importinfo.ImportContext(self.project, None) if import_stmt.import_info.get_imported_resource(context) == parent_module: imports = import_stmt.import_info.names_and_aliases new_imports = [] for name, alias in imports: # The moving module was imported. if name == self.old_name: changed = True new_import = importutils.FromImport( libutils.modname(dest), 0, [(self.old_name, alias)] ) module_imports.add_import(new_import) else: new_imports.append((name, alias)) # Update the imports if the imported names were changed. if new_imports != imports: changed = True if new_imports: import_stmt.import_info = importutils.FromImport( import_stmt.import_info.module_name, import_stmt.import_info.level, new_imports, ) else: import_stmt.empty_import() return changed class _ChangeMoveOccurrencesHandle: def __init__(self, new_name): self.new_name = new_name self.occurred = False def occurred_inside_skip(self, change_collector, occurrence): pass def occurred_outside_skip(self, change_collector, occurrence): start, end = occurrence.get_primary_range() change_collector.add_change(start, end, self.new_name) self.occurred = True class _MoveTools: def __init__(self, project, source, pyname, old_name): self.project = project self.source = source self.old_pyname = pyname self.old_name = old_name self.import_tools = importutils.ImportTools(self.project) def remove_old_imports(self, pymodule): old_source = pymodule.source_code module_with_imports = self.import_tools.module_imports(pymodule) class CanSelect: changed = False old_name = self.old_name old_pyname = self.old_pyname def __call__(self, name): try: if ( name == self.old_name and pymodule[name].get_object() == self.old_pyname.get_object() ): self.changed = True return False except exceptions.AttributeNotFoundError: pass return True can_select = CanSelect() module_with_imports.filter_names(can_select) new_source = module_with_imports.get_changed_source() if old_source != new_source: return new_source def rename_in_module(self, new_name, pymodule=None, imports=False, resource=None): occurrence_finder = self._create_finder(imports) source = rename.rename_in_module( occurrence_finder, new_name, replace_primary=True, pymodule=pymodule, resource=resource, ) return source def occurs_in_module(self, pymodule=None, resource=None, imports=True): finder = self._create_finder(imports) occurrences = finder.find_occurrences(pymodule=pymodule, resource=resource) sentinel = object() return next(occurrences, sentinel) is not sentinel def _create_finder(self, imports): return occurrences.create_finder( self.project, self.old_name, self.old_pyname, imports=imports, keywords=False, ) def new_pymodule(self, pymodule, source): if source is not None: return libutils.get_string_module( self.project, source, pymodule.get_resource() ) return pymodule def new_source(self, pymodule, source): if source is None: return pymodule.source_code return source def add_imports(self, pymodule, new_imports): return _add_imports_to_module(self.import_tools, pymodule, new_imports) def _add_imports_to_module(import_tools, pymodule, new_imports): module_with_imports = import_tools.module_imports(pymodule) for new_import in new_imports: module_with_imports.add_import(new_import) return module_with_imports.get_changed_source() def moving_code_with_imports(project, resource, source): import_tools = importutils.ImportTools(project) pymodule = libutils.get_string_module(project, source, resource) # Strip comment prefix, if any. These need to stay before the moving # section, but imports would be added between them. lines = codeanalyze.SourceLinesAdapter(source) start = 1 while start < lines.length() and lines.get_line(start).startswith("#"): start += 1 moving_prefix = source[: lines.get_line_start(start)] pymodule = libutils.get_string_module( project, source[lines.get_line_start(start) :], resource ) origin = project.get_pymodule(resource) imports = [stmt.import_info for stmt in import_tools.module_imports(origin).imports] back_names = [name for name in origin if name not in pymodule] imports.append(import_tools.get_from_import(resource, back_names)) source = _add_imports_to_module(import_tools, pymodule, imports) pymodule = libutils.get_string_module(project, source, resource) source = import_tools.relatives_to_absolutes(pymodule) pymodule = libutils.get_string_module(project, source, resource) source = import_tools.organize_imports(pymodule, selfs=False) pymodule = libutils.get_string_module(project, source, resource) # extracting imports after changes module_imports = import_tools.module_imports(pymodule) imports = [import_stmt.import_info for import_stmt in module_imports.imports] start = 1 if module_imports.imports: start = module_imports.imports[-1].end_line lines = codeanalyze.SourceLinesAdapter(source) while start < lines.length() and not lines.get_line(start).strip(): start += 1 # Reinsert the prefix which was removed at the beginning moving = moving_prefix + source[lines.get_line_start(start) :] return moving, imports class ModuleSkipRenamerHandle: def occurred_outside_skip(self, change_collector, occurrence): pass def occurred_inside_skip(self, change_collector, occurrence): pass class ModuleSkipRenamer: """Rename occurrences in a module This class can be used when you want to treat a region in a file separately from other parts when renaming. """ def __init__( self, occurrence_finder, resource, handle=None, skip_start=0, skip_end=0, replacement="", ): """Constructor if replacement is `None` the region is not changed. Otherwise it is replaced with `replacement`. """ self.occurrence_finder = occurrence_finder self.resource = resource self.skip_start = skip_start self.skip_end = skip_end self.replacement = replacement self.handle = handle if self.handle is None: self.handle = ModuleSkipRenamerHandle() def get_changed_module(self): source = self.resource.read() change_collector = codeanalyze.ChangeCollector(source) if self.replacement is not None: change_collector.add_change( self.skip_start, self.skip_end, self.replacement ) for occurrence in self.occurrence_finder.find_occurrences(self.resource): start, end = occurrence.get_primary_range() if self.skip_start <= start < self.skip_end: self.handle.occurred_inside_skip(change_collector, occurrence) else: self.handle.occurred_outside_skip(change_collector, occurrence) result = change_collector.get_changed() if result is not None and result != source: return result ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/rope/refactor/multiproject.py0000664000175000017500000000476014512700666020355 0ustar00lieryanlieryan"""This module can be used for performing cross-project refactorings See the "cross-project refactorings" section of ``docs/library.rst`` file. """ from rope.base import libutils, resources class MultiProjectRefactoring: def __init__(self, refactoring, projects, addpath=True): """Create a multiproject proxy for the main refactoring `projects` are other project. """ self.refactoring = refactoring self.projects = projects self.addpath = addpath def __call__(self, project, *args, **kwds): """Create the refactoring""" return _MultiRefactoring( self.refactoring, self.projects, self.addpath, project, *args, **kwds ) class _MultiRefactoring: def __init__(self, refactoring, other_projects, addpath, project, *args, **kwds): self.refactoring = refactoring self.projects = [project] + other_projects for other_project in other_projects: for folder in self.project.get_source_folders(): other_project.get_prefs().add("python_path", folder.real_path) self.refactorings = [] for other in self.projects: args, kwds = self._resources_for_args(other, args, kwds) self.refactorings.append(self.refactoring(other, *args, **kwds)) def get_all_changes(self, *args, **kwds): """Get a project to changes dict""" result = [] for project, refactoring in zip(self.projects, self.refactorings): args, kwds = self._resources_for_args(project, args, kwds) result.append((project, refactoring.get_changes(*args, **kwds))) return result def __getattr__(self, name): return getattr(self.main_refactoring, name) def _resources_for_args(self, project, args, kwds): newargs = [self._change_project_resource(project, arg) for arg in args] newkwds = { name: self._change_project_resource(project, value) for name, value in kwds.items() } return newargs, newkwds def _change_project_resource(self, project, obj): if isinstance(obj, resources.Resource) and obj.project != project: return libutils.path_to_resource(project, obj.real_path) return obj @property def project(self): return self.projects[0] @property def main_refactoring(self): return self.refactorings[0] def perform(project_changes): for project, changes in project_changes: project.do(changes) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/rope/refactor/occurrences.py0000664000175000017500000003136014512700666020143 0ustar00lieryanlieryan"""Find occurrences of a name in a project. This module consists of a `Finder` that finds all occurrences of a name in a project. The `Finder.find_occurrences()` method is a generator that yields `Occurrence` instances for each occurrence of the name. To create a `Finder` object, use the `create_finder()` function: finder = occurrences.create_finder(project, 'foo', pyname) for occurrence in finder.find_occurrences(): pass It's possible to filter the occurrences. They can be specified when calling the `create_finder()` function. * `only_calls`: If True, return only those instances where the name is a function that's being called. * `imports`: If False, don't return instances that are in import statements. * `unsure`: If a predicate function, return instances where we don't know what the name references. It also filters based on the predicate function. * `docs`: If True, it will search for occurrences in regions normally ignored. E.g., strings and comments. * `in_hierarchy`: If True, it will find occurrences if the name is in the class's hierarchy. * `instance`: Used only when you want implicit interfaces to be considered. * `keywords`: If False, don't return instances that are the names of keyword arguments """ import contextlib import re from rope.base import ( ast, codeanalyze, evaluate, exceptions, pynames, pyobjects, utils, worder, ) class Finder: """For finding occurrences of a name The constructor takes a `filters` argument. It should be a list of functions that take a single argument. For each possible occurrence, these functions are called in order with the an instance of `Occurrence`: * If it returns `None` other filters are tried. * If it returns `True`, the occurrence will be a match. * If it returns `False`, the occurrence will be skipped. * If all of the filters return `None`, it is skipped also. """ def __init__(self, project, name, filters=None, docs=False): if filters is None: filters = [lambda o: True] self.project = project self.name = name self.docs = docs self.filters = filters self._textual_finder = _TextualFinder(name, docs=docs) def find_occurrences(self, resource=None, pymodule=None): """Generate `Occurrence` instances""" tools = _OccurrenceToolsCreator( self.project, resource=resource, pymodule=pymodule, docs=self.docs ) for offset in self._textual_finder.find_offsets(tools.source_code): occurrence = Occurrence(tools, offset) for filter in self.filters: result = filter(occurrence) if result is None: continue if result: yield occurrence break def create_finder( project, name, pyname, only_calls=False, imports=True, unsure=None, docs=False, instance=None, in_hierarchy=False, keywords=True, ): """A factory for `Finder` Based on the arguments it creates a list of filters. `instance` argument is needed only when you want implicit interfaces to be considered. """ pynames_ = {pyname} filters = [] if only_calls: filters.append(CallsFilter()) if not imports: filters.append(NoImportsFilter()) if not keywords: filters.append(NoKeywordsFilter()) if isinstance(instance, pynames.ParameterName): for pyobject in instance.get_objects(): try: pynames_.add(pyobject[name]) except exceptions.AttributeNotFoundError: pass for pyname in pynames_: filters.append(PyNameFilter(pyname)) if in_hierarchy: filters.append(InHierarchyFilter(pyname)) if unsure: filters.append(UnsureFilter(unsure)) return Finder(project, name, filters=filters, docs=docs) class Occurrence: def __init__(self, tools, offset): self.tools = tools self.offset = offset self.resource = tools.resource @utils.saveit def get_word_range(self): return self.tools.word_finder.get_word_range(self.offset) @utils.saveit def get_primary_range(self): return self.tools.word_finder.get_primary_range(self.offset) @utils.saveit def get_pyname(self): with contextlib.suppress(exceptions.BadIdentifierError): return self.tools.name_finder.get_pyname_at(self.offset) @utils.saveit def get_primary_and_pyname(self): with contextlib.suppress(exceptions.BadIdentifierError): return self.tools.name_finder.get_primary_and_pyname_at(self.offset) @utils.saveit def is_in_import_statement(self): return self.tools.word_finder.is_from_statement( self.offset ) or self.tools.word_finder.is_import_statement(self.offset) def is_called(self): return self.tools.word_finder.is_a_function_being_called(self.offset) def is_defined(self): return self.tools.word_finder.is_a_class_or_function_name_in_header(self.offset) def is_a_fixed_primary(self): return self.tools.word_finder.is_a_class_or_function_name_in_header( self.offset ) or self.tools.word_finder.is_a_name_after_from_import(self.offset) def is_written(self): return self.tools.word_finder.is_assigned_here(self.offset) def is_unsure(self): return unsure_pyname(self.get_pyname()) def is_function_keyword_parameter(self): return self.tools.word_finder.is_function_keyword_parameter(self.offset) @property @utils.saveit def lineno(self): offset = self.get_word_range()[0] return self.tools.pymodule.lines.get_line_number(offset) def same_pyname(expected, pyname): """Check whether `expected` and `pyname` are the same""" if expected is None or pyname is None: return False if expected == pyname: return True if not isinstance( expected, (pynames.ImportedModule, pynames.ImportedName), ) and not isinstance( pyname, (pynames.ImportedModule, pynames.ImportedName), ): return False return ( expected.get_definition_location() == pyname.get_definition_location() and expected.get_object() == pyname.get_object() ) def unsure_pyname(pyname, unbound=True): """Return `True` if we don't know what this name references""" if pyname is None: return True if unbound and not isinstance(pyname, pynames.UnboundName): return False if pyname.get_object() == pyobjects.get_unknown(): return True class PyNameFilter: """For finding occurrences of a name.""" def __init__(self, pyname): self.pyname = pyname def __call__(self, occurrence): if same_pyname(self.pyname, occurrence.get_pyname()): return True class InHierarchyFilter: """Finds the occurrence if the name is in the class's hierarchy.""" def __init__(self, pyname, implementations_only=False): self.pyname = pyname self.impl_only = implementations_only self.pyclass = self._get_containing_class(pyname) if self.pyclass is not None: self.name = pyname.get_object().get_name() self.roots = self._get_root_classes(self.pyclass, self.name) else: self.roots = None def __call__(self, occurrence): if self.roots is None: return pyclass = self._get_containing_class(occurrence.get_pyname()) if pyclass is not None: roots = self._get_root_classes(pyclass, self.name) if self.roots.intersection(roots): return True def _get_containing_class(self, pyname): if isinstance(pyname, pynames.DefinedName): scope = pyname.get_object().get_scope() parent = scope.parent if parent is not None and parent.get_kind() == "Class": return parent.pyobject def _get_root_classes(self, pyclass, name): if self.impl_only and pyclass == self.pyclass: return {pyclass} result = set() for superclass in pyclass.get_superclasses(): if name in superclass: result.update(self._get_root_classes(superclass, name)) if not result: return {pyclass} return result class UnsureFilter: """Occurrences where we don't knoow what the name references.""" def __init__(self, unsure): self.unsure = unsure def __call__(self, occurrence): if occurrence.is_unsure() and self.unsure(occurrence): return True class NoImportsFilter: """Don't include import statements as occurrences.""" def __call__(self, occurrence): if occurrence.is_in_import_statement(): return False class CallsFilter: """Filter out non-call occurrences.""" def __call__(self, occurrence): if not occurrence.is_called(): return False class NoKeywordsFilter: """Filter out keyword parameters.""" def __call__(self, occurrence): if occurrence.is_function_keyword_parameter(): return False class _TextualFinder: def __init__(self, name, docs=False): self.name = name self.docs = docs self.comment_pattern = _TextualFinder.any("comment", [r"#[^\n]*"]) self.string_pattern = _TextualFinder.any( "string", [codeanalyze.get_string_pattern()] ) self.f_string_pattern = _TextualFinder.any( "fstring", [codeanalyze.get_formatted_string_pattern()] ) self.pattern = self._get_occurrence_pattern(self.name) def find_offsets(self, source): if not self._fast_file_query(source): return if self.docs: searcher = self._normal_search else: searcher = self._re_search yield from searcher(source) def _re_search(self, source): for match in self.pattern.finditer(source): if match.groupdict()["occurrence"]: yield match.start("occurrence") elif match.groupdict()["fstring"]: f_string = match.groupdict()["fstring"] for occurrence_node in self._search_in_f_string(f_string): yield match.start("fstring") + occurrence_node.col_offset def _search_in_f_string(self, f_string): tree = ast.parse(f_string) for node in ast.walk(tree): if isinstance(node, ast.Name) and node.id == self.name: yield node def _normal_search(self, source): current = 0 while True: try: found = source.index(self.name, current) current = found + len(self.name) if (found == 0 or not self._is_id_char(source[found - 1])) and ( current == len(source) or not self._is_id_char(source[current]) ): yield found except ValueError: break def _is_id_char(self, c): return c.isalnum() or c == "_" def _fast_file_query(self, source): return self.name in source def _get_source(self, resource, pymodule): if resource is not None: return resource.read() else: return pymodule.source_code def _get_occurrence_pattern(self, name): occurrence_pattern = _TextualFinder.any("occurrence", ["\\b" + name + "\\b"]) pattern = re.compile( occurrence_pattern + "|" + self.comment_pattern + "|" + self.string_pattern + "|" + self.f_string_pattern ) return pattern @staticmethod def any(name, list_): return "(?P<%s>" % name + "|".join(list_) + ")" class _OccurrenceToolsCreator: def __init__(self, project, resource=None, pymodule=None, docs=False): self.project = project self.__resource = resource self.__pymodule = pymodule self.docs = docs @property @utils.saveit def name_finder(self): return evaluate.ScopeNameFinder(self.pymodule) @property @utils.saveit def source_code(self): return self.pymodule.source_code @property @utils.saveit def word_finder(self): return worder.Worder(self.source_code, self.docs) @property @utils.saveit def resource(self): if self.__resource is not None: return self.__resource if self.__pymodule is not None: return self.__pymodule.resource @property @utils.saveit def pymodule(self): if self.__pymodule is not None: return self.__pymodule return self.project.get_pymodule(self.resource) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1699196919.0 rope-1.12.0/rope/refactor/patchedast.py0000664000175000017500000007504014521727767017766 0ustar00lieryanlieryanimport collections import numbers import re import warnings from itertools import chain from rope.base import ast, codeanalyze, exceptions COMMA_IN_WITH_PATTERN = re.compile(r"\(.*?\)|(,)") def get_patched_ast(source, sorted_children=False): """Adds ``region`` and ``sorted_children`` fields to nodes Adds ``sorted_children`` field only if `sorted_children` is True. """ return patch_ast(ast.parse(source), source, sorted_children) def patch_ast(node, source, sorted_children=False): """Patches the given node After calling, each node in `node` will have a new field named `region` that is a tuple containing the start and end offsets of the code that generated it. If `sorted_children` is true, a `sorted_children` field will be created for each node, too. It is a list containing child nodes as well as whitespaces and comments that occur between them. """ if hasattr(node, "region"): return node walker = _PatchingASTWalker(source, children=sorted_children) walker(node) return node def node_region(patched_ast_node): """Get the region of a patched ast node""" return patched_ast_node.region def write_ast(patched_ast_node): """Extract source form a patched AST node with `sorted_children` field If the node is patched with sorted_children turned off you can use `node_region` function for obtaining code using module source code. """ result = [] for child in patched_ast_node.sorted_children: if isinstance(child, ast.AST): result.append(write_ast(child)) else: result.append(child) return "".join(result) class MismatchedTokenError(exceptions.RopeError): pass class _PatchingASTWalker: def __init__(self, source, children=False): self.source = _Source(source) self.children = children self.lines = codeanalyze.SourceLinesAdapter(source) self.children_stack = [] Number = object() String = object() with_or_comma_context_manager = object() empty_tuple = object() def __call__(self, node): method = getattr(self, "_" + node.__class__.__name__, None) if method is not None: return method(node) # ???: Unknown node; what should we do here? warnings.warn( "Unknown node type <%s>; please report!" % node.__class__.__name__, RuntimeWarning, ) node.region = (self.source.offset, self.source.offset) if self.children: node.sorted_children = [child for field, child in ast.iter_fields(node)] def _handle(self, node, base_children, eat_parens=False, eat_spaces=False): if hasattr(node, "region"): # ???: The same node was seen twice; what should we do? warnings.warn( "Node <%s> has been already patched; please report!" % node.__class__.__name__, RuntimeWarning, ) return base_children = collections.deque(base_children) self.children_stack.append(base_children) children = collections.deque() formats = [] suspected_start = self.source.offset start = suspected_start first_token = True while base_children: child = base_children.popleft() if child is None: continue offset = self.source.offset if isinstance(child, ast.AST): self(child) token_start = child.region[0] else: if child is self.String: region = self.source.consume_string( end=self._find_next_statement_start() ) elif child is self.Number: region = self.source.consume_number() elif child == self.empty_tuple: region = self.source.consume_empty_tuple() elif child == self.with_or_comma_context_manager: region = self.source.consume_with_or_comma_context_manager() elif 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: if not isinstance(node, ast.JoinedStr): formats.append(self.source[offset:token_start]) if self.children: children.append(self.source[offset:token_start]) else: first_token = False start = token_start if self.children: children.append(child) start = self._handle_parens(children, start, formats) if eat_parens: start = self._eat_surrounding_parens(children, suspected_start, start) if eat_spaces: if self.children: children.appendleft(self.source[0:start]) end_spaces = self.source[self.source.offset :] self.source.consume(end_spaces) if self.children: children.append(end_spaces) start = 0 if self.children: node.sorted_children = children node.region = (start, self.source.offset) self.children_stack.pop() def _handle_parens(self, children, start, formats): """Changes `children` and returns new start""" opens, closes = self._count_needed_parens(formats) old_end = self.source.offset new_end = None for i in range(closes): new_end = self.source.consume(")")[1] if new_end is not None and self.children: children.append(self.source[old_end:new_end]) new_start = start for i in range(opens): new_start = self.source.rfind_token("(", 0, new_start) if new_start != start: if self.children: children.appendleft(self.source[new_start:start]) start = new_start return start def _eat_surrounding_parens(self, children, suspected_start, start): index = self.source.rfind_token("(", suspected_start, start) if index is not None: old_start = start old_offset = self.source.offset start = index if self.children: children.appendleft(self.source[start + 1 : old_start]) children.appendleft("(") token_start, token_end = self.source.consume(")") if self.children: children.append(self.source[old_offset:token_start]) children.append(")") return start def _count_needed_parens(self, children): start = 0 opens = 0 for child in children: if not isinstance(child, (str, bytes)): continue if child == "" or child[0] in "'\"": continue index = 0 while index < len(child): if child[index] == ")": if opens > 0: opens -= 1 else: start += 1 if child[index] == "(": opens += 1 if child[index] == "#": try: index = child.index("\n", index) except ValueError: break index += 1 return start, opens def _find_next_statement_start(self): for children in reversed(self.children_stack): for child in children: if isinstance(child, ast.stmt): return child.col_offset + self.lines.get_line_start(child.lineno) return len(self.source.source) def _join(self, iterable, separator): iterable = iter(iterable) try: yield next(iterable) except StopIteration: return for child in iterable: yield separator yield child def _flatten_keywords(self, iterable): iterable = ([attr, "=", pattern] for attr, pattern in iterable) iterable = self._join(iterable, separator=[","]) return chain.from_iterable(iterable) def _child_nodes(self, nodes, separator): return list(self._join(nodes, separator=separator)) _operators = { "And": "and", "Or": "or", "Add": "+", "Sub": "-", "Mult": "*", "Div": "/", "Mod": "%", "Pow": "**", "MatMult": "@", "LShift": "<<", "RShift": ">>", "BitOr": "|", "BitAnd": "&", "BitXor": "^", "FloorDiv": "//", "Invert": "~", "Not": "not", "UAdd": "+", "USub": "-", "Eq": "==", "NotEq": "!=", "Lt": "<", "LtE": "<=", "Gt": ">", "GtE": ">=", "Is": "is", "IsNot": "is not", "In": "in", "NotIn": "not in", } def _get_op(self, node): return self._operators[node.__class__.__name__].split(" ") def _Attribute(self, node): self._handle(node, [node.value, ".", node.attr]) def _Assert(self, node): children = ["assert", node.test] if node.msg: children.append(",") children.append(node.msg) self._handle(node, children) def _Assign(self, node): children = [*self._child_nodes(node.targets, "="), "=", node.value] self._handle(node, children) def _AugAssign(self, node): children = [node.target, *self._get_op(node.op), "=", 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 _BinOp(self, node): children = [node.left] + self._get_op(node.op) + [node.right] self._handle(node, children) def _BoolOp(self, node): self._handle(node, self._child_nodes(node.values, self._get_op(node.op)[0])) def _Break(self, node): self._handle(node, ["break"]) def _Call(self, node): def _arg_sort_key(node): if isinstance(node, ast.keyword): return (node.value.lineno, node.value.col_offset) return (node.lineno, node.col_offset) children = [node.func, "("] args = sorted([*node.args, *node.keywords], key=_arg_sort_key) children.extend(self._child_nodes(args, ",")) children.append(")") self._handle(node, children) def _ClassDef(self, node): children = [] for decorator in node.decorator_list: children.extend(("@", decorator)) children.extend(["class", node.name]) if node.bases: children.append("(") children.extend(self._child_nodes(node.bases, ",")) children.append(")") children.append(":") children.extend(node.body) self._handle(node, children) def _Compare(self, node): children = [] children.append(node.left) for op, expr in zip(node.ops, node.comparators): children.extend(self._get_op(op)) children.append(expr) self._handle(node, children) def _Delete(self, node): self._handle(node, ["del"] + self._child_nodes(node.targets, ",")) def _Constant(self, node): if isinstance(node.value, (str, bytes)): self._handle(node, [self.String]) return if any(node.value is v for v in [True, False, None]): self._handle(node, [str(node.value)]) return if isinstance(node.value, numbers.Number): self._handle(node, [self.Number]) return if node.value is Ellipsis: self._handle(node, ["..."]) return assert False def _Num(self, node): self._handle(node, [self.Number]) def _Str(self, node): self._handle(node, [self.String]) 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: children.append(val.value) children.append("}") self._handle(node, children) def _Continue(self, node): self._handle(node, ["continue"]) def _Dict(self, node): children = [] children.append("{") if node.keys: for index, (key, value) in enumerate(zip(node.keys, node.values)): 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("}") self._handle(node, children) def _Ellipsis(self, node): self._handle(node, ["..."]) def _Expr(self, node): self._handle(node, [node.value]) def _NamedExpr(self, node): children = [node.target, ":=", node.value] self._handle(node, children) def _ExtSlice(self, node): children = [] for index, dim in enumerate(node.dims): if index > 0: children.append(",") children.append(dim) self._handle(node, children) def _handle_for_loop_node(self, node, is_async): children = ["async", "for"] if is_async else ["for"] children.extend([node.target, "in", node.iter, ":"]) children.extend(node.body) if node.orelse: children.extend(["else", ":"]) children.extend(node.orelse) self._handle(node, children) def _For(self, node): self._handle_for_loop_node(node, is_async=False) def _AsyncFor(self, node): self._handle_for_loop_node(node, is_async=True) def _ImportFrom(self, node): children = ["from"] if node.level: children.extend("." * node.level) if node.module: children.extend(node.module.split(".")) children.append("import") children.extend(self._child_nodes(node.names, ",")) self._handle(node, children) def _alias(self, node): children = node.name.split(".") if node.asname: children.extend(["as", node.asname]) self._handle(node, children) def _handle_function_def_node(self, node, is_async): children = [] for decorator in node.decorator_list: children.extend(("@", decorator)) children.extend(["async", "def"] if is_async else ["def"]) children.append(node.name) children.extend(["(", node.args, ")"]) children.append(":") children.extend(node.body) self._handle(node, children) def _FunctionDef(self, node): self._handle_function_def_node(node, is_async=False) def _AsyncFunctionDef(self, node): self._handle_function_def_node(node, is_async=True) def _arguments(self, node): children = [] args = list(node.args) defaults = [None] * (len(args) - len(node.defaults)) + list(node.defaults) for index, (arg, default) in enumerate(zip(args, defaults)): if index > 0: children.append(",") self._add_args_to_children(children, arg, default) if node.vararg is not None: if args: children.append(",") children.extend(["*", node.vararg.arg]) if node.kwarg is not None: if args or node.vararg is not None: children.append(",") children.extend(["**", node.kwarg.arg]) self._handle(node, children) def _add_args_to_children(self, children, arg, default): if isinstance(arg, (list, tuple)): self._add_tuple_parameter(children, arg) else: children.append(arg) if default is not None: children.append("=") children.append(default) def _add_tuple_parameter(self, children, arg): children.append("(") for index, token in enumerate(arg): if index > 0: children.append(",") if isinstance(token, (list, tuple)): self._add_tuple_parameter(children, token) else: children.append(token) children.append(")") def _GeneratorExp(self, node): children = [node.elt, *node.generators] self._handle(node, children, eat_parens=True) def _comprehension(self, node): children = ["for", node.target, "in", node.iter] for if_ in node.ifs: children.extend(["if", if_]) self._handle(node, children) def _Global(self, node): children = ["global", *self._child_nodes(node.names, ",")] self._handle(node, children) def _Nonlocal(self, node): children = ["nonlocal", *self._child_nodes(node.names, ",")] self._handle(node, children) def _If(self, node): children = ["elif"] if self._is_elif(node) else ["if"] children.extend([node.test, ":"]) children.extend(node.body) if node.orelse: if len(node.orelse) == 1 and self._is_elif(node.orelse[0]): pass else: children.extend(["else", ":"]) children.extend(node.orelse) self._handle(node, children) def _is_elif(self, node): if not isinstance(node, ast.If): return False offset = self.lines.get_line_start(node.lineno) + node.col_offset word = self.source[offset : offset + 4] # XXX: This is a bug; the offset does not point to the first alt_word = self.source[offset - 5 : offset - 1] return "elif" in (word, alt_word) def _IfExp(self, node): return self._handle(node, [node.body, "if", node.test, "else", node.orelse]) def _Import(self, node): children = ["import", *self._child_nodes(node.names, ",")] self._handle(node, children) def _keyword(self, node): if node.arg is None: children = [node.value] else: children = [node.arg, "=", node.value] self._handle(node, children) def _Lambda(self, node): self._handle(node, ["lambda", node.args, ":", node.body]) def _List(self, node): self._handle(node, ["[", *self._child_nodes(node.elts, ","), "]"]) def _ListComp(self, node): children = ["[", node.elt, *node.generators, "]"] self._handle(node, children) def _Set(self, node): if node.elts: self._handle(node, ["{", *self._child_nodes(node.elts, ","), "}"]) return # Python doesn't have empty set literals warnings.warn( "Tried to handle empty literal; please report!", RuntimeWarning ) self._handle(node, ["set(", ")"]) def _SetComp(self, node): children = ["{", node.elt, *node.generators, "}"] self._handle(node, children) def _DictComp(self, node): children = ["{", *[node.key, ":", node.value], *node.generators, "}"] self._handle(node, children) def _Module(self, node): self._handle(node, list(node.body), eat_spaces=True) def _Name(self, node): self._handle(node, [node.id]) def _NameConstant(self, node): self._handle(node, [str(node.value)]) def _arg(self, node): self._handle(node, [node.arg]) def _Pass(self, node): self._handle(node, ["pass"]) def _Raise(self, node): children = ["raise"] if node.exc: children.append(node.exc) if node.cause: children.append("from") children.append(node.cause) self._handle(node, children) def _Return(self, node): children = ["return"] if node.value: children.append(node.value) self._handle(node, children) def _Index(self, node): self._handle(node, [node.value]) def _Subscript(self, node): self._handle(node, [node.value, "[", node.slice, "]"]) def _Slice(self, node): children = [] if node.lower: children.append(node.lower) children.append(":") if node.upper: children.append(node.upper) if node.step: children.append(":") children.append(node.step) self._handle(node, children) def _TryFinally(self, node): # @todo fixme is_there_except_handler = False not_empty_body = True if len(node.finalbody) == 1: try: is_there_except_handler = isinstance( node.handlers[0], ast.ExceptHandler ) not_empty_body = True except IndexError: pass children = [] if not_empty_body or not is_there_except_handler: children.extend(["try", ":"]) children.extend(node.body) children.extend(node.handlers) children.extend(["finally", ":"]) children.extend(node.finalbody) self._handle(node, children) def _TryExcept(self, node): children = ["try", ":"] children.extend(node.body) children.extend(node.handlers) if node.orelse: children.extend(["else", ":"]) children.extend(node.orelse) self._handle(node, children) def _Try(self, node): if len(node.finalbody): self._TryFinally(node) else: self._TryExcept(node) def _TryStar(self, node): self._Try(node) def _ExceptHandler(self, node): self._excepthandler(node) def _excepthandler(self, node): children = ["except"] if node.type: children.append(node.type) if node.name: children.append("as") children.append(node.name) children.append(":") children.extend(node.body) self._handle(node, children) def _Tuple(self, node): if node.elts: self._handle(node, self._child_nodes(node.elts, ","), eat_parens=True) else: self._handle(node, [self.empty_tuple]) def _UnaryOp(self, node): children = self._get_op(node.op) children.append(node.operand) self._handle(node, children) def _Await(self, node): children = ["await"] if node.value: children.append(node.value) self._handle(node, children) def _Yield(self, node): children = ["yield"] if node.value: 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) if node.orelse: children.extend(["else", ":"]) children.extend(node.orelse) self._handle(node, children) def _handle_with_node(self, node, is_async): children = [] if is_async: children.extend(["async"]) for item in node.items: children.extend([self.with_or_comma_context_manager, item.context_expr]) if item.optional_vars: children.extend(["as", item.optional_vars]) children.append(":") children.extend(node.body) self._handle(node, children) def _With(self, node): self._handle_with_node(node, is_async=False) def _AsyncWith(self, node): self._handle_with_node(node, is_async=True) def _Starred(self, node): self._handle(node, [node.value]) def _Match(self, node): children = ["match", node.subject, ":"] children.extend(node.cases) self._handle(node, children) def _match_case(self, node): children = ["case", node.pattern] if node.guard: children.extend(["if", node.guard]) children.append(":") children.extend(node.body) self._handle(node, children) def _MatchAs(self, node): if node.pattern: children = [node.pattern, "as", node.name] elif node.name is None: children = ["_"] else: children = [node.name] self._handle(node, children) def _MatchClass(self, node): children = [ node.cls, "(", *self._child_nodes(node.patterns, ","), *self._flatten_keywords(zip(node.kwd_attrs, node.kwd_patterns)), ")", ] self._handle(node, children) def _MatchValue(self, node): self._handle(node, [node.value]) def _MatchMapping(self, node): children = [] children.append("{") for index, (key, value) in enumerate(zip(node.keys, node.patterns)): children.extend([key, ":", value]) if index < len(node.keys) - 1: children.append(",") children.append("}") self._handle(node, children) class _Source: def __init__(self, source): self.source = source self.offset = 0 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) or not skip_comment: break else: self._skip_comment() except (ValueError, TypeError): raise MismatchedTokenError( f"Token <{token}> at {self._get_location()} cannot be matched" ) 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: string_pattern = codeanalyze.get_string_pattern() formatted_string_pattern = codeanalyze.get_formatted_string_pattern() original = r"(?:{})|(?:{})".format( string_pattern, formatted_string_pattern, ) pattern = r"({})((\s|\\\n|#[^\n]*\n)*({}))*".format( original, original, ) _Source._string_pattern = re.compile(pattern) repattern = _Source._string_pattern return self._consume_pattern(repattern, end) def consume_number(self): if _Source._number_pattern is None: _Source._number_pattern = re.compile(self._get_number_pattern()) repattern = _Source._number_pattern return self._consume_pattern(repattern) def consume_empty_tuple(self): return self._consume_pattern(re.compile(r"\(\s*\)")) def consume_with_or_comma_context_manager(self): repattern = re.compile(r"with|,") return self._consume_pattern(repattern) def _good_token(self, token, offset, start=None): """Checks whether consumed token is in comments""" if start is None: start = self.offset try: comment_index = self.source.rindex("#", start, offset) except ValueError: return True try: new_line_index = self.source.rindex("\n", start, offset) except ValueError: return False return comment_index < new_line_index def _skip_comment(self): self.offset = self.source.index("\n", self.offset + 1) def _get_location(self): lines = self.source[: self.offset].split("\n") return (len(lines), len(lines[-1])) def _consume_pattern(self, repattern, end=None): while True: if end is None: end = len(self.source) match = repattern.search(self.source, self.offset, end) if self._good_token(match.group(), match.start()): break else: self._skip_comment() self.offset = match.end() return match.start(), match.end() def till_token(self, token): new_offset = self.source.index(token, self.offset) return self[self.offset : new_offset] def rfind_token(self, token, start, end): index = start while True: try: index = self.source.rindex(token, start, end) if self._good_token(token, index, start=start): return index else: end = index except ValueError: return None def from_offset(self, offset): return self[offset : self.offset] def find_backwards(self, pattern, offset): return self.source.rindex(pattern, 0, offset) def __getitem__(self, index): return self.source[index] def __getslice__(self, i, j): return self.source[i:j] def _get_number_pattern(self): # HACK: It is merely an approaximation and does the job integer = r"\-?(0[xo][\da-fA-F]+|\d+)" return r"(%s(\.\d*)?|(\.\d+))([eE][-+]?\d+)?[jJ]?" % integer _string_pattern = None _number_pattern = None ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/rope/refactor/rename.py0000664000175000017500000002220414512700666017074 0ustar00lieryanlieryanimport warnings from rope.base import ( codeanalyze, evaluate, exceptions, libutils, pynames, pyobjects, taskhandle, worder, ) from rope.base.change import ChangeContents, ChangeSet, MoveResource from rope.refactor import occurrences class Rename: """A class for performing rename refactoring It can rename everything: classes, functions, modules, packages, methods, variables and keyword arguments. """ def __init__(self, project, resource, offset=None): """If `offset` is None, the `resource` itself will be renamed""" self.project = project self.resource = resource 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( "Rename refactoring should be performed" " on resolvable python identifiers." ) else: if not resource.is_folder() and resource.name == "__init__.py": resource = resource.parent dummy_pymodule = libutils.get_string_module(self.project, "") self.old_instance = None self.old_pyname = pynames.ImportedModule(dummy_pymodule, resource=resource) if resource.is_folder(): self.old_name = resource.name else: self.old_name = resource.name[:-3] def get_old_name(self): return self.old_name def get_changes( self, new_name, in_file=None, in_hierarchy=False, unsure=None, docs=False, resources=None, task_handle=taskhandle.DEFAULT_TASK_HANDLE, ): """Get the changes needed for this refactoring Parameters: - `in_hierarchy`: when renaming a method this keyword forces to rename all matching methods in the hierarchy - `docs`: when `True` rename refactoring will rename occurrences in comments and strings where the name is visible. Setting it will make renames faster, too. - `unsure`: decides what to do about unsure occurrences. If `None`, they are ignored. Otherwise `unsure` is called with an instance of `occurrence.Occurrence` as parameter. If it returns `True`, the occurrence is considered to be a match. - `resources` can be a list of `rope.base.resources.File` to apply this refactoring on. If `None`, the restructuring will be applied to all python files. - `in_file`: this argument has been deprecated; use `resources` instead. """ if unsure in (True, False): warnings.warn( "unsure parameter should be a function that returns " "True or False", DeprecationWarning, stacklevel=2, ) def unsure_func(value=unsure): return value unsure = unsure_func if in_file is not None: warnings.warn( "`in_file` argument has been deprecated; use `resources` " "instead. ", DeprecationWarning, stacklevel=2, ) if in_file: resources = [self.resource] if _is_local(self.old_pyname): resources = [self.resource] if resources is None: resources = self.project.get_python_files() changes = ChangeSet(f"Renaming <{self.old_name}> to <{new_name}>") finder = occurrences.create_finder( self.project, self.old_name, self.old_pyname, unsure=unsure, docs=docs, instance=self.old_instance, in_hierarchy=in_hierarchy and self.is_method(), ) job_set = task_handle.create_jobset("Collecting Changes", len(resources)) for file_ in resources: job_set.started_job(file_.path) new_content = rename_in_module(finder, new_name, resource=file_) if new_content is not None: changes.add_change(ChangeContents(file_, new_content)) job_set.finished_job() if self._is_renaming_a_module(): resource = self.old_pyname.get_object().get_resource() if self._is_allowed_to_move(resources, resource): self._rename_module(resource, new_name, changes) return changes def _is_allowed_to_move(self, resources, resource): if resource.is_folder(): try: return resource.get_child("__init__.py") in resources except exceptions.ResourceNotFoundError: return False else: return resource in resources def _is_renaming_a_module(self): return isinstance(self.old_pyname.get_object(), pyobjects.AbstractModule) def is_method(self): pyname = self.old_pyname return ( isinstance(pyname, pynames.DefinedName) and isinstance(pyname.get_object(), pyobjects.PyFunction) and isinstance(pyname.get_object().parent, pyobjects.PyClass) ) def _rename_module(self, resource, new_name, changes): if not resource.is_folder(): new_name = new_name + ".py" parent_path = resource.parent.path if parent_path == "": new_location = new_name else: new_location = parent_path + "/" + new_name changes.add_change(MoveResource(resource, new_location)) class ChangeOccurrences: """A class for changing the occurrences of a name in a scope This class replaces the occurrences of a name. Note that it only changes the scope containing the offset passed to the constructor. What's more it does not have any side-effects. That is for example changing occurrences of a module does not rename the module; it merely replaces the occurrences of that module in a scope with the given expression. This class is useful for performing many custom refactorings. """ def __init__(self, project, resource, offset): self.project = project self.resource = resource self.offset = offset self.old_name = worder.get_name_at(resource, offset) self.pymodule = project.get_pymodule(self.resource) self.old_pyname = evaluate.eval_location(self.pymodule, offset) def get_old_name(self): word_finder = worder.Worder(self.resource.read()) return word_finder.get_primary_at(self.offset) def _get_scope_offset(self): scope = self.pymodule.get_scope().get_inner_scope_for_offset(self.offset) return scope.get_region() def get_changes(self, new_name, only_calls=False, reads=True, writes=True): changes = ChangeSet(f"Changing <{self.old_name}> occurrences to <{new_name}>") scope_start, scope_end = self._get_scope_offset() finder = occurrences.create_finder( self.project, self.old_name, self.old_pyname, imports=False, only_calls=only_calls, ) new_contents = rename_in_module( finder, new_name, pymodule=self.pymodule, replace_primary=True, region=(scope_start, scope_end), reads=reads, writes=writes, ) if new_contents is not None: changes.add_change(ChangeContents(self.resource, new_contents)) return changes def rename_in_module( occurrences_finder, new_name, resource=None, pymodule=None, replace_primary=False, region=None, reads=True, writes=True, ): """Returns the changed source or `None` if there is no changes""" if resource is not None: source_code = resource.read() else: source_code = pymodule.source_code change_collector = codeanalyze.ChangeCollector(source_code) for occurrence in occurrences_finder.find_occurrences(resource, pymodule): if replace_primary and occurrence.is_a_fixed_primary(): continue if replace_primary: start, end = occurrence.get_primary_range() else: start, end = occurrence.get_word_range() if (not reads and not occurrence.is_written()) or ( not writes and occurrence.is_written() ): continue if region is None or region[0] <= start < region[1]: change_collector.add_change(start, end, new_name) return change_collector.get_changed() def _is_local(pyname): module, lineno = pyname.get_definition_location() if lineno is None: return False scope = module.get_scope().get_inner_scope_for_line(lineno) if isinstance(pyname, pynames.DefinedName) and scope.get_kind() in ( "Function", "Class", ): scope = scope.parent return ( scope.get_kind() == "Function" and pyname in scope.get_names().values() and isinstance(pyname, pynames.AssignedName) ) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/rope/refactor/restructure.py0000664000175000017500000002626114512700666020223 0ustar00lieryanlieryanimport warnings from rope.base import ast, builtins, change, codeanalyze, libutils, taskhandle from rope.refactor import patchedast, similarfinder, sourceutils from rope.refactor.importutils import module_imports class Restructure: """A class to perform python restructurings A restructuring transforms pieces of code matching `pattern` to `goal`. In the `pattern` wildcards can appear. Wildcards match some piece of code based on their kind and arguments that are passed to them through `args`. `args` is a dictionary of wildcard names to wildcard arguments. If the argument is a tuple, the first item of the tuple is considered to be the name of the wildcard to use; otherwise the "default" wildcard is used. For getting the list arguments a wildcard supports, see the pydoc of the wildcard. (see `rope.refactor.wildcard.DefaultWildcard` for the default wildcard.) `wildcards` is the list of wildcard types that can appear in `pattern`. See `rope.refactor.wildcards`. If a wildcard does not specify its kind (by using a tuple in args), the wildcard named "default" is used. So there should be a wildcard with "default" name in `wildcards`. `imports` is the list of imports that changed modules should import. Note that rope handles duplicate imports and does not add the import if it already appears. Example #1:: pattern ${pyobject}.get_attribute(${name}) goal ${pyobject}[${name}] args pyobject: instance=rope.base.pyobjects.PyObject Example #2:: pattern ${name} in ${pyobject}.get_attributes() goal ${name} in {pyobject} args pyobject: instance=rope.base.pyobjects.PyObject Example #3:: pattern ${pycore}.create_module(${project}.root, ${name}) goal generate.create_module(${project}, ${name}) imports from rope.contrib import generate args project: type=rope.base.project.Project Example #4:: pattern ${pow}(${param1}, ${param2}) goal ${param1} ** ${param2} args pow: name=mod.pow, exact Example #5:: pattern ${inst}.longtask(${p1}, ${p2}) goal ${inst}.subtask1(${p1}) ${inst}.subtask2(${p2}) args inst: type=mod.A,unsure """ def __init__(self, project, pattern, goal, args=None, imports=None, wildcards=None): """Construct a restructuring See class pydoc for more info about the arguments. """ self.project = project self.pattern = pattern self.goal = goal self.args = args if self.args is None: self.args = {} self.imports = imports if self.imports is None: self.imports = [] self.wildcards = wildcards self.template = similarfinder.CodeTemplate(self.goal) def get_changes( self, checks=None, imports=None, resources=None, task_handle=taskhandle.DEFAULT_TASK_HANDLE, ): """Get the changes needed by this restructuring `resources` can be a list of `rope.base.resources.File` to apply the restructuring on. If `None`, the restructuring will be applied to all python files. `checks` argument has been deprecated. Use the `args` argument of the constructor. The usage of:: strchecks = {'obj1.type': 'mod.A', 'obj2': 'mod.B', 'obj3.object': 'mod.C'} checks = restructuring.make_checks(strchecks) can be replaced with:: args = {'obj1': 'type=mod.A', 'obj2': 'name=mod.B', 'obj3': 'object=mod.C'} where obj1, obj2 and obj3 are wildcard names that appear in restructuring pattern. """ if checks is not None: warnings.warn( "The use of checks parameter is deprecated; " "use the args parameter of the constructor instead.", DeprecationWarning, stacklevel=2, ) for name, value in checks.items(): self.args[name] = similarfinder._pydefined_to_str(value) if imports is not None: warnings.warn( "The use of imports parameter is deprecated; " "use imports parameter of the constructor, instead.", DeprecationWarning, stacklevel=2, ) self.imports = imports changes = change.ChangeSet(f"Restructuring <{self.pattern}> to <{self.goal}>") if resources is not None: files = [ resource for resource in resources if libutils.is_python_file(self.project, resource) ] else: files = self.project.get_python_files() job_set = task_handle.create_jobset("Collecting Changes", len(files)) for resource in files: job_set.started_job(resource.path) pymodule = self.project.get_pymodule(resource) finder = similarfinder.SimilarFinder(pymodule, wildcards=self.wildcards) matches = list(finder.get_matches(self.pattern, self.args)) computer = self._compute_changes(matches, pymodule) result = computer.get_changed() if result is not None: imported_source = self._add_imports(resource, result, self.imports) changes.add_change(change.ChangeContents(resource, imported_source)) job_set.finished_job() return changes def _compute_changes(self, matches, pymodule): return _ChangeComputer( pymodule.source_code, pymodule.get_ast(), pymodule.lines, self.template, matches, ) def _add_imports(self, resource, source, imports): if not imports: return source import_infos = self._get_import_infos(resource, imports) pymodule = libutils.get_string_module(self.project, source, resource) imports = module_imports.ModuleImports(self.project, pymodule) for import_info in import_infos: imports.add_import(import_info) return imports.get_changed_source() def _get_import_infos(self, resource, imports): pymodule = libutils.get_string_module( self.project, "\n".join(imports), resource ) imports = module_imports.ModuleImports(self.project, pymodule) return [imports.import_info for imports in imports.imports] def make_checks(self, string_checks): """Convert str to str dicts to str to PyObject dicts This function is here to ease writing a UI. """ checks = {} for key, value in string_checks.items(): is_pyname = not key.endswith(".object") and not key.endswith(".type") evaluated = self._evaluate(value, is_pyname=is_pyname) if evaluated is not None: checks[key] = evaluated return checks def _evaluate(self, code, is_pyname=True): attributes = code.split(".") pyname = None if attributes[0] in ("__builtin__", "__builtins__"): class _BuiltinsStub: def get_attribute(self, name): return builtins.builtins[name] pyobject = _BuiltinsStub() else: pyobject = self.project.get_module(attributes[0]) for attribute in attributes[1:]: pyname = pyobject[attribute] if pyname is None: return None pyobject = pyname.get_object() return pyname if is_pyname else pyobject def replace(code, pattern, goal): """used by other refactorings""" finder = similarfinder.RawSimilarFinder(code) matches = list(finder.get_matches(pattern)) ast = patchedast.get_patched_ast(code) lines = codeanalyze.SourceLinesAdapter(code) template = similarfinder.CodeTemplate(goal) computer = _ChangeComputer(code, ast, lines, template, matches) result = computer.get_changed() if result is None: return code return result class _ChangeComputer: def __init__(self, code, ast, lines, goal, matches): self.source = code self.goal = goal self.matches = matches self.ast = ast self.lines = lines self.matched_asts = {} self._nearest_roots = {} if self._is_expression(): for match in self.matches: self.matched_asts[match.ast] = match def get_changed(self): if self._is_expression(): result = self._get_node_text(self.ast) if result == self.source: return None return result else: collector = codeanalyze.ChangeCollector(self.source) last_end = -1 for match in self.matches: start, end = match.get_region() if start < last_end: if not self._is_expression(): continue last_end = end replacement = self._get_matched_text(match) collector.add_change(start, end, replacement) return collector.get_changed() def _is_expression(self): return self.matches and isinstance( self.matches[0], similarfinder.ExpressionMatch ) def _get_matched_text(self, match): mapping = {} for name in self.goal.get_names(): node = match.get_ast(name) if node is None: raise similarfinder.BadNameInCheckError("Unknown name <%s>" % name) force = self._is_expression() and match.ast == node mapping[name] = self._get_node_text(node, force) unindented = self.goal.substitute(mapping) return self._auto_indent(match.get_region()[0], unindented) def _get_node_text(self, node, force=False): if not force and node in self.matched_asts: return self._get_matched_text(self.matched_asts[node]) start, end = patchedast.node_region(node) main_text = self.source[start:end] collector = codeanalyze.ChangeCollector(main_text) for node in self._get_nearest_roots(node): sub_start, sub_end = patchedast.node_region(node) collector.add_change( sub_start - start, sub_end - start, self._get_node_text(node) ) result = collector.get_changed() if result is None: return main_text return result def _auto_indent(self, offset, text): lineno = self.lines.get_line_number(offset) indents = sourceutils.get_indents(self.lines, lineno) result = [] for index, line in enumerate(text.splitlines(True)): if index != 0 and line.strip(): result.append(" " * indents) result.append(line) return "".join(result) def _get_nearest_roots(self, node): if node not in self._nearest_roots: result = [] for child in ast.iter_child_nodes(node): if child in self.matched_asts: result.append(child) else: result.extend(self._get_nearest_roots(child)) self._nearest_roots[node] = result return self._nearest_roots[node] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/rope/refactor/similarfinder.py0000664000175000017500000003065714512700666020470 0ustar00lieryanlieryan"""This module can be used for finding similar code""" import re import rope.base.builtins # Use full qualification for clarity. import rope.refactor.wildcards # Use full qualification for clarity. from rope.base import ast, codeanalyze, exceptions, libutils from rope.refactor import patchedast from rope.refactor.patchedast import MismatchedTokenError class BadNameInCheckError(exceptions.RefactoringError): pass class SimilarFinder: """`SimilarFinder` can be used to find similar pieces of code See the notes in the `rope.refactor.restructure` module for more info. """ def __init__(self, pymodule, wildcards=None): """Construct a SimilarFinder""" self.source = pymodule.source_code try: self.raw_finder = RawSimilarFinder( pymodule.source_code, pymodule.get_ast(), self._does_match ) except MismatchedTokenError: print("in file %s" % pymodule.resource.path) raise self.pymodule = pymodule if wildcards is None: self.wildcards = {} for wildcard in [ rope.refactor.wildcards.DefaultWildcard(pymodule.pycore.project) ]: self.wildcards[wildcard.get_name()] = wildcard else: self.wildcards = wildcards def get_matches(self, code, args=None, start=0, end=None): if args is None: args = {} self.args = args if end is None: end = len(self.source) skip_region = None if "skip" in args.get("", {}): resource, region = args[""]["skip"] if resource == self.pymodule.get_resource(): skip_region = region return self.raw_finder.get_matches(code, start=start, end=end, skip=skip_region) def get_match_regions(self, *args, **kwds): for match in self.get_matches(*args, **kwds): yield match.get_region() def _does_match(self, node, name): arg = self.args.get(name, "") kind = "default" if isinstance(arg, (tuple, list)): kind = arg[0] arg = arg[1] suspect = rope.refactor.wildcards.Suspect(self.pymodule, node, name) return self.wildcards[kind].matches(suspect, arg) class RawSimilarFinder: """A class for finding similar expressions and statements""" def __init__(self, source, node=None, does_match=None): if node is None: try: node = ast.parse(source) except SyntaxError: # needed to parse expression containing := operator node = ast.parse("(" + source + ")") if does_match is None: self.does_match = self._simple_does_match else: self.does_match = does_match self._init_using_ast(node, source) def _simple_does_match(self, node, name): return isinstance(node, (ast.expr, ast.Name)) def _init_using_ast(self, node, source): self.source = source self._matched_asts = {} if not hasattr(node, "region"): patchedast.patch_ast(node, source) self.ast = node def get_matches(self, code, start=0, end=None, skip=None): """Search for `code` in source and return a list of `Match`-es `code` can contain wildcards. ``${name}`` matches normal names and ``${?name} can match any expression. You can use `Match.get_ast()` for getting the node that has matched a given pattern. """ if end is None: end = len(self.source) for match in self._get_matched_asts(code): match_start, match_end = match.get_region() if start <= match_start and match_end <= end: if skip is not None and (skip[0] < match_end and skip[1] > match_start): continue yield match def _get_matched_asts(self, code): if code not in self._matched_asts: wanted = self._create_pattern(code) matches = _ASTMatcher(self.ast, wanted, self.does_match).find_matches() self._matched_asts[code] = matches return self._matched_asts[code] def _create_pattern(self, expression): expression = self._replace_wildcards(expression) node = ast.parse(expression) # Getting Module.Stmt.nodes nodes = node.body if len(nodes) == 1 and isinstance(nodes[0], ast.Expr): # Getting Discard.expr wanted = nodes[0].value else: wanted = nodes return wanted def _replace_wildcards(self, expression): ropevar = _RopeVariable() template = CodeTemplate(expression) mapping = {name: ropevar.get_var(name) for name in template.get_names()} return template.substitute(mapping) class _ASTMatcher: def __init__(self, body, pattern, does_match): """Searches the given pattern in the body AST. body is an AST node and pattern can be either an AST node or a list of ASTs nodes """ self.body = body self.pattern = pattern self.matches = None self.ropevar = _RopeVariable() self.matches_callback = does_match def find_matches(self): if self.matches is None: self.matches = [] # _check_nodes always returns None, so # call_for_nodes traverses self.body's entire tree. ast.call_for_nodes(self.body, self._check_node) return self.matches def _check_node(self, node): if isinstance(self.pattern, list): self._check_statements(node) else: self._check_expression(node) def _check_expression(self, node): mapping = {} if self._match_nodes(self.pattern, node, mapping): self.matches.append(ExpressionMatch(node, mapping)) def _check_statements(self, node): for field, child in ast.iter_fields(node): if isinstance(child, (list, tuple)): self.__check_stmt_list(child) def __check_stmt_list(self, nodes): for index in range(len(nodes)): if len(nodes) - index >= len(self.pattern): current_stmts = nodes[index : index + len(self.pattern)] mapping = {} if self._match_stmts(current_stmts, mapping): self.matches.append(StatementMatch(current_stmts, mapping)) def _match_nodes(self, expected, node, mapping): if isinstance(expected, ast.Name): if self.ropevar.is_var(expected.id): return self._match_wildcard(expected, node, mapping) if not isinstance(expected, ast.AST): return expected == node if expected.__class__ != node.__class__: return False children1 = self._get_children(expected) children2 = self._get_children(node) if len(children1) != len(children2): return False for child1, child2 in zip(children1, children2): if isinstance(child1, ast.AST): if not self._match_nodes(child1, child2, mapping): return False elif isinstance(child1, (list, tuple)): if not isinstance(child2, (list, tuple)) or len(child1) != len(child2): return False for c1, c2 in zip(child1, child2): if not self._match_nodes(c1, c2, mapping): return False else: if type(child1) is not type(child2) or child1 != child2: return False return True def _get_children(self, node): """Return not `ast.expr_context` children of `node`""" return [ child for field, child in ast.iter_fields(node) if not isinstance(child, ast.expr_context) ] def _match_stmts(self, current_stmts, mapping): if len(current_stmts) != len(self.pattern): return False for stmt, expected in zip(current_stmts, self.pattern): if not self._match_nodes(expected, stmt, mapping): return False return True def _match_wildcard(self, node1, node2, mapping): name = self.ropevar.get_base(node1.id) if name not in mapping: if self.matches_callback(node2, name): mapping[name] = node2 return True return False else: return self._match_nodes(mapping[name], node2, {}) class Match: def __init__(self, mapping): self.mapping = mapping def get_region(self): """Returns match region""" def get_ast(self, name): """Return the ast node that has matched rope variables""" return self.mapping.get(name, None) class ExpressionMatch(Match): def __init__(self, ast, mapping): super().__init__(mapping) self.ast = ast def get_region(self): return self.ast.region class StatementMatch(Match): def __init__(self, ast_list, mapping): super().__init__(mapping) self.ast_list = ast_list def get_region(self): return self.ast_list[0].region[0], self.ast_list[-1].region[1] class CodeTemplate: def __init__(self, template): self.template = template self._find_names() def _find_names(self): self.names = {} for match in CodeTemplate._get_pattern().finditer(self.template): if "name" in match.groupdict() and match.group("name") is not None: start, end = match.span("name") name = self.template[start + 2 : end - 1] if name not in self.names: self.names[name] = [] self.names[name].append((start, end)) def get_names(self): return self.names.keys() def substitute(self, mapping): collector = codeanalyze.ChangeCollector(self.template) for name, occurrences in self.names.items(): for region in occurrences: collector.add_change(region[0], region[1], mapping[name]) result = collector.get_changed() if result is None: return self.template return result _match_pattern = None @classmethod def _get_pattern(cls): if cls._match_pattern is None: pattern = ( codeanalyze.get_comment_pattern() + "|" + codeanalyze.get_string_pattern() + "|" + r"(?P\$\{[^\s\$\}]*\})" ) cls._match_pattern = re.compile(pattern) return cls._match_pattern class _RopeVariable: """Transform and identify rope inserted wildcards""" _normal_prefix = "__rope__variable_normal_" _any_prefix = "__rope__variable_any_" def get_var(self, name): if name.startswith("?"): return self._get_any(name) else: return self._get_normal(name) def is_var(self, name): return self._is_normal(name) or self._is_var(name) def get_base(self, name): if self._is_normal(name): return name[len(self._normal_prefix) :] if self._is_var(name): return "?" + name[len(self._any_prefix) :] def _get_normal(self, name): return self._normal_prefix + name def _get_any(self, name): return self._any_prefix + name[1:] def _is_normal(self, name): return name.startswith(self._normal_prefix) def _is_var(self, name): return name.startswith(self._any_prefix) def make_pattern(code, variables): variables = set(variables) collector = codeanalyze.ChangeCollector(code) def does_match(node, name): return isinstance(node, ast.Name) and node.id == name finder = RawSimilarFinder(code, does_match=does_match) for variable in variables: for match in finder.get_matches("${%s}" % variable): start, end = match.get_region() collector.add_change(start, end, "${%s}" % variable) result = collector.get_changed() return result if result is not None else code def _pydefined_to_str(pydefined): address = [] if isinstance( pydefined, (rope.base.builtins.BuiltinClass, rope.base.builtins.BuiltinFunction) ): return f"__builtins__.{pydefined.get_name()}" while pydefined.parent is not None: address.insert(0, pydefined.get_name()) pydefined = pydefined.parent module_name = libutils.modname(pydefined.resource) return ".".join(module_name.split(".") + address) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/rope/refactor/sourceutils.py0000664000175000017500000000567114512700666020217 0ustar00lieryanlieryanfrom rope.base import codeanalyze def get_indents(lines, lineno): return codeanalyze.count_line_indents(lines.get_line(lineno)) def find_minimum_indents(source_code): result = 80 lines = source_code.split("\n") for line in lines: if line.strip() == "": continue result = min(result, codeanalyze.count_line_indents(line)) return result def indent_lines(source_code, amount): if amount == 0: return source_code lines = source_code.splitlines(True) result = [] for line in lines: if line.strip() == "": result.append("\n") continue if amount < 0: indents = codeanalyze.count_line_indents(line) result.append(max(0, indents + amount) * " " + line.lstrip()) else: result.append(" " * amount + line) return "".join(result) def fix_indentation(code: str, new_indents: int) -> str: """Change the indentation of `code` to `new_indents`""" min_indents = find_minimum_indents(code) return indent_lines(code, new_indents - min_indents) def add_methods(pymodule, class_scope, methods_sources): source_code = pymodule.source_code lines = pymodule.lines insertion_line = class_scope.get_end() if class_scope.get_scopes(): insertion_line = class_scope.get_scopes()[-1].get_end() insertion_offset = lines.get_line_end(insertion_line) methods = "\n\n" + "\n\n".join(methods_sources) indented_methods = fix_indentation( methods, get_indents(lines, class_scope.get_start()) + get_indent(pymodule.pycore.project), ) result = [] result.append(source_code[:insertion_offset]) result.append(indented_methods) result.append(source_code[insertion_offset:]) return "".join(result) def get_body(pyfunction): """Return unindented function body""" # FIXME scope = pyfunction.get_scope() pymodule = pyfunction.get_module() start, end = get_body_region(pyfunction) return fix_indentation(pymodule.source_code[start:end], 0) def get_body_region(defined): """Return the start and end offsets of function body""" scope = defined.get_scope() pymodule = defined.get_module() lines = pymodule.lines node = defined.get_ast() start_line = node.lineno if defined.get_doc() is None: start_line = node.body[0].lineno elif len(node.body) > 1: start_line = node.body[1].lineno start = lines.get_line_start(start_line) scope_start = pymodule.logical_lines.logical_line_in(scope.start) if scope_start[1] >= start_line: # a one-liner! # XXX: what if colon appears in a string start = pymodule.source_code.index(":", start) + 1 while pymodule.source_code[start].isspace(): start += 1 end = min(lines.get_line_end(scope.end) + 1, len(pymodule.source_code)) return start, end def get_indent(project): return project.prefs.get("indent_size", 4) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/rope/refactor/suites.py0000664000175000017500000001204014512700666017136 0ustar00lieryanlieryanfrom itertools import chain from rope.base import ast def find_visible(node, lines): """Return the line which is visible from all `lines`""" root = ast_suite_tree(node) return find_visible_for_suite(root, lines) def find_visible_for_suite(root, lines): if len(lines) == 1: return lines[0] line1 = lines[0] line2 = find_visible_for_suite(root, lines[1:]) suite1 = root.find_suite(line1) suite2 = root.find_suite(line2) def valid(suite): return suite is not None and not suite.ignored if valid(suite1) and not valid(suite2): return line1 if not valid(suite1) and valid(suite2): return line2 if not valid(suite1) and not valid(suite2): return None while suite1 != suite2 and suite1.parent != suite2.parent: if suite1._get_level() < suite2._get_level(): line2 = suite2.get_start() suite2 = suite2.parent elif suite1._get_level() > suite2._get_level(): line1 = suite1.get_start() suite1 = suite1.parent else: line1 = suite1.get_start() line2 = suite2.get_start() suite1 = suite1.parent suite2 = suite2.parent if suite1 == suite2: return min(line1, line2) return min(suite1.get_start(), suite2.get_start()) def ast_suite_tree(node): if hasattr(node, "lineno"): lineno = node.lineno else: lineno = 1 return Suite(node.body, lineno) class Suite: def __init__(self, child_nodes, lineno, parent=None, ignored=False): self.parent = parent self.lineno = lineno self.child_nodes = child_nodes self._children = None self.ignored = ignored def get_start(self): if self.parent is None: if self.child_nodes: return self.local_start() else: return 1 return self.lineno def get_children(self): if self._children is None: walker = _SuiteWalker(self) for child in self.child_nodes: walker.visit(child) self._children = walker.suites return self._children def local_start(self): return self.child_nodes[0].lineno def local_end(self): end = self.child_nodes[-1].lineno if self.get_children(): end = max(end, self.get_children()[-1].local_end()) return end def find_suite(self, line): if line is None: return None for child in self.get_children(): if child.local_start() <= line <= child.local_end(): return child.find_suite(line) return self def _get_level(self): if self.parent is None: return 0 return self.parent._get_level() + 1 class _SuiteWalker(ast.RopeNodeVisitor): def __init__(self, suite): self.suite = suite self.suites = [] def _If(self, node): self._add_if_like_node(node) def _For(self, node): self._add_if_like_node(node) def _While(self, node): self._add_if_like_node(node) def _With(self, node): self.suites.append(Suite(node.body, node.lineno, self.suite)) def _AsyncWith(self, node): self.suites.append(Suite(node.body, node.lineno, self.suite)) def _Match(self, node): case_bodies = list( chain.from_iterable([[case.pattern] + case.body for case in node.cases]) ) self.suites.append(Suite(case_bodies, node.lineno, self.suite)) def _TryFinally(self, node): proceed_to_except_handler = False if len(node.finalbody) == 1: try: proceed_to_except_handler = isinstance( node.handlers[0], ast.ExceptHandler ) except IndexError: pass if proceed_to_except_handler: self._TryExcept(node) else: self.suites.append(Suite(node.body, node.lineno, self.suite)) self.suites.append(Suite(node.finalbody, node.lineno, self.suite)) def _Try(self, node): if len(node.finalbody) == 1: self._TryFinally(node) else: self._TryExcept(node) def _TryExcept(self, node): self.suites.append(Suite(node.body, node.lineno, self.suite)) for handler in node.handlers: self.suites.append(Suite(handler.body, node.lineno, self.suite)) if node.orelse: self.suites.append(Suite(node.orelse, node.lineno, self.suite)) def _add_if_like_node(self, node): self.suites.append(Suite(node.body, node.lineno, self.suite)) if node.orelse: self.suites.append(Suite(node.orelse, node.lineno, self.suite)) def _FunctionDef(self, node): self.suites.append(Suite(node.body, node.lineno, self.suite, ignored=True)) def _AsyncFunctionDef(self, node): self.suites.append(Suite(node.body, node.lineno, self.suite, ignored=True)) def _ClassDef(self, node): self.suites.append(Suite(node.body, node.lineno, self.suite, ignored=True)) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/rope/refactor/topackage.py0000664000175000017500000000231214512700666017561 0ustar00lieryanlieryanimport rope.refactor.importutils from rope.base.change import ChangeContents, ChangeSet, CreateFolder, MoveResource class ModuleToPackage: def __init__(self, project, resource): self.project = project self.resource = resource def get_changes(self): changes = ChangeSet("Transform <%s> module to package" % self.resource.path) new_content = self._transform_relatives_to_absolute(self.resource) if new_content is not None: changes.add_change(ChangeContents(self.resource, new_content)) parent = self.resource.parent name = self.resource.name[:-3] changes.add_change(CreateFolder(parent, name)) parent_path = parent.path + "/" if not parent.path: parent_path = "" new_path = parent_path + "%s/__init__.py" % name if self.resource.project == self.project: changes.add_change(MoveResource(self.resource, new_path)) return changes def _transform_relatives_to_absolute(self, resource): pymodule = self.project.get_pymodule(resource) import_tools = rope.refactor.importutils.ImportTools(self.project) return import_tools.relatives_to_absolutes(pymodule) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/rope/refactor/usefunction.py0000664000175000017500000001477314512700666020203 0ustar00lieryanlieryanfrom rope.base import ( ast, change, evaluate, exceptions, libutils, pynames, pyobjects, taskhandle, ) from rope.refactor import restructure, similarfinder, sourceutils class UseFunction: """Try to use a function wherever possible""" def __init__(self, project, resource, offset): self.project = project self.offset = offset this_pymodule = project.get_pymodule(resource) pyname = evaluate.eval_location(this_pymodule, offset) if pyname is None: raise exceptions.RefactoringError("Unresolvable name selected") self.pyfunction = pyname.get_object() if not isinstance(self.pyfunction, pyobjects.PyFunction) or not isinstance( self.pyfunction.parent, pyobjects.PyModule ): raise exceptions.RefactoringError( "Use function works for global functions, only." ) self.resource = self.pyfunction.get_module().get_resource() self._check_returns() def _check_returns(self): node = self.pyfunction.get_ast() if _yield_count(node): raise exceptions.RefactoringError( "Use function should not be used on generatorS." ) returns = _return_count(node) if returns > 1: raise exceptions.RefactoringError( "usefunction: Function has more than one return statement." ) if returns == 1 and not _returns_last(node): raise exceptions.RefactoringError( "usefunction: return should be the last statement." ) def get_changes(self, resources=None, task_handle=taskhandle.DEFAULT_TASK_HANDLE): if resources is None: resources = self.project.get_python_files() changes = change.ChangeSet("Using function <%s>" % self.pyfunction.get_name()) if self.resource in resources: newresources = list(resources) newresources.remove(self.resource) for c in self._restructure(newresources, task_handle).changes: changes.add_change(c) if self.resource in resources: for c in self._restructure( [self.resource], task_handle, others=False ).changes: changes.add_change(c) return changes def get_function_name(self): return self.pyfunction.get_name() def _restructure(self, resources, task_handle, others=True): pattern = self._make_pattern() goal = self._make_goal(import_=others) imports = None if others: imports = ["import %s" % self._module_name()] body_region = sourceutils.get_body_region(self.pyfunction) args_value = {"skip": (self.resource, body_region)} args = {"": args_value} restructuring = restructure.Restructure( self.project, pattern, goal, args=args, imports=imports ) return restructuring.get_changes(resources=resources, task_handle=task_handle) def _find_temps(self): return find_temps(self.project, self._get_body()) def _module_name(self): return libutils.modname(self.resource) def _make_pattern(self): params = self.pyfunction.get_param_names() body = self._get_body() body = restructure.replace(body, "return", "pass") wildcards = list(params) wildcards.extend(self._find_temps()) if self._does_return(): if self._is_expression(): replacement = "${%s}" % self._rope_returned else: replacement = "{} = ${{{}}}".format( self._rope_result, self._rope_returned ) body = restructure.replace( body, "return ${%s}" % self._rope_returned, replacement ) wildcards.append(self._rope_result) return similarfinder.make_pattern(body, wildcards) def _get_body(self): return sourceutils.get_body(self.pyfunction) def _make_goal(self, import_=False): params = self.pyfunction.get_param_names() function_name = self.pyfunction.get_name() if import_: function_name = self._module_name() + "." + function_name goal = "{}({})".format( function_name, ", ".join(("${%s}" % p) for p in params), ) if self._does_return() and not self._is_expression(): goal = "${{{}}} = {}".format( self._rope_result, goal, ) return goal def _does_return(self): body = self._get_body() removed_return = restructure.replace(body, "return ${result}", "") return removed_return != body def _is_expression(self): return len(self.pyfunction.get_ast().body) == 1 _rope_result = "_rope__result" _rope_returned = "_rope__returned" def find_temps(project, code): code = "def f():\n" + sourceutils.indent_lines(code, 4) pymodule = libutils.get_string_module(project, code) function_scope = pymodule.get_scope().get_scopes()[0] return [ name for name, pyname in function_scope.get_names().items() if isinstance(pyname, pynames.AssignedName) ] def _returns_last(node): return node.body and isinstance(node.body[-1], ast.Return) def _namedexpr_last(node): if not hasattr(ast, "NamedExpr"): # python<3.8 return False return ( bool(node.body) and len(node.body) == 1 and isinstance(node.body[-1].value, ast.NamedExpr) ) def _yield_count(node): visitor = _ReturnOrYieldFinder() visitor.start_walking(node) return visitor.yields def _return_count(node): visitor = _ReturnOrYieldFinder() visitor.start_walking(node) return visitor.returns def _named_expr_count(node): visitor = _ReturnOrYieldFinder() visitor.start_walking(node) return visitor.named_expression class _ReturnOrYieldFinder(ast.RopeNodeVisitor): def __init__(self): self.returns = 0 self.named_expression = 0 self.yields = 0 def _Return(self, node): self.returns += 1 def _NamedExpr(self, node): self.named_expression += 1 def _Yield(self, node): self.yields += 1 def _FunctionDef(self, node): pass def _ClassDef(self, node): pass def start_walking(self, node): nodes = [node] if isinstance(node, ast.FunctionDef): nodes = list(ast.iter_child_nodes(node)) for child in nodes: self.visit(child) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/rope/refactor/wildcards.py0000664000175000017500000001315414512700666017605 0ustar00lieryanlieryanfrom rope.base import ast, builtins, evaluate, pyobjects from rope.refactor import occurrences, patchedast class Wildcard: def get_name(self): """Return the name of this wildcard""" def matches(self, suspect, arg): """Return `True` if `suspect` matches this wildcard""" class Suspect: def __init__(self, pymodule, node, name): self.name = name self.pymodule = pymodule self.node = node class DefaultWildcard: """The default restructuring wildcard The argument passed to this wildcard is in the ``key1=value1,key2=value2,...`` format. Possible keys are: * name - for checking the reference * type - for checking the type * object - for checking the object * instance - for checking types but similar to builtin isinstance * exact - matching only occurrences with the same name as the wildcard * unsure - matching unsure occurrences """ def __init__(self, project): self.project = project def get_name(self): return "default" def matches(self, suspect, arg=""): args = parse_arg(arg) if not self._check_exact(args, suspect): return False if not self._check_object(args, suspect): return False return True def _check_object(self, args, suspect): kind = None expected = None unsure = args.get("unsure", False) for check in ["name", "object", "type", "instance"]: if check in args: kind = check expected = args[check] if expected is not None: checker = _CheckObject(self.project, expected, kind, unsure=unsure) return checker(suspect.pymodule, suspect.node) return True def _check_exact(self, args, suspect): node = suspect.node if args.get("exact"): if not isinstance(node, ast.Name) or not node.id == suspect.name: return False else: if not isinstance(node, ast.expr): return False return True def parse_arg(arg): if isinstance(arg, dict): return arg result = {} tokens = arg.split(",") for token in tokens: if "=" in token: parts = token.split("=", 1) result[parts[0].strip()] = parts[1].strip() else: result[token.strip()] = True return result class _CheckObject: def __init__(self, project, expected, kind="object", unsure=False): self.project = project self.kind = kind self.unsure = unsure self.expected = self._evaluate(expected) def __call__(self, pymodule, node): pyname = self._evaluate_node(pymodule, node) if pyname is None or self.expected is None: return self.unsure if self._unsure_pyname(pyname, unbound=self.kind == "name"): return True if self.kind == "name": return self._same_pyname(self.expected, pyname) else: pyobject = pyname.get_object() if self.kind == "object": objects = [pyobject] if self.kind == "type": objects = [pyobject.get_type()] if self.kind == "instance": objects = [pyobject] objects.extend(self._get_super_classes(pyobject)) objects.extend(self._get_super_classes(pyobject.get_type())) for pyobject in objects: if self._same_pyobject(self.expected.get_object(), pyobject): return True return False def _get_super_classes(self, pyobject): result = [] if isinstance(pyobject, pyobjects.AbstractClass): for superclass in pyobject.get_superclasses(): result.append(superclass) result.extend(self._get_super_classes(superclass)) return result def _same_pyobject(self, expected, pyobject): return expected == pyobject def _same_pyname(self, expected, pyname): return occurrences.same_pyname(expected, pyname) def _unsure_pyname(self, pyname, unbound=True): return self.unsure and occurrences.unsure_pyname(pyname, unbound) def _split_name(self, name): parts = name.split(".") expression, kind = parts[0], parts[-1] if len(parts) == 1: kind = "name" return expression, kind def _evaluate_node(self, pymodule, node): scope = pymodule.get_scope().get_inner_scope_for_line(node.lineno) expression = node if isinstance(expression, ast.Name) and isinstance(expression.ctx, ast.Store): start, end = patchedast.node_region(expression) text = pymodule.source_code[start:end] return evaluate.eval_str(scope, text) else: return evaluate.eval_node(scope, expression) def _evaluate(self, code): attributes = code.split(".") pyname = None if attributes[0] in ("__builtin__", "__builtins__"): class _BuiltinsStub: def get_attribute(self, name): return builtins.builtins[name] def __getitem__(self, name): return builtins.builtins[name] def __contains__(self, name): return name in builtins.builtins pyobject = _BuiltinsStub() else: pyobject = self.project.get_module(attributes[0]) for attribute in attributes[1:]: pyname = pyobject[attribute] if pyname is None: return None pyobject = pyname.get_object() return pyname ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1705553002.7139843 rope-1.12.0/rope.egg-info/0000775000175000017500000000000014552126153015135 5ustar00lieryanlieryan././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1705553002.0 rope-1.12.0/rope.egg-info/PKG-INFO0000644000175000017500000001431314552126152016231 0ustar00lieryanlieryanMetadata-Version: 2.1 Name: rope Version: 1.12.0 Summary: a python refactoring library... Author-email: Ali Gholami Rudi Maintainer-email: Lie Ryan License: LGPL-3.0-or-later Project-URL: Source, https://github.com/python-rope/rope Project-URL: Documentation, https://rope.readthedocs.io/ Classifier: Development Status :: 4 - Beta Classifier: Operating System :: OS Independent Classifier: Environment :: X11 Applications Classifier: Environment :: Win32 (MS Windows) Classifier: Environment :: MacOS X Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+) Classifier: Natural Language :: English Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3.12 Classifier: Topic :: Software Development Requires-Python: >=3.8 Description-Content-Type: text/x-rst License-File: COPYING Requires-Dist: pytoolconfig[global]>=1.2.2 Provides-Extra: doc Requires-Dist: pytoolconfig[doc]; extra == "doc" Requires-Dist: sphinx>=4.5.0; extra == "doc" Requires-Dist: sphinx-autodoc-typehints>=1.18.1; extra == "doc" Requires-Dist: sphinx-rtd-theme>=1.0.0; extra == "doc" Provides-Extra: dev Requires-Dist: pytest>=7.0.1; extra == "dev" Requires-Dist: pytest-timeout>=2.1.0; extra == "dev" Requires-Dist: build>=0.7.0; extra == "dev" Requires-Dist: pre-commit>=2.20.0; extra == "dev" Provides-Extra: release Requires-Dist: toml>=0.10.2; extra == "release" Requires-Dist: twine>=4.0.2; extra == "release" Requires-Dist: pip-tools>=6.12.1; extra == "release" .. _GitHub python-rope / rope: https://github.com/python-rope/rope ========================================================================= rope -- the world's most advanced open source Python refactoring library ========================================================================= |Build status badge| |Latest version badge| |Download count badge| |ReadTheDocs status badge| .. |Build status badge| image:: https://github.com/python-rope/rope/actions/workflows/main.yml/badge.svg :target: https://github.com/python-rope/rope/actions/workflows/main.yml :alt: Build Status .. |Latest version badge| image:: https://badge.fury.io/py/rope.svg :target: https://badge.fury.io/py/rope :alt: Latest version .. |Download count badge| image:: https://img.shields.io/pypi/dm/rope.svg :alt: Download count .. |ReadTheDocs status badge| image:: https://readthedocs.org/projects/rope/badge/?version=latest :target: https://rope.readthedocs.io/en/latest/?badge=latest :alt: Documentation Status 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 up to Python 3.10 is supported. Please file bugs and contribute patches if you encounter gaps. Since version 1.0.0, rope no longer support running on Python 2. If you need Python 2 support, then check out the `python2` branch or the 0.x.x releases. Getting Started =============== * `Documentation `_ * `How to use Rope in my IDE or Text editor? `_ * `Configuration `_ * `List of features `_ * `Overview of some of rope's features `_ * `Using as a library `_ * `Contributing `_ Why use Rope? ============= - Rope aims to provide powerful and safe refactoring - Rope is light on dependency, Rope only depends on Python itself - Unlike PyRight or PyLance, Rope does not depend on Node.js - Unlike PyLance or PyCharm, Rope is open source. - Unlike PyRight and PyLance, Rope is written in Python itself, so if you experience problems, you would be able to debug and hack it yourself in a language that you are already familiar with - In comparison to Jedi, Rope is focused on refactoring. While Jedi provides some basic refactoring capabilities, Rope supports many more advanced refactoring operations and options that Jedi does not. Bug Reports =========== Send your bug reports and feature requests at `python-rope's issue tracker`_ in GitHub. .. _`python-rope's issue tracker`: https://github.com/python-rope/rope/issues Maintainers =========== Current active maintainer of Rope is Lie Ryan (`@lieryan`_). Special Thanks ============== Many thanks the following people: - Ali Gholami Rudi (`@aligrudi`_) for initially creating the initial Rope project and most of Rope's code - Matej Cepl (`@mcepl`_) as former long-time Rope maintainer - Nick Smith (`@soupytwist`_) as former Rope maintainer - `all of our current and former contributors`_ - `all authors of editor integrations`_ - all maintainers of distro/package managers .. _`@aligrudi`: https://github.com/aligrudi .. _`@soupytwist`: https://github.com/soupytwist .. _`@lieryan`: https://github.com/lieryan .. _`@mcepl`: https://github.com/mcepl .. _`all of our current and former contributors`: https://github.com/python-rope/rope/blob/master/CONTRIBUTORS.md .. _`all authors of editor integrations`: https://github.com/python-rope/rope/wiki/How-to-use-Rope-in-my-IDE-or-Text-editor%3F Packaging Status ================ .. image:: https://repology.org/badge/vertical-allrepos/python:rope.svg?exclude_unsupported=1 :target: https://repology.org/project/python:rope/versions :alt: Packaging status .. image:: https://repology.org/badge/vertical-allrepos/rope.svg?exclude_unsupported=1 :target: https://repology.org/project/rope/versions :alt: Packaging status License ======= This program is under the terms of LGPL v3+ (GNU Lesser General Public License). Have a look at `COPYING`_ for more information. .. _`COPYING`: COPYING ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1705553002.0 rope-1.12.0/rope.egg-info/SOURCES.txt0000664000175000017500000001111014552126152017012 0ustar00lieryanlieryanCHANGELOG.md COPYING MANIFEST.in README.rst pyproject.toml setup.cfg setup.py docs/configuration.rst docs/contributing.rst docs/index.rst docs/library.rst docs/overview.rst docs/release-process.rst docs/rope.rst docs/dev/issues.rst rope/__init__.py rope.egg-info/PKG-INFO rope.egg-info/SOURCES.txt rope.egg-info/dependency_links.txt rope.egg-info/requires.txt rope.egg-info/top_level.txt rope/base/__init__.py rope/base/arguments.py rope/base/ast.py rope/base/builtins.py rope/base/change.py rope/base/codeanalyze.py rope/base/evaluate.py rope/base/exceptions.py rope/base/fscommands.py rope/base/history.py rope/base/libutils.py rope/base/nameanalyze.py rope/base/prefs.py rope/base/project.py rope/base/pycore.py rope/base/pynames.py rope/base/pynamesdef.py rope/base/pyobjects.py rope/base/pyobjectsdef.py rope/base/pyscopes.py rope/base/resourceobserver.py rope/base/resources.py rope/base/serializer.py rope/base/simplify.py rope/base/stdmods.py rope/base/taskhandle.py rope/base/versioning.py rope/base/worder.py rope/base/oi/__init__.py rope/base/oi/doa.py rope/base/oi/memorydb.py rope/base/oi/objectdb.py rope/base/oi/objectinfo.py rope/base/oi/runmod.py rope/base/oi/soa.py rope/base/oi/soi.py rope/base/oi/transform.py rope/base/oi/type_hinting/__init__.py rope/base/oi/type_hinting/evaluate.py rope/base/oi/type_hinting/factory.py rope/base/oi/type_hinting/interfaces.py rope/base/oi/type_hinting/utils.py rope/base/oi/type_hinting/providers/__init__.py rope/base/oi/type_hinting/providers/composite.py rope/base/oi/type_hinting/providers/docstrings.py rope/base/oi/type_hinting/providers/inheritance.py rope/base/oi/type_hinting/providers/interfaces.py rope/base/oi/type_hinting/providers/numpydocstrings.py rope/base/oi/type_hinting/providers/pep0484_type_comments.py rope/base/oi/type_hinting/resolvers/__init__.py rope/base/oi/type_hinting/resolvers/composite.py rope/base/oi/type_hinting/resolvers/interfaces.py rope/base/oi/type_hinting/resolvers/types.py rope/base/utils/__init__.py rope/base/utils/datastructures.py rope/contrib/__init__.py rope/contrib/changestack.py rope/contrib/codeassist.py rope/contrib/finderrors.py rope/contrib/findit.py rope/contrib/fixmodnames.py rope/contrib/fixsyntax.py rope/contrib/generate.py rope/contrib/autoimport/__init__.py rope/contrib/autoimport/defs.py rope/contrib/autoimport/models.py rope/contrib/autoimport/parse.py rope/contrib/autoimport/pickle.py rope/contrib/autoimport/sqlite.py rope/contrib/autoimport/utils.py rope/refactor/__init__.py rope/refactor/change_signature.py rope/refactor/encapsulate_field.py rope/refactor/extract.py rope/refactor/functionutils.py rope/refactor/inline.py rope/refactor/introduce_factory.py rope/refactor/introduce_parameter.py rope/refactor/localtofield.py rope/refactor/method_object.py rope/refactor/move.py rope/refactor/multiproject.py rope/refactor/occurrences.py rope/refactor/patchedast.py rope/refactor/rename.py rope/refactor/restructure.py rope/refactor/similarfinder.py rope/refactor/sourceutils.py rope/refactor/suites.py rope/refactor/topackage.py rope/refactor/usefunction.py rope/refactor/wildcards.py rope/refactor/importutils/__init__.py rope/refactor/importutils/actions.py rope/refactor/importutils/importinfo.py rope/refactor/importutils/module_imports.py ropetest/__init__.py ropetest/advanced_oi_test.py ropetest/builtinstest.py ropetest/codeanalyzetest.py ropetest/conftest.py ropetest/doatest.py ropetest/historytest.py ropetest/objectdbtest.py ropetest/objectinfertest.py ropetest/projecttest.py ropetest/pycoretest.py ropetest/pyscopestest.py ropetest/reprtest.py ropetest/runmodtest.py ropetest/serializer_test.py ropetest/simplifytest.py ropetest/testutils.py ropetest/type_hinting_test.py ropetest/versioningtest.py ropetest/contrib/__init__.py ropetest/contrib/autoimporttest.py ropetest/contrib/changestacktest.py ropetest/contrib/codeassisttest.py ropetest/contrib/finderrorstest.py ropetest/contrib/findittest.py ropetest/contrib/fixmodnamestest.py ropetest/contrib/generatetest.py ropetest/contrib/autoimport/autoimporttest.py ropetest/contrib/autoimport/conftest.py ropetest/contrib/autoimport/modeltest.py ropetest/contrib/autoimport/parsetest.py ropetest/contrib/autoimport/utilstest.py ropetest/refactor/__init__.py ropetest/refactor/change_signature_test.py ropetest/refactor/extracttest.py ropetest/refactor/importutilstest.py ropetest/refactor/inlinetest.py ropetest/refactor/movetest.py ropetest/refactor/multiprojecttest.py ropetest/refactor/patchedasttest.py ropetest/refactor/renametest.py ropetest/refactor/restructuretest.py ropetest/refactor/similarfindertest.py ropetest/refactor/suitestest.py ropetest/refactor/usefunctiontest.py././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1705553002.0 rope-1.12.0/rope.egg-info/dependency_links.txt0000664000175000017500000000000114552126152021202 0ustar00lieryanlieryan ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1705553002.0 rope-1.12.0/rope.egg-info/requires.txt0000664000175000017500000000037614552126152017542 0ustar00lieryanlieryanpytoolconfig[global]>=1.2.2 [dev] pytest>=7.0.1 pytest-timeout>=2.1.0 build>=0.7.0 pre-commit>=2.20.0 [doc] pytoolconfig[doc] sphinx>=4.5.0 sphinx-autodoc-typehints>=1.18.1 sphinx-rtd-theme>=1.0.0 [release] toml>=0.10.2 twine>=4.0.2 pip-tools>=6.12.1 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1705553002.0 rope-1.12.0/rope.egg-info/top_level.txt0000664000175000017500000000000514552126152017661 0ustar00lieryanlieryanrope ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1705553002.7059839 rope-1.12.0/ropetest/0000775000175000017500000000000014552126153014343 5ustar00lieryanlieryan././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/ropetest/__init__.py0000664000175000017500000000000014512700666016445 0ustar00lieryanlieryan././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/ropetest/advanced_oi_test.py0000664000175000017500000011116514512700666020220 0ustar00lieryanlieryanimport unittest from textwrap import dedent import rope.base.libutils import rope.base.oi from rope.base.builtins import Str from ropetest import testutils class DynamicOITest(unittest.TestCase): def setUp(self): super().setUp() self.project = testutils.sample_project(validate_objectdb=True) self.pycore = self.project.pycore def tearDown(self): testutils.remove_project(self.project) super().tearDown() def test_simple_dti(self): mod = testutils.create_module(self.project, "mod") code = dedent("""\ def a_func(arg): return eval("arg") a_var = a_func(a_func) """) mod.write(code) self.pycore.run_module(mod).wait_process() pymod = self.project.get_pymodule(mod) self.assertEqual(pymod["a_func"].get_object(), pymod["a_var"].get_object()) def test_module_dti(self): mod1 = testutils.create_module(self.project, "mod1") mod2 = testutils.create_module(self.project, "mod2") code = dedent("""\ import mod1 def a_func(arg): return eval("arg") a_var = a_func(mod1) """) mod2.write(code) self.pycore.run_module(mod2).wait_process() pymod2 = self.project.get_pymodule(mod2) self.assertEqual(self.project.get_pymodule(mod1), pymod2["a_var"].get_object()) def test_class_from_another_module_dti(self): mod1 = testutils.create_module(self.project, "mod1") mod2 = testutils.create_module(self.project, "mod2") code1 = dedent("""\ class AClass(object): pass """) code2 = dedent("""\ from mod1 import AClass def a_func(arg): return eval("arg") a_var = a_func(AClass) """) mod1.write(code1) mod2.write(code2) self.pycore.run_module(mod2).wait_process() # pymod1 = self.project.get_pymodule(mod1) pymod2 = self.project.get_pymodule(mod2) self.assertEqual(pymod2["AClass"].get_object(), pymod2["a_var"].get_object()) def test_class_dti(self): mod = testutils.create_module(self.project, "mod") code = dedent("""\ class AClass(object): pass def a_func(arg): return eval("arg") a_var = a_func(AClass) """) mod.write(code) self.pycore.run_module(mod).wait_process() pymod = self.project.get_pymodule(mod) self.assertEqual(pymod["AClass"].get_object(), pymod["a_var"].get_object()) def test_instance_dti(self): mod = testutils.create_module(self.project, "mod") code = dedent("""\ class AClass(object): pass def a_func(arg): return eval("arg()") a_var = a_func(AClass) """) mod.write(code) self.pycore.run_module(mod).wait_process() pymod = self.project.get_pymodule(mod) self.assertEqual( pymod["AClass"].get_object(), pymod["a_var"].get_object().get_type() ) def test_method_dti(self): mod = testutils.create_module(self.project, "mod") code = dedent("""\ class AClass(object): def a_method(self, arg): return eval("arg()") an_instance = AClass() a_var = an_instance.a_method(AClass) """) mod.write(code) self.pycore.run_module(mod).wait_process() pymod = self.project.get_pymodule(mod) self.assertEqual( pymod["AClass"].get_object(), pymod["a_var"].get_object().get_type() ) def test_function_argument_dti(self): mod = testutils.create_module(self.project, "mod") code = dedent("""\ def a_func(arg): pass a_func(a_func) """) mod.write(code) self.pycore.run_module(mod).wait_process() pyscope = self.project.get_pymodule(mod).get_scope() self.assertEqual( pyscope["a_func"].get_object(), pyscope.get_scopes()[0]["arg"].get_object() ) def test_classes_with_the_same_name(self): mod = testutils.create_module(self.project, "mod") code = dedent("""\ def a_func(arg): class AClass(object): pass return eval("arg") class AClass(object): pass a_var = a_func(AClass) """) mod.write(code) self.pycore.run_module(mod).wait_process() pymod = self.project.get_pymodule(mod) self.assertEqual(pymod["AClass"].get_object(), pymod["a_var"].get_object()) def test_nested_classes(self): mod = testutils.create_module(self.project, "mod") code = dedent("""\ def a_func(): class AClass(object): pass return AClass def another_func(arg): return eval("arg") a_var = another_func(a_func()) """) mod.write(code) self.pycore.run_module(mod).wait_process() pyscope = self.project.get_pymodule(mod).get_scope() self.assertEqual( pyscope.get_scopes()[0]["AClass"].get_object(), pyscope["a_var"].get_object(), ) def test_function_argument_dti2(self): mod = testutils.create_module(self.project, "mod") code = dedent("""\ def a_func(arg, a_builtin_type): pass a_func(a_func, []) """) mod.write(code) self.pycore.run_module(mod).wait_process() pyscope = self.project.get_pymodule(mod).get_scope() self.assertEqual( pyscope["a_func"].get_object(), pyscope.get_scopes()[0]["arg"].get_object() ) def test_dti_and_concluded_data_invalidation(self): mod = testutils.create_module(self.project, "mod") code = dedent("""\ def a_func(arg): return eval("arg") a_var = a_func(a_func) """) mod.write(code) pymod = self.project.get_pymodule(mod) pymod["a_var"].get_object() self.pycore.run_module(mod).wait_process() self.assertEqual(pymod["a_func"].get_object(), pymod["a_var"].get_object()) def test_list_objects_and_dynamicoi(self): mod = testutils.create_module(self.project, "mod") code = dedent("""\ class C(object): pass def a_func(arg): return eval("arg") a_var = a_func([C()])[0] """) mod.write(code) self.pycore.run_module(mod).wait_process() pymod = self.project.get_pymodule(mod) c_class = pymod["C"].get_object() a_var = pymod["a_var"].get_object() self.assertEqual(c_class, a_var.get_type()) def test_for_loops_and_dynamicoi(self): mod = testutils.create_module(self.project, "mod") code = dedent("""\ class C(object): pass def a_func(arg): return eval("arg") for c in a_func([C()]): a_var = c """) mod.write(code) self.pycore.run_module(mod).wait_process() pymod = self.project.get_pymodule(mod) c_class = pymod["C"].get_object() a_var = pymod["a_var"].get_object() self.assertEqual(c_class, a_var.get_type()) def test_dict_objects_and_dynamicoi(self): mod = testutils.create_module(self.project, "mod") code = dedent("""\ class C(object): pass def a_func(arg): return eval("arg") a_var = a_func({1: C()})[1] """) mod.write(code) self.pycore.run_module(mod).wait_process() pymod = self.project.get_pymodule(mod) c_class = pymod["C"].get_object() a_var = pymod["a_var"].get_object() self.assertEqual(c_class, a_var.get_type()) def test_dict_keys_and_dynamicoi(self): mod = testutils.create_module(self.project, "mod") code = dedent("""\ class C(object): pass def a_func(arg): return eval("arg") a_var = list(a_func({C(): 1}))[0] """) mod.write(code) self.pycore.run_module(mod).wait_process() pymod = self.project.get_pymodule(mod) c_class = pymod["C"].get_object() a_var = pymod["a_var"].get_object() self.assertEqual(c_class, a_var.get_type()) def test_dict_keys_and_dynamicoi2(self): mod = testutils.create_module(self.project, "mod") code = dedent("""\ class C1(object): pass class C2(object): pass def a_func(arg): return eval("arg") a, b = a_func((C1(), C2())) """) mod.write(code) self.pycore.run_module(mod).wait_process() pymod = self.project.get_pymodule(mod) c1_class = pymod["C1"].get_object() c2_class = pymod["C2"].get_object() a_var = pymod["a"].get_object() b_var = pymod["b"].get_object() self.assertEqual(c1_class, a_var.get_type()) self.assertEqual(c2_class, b_var.get_type()) def test_strs_and_dynamicoi(self): mod = testutils.create_module(self.project, "mod") code = dedent("""\ def a_func(arg): return eval("arg") a_var = a_func("hey") """) mod.write(code) self.pycore.run_module(mod).wait_process() pymod = self.project.get_pymodule(mod) a_var = pymod["a_var"].get_object() self.assertTrue(isinstance(a_var.get_type(), rope.base.builtins.Str)) def test_textual_transformations(self): mod = testutils.create_module(self.project, "mod") code = dedent("""\ class C(object): pass def f(): pass a_var = C() a_list = [C()] a_str = "hey" a_file = open("file.txt") """) mod.write(code) to_pyobject = rope.base.oi.transform.TextualToPyObject(self.project) to_textual = rope.base.oi.transform.PyObjectToTextual(self.project) pymod = self.project.get_pymodule(mod) def complex_to_textual(pyobject): return to_textual.transform( to_pyobject.transform(to_textual.transform(pyobject)) ) test_variables = [ ("C", ("defined", "mod.py", "C")), ("f", ("defined", "mod.py", "f")), ("a_var", ("instance", ("defined", "mod.py", "C"))), ("a_list", ("builtin", "list", ("instance", ("defined", "mod.py", "C")))), ("a_str", ("builtin", "str")), ("a_file", ("builtin", "file")), ] test_cases = [(pymod[v].get_object(), r) for v, r in test_variables] test_cases += [ (pymod, ("defined", "mod.py")), ( rope.base.builtins.builtins["enumerate"].get_object(), ("builtin", "function", "enumerate"), ), ] for var, result in test_cases: self.assertEqual(to_textual.transform(var), result) self.assertEqual(complex_to_textual(var), result) def test_arguments_with_keywords(self): mod = testutils.create_module(self.project, "mod") code = dedent("""\ class C1(object): pass class C2(object): pass def a_func(arg): return eval("arg") a = a_func(arg=C1()) b = a_func(arg=C2()) """) mod.write(code) self.pycore.run_module(mod).wait_process() pymod = self.project.get_pymodule(mod) c1_class = pymod["C1"].get_object() c2_class = pymod["C2"].get_object() a_var = pymod["a"].get_object() b_var = pymod["b"].get_object() self.assertEqual(c1_class, a_var.get_type()) self.assertEqual(c2_class, b_var.get_type()) def test_a_function_with_different_returns(self): mod = testutils.create_module(self.project, "mod") code = dedent("""\ class C1(object): pass class C2(object): pass def a_func(arg): return eval("arg") a = a_func(C1()) b = a_func(C2()) """) mod.write(code) self.pycore.run_module(mod).wait_process() pymod = self.project.get_pymodule(mod) c1_class = pymod["C1"].get_object() c2_class = pymod["C2"].get_object() a_var = pymod["a"].get_object() b_var = pymod["b"].get_object() self.assertEqual(c1_class, a_var.get_type()) self.assertEqual(c2_class, b_var.get_type()) def test_a_function_with_different_returns2(self): mod = testutils.create_module(self.project, "mod") code = dedent("""\ class C1(object): pass class C2(object): pass def a_func(p): if p == C1: return C1() else: return C2() a = a_func(C1) b = a_func(C2) """) mod.write(code) self.pycore.run_module(mod).wait_process() pymod = self.project.get_pymodule(mod) c1_class = pymod["C1"].get_object() c2_class = pymod["C2"].get_object() a_var = pymod["a"].get_object() b_var = pymod["b"].get_object() self.assertEqual(c1_class, a_var.get_type()) self.assertEqual(c2_class, b_var.get_type()) def test_ignoring_star_args(self): mod = testutils.create_module(self.project, "mod") code = dedent("""\ class C1(object): pass class C2(object): pass def a_func(p, *args): if p == C1: return C1() else: return C2() a = a_func(C1, 1) b = a_func(C2, 2) """) mod.write(code) self.pycore.run_module(mod).wait_process() pymod = self.project.get_pymodule(mod) c1_class = pymod["C1"].get_object() c2_class = pymod["C2"].get_object() a_var = pymod["a"].get_object() b_var = pymod["b"].get_object() self.assertEqual(c1_class, a_var.get_type()) self.assertEqual(c2_class, b_var.get_type()) def test_ignoring_double_star_args(self): mod = testutils.create_module(self.project, "mod") code = dedent("""\ class C1(object): pass class C2(object): pass def a_func(p, *kwds, **args): if p == C1: return C1() else: return C2() a = a_func(C1, kwd=1) b = a_func(C2, kwd=2) """) mod.write(code) self.pycore.run_module(mod).wait_process() pymod = self.project.get_pymodule(mod) c1_class = pymod["C1"].get_object() c2_class = pymod["C2"].get_object() a_var = pymod["a"].get_object() b_var = pymod["b"].get_object() self.assertEqual(c1_class, a_var.get_type()) self.assertEqual(c2_class, b_var.get_type()) def test_invalidating_data_after_changing(self): mod = testutils.create_module(self.project, "mod") code = dedent("""\ def a_func(arg): return eval("arg") a_var = a_func(a_func) """) mod.write(code) self.pycore.run_module(mod).wait_process() mod.write(code.replace("a_func", "newfunc")) mod.write(code) pymod = self.project.get_pymodule(mod) self.assertNotEqual(pymod["a_func"].get_object(), pymod["a_var"].get_object()) def test_invalidating_data_after_moving(self): mod2 = testutils.create_module(self.project, "mod2") mod2.write("class C(object):\n pass\n") mod = testutils.create_module(self.project, "mod") code = dedent("""\ import mod2 def a_func(arg): return eval(arg) a_var = a_func("mod2.C") """) mod.write(code) self.pycore.run_module(mod).wait_process() mod.move("newmod.py") pymod = self.project.get_module("newmod") pymod2 = self.project.get_pymodule(mod2) self.assertEqual(pymod2["C"].get_object(), pymod["a_var"].get_object()) class NewStaticOITest(unittest.TestCase): def setUp(self): super().setUp() self.project = testutils.sample_project(validate_objectdb=True) self.pycore = self.project.pycore self.mod = testutils.create_module(self.project, "mod") def tearDown(self): testutils.remove_project(self.project) super().tearDown() def test_static_oi_for_simple_function_calls(self): code = dedent("""\ class C(object): pass def f(p): pass f(C()) """) self.mod.write(code) self.pycore.analyze_module(self.mod) pymod = self.project.get_pymodule(self.mod) c_class = pymod["C"].get_object() f_scope = pymod["f"].get_object().get_scope() p_type = f_scope["p"].get_object().get_type() self.assertEqual(c_class, p_type) def test_static_oi_not_failing_when_callin_callables(self): code = dedent("""\ class C(object): pass C() """) self.mod.write(code) self.pycore.analyze_module(self.mod) def test_static_oi_for_nested_calls(self): code = dedent("""\ class C(object): pass def f(p): pass def g(p): return p f(g(C())) """) self.mod.write(code) self.pycore.analyze_module(self.mod) pymod = self.project.get_pymodule(self.mod) c_class = pymod["C"].get_object() f_scope = pymod["f"].get_object().get_scope() p_type = f_scope["p"].get_object().get_type() self.assertEqual(c_class, p_type) def test_static_oi_class_methods(self): code = dedent("""\ class C(object): def f(self, p): pass C().f(C())""") self.mod.write(code) self.pycore.analyze_module(self.mod) pymod = self.project.get_pymodule(self.mod) c_class = pymod["C"].get_object() f_scope = c_class["f"].get_object().get_scope() p_type = f_scope["p"].get_object().get_type() self.assertEqual(c_class, p_type) def test_static_oi_preventing_soi_maximum_recursion_exceptions(self): code = dedent("""\ item = {} for item in item.keys(): pass """) self.mod.write(code) try: self.pycore.analyze_module(self.mod) except RuntimeError as e: self.fail(str(e)) def test_static_oi_for_infer_return_typs_from_funcs_based_on_params(self): code = dedent("""\ class C(object): pass def func(p): return p a_var = func(C()) """) self.mod.write(code) pymod = self.project.get_pymodule(self.mod) c_class = pymod["C"].get_object() a_var = pymod["a_var"].get_object() self.assertEqual(c_class, a_var.get_type()) def test_a_function_with_different_returns(self): code = dedent("""\ class C1(object): pass class C2(object): pass def a_func(arg): return arg a = a_func(C1()) b = a_func(C2()) """) self.mod.write(code) pymod = self.project.get_pymodule(self.mod) c1_class = pymod["C1"].get_object() c2_class = pymod["C2"].get_object() a_var = pymod["a"].get_object() b_var = pymod["b"].get_object() self.assertEqual(c1_class, a_var.get_type()) self.assertEqual(c2_class, b_var.get_type()) def test_not_reporting_out_of_date_information(self): code = dedent("""\ class C1(object): pass def f(arg): return C1() a_var = f() """) self.mod.write(code) pymod = self.project.get_pymodule(self.mod) c1_class = pymod["C1"].get_object() a_var = pymod["a_var"].get_object() self.assertEqual(c1_class, a_var.get_type()) self.mod.write(code.replace("C1", "C2")) pymod = self.project.get_pymodule(self.mod) c2_class = pymod["C2"].get_object() a_var = pymod["a_var"].get_object() self.assertEqual(c2_class, a_var.get_type()) def test_invalidating_concluded_data_in_a_function(self): mod1 = testutils.create_module(self.project, "mod1") mod2 = testutils.create_module(self.project, "mod2") mod1.write(dedent("""\ def func(arg): temp = arg return temp """)) mod2.write(dedent("""\ import mod1 class C1(object): pass class C2(object): pass a_var = mod1.func(C1()) """)) pymod2 = self.project.get_pymodule(mod2) c1_class = pymod2["C1"].get_object() a_var = pymod2["a_var"].get_object() self.assertEqual(c1_class, a_var.get_type()) mod2.write(mod2.read()[: mod2.read().rfind("C1()")] + "C2())\n") pymod2 = self.project.get_pymodule(mod2) c2_class = pymod2["C2"].get_object() a_var = pymod2["a_var"].get_object() self.assertEqual(c2_class, a_var.get_type()) def test_handling_generator_functions_for_strs(self): self.mod.write(dedent("""\ class C(object): pass def f(p): yield p() for c in f(C): a_var = c """)) pymod = self.project.get_pymodule(self.mod) c_class = pymod["C"].get_object() a_var = pymod["a_var"].get_object() self.assertEqual(c_class, a_var.get_type()) # TODO: Returning a generator for functions that yield unknowns @unittest.skip("Returning a generator that yields unknowns") def xxx_test_handl_generator_functions_when_unknown_type_is_yielded(self): self.mod.write(dedent("""\ class C(object): pass def f(): yield eval("C()") a_var = f() """)) pymod = self.project.get_pymodule(self.mod) a_var = pymod["a_var"].get_object() self.assertTrue(isinstance(a_var.get_type(), rope.base.builtins.Generator)) def test_static_oi_for_lists_depending_on_append_function(self): code = dedent("""\ class C(object): pass l = list() l.append(C()) a_var = l.pop() """) self.mod.write(code) self.pycore.analyze_module(self.mod) pymod = self.project.get_pymodule(self.mod) c_class = pymod["C"].get_object() a_var = pymod["a_var"].get_object() self.assertEqual(c_class, a_var.get_type()) def test_static_oi_for_lists_per_object_for_get_item(self): code = dedent("""\ class C(object): pass l = list() l.append(C()) a_var = l[0] """) self.mod.write(code) self.pycore.analyze_module(self.mod) pymod = self.project.get_pymodule(self.mod) c_class = pymod["C"].get_object() a_var = pymod["a_var"].get_object() self.assertEqual(c_class, a_var.get_type()) def test_static_oi_for_lists_per_object_for_fields(self): code = dedent("""\ class C(object): pass class A(object): def __init__(self): self.l = [] def set(self): self.l.append(C()) a = A() a.set() a_var = a.l[0] """) self.mod.write(code) self.pycore.analyze_module(self.mod) pymod = self.project.get_pymodule(self.mod) c_class = pymod["C"].get_object() a_var = pymod["a_var"].get_object() self.assertEqual(c_class, a_var.get_type()) def test_static_oi_for_lists_per_object_for_set_item(self): code = dedent("""\ class C(object): pass l = [None] l[0] = C() a_var = l[0] """) self.mod.write(code) self.pycore.analyze_module(self.mod) pymod = self.project.get_pymodule(self.mod) c_class = pymod["C"].get_object() a_var = pymod["a_var"].get_object() self.assertEqual(c_class, a_var.get_type()) def test_static_oi_for_lists_per_object_for_extending_lists(self): code = dedent("""\ class C(object): pass l = [] l.append(C()) l2 = [] l2.extend(l) a_var = l2[0] """) self.mod.write(code) self.pycore.analyze_module(self.mod) pymod = self.project.get_pymodule(self.mod) c_class = pymod["C"].get_object() a_var = pymod["a_var"].get_object() self.assertEqual(c_class, a_var.get_type()) def test_static_oi_for_lists_per_object_for_iters(self): code = dedent("""\ class C(object): pass l = [] l.append(C()) for c in l: a_var = c """) self.mod.write(code) self.pycore.analyze_module(self.mod) pymod = self.project.get_pymodule(self.mod) c_class = pymod["C"].get_object() a_var = pymod["a_var"].get_object() self.assertEqual(c_class, a_var.get_type()) def test_static_oi_for_dicts_depending_on_append_function(self): code = dedent("""\ class C1(object): pass class C2(object): pass d = {} d[C1()] = C2() a, b = d.popitem() """) self.mod.write(code) self.pycore.analyze_module(self.mod) pymod = self.project.get_pymodule(self.mod) c1_class = pymod["C1"].get_object() c2_class = pymod["C2"].get_object() a_var = pymod["a"].get_object() b_var = pymod["b"].get_object() self.assertEqual(c1_class, a_var.get_type()) self.assertEqual(c2_class, b_var.get_type()) def test_static_oi_for_dicts_depending_on_for_loops(self): code = dedent("""\ class C1(object): pass class C2(object): pass d = {} d[C1()] = C2() for k, v in d.items(): a = k b = v """) self.mod.write(code) self.pycore.analyze_module(self.mod) pymod = self.project.get_pymodule(self.mod) c1_class = pymod["C1"].get_object() c2_class = pymod["C2"].get_object() a_var = pymod["a"].get_object() b_var = pymod["b"].get_object() self.assertEqual(c1_class, a_var.get_type()) self.assertEqual(c2_class, b_var.get_type()) def test_static_oi_for_dicts_depending_on_update(self): code = dedent("""\ class C1(object): pass class C2(object): pass d = {} d[C1()] = C2() d2 = {} d2.update(d) a, b = d2.popitem() """) self.mod.write(code) self.pycore.analyze_module(self.mod) pymod = self.project.get_pymodule(self.mod) c1_class = pymod["C1"].get_object() c2_class = pymod["C2"].get_object() a_var = pymod["a"].get_object() b_var = pymod["b"].get_object() self.assertEqual(c1_class, a_var.get_type()) self.assertEqual(c2_class, b_var.get_type()) def test_static_oi_for_dicts_depending_on_update_on_seqs(self): code = dedent("""\ class C1(object): pass class C2(object): pass d = {} d.update([(C1(), C2())]) a, b = d.popitem() """) self.mod.write(code) self.pycore.analyze_module(self.mod) pymod = self.project.get_pymodule(self.mod) c1_class = pymod["C1"].get_object() c2_class = pymod["C2"].get_object() a_var = pymod["a"].get_object() b_var = pymod["b"].get_object() self.assertEqual(c1_class, a_var.get_type()) self.assertEqual(c2_class, b_var.get_type()) def test_static_oi_for_sets_per_object_for_set_item(self): code = dedent("""\ class C(object): pass s = set() s.add(C()) a_var = s.pop() """) self.mod.write(code) self.pycore.analyze_module(self.mod) pymod = self.project.get_pymodule(self.mod) c_class = pymod["C"].get_object() a_var = pymod["a_var"].get_object() self.assertEqual(c_class, a_var.get_type()) def test_properties_and_calling_get_property(self): code = dedent("""\ class C1(object): pass class C2(object): c1 = C1() def get_c1(self): return self.c1 p = property(get_c1) c2 = C2() a_var = c2.p """) self.mod.write(code) pymod = self.project.get_pymodule(self.mod) c1_class = pymod["C1"].get_object() a_var = pymod["a_var"].get_object() self.assertEqual(c1_class, a_var.get_type()) def test_soi_on_constructors(self): code = dedent("""\ class C1(object): pass class C2(object): def __init__(self, arg): self.attr = arg c2 = C2(C1()) a_var = c2.attr""") self.mod.write(code) self.pycore.analyze_module(self.mod) pymod = self.project.get_pymodule(self.mod) c1_class = pymod["C1"].get_object() a_var = pymod["a_var"].get_object() self.assertEqual(c1_class, a_var.get_type()) def test_soi_on_literal_assignment(self): code = 'a_var = ""' self.mod.write(code) self.pycore.analyze_module(self.mod) pymod = self.project.get_pymodule(self.mod) a_var = pymod["a_var"].get_object() self.assertEqual(Str, type(a_var.get_type())) @testutils.only_for_versions_higher("3.6") def test_soi_on_typed_assignment(self): code = "a_var: str" self.mod.write(code) self.pycore.analyze_module(self.mod) pymod = self.project.get_pymodule(self.mod) a_var = pymod["a_var"].get_object() self.assertEqual(Str, type(a_var.get_type())) def test_not_saving_unknown_function_returns(self): mod2 = testutils.create_module(self.project, "mod2") self.mod.write(dedent("""\ class C(object): pass l = [] l.append(C()) """)) mod2.write(dedent("""\ import mod def f(): return mod.l.pop() a_var = f() """)) pymod = self.project.get_pymodule(self.mod) pymod2 = self.project.get_pymodule(mod2) c_class = pymod["C"].get_object() a_var = pymod2["a_var"] self.pycore.analyze_module(mod2) self.assertNotEqual(c_class, a_var.get_object().get_type()) self.pycore.analyze_module(self.mod) self.assertEqual(c_class, a_var.get_object().get_type()) def test_using_the_best_callinfo(self): code = dedent("""\ class C1(object): pass def f(arg1, arg2, arg3): pass f("", None, C1()) f("", C1(), None) """) self.mod.write(code) self.pycore.analyze_module(self.mod) pymod = self.project.get_pymodule(self.mod) c1_class = pymod["C1"].get_object() f_scope = pymod["f"].get_object().get_scope() arg2 = f_scope["arg2"].get_object() self.assertEqual(c1_class, arg2.get_type()) def test_call_function_and_parameters(self): code = dedent("""\ class A(object): def __call__(self, p): pass A()("") """) self.mod.write(code) self.pycore.analyze_module(self.mod) scope = self.project.get_pymodule(self.mod).get_scope() p_object = scope.get_scopes()[0].get_scopes()[0]["p"].get_object() self.assertTrue(isinstance(p_object.get_type(), rope.base.builtins.Str)) def test_report_change_in_libutils(self): self.project.prefs["automatic_soa"] = True code = dedent("""\ class C(object): pass def f(p): pass f(C()) """) with open(self.mod.real_path, "w") as mod_file: mod_file.write(code) rope.base.libutils.report_change(self.project, self.mod.real_path, "") pymod = self.project.get_pymodule(self.mod) c_class = pymod["C"].get_object() f_scope = pymod["f"].get_object().get_scope() p_type = f_scope["p"].get_object().get_type() self.assertEqual(c_class, p_type) def test_report_libutils_and_analyze_all_modules(self): code = dedent("""\ class C(object): pass def f(p): pass f(C()) """) self.mod.write(code) rope.base.libutils.analyze_modules(self.project) pymod = self.project.get_pymodule(self.mod) c_class = pymod["C"].get_object() f_scope = pymod["f"].get_object().get_scope() p_type = f_scope["p"].get_object().get_type() self.assertEqual(c_class, p_type) def test_validation_problems_for_objectdb_retrievals(self): mod1 = testutils.create_module(self.project, "mod1") mod2 = testutils.create_module(self.project, "mod2") mod1.write(dedent("""\ l = [] var = l.pop() """)) mod2.write(dedent("""\ import mod1 class C(object): pass mod1.l.append(C()) """)) self.pycore.analyze_module(mod2) pymod2 = self.project.get_pymodule(mod2) c_class = pymod2["C"].get_object() pymod1 = self.project.get_pymodule(mod1) var_pyname = pymod1["var"] self.assertEqual(c_class, var_pyname.get_object().get_type()) mod2.write(dedent("""\ import mod1 mod1.l.append("") """)) self.assertNotEqual( c_class, var_pyname.get_object().get_type(), "Class `C` no more exists" ) def test_validation_problems_for_changing_builtin_types(self): mod1 = testutils.create_module(self.project, "mod1") mod1.write(dedent("""\ l = [] l.append("") """)) self.pycore.analyze_module(mod1) mod1.write(dedent("""\ l = {} v = l["key"] """)) pymod1 = self.project.get_pymodule(mod1) var = pymod1["v"].get_object() # noqa def test_always_returning_containing_class_for_selfs(self): code = dedent("""\ class A(object): def f(p): return p class B(object): pass b = B() b.f() """) self.mod.write(code) self.pycore.analyze_module(self.mod) pymod = self.project.get_pymodule(self.mod) a_class = pymod["A"].get_object() f_scope = a_class.get_scope().get_scopes()[0] p_type = f_scope["p"].get_object().get_type() self.assertEqual(a_class, p_type) def test_following_function_calls_when_asked_to(self): code = dedent("""\ class A(object): pass class C(object): def __init__(self, arg): self.attr = arg def f(p): return C(p) c = f(A()) x = c.attr """) self.mod.write(code) self.pycore.analyze_module(self.mod, followed_calls=1) pymod = self.project.get_pymodule(self.mod) a_class = pymod["A"].get_object() x_var = pymod["x"].get_object().get_type() self.assertEqual(a_class, x_var) def test_set_comprehension(self): code = dedent("""\ x = {s.strip() for s in X()} x.add('x') """) self.mod.write(code) pymod = self.project.get_pymodule(self.mod) x_var = pymod["x"].pyobject.get() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/ropetest/builtinstest.py0000664000175000017500000005526114512700666017462 0ustar00lieryanlieryanimport unittest from textwrap import dedent from rope.base import builtins, libutils, pyobjects from rope.base.builtins import Dict from ropetest import testutils class BuiltinTypesTest(unittest.TestCase): def setUp(self): super().setUp() self.project = testutils.sample_project() self.pycore = self.project.pycore self.mod = testutils.create_module(self.project, "mod") def tearDown(self): testutils.remove_project(self.project) super().tearDown() def test_simple_case(self): self.mod.write("l = []\n") pymod = self.project.get_pymodule(self.mod) self.assertTrue("append" in pymod["l"].get_object()) def test_holding_type_information(self): self.mod.write(dedent("""\ class C(object): pass l = [C()] a_var = l.pop() """)) pymod = self.project.get_pymodule(self.mod) c_class = pymod["C"].get_object() a_var = pymod["a_var"].get_object() self.assertEqual(c_class, a_var.get_type()) def test_get_items(self): self.mod.write(dedent("""\ class C(object): def __getitem__(self, i): return C() c = C() a_var = c[0] """)) pymod = self.project.get_pymodule(self.mod) c_class = pymod["C"].get_object() a_var = pymod["a_var"].get_object() self.assertEqual(c_class, a_var.get_type()) def test_get_items_for_lists(self): self.mod.write(dedent("""\ class C(object): pass l = [C()] a_var = l[0] """)) pymod = self.project.get_pymodule(self.mod) c_class = pymod["C"].get_object() a_var = pymod["a_var"].get_object() self.assertEqual(c_class, a_var.get_type()) def test_get_items_from_slices(self): self.mod.write(dedent("""\ class C(object): pass l = [C()] a_var = l[:].pop() """)) pymod = self.project.get_pymodule(self.mod) c_class = pymod["C"].get_object() a_var = pymod["a_var"].get_object() self.assertEqual(c_class, a_var.get_type()) def test_simple_for_loops(self): self.mod.write(dedent("""\ class C(object): pass l = [C()] for c in l: a_var = c """)) pymod = self.project.get_pymodule(self.mod) c_class = pymod["C"].get_object() a_var = pymod["a_var"].get_object() self.assertEqual(c_class, a_var.get_type()) def test_definition_location_for_loop_variables(self): self.mod.write(dedent("""\ class C(object): pass l = [C()] for c in l: pass """)) pymod = self.project.get_pymodule(self.mod) c_var = pymod["c"] self.assertEqual((pymod, 4), c_var.get_definition_location()) def test_simple_case_for_dicts(self): self.mod.write("d = {}\n") pymod = self.project.get_pymodule(self.mod) self.assertTrue("get" in pymod["d"].get_object()) def test_get_item_for_dicts(self): self.mod.write(dedent("""\ class C(object): pass d = {1: C()} a_var = d[1] """)) pymod = self.project.get_pymodule(self.mod) c_class = pymod["C"].get_object() a_var = pymod["a_var"].get_object() self.assertEqual(c_class, a_var.get_type()) def test_dict_function_parent(self): self.mod.write(dedent("""\ d = {1: 2} a_var = d.keys() """)) pymod = self.project.get_pymodule(self.mod) a_var = pymod["d"].get_object()["keys"].get_object() self.assertEqual(type(a_var.parent), Dict) def test_popping_dicts(self): self.mod.write(dedent("""\ class C(object): pass d = {1: C()} a_var = d.pop(1) """)) pymod = self.project.get_pymodule(self.mod) c_class = pymod["C"].get_object() a_var = pymod["a_var"].get_object() self.assertEqual(c_class, a_var.get_type()) def test_getting_keys_from_dicts(self): self.mod.write(dedent("""\ class C1(object): pass class C2(object): pass d = {C1(): C2()} for c in d.keys(): a_var = c """)) pymod = self.project.get_pymodule(self.mod) c_class = pymod["C1"].get_object() a_var = pymod["a_var"].get_object() self.assertEqual(c_class, a_var.get_type()) def test_getting_values_from_dicts(self): self.mod.write(dedent("""\ class C1(object): pass class C2(object): pass d = {C1(): C2()} for c in d.values(): a_var = c """)) pymod = self.project.get_pymodule(self.mod) c_class = pymod["C2"].get_object() a_var = pymod["a_var"].get_object() self.assertEqual(c_class, a_var.get_type()) def test_getting_iterkeys_from_dicts(self): self.mod.write(dedent("""\ class C1(object): pass class C2(object): pass d = {C1(): C2()} for c in d.keys(): a_var = c """)) pymod = self.project.get_pymodule(self.mod) c_class = pymod["C1"].get_object() a_var = pymod["a_var"].get_object() self.assertEqual(c_class, a_var.get_type()) def test_getting_itervalues_from_dicts(self): self.mod.write(dedent("""\ class C1(object): pass class C2(object): pass d = {C1(): C2()} for c in d.values(): a_var = c """)) pymod = self.project.get_pymodule(self.mod) c_class = pymod["C2"].get_object() a_var = pymod["a_var"].get_object() self.assertEqual(c_class, a_var.get_type()) def test_using_copy_for_dicts(self): self.mod.write(dedent("""\ class C1(object): pass class C2(object): pass d = {C1(): C2()} for c in d.copy(): a_var = c """)) pymod = self.project.get_pymodule(self.mod) c_class = pymod["C1"].get_object() a_var = pymod["a_var"].get_object() self.assertEqual(c_class, a_var.get_type()) def test_tuple_assignments_for_items(self): self.mod.write(dedent("""\ class C1(object): pass class C2(object): pass d = {C1(): C2()} key, value = d.items()[0] """)) pymod = self.project.get_pymodule(self.mod) c1_class = pymod["C1"].get_object() c2_class = pymod["C2"].get_object() key = pymod["key"].get_object() value = pymod["value"].get_object() self.assertEqual(c1_class, key.get_type()) self.assertEqual(c2_class, value.get_type()) def test_tuple_assignment_for_lists(self): self.mod.write(dedent("""\ class C(object): pass l = [C(), C()] a, b = l """)) pymod = self.project.get_pymodule(self.mod) c_class = pymod["C"].get_object() a_var = pymod["a"].get_object() b_var = pymod["b"].get_object() self.assertEqual(c_class, a_var.get_type()) self.assertEqual(c_class, b_var.get_type()) def test_tuple_assignments_for_iteritems_in_fors(self): self.mod.write(dedent("""\ class C1(object): pass class C2(object): pass d = {C1(): C2()} for x, y in d.items(): a = x; b = y """)) pymod = self.project.get_pymodule(self.mod) c1_class = pymod["C1"].get_object() c2_class = pymod["C2"].get_object() a_var = pymod["a"].get_object() b_var = pymod["b"].get_object() self.assertEqual(c1_class, a_var.get_type()) self.assertEqual(c2_class, b_var.get_type()) def test_simple_tuple_assignments(self): self.mod.write(dedent("""\ class C1(object): pass class C2(object): pass a, b = C1(), C2() """)) pymod = self.project.get_pymodule(self.mod) c1_class = pymod["C1"].get_object() c2_class = pymod["C2"].get_object() a_var = pymod["a"].get_object() b_var = pymod["b"].get_object() self.assertEqual(c1_class, a_var.get_type()) self.assertEqual(c2_class, b_var.get_type()) def test_overriding_builtin_names(self): self.mod.write(dedent("""\ class C(object): pass list = C """)) pymod = self.project.get_pymodule(self.mod) c_class = pymod["C"].get_object() list_var = pymod["list"].get_object() self.assertEqual(c_class, list_var) def test_simple_builtin_scope_test(self): self.mod.write("l = list()\n") pymod = self.project.get_pymodule(self.mod) self.assertTrue("append" in pymod["l"].get_object()) def test_simple_sets(self): self.mod.write("s = set()\n") pymod = self.project.get_pymodule(self.mod) self.assertTrue("add" in pymod["s"].get_object()) def test_making_lists_using_the_passed_argument_to_init(self): self.mod.write(dedent("""\ class C(object): pass l1 = [C()] l2 = list(l1) a_var = l2.pop() """)) pymod = self.project.get_pymodule(self.mod) c_class = pymod["C"].get_object() a_var = pymod["a_var"].get_object() self.assertEqual(c_class, a_var.get_type()) def test_making_tuples_using_the_passed_argument_to_init(self): self.mod.write(dedent("""\ class C(object): pass l1 = [C()] l2 = tuple(l1) a_var = l2[0] """)) pymod = self.project.get_pymodule(self.mod) c_class = pymod["C"].get_object() a_var = pymod["a_var"].get_object() self.assertEqual(c_class, a_var.get_type()) def test_making_sets_using_the_passed_argument_to_init(self): self.mod.write(dedent("""\ class C(object): pass l1 = [C()] l2 = set(l1) a_var = l2.pop() """)) pymod = self.project.get_pymodule(self.mod) c_class = pymod["C"].get_object() a_var = pymod["a_var"].get_object() self.assertEqual(c_class, a_var.get_type()) def test_making_dicts_using_the_passed_argument_to_init(self): self.mod.write(dedent("""\ class C1(object): pass class C2(object): pass l1 = [(C1(), C2())] l2 = dict(l1) a, b = l2.items()[0] """)) pymod = self.project.get_pymodule(self.mod) c1_class = pymod["C1"].get_object() c2_class = pymod["C2"].get_object() a_var = pymod["a"].get_object() b_var = pymod["b"].get_object() self.assertEqual(c1_class, a_var.get_type()) self.assertEqual(c2_class, b_var.get_type()) def test_range_builtin_function(self): self.mod.write("l = range(1)\n") pymod = self.project.get_pymodule(self.mod) l = pymod["l"].get_object() self.assertTrue("append" in l) def test_reversed_builtin_function(self): self.mod.write(dedent("""\ class C(object): pass l = [C()] for x in reversed(l): a_var = x """)) pymod = self.project.get_pymodule(self.mod) c_class = pymod["C"].get_object() a_var = pymod["a_var"].get_object() self.assertEqual(c_class, a_var.get_type()) def test_sorted_builtin_function(self): self.mod.write(dedent("""\ class C(object): pass l = [C()] a_var = sorted(l).pop() """)) pymod = self.project.get_pymodule(self.mod) c_class = pymod["C"].get_object() a_var = pymod["a_var"].get_object() self.assertEqual(c_class, a_var.get_type()) def test_super_builtin_function(self): self.mod.write(dedent("""\ class C(object): pass class A(object): def a_f(self): return C() class B(A): def b_f(self): return super(B, self).a_f() a_var = B.b_f() """)) pymod = self.project.get_pymodule(self.mod) c_class = pymod["C"].get_object() a_var = pymod["a_var"].get_object() self.assertEqual(c_class, a_var.get_type()) def test_file_builtin_type(self): self.mod.write(dedent("""\ for line in open("file.txt"): a_var = line """)) pymod = self.project.get_pymodule(self.mod) a_var = pymod["a_var"].get_object() self.assertTrue(isinstance(a_var.get_type(), builtins.Str)) def test_property_builtin_type(self): self.mod.write("p = property()\n") pymod = self.project.get_pymodule(self.mod) p_var = pymod["p"].get_object() self.assertTrue("fget" in p_var) def test_lambda_functions(self): self.mod.write("l = lambda: 1\n") pymod = self.project.get_pymodule(self.mod) l_var = pymod["l"].get_object() self.assertEqual(pyobjects.get_base_type("Function"), l_var.get_type()) def test_lambda_function_definition(self): self.mod.write("l = lambda x, y = 2, *a, **b: x + y\n") pymod = self.project.get_pymodule(self.mod) l_var = pymod["l"].get_object() self.assertTrue(l_var.get_name() is not None) self.assertEqual(len(l_var.get_param_names()), 4) self.assertEqual((pymod, 1), pymod["l"].get_definition_location()) def test_lambdas_that_return_unknown(self): self.mod.write("a_var = (lambda: None)()\n") pymod = self.project.get_pymodule(self.mod) a_var = pymod["a_var"].get_object() self.assertTrue(a_var is not None) def test_builtin_zip_function(self): self.mod.write(dedent("""\ class C1(object): pass class C2(object): pass c1_list = [C1()] c2_list = [C2()] a, b = zip(c1_list, c2_list)[0] """)) pymod = self.project.get_pymodule(self.mod) c1_class = pymod["C1"].get_object() c2_class = pymod["C2"].get_object() a_var = pymod["a"].get_object() b_var = pymod["b"].get_object() self.assertEqual(c1_class, a_var.get_type()) self.assertEqual(c2_class, b_var.get_type()) def test_builtin_zip_function_with_more_than_two_args(self): self.mod.write(dedent("""\ class C1(object): pass class C2(object): pass c1_list = [C1()] c2_list = [C2()] a, b, c = zip(c1_list, c2_list, c1_list)[0] """)) pymod = self.project.get_pymodule(self.mod) c1_class = pymod["C1"].get_object() c2_class = pymod["C2"].get_object() a_var = pymod["a"].get_object() b_var = pymod["b"].get_object() c_var = pymod["c"].get_object() self.assertEqual(c1_class, a_var.get_type()) self.assertEqual(c2_class, b_var.get_type()) self.assertEqual(c1_class, c_var.get_type()) def test_wrong_arguments_to_zip_function(self): self.mod.write(dedent("""\ class C1(object): pass c1_list = [C1()] a, b = zip(c1_list, 1)[0] """)) pymod = self.project.get_pymodule(self.mod) c1_class = pymod["C1"].get_object() a_var = pymod["a"].get_object() b_var = pymod["b"].get_object() # noqa self.assertEqual(c1_class, a_var.get_type()) def test_enumerate_builtin_function(self): self.mod.write(dedent("""\ class C(object): pass l = [C()] for i, x in enumerate(l): a_var = x """)) pymod = self.project.get_pymodule(self.mod) c_class = pymod["C"].get_object() a_var = pymod["a_var"].get_object() self.assertEqual(c_class, a_var.get_type()) def test_builtin_class_get_name(self): self.assertEqual("object", builtins.builtins["object"].get_object().get_name()) self.assertEqual( "property", builtins.builtins["property"].get_object().get_name() ) def test_star_args_and_double_star_args(self): self.mod.write(dedent("""\ def func(p, *args, **kwds): pass """)) pymod = self.project.get_pymodule(self.mod) func_scope = pymod["func"].get_object().get_scope() args = func_scope["args"].get_object() kwds = func_scope["kwds"].get_object() self.assertTrue(isinstance(args.get_type(), builtins.List)) self.assertTrue(isinstance(kwds.get_type(), builtins.Dict)) def test_simple_list_comprehension_test(self): self.mod.write("a_var = [i for i in range(10)]\n") pymod = self.project.get_pymodule(self.mod) a_var = pymod["a_var"].get_object() self.assertTrue(isinstance(a_var.get_type(), builtins.List)) def test_simple_list_generator_expression(self): self.mod.write("a_var = (i for i in range(10))\n") pymod = self.project.get_pymodule(self.mod) a_var = pymod["a_var"].get_object() self.assertTrue(isinstance(a_var.get_type(), builtins.Iterator)) def test_iter_builtin_function(self): self.mod.write(dedent("""\ class C(object): pass l = [C()] for c in iter(l): a_var = c """)) pymod = self.project.get_pymodule(self.mod) c_class = pymod["C"].get_object() a_var = pymod["a_var"].get_object() self.assertEqual(c_class, a_var.get_type()) def test_simple_int_type(self): self.mod.write("l = 1\n") pymod = self.project.get_pymodule(self.mod) self.assertEqual( builtins.builtins["int"].get_object(), pymod["l"].get_object().get_type() ) def test_simple_float_type(self): self.mod.write("l = 1.0\n") pymod = self.project.get_pymodule(self.mod) self.assertEqual( builtins.builtins["float"].get_object(), pymod["l"].get_object().get_type() ) def test_simple_float_type2(self): self.mod.write("l = 1e1\n") pymod = self.project.get_pymodule(self.mod) self.assertEqual( builtins.builtins["float"].get_object(), pymod["l"].get_object().get_type() ) def test_simple_complex_type(self): self.mod.write("l = 1.0j\n") pymod = self.project.get_pymodule(self.mod) self.assertEqual( builtins.builtins["complex"].get_object(), pymod["l"].get_object().get_type(), ) def test_handling_unaryop_on_ints(self): self.mod.write("l = -(1)\n") pymod = self.project.get_pymodule(self.mod) self.assertEqual( builtins.builtins["int"].get_object(), pymod["l"].get_object().get_type() ) def test_handling_binop_on_ints(self): self.mod.write("l = 1 + 1\n") pymod = self.project.get_pymodule(self.mod) self.assertEqual( builtins.builtins["int"].get_object(), pymod["l"].get_object().get_type() ) def test_handling_compares(self): self.mod.write("l = 1 == 1\n") pymod = self.project.get_pymodule(self.mod) self.assertEqual( builtins.builtins["bool"].get_object(), pymod["l"].get_object().get_type() ) def test_handling_boolops(self): self.mod.write("l = 1 and 2\n") pymod = self.project.get_pymodule(self.mod) self.assertEqual( builtins.builtins["int"].get_object(), pymod["l"].get_object().get_type() ) def test_binary_or_left_value_unknown(self): code = "var = (asdsd or 3)\n" pymod = libutils.get_string_module(self.project, code) self.assertEqual( builtins.builtins["int"].get_object(), pymod["var"].get_object().get_type() ) def test_unknown_return_object(self): src = dedent("""\ import sys def foo(): res = set(sys.builtin_module_names) if foo: res.add(bar) """) self.project.prefs["import_dynload_stdmods"] = True self.mod.write(src) self.project.pycore.analyze_module(self.mod) def test_abstractmethods_attribute(self): # see http://bugs.python.org/issue10006 for details src = "class SubType(type): pass\nsubtype = SubType()\n" self.mod.write(src) self.project.pycore.analyze_module(self.mod) class BuiltinModulesTest(unittest.TestCase): def setUp(self): super().setUp() self.project = testutils.sample_project( extension_modules=["time", "invalid", "invalid.sub"] ) self.mod = testutils.create_module(self.project, "mod") def tearDown(self): testutils.remove_project(self.project) super().tearDown() def test_simple_case(self): self.mod.write("import time") pymod = self.project.get_pymodule(self.mod) self.assertTrue("time" in pymod["time"].get_object()) def test_ignored_extensions(self): self.mod.write("import os") pymod = self.project.get_pymodule(self.mod) self.assertTrue("rename" not in pymod["os"].get_object()) def test_ignored_extensions_2(self): self.mod.write("import os") pymod = self.project.get_pymodule(self.mod) self.assertTrue("rename" not in pymod["os"].get_object()) def test_nonexistent_modules(self): self.mod.write("import invalid") pymod = self.project.get_pymodule(self.mod) pymod["invalid"].get_object() def test_nonexistent_modules_2(self): self.mod.write(dedent("""\ import invalid import invalid.sub """)) pymod = self.project.get_pymodule(self.mod) invalid = pymod["invalid"].get_object() self.assertTrue("sub" in invalid) def test_time_in_std_mods(self): import rope.base.stdmods self.assertTrue("time" in rope.base.stdmods.standard_modules()) def test_timemodule_normalizes_to_time(self): import rope.base.stdmods self.assertEqual(rope.base.stdmods.normalize_so_name("timemodule.so"), "time") ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1699196919.0 rope-1.12.0/ropetest/codeanalyzetest.py0000664000175000017500000010611214521727767020132 0ustar00lieryanlieryanimport unittest from textwrap import dedent import rope.base.evaluate from rope.base import codeanalyze, exceptions, libutils, worder from rope.base.codeanalyze import LogicalLineFinder, SourceLinesAdapter, get_block_start from ropetest import testutils class SourceLinesAdapterTest(unittest.TestCase): def test_source_lines_simple(self): to_lines = SourceLinesAdapter("line1\nline2\n") self.assertEqual("line1", to_lines.get_line(1)) self.assertEqual("line2", to_lines.get_line(2)) self.assertEqual("", to_lines.get_line(3)) self.assertEqual(3, to_lines.length()) def test_source_lines_get_line_number(self): to_lines = SourceLinesAdapter("line1\nline2\n") self.assertEqual(1, to_lines.get_line_number(0)) self.assertEqual(1, to_lines.get_line_number(5)) self.assertEqual(2, to_lines.get_line_number(7)) self.assertEqual(3, to_lines.get_line_number(12)) def test_source_lines_get_line_start(self): to_lines = SourceLinesAdapter("line1\nline2\n") self.assertEqual(0, to_lines.get_line_start(1)) self.assertEqual(6, to_lines.get_line_start(2)) self.assertEqual(12, to_lines.get_line_start(3)) def test_source_lines_get_line_end(self): to_lines = SourceLinesAdapter("line1\nline2\n") self.assertEqual(5, to_lines.get_line_end(1)) self.assertEqual(11, to_lines.get_line_end(2)) self.assertEqual(12, to_lines.get_line_end(3)) def test_source_lines_last_line_with_no_new_line(self): to_lines = SourceLinesAdapter("line1") self.assertEqual(1, to_lines.get_line_number(5)) class WordRangeFinderTest(unittest.TestCase): def _find_primary(self, code, offset): word_finder = worder.Worder(code) return word_finder.get_primary_at(offset) 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: pass """) self.assertEqual("(a_var).an_attr", self._find_primary(code, code.index(":"))) def test_inside_parans(self): code = "a_func(a_var)" self.assertEqual("a_var", self._find_primary(code, 10)) def test_simple_names(self): code = "a_var = 10" self.assertEqual("a_var", self._find_primary(code, 3)) def test_function_calls(self): code = "sample_function()" self.assertEqual("sample_function", self._find_primary(code, 10)) def test_attribute_accesses(self): code = "a_var.an_attr" self.assertEqual("a_var.an_attr", self._find_primary(code, 10)) def test_word_finder_on_word_beginning(self): code = "print(a_var)\n" word_finder = worder.Worder(code) result = word_finder.get_word_at(code.index("a_var")) self.assertEqual("a_var", result) def test_word_finder_on_primary_beginning(self): code = "print(a_var)\n" result = self._find_primary(code, code.index("a_var")) self.assertEqual("a_var", result) def test_word_finder_on_word_ending(self): code = "print(a_var)\n" word_finder = worder.Worder(code) result = word_finder.get_word_at(code.index("a_var") + 5) self.assertEqual("a_var", result) def test_word_finder_on_primary_ending(self): code = "print(a_var)\n" result = self._find_primary(code, code.index("a_var") + 5) self.assertEqual("a_var", result) def test_word_finder_on_primaries_with_dots_inside_parens(self): code = "(a_var.\nattr)" result = self._find_primary(code, code.index("attr") + 1) self.assertEqual("a_var.\nattr", result) def test_word_finder_on_primary_like_keyword(self): code = "is_keyword = False\n" result = self._find_primary(code, 1) self.assertEqual("is_keyword", result) def test_keyword_before_parens_no_space(self): code = dedent("""\ if(a_var).an_attr: pass """) self.assertEqual("(a_var).an_attr", self._find_primary(code, code.index(":"))) def test_strings(self): code = '"a string".split()' self.assertEqual('"a string".split', self._find_primary(code, 14)) def test_function_calls2(self): code = 'file("afile.txt").read()' self.assertEqual('file("afile.txt").read', self._find_primary(code, 18)) def test_parens(self): code = '("afile.txt").split()' self.assertEqual('("afile.txt").split', self._find_primary(code, 18)) def test_function_with_no_param(self): code = "AClass().a_func()" self.assertEqual("AClass().a_func", self._find_primary(code, 12)) def test_function_with_multiple_param(self): code = 'AClass(a_param, another_param, "a string").a_func()' self.assertEqual( 'AClass(a_param, another_param, "a string").a_func', self._find_primary(code, 44), ) def test_param_expressions(self): code = "AClass(an_object.an_attr).a_func()" self.assertEqual("an_object.an_attr", self._find_primary(code, 20)) def test_string_parens(self): code = 'a_func("(").an_attr' self.assertEqual('a_func("(").an_attr', self._find_primary(code, 16)) def test_extra_spaces(self): code = 'a_func ( "(" ) . an_attr' 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)) def test_split_statement(self): word_finder = worder.Worder("an_object.an_attr") self.assertEqual( ("an_object", "an_at", 10), word_finder.get_splitted_primary_before(15) ) def test_empty_split_statement(self): word_finder = worder.Worder("an_attr") self.assertEqual(("", "an_at", 0), word_finder.get_splitted_primary_before(5)) def test_empty_split_statement2(self): word_finder = worder.Worder("an_object.") self.assertEqual( ("an_object", "", 10), word_finder.get_splitted_primary_before(10) ) def test_empty_split_statement3(self): word_finder = worder.Worder("") self.assertEqual(("", "", 0), word_finder.get_splitted_primary_before(0)) def test_empty_split_statement4(self): word_finder = worder.Worder("a_var = ") self.assertEqual(("", "", 8), word_finder.get_splitted_primary_before(8)) def test_empty_split_statement5(self): word_finder = worder.Worder("a.") self.assertEqual(("a", "", 2), word_finder.get_splitted_primary_before(2)) def test_operators_inside_parens(self): code = "(a_var + another_var).reverse()" self.assertEqual("(a_var + another_var).reverse", self._find_primary(code, 25)) def test_dictionaries(self): code = 'print({1: "one", 2: "two"}.keys())' self.assertEqual('{1: "one", 2: "two"}.keys', self._find_primary(code, 29)) def test_following_parens(self): code = "a_var = a_func()()" result = self._find_primary(code, code.index(")(") + 3) self.assertEqual("a_func()()", result) def test_comments_for_finding_statements(self): code = "# var2 . \n var3" self.assertEqual("var3", self._find_primary(code, code.index("3"))) def test_str_in_comments_for_finding_statements(self): code = '# "var2" . \n var3' self.assertEqual("var3", self._find_primary(code, code.index("3"))) def test_comments_for_finding_statements2(self): code = 'var1 + "# var2".\n var3' self.assertEqual("var3", self._find_primary(code, 21)) def test_comments_for_finding_statements3(self): code = '"" + # var2.\n var3' self.assertEqual("var3", self._find_primary(code, 21)) 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 """) word_finder = worder.Worder(code) 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_is_import_statement_finding2(self): code = dedent("""\ import a.b.c.d result = a.b.c.d.f() """) word_finder = worder.Worder(code) self.assertFalse(word_finder.is_import_statement(code.rindex("d") + 1)) def test_word_parens_range(self): code = dedent("""\ s = str() s.title() """) word_finder = worder.Worder(code) result = word_finder.get_word_parens_range(code.rindex("()") - 1) self.assertEqual((len(code) - 3, len(code) - 1), result) def test_getting_primary_before_get_index(self): code = "\na = (b + c).d[0]()\n" result = self._find_primary(code, len(code) - 2) self.assertEqual("(b + c).d[0]()", result) def test_getting_primary_and_strings_at_the_end_of_line(self): code = "f('\\'')\n" result = self._find_primary(code, len(code) - 1) # noqa def test_getting_primary_and_not_crossing_newlines(self): code = "\na = (b + c)\n(4 + 1).x\n" result = self._find_primary(code, len(code) - 1) self.assertEqual("(4 + 1).x", result) # XXX: concatenated string literals def xxx_test_getting_primary_cancatenating_strs(self): code = 's = "a"\n"b" "c"\n' result = self._find_primary(code, len(code) - 2) self.assertEqual('"b" "c"', result) def test_is_a_function_being_called_with_parens_on_next_line(self): code = "func\n(1, 2)\n" word_finder = worder.Worder(code) self.assertFalse(word_finder.is_a_function_being_called(1)) # XXX: handling triple quotes def xxx_test_triple_quotes(self): code = 's = """string"""\n' result = self._find_primary(code, len(code) - 1) self.assertEqual('"""string"""', result) def test_triple_quotes_spanning_multiple_lines(self): code = 's = """\\\nl1\nl2\n """\n' result = self._find_primary(code, len(code) - 2) self.assertEqual('"""\\\nl1\nl2\n """', result) def test_get_word_parens_range_and_string_literals(self): code = 'f(1, ")", 2)\n' word_finder = worder.Worder(code) result = word_finder.get_word_parens_range(0) self.assertEqual((1, len(code) - 1), result) def test_is_assigned_here_for_equality_test(self): code = "a == 1\n" word_finder = worder.Worder(code) self.assertFalse(word_finder.is_assigned_here(0)) def test_is_assigned_here_for_not_equal_test(self): code = "a != 1\n" word_finder = worder.Worder(code) self.assertFalse(word_finder.is_assigned_here(0)) # XXX: is_assigned_here should work for tuple assignments def xxx_test_is_assigned_here_for_tuple_assignment(self): code = "a, b = (1, 2)\n" 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) self.assertTrue(word_finder.is_from_statement(code.rindex("g"))) def test_is_from_with_from_import_and_line_breaks_in_the_middle(self): code = "from mod import f,\\\n g\n" word_finder = worder.Worder(code) self.assertTrue(word_finder.is_from_statement(code.rindex("g"))) 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") self.assertTrue(word_finder.is_function_keyword_parameter(index)) def test_find_parens_start(self): code = "f(p)\n" finder = worder.Worder(code) self.assertEqual(1, finder.find_parens_start_from_inside(2)) def test_underlined_find_parens_start(self): code = 'f(p="")\n' finder = worder.Worder(code) self.assertEqual(1, finder._find_parens_start(len(code) - 2)) def test_find_parens_start_with_multiple_entries(self): code = "myfunc(p1, p2, p3\n" finder = worder.Worder(code) self.assertEqual( code.index("("), finder.find_parens_start_from_inside(len(code) - 1) ) def test_find_parens_start_with_nested_parens(self): code = "myfunc(p1, (p2, p3), p4\n" finder = worder.Worder(code) self.assertEqual( code.index("("), finder.find_parens_start_from_inside(len(code) - 1) ) def test_find_parens_start_with_parens_in_strs(self): code = 'myfunc(p1, "(", p4\n' finder = worder.Worder(code) self.assertEqual( code.index("("), finder.find_parens_start_from_inside(len(code) - 1) ) def test_find_parens_start_with_parens_in_strs_in_multiple_lines(self): code = 'myfunc (\np1\n , \n "(" \n, \np4\n' finder = worder.Worder(code) self.assertEqual( code.index("("), finder.find_parens_start_from_inside(len(code) - 1) ) 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)) def test_get_word_range_with_fstring(self): code = dedent('''\ auth = 8 my_var = f"some value {auth}" print(auth) other_val = "some other"''') finder = worder.Worder(code) self.assertEqual(finder.get_word_range(45), (45, 49)) class ScopeNameFinderTest(unittest.TestCase): def setUp(self): super().setUp() self.project = testutils.sample_project() def tearDown(self): testutils.remove_project(self.project) super().tearDown() # FIXME: in normal scopes the interpreter raises `UnboundLocalName` # exception, but not in class bodies def xxx_test_global_name_in_class_body(self): code = dedent("""\ a_var = 10 class C(object): a_var = a_var """) scope = libutils.get_string_scope(self.project, code) name_finder = rope.base.evaluate.ScopeNameFinder(scope.pyobject) result = name_finder.get_pyname_at(len(code) - 3) self.assertEqual(scope["a_var"], result) def test_class_variable_attribute_in_class_body(self): code = dedent("""\ a_var = 10 class C(object): a_var = a_var """) scope = libutils.get_string_scope(self.project, code) name_finder = rope.base.evaluate.ScopeNameFinder(scope.pyobject) a_var_pyname = scope["C"].get_object()["a_var"] result = name_finder.get_pyname_at(len(code) - 12) self.assertEqual(a_var_pyname, result) def test_class_variable_attribute_in_class_body2(self): code = dedent("""\ a_var = 10 class C(object): a_var \\ = a_var """) scope = libutils.get_string_scope(self.project, code) name_finder = rope.base.evaluate.ScopeNameFinder(scope.pyobject) a_var_pyname = scope["C"].get_object()["a_var"] result = name_finder.get_pyname_at(len(code) - 12) self.assertEqual(a_var_pyname, result) def test_class_method_attribute_in_class_body(self): code = dedent("""\ class C(object): def a_method(self): pass """) scope = libutils.get_string_scope(self.project, code) name_finder = rope.base.evaluate.ScopeNameFinder(scope.pyobject) a_method_pyname = scope["C"].get_object()["a_method"] result = name_finder.get_pyname_at(code.index("a_method") + 2) self.assertEqual(a_method_pyname, result) def test_inner_class_attribute_in_class_body(self): code = dedent("""\ class C(object): class CC(object): pass """) scope = libutils.get_string_scope(self.project, code) name_finder = rope.base.evaluate.ScopeNameFinder(scope.pyobject) a_class_pyname = scope["C"].get_object()["CC"] result = name_finder.get_pyname_at(code.index("CC") + 2) self.assertEqual(a_class_pyname, result) def test_class_method_in_class_body_but_not_indexed(self): code = dedent("""\ class C(object): def func(self, func): pass """) scope = libutils.get_string_scope(self.project, code) a_func_pyname = scope.get_scopes()[0].get_scopes()[0]["func"] name_finder = rope.base.evaluate.ScopeNameFinder(scope.pyobject) result = name_finder.get_pyname_at(code.index(", func") + 3) self.assertEqual(a_func_pyname, result) def test_function_but_not_indexed(self): code = dedent("""\ def a_func(a_func): pass """) scope = libutils.get_string_scope(self.project, code) a_func_pyname = scope["a_func"] name_finder = rope.base.evaluate.ScopeNameFinder(scope.pyobject) result = name_finder.get_pyname_at(code.index("a_func") + 3) self.assertEqual(a_func_pyname, result) 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 """)) code = "from mod import a_func\n" scope = libutils.get_string_scope(self.project, code) name_finder = rope.base.evaluate.ScopeNameFinder(scope.pyobject) mod_pyobject = self.project.get_pymodule(mod) found_pyname = name_finder.get_pyname_at(code.index("mod") + 1) self.assertEqual(mod_pyobject, found_pyname.get_object()) def test_renaming_functions_with_from_import_and_parens(self): mod1 = testutils.create_module(self.project, "mod1") mod1.write(dedent("""\ def afunc(): pass """)) code = dedent("""\ from mod1 import ( afunc as func) """) scope = libutils.get_string_scope(self.project, code) name_finder = rope.base.evaluate.ScopeNameFinder(scope.pyobject) mod_pyobject = self.project.get_pymodule(mod1) afunc = mod_pyobject["afunc"] found_pyname = name_finder.get_pyname_at(code.index("afunc") + 1) self.assertEqual(afunc.get_object(), found_pyname.get_object()) def test_relative_modules_after_from_statements(self): pkg1 = testutils.create_package(self.project, "pkg1") 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 """)) code = "from ..mod1 import a_func\n" mod2.write(code) mod2_scope = self.project.get_pymodule(mod2).get_scope() name_finder = rope.base.evaluate.ScopeNameFinder(mod2_scope.pyobject) mod1_pyobject = self.project.get_pymodule(mod1) found_pyname = name_finder.get_pyname_at(code.index("mod1") + 1) self.assertEqual(mod1_pyobject, found_pyname.get_object()) def test_relative_modules_after_from_statements2(self): mod1 = testutils.create_module(self.project, "mod1") pkg1 = testutils.create_package(self.project, "pkg1") pkg2 = testutils.create_package(self.project, "pkg2", pkg1) mod2 = testutils.create_module(self.project, "mod2", pkg2) # noqa mod1.write("import pkg1.pkg2.mod2") mod1_scope = self.project.get_pymodule(mod1).get_scope() name_finder = rope.base.evaluate.ScopeNameFinder(mod1_scope.pyobject) pkg2_pyobject = self.project.get_pymodule(pkg2) found_pyname = name_finder.get_pyname_at(mod1.read().index("pkg2") + 1) self.assertEqual(pkg2_pyobject, found_pyname.get_object()) def test_get_pyname_at_on_language_keywords(self): code = dedent("""\ def a_func(a_func): pass """) pymod = libutils.get_string_module(self.project, code) name_finder = rope.base.evaluate.ScopeNameFinder(pymod) with self.assertRaises(exceptions.RopeError): name_finder.get_pyname_at(code.index("pass")) def test_one_liners(self): code = dedent("""\ var = 1 def f(): var = 2 print(var) """) pymod = libutils.get_string_module(self.project, code) name_finder = rope.base.evaluate.ScopeNameFinder(pymod) pyname = name_finder.get_pyname_at(code.rindex("var")) self.assertEqual(pymod["var"], pyname) def test_one_liners_with_line_breaks(self): code = dedent("""\ var = 1 def f( ): var = 2 print(var) """) pymod = libutils.get_string_module(self.project, code) name_finder = rope.base.evaluate.ScopeNameFinder(pymod) pyname = name_finder.get_pyname_at(code.rindex("var")) self.assertEqual(pymod["var"], pyname) def test_one_liners_with_line_breaks2(self): code = dedent("""\ var = 1 def f( p): var = 2 print(var) """) pymod = libutils.get_string_module(self.project, code) name_finder = rope.base.evaluate.ScopeNameFinder(pymod) pyname = name_finder.get_pyname_at(code.rindex("var")) self.assertEqual(pymod["var"], pyname) def test_var_in_list_comprehension_differs_from_var_outside(self): code = "var = 1\n[var for var in range(1)]\n" pymod = libutils.get_string_module(self.project, code) name_finder = rope.base.evaluate.ScopeNameFinder(pymod) outside_pyname = name_finder.get_pyname_at(code.index("var")) inside_pyname = name_finder.get_pyname_at(code.rindex("var")) self.assertNotEqual(outside_pyname, inside_pyname) class LogicalLineFinderTest(unittest.TestCase): def _logical_finder(self, code): return LogicalLineFinder(SourceLinesAdapter(code)) def test_normal_lines(self): code = "a_var = 10" line_finder = self._logical_finder(code) self.assertEqual((1, 1), line_finder.logical_line_in(1)) def test_normal_lines2(self): code = dedent("""\ another = 10 a_var = 20 """) line_finder = self._logical_finder(code) self.assertEqual((1, 1), line_finder.logical_line_in(1)) self.assertEqual((2, 2), line_finder.logical_line_in(2)) def test_implicit_continuation(self): code = "a_var = 3 + \\\n 4 + \\\n 5" line_finder = self._logical_finder(code) self.assertEqual((1, 3), line_finder.logical_line_in(2)) def test_explicit_continuation(self): code = dedent("""\ print(2) a_var = (3 + 4, 5) """) line_finder = self._logical_finder(code) self.assertEqual((2, 4), line_finder.logical_line_in(2)) def test_explicit_continuation_comments(self): code = "#\na_var = 3\n" line_finder = self._logical_finder(code) self.assertEqual((2, 2), line_finder.logical_line_in(2)) def test_multiple_indented_ifs(self): code = dedent("""\ if True: if True: if True: pass a = 10 """) line_finder = self._logical_finder(code) self.assertEqual((5, 5), line_finder.logical_line_in(5)) def test_list_comprehensions_and_fors(self): code = dedent("""\ a_list = [i for i in range(10)] """) line_finder = self._logical_finder(code) self.assertEqual((1, 2), line_finder.logical_line_in(2)) def test_generator_expressions_and_fors(self): code = dedent("""\ a_list = (i for i in range(10)) """) line_finder = self._logical_finder(code) self.assertEqual((1, 2), line_finder.logical_line_in(2)) def test_fors_and_block_start(self): code = dedent("""\ l = range(10) for i in l: print(i) """) self.assertEqual(2, get_block_start(SourceLinesAdapter(code), 2)) def test_problems_with_inner_indentations(self): code = dedent("""\ if True: if True: if True: pass a = \\ 1 """) line_finder = self._logical_finder(code) self.assertEqual((5, 6), line_finder.logical_line_in(6)) def test_problems_with_inner_indentations2(self): code = dedent("""\ if True: if True: pass a = 1 """) line_finder = self._logical_finder(code) self.assertEqual((4, 4), line_finder.logical_line_in(4)) def test_logical_lines_for_else(self): code = dedent("""\ if True: pass else: pass """) line_finder = self._logical_finder(code) self.assertEqual((3, 3), line_finder.logical_line_in(3)) def test_logical_lines_for_lines_with_wrong_continues(self): code = "var = 1 + \\" line_finder = self._logical_finder(code) self.assertEqual((1, 1), line_finder.logical_line_in(1)) def test_logical_lines_for_multiline_string_with_extra_quotes_front(self): code = '""""Docs."""\na = 1\n' line_finder = self._logical_finder(code) self.assertEqual((2, 2), line_finder.logical_line_in(2)) def test_logical_lines_for_multiline_string_with_escaped_quotes(self): code = '"""Quotes \\""" "\\"" \' """\na = 1\n' line_finder = self._logical_finder(code) self.assertEqual((2, 2), line_finder.logical_line_in(2)) def test_generating_line_starts(self): code = dedent("""\ a = 1 a = 2 a = 3 """) line_finder = self._logical_finder(code) self.assertEqual([1, 2, 4], list(line_finder.generate_starts())) def test_generating_line_starts2(self): code = "a = 1\na = 2\n\na = \\\n 3\n" 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 = \\\n 3\n" 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): code = "\na = \\\n 1 + \\\n 1\n" line_finder = self._logical_finder(code) self.assertEqual([2], list(line_finder.generate_starts())) def test_generating_line_starts_and_unmatched_deindents(self): code = dedent("""\ if True: if True: if True: a = 1 b = 1 """) 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): def _logical_finder(self, code): lines = SourceLinesAdapter(code) return codeanalyze.CachingLogicalLineFinder( lines, codeanalyze.tokenizer_generator ) class CustomLogicalLineFinderTest(LogicalLineFinderTest): def _logical_finder(self, code): lines = SourceLinesAdapter(code) return codeanalyze.CachingLogicalLineFinder(lines, codeanalyze.custom_generator) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1699196919.0 rope-1.12.0/ropetest/conftest.py0000664000175000017500000000207314521727767016562 0ustar00lieryanlieryanimport pathlib import pytest from rope.base import resources from ropetest import testutils @pytest.fixture def project(): project = testutils.sample_project() yield project testutils.remove_project(project) @pytest.fixture def project2(): project = testutils.sample_project("another_project") yield project testutils.remove_project(project) @pytest.fixture def project_path(project): yield pathlib.Path(project.address) @pytest.fixture def project2(): project = testutils.sample_project("sample_project2") yield project testutils.remove_project(project) """ Standard project structure for pytest fixtures /mod1.py -- mod1 /pkg1/__init__.py -- pkg1 /pkg1/mod2.py -- mod2 """ @pytest.fixture def mod1(project) -> resources.File: return testutils.create_module(project, "mod1") @pytest.fixture def pkg1(project) -> resources.Folder: return testutils.create_package(project, "pkg1") @pytest.fixture def mod2(project, pkg1) -> resources.Folder: return testutils.create_module(project, "mod2", pkg1) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1705553002.7059839 rope-1.12.0/ropetest/contrib/0000775000175000017500000000000014552126153016003 5ustar00lieryanlieryan././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/ropetest/contrib/__init__.py0000664000175000017500000000000014512700666020105 0ustar00lieryanlieryan././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1705553002.709984 rope-1.12.0/ropetest/contrib/autoimport/0000775000175000017500000000000014552126153020206 5ustar00lieryanlieryan././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1705551350.0 rope-1.12.0/ropetest/contrib/autoimport/autoimporttest.py0000664000175000017500000001576114552122766023703 0ustar00lieryanlieryanimport sqlite3 from concurrent.futures import ThreadPoolExecutor from contextlib import closing, contextmanager from textwrap import dedent from unittest.mock import ANY, patch import pytest from rope.base.project import Project from rope.base.resources import File, Folder from rope.contrib.autoimport import models from rope.contrib.autoimport.sqlite import AutoImport @pytest.fixture def autoimport(project: Project): with closing(AutoImport(project)) as ai: yield ai def is_in_memory_database(connection): db_list = database_list(connection) assert db_list == [(0, "main", ANY)] return db_list[0][2] == "" def database_list(connection): return list(connection.execute("PRAGMA database_list")) def test_in_memory_database_share_cache(project, project2): ai_1 = AutoImport(project, memory=True) ai_2 = AutoImport(project, memory=True) ai_3 = AutoImport(project2, memory=True) with ai_1.connection: ai_1.connection.execute("CREATE TABLE shared(data)") ai_1.connection.execute("INSERT INTO shared VALUES(28)") assert ai_2.connection.execute("SELECT data FROM shared").fetchone() == (28,) with pytest.raises(sqlite3.OperationalError, match="no such table: shared"): ai_3.connection.execute("SELECT data FROM shared").fetchone() def test_autoimport_connection_parameter_with_in_memory( project: Project, autoimport: AutoImport, ): connection = AutoImport.create_database_connection(memory=True) assert is_in_memory_database(connection) def test_autoimport_connection_parameter_with_project( project: Project, autoimport: AutoImport, ): connection = AutoImport.create_database_connection(project=project) assert not is_in_memory_database(connection) def test_autoimport_create_database_connection_conflicting_parameter( project: Project, autoimport: AutoImport, ): with pytest.raises(Exception, match="if memory=False, project must be provided"): AutoImport.create_database_connection(memory=False) def test_autoimport_memory_parameter_is_true( project: Project, autoimport: AutoImport, ): ai = AutoImport(project, memory=True) assert is_in_memory_database(ai.connection) def test_autoimport_memory_parameter_is_false( project: Project, autoimport: AutoImport, ): ai = AutoImport(project, memory=False) assert not is_in_memory_database(ai.connection) def test_init_py( autoimport: AutoImport, project: Project, pkg1: Folder, mod1: File, ): mod1_init = pkg1.get_child("__init__.py") mod1_init.write(dedent("""\ def foo(): pass """)) mod1.write(dedent("""\ foo """)) autoimport.generate_cache([mod1_init]) results = autoimport.search("foo", True) assert [("from pkg1 import foo", "foo")] == results def test_multithreading( autoimport: AutoImport, project: Project, pkg1: Folder, mod1: File, ): mod1_init = pkg1.get_child("__init__.py") mod1_init.write(dedent("""\ def foo(): pass """)) mod1.write(dedent("""\ foo """)) autoimport = AutoImport(project, memory=False) autoimport.generate_cache([mod1_init]) tp = ThreadPoolExecutor(1) results = tp.submit(autoimport.search, "foo", True).result() assert [("from pkg1 import foo", "foo")] == results def test_connection(project: Project, project2: Project): ai1 = AutoImport(project) ai2 = AutoImport(project) ai3 = AutoImport(project2) assert ai1.connection is not ai2.connection assert ai1.connection is not ai3.connection @contextmanager def assert_database_is_reset(conn): conn.execute("ALTER TABLE names ADD COLUMN deprecated_column") names_ddl, = [ddl for ddl in conn.iterdump() if "CREATE TABLE names" in ddl] assert "deprecated_column" in names_ddl yield names_ddl, = [ddl for ddl in conn.iterdump() if "CREATE TABLE names" in ddl] assert "deprecated_column" not in names_ddl, "Database did not get reset" @contextmanager def assert_database_is_preserved(conn): conn.execute("ALTER TABLE names ADD COLUMN deprecated_column") names_ddl, = [ddl for ddl in conn.iterdump() if "CREATE TABLE names" in ddl] assert "deprecated_column" in names_ddl yield names_ddl, = [ddl for ddl in conn.iterdump() if "CREATE TABLE names" in ddl] assert "deprecated_column" in names_ddl, "Database was reset unexpectedly" def test_setup_db_metadata_table_is_missing(autoimport): conn = autoimport.connection conn.execute("DROP TABLE metadata") with assert_database_is_reset(conn): autoimport._setup_db() def test_setup_db_metadata_table_is_outdated(autoimport): conn = autoimport.connection data = ("outdated", "", "2020-01-01T00:00:00") # (version_hash, hash_data, created_at) autoimport._execute(models.Metadata.objects.insert_into(), data) with assert_database_is_reset(conn), \ patch("rope.base.versioning.calculate_version_hash", return_value="up-to-date-value"): autoimport._setup_db() with assert_database_is_preserved(conn), \ patch("rope.base.versioning.calculate_version_hash", return_value="up-to-date-value"): autoimport._setup_db() def test_setup_db_metadata_table_is_current(autoimport): conn = autoimport.connection data = ("up-to-date-value", "", "2020-01-01T00:00:00") # (version_hash, hash_data, created_at) autoimport._execute(models.Metadata.objects.delete_from()) autoimport._execute(models.Metadata.objects.insert_into(), data) with assert_database_is_preserved(conn), \ patch("rope.base.versioning.calculate_version_hash", return_value="up-to-date-value"): autoimport._setup_db() class TestQueryUsesIndexes: def explain(self, autoimport, query): explanation = list(autoimport._execute(query.explain(), ("abc",)))[0][-1] # the explanation text varies, on some sqlite version explanation = explanation.replace("TABLE ", "") return explanation def test_search_by_name_uses_index(self, autoimport): query = models.Name.search_by_name.select_star() assert ( self.explain(autoimport, query) == "SEARCH names USING INDEX names_name (name=?)" ) def test_search_by_name_like_uses_index(self, autoimport): query = models.Name.search_by_name_like.select_star() assert ( self.explain(autoimport, query) == "SEARCH names USING INDEX names_name_nocase (name>? AND name? AND module has wrong scope, expected " "%r, got %r" % (name, scope, proposal.scope), ) if type is not None: self.assertEqual( type, proposal.type, "proposal <%s> has wrong type, expected " "%r, got %r" % (name, type, proposal.type), ) return self.fail("completion <%s> not proposed" % name) def assert_completion_not_in_result(self, name, scope, result): for proposal in result: if proposal.name == name and proposal.scope == scope: self.fail("completion <%s> was proposed" % name) def test_completing_global_variables(self): code = dedent("""\ my_global = 10 t = my""") result = self._assist(code) self.assert_completion_in_result("my_global", "global", result) def test_not_proposing_unmatched_vars(self): code = dedent("""\ my_global = 10 t = you""") result = self._assist(code) self.assert_completion_not_in_result("my_global", "global", result) def test_not_proposing_unmatched_vars_with_underlined_starting(self): code = dedent("""\ my_global = 10 t = your_""") result = self._assist(code) self.assert_completion_not_in_result("my_global", "global", result) def test_not_proposing_local_assigns_as_global_completions(self): code = dedent("""\ def f(): my_global = 10 t = my_""") result = self._assist(code) self.assert_completion_not_in_result("my_global", "global", result) def test_proposing_functions(self): code = dedent("""\ def my_func(): return 2 t = my_""") result = self._assist(code) self.assert_completion_in_result("my_func", "global", result) def test_proposing_classes(self): code = dedent("""\ class Sample(object): pass t = Sam""") result = self._assist(code) self.assert_completion_in_result("Sample", "global", result) def test_proposing_each_name_at_most_once(self): code = dedent("""\ variable = 10 variable = 20 t = vari""") result = self._assist(code) count = len([x for x in result if x.name == "variable" and x.scope == "global"]) self.assertEqual(1, count) def test_throwing_exception_in_case_of_syntax_errors(self): code = dedent("""\ sample (sdf+) """) with self.assertRaises(exceptions.ModuleSyntaxError): self._assist(code, maxfixes=0) def test_fixing_errors_with_maxfixes(self): code = dedent("""\ def f(): sldj sldj def g(): ran""") result = self._assist(code, maxfixes=2) self.assertTrue(len(result) > 0) def test_ignoring_errors_in_current_line(self): code = dedent("""\ def my_func(): return 2 t = """) result = self._assist(code) self.assert_completion_in_result("my_func", "global", result) def test_not_reporting_variables_in_current_line(self): code = dedent("""\ def my_func(): return 2 t = my_""") result = self._assist(code) self.assert_completion_not_in_result("my_", "global", result) def test_completion_result(self): code = dedent("""\ my_global = 10 t = my""") self.assertEqual(len(code) - 2, starting_offset(code, len(code))) def test_completing_imported_names(self): code = dedent("""\ import sys a = sy""") result = self._assist(code) self.assert_completion_in_result("sys", "imported", result) def test_completing_imported_names_with_as(self): code = dedent("""\ import sys as mysys a = mys""") result = self._assist(code) self.assert_completion_in_result("mysys", "imported", result) def test_not_completing_imported_names_with_as(self): code = dedent("""\ import sys as mysys a = sy""") result = self._assist(code) self.assert_completion_not_in_result("sys", "global", result) def test_including_matching_builtins_types(self): code = "my_var = Excep" result = self._assist(code) self.assert_completion_in_result("Exception", "builtin", result) self.assert_completion_not_in_result("zip", "builtin", result) def test_including_matching_builtins_functions(self): code = "my_var = zi" result = self._assist(code) self.assert_completion_in_result("zip", "builtin", result) def test_builtin_instances(self): # ``import_dynload_stdmods`` pref is disabled for test project. # we need to have it enabled to make pycore._find_module() # load ``sys`` module. self.project.prefs["import_dynload_stdmods"] = True code = dedent("""\ from sys import stdout stdout.wr""") result = self._assist(code) self.assert_completion_in_result("write", "builtin", result) self.assert_completion_in_result("writelines", "builtin", result) def test_including_keywords(self): code = "fo" result = self._assist(code) self.assert_completion_in_result("for", "keyword", result) def test_not_reporting_proposals_after_dot(self): code = dedent("""\ a_dict = {} key = 3 a_dict.ke""") result = self._assist(code) self.assert_completion_not_in_result("key", "global", result) def test_proposing_local_variables_in_functions(self): code = dedent("""\ def f(self): my_var = 10 my_""") result = self._assist(code) self.assert_completion_in_result("my_var", "local", result) def test_local_variables_override_global_ones(self): code = dedent("""\ my_var = 20 def f(self): my_var = 10 my_""") result = self._assist(code) self.assert_completion_in_result("my_var", "local", result) def test_not_including_class_body_variables(self): code = dedent("""\ class C(object): my_var = 20 def f(self): a = 20 my_""") result = self._assist(code) self.assert_completion_not_in_result("my_var", "local", result) def test_nested_functions(self): code = dedent("""\ def my_func(): func_var = 20 def inner_func(): a = 20 func""") result = self._assist(code) self.assert_completion_in_result("func_var", "local", result) def test_scope_endpoint_selection(self): code = dedent("""\ def my_func(): func_var = 20 """) result = self._assist(code) self.assert_completion_not_in_result("func_var", "local", result) def test_scope_better_endpoint_selection(self): code = dedent("""\ if True: def f(): my_var = 10 my_""") result = self._assist(code) self.assert_completion_not_in_result("my_var", "local", result) def test_imports_inside_function(self): code = dedent("""\ def f(): import sys sy""") result = self._assist(code) self.assert_completion_in_result("sys", "imported", result) def test_imports_inside_function_dont_mix_with_globals(self): code = dedent("""\ def f(): import sys sy""") result = self._assist(code) self.assert_completion_not_in_result("sys", "local", result) def test_nested_classes_local_names(self): code = dedent("""\ global_var = 10 def my_func(): func_var = 20 class C(object): def another_func(self): local_var = 10 func""") result = self._assist(code) self.assert_completion_in_result("func_var", "local", result) def test_nested_classes_global(self): code = dedent("""\ global_var = 10 def my_func(): func_var = 20 class C(object): def another_func(self): local_var = 10 globa""") result = self._assist(code) self.assert_completion_in_result("global_var", "global", result) def test_nested_classes_global_function(self): code = dedent("""\ global_var = 10 def my_func(): func_var = 20 class C(object): def another_func(self): local_var = 10 my_f""") result = self._assist(code) self.assert_completion_in_result("my_func", "global", result) def test_proposing_function_parameters_in_functions(self): code = dedent("""\ def my_func(my_param): my_var = 20 my_""") result = self._assist(code) self.assert_completion_in_result("my_param", "local", result) def test_proposing_function_keyword_parameters_in_functions(self): code = dedent("""\ def my_func(my_param, *my_list, **my_kws): my_var = 20 my_""") result = self._assist(code) self.assert_completion_in_result("my_param", "local", result) self.assert_completion_in_result("my_list", "local", result) self.assert_completion_in_result("my_kws", "local", result) def test_not_proposing_unmatching_function_parameters_in_functions(self): code = dedent("""\ def my_func(my_param): my_var = 20 you_""") result = self._assist(code) self.assert_completion_not_in_result("my_param", "local", result) def test_ignoring_current_statement(self): code = dedent("""\ my_var = 10 my_tuple = (10, my_""") result = self._assist(code) self.assert_completion_in_result("my_var", "global", result) def test_ignoring_current_statement_brackets_continuation(self): code = dedent("""\ my_var = 10 'hello'[10: my_""") result = self._assist(code) self.assert_completion_in_result("my_var", "global", result) def test_ignoring_current_statement_explicit_continuation(self): code = dedent("""\ my_var = 10 my_var2 = 2 + \\ my_""") result = self._assist(code) self.assert_completion_in_result("my_var", "global", result) def test_ignor_current_statement_while_the_first_stmnt_of_the_block(self): code = dedent("""\ my_var = 10 def f(): my_""") result = self._assist(code) self.assert_completion_in_result("my_var", "global", result) def test_ignor_current_stmnt_while_current_line_ends_with_a_colon(self): code = dedent("""\ my_var = 10 if my_: pass""") result = self._assist(code, 18) self.assert_completion_in_result("my_var", "global", result) def test_ignoring_string_contents(self): code = "my_var = '('\nmy_" result = self._assist(code) self.assert_completion_in_result("my_var", "global", result) def test_ignoring_comment_contents(self): code = "my_var = 10 #(\nmy_" result = self._assist(code) self.assert_completion_in_result("my_var", "global", result) def test_ignoring_string_contents_backslash_plus_quotes(self): code = "my_var = '\\''\nmy_" result = self._assist(code) self.assert_completion_in_result("my_var", "global", result) def test_ignoring_string_contents_backslash_plus_backslash(self): code = "my_var = '\\\\'\nmy_" result = self._assist(code) self.assert_completion_in_result("my_var", "global", result) def test_not_proposing_later_defined_variables_in_current_block(self): code = dedent("""\ my_ my_var = 10 """) result = self._assist(code, 3, later_locals=False) self.assert_completion_not_in_result("my_var", "global", result) def test_not_proposing_later_defined_variables_in_current_function(self): code = dedent("""\ def f(): my_ my_var = 10 """) result = self._assist(code, 16, later_locals=False) self.assert_completion_not_in_result("my_var", "local", result) def test_ignoring_string_contents_with_triple_quotes(self): code = "my_var = '''(\n'('''\nmy_" result = self._assist(code) self.assert_completion_in_result("my_var", "global", result) def test_ignoring_string_contents_with_triple_quotes_and_backslash(self): code = 'my_var = """\\"""("""\nmy_' result = self._assist(code) self.assert_completion_in_result("my_var", "global", result) def test_ignor_str_contents_with_triple_quotes_and_double_backslash(self): code = 'my_var = """\\\\"""\nmy_' result = self._assist(code) self.assert_completion_in_result("my_var", "global", result) def test_reporting_params_when_in_the_first_line_of_a_function(self): code = dedent("""\ def f(param): para""") result = self._assist(code) self.assert_completion_in_result("param", "local", result) def test_code_assist_when_having_a_two_line_function_header(self): code = dedent("""\ def f(param1, param2): para""") result = self._assist(code) self.assert_completion_in_result("param1", "local", result) def test_code_assist_with_function_with_two_line_return(self): code = dedent("""\ def f(param1, param2): return(param1, para""") result = self._assist(code) self.assert_completion_in_result("param2", "local", result) def test_get_definition_location(self): code = dedent("""\ def a_func(): pass a_func()""") result = get_definition_location(self.project, code, len(code) - 3) self.assertEqual((None, 1), result) def test_get_definition_location_underlined_names(self): code = dedent("""\ def a_sample_func(): pass a_sample_func()""") result = get_definition_location(self.project, code, len(code) - 11) self.assertEqual((None, 1), result) def test_get_definition_location_dotted_names_method(self): code = dedent("""\ class AClass(object): @staticmethod def a_method(): pass AClass.a_method()""") result = get_definition_location(self.project, code, len(code) - 3) self.assertEqual((None, 3), result) def test_get_definition_location_dotted_names_property(self): code = dedent("""\ class AClass(object): @property @somedecorator def a_method(): pass AClass.a_method()""") result = get_definition_location(self.project, code, len(code) - 3) self.assertEqual((None, 4), result) def test_get_definition_location_dotted_names_free_function(self): code = dedent("""\ @custom_decorator def a_method(): pass a_method()""") result = get_definition_location(self.project, code, len(code) - 3) self.assertEqual((None, 2), result) @testutils.only_for_versions_higher("3.5") def test_get_definition_location_dotted_names_async_def(self): code = dedent("""\ class AClass(object): @property @decorator2 async def a_method(): pass AClass.a_method()""") result = get_definition_location(self.project, code, len(code) - 3) self.assertEqual((None, 4), result) def test_get_definition_location_dotted_names_class(self): code = dedent("""\ @custom_decorator class AClass(object): def a_method(): pass AClass.a_method()""") result = get_definition_location(self.project, code, len(code) - 12) self.assertEqual((None, 2), result) def test_get_definition_location_dotted_names_with_space(self): code = dedent("""\ class AClass(object): @staticmethod def a_method(): pass AClass.a_method()""") result = get_definition_location(self.project, code, len(code) - 3) self.assertEqual((None, 3), result) def test_get_definition_location_dotted_names_inline_body(self): code = dedent("""\ class AClass(object): @staticmethod def a_method(): pass AClass.a_method()""") result = get_definition_location(self.project, code, len(code) - 3) self.assertEqual((None, 3), result) def test_get_definition_location_dotted_names_inline_body_split_arg(self): code = dedent("""\ class AClass(object): @staticmethod def a_method( self, arg1 ): pass AClass.a_method()""") result = get_definition_location(self.project, code, len(code) - 3) self.assertEqual((None, 3), result) def test_get_definition_location_dotted_module_names(self): module_resource = testutils.create_module(self.project, "mod") module_resource.write("def a_func():\n pass\n") code = "import mod\nmod.a_func()" result = get_definition_location(self.project, code, len(code) - 3) self.assertEqual((module_resource, 1), result) def test_get_definition_location_for_nested_packages(self): mod1 = testutils.create_module(self.project, "mod1") pkg1 = testutils.create_package(self.project, "pkg1") pkg2 = testutils.create_package(self.project, "pkg2", pkg1) mod1.write("import pkg1.pkg2.mod2") init_dot_py = pkg2.get_child("__init__.py") found_pyname = get_definition_location( self.project, mod1.read(), mod1.read().index("pkg2") + 1 ) self.assertEqual(init_dot_py, found_pyname[0]) def test_get_definition_location_unknown(self): code = "a_func()\n" result = get_definition_location(self.project, code, len(code) - 3) self.assertEqual((None, None), result) def test_get_definition_location_dot_spaces(self): code = dedent("""\ class AClass(object): @staticmethod def a_method(): pass AClass.\\ a_method()""") result = get_definition_location(self.project, code, len(code) - 3) self.assertEqual((None, 3), result) def test_get_definition_location_dot_line_break_inside_parens(self): code = dedent("""\ class A(object): def a_method(self): pass (A. a_method)""") result = get_definition_location( self.project, code, code.rindex("a_method") + 1 ) self.assertEqual((None, 2), result) def test_if_scopes_in_other_scopes_for_get_definition_location(self): code = dedent("""\ def f(a_var): pass a_var = 10 if True: print(a_var) """) 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 = dedent("""\ def a_func(a_var): pass a_var = 10 a_func(a_""") result = self._assist(code) self.assert_completion_in_result("a_var", "global", result) def test_simple_type_inferencing(self): code = dedent("""\ class Sample(object): def __init__(self, a_param): pass def a_method(self): pass Sample("hey").a_""") result = self._assist(code) self.assert_completion_in_result("a_method", "attribute", result) def test_proposals_sorter(self): code = dedent("""\ def my_sample_function(self): my_sample_var = 20 my_sample_""") proposals = sorted_proposals(self._assist(code)) self.assertEqual("my_sample_var", proposals[0].name) self.assertEqual("my_sample_function", proposals[1].name) def test_proposals_sorter_for_methods_and_attributes(self): code = dedent("""\ class A(object): def __init__(self): self.my_a_var = 10 def my_b_func(self): pass def my_c_func(self): pass a_var = A() a_var.my_""") proposals = sorted_proposals(self._assist(code)) self.assertEqual("my_b_func", proposals[0].name) self.assertEqual("my_c_func", proposals[1].name) self.assertEqual("my_a_var", proposals[2].name) def test_proposals_sorter_for_global_methods_and_funcs(self): code = dedent("""\ def my_b_func(self): pass my_a_var = 10 my_""") proposals = sorted_proposals(self._assist(code)) self.assertEqual("my_b_func", proposals[0].name) self.assertEqual("my_a_var", proposals[1].name) def test_proposals_sorter_underlined_methods(self): code = dedent("""\ class A(object): def _my_func(self): self.my_a_var = 10 def my_func(self): pass a_var = A() a_var.""") proposals = sorted_proposals(self._assist(code)) self.assertEqual("my_func", proposals[0].name) self.assertEqual("_my_func", proposals[1].name) def test_proposals_sorter_and_scope_prefs(self): code = dedent("""\ my_global_var = 1 def func(self): my_local_var = 2 my_""") result = self._assist(code) proposals = sorted_proposals(result, scopepref=["global", "local"]) self.assertEqual("my_global_var", proposals[0].name) self.assertEqual("my_local_var", proposals[1].name) def test_proposals_sorter_and_type_prefs(self): code = dedent("""\ my_global_var = 1 def my_global_func(self): pass my_""") result = self._assist(code) proposals = sorted_proposals(result, typepref=["instance", "function"]) self.assertEqual("my_global_var", proposals[0].name) self.assertEqual("my_global_func", proposals[1].name) def test_proposals_sorter_and_missing_type_in_typepref(self): code = dedent("""\ my_global_var = 1 def my_global_func(): pass my_""") result = self._assist(code) proposals = sorted_proposals(result, typepref=["function"]) # noqa def test_get_pydoc_unicode(self): src = dedent('''\ # coding: utf-8 def foo(): u"юникод-объект"''') doc = get_doc(self.project, src, src.index("foo") + 1) self.assertTrue(isinstance(doc, str)) self.assertTrue("юникод-объект" in doc) def test_get_pydoc_utf8_bytestring(self): src = dedent('''\ # coding: utf-8 def foo(): "байтстринг"''') doc = get_doc(self.project, src, src.index("foo") + 1) self.assertTrue(isinstance(doc, str)) self.assertTrue("байтстринг" in doc) def test_get_pydoc_for_functions(self): src = dedent('''\ def a_func(): """a function""" a_var = 10 a_func()''') self.assertTrue(get_doc(self.project, src, len(src) - 4).endswith("a function")) get_doc(self.project, src, len(src) - 4).index("a_func()") def test_get_pydoc_for_classes(self): src = dedent("""\ class AClass(object): pass """) get_doc(self.project, src, src.index("AClass") + 1).index("AClass") def test_get_pydoc_for_classes_with_init(self): src = dedent("""\ class AClass(object): def __init__(self): pass """) get_doc(self.project, src, src.index("AClass") + 1).index("AClass") def test_get_pydoc_for_modules(self): mod = testutils.create_module(self.project, "mod") mod.write('"""a module"""\n') src = "import mod\nmod" self.assertEqual("a module", get_doc(self.project, src, len(src) - 1)) def test_get_pydoc_for_builtins(self): src = "print(object)\n" self.assertTrue(get_doc(self.project, src, src.index("obj")) is not None) def test_get_pydoc_for_methods_should_include_class_name(self): src = dedent('''\ class AClass(object): def a_method(self): """hey""" pass ''') doc = get_doc(self.project, src, src.index("a_method") + 1) doc.index("AClass.a_method") doc.index("hey") def test_get_pydoc_for_meths_should_inc_methods_from_super_classes(self): src = dedent('''\ class A(object): def a_method(self): """hey1""" pass class B(A): def a_method(self): """hey2""" pass ''') doc = get_doc(self.project, src, src.rindex("a_method") + 1) doc.index("A.a_method") doc.index("hey1") doc.index("B.a_method") doc.index("hey2") def test_get_pydoc_for_classes_should_name_super_classes(self): src = dedent("""\ class A(object): pass class B(A): pass """) doc = get_doc(self.project, src, src.rindex("B") + 1) doc.index("B(A)") def test_get_pydoc_for_builtin_functions(self): src = dedent("""\ s = "hey" s.replace """) doc = get_doc(self.project, src, src.rindex("replace") + 1) self.assertTrue(doc is not None) def test_commenting_errors_before_offset(self): src = dedent("""\ lsjd lsjdf s = "hey" s.replace() """) doc = get_doc(self.project, src, src.rindex("replace") + 1) # noqa def test_proposing_variables_defined_till_the_end_of_scope(self): code = dedent("""\ if True: a_v a_var = 10 """) result = self._assist(code, code.index("a_v") + 3) self.assert_completion_in_result("a_var", "global", result) def test_completing_in_incomplete_try_blocks(self): code = dedent("""\ try: a_var = 10 a_""") result = self._assist(code) self.assert_completion_in_result("a_var", "global", result) def test_completing_in_incomplete_try_blocks_in_functions(self): code = dedent("""\ def a_func(): try: a_var = 10 a_""") result = self._assist(code) self.assert_completion_in_result("a_var", "local", result) def test_already_complete_try_blocks_with_finally(self): code = dedent("""\ def a_func(): try: a_var = 10 a_""") result = self._assist(code) self.assert_completion_in_result("a_var", "local", result) def test_already_complete_try_blocks_with_finally2(self): code = dedent("""\ try: a_var = 10 a_ finally: pass """) result = self._assist(code, code.rindex("a_") + 2) self.assert_completion_in_result("a_var", "global", result) def test_already_complete_try_blocks_with_except(self): code = dedent("""\ try: a_var = 10 a_ except Exception: pass """) result = self._assist(code, code.rindex("a_") + 2) self.assert_completion_in_result("a_var", "global", result) def test_already_complete_try_blocks_with_except2(self): code = dedent("""\ a_var = 10 try: another_var = a_ another_var = 10 except Exception: pass """) result = self._assist(code, code.rindex("a_") + 2) self.assert_completion_in_result("a_var", "global", result) def test_completing_ifs_in_incomplete_try_blocks(self): code = dedent("""\ try: if True: a_var = 10 a_""") result = self._assist(code) self.assert_completion_in_result("a_var", "global", result) def test_completing_ifs_in_incomplete_try_blocks2(self): code = dedent("""\ try: if True: a_var = 10 a_""") result = self._assist(code) self.assert_completion_in_result("a_var", "global", result) def test_completing_excepts_in_incomplete_try_blocks(self): code = dedent("""\ try: pass except Exc""") result = self._assist(code) self.assert_completion_in_result("Exception", "builtin", result) def test_and_normal_complete_blocks_and_single_fixing(self): code = dedent("""\ try: range. except: pass """) result = self._assist(code, code.index("."), maxfixes=1) # noqa def test_nested_blocks(self): code = dedent("""\ a_var = 10 try: try: a_v""") result = self._assist(code) self.assert_completion_in_result("a_var", "global", result) def test_proposing_function_keywords_when_calling(self): code = dedent("""\ def f(p): pass f(p""") result = self._assist(code) self.assert_completion_in_result("p=", "parameter_keyword", result) def test_proposing_function_keywords_when_calling_for_non_functions(self): code = dedent("""\ f = 1 f(p""") result = self._assist(code) # noqa def test_proposing_function_keywords_when_calling_extra_spaces(self): code = dedent("""\ def f(p): pass f( p""") result = self._assist(code) self.assert_completion_in_result("p=", "parameter_keyword", result) def test_proposing_function_keywords_when_calling_on_second_argument(self): code = dedent("""\ def f(p1, p2): pass f(1, p""") result = self._assist(code) self.assert_completion_in_result("p2=", "parameter_keyword", result) def test_proposing_function_keywords_when_calling_not_proposing_args(self): code = dedent("""\ def f(p1, *args): pass f(1, a""") result = self._assist(code) self.assert_completion_not_in_result("args=", "parameter_keyword", result) def test_propos_function_kwrds_when_call_with_no_noth_after_parens(self): code = dedent("""\ def f(p): pass f(""") result = self._assist(code) self.assert_completion_in_result("p=", "parameter_keyword", result) def test_propos_function_kwrds_when_call_with_no_noth_after_parens2(self): code = dedent("""\ def f(p): pass def g(): h = f f(""") result = self._assist(code) self.assert_completion_in_result("p=", "parameter_keyword", result) def test_codeassists_before_opening_of_parens(self): code = dedent("""\ def f(p): pass a_var = 1 f(1) """) result = self._assist(code, code.rindex("f") + 1) self.assert_completion_not_in_result("a_var", "global", result) def test_codeassist_before_single_line_indents(self): code = dedent("""\ myvar = 1 if True: (myv if True: pass """) result = self._assist(code, code.rindex("myv") + 3) self.assert_completion_not_in_result("myvar", "local", result) def test_codeassist_before_line_indents_in_a_blank_line(self): code = dedent("""\ myvar = 1 if True: if True: pass """) result = self._assist(code, code.rindex(" ") + 4) self.assert_completion_not_in_result("myvar", "local", result) def test_simple_get_calltips(self): src = dedent("""\ def f(): pass var = f() """) doc = get_calltip(self.project, src, src.rindex("f")) self.assertEqual("f()", doc) def test_get_calltips_for_classes(self): src = dedent("""\ class C(object): def __init__(self): pass C(""") doc = get_calltip(self.project, src, len(src) - 1) self.assertEqual("C.__init__(self)", doc) def test_get_calltips_for_objects_with_call(self): src = dedent("""\ class C(object): def __call__(self, p): pass c = C() c(1,""") doc = get_calltip(self.project, src, src.rindex("c")) self.assertEqual("C.__call__(self, p)", doc) def test_get_calltips_and_including_module_name(self): src = dedent("""\ class C(object): def __call__(self, p): pass c = C() c(1,""") mod = testutils.create_module(self.project, "mod") mod.write(src) doc = get_calltip(self.project, src, src.rindex("c"), mod) self.assertEqual("mod.C.__call__(self, p)", doc) def test_get_calltips_and_including_module_name_2(self): src = "range()\n" doc = get_calltip(self.project, src, 1, ignore_unknown=True) self.assertTrue(doc is None) def test_removing_self_parameter(self): src = dedent("""\ class C(object): def f(self): pass C().f()""") doc = get_calltip(self.project, src, src.rindex("f"), remove_self=True) self.assertEqual("C.f()", doc) def test_removing_self_parameter_and_more_than_one_parameter(self): src = dedent("""\ class C(object): def f(self, p1): pass C().f()""") doc = get_calltip(self.project, src, src.rindex("f"), remove_self=True) self.assertEqual("C.f(p1)", doc) def test_lambda_calltip(self): src = dedent("""\ foo = lambda x, y=1: None foo()""") doc = get_calltip(self.project, src, src.rindex("f")) self.assertEqual(doc, "lambda(x, y)") def test_keyword_before_parens(self): code = dedent("""\ if (1).: pass""") result = self._assist(code, offset=len("if (1).")) self.assertTrue(result) # TESTING PROPOSAL'S KINDS AND TYPES. # SEE RELATION MATRIX IN `CompletionProposal`'s DOCSTRING def test_local_variable_completion_proposal(self): code = dedent("""\ def foo(): xvar = 5 x""") result = self._assist(code) self.assert_completion_in_result("xvar", "local", result, "instance") def test_global_variable_completion_proposal(self): code = dedent("""\ yvar = 5 y""") result = self._assist(code) self.assert_completion_in_result("yvar", "global", result, "instance") def test_builtin_variable_completion_proposal(self): for varname in ("False", "True"): result = self._assist(varname[0]) self.assert_completion_in_result( varname, "builtin", result, type="instance" ) def test_attribute_variable_completion_proposal(self): code = dedent("""\ class AClass(object): def foo(self): self.bar = 1 self.b""") result = self._assist(code) self.assert_completion_in_result("bar", "attribute", result, type="instance") def test_local_class_completion_proposal(self): code = dedent("""\ def foo(): class LocalClass(object): pass Lo""") result = self._assist(code) self.assert_completion_in_result("LocalClass", "local", result, type="class") def test_global_class_completion_proposal(self): code = dedent("""\ class GlobalClass(object): pass Gl""") result = self._assist(code) self.assert_completion_in_result("GlobalClass", "global", result, type="class") def test_builtin_class_completion_proposal(self): for varname in ("object", "dict"): result = self._assist(varname[0]) self.assert_completion_in_result(varname, "builtin", result, type="class") def test_attribute_class_completion_proposal(self): code = dedent("""\ class Outer(object): class Inner(object): pass Outer.""") result = self._assist(code) self.assert_completion_in_result("Inner", "attribute", result, type="class") def test_local_function_completion_proposal(self): code = dedent("""\ def outer(): def inner(): pass in""") result = self._assist(code) self.assert_completion_in_result("inner", "local", result, type="function") def test_global_function_completion_proposal(self): code = dedent("""\ def foo(): pass f""") result = self._assist(code) self.assert_completion_in_result("foo", "global", result, type="function") def test_builtin_function_completion_proposal(self): code = "a" result = self._assist(code) for expected in ("all", "any", "abs"): self.assert_completion_in_result( expected, "builtin", result, type="function" ) code2 = "o" result = self._assist(code2) for expected in ("open",): self.assert_completion_in_result( expected, "builtin", result, type="function" ) def test_attribute_function_completion_proposal(self): code = dedent("""\ class Some(object): def method(self): self.""") result = self._assist(code) self.assert_completion_in_result("method", "attribute", result, type="function") def test_local_module_completion_proposal(self): code = dedent("""\ def foo(): import types t""") result = self._assist(code) self.assert_completion_in_result("types", "imported", result, type="module") def test_global_module_completion_proposal(self): code = dedent("""\ import operator o""") result = self._assist(code) self.assert_completion_in_result("operator", "imported", result, type="module") def test_attribute_module_completion_proposal(self): code = dedent("""\ class Some(object): import os Some.o""") result = self._assist(code) self.assert_completion_in_result("os", "imported", result, type="module") def test_builtin_exception_completion_proposal(self): code = dedent("""\ def blah(): Z""") result = self._assist(code) self.assert_completion_in_result( "ZeroDivisionError", "builtin", result, type="class" ) def test_keyword_completion_proposal(self): code = "f" result = self._assist(code) self.assert_completion_in_result("for", "keyword", result, type=None) self.assert_completion_in_result("from", "keyword", result, type=None) def test_parameter_keyword_completion_proposal(self): code = dedent("""\ def func(abc, aloha, alpha, amigo): pass func(a""") result = self._assist(code) for expected in ("abc=", "aloha=", "alpha=", "amigo="): self.assert_completion_in_result( expected, "parameter_keyword", result, type=None ) def test_object_path_global(self): code = "GLOBAL_VARIABLE = 42\n" resource = testutils.create_module(self.project, "mod") resource.write(code) result = get_canonical_path(self.project, resource, 1) mod_path = os.path.join(self.project.address, "mod.py") self.assertEqual( result, [(mod_path, "MODULE"), ("GLOBAL_VARIABLE", "VARIABLE")] ) def test_object_path_attribute(self): code = dedent("""\ class Foo(object): attr = 42 """) resource = testutils.create_module(self.project, "mod") resource.write(code) result = get_canonical_path(self.project, resource, 24) mod_path = os.path.join(self.project.address, "mod.py") self.assertEqual( result, [(mod_path, "MODULE"), ("Foo", "CLASS"), ("attr", "VARIABLE")] ) def test_object_path_subclass(self): code = dedent("""\ class Foo(object): class Bar(object): pass """) resource = testutils.create_module(self.project, "mod") resource.write(code) result = get_canonical_path(self.project, resource, 30) mod_path = os.path.join(self.project.address, "mod.py") self.assertEqual( result, [(mod_path, "MODULE"), ("Foo", "CLASS"), ("Bar", "CLASS")] ) def test_object_path_method_parameter(self): code = dedent("""\ class Foo(object): def bar(self, a, b, c): pass """) resource = testutils.create_module(self.project, "mod") resource.write(code) result = get_canonical_path(self.project, resource, 41) mod_path = os.path.join(self.project.address, "mod.py") self.assertEqual( result, [ (mod_path, "MODULE"), ("Foo", "CLASS"), ("bar", "FUNCTION"), ("b", "PARAMETER"), ], ) def test_object_path_variable(self): code = dedent("""\ def bar(a): x = a + 42 """) resource = testutils.create_module(self.project, "mod") resource.write(code) result = get_canonical_path(self.project, resource, 17) mod_path = os.path.join(self.project.address, "mod.py") self.assertEqual( result, [(mod_path, "MODULE"), ("bar", "FUNCTION"), ("x", "VARIABLE")] ) class CodeAssistInProjectsTest(unittest.TestCase): def setUp(self): super().setUp() self.project = testutils.sample_project() self.pycore = self.project.pycore samplemod = testutils.create_module(self.project, "samplemod") code = dedent("""\ class SampleClass(object): def sample_method(): pass def sample_func(): pass sample_var = 10 def _underlined_func(): pass """) samplemod.write(code) package = testutils.create_package(self.project, "package") nestedmod = testutils.create_module(self.project, "nestedmod", package) # noqa def tearDown(self): testutils.remove_project(self.project) super().tearDown() def _assist(self, code, resource=None, **kwds): return code_assist(self.project, code, len(code), resource, **kwds) def assert_completion_in_result(self, name, scope, result): for proposal in result: if proposal.name == name and proposal.scope == scope: return self.fail("completion <%s> not proposed" % name) def assert_completion_not_in_result(self, name, scope, result): for proposal in result: if proposal.name == name and proposal.scope == scope: self.fail("completion <%s> was proposed" % name) def test_simple_import(self): code = dedent("""\ import samplemod sample""") result = self._assist(code) self.assert_completion_in_result("samplemod", "imported", result) def test_from_import_class(self): code = dedent("""\ from samplemod import SampleClass Sample""") result = self._assist(code) self.assert_completion_in_result("SampleClass", "imported", result) def test_from_import_function(self): code = dedent("""\ from samplemod import sample_func sample""") result = self._assist(code) self.assert_completion_in_result("sample_func", "imported", result) def test_from_import_variable(self): code = dedent("""\ from samplemod import sample_var sample""") result = self._assist(code) self.assert_completion_in_result("sample_var", "imported", result) def test_from_imports_inside_functions(self): code = dedent("""\ def f(): from samplemod import SampleClass Sample""") result = self._assist(code) self.assert_completion_in_result("SampleClass", "imported", result) def test_from_import_only_imports_imported(self): code = dedent("""\ from samplemod import sample_func Sample""") result = self._assist(code) self.assert_completion_not_in_result("SampleClass", "global", result) def test_from_import_star(self): code = dedent("""\ from samplemod import * Sample""") result = self._assist(code) self.assert_completion_in_result("SampleClass", "imported", result) def test_from_import_star2(self): code = dedent("""\ from samplemod import * sample""") result = self._assist(code) self.assert_completion_in_result("sample_func", "imported", result) self.assert_completion_in_result("sample_var", "imported", result) def test_from_import_star_not_importing_underlined(self): code = dedent("""\ from samplemod import * _under""") result = self._assist(code) self.assert_completion_not_in_result("_underlined_func", "global", result) def test_from_package_import_mod(self): code = dedent("""\ from package import nestedmod nest""") result = self._assist(code) self.assert_completion_in_result("nestedmod", "imported", result) def test_completing_after_dot(self): code = dedent("""\ class SampleClass(object): def sample_method(self): pass SampleClass.sam""") result = self._assist(code) self.assert_completion_in_result("sample_method", "attribute", result) def test_completing_after_multiple_dots(self): code = dedent("""\ class Class1(object): class Class2(object): def sample_method(self): pass Class1.Class2.sam""") result = self._assist(code) self.assert_completion_in_result("sample_method", "attribute", result) def test_completing_after_self_dot(self): code = dedent("""\ class Sample(object): def method1(self): pass def method2(self): self.m""") result = self._assist(code) self.assert_completion_in_result("method1", "attribute", result) def test_result_start_offset_for_dotted_completions(self): code = dedent("""\ class Sample(object): def method1(self): pass Sample.me""") self.assertEqual(len(code) - 2, starting_offset(code, len(code))) def test_backslash_after_dots(self): code = dedent("""\ class Sample(object): def a_method(self): pass Sample.\\ a_m""") result = self._assist(code) self.assert_completion_in_result("a_method", "attribute", result) def test_not_proposing_global_names_after_dot(self): code = dedent("""\ class Sample(object): def a_method(self): pass Sample.""") result = self._assist(code) self.assert_completion_not_in_result("Sample", "global", result) def test_assist_on_relative_imports(self): pkg = testutils.create_package(self.project, "pkg") mod1 = testutils.create_module(self.project, "mod1", pkg) mod2 = testutils.create_module(self.project, "mod2", pkg) mod1.write(dedent("""\ def a_func(): pass """)) code = dedent("""\ import mod1 mod1.""") result = self._assist(code, resource=mod2) self.assert_completion_in_result("a_func", "imported", result) def test_get_location_on_relative_imports(self): pkg = testutils.create_package(self.project, "pkg") mod1 = testutils.create_module(self.project, "mod1", pkg) mod2 = testutils.create_module(self.project, "mod2", pkg) mod1.write(dedent("""\ def a_func(): pass """)) code = dedent("""\ import mod1 mod1.a_func """) result = get_definition_location(self.project, code, len(code) - 2, mod2) self.assertEqual((mod1, 1), result) def test_get_definition_location_for_builtins(self): code = "import sys\n" result = get_definition_location(self.project, code, len(code) - 2) self.assertEqual((None, None), result) def test_get_doc_on_relative_imports(self): pkg = testutils.create_package(self.project, "pkg") mod1 = testutils.create_module(self.project, "mod1", pkg) mod2 = testutils.create_module(self.project, "mod2", pkg) mod1.write(dedent('''\ def a_func(): """hey""" pass ''')) code = dedent("""\ import mod1 mod1.a_func """) result = get_doc(self.project, code, len(code) - 2, mod2) self.assertTrue(result.endswith("hey")) def test_get_doc_on_from_import_module(self): mod1 = testutils.create_module(self.project, "mod1") mod1.write(dedent('''\ """mod1 docs""" var = 1 ''')) code = "from mod1 import var\n" result = get_doc(self.project, code, code.index("mod1")) result.index("mod1 docs") def test_fixing_errors_with_maxfixes_in_resources(self): mod = testutils.create_module(self.project, "mod") code = dedent("""\ def f(): sldj sldj def g(): ran""") mod.write(code) result = self._assist(code, maxfixes=2, resource=mod) self.assertTrue(len(result) > 0) def test_completing_names_after_from_import(self): mod1 = testutils.create_module(self.project, "mod1") mod2 = testutils.create_module(self.project, "mod2") mod1.write("myvar = None\n") result = self._assist("from mod1 import myva", resource=mod2) self.assertTrue(len(result) > 0) self.assert_completion_in_result("myvar", "global", result) def test_completing_names_after_from_import_and_sorted_proposals(self): mod1 = testutils.create_module(self.project, "mod1") mod2 = testutils.create_module(self.project, "mod2") mod1.write("myvar = None\n") result = self._assist("from mod1 import myva", resource=mod2) result = sorted_proposals(result) self.assertTrue(len(result) > 0) self.assert_completion_in_result("myvar", "global", result) def test_completing_names_after_from_import2(self): mod1 = testutils.create_module(self.project, "mod1") mod2 = testutils.create_module(self.project, "mod2") mod1.write("myvar = None\n") result = self._assist("from mod1 import ", resource=mod2) self.assertTrue(len(result) > 0) self.assert_completion_in_result("myvar", "global", result) def test_starting_expression(self): code = dedent("""\ l = list() l.app""") self.assertEqual("l.app", starting_expression(code, len(code))) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/ropetest/contrib/finderrorstest.py0000664000175000017500000000313514512700666021437 0ustar00lieryanlieryanimport unittest from textwrap import dedent from rope.contrib import finderrors from ropetest import testutils class FindErrorsTest(unittest.TestCase): def setUp(self): super().setUp() self.project = testutils.sample_project() self.mod = self.project.root.create_file("mod.py") def tearDown(self): testutils.remove_project(self.project) super().tearDown() def test_unresolved_variables(self): self.mod.write("print(var)\n") result = finderrors.find_errors(self.project, self.mod) self.assertEqual(1, len(result)) self.assertEqual(1, result[0].lineno) def test_defined_later(self): self.mod.write(dedent("""\ print(var) var = 1 """)) result = finderrors.find_errors(self.project, self.mod) self.assertEqual(1, len(result)) self.assertEqual(1, result[0].lineno) def test_ignoring_builtins(self): self.mod.write("range(2)\n") result = finderrors.find_errors(self.project, self.mod) self.assertEqual(0, len(result)) def test_ignoring_none(self): self.mod.write("var = None\n") result = finderrors.find_errors(self.project, self.mod) self.assertEqual(0, len(result)) def test_bad_attributes(self): code = dedent("""\ class C(object): pass c = C() print(c.var) """) self.mod.write(code) result = finderrors.find_errors(self.project, self.mod) self.assertEqual(1, len(result)) self.assertEqual(4, result[0].lineno) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/ropetest/contrib/findittest.py0000664000175000017500000001254314512700666020542 0ustar00lieryanlieryanimport unittest from textwrap import dedent from rope.base import exceptions from rope.contrib.findit import find_definition, find_implementations, find_occurrences from ropetest import testutils class FindItTest(unittest.TestCase): def setUp(self): super().setUp() self.project = testutils.sample_project() def tearDown(self): testutils.remove_project(self.project) super().tearDown() def test_finding_occurrences(self): mod = testutils.create_module(self.project, "mod") mod.write("a_var = 1\n") result = find_occurrences(self.project, mod, 1) self.assertEqual(mod, result[0].resource) self.assertEqual(0, result[0].offset) self.assertEqual(False, result[0].unsure) def test_finding_occurrences_in_more_than_one_module(self): mod1 = testutils.create_module(self.project, "mod1") mod2 = testutils.create_module(self.project, "mod2") mod1.write("a_var = 1\n") mod2.write(dedent("""\ import mod1 my_var = mod1.a_var""")) result = find_occurrences(self.project, mod1, 1) self.assertEqual(2, len(result)) modules = (result[0].resource, result[1].resource) self.assertTrue(mod1 in modules and mod2 in modules) def test_finding_occurrences_matching_when_unsure(self): mod1 = testutils.create_module(self.project, "mod1") mod1.write(dedent("""\ class C(object): def a_func(self): pass def f(arg): arg.a_func() """)) result = find_occurrences( self.project, mod1, mod1.read().index("a_func"), unsure=True ) self.assertEqual(2, len(result)) def test_find_occurrences_resources_parameter(self): mod1 = testutils.create_module(self.project, "mod1") mod2 = testutils.create_module(self.project, "mod2") mod1.write("a_var = 1\n") mod2.write(dedent("""\ import mod1 my_var = mod1.a_var""")) result = find_occurrences(self.project, mod1, 1, resources=[mod1]) self.assertEqual(1, len(result)) self.assertEqual((mod1, 0), (result[0].resource, result[0].offset)) def test_find_occurrences_and_class_hierarchies(self): mod1 = testutils.create_module(self.project, "mod1") mod1.write(dedent("""\ class A(object): def f(): pass class B(A): def f(): pass """)) offset = mod1.read().rindex("f") result1 = find_occurrences(self.project, mod1, offset) result2 = find_occurrences(self.project, mod1, offset, in_hierarchy=True) self.assertEqual(1, len(result1)) self.assertEqual(2, len(result2)) def test_trivial_find_implementations(self): mod1 = testutils.create_module(self.project, "mod1") mod1.write(dedent("""\ class A(object): def f(self): pass """)) offset = mod1.read().rindex("f(") result = find_implementations(self.project, mod1, offset) self.assertEqual([], result) def test_find_implementations_and_not_returning_parents(self): mod1 = testutils.create_module(self.project, "mod1") mod1.write(dedent("""\ class A(object): def f(self): pass class B(A): def f(self): pass """)) offset = mod1.read().rindex("f(") result = find_implementations(self.project, mod1, offset) self.assertEqual([], result) def test_find_implementations_real_implementation(self): mod1 = testutils.create_module(self.project, "mod1") mod1.write(dedent("""\ class A(object): def f(self): pass class B(A): def f(self): pass """)) offset = mod1.read().index("f(") result = find_implementations(self.project, mod1, offset) self.assertEqual(1, len(result)) self.assertEqual(mod1.read().rindex("f("), result[0].offset) def test_find_implementations_real_implementation_simple(self): mod1 = testutils.create_module(self.project, "mod1") mod1.write("class A(object):\n pass\n") offset = mod1.read().index("A") with self.assertRaises(exceptions.BadIdentifierError): find_implementations(self.project, mod1, offset) def test_trivial_find_definition(self): code = dedent("""\ def a_func(): pass a_func()""") result = find_definition(self.project, code, code.rindex("a_func")) start = code.index("a_func") self.assertEqual(start, result.offset) self.assertEqual(None, result.resource) self.assertEqual(1, result.lineno) self.assertEqual((start, start + len("a_func")), result.region) def test_find_definition_in_other_modules(self): mod1 = testutils.create_module(self.project, "mod1") mod1.write("var = 1\n") code = dedent("""\ import mod1 print(mod1.var) """) result = find_definition(self.project, code, code.index("var")) self.assertEqual(mod1, result.resource) self.assertEqual(0, result.offset) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/ropetest/contrib/fixmodnamestest.py0000664000175000017500000000402214512700666021570 0ustar00lieryanlieryanimport unittest from rope.contrib.fixmodnames import FixModuleNames from rope.contrib.generate import create_module, create_package from ropetest import testutils # HACK: for making this test work on case-insensitive file-systems, it # uses a name.replace('x', '_') fixer. class FixModuleNamesTest(unittest.TestCase): def setUp(self): super().setUp() self.project = testutils.sample_project() def tearDown(self): testutils.remove_project(self.project) super().tearDown() def test_simple_module_renaming(self): mod = create_module(self.project, "xod") self.project.do(FixModuleNames(self.project).get_changes(_fixer)) self.assertFalse(mod.exists()) self.assertTrue(self.project.get_resource("_od.py").exists()) def test_packages_module_renaming(self): pkg = create_package(self.project, "xkg") self.project.do(FixModuleNames(self.project).get_changes(_fixer)) self.assertFalse(pkg.exists()) self.assertTrue(self.project.get_resource("_kg/__init__.py").exists()) def test_fixing_contents(self): mod1 = create_module(self.project, "xod1") mod2 = create_module(self.project, "xod2") mod1.write("import xod2\n") mod2.write("import xod1\n") self.project.do(FixModuleNames(self.project).get_changes(_fixer)) newmod1 = self.project.get_resource("_od1.py") newmod2 = self.project.get_resource("_od2.py") self.assertEqual("import _od2\n", newmod1.read()) self.assertEqual("import _od1\n", newmod2.read()) def test_handling_nested_modules(self): pkg = create_package(self.project, "xkg") mod = create_module(self.project, "xkg.xod") # noqa self.project.do(FixModuleNames(self.project).get_changes(_fixer)) self.assertFalse(pkg.exists()) self.assertTrue(self.project.get_resource("_kg/__init__.py").exists()) self.assertTrue(self.project.get_resource("_kg/_od.py").exists()) def _fixer(name): return name.replace("x", "_") ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/ropetest/contrib/generatetest.py0000664000175000017500000003546514512700666021067 0ustar00lieryanlieryanimport unittest from textwrap import dedent from rope.base import exceptions from rope.contrib import generate from ropetest import testutils class GenerateTest(unittest.TestCase): def setUp(self): super().setUp() self.project = testutils.sample_project() self.pycore = self.project.pycore self.mod = testutils.create_module(self.project, "mod1") self.mod2 = testutils.create_module(self.project, "mod2") self.pkg = testutils.create_package(self.project, "pkg") def tearDown(self): testutils.remove_project(self.project) super().tearDown() def _get_generate(self, offset): return generate.GenerateVariable(self.project, self.mod, offset) def _get_generate_class(self, offset, goal_mod=None): return generate.GenerateClass( self.project, self.mod, offset, goal_resource=goal_mod ) def _get_generate_module(self, offset): return generate.GenerateModule(self.project, self.mod, offset) def _get_generate_package(self, offset): return generate.GeneratePackage(self.project, self.mod, offset) def _get_generate_function(self, offset): return generate.GenerateFunction(self.project, self.mod, offset) def test_getting_location(self): code = "a_var = name\n" self.mod.write(code) generator = self._get_generate(code.index("name")) self.assertEqual((self.mod, 1), generator.get_location()) def test_generating_variable(self): code = dedent("""\ a_var = name """) self.mod.write(code) changes = self._get_generate(code.index("name")).get_changes() self.project.do(changes) self.assertEqual( dedent("""\ name = None a_var = name """), self.mod.read(), ) def test_generating_variable_inserting_before_statement(self): code = dedent("""\ c = 1 c = b """) self.mod.write(code) changes = self._get_generate(code.index("b")).get_changes() self.project.do(changes) self.assertEqual( dedent("""\ c = 1 b = None c = b """), self.mod.read(), ) def test_generating_variable_in_local_scopes(self): code = dedent("""\ def f(): c = 1 c = b """) self.mod.write(code) changes = self._get_generate(code.index("b")).get_changes() self.project.do(changes) self.assertEqual( dedent("""\ def f(): c = 1 b = None c = b """), self.mod.read(), ) def test_generating_variable_in_other_modules(self): code = dedent("""\ import mod2 c = mod2.b """) self.mod.write(code) generator = self._get_generate(code.index("b")) self.project.do(generator.get_changes()) self.assertEqual((self.mod2, 1), generator.get_location()) self.assertEqual("b = None\n", self.mod2.read()) def test_generating_variable_in_classes(self): code = dedent("""\ class C(object): def f(self): pass c = C() a_var = c.attr""") self.mod.write(code) changes = self._get_generate(code.index("attr")).get_changes() self.project.do(changes) self.assertEqual( dedent("""\ class C(object): def f(self): pass attr = None c = C() a_var = c.attr"""), self.mod.read(), ) def test_generating_variable_in_classes_removing_pass(self): code = dedent("""\ class C(object): pass c = C() a_var = c.attr""") self.mod.write(code) changes = self._get_generate(code.index("attr")).get_changes() self.project.do(changes) self.assertEqual( dedent("""\ class C(object): attr = None c = C() a_var = c.attr"""), self.mod.read(), ) def test_generating_variable_in_packages(self): code = "import pkg\na = pkg.a\n" self.mod.write(code) generator = self._get_generate(code.rindex("a")) self.project.do(generator.get_changes()) init = self.pkg.get_child("__init__.py") self.assertEqual((init, 1), generator.get_location()) self.assertEqual("a = None\n", init.read()) def test_generating_classes(self): code = "c = C()\n" self.mod.write(code) changes = self._get_generate_class(code.index("C")).get_changes() self.project.do(changes) self.assertEqual( dedent("""\ class C(object): pass c = C() """), self.mod.read(), ) def test_generating_classes_in_other_module(self): code = "c = C()\n" self.mod.write(code) changes = self._get_generate_class(code.index("C"), self.mod2).get_changes() self.project.do(changes) self.assertEqual( dedent("""\ class C(object): pass """), self.mod2.read(), ) self.assertEqual( dedent("""\ from mod2 import C c = C() """), self.mod.read(), ) def test_generating_modules(self): code = dedent("""\ import pkg pkg.mod """) self.mod.write(code) generator = self._get_generate_module(code.rindex("mod")) self.project.do(generator.get_changes()) mod = self.pkg.get_child("mod.py") self.assertEqual((mod, 1), generator.get_location()) self.assertEqual( dedent("""\ import pkg.mod pkg.mod """), self.mod.read(), ) def test_generating_packages(self): code = dedent("""\ import pkg pkg.pkg2 """) self.mod.write(code) generator = self._get_generate_package(code.rindex("pkg2")) self.project.do(generator.get_changes()) pkg2 = self.pkg.get_child("pkg2") init = pkg2.get_child("__init__.py") self.assertEqual((init, 1), generator.get_location()) self.assertEqual( dedent("""\ import pkg.pkg2 pkg.pkg2 """), self.mod.read(), ) def test_generating_function(self): code = "a_func()\n" self.mod.write(code) changes = self._get_generate_function(code.index("a_func")).get_changes() self.project.do(changes) self.assertEqual( dedent("""\ def a_func(): pass a_func() """), self.mod.read(), ) def test_generating_modules_with_empty_primary(self): code = "mod\n" self.mod.write(code) generator = self._get_generate_module(code.rindex("mod")) self.project.do(generator.get_changes()) mod = self.project.root.get_child("mod.py") self.assertEqual((mod, 1), generator.get_location()) self.assertEqual("import mod\nmod\n", self.mod.read()) def test_generating_variable_already_exists(self): code = dedent("""\ b = 1 c = b """) self.mod.write(code) with self.assertRaises(exceptions.RefactoringError): self._get_generate(code.index("b")).get_changes() def test_generating_variable_primary_cannot_be_determined(self): code = "c = can_not_be_found.b\n" self.mod.write(code) with self.assertRaises(exceptions.RefactoringError): self._get_generate(code.rindex("b")).get_changes() def test_generating_modules_when_already_exists(self): code = "mod2\n" self.mod.write(code) generator = self._get_generate_module(code.rindex("mod")) with self.assertRaises(exceptions.RefactoringError): self.project.do(generator.get_changes()) def test_generating_static_methods(self): code = dedent("""\ class C(object): pass C.a_func() """) self.mod.write(code) changes = self._get_generate_function(code.index("a_func")).get_changes() self.project.do(changes) self.assertEqual( dedent("""\ class C(object): @staticmethod def a_func(): pass C.a_func() """), self.mod.read(), ) def test_generating_methods(self): code = dedent("""\ class C(object): pass c = C() c.a_func() """) self.mod.write(code) changes = self._get_generate_function(code.index("a_func")).get_changes() self.project.do(changes) self.assertEqual( dedent("""\ class C(object): def a_func(self): pass c = C() c.a_func() """), self.mod.read(), ) def test_generating_constructors(self): code = dedent("""\ class C(object): pass c = C() """) self.mod.write(code) changes = self._get_generate_function(code.rindex("C")).get_changes() self.project.do(changes) self.assertEqual( dedent("""\ class C(object): def __init__(self): pass c = C() """), self.mod.read(), ) def test_generating_calls(self): code = dedent("""\ class C(object): pass c = C() c() """) self.mod.write(code) changes = self._get_generate_function(code.rindex("c")).get_changes() self.project.do(changes) self.assertEqual( dedent("""\ class C(object): def __call__(self): pass c = C() c() """), self.mod.read(), ) def test_generating_calls_in_other_modules(self): self.mod2.write(dedent("""\ class C(object): pass """)) code = dedent("""\ import mod2 c = mod2.C() c() """) self.mod.write(code) changes = self._get_generate_function(code.rindex("c")).get_changes() self.project.do(changes) self.assertEqual( dedent("""\ class C(object): def __call__(self): pass """), self.mod2.read(), ) def test_generating_function_handling_arguments(self): code = "a_func(1)\n" self.mod.write(code) changes = self._get_generate_function(code.index("a_func")).get_changes() self.project.do(changes) self.assertEqual( dedent("""\ def a_func(arg0): pass a_func(1) """), self.mod.read(), ) def test_generating_function_handling_keyword_xarguments(self): code = "a_func(p=1)\n" self.mod.write(code) changes = self._get_generate_function(code.index("a_func")).get_changes() self.project.do(changes) self.assertEqual( dedent("""\ def a_func(p): pass a_func(p=1) """), self.mod.read(), ) def test_generating_function_handling_arguments_better_naming(self): code = dedent("""\ a_var = 1 a_func(a_var) """) self.mod.write(code) changes = self._get_generate_function(code.index("a_func")).get_changes() self.project.do(changes) self.assertEqual( dedent("""\ a_var = 1 def a_func(a_var): pass a_func(a_var) """), self.mod.read(), ) def test_generating_variable_in_other_modules2(self): self.mod2.write("\n\n\nprint(1)\n") code = dedent("""\ import mod2 c = mod2.b """) self.mod.write(code) generator = self._get_generate(code.index("b")) self.project.do(generator.get_changes()) self.assertEqual((self.mod2, 5), generator.get_location()) self.assertEqual( dedent("""\ print(1) b = None """), self.mod2.read(), ) def test_generating_function_in_a_suite(self): code = dedent("""\ if True: a_func() """) self.mod.write(code) changes = self._get_generate_function(code.index("a_func")).get_changes() self.project.do(changes) self.assertEqual( dedent("""\ def a_func(): pass if True: a_func() """), self.mod.read(), ) def test_generating_function_in_a_suite_in_a_function(self): code = dedent("""\ def f(): a = 1 if 1: g() """) self.mod.write(code) changes = self._get_generate_function(code.index("g()")).get_changes() self.project.do(changes) self.assertEqual( dedent("""\ def f(): a = 1 def g(): pass if 1: g() """), 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) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/ropetest/doatest.py0000664000175000017500000000551614512700666016372 0ustar00lieryanlieryanimport base64 import hashlib import hmac import multiprocessing try: import cPickle as pickle except ImportError: import pickle import socket import unittest from rope.base.oi import doa def cve_2014_3539_attacker(data_port, payload): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(("127.0.0.1", data_port)) s_file = s.makefile("wb") s_file.write(payload) s.close() class DOATest(unittest.TestCase): def try_CVE_2014_3539_exploit(self, receiver, payload): # Simulated attacker writing to the socket # Assume the attacker guesses the port correctly; 3037 is used by # default if it is available. attacker_proc = multiprocessing.Process( target=cve_2014_3539_attacker, args=(receiver.data_port, payload) ) attacker_proc.start() received_objs = list(receiver.receive_data()) attacker_proc.join() return received_objs def test_CVE_2014_3539_no_encoding(self): # Attacker sends pickled data to the receiver socket. receiver = doa._SocketReceiver() payload = pickle.dumps("def foo():\n return 123\n") received_objs = self.try_CVE_2014_3539_exploit(receiver, payload) # Make sure the exploit did not run self.assertEqual(0, len(received_objs)) def test_CVE_2014_3539_signature_mismatch(self): # Attacker sends well-formed data with an incorrect signature. receiver = doa._SocketReceiver() pickled_data = pickle.dumps( "def foo():\n return 123\n", pickle.HIGHEST_PROTOCOL ) digest = hmac.new(b"invalid-key", pickled_data, hashlib.sha256).digest() payload = ( base64.b64encode(digest) + b":" + base64.b64encode(pickled_data) + b"\n" ) received_objs = self.try_CVE_2014_3539_exploit(receiver, payload) # Make sure the exploit did not run self.assertEqual(0, len(received_objs)) def test_CVE_2014_3539_sanity(self): # Tests that sending valid, signed data on the socket does work. receiver = doa._SocketReceiver() pickled_data = base64.b64encode( pickle.dumps("def foo():\n return 123\n", pickle.HIGHEST_PROTOCOL) ) digest = hmac.new(receiver.key, pickled_data, hashlib.sha256).digest() payload = base64.b64encode(digest) + b":" + pickled_data + b"\n" received_objs = self.try_CVE_2014_3539_exploit(receiver, payload) # Make sure the exploit did not run self.assertEqual(1, len(received_objs)) def test_compare_digest_compat(self): self.assertTrue(doa._compat_compare_digest("", "")) self.assertTrue(doa._compat_compare_digest("abc", "abc")) self.assertFalse(doa._compat_compare_digest("abc", "abd")) self.assertFalse(doa._compat_compare_digest("abc", "abcd")) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/ropetest/historytest.py0000664000175000017500000003705014512700666017326 0ustar00lieryanlieryanimport unittest import rope.base.change # Use fully-qualified names for clarity. import rope.base.history # Use fully-qualified names for clarity. from rope.base import exceptions from ropetest import testutils class HistoryTest(unittest.TestCase): def setUp(self): super().setUp() self.project = testutils.sample_project() self.history = self.project.history def tearDown(self): testutils.remove_project(self.project) super().tearDown() def test_undoing_writes(self): my_file = self.project.root.create_file("my_file.txt") my_file.write("text1") self.history.undo() self.assertEqual("", my_file.read()) def test_moving_files(self): my_file = self.project.root.create_file("my_file.txt") my_file.move("new_file.txt") self.history.undo() self.assertEqual("", my_file.read()) def test_moving_files_to_folders(self): my_file = self.project.root.create_file("my_file.txt") my_folder = self.project.root.create_folder("my_folder") my_file.move(my_folder.path) self.history.undo() self.assertEqual("", my_file.read()) def test_writing_files_that_does_not_change_contents(self): my_file = self.project.root.create_file("my_file.txt") my_file.write("") self.project.history.undo() self.assertFalse(my_file.exists()) class IsolatedHistoryTest(unittest.TestCase): def setUp(self): super().setUp() self.project = testutils.sample_project() self.history = rope.base.history.History(self.project) self.file1 = self.project.root.create_file("file1.txt") self.file2 = self.project.root.create_file("file2.txt") def tearDown(self): testutils.remove_project(self.project) super().tearDown() def test_simple_undo(self): change = rope.base.change.ChangeContents(self.file1, "1") self.history.do(change) self.assertEqual("1", self.file1.read()) self.history.undo() self.assertEqual("", self.file1.read()) def test_tobe_undone(self): change1 = rope.base.change.ChangeContents(self.file1, "1") self.assertEqual(None, self.history.tobe_undone) self.history.do(change1) self.assertEqual(change1, self.history.tobe_undone) change2 = rope.base.change.ChangeContents(self.file1, "2") self.history.do(change2) self.assertEqual(change2, self.history.tobe_undone) self.history.undo() self.assertEqual(change1, self.history.tobe_undone) def test_tobe_redone(self): change = rope.base.change.ChangeContents(self.file1, "1") self.history.do(change) self.assertEqual(None, self.history.tobe_redone) self.history.undo() self.assertEqual(change, self.history.tobe_redone) def test_undo_limit(self): history = rope.base.history.History(self.project, maxundos=1) history.do(rope.base.change.ChangeContents(self.file1, "1")) history.do(rope.base.change.ChangeContents(self.file1, "2")) try: history.undo() with self.assertRaises(exceptions.HistoryError): history.undo() finally: self.assertEqual("1", self.file1.read()) def test_simple_redo(self): change = rope.base.change.ChangeContents(self.file1, "1") self.history.do(change) self.history.undo() self.history.redo() self.assertEqual("1", self.file1.read()) def test_simple_re_undo(self): change = rope.base.change.ChangeContents(self.file1, "1") self.history.do(change) self.history.undo() self.history.redo() self.history.undo() self.assertEqual("", self.file1.read()) def test_multiple_undos(self): change = rope.base.change.ChangeContents(self.file1, "1") self.history.do(change) change = rope.base.change.ChangeContents(self.file1, "2") self.history.do(change) self.history.undo() self.assertEqual("1", self.file1.read()) change = rope.base.change.ChangeContents(self.file1, "3") self.history.do(change) self.history.undo() self.assertEqual("1", self.file1.read()) self.history.redo() self.assertEqual("3", self.file1.read()) def test_undo_list_underflow(self): with self.assertRaises(exceptions.HistoryError): self.history.undo() def test_redo_list_underflow(self): with self.assertRaises(exceptions.HistoryError): self.history.redo() def test_dropping_undone_changes(self): self.file1.write("1") with self.assertRaises(exceptions.HistoryError): self.history.undo(drop=True) self.history.redo() def test_undoing_chosen_changes(self): change = rope.base.change.ChangeContents(self.file1, "1") self.history.do(change) self.history.undo(change) self.assertEqual("", self.file1.read()) self.assertFalse(self.history.undo_list) def test_undoing_chosen_changes2(self): change1 = rope.base.change.ChangeContents(self.file1, "1") self.history.do(change1) self.history.do(rope.base.change.ChangeContents(self.file1, "2")) self.history.undo(change1) self.assertEqual("", self.file1.read()) self.assertFalse(self.history.undo_list) def test_undoing_chosen_changes_not_undoing_others(self): change1 = rope.base.change.ChangeContents(self.file1, "1") self.history.do(change1) self.history.do(rope.base.change.ChangeContents(self.file2, "2")) self.history.undo(change1) self.assertEqual("", self.file1.read()) self.assertEqual("2", self.file2.read()) def test_undoing_writing_after_moving(self): change1 = rope.base.change.ChangeContents(self.file1, "1") self.history.do(change1) self.history.do(rope.base.change.MoveResource(self.file1, "file3.txt")) file3 = self.project.get_resource("file3.txt") self.history.undo(change1) self.assertEqual("", self.file1.read()) self.assertFalse(file3.exists()) def test_undoing_folder_movements_for_undoing_writes_inside_it(self): folder = self.project.root.create_folder("folder") file3 = folder.create_file("file3.txt") change1 = rope.base.change.ChangeContents(file3, "1") self.history.do(change1) self.history.do(rope.base.change.MoveResource(folder, "new_folder")) new_folder = self.project.get_resource("new_folder") self.history.undo(change1) self.assertEqual("", file3.read()) self.assertFalse(new_folder.exists()) def test_undoing_changes_that_depend_on_a_dependant_change(self): change1 = rope.base.change.ChangeContents(self.file1, "1") self.history.do(change1) changes = rope.base.change.ChangeSet("2nd change") changes.add_change(rope.base.change.ChangeContents(self.file1, "2")) changes.add_change(rope.base.change.ChangeContents(self.file2, "2")) self.history.do(changes) self.history.do(rope.base.change.MoveResource(self.file2, "file3.txt")) file3 = self.project.get_resource("file3.txt") self.history.undo(change1) self.assertEqual("", self.file1.read()) self.assertEqual("", self.file2.read()) self.assertFalse(file3.exists()) def test_undoing_writes_for_undoing_folder_movements_containing_it(self): folder = self.project.root.create_folder("folder") old_file = folder.create_file("file3.txt") change1 = rope.base.change.MoveResource(folder, "new_folder") self.history.do(change1) new_file = self.project.get_resource("new_folder/file3.txt") self.history.do(rope.base.change.ChangeContents(new_file, "1")) self.history.undo(change1) self.assertEqual("", old_file.read()) self.assertFalse(new_file.exists()) def test_undoing_not_available_change(self): change = rope.base.change.ChangeContents(self.file1, "1") with self.assertRaises(exceptions.HistoryError): self.history.undo(change) def test_ignoring_ignored_resources(self): self.project.set("ignored_resources", ["ignored*"]) ignored = self.project.get_file("ignored.txt") change = rope.base.change.CreateResource(ignored) self.history.do(change) self.assertTrue(ignored.exists()) self.assertEqual(0, len(self.history.undo_list)) def test_get_file_undo_list_simple(self): change = rope.base.change.ChangeContents(self.file1, "1") self.history.do(change) self.assertEqual({change}, set(self.history.get_file_undo_list(self.file1))) def test_get_file_undo_list_for_moves(self): change = rope.base.change.MoveResource(self.file1, "file2.txt") self.history.do(change) self.assertEqual({change}, set(self.history.get_file_undo_list(self.file1))) # XXX: What happens for moves before the file is created? def xxx_test_get_file_undo_list_and_moving_its_containing_folder(self): folder = self.project.root.create_folder("folder") old_file = folder.create_file("file3.txt") change1 = rope.base.change.MoveResource(folder, "new_folder") self.history.do(change1) self.assertEqual({change1}, set(self.history.get_file_undo_list(old_file))) def test_clearing_redo_list_after_do(self): change = rope.base.change.ChangeContents(self.file1, "1") self.history.do(change) self.history.undo() self.history.do(change) self.assertEqual(0, len(self.history.redo_list)) def test_undoing_a_not_yet_performed_change(self): change = rope.base.change.ChangeContents(self.file1, "1") str(change) with self.assertRaises(exceptions.HistoryError): change.undo() def test_clearing_up_the_history(self): change1 = rope.base.change.ChangeContents(self.file1, "1") change2 = rope.base.change.ChangeContents(self.file1, "2") self.history.do(change1) self.history.do(change2) self.history.undo() self.history.clear() self.assertEqual(0, len(self.history.undo_list)) self.assertEqual(0, len(self.history.redo_list)) def test_redoing_chosen_changes_not_undoing_others(self): change1 = rope.base.change.ChangeContents(self.file1, "1") change2 = rope.base.change.ChangeContents(self.file2, "2") self.history.do(change1) self.history.do(change2) self.history.undo() self.history.undo() redone = self.history.redo(change2) self.assertEqual([change2], redone) self.assertEqual("", self.file1.read()) self.assertEqual("2", self.file2.read()) class SavingHistoryTest(unittest.TestCase): def setUp(self): super().setUp() self.project = testutils.sample_project() self.history = rope.base.history.History(self.project) self.to_data = rope.base.change.ChangeToData() self.to_change = rope.base.change.DataToChange(self.project) def tearDown(self): testutils.remove_project(self.project) super().tearDown() def test_simple_set_saving(self): data = self.to_data(rope.base.change.ChangeSet("testing")) change = self.to_change(data) self.assertEqual("testing", str(change)) def test_simple_change_content_saving(self): myfile = self.project.get_file("myfile.txt") myfile.create() myfile.write("1") data = self.to_data(rope.base.change.ChangeContents(myfile, "2")) change = self.to_change(data) self.history.do(change) self.assertEqual("2", myfile.read()) self.history.undo() self.assertEqual("1", change.old_contents) def test_move_resource_saving(self): myfile = self.project.root.create_file("myfile.txt") myfolder = self.project.root.create_folder("myfolder") data = self.to_data(rope.base.change.MoveResource(myfile, "myfolder")) change = self.to_change(data) self.history.do(change) self.assertFalse(myfile.exists()) self.assertTrue(myfolder.has_child("myfile.txt")) self.history.undo() self.assertTrue(myfile.exists()) self.assertFalse(myfolder.has_child("myfile.txt")) def test_move_resource_saving_for_folders(self): myfolder = self.project.root.create_folder("myfolder") newfolder = self.project.get_folder("newfolder") change = rope.base.change.MoveResource(myfolder, "newfolder") self.history.do(change) data = self.to_data(change) change = self.to_change(data) change.undo() self.assertTrue(myfolder.exists()) self.assertFalse(newfolder.exists()) def test_create_file_saving(self): myfile = self.project.get_file("myfile.txt") data = self.to_data( rope.base.change.CreateFile(self.project.root, "myfile.txt") ) change = self.to_change(data) self.history.do(change) self.assertTrue(myfile.exists()) self.history.undo() self.assertFalse(myfile.exists()) def test_create_folder_saving(self): myfolder = self.project.get_folder("myfolder") data = self.to_data( rope.base.change.CreateFolder(self.project.root, "myfolder") ) change = self.to_change(data) self.history.do(change) self.assertTrue(myfolder.exists()) self.history.undo() self.assertFalse(myfolder.exists()) def test_create_resource_saving(self): myfile = self.project.get_file("myfile.txt") data = self.to_data(rope.base.change.CreateResource(myfile)) change = self.to_change(data) self.history.do(change) self.assertTrue(myfile.exists()) self.history.undo() self.assertFalse(myfile.exists()) def test_remove_resource_saving(self): myfile = self.project.root.create_file("myfile.txt") data = self.to_data(rope.base.change.RemoveResource(myfile)) change = self.to_change(data) self.history.do(change) self.assertFalse(myfile.exists()) def test_change_set_saving(self): change = rope.base.change.ChangeSet("testing") myfile = self.project.get_file("myfile.txt") change.add_change(rope.base.change.CreateResource(myfile)) change.add_change(rope.base.change.ChangeContents(myfile, "1")) data = self.to_data(change) change = self.to_change(data) self.history.do(change) self.assertEqual("1", myfile.read()) self.history.undo() self.assertFalse(myfile.exists()) def test_writing_and_reading_history(self): history_file = self.project.get_file("history.pickle") # noqa self.project.set("save_history", True) history = rope.base.history.History(self.project) myfile = self.project.get_file("myfile.txt") history.do(rope.base.change.CreateResource(myfile)) history.write() history = rope.base.history.History(self.project) history.undo() self.assertFalse(myfile.exists()) def test_writing_and_reading_history2(self): history_file = self.project.get_file("history.pickle") # noqa self.project.set("save_history", True) history = rope.base.history.History(self.project) myfile = self.project.get_file("myfile.txt") history.do(rope.base.change.CreateResource(myfile)) history.undo() history.write() history = rope.base.history.History(self.project) history.redo() self.assertTrue(myfile.exists()) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/ropetest/objectdbtest.py0000664000175000017500000001533714512700666017405 0ustar00lieryanlieryanimport unittest from rope.base.oi import memorydb, objectdb from ropetest import testutils def _do_for_all_dbs(function): def called(self): for db in self.dbs: function(self, db) return called class _MockValidation: def is_value_valid(self, value): return value != -1 def is_more_valid(self, new, old): return new != -1 def is_file_valid(self, path): return path != "invalid" def is_scope_valid(self, path, key): return path != "invalid" and key != "invalid" class _MockFileListObserver: log = "" def added(self, path): self.log += "added %s " % path def removed(self, path): self.log += "removed %s " % path class ObjectDBTest(unittest.TestCase): def setUp(self): super().setUp() self.project = testutils.sample_project() validation = _MockValidation() self.dbs = [objectdb.ObjectDB(memorydb.MemoryDB(self.project), validation)] def tearDown(self): for db in self.dbs: db.write() testutils.remove_project(self.project) super().tearDown() @_do_for_all_dbs def test_simple_per_name(self, db): db.add_pername("file", "key", "name", 1) self.assertEqual(1, db.get_pername("file", "key", "name")) @_do_for_all_dbs def test_simple_per_name_does_not_exist(self, db): self.assertEqual(None, db.get_pername("file", "key", "name")) @_do_for_all_dbs def test_simple_per_name_after_syncing(self, db): db.add_pername("file", "key", "name", 1) db.write() self.assertEqual(1, db.get_pername("file", "key", "name")) @_do_for_all_dbs def test_getting_returned(self, db): db.add_callinfo("file", "key", (1, 2), 3) self.assertEqual(3, db.get_returned("file", "key", (1, 2))) @_do_for_all_dbs def test_getting_returned_when_does_not_match(self, db): db.add_callinfo("file", "key", (1, 2), 3) self.assertEqual(None, db.get_returned("file", "key", (1, 1))) @_do_for_all_dbs def test_getting_call_info(self, db): db.add_callinfo("file", "key", (1, 2), 3) call_infos = list(db.get_callinfos("file", "key")) self.assertEqual(1, len(call_infos)) self.assertEqual((1, 2), call_infos[0].get_parameters()) self.assertEqual(3, call_infos[0].get_returned()) @_do_for_all_dbs def test_invalid_per_name(self, db): db.add_pername("file", "key", "name", -1) self.assertEqual(None, db.get_pername("file", "key", "name")) @_do_for_all_dbs def test_overwriting_per_name(self, db): db.add_pername("file", "key", "name", 1) db.add_pername("file", "key", "name", 2) self.assertEqual(2, db.get_pername("file", "key", "name")) @_do_for_all_dbs def test_not_overwriting_with_invalid_per_name(self, db): db.add_pername("file", "key", "name", 1) db.add_pername("file", "key", "name", -1) self.assertEqual(1, db.get_pername("file", "key", "name")) @_do_for_all_dbs def test_getting_invalid_returned(self, db): db.add_callinfo("file", "key", (1, 2), -1) self.assertEqual(None, db.get_returned("file", "key", (1, 2))) @_do_for_all_dbs def test_not_overwriting_with_invalid_returned(self, db): db.add_callinfo("file", "key", (1, 2), 3) db.add_callinfo("file", "key", (1, 2), -1) self.assertEqual(3, db.get_returned("file", "key", (1, 2))) @_do_for_all_dbs def test_get_files(self, db): db.add_callinfo("file1", "key", (1, 2), 3) db.add_callinfo("file2", "key", (1, 2), 3) self.assertEqual({"file1", "file2"}, set(db.get_files())) @_do_for_all_dbs def test_validating_files(self, db): db.add_callinfo("invalid", "key", (1, 2), 3) db.validate_files() self.assertEqual(0, len(db.get_files())) @_do_for_all_dbs def test_validating_file_for_scopes(self, db): db.add_callinfo("file", "invalid", (1, 2), 3) db.validate_file("file") self.assertEqual(1, len(db.get_files())) self.assertEqual(0, len(list(db.get_callinfos("file", "invalid")))) @_do_for_all_dbs def test_validating_file_moved(self, db): db.add_callinfo("file", "key", (1, 2), 3) db.file_moved("file", "newfile") self.assertEqual(1, len(db.get_files())) self.assertEqual(1, len(list(db.get_callinfos("newfile", "key")))) @_do_for_all_dbs def test_using_file_list_observer(self, db): db.add_callinfo("invalid", "key", (1, 2), 3) observer = _MockFileListObserver() db.add_file_list_observer(observer) db.validate_files() self.assertEqual("removed invalid ", observer.log) @_do_for_all_dbs def test_legacy_serialization(self, db): import pickle db.add_callinfo("file", "key", (1, 2), 3) db.add_pername("file", "key", "name", 1) scope_info = db._get_scope_info("file", "key") pickled_data = b'\x80\x04\x95D\x00\x00\x00\x00\x00\x00\x00\x8c\x15rope.base.oi.memorydb\x94\x8c\tScopeInfo\x94\x93\x94)\x81\x94}\x94K\x01K\x02\x86\x94K\x03s}\x94\x8c\x04name\x94K\x01s\x86\x94b.' # noqa assert pickle.loads(pickled_data).call_info == scope_info.call_info assert pickle.loads(pickled_data).per_name == scope_info.per_name @_do_for_all_dbs def test_new_pickle_serialization(self, db): import pickle db.add_callinfo("file", "key", (1, 2), 3) db.add_pername("file", "key", "name", 1) scope_info = db._get_scope_info("file", "key") serialized = pickle.dumps(scope_info) rehydrated_data = pickle.loads(serialized) assert rehydrated_data.call_info == scope_info.call_info assert rehydrated_data.per_name == scope_info.per_name @_do_for_all_dbs def test_new_json_serialization(self, db): import json from rope.base.oi.memorydb import ScopeInfo db.add_callinfo("file", "key", (1, 2), 3) db.add_pername("file", "key", "name", 1) scope_info = db._get_scope_info("file", "key") data = {"inside": [scope_info], "other": scope_info, "things": [1, 2, 3]} def object_hook(o): if o.get("$") == "ScopeInfo": new_o = ScopeInfo.__new__(ScopeInfo) new_o.__setstate__(o) return new_o return o serialized = json.dumps(data, default=lambda o: o.__getstate__()) rehydrated_data = json.loads(serialized, object_hook=object_hook) rehydrated_scope_info = rehydrated_data["inside"][0] assert isinstance(rehydrated_scope_info, ScopeInfo) assert rehydrated_scope_info.call_info == scope_info.call_info assert rehydrated_scope_info.per_name == scope_info.per_name ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/ropetest/objectinfertest.py0000664000175000017500000003650114512700666020117 0ustar00lieryanlieryanimport unittest from textwrap import dedent import rope.base.builtins # Use fully-qualified names for clarity. from rope.base import libutils from ropetest import testutils class ObjectInferTest(unittest.TestCase): def setUp(self): super().setUp() self.project = testutils.sample_project() def tearDown(self): testutils.remove_project(self.project) super().tearDown() def test_simple_type_inferencing(self): code = dedent("""\ class Sample(object): pass a_var = Sample() """) scope = libutils.get_string_scope(self.project, code) sample_class = scope["Sample"].get_object() a_var = scope["a_var"].get_object() self.assertEqual(sample_class, a_var.get_type()) def test_simple_type_inferencing_classes_defined_in_holding_scope(self): code = dedent("""\ class Sample(object): pass def a_func(): a_var = Sample() """) scope = libutils.get_string_scope(self.project, code) sample_class = scope["Sample"].get_object() a_var = scope["a_func"].get_object().get_scope()["a_var"].get_object() self.assertEqual(sample_class, a_var.get_type()) def test_simple_type_inferencing_classes_in_class_methods(self): code = dedent("""\ class Sample(object): pass class Another(object): def a_method(): a_var = Sample() """) scope = libutils.get_string_scope(self.project, code) sample_class = scope["Sample"].get_object() another_class = scope["Another"].get_object() a_var = another_class["a_method"].get_object().get_scope()["a_var"].get_object() self.assertEqual(sample_class, a_var.get_type()) def test_simple_type_inferencing_class_attributes(self): code = dedent("""\ class Sample(object): pass class Another(object): def __init__(self): self.a_var = Sample() """) scope = libutils.get_string_scope(self.project, code) sample_class = scope["Sample"].get_object() another_class = scope["Another"].get_object() a_var = another_class["a_var"].get_object() self.assertEqual(sample_class, a_var.get_type()) def test_simple_type_inferencing_for_in_class_assignments(self): code = dedent("""\ class Sample(object): pass class Another(object): an_attr = Sample() """) scope = libutils.get_string_scope(self.project, code) sample_class = scope["Sample"].get_object() another_class = scope["Another"].get_object() an_attr = another_class["an_attr"].get_object() self.assertEqual(sample_class, an_attr.get_type()) def test_simple_type_inferencing_for_chained_assignments(self): mod = dedent("""\ class Sample(object): pass copied_sample = Sample""") mod_scope = libutils.get_string_scope(self.project, mod) sample_class = mod_scope["Sample"] copied_sample = mod_scope["copied_sample"] self.assertEqual(sample_class.get_object(), copied_sample.get_object()) def test_following_chained_assignments_avoiding_circles(self): mod = dedent("""\ class Sample(object): pass sample_class = Sample sample_class = sample_class """) mod_scope = libutils.get_string_scope(self.project, mod) sample_class = mod_scope["Sample"] sample_class_var = mod_scope["sample_class"] self.assertEqual(sample_class.get_object(), sample_class_var.get_object()) def test_function_returned_object_static_type_inference1(self): src = dedent("""\ class Sample(object): pass def a_func(): return Sample a_var = a_func() """) scope = libutils.get_string_scope(self.project, src) sample_class = scope["Sample"] a_var = scope["a_var"] self.assertEqual(sample_class.get_object(), a_var.get_object()) def test_function_returned_object_static_type_inference2(self): src = dedent("""\ class Sample(object): pass def a_func(): return Sample() a_var = a_func() """) scope = libutils.get_string_scope(self.project, src) sample_class = scope["Sample"].get_object() a_var = scope["a_var"].get_object() self.assertEqual(sample_class, a_var.get_type()) def test_recursive_function_returned_object_static_type_inference(self): src = dedent("""\ class Sample(object): pass def a_func(): if True: return Sample() else: return a_func() a_var = a_func() """) scope = libutils.get_string_scope(self.project, src) sample_class = scope["Sample"].get_object() a_var = scope["a_var"].get_object() self.assertEqual(sample_class, a_var.get_type()) def test_func_returned_obj_using_call_spec_func_static_type_infer(self): src = dedent("""\ class Sample(object): def __call__(self): return Sample sample = Sample() a_var = sample()""") scope = libutils.get_string_scope(self.project, src) sample_class = scope["Sample"] a_var = scope["a_var"] self.assertEqual(sample_class.get_object(), a_var.get_object()) def test_list_type_inferencing(self): src = dedent("""\ class Sample(object): pass a_var = [Sample()] """) scope = libutils.get_string_scope(self.project, src) sample_class = scope["Sample"].get_object() a_var = scope["a_var"].get_object() self.assertNotEqual(sample_class, a_var.get_type()) def test_attributed_object_inference(self): src = dedent("""\ class Sample(object): def __init__(self): self.a_var = None def set(self): self.a_var = Sample() """) scope = libutils.get_string_scope(self.project, src) sample_class = scope["Sample"].get_object() a_var = sample_class["a_var"].get_object() self.assertEqual(sample_class, a_var.get_type()) def test_getting_property_attributes(self): src = dedent("""\ class A(object): pass def f(*args): return A() class B(object): p = property(f) a_var = B().p """) pymod = libutils.get_string_module(self.project, src) a_class = pymod["A"].get_object() a_var = pymod["a_var"].get_object() self.assertEqual(a_class, a_var.get_type()) def test_getting_property_attributes_with_method_getters(self): src = dedent("""\ class A(object): pass class B(object): def p_get(self): return A() p = property(p_get) a_var = B().p """) pymod = libutils.get_string_module(self.project, src) a_class = pymod["A"].get_object() a_var = pymod["a_var"].get_object() self.assertEqual(a_class, a_var.get_type()) def test_lambda_functions(self): code = dedent("""\ class C(object): pass l = lambda: C() a_var = l()""") mod = libutils.get_string_module(self.project, code) c_class = mod["C"].get_object() a_var = mod["a_var"].get_object() self.assertEqual(c_class, a_var.get_type()) def test_mixing_subscript_with_tuple_assigns(self): code = dedent("""\ class C(object): attr = 0 d = {} d[0], b = (0, C()) """) mod = libutils.get_string_module(self.project, code) c_class = mod["C"].get_object() a_var = mod["b"].get_object() self.assertEqual(c_class, a_var.get_type()) def test_mixing_ass_attr_with_tuple_assignment(self): code = dedent("""\ class C(object): attr = 0 c = C() c.attr, b = (0, C()) """) mod = libutils.get_string_module(self.project, code) c_class = mod["C"].get_object() a_var = mod["b"].get_object() self.assertEqual(c_class, a_var.get_type()) def test_mixing_slice_with_tuple_assigns(self): code = dedent("""\ class C(object): attr = 0 d = [None] * 3 d[0:2], b = ((0,), C()) """) mod = libutils.get_string_module(self.project, code) c_class = mod["C"].get_object() a_var = mod["b"].get_object() self.assertEqual(c_class, a_var.get_type()) def test_nested_tuple_assignments(self): code = dedent("""\ class C1(object): pass class C2(object): pass a, (b, c) = (C1(), (C2(), C1())) """) mod = libutils.get_string_module(self.project, code) c1_class = mod["C1"].get_object() c2_class = mod["C2"].get_object() a_var = mod["a"].get_object() b_var = mod["b"].get_object() c_var = mod["c"].get_object() self.assertEqual(c1_class, a_var.get_type()) self.assertEqual(c2_class, b_var.get_type()) self.assertEqual(c1_class, c_var.get_type()) def test_empty_tuples(self): code = dedent("""\ t = () a, b = t """) mod = libutils.get_string_module(self.project, code) a = mod["a"].get_object() # noqa def test_handling_generator_functions(self): code = dedent("""\ class C(object): pass def f(): yield C() for c in f(): a_var = c """) mod = libutils.get_string_module(self.project, code) c_class = mod["C"].get_object() a_var = mod["a_var"].get_object() self.assertEqual(c_class, a_var.get_type()) def test_handling_generator_functions_for_strs(self): mod = testutils.create_module(self.project, "mod") mod.write(dedent("""\ def f(): yield "" for s in f(): a_var = s """)) pymod = self.project.get_pymodule(mod) a_var = pymod["a_var"].get_object() self.assertTrue(isinstance(a_var.get_type(), rope.base.builtins.Str)) def test_considering_nones_to_be_unknowns(self): code = dedent("""\ class C(object): pass a_var = None a_var = C() a_var = None """) mod = libutils.get_string_module(self.project, code) c_class = mod["C"].get_object() a_var = mod["a_var"].get_object() self.assertEqual(c_class, a_var.get_type()) def test_basic_list_comprehensions(self): code = dedent("""\ class C(object): pass l = [C() for i in range(1)] a_var = l[0] """) mod = libutils.get_string_module(self.project, code) c_class = mod["C"].get_object() a_var = mod["a_var"].get_object() self.assertEqual(c_class, a_var.get_type()) def test_basic_generator_expressions(self): code = dedent("""\ class C(object): pass l = (C() for i in range(1)) a_var = list(l)[0] """) mod = libutils.get_string_module(self.project, code) c_class = mod["C"].get_object() a_var = mod["a_var"].get_object() self.assertEqual(c_class, a_var.get_type()) def test_list_comprehensions_and_loop_var(self): code = dedent("""\ class C(object): pass c_objects = [C(), C()] l = [c for c in c_objects] a_var = l[0] """) mod = libutils.get_string_module(self.project, code) c_class = mod["C"].get_object() a_var = mod["a_var"].get_object() self.assertEqual(c_class, a_var.get_type()) def test_list_comprehensions_and_multiple_loop_var(self): code = dedent("""\ class C1(object): pass class C2(object): pass l = [(c1, c2) for c1 in [C1()] for c2 in [C2()]] a, b = l[0] """) mod = libutils.get_string_module(self.project, code) c1_class = mod["C1"].get_object() c2_class = mod["C2"].get_object() a_var = mod["a"].get_object() b_var = mod["b"].get_object() self.assertEqual(c1_class, a_var.get_type()) self.assertEqual(c2_class, b_var.get_type()) def test_list_comprehensions_and_multiple_iters(self): code = dedent("""\ class C1(object): pass class C2(object): pass l = [(c1, c2) for c1, c2 in [(C1(), C2())]] a, b = l[0] """) mod = libutils.get_string_module(self.project, code) c1_class = mod["C1"].get_object() c2_class = mod["C2"].get_object() a_var = mod["a"].get_object() b_var = mod["b"].get_object() self.assertEqual(c1_class, a_var.get_type()) self.assertEqual(c2_class, b_var.get_type()) def test_we_know_the_type_of_caught_exceptions(self): code = dedent("""\ class MyError(Exception): pass try: raise MyError() except MyError as e: pass """) mod = libutils.get_string_module(self.project, code) my_error = mod["MyError"].get_object() e_var = mod["e"].get_object() self.assertEqual(my_error, e_var.get_type()) def test_we_know_the_type_of_caught_multiple_excepts(self): code = dedent("""\ class MyError(Exception): pass try: raise MyError() except (MyError, Exception) as e: pass """) mod = libutils.get_string_module(self.project, code) my_error = mod["MyError"].get_object() e_var = mod["e"].get_object() self.assertEqual(my_error, e_var.get_type()) def test_using_property_as_decorators(self): code = dedent("""\ class A(object): pass class B(object): @property def f(self): return A() b = B() var = b.f """) mod = libutils.get_string_module(self.project, code) var = mod["var"].get_object() a = mod["A"].get_object() self.assertEqual(a, var.get_type()) def test_using_property_as_decorators_and_passing_parameter(self): code = dedent("""\ class B(object): @property def f(self): return self b = B() var = b.f """) mod = libutils.get_string_module(self.project, code) var = mod["var"].get_object() a = mod["B"].get_object() self.assertEqual(a, var.get_type()) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1705551350.0 rope-1.12.0/ropetest/projecttest.py0000664000175000017500000013663714552122766017311 0ustar00lieryanlieryanimport os.path import pathlib import tempfile import unittest from textwrap import dedent from rope.base.exceptions import ResourceNotFoundError, RopeError from rope.base.fscommands import FileSystemCommands from rope.base.libutils import path_to_resource from rope.base.project import NoProject, Project, _realpath from rope.base.resourceobserver import FilteredResourceObserver from rope.base.resources import File, Folder from ropetest import testutils class ProjectTest(unittest.TestCase): def setUp(self): unittest.TestCase.setUp(self) self.project = testutils.sample_project( foldername="sampleproject", ropefolder=None ) self.project_root = self.project.address self._make_sample_project() self.no_project = NoProject() def _make_sample_project(self): self.sample_file = "sample_file.txt" self.sample_path = os.path.join(self.project_root, "sample_file.txt") if not os.path.exists(self.project_root): os.mkdir(self.project_root) self.sample_folder = "sample_folder" os.mkdir(os.path.join(self.project_root, self.sample_folder)) sample = open(self.sample_path, "w") sample.write("sample text\n") sample.close() def tearDown(self): testutils.remove_project(self.project) unittest.TestCase.tearDown(self) def test_project_creation(self): self.assertEqual(_realpath(self.project_root), self.project.address) def test_getting_project_file(self): project_file = self.project.get_resource(self.sample_file) self.assertTrue(project_file is not None) def test_project_file_reading(self): projectFile = self.project.get_resource(self.sample_file) self.assertEqual("sample text\n", projectFile.read()) def test_getting_not_existing_project_file(self): with self.assertRaises(ResourceNotFoundError): self.project.get_resource("DoesNotExistFile.txt") def test_writing_in_project_files(self): project_file = self.project.get_resource(self.sample_file) project_file.write("another text\n") self.assertEqual("another text\n", project_file.read()) def test_creating_files(self): project_file = "newfile.txt" self.project.root.create_file(project_file) newFile = self.project.get_resource(project_file) self.assertTrue(newFile is not None) def test_creating_files_that_already_exist(self): with self.assertRaises(RopeError): self.project.root.create_file(self.sample_file) def test_making_root_folder_if_it_does_not_exist(self): with tempfile.TemporaryDirectory(dir=testutils.RUN_TMP_DIR) as tmpdir: project_root = pathlib.Path(tmpdir) / "sampleproject2" assert not project_root.exists() project = Project(project_root) assert project_root.exists() assert project_root.is_dir() def test_failure_when_project_root_exists_and_is_a_file(self): with tempfile.TemporaryDirectory(dir=testutils.RUN_TMP_DIR) as tmpdir: project_root = pathlib.Path(tmpdir) / "sampleproject2" project_root.touch() assert project_root.exists() and project_root.is_file() with self.assertRaises(RopeError): Project(project_root) def test_creating_folders(self): folderName = "SampleFolder" self.project.root.create_folder(folderName) folderPath = os.path.join(self.project.address, folderName) self.assertTrue(os.path.exists(folderPath) and os.path.isdir(folderPath)) def test_making_folder_that_already_exists(self): folderName = "SampleFolder" with self.assertRaises(RopeError): self.project.root.create_folder(folderName) self.project.root.create_folder(folderName) def test_failing_if_creating_folder_while_file_already_exists(self): folderName = "SampleFolder" with self.assertRaises(RopeError): self.project.root.create_file(folderName) self.project.root.create_folder(folderName) def test_creating_file_inside_folder(self): folder_name = "sampleFolder" file_name = "sample2.txt" file_path = folder_name + "/" + file_name parent_folder = self.project.root.create_folder(folder_name) parent_folder.create_file(file_name) file = self.project.get_resource(file_path) file.write("sample notes") self.assertEqual(file_path, file.path) self.assertEqual( "sample notes", open(os.path.join(self.project.address, file_path)).read() ) def test_failing_when_creating_file_inside_non_existent_folder(self): with self.assertRaises(ResourceNotFoundError): self.project.root.create_file("NonexistentFolder/SomeFile.txt") def test_nested_directories(self): folder_name = "SampleFolder" parent = self.project.root.create_folder(folder_name) parent.create_folder(folder_name) folder_path = os.path.join(self.project.address, folder_name, folder_name) self.assertTrue(os.path.exists(folder_path) and os.path.isdir(folder_path)) def test_removing_files(self): self.assertTrue(os.path.exists(self.sample_path)) self.project.get_resource(self.sample_file).remove() self.assertFalse(os.path.exists(self.sample_path)) def test_removing_files_invalidating_in_project_resource_pool(self): root_folder = self.project.root my_file = root_folder.create_file("my_file.txt") my_file.remove() self.assertFalse(root_folder.has_child("my_file.txt")) def test_removing_directories(self): self.assertTrue( os.path.exists(os.path.join(self.project.address, self.sample_folder)) ) self.project.get_resource(self.sample_folder).remove() self.assertFalse( os.path.exists(os.path.join(self.project.address, self.sample_folder)) ) def test_removing_non_existent_files(self): with self.assertRaises(ResourceNotFoundError): self.project.get_resource("NonExistentFile.txt").remove() def test_removing_nested_files(self): file_name = self.sample_folder + "/sample_file.txt" self.project.root.create_file(file_name) self.project.get_resource(file_name).remove() self.assertTrue( os.path.exists(os.path.join(self.project.address, self.sample_folder)) ) self.assertTrue( not os.path.exists(os.path.join(self.project.address, file_name)) ) def test_file_get_name(self): file = self.project.get_resource(self.sample_file) self.assertEqual(self.sample_file, file.name) file_name = "nestedFile.txt" parent = self.project.get_resource(self.sample_folder) filePath = self.sample_folder + "/" + file_name parent.create_file(file_name) nestedFile = self.project.get_resource(filePath) self.assertEqual(file_name, nestedFile.name) def test_folder_get_name(self): folder = self.project.get_resource(self.sample_folder) self.assertEqual(self.sample_folder, folder.name) def test_file_get_path(self): file = self.project.get_resource(self.sample_file) self.assertEqual(self.sample_file, file.path) fileName = "nestedFile.txt" parent = self.project.get_resource(self.sample_folder) filePath = self.sample_folder + "/" + fileName parent.create_file(fileName) nestedFile = self.project.get_resource(filePath) self.assertEqual(filePath, nestedFile.path) def test_folder_get_path(self): folder = self.project.get_resource(self.sample_folder) self.assertEqual(self.sample_folder, folder.path) def test_is_folder(self): self.assertTrue(self.project.get_resource(self.sample_folder).is_folder()) self.assertTrue(not self.project.get_resource(self.sample_file).is_folder()) def testget_children(self): children = self.project.get_resource(self.sample_folder).get_children() self.assertEqual([], children) def test_nonempty_get_children(self): file_name = "nestedfile.txt" filePath = self.sample_folder + "/" + file_name parent = self.project.get_resource(self.sample_folder) parent.create_file(file_name) children = parent.get_children() self.assertEqual(1, len(children)) self.assertEqual(filePath, children[0].path) def test_nonempty_get_children2(self): file_name = "nestedfile.txt" folder_name = "nestedfolder.txt" filePath = self.sample_folder + "/" + file_name folderPath = self.sample_folder + "/" + folder_name parent = self.project.get_resource(self.sample_folder) parent.create_file(file_name) parent.create_folder(folder_name) children = parent.get_children() self.assertEqual(2, len(children)) self.assertTrue(filePath == children[0].path or filePath == children[1].path) self.assertTrue( folderPath == children[0].path or folderPath == children[1].path ) def test_does_not_fail_for_permission_denied(self): with tempfile.TemporaryDirectory(suffix="bad_dir") as bad_dir: os.chmod(bad_dir, 0o000) try: parent = self.project.get_resource(self.sample_folder) parent.get_children() finally: os.chmod(bad_dir, 0o755) def test_getting_files(self): files = self.project.root.get_files() self.assertEqual(1, len(files)) self.assertTrue(self.project.get_resource(self.sample_file) in files) def test_getting_folders(self): folders = self.project.root.get_folders() self.assertEqual(1, len(folders)) self.assertTrue(self.project.get_resource(self.sample_folder) in folders) def test_nested_folder_get_files(self): parent = self.project.root.create_folder("top") parent.create_file("file1.txt") parent.create_file("file2.txt") files = parent.get_files() self.assertEqual(2, len(files)) self.assertTrue(self.project.get_resource("top/file2.txt") in files) self.assertEqual(0, len(parent.get_folders())) def test_nested_folder_get_folders(self): parent = self.project.root.create_folder("top") parent.create_folder("dir1") parent.create_folder("dir2") folders = parent.get_folders() self.assertEqual(2, len(folders)) self.assertTrue(self.project.get_resource("top/dir1") in folders) self.assertEqual(0, len(parent.get_files())) def test_root_folder(self): root_folder = self.project.root self.assertEqual(2, len(root_folder.get_children())) self.assertEqual("", root_folder.path) self.assertEqual("", root_folder.name) def test_get_all_files(self): files = tuple(self.project.get_files()) self.assertEqual(1, len(files)) self.assertEqual(self.sample_file, files[0].name) def test_get_all_files_after_changing(self): self.assertEqual(1, len(self.project.get_files())) myfile = self.project.root.create_file("myfile.txt") self.assertEqual(2, len(self.project.get_files())) myfile.move("newfile.txt") self.assertEqual(2, len(self.project.get_files())) self.project.get_file("newfile.txt").remove() self.assertEqual(1, len(self.project.get_files())) def test_multifile_get_all_files(self): fileName = "nestedFile.txt" parent = self.project.get_resource(self.sample_folder) parent.create_file(fileName) files = list(self.project.get_files()) self.assertEqual(2, len(files)) self.assertTrue(fileName == files[0].name or fileName == files[1].name) def test_ignoring_dot_pyc_files_in_get_files(self): root = self.project.address src_folder = os.path.join(root, "src") os.mkdir(src_folder) test_pyc = os.path.join(src_folder, "test.pyc") open(test_pyc, "w").close() for x in self.project.get_files(): self.assertNotEqual("src/test.pyc", x.path) def test_folder_creating_files(self): projectFile = "NewFile.txt" self.project.root.create_file(projectFile) new_file = self.project.get_resource(projectFile) self.assertTrue(new_file is not None and not new_file.is_folder()) def test_folder_creating_nested_files(self): project_file = "NewFile.txt" parent_folder = self.project.get_resource(self.sample_folder) parent_folder.create_file(project_file) new_file = self.project.get_resource(self.sample_folder + "/" + project_file) self.assertTrue(new_file is not None and not new_file.is_folder()) def test_folder_creating_files2(self): projectFile = "newfolder" self.project.root.create_folder(projectFile) new_folder = self.project.get_resource(projectFile) self.assertTrue(new_folder is not None and new_folder.is_folder()) def test_folder_creating_nested_files2(self): project_file = "newfolder" parent_folder = self.project.get_resource(self.sample_folder) parent_folder.create_folder(project_file) new_folder = self.project.get_resource(self.sample_folder + "/" + project_file) self.assertTrue(new_folder is not None and new_folder.is_folder()) def test_folder_get_child(self): folder = self.project.root folder.create_file("myfile.txt") folder.create_folder("myfolder") self.assertEqual( self.project.get_resource("myfile.txt"), folder.get_child("myfile.txt") ) self.assertEqual( self.project.get_resource("myfolder"), folder.get_child("myfolder") ) def test_folder_get_child_nested(self): root = self.project.root folder = root.create_folder("myfolder") folder.create_file("myfile.txt") folder.create_folder("myfolder") self.assertEqual( self.project.get_resource("myfolder/myfile.txt"), folder.get_child("myfile.txt"), ) self.assertEqual( self.project.get_resource("myfolder/myfolder"), folder.get_child("myfolder") ) def test_project_root_is_root_folder(self): self.assertEqual("", self.project.root.path) def test_moving_files(self): root_folder = self.project.root my_file = root_folder.create_file("my_file.txt") my_file.move("my_other_file.txt") self.assertFalse(my_file.exists()) root_folder.get_child("my_other_file.txt") def test_moving_folders(self): root_folder = self.project.root my_folder = root_folder.create_folder("my_folder") my_file = my_folder.create_file("my_file.txt") my_folder.move("new_folder") self.assertFalse(root_folder.has_child("my_folder")) self.assertFalse(my_file.exists()) self.assertTrue(root_folder.get_child("new_folder") is not None) def test_moving_destination_folders(self): root_folder = self.project.root my_folder = root_folder.create_folder("my_folder") my_file = root_folder.create_file("my_file.txt") my_file.move("my_folder") self.assertFalse(root_folder.has_child("my_file.txt")) self.assertFalse(my_file.exists()) my_folder.get_child("my_file.txt") def test_moving_files_and_resource_objects(self): root_folder = self.project.root my_file = root_folder.create_file("my_file.txt") old_hash = hash(my_file) my_file.move("my_other_file.txt") self.assertEqual(old_hash, hash(my_file)) def test_file_encoding_reading(self): sample_file = self.project.root.create_file("my_file.txt") contents = ( b"# -*- coding: utf-8 -*-\n" + br"#\N{LATIN SMALL LETTER I WITH DIAERESIS}\n" ).decode("utf8") file = open(sample_file.real_path, "wb") file.write(contents.encode("utf-8")) file.close() self.assertEqual(contents, sample_file.read()) def test_file_encoding_writing(self): sample_file = self.project.root.create_file("my_file.txt") contents = ( b"# -*- coding: utf-8 -*-\n" + br"\N{LATIN SMALL LETTER I WITH DIAERESIS}\n" ).decode("utf8") sample_file.write(contents) self.assertEqual(contents, sample_file.read()) def test_using_utf8_when_writing_in_case_of_errors(self): sample_file = self.project.root.create_file("my_file.txt") contents = br"\n\N{LATIN SMALL LETTER I WITH DIAERESIS}\n".decode("utf8") sample_file.write(contents) self.assertEqual(contents, sample_file.read()) def test_encoding_declaration_in_the_second_line(self): sample_file = self.project.root.create_file("my_file.txt") contents = b"\n# -*- coding: latin-1 -*-\n\xa9\n" file = open(sample_file.real_path, "wb") file.write(contents) file.close() self.assertEqual(contents, sample_file.read().encode("latin-1")) def test_not_an_encoding_declaration(self): sample_file = self.project.root.create_file("my_file.txt") contents = b"def my_method(self, encoding='latin-1'):\n var = {}\n\xc2\xa9\n" file = open(sample_file.real_path, "wb") file.write(contents) file.close() self.assertEqual(contents, sample_file.read().encode("utf-8")) self.assertNotEqual(contents, sample_file.read().encode("latin-1")) def test_read_bytes(self): sample_file = self.project.root.create_file("my_file.txt") contents = b"\n# -*- coding: latin-1 -*-\n\xa9\n" file = open(sample_file.real_path, "wb") file.write(contents) file.close() self.assertEqual(contents, sample_file.read_bytes()) def test_file_with_unix_line_ending(self): sample_file = self.project.root.create_file("my_file.txt") contents = b"1\n" file = open(sample_file.real_path, "wb") file.write(contents) file.close() self.assertIsNone(sample_file.newlines) self.assertEqual("1\n", sample_file.read()) self.assertEqual("\n", sample_file.newlines) sample_file.write("1\n") self.assertEqual(b"1\n", sample_file.read_bytes()) def test_file_with_dos_line_ending(self): sample_file = self.project.root.create_file("my_file.txt") contents = b"1\r\n" file = open(sample_file.real_path, "wb") file.write(contents) file.close() self.assertIsNone(sample_file.newlines) self.assertEqual("1\n", sample_file.read()) self.assertEqual("\r\n", sample_file.newlines) sample_file.write("1\n") self.assertEqual(b"1\r\n", sample_file.read_bytes()) def test_file_with_mac_line_ending(self): sample_file = self.project.root.create_file("my_file.txt") contents = b"1\r" file = open(sample_file.real_path, "wb") file.write(contents) file.close() self.assertIsNone(sample_file.newlines) self.assertEqual("1\n", sample_file.read()) self.assertEqual("\r", sample_file.newlines) sample_file.write("1\n") self.assertEqual(b"1\r", sample_file.read_bytes()) def test_file_binary(self): sample_file = self.project.root.create_file("my_file.txt") contents = b"1\r\n" file = open(sample_file.real_path, "wb") file.write(contents) file.close() self.assertIsNone(sample_file.newlines) self.assertEqual(b"1\r\n", sample_file.read_bytes()) self.assertIsNone(sample_file.newlines) sample_file.write(b"1\nx\r") self.assertEqual((b"1\nx\r"), sample_file.read_bytes()) # TODO: Detecting utf-16 encoding def xxx_test_using_utf16(self): sample_file = self.project.root.create_file("my_file.txt") contents = b"# -*- coding: utf-16 -*-\n# This is a sample file ...\n" file = open(sample_file.real_path, "w") file.write(contents.encode("utf-16")) file.close() sample_file.write(contents) self.assertEqual(contents, sample_file.read()) # XXX: supporting utf_8_sig def xxx_test_file_encoding_reading_for_notepad_styles(self): sample_file = self.project.root.create_file("my_file.txt") contents = "#\N{LATIN SMALL LETTER I WITH DIAERESIS}\n" file = open(sample_file.real_path, "w") # file.write('\xef\xbb\xbf') file.write(contents.encode("utf-8-sig")) file.close() self.assertEqual(contents, sample_file.read()) def test_using_project_get_file(self): myfile = self.project.get_file(self.sample_file) self.assertTrue(myfile.exists()) def test_using_file_create(self): myfile = self.project.get_file("myfile.txt") self.assertFalse(myfile.exists()) myfile.create() self.assertTrue(myfile.exists()) self.assertFalse(myfile.is_folder()) def test_using_folder_create(self): myfolder = self.project.get_folder("myfolder") self.assertFalse(myfolder.exists()) myfolder.create() self.assertTrue(myfolder.exists()) self.assertTrue(myfolder.is_folder()) def test_exception_when_creating_twice(self): with self.assertRaises(RopeError): myfile = self.project.get_file("myfile.txt") myfile.create() myfile.create() def test_exception_when_parent_does_not_exist(self): with self.assertRaises(ResourceNotFoundError): myfile = self.project.get_file("myfolder/myfile.txt") myfile.create() def test_simple_path_to_resource(self): myfile = self.project.root.create_file("myfile.txt") self.assertEqual(myfile, path_to_resource(self.project, myfile.real_path)) self.assertEqual( myfile, path_to_resource(self.project, myfile.real_path, type="file") ) myfolder = self.project.root.create_folder("myfolder") self.assertEqual(myfolder, path_to_resource(self.project, myfolder.real_path)) self.assertEqual( myfolder, path_to_resource(self.project, myfolder.real_path, type="folder") ) @testutils.skipNotPOSIX() def test_ignoring_symlinks_inside_project(self): project2 = testutils.sample_project(folder_name="sampleproject2") mod = project2.root.create_file("mod.py") try: path = os.path.join(self.project.address, "linkedfile.txt") os.symlink(mod.real_path, path) files = self.project.root.get_files() self.assertEqual(1, len(files)) finally: testutils.remove_project(project2) def test_getting_empty_source_folders(self): self.assertEqual([], self.project.get_source_folders()) def test_root_source_folder(self): self.project.root.create_file("sample.py") source_folders = self.project.get_source_folders() self.assertEqual(1, len(source_folders)) self.assertTrue(self.project.root in source_folders) def test_root_source_folder2(self): self.project.root.create_file("mod1.py") self.project.root.create_file("mod2.py") source_folders = self.project.get_source_folders() self.assertEqual(1, len(source_folders)) self.assertTrue(self.project.root in source_folders) def test_src_source_folder(self): src = self.project.root.create_folder("src") src.create_file("sample.py") source_folders = self.project.get_source_folders() self.assertEqual(1, len(source_folders)) self.assertTrue(self.project.get_resource("src") in source_folders) def test_packages(self): src = self.project.root.create_folder("src") pkg = src.create_folder("package") pkg.create_file("__init__.py") source_folders = self.project.get_source_folders() self.assertEqual(1, len(source_folders)) self.assertTrue(src in source_folders) def test_multi_source_folders(self): src = self.project.root.create_folder("src") package = src.create_folder("package") package.create_file("__init__.py") test = self.project.root.create_folder("test") test.create_file("alltests.py") source_folders = self.project.get_source_folders() self.assertEqual(2, len(source_folders)) self.assertTrue(src in source_folders) self.assertTrue(test in source_folders) def test_multi_source_folders2(self): testutils.create_module(self.project, "mod1") src = self.project.root.create_folder("src") package = testutils.create_package(self.project, "package", src) testutils.create_module(self.project, "mod2", package) source_folders = self.project.get_source_folders() self.assertEqual(2, len(source_folders)) self.assertTrue(self.project.root in source_folders and src in source_folders) def test_folder_is_pathlike(self): resource = self.project.root.create_folder("src") self.assertIsInstance(resource, Folder) self.assertIsInstance(os.fspath(resource), str) def test_file_is_pathlike(self): resource = self.project.root.create_file("mod.py") self.assertIsInstance(resource, File) self.assertIsInstance(os.fspath(resource), str) class ResourceObserverTest(unittest.TestCase): def setUp(self): super().setUp() self.project = testutils.sample_project() def tearDown(self): testutils.remove_project(self.project) super().tearDown() def test_resource_change_observer(self): sample_file = self.project.root.create_file("my_file.txt") sample_file.write("a sample file version 1") sample_observer = _SampleObserver() self.project.add_observer(sample_observer) sample_file.write("a sample file version 2") self.assertEqual(1, sample_observer.change_count) self.assertEqual(sample_file, sample_observer.last_changed) def test_resource_change_observer_after_removal(self): sample_file = self.project.root.create_file("my_file.txt") sample_file.write("text") sample_observer = _SampleObserver() self.project.add_observer( FilteredResourceObserver(sample_observer, [sample_file]) ) sample_file.remove() self.assertEqual(1, sample_observer.change_count) self.assertEqual(sample_file, sample_observer.last_removed) def test_resource_change_observer2(self): sample_file = self.project.root.create_file("my_file.txt") sample_observer = _SampleObserver() self.project.add_observer(sample_observer) self.project.remove_observer(sample_observer) sample_file.write("a sample file version 2") self.assertEqual(0, sample_observer.change_count) def test_resource_change_observer_for_folders(self): root_folder = self.project.root my_folder = root_folder.create_folder("my_folder") my_folder_observer = _SampleObserver() root_folder_observer = _SampleObserver() self.project.add_observer( FilteredResourceObserver(my_folder_observer, [my_folder]) ) self.project.add_observer( FilteredResourceObserver(root_folder_observer, [root_folder]) ) my_file = my_folder.create_file("my_file.txt") self.assertEqual(1, my_folder_observer.change_count) my_file.move("another_file.txt") self.assertEqual(2, my_folder_observer.change_count) self.assertEqual(1, root_folder_observer.change_count) self.project.get_resource("another_file.txt").remove() self.assertEqual(2, my_folder_observer.change_count) self.assertEqual(2, root_folder_observer.change_count) def test_resource_change_observer_after_moving(self): sample_file = self.project.root.create_file("my_file.txt") sample_observer = _SampleObserver() self.project.add_observer(sample_observer) sample_file.move("new_file.txt") self.assertEqual(1, sample_observer.change_count) self.assertEqual( (sample_file, self.project.get_resource("new_file.txt")), sample_observer.last_moved, ) def test_revalidating_files(self): root = self.project.root my_file = root.create_file("my_file.txt") sample_observer = _SampleObserver() self.project.add_observer(FilteredResourceObserver(sample_observer, [my_file])) os.remove(my_file.real_path) self.project.validate(root) self.assertEqual(my_file, sample_observer.last_removed) self.assertEqual(1, sample_observer.change_count) def test_revalidating_files_and_no_changes2(self): root = self.project.root my_file = root.create_file("my_file.txt") sample_observer = _SampleObserver() self.project.add_observer(FilteredResourceObserver(sample_observer, [my_file])) self.project.validate(root) self.assertEqual(None, sample_observer.last_moved) self.assertEqual(0, sample_observer.change_count) def test_revalidating_folders(self): root = self.project.root my_folder = root.create_folder("myfolder") my_file = my_folder.create_file("myfile.txt") # noqa sample_observer = _SampleObserver() self.project.add_observer( FilteredResourceObserver(sample_observer, [my_folder]) ) testutils.remove_recursively(my_folder.real_path) self.project.validate(root) self.assertEqual(my_folder, sample_observer.last_removed) self.assertEqual(1, sample_observer.change_count) def test_removing_and_adding_resources_to_filtered_observer(self): my_file = self.project.root.create_file("my_file.txt") sample_observer = _SampleObserver() filtered_observer = FilteredResourceObserver(sample_observer) self.project.add_observer(filtered_observer) my_file.write("1") self.assertEqual(0, sample_observer.change_count) filtered_observer.add_resource(my_file) my_file.write("2") self.assertEqual(1, sample_observer.change_count) filtered_observer.remove_resource(my_file) my_file.write("3") self.assertEqual(1, sample_observer.change_count) def test_validation_and_changing_files(self): my_file = self.project.root.create_file("my_file.txt") sample_observer = _SampleObserver() timekeeper = _MockChangeIndicator() filtered_observer = FilteredResourceObserver( sample_observer, [my_file], timekeeper=timekeeper ) self.project.add_observer(filtered_observer) self._write_file(my_file.real_path) timekeeper.set_indicator(my_file, 1) self.project.validate(self.project.root) self.assertEqual(1, sample_observer.change_count) def test_validation_and_changing_files2(self): my_file = self.project.root.create_file("my_file.txt") sample_observer = _SampleObserver() timekeeper = _MockChangeIndicator() self.project.add_observer( FilteredResourceObserver(sample_observer, [my_file], timekeeper=timekeeper) ) timekeeper.set_indicator(my_file, 1) my_file.write("hey") self.assertEqual(1, sample_observer.change_count) self.project.validate(self.project.root) self.assertEqual(1, sample_observer.change_count) def test_not_reporting_multiple_changes_to_folders(self): root = self.project.root file1 = root.create_file("file1.txt") file2 = root.create_file("file2.txt") sample_observer = _SampleObserver() self.project.add_observer( FilteredResourceObserver(sample_observer, [root, file1, file2]) ) os.remove(file1.real_path) os.remove(file2.real_path) self.assertEqual(0, sample_observer.change_count) self.project.validate(self.project.root) self.assertEqual(3, sample_observer.change_count) def _write_file(self, path): my_file = open(path, "w") my_file.write("\n") my_file.close() def test_moving_and_being_interested_about_a_folder_and_a_child(self): my_folder = self.project.root.create_folder("my_folder") my_file = my_folder.create_file("my_file.txt") sample_observer = _SampleObserver() filtered_observer = FilteredResourceObserver( sample_observer, [my_folder, my_file] ) self.project.add_observer(filtered_observer) my_folder.move("new_folder") self.assertEqual(2, sample_observer.change_count) def test_contains_for_folders(self): folder1 = self.project.root.create_folder("folder") folder2 = self.project.root.create_folder("folder2") self.assertFalse(folder1.contains(folder2)) def test_validating_when_created(self): root = self.project.root my_file = self.project.get_file("my_file.txt") sample_observer = _SampleObserver() self.project.add_observer(FilteredResourceObserver(sample_observer, [my_file])) open(my_file.real_path, "w").close() self.project.validate(root) self.assertEqual(my_file, sample_observer.last_created) self.assertEqual(1, sample_observer.change_count) def test_validating_twice_when_created(self): root = self.project.root my_file = self.project.get_file("my_file.txt") sample_observer = _SampleObserver() self.project.add_observer(FilteredResourceObserver(sample_observer, [my_file])) open(my_file.real_path, "w").close() self.project.validate(root) self.project.validate(root) self.assertEqual(my_file, sample_observer.last_created) self.assertEqual(1, sample_observer.change_count) def test_changes_and_adding_resources(self): root = self.project.root # noqa file1 = self.project.get_file("file1.txt") file2 = self.project.get_file("file2.txt") file1.create() sample_observer = _SampleObserver() self.project.add_observer( FilteredResourceObserver(sample_observer, [file1, file2]) ) file1.move(file2.path) self.assertEqual(2, sample_observer.change_count) self.assertEqual(file2, sample_observer.last_created) self.assertEqual((file1, file2), sample_observer.last_moved) def test_validating_get_files_list(self): root = self.project.root # noqa self.assertEqual(0, len(self.project.get_files())) file = open(os.path.join(self.project.address, "myfile.txt"), "w") file.close() self.project.validate() self.assertEqual(1, len(self.project.get_files())) def test_clear_observed_resources_for_filtered_observers(self): sample_file = self.project.root.create_file("myfile.txt") sample_observer = _SampleObserver() filtered = FilteredResourceObserver(sample_observer) self.project.add_observer(filtered) filtered.add_resource(sample_file) filtered.clear_resources() sample_file.write("1") self.assertEqual(0, sample_observer.change_count) class _MockChangeIndicator: def __init__(self): self.times = {} def set_indicator(self, resource, time): self.times[resource] = time def get_indicator(self, resource): return self.times.get(resource, 0) class _SampleObserver: def __init__(self): self.change_count = 0 self.last_changed = None self.last_moved = None self.last_created = None self.last_removed = None def resource_changed(self, resource): self.last_changed = resource self.change_count += 1 def resource_moved(self, resource, new_resource): self.last_moved = (resource, new_resource) self.change_count += 1 def resource_created(self, resource): self.last_created = resource self.change_count += 1 def resource_removed(self, resource): self.last_removed = resource self.change_count += 1 class OutOfProjectTest(unittest.TestCase): def setUp(self): super().setUp() self.test_directory = "temp_test_directory" testutils.remove_recursively(self.test_directory) os.mkdir(self.test_directory) self.project = testutils.sample_project() self.no_project = NoProject() def tearDown(self): testutils.remove_project(self.project) testutils.remove_recursively(self.test_directory) super().tearDown() def test_simple_out_of_project_file(self): sample_file_path = os.path.join(self.test_directory, "sample.txt") sample_file = open(sample_file_path, "w") sample_file.write("sample content\n") sample_file.close() sample_resource = self.no_project.get_resource(sample_file_path) self.assertEqual("sample content\n", sample_resource.read()) def test_simple_out_of_project_folder(self): sample_folder_path = os.path.join(self.test_directory, "sample_folder") os.mkdir(sample_folder_path) sample_folder = self.no_project.get_resource(sample_folder_path) self.assertEqual([], sample_folder.get_children()) sample_file_path = os.path.join(sample_folder_path, "sample.txt") open(sample_file_path, "w").close() sample_resource = self.no_project.get_resource(sample_file_path) self.assertEqual(sample_resource, sample_folder.get_children()[0]) def test_using_absolute_path(self): sample_file_path = os.path.join(self.test_directory, "sample.txt") open(sample_file_path, "w").close() normal_sample_resource = self.no_project.get_resource(sample_file_path) absolute_sample_resource = self.no_project.get_resource( os.path.abspath(sample_file_path) ) self.assertEqual(normal_sample_resource, absolute_sample_resource) def test_folder_get_child(self): sample_folder_path = os.path.join(self.test_directory, "sample_folder") os.mkdir(sample_folder_path) sample_folder = self.no_project.get_resource(sample_folder_path) self.assertEqual([], sample_folder.get_children()) sample_file_path = os.path.join(sample_folder_path, "sample.txt") open(sample_file_path, "w").close() sample_resource = self.no_project.get_resource(sample_file_path) self.assertTrue(sample_folder.has_child("sample.txt")) self.assertFalse(sample_folder.has_child("doesnothave.txt")) self.assertEqual(sample_resource, sample_folder.get_child("sample.txt")) def test_out_of_project_files_and_path_to_resource(self): sample_file_path = os.path.join(self.test_directory, "sample.txt") sample_file = open(sample_file_path, "w") sample_file.write("sample content\n") sample_file.close() sample_resource = self.no_project.get_resource(sample_file_path) self.assertEqual( sample_resource, path_to_resource(self.project, sample_file_path) ) class _MockFSCommands: def __init__(self): self.log = "" self.fscommands = FileSystemCommands() def create_file(self, path): self.log += "create_file " self.fscommands.create_file(path) def create_folder(self, path): self.log += "create_folder " self.fscommands.create_folder(path) def move(self, path, new_location): self.log += "move " self.fscommands.move(path, new_location) def remove(self, path): self.log += "remove " self.fscommands.remove(path) def read(self, path): self.log += "read " return self.fscommands.read(path) class _DeprecatedFSCommands: def __init__(self): self.log = "" self.fscommands = FileSystemCommands() def create_file(self, path): self.log += "create_file " self.fscommands.create_file(path) def create_folder(self, path): self.log += "create_folder " self.fscommands.create_folder(path) def move(self, path, new_location): self.log += "move " self.fscommands.move(path, new_location) def remove(self, path): self.log += "remove " self.fscommands.remove(path) class RopeFolderTest(unittest.TestCase): def setUp(self): super().setUp() self.project = None def tearDown(self): if self.project: testutils.remove_project(self.project) super().tearDown() def test_none_project_rope_folder(self): self.project = testutils.sample_project(ropefolder=None) self.assertTrue(self.project.ropefolder is None) def test_getting_project_rope_folder(self): self.project = testutils.sample_project() self.assertTrue(self.project.ropefolder.exists()) self.assertEqual(".ropeproject", self.project.ropefolder.path) def test_setting_ignored_resources(self): self.project = testutils.sample_project(ignored_resources=["myfile.txt"]) myfile = self.project.get_file("myfile.txt") file2 = self.project.get_file("file2.txt") self.assertTrue(self.project.is_ignored(myfile)) self.assertFalse(self.project.is_ignored(file2)) def test_ignored_folders(self): self.project = testutils.sample_project(ignored_resources=["myfolder"]) myfolder = self.project.root.create_folder("myfolder") self.assertTrue(self.project.is_ignored(myfolder)) myfile = myfolder.create_file("myfile.txt") self.assertTrue(self.project.is_ignored(myfile)) def test_ignored_resources_and_get_files(self): self.project = testutils.sample_project( ignored_resources=["myfile.txt"], ropefolder=None ) myfile = self.project.get_file("myfile.txt") self.assertEqual(0, len(self.project.get_files())) myfile.create() self.assertEqual(0, len(self.project.get_files())) def test_ignored_resources_and_get_files2(self): self.project = testutils.sample_project( ignored_resources=["myfile.txt"], ropefolder=None ) myfile = self.project.root.create_file("myfile.txt") # noqa self.assertEqual(0, len(self.project.get_files())) def test_setting_ignored_resources_patterns(self): self.project = testutils.sample_project(ignored_resources=["m?file.*"]) myfile = self.project.get_file("myfile.txt") file2 = self.project.get_file("file2.txt") self.assertTrue(self.project.is_ignored(myfile)) self.assertFalse(self.project.is_ignored(file2)) def test_star_should_not_include_slashes(self): self.project = testutils.sample_project(ignored_resources=["f*.txt"]) folder = self.project.root.create_folder("folder") file1 = folder.create_file("myfile.txt") file2 = folder.create_file("file2.txt") self.assertFalse(self.project.is_ignored(file1)) self.assertTrue(self.project.is_ignored(file2)) def test_normal_fscommands(self): fscommands = _MockFSCommands() self.project = testutils.sample_project(fscommands=fscommands) myfile = self.project.get_file("myfile.txt") myfile.create() self.assertEqual("create_file ", fscommands.log) def test_fscommands_and_ignored_resources(self): fscommands = _MockFSCommands() self.project = testutils.sample_project( fscommands=fscommands, ignored_resources=["myfile.txt"], ropefolder=None ) myfile = self.project.get_file("myfile.txt") myfile.create() self.assertEqual("", fscommands.log) def test_deprecated_fscommands(self): fscommands = _DeprecatedFSCommands() self.project = testutils.sample_project(fscommands=fscommands) myfile = self.project.get_file("myfile.txt") myfile.create() self.assertEqual("create_file ", fscommands.log) def test_ignored_resources_and_prefixes(self): self.project = testutils.sample_project(ignored_resources=[".hg"]) myfile = self.project.root.create_file(".hgignore") self.assertFalse(self.project.is_ignored(myfile)) def test_loading_config_dot_py(self): self.project = testutils.sample_project() config = self.project.get_file(".ropeproject/config.py") if not config.exists(): config.create() config.write(dedent("""\ def set_prefs(prefs): prefs["ignored_resources"] = ["myfile.txt"] def project_opened(project): project.root.create_file("loaded") """)) self.project.close() self.project = Project(self.project.address) self.assertTrue(self.project.get_file("loaded").exists()) myfile = self.project.get_file("myfile.txt") self.assertTrue(self.project.is_ignored(myfile)) def test_loading_pyproject(self): self.project = testutils.sample_project() config = self.project.get_file("pyproject.toml") if not config.exists(): config.create() config.write(dedent("""\ [tool.rope] ignored_resources=["pyproject.py"] """)) self.project.close() self.project = Project(self.project.address) myfile = self.project.get_file("pyproject.py") self.assertTrue(self.project.is_ignored(myfile)) def test_loading_pyproject_empty_file(self): self.project = testutils.sample_project() config = self.project.get_file("pyproject.toml") if not config.exists(): config.create() config.write("") self.project.close() self.project = Project(self.project.address) myfile = self.project.get_file("pyproject.py") self.assertFalse(self.project.is_ignored(myfile)) def test_loading_pyproject_no_tool_section(self): self.project = testutils.sample_project() config = self.project.get_file("pyproject.toml") if not config.exists(): config.create() config.write(dedent("""\ [project] name = 'testproject' """)) self.project.close() self.project = Project(self.project.address) myfile = self.project.get_file("pyproject.py") self.assertFalse(self.project.is_ignored(myfile)) def test_loading_pyproject_no_tool_rope_section(self): self.project = testutils.sample_project() config = self.project.get_file("pyproject.toml") if not config.exists(): config.create() config.write(dedent("""\ [tool.anothertool] name = 'testproject' """)) self.project.close() self.project = Project(self.project.address) myfile = self.project.get_file("pyproject.py") self.assertFalse(self.project.is_ignored(myfile)) def test_ignoring_syntax_errors(self): self.project = testutils.sample_project( ropefolder=None, ignore_syntax_errors=True ) mod = testutils.create_module(self.project, "mod") mod.write("xyz print") pymod = self.project.get_pymodule(mod) # noqa def test_compressed_history(self): self.project = testutils.sample_project(compress_history=True) mod = testutils.create_module(self.project, "mod") mod.write("") def test_compressed_objectdb(self): self.project = testutils.sample_project(compress_objectdb=True) mod = testutils.create_module(self.project, "mod") self.project.pycore.analyze_module(mod) def test_nested_dot_ropeproject_folder(self): self.project = testutils.sample_project(ropefolder=".f1/f2") ropefolder = self.project.ropefolder self.assertEqual(".f1/f2", ropefolder.path) self.assertTrue(ropefolder.exists()) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/ropetest/pycoretest.py0000664000175000017500000014566114512700666017136 0ustar00lieryanlieryanimport sys import unittest from textwrap import dedent from rope.base import exceptions, libutils from rope.base.builtins import BuiltinClass, File from rope.base.pycore import _TextChangeDetector from rope.base.pynamesdef import AssignedName from rope.base.pyobjects import AbstractFunction, get_base_type from ropetest import testutils class PyCoreTest(unittest.TestCase): def setUp(self): super().setUp() self.project = testutils.sample_project() self.pycore = self.project.pycore def tearDown(self): testutils.remove_project(self.project) super().tearDown() def test_simple_module(self): testutils.create_module(self.project, "mod") result = self.project.get_module("mod") self.assertEqual(get_base_type("Module"), result.type) self.assertEqual(0, len(result.get_attributes())) def test_nested_modules(self): pkg = testutils.create_package(self.project, "pkg") mod = testutils.create_module(self.project, "mod", pkg) # noqa package = self.project.get_module("pkg") self.assertEqual(get_base_type("Module"), package.get_type()) self.assertEqual(1, len(package.get_attributes())) module = package["mod"].get_object() self.assertEqual(get_base_type("Module"), module.get_type()) def test_package(self): pkg = testutils.create_package(self.project, "pkg") mod = testutils.create_module(self.project, "mod", pkg) # noqa result = self.project.get_module("pkg") self.assertEqual(get_base_type("Module"), result.type) def test_simple_class(self): mod = testutils.create_module(self.project, "mod") mod.write(dedent("""\ class SampleClass(object): pass """)) mod_element = self.project.get_module("mod") result = mod_element["SampleClass"].get_object() self.assertEqual(get_base_type("Type"), result.get_type()) def test_simple_function(self): mod = testutils.create_module(self.project, "mod") mod.write(dedent("""\ def sample_function(): pass """)) mod_element = self.project.get_module("mod") result = mod_element["sample_function"].get_object() self.assertEqual(get_base_type("Function"), result.get_type()) def test_class_methods(self): mod = testutils.create_module(self.project, "mod") code = dedent("""\ class SampleClass(object): def sample_method(self): pass """) mod.write(code) mod_element = self.project.get_module("mod") sample_class = mod_element["SampleClass"].get_object() self.assertTrue("sample_method" in sample_class) method = sample_class["sample_method"].get_object() self.assertEqual(get_base_type("Function"), method.get_type()) def test_global_variable_without_type_annotation(self): mod = testutils.create_module(self.project, "mod") mod.write("var = 10") mod_element = self.project.get_module("mod") var = mod_element["var"] self.assertEqual(AssignedName, type(var)) @testutils.only_for_versions_higher("3.6") def test_global_variable_with_type_annotation(self): mod = testutils.create_module(self.project, "mod") mod.write("py3_var: str = foo_bar") mod_element = self.project.get_module("mod") py3_var = mod_element["py3_var"] self.assertEqual(AssignedName, type(py3_var)) def test_class_variables(self): mod = testutils.create_module(self.project, "mod") mod.write(dedent("""\ class SampleClass(object): var = 10 """)) mod_element = self.project.get_module("mod") sample_class = mod_element["SampleClass"].get_object() var = sample_class["var"] # noqa def test_class_attributes_set_in_init(self): mod = testutils.create_module(self.project, "mod") mod.write(dedent("""\ class C(object): def __init__(self): self.var = 20 """)) mod_element = self.project.get_module("mod") sample_class = mod_element["C"].get_object() var = sample_class["var"] # noqa def test_class_attributes_set_in_init_overwriting_a_defined(self): mod = testutils.create_module(self.project, "mod") code = dedent("""\ class C(object): def __init__(self): self.f = 20 def f(): pass """) mod.write(code) mod_element = self.project.get_module("mod") sample_class = mod_element["C"].get_object() f = sample_class["f"].get_object() self.assertTrue(isinstance(f, AbstractFunction)) def test_classes_inside_other_classes(self): mod = testutils.create_module(self.project, "mod") code = dedent("""\ class SampleClass(object): class InnerClass(object): pass """) mod.write(code) mod_element = self.project.get_module("mod") sample_class = mod_element["SampleClass"].get_object() var = sample_class["InnerClass"].get_object() self.assertEqual(get_base_type("Type"), var.get_type()) def test_non_existent_module(self): with self.assertRaises(exceptions.ModuleNotFoundError): self.project.get_module("doesnotexistmodule") def test_imported_names(self): testutils.create_module(self.project, "mod1") mod = testutils.create_module(self.project, "mod2") mod.write("import mod1\n") module = self.project.get_module("mod2") imported_sys = module["mod1"].get_object() self.assertEqual(get_base_type("Module"), imported_sys.get_type()) def test_imported_as_names(self): testutils.create_module(self.project, "mod1") mod = testutils.create_module(self.project, "mod2") mod.write("import mod1 as my_import\n") module = self.project.get_module("mod2") imported_mod = module["my_import"].get_object() self.assertEqual(get_base_type("Module"), imported_mod.get_type()) def test_get_string_module(self): code = dedent("""\ class Sample(object): pass """) mod = libutils.get_string_module(self.project, code) sample_class = mod["Sample"].get_object() self.assertEqual(get_base_type("Type"), sample_class.get_type()) def test_get_string_module_with_extra_spaces(self): code = "a = 10\n " mod = libutils.get_string_module(self.project, code) # noqa def test_parameter_info_for_functions(self): code = dedent("""\ def func(param1, param2=10, *param3, **param4): pass""") mod = libutils.get_string_module(self.project, code) sample_function = mod["func"] self.assertEqual( ["param1", "param2", "param3", "param4"], sample_function.get_object().get_param_names(), ) # FIXME: Not found modules def xxx_test_not_found_module_is_module(self): code = "import doesnotexist\n" mod = libutils.get_string_module(self.project, code) self.assertEqual( get_base_type("Module"), mod["doesnotexist"].get_object().get_type() ) def test_mixing_scopes_and_objects_hierarchy(self): code = "var = 200\n" mod = libutils.get_string_module(self.project, code) scope = mod.get_scope() self.assertTrue("var" in scope.get_names()) def test_inheriting_base_class_attributes(self): code = dedent("""\ class Base(object): def method(self): pass class Derived(Base): pass """) mod = libutils.get_string_module(self.project, code) derived = mod["Derived"].get_object() self.assertTrue("method" in derived) self.assertEqual( get_base_type("Function"), derived["method"].get_object().get_type() ) def test_inheriting_multiple_base_class_attributes(self): code = dedent("""\ class Base1(object): def method1(self): pass class Base2(object): def method2(self): pass class Derived(Base1, Base2): pass """) mod = libutils.get_string_module(self.project, code) derived = mod["Derived"].get_object() self.assertTrue("method1" in derived) self.assertTrue("method2" in derived) def test_inherit_multiple_base_class_attrs_with_the_same_name(self): code = dedent("""\ class Base1(object): def method(self): pass class Base2(object): def method(self): pass class Derived(Base1, Base2): pass """) mod = libutils.get_string_module(self.project, code) base1 = mod["Base1"].get_object() derived = mod["Derived"].get_object() self.assertEqual(base1["method"].get_object(), derived["method"].get_object()) def test_inheriting_unknown_base_class(self): code = dedent("""\ class Derived(NotFound): def f(self): pass """) mod = libutils.get_string_module(self.project, code) derived = mod["Derived"].get_object() self.assertTrue("f" in derived) def test_module_creation(self): new_module = testutils.create_module(self.project, "module") self.assertFalse(new_module.is_folder()) self.assertEqual(self.project.get_resource("module.py"), new_module) def test_packaged_module_creation(self): package = self.project.root.create_folder("package") # noqa new_module = testutils.create_module(self.project, "package.module") self.assertEqual(self.project.get_resource("package/module.py"), new_module) def test_packaged_module_creation_with_nested_src(self): src = self.project.root.create_folder("src") src.create_folder("pkg") new_module = testutils.create_module(self.project, "pkg.mod", src) self.assertEqual(self.project.get_resource("src/pkg/mod.py"), new_module) def test_package_creation(self): new_package = testutils.create_package(self.project, "pkg") self.assertTrue(new_package.is_folder()) self.assertEqual(self.project.get_resource("pkg"), new_package) self.assertEqual( self.project.get_resource("pkg/__init__.py"), new_package.get_child("__init__.py"), ) def test_nested_package_creation(self): testutils.create_package(self.project, "pkg1") nested_package = testutils.create_package(self.project, "pkg1.pkg2") self.assertEqual(self.project.get_resource("pkg1/pkg2"), nested_package) def test_packaged_package_creation_with_nested_src(self): src = self.project.root.create_folder("src") testutils.create_package(self.project, "pkg1", src) nested_package = testutils.create_package(self.project, "pkg1.pkg2", src) self.assertEqual(self.project.get_resource("src/pkg1/pkg2"), nested_package) def test_find_module(self): src = self.project.root.create_folder("src") samplemod = testutils.create_module(self.project, "samplemod", src) found_module = self.project.find_module("samplemod") self.assertEqual(samplemod, found_module) def test_find_nested_module(self): src = self.project.root.create_folder("src") samplepkg = testutils.create_package(self.project, "samplepkg", src) samplemod = testutils.create_module(self.project, "samplemod", samplepkg) found_module = self.project.find_module("samplepkg.samplemod") self.assertEqual(samplemod, found_module) def test_find_multiple_module(self): src = self.project.root.create_folder("src") samplemod1 = testutils.create_module(self.project, "samplemod", src) samplemod2 = testutils.create_module(self.project, "samplemod") test = self.project.root.create_folder("test") samplemod3 = testutils.create_module(self.project, "samplemod", test) found_module = self.project.find_module("samplemod") self.assertTrue( samplemod1 == found_module or samplemod2 == found_module or samplemod3 == found_module ) def test_find_module_packages(self): src = self.project.root samplepkg = testutils.create_package(self.project, "samplepkg", src) found_module = self.project.find_module("samplepkg") self.assertEqual(samplepkg, found_module) def test_find_module_when_module_and_package_with_the_same_name(self): src = self.project.root testutils.create_module(self.project, "sample", src) samplepkg = testutils.create_package(self.project, "sample", src) found_module = self.project.find_module("sample") self.assertEqual(samplepkg, found_module) def test_source_folders_preference(self): testutils.create_package(self.project, "pkg1") testutils.create_package(self.project, "pkg1.src2") lost = testutils.create_module(self.project, "pkg1.src2.lost") self.assertEqual(self.project.find_module("lost"), None) self.project.close() from rope.base.project import Project self.project = Project(self.project.address, source_folders=["pkg1/src2"]) self.assertEqual(self.project.find_module("lost"), lost) def test_get_pyname_definition_location(self): code = "a_var = 20\n" mod = libutils.get_string_module(self.project, code) a_var = mod["a_var"] self.assertEqual((mod, 1), a_var.get_definition_location()) def test_get_pyname_definition_location_functions(self): code = dedent("""\ def a_func(): pass """) mod = libutils.get_string_module(self.project, code) a_func = mod["a_func"] self.assertEqual((mod, 1), a_func.get_definition_location()) def test_get_pyname_definition_location_class(self): code = dedent("""\ class AClass(object): pass """) mod = libutils.get_string_module(self.project, code) a_class = mod["AClass"] self.assertEqual((mod, 1), a_class.get_definition_location()) def test_get_pyname_definition_location_local_variables(self): code = dedent("""\ def a_func(): a_var = 10 """) mod = libutils.get_string_module(self.project, code) a_func_scope = mod.get_scope().get_scopes()[0] a_var = a_func_scope["a_var"] self.assertEqual((mod, 2), a_var.get_definition_location()) def test_get_pyname_definition_location_reassigning(self): code = dedent("""\ a_var = 20 a_var=30 """) mod = libutils.get_string_module(self.project, code) a_var = mod["a_var"] self.assertEqual((mod, 1), a_var.get_definition_location()) def test_get_pyname_definition_location_imported(self): testutils.create_module(self.project, "mod") code = "import mod\n" mod = libutils.get_string_module(self.project, code) imported_module = self.project.get_module("mod") module_pyname = mod["mod"] self.assertEqual((imported_module, 1), module_pyname.get_definition_location()) def test_get_pyname_definition_location_imports(self): module_resource = testutils.create_module(self.project, "mod") module_resource.write(dedent("""\ def a_func(): pass """)) imported_module = self.project.get_module("mod") code = dedent("""\ from mod import a_func """) mod = libutils.get_string_module(self.project, code) a_func = mod["a_func"] self.assertEqual((imported_module, 2), a_func.get_definition_location()) def test_get_pyname_definition_location_parameters(self): code = dedent("""\ def a_func(param1, param2): a_var = param """) mod = libutils.get_string_module(self.project, code) a_func_scope = mod.get_scope().get_scopes()[0] param1 = a_func_scope["param1"] self.assertEqual((mod, 1), param1.get_definition_location()) param2 = a_func_scope["param2"] self.assertEqual((mod, 1), param2.get_definition_location()) def test_module_get_resource(self): module_resource = testutils.create_module(self.project, "mod") module = self.project.get_module("mod") self.assertEqual(module_resource, module.get_resource()) code = dedent("""\ from mod import a_func """) string_module = libutils.get_string_module(self.project, code) self.assertEqual(None, string_module.get_resource()) def test_get_pyname_definition_location_class2(self): code = dedent("""\ class AClass(object): def __init__(self): self.an_attr = 10 """) mod = libutils.get_string_module(self.project, code) a_class = mod["AClass"].get_object() an_attr = a_class["an_attr"] self.assertEqual((mod, 3), an_attr.get_definition_location()) def test_import_not_found_module_get_definition_location(self): code = "import doesnotexist\n" mod = libutils.get_string_module(self.project, code) does_not_exist = mod["doesnotexist"] self.assertEqual((None, None), does_not_exist.get_definition_location()) def test_from_not_found_module_get_definition_location(self): code = "from doesnotexist import Sample\n" mod = libutils.get_string_module(self.project, code) sample = mod["Sample"] self.assertEqual((None, None), sample.get_definition_location()) def test_from_package_import_module_get_definition_location(self): pkg = testutils.create_package(self.project, "pkg") testutils.create_module(self.project, "mod", pkg) pkg_mod = self.project.get_module("pkg.mod") code = "from pkg import mod\n" mod = libutils.get_string_module(self.project, code) imported_mod = mod["mod"] self.assertEqual((pkg_mod, 1), imported_mod.get_definition_location()) def test_get_module_for_defined_pyobjects(self): code = dedent("""\ class AClass(object): pass """) mod = libutils.get_string_module(self.project, code) a_class = mod["AClass"].get_object() self.assertEqual(mod, a_class.get_module()) def test_get_definition_location_for_packages(self): testutils.create_package(self.project, "pkg") init_module = self.project.get_module("pkg.__init__") code = "import pkg\n" mod = libutils.get_string_module(self.project, code) pkg_pyname = mod["pkg"] self.assertEqual((init_module, 1), pkg_pyname.get_definition_location()) def test_get_definition_location_for_filtered_packages(self): pkg = testutils.create_package(self.project, "pkg") testutils.create_module(self.project, "mod", pkg) init_module = self.project.get_module("pkg.__init__") code = "import pkg.mod" mod = libutils.get_string_module(self.project, code) pkg_pyname = mod["pkg"] self.assertEqual((init_module, 1), pkg_pyname.get_definition_location()) def test_out_of_project_modules(self): code = "import rope.base.project as project\n" scope = libutils.get_string_scope(self.project, code) imported_module = scope["project"].get_object() self.assertTrue("Project" in imported_module) def test_file_encoding_reading(self): contents = ( "# -*- coding: utf-8 -*-\n" + "#\N{LATIN SMALL LETTER I WITH DIAERESIS}\n" ) mod = testutils.create_module(self.project, "mod") mod.write(contents) self.project.get_module("mod") def test_global_keyword(self): code = dedent("""\ a_var = 1 def a_func(): global a_var """) mod = libutils.get_string_module(self.project, code) global_var = mod["a_var"] func_scope = mod["a_func"].get_object().get_scope() local_var = func_scope["a_var"] self.assertEqual(global_var, local_var) def test_not_leaking_for_vars_inside_parent_scope(self): mod = testutils.create_module(self.project, "mod") code = dedent("""\ class C(object): def f(self): for my_var1, my_var2 in []: pass """) mod.write(code) pymod = self.pycore.resource_to_pyobject(mod) c_class = pymod["C"].get_object() self.assertFalse("my_var1" in c_class) self.assertFalse("my_var2" in c_class) def test_not_leaking_for_vars_inside_parent_scope2(self): mod = testutils.create_module(self.project, "mod") code = dedent("""\ class C(object): def f(self): for my_var in []: pass """) mod.write(code) pymod = self.pycore.resource_to_pyobject(mod) c_class = pymod["C"].get_object() self.assertFalse("my_var" in c_class) def test_variables_defined_in_excepts(self): mod = testutils.create_module(self.project, "mod") code = dedent("""\ try: myvar1 = 1 except: myvar2 = 1 finally: myvar3 = 1 """) mod.write(code) pymod = self.pycore.resource_to_pyobject(mod) self.assertTrue("myvar1" in pymod) self.assertTrue("myvar2" in pymod) self.assertTrue("myvar3" in pymod) def test_not_leaking_tuple_assigned_names_inside_parent_scope(self): mod = testutils.create_module(self.project, "mod") code = dedent("""\ class C(object): def f(self): var1, var2 = range(2) """) mod.write(code) pymod = self.pycore.resource_to_pyobject(mod) c_class = pymod["C"].get_object() self.assertFalse("var1" in c_class) def test_with_statement_variables(self): code = dedent("""\ import threading with threading.lock() as var: pass """) if sys.version_info < (2, 6, 0): code = "from __future__ import with_statement\n" + code pymod = libutils.get_string_module(self.project, code) self.assertTrue("var" in pymod) def test_with_statement_variables_and_tuple_assignment(self): code = dedent("""\ class A(object): def __enter__(self): return (1, 2) def __exit__(self, type, value, tb): pass with A() as (a, b): pass """) if sys.version_info < (2, 6, 0): code = "from __future__ import with_statement\n" + code pymod = libutils.get_string_module(self.project, code) self.assertTrue("a" in pymod) self.assertTrue("b" in pymod) def test_with_statement_variable_type(self): code = dedent("""\ class A(object): def __enter__(self): return self def __exit__(self, type, value, tb): pass with A() as var: pass """) if sys.version_info < (2, 6, 0): code = "from __future__ import with_statement\n" + code pymod = libutils.get_string_module(self.project, code) a_class = pymod["A"].get_object() var = pymod["var"].get_object() self.assertEqual(a_class, var.get_type()) def test_nested_with_statement_variable_type(self): code = dedent("""\ class A(object): def __enter__(self): return self def __exit__(self, type, value, tb): pass class B(object): def __enter__(self): return self def __exit__(self, type, value, tb): pass with A() as var_a, B() as var_b: pass """) if sys.version_info < (2, 6, 0): code = "from __future__ import with_statement\n" + code pymod = libutils.get_string_module(self.project, code) a_class = pymod["A"].get_object() var_a = pymod["var_a"].get_object() self.assertEqual(a_class, var_a.get_type()) b_class = pymod["B"].get_object() var_b = pymod["var_b"].get_object() self.assertEqual(b_class, var_b.get_type()) def test_with_statement_with_no_vars(self): code = dedent("""\ with open("file"): pass """) if sys.version_info < (2, 6, 0): code = "from __future__ import with_statement\n" + code pymod = libutils.get_string_module(self.project, code) pymod.get_attributes() def test_with_statement(self): code = dedent("""\ a = 10 with open("file") as f: pass """) pymod = libutils.get_string_module(self.project, code) assigned = pymod.get_attribute("a") self.assertEqual(BuiltinClass, type(assigned.get_object().get_type())) assigned = pymod.get_attribute("f") self.assertEqual(File, type(assigned.get_object().get_type())) def test_check_for_else_block(self): code = dedent("""\ for i in range(10): pass else: myvar = 1 """) mod = libutils.get_string_module(self.project, code) a_var = mod["myvar"] self.assertEqual((mod, 4), a_var.get_definition_location()) def test_check_names_defined_in_whiles(self): code = dedent("""\ while False: myvar = 1 """) mod = libutils.get_string_module(self.project, code) a_var = mod["myvar"] self.assertEqual((mod, 2), a_var.get_definition_location()) def test_get_definition_location_in_tuple_assnames(self): code = dedent("""\ def f(x): x.z, a = range(2) """) mod = libutils.get_string_module(self.project, code) x = mod["f"].get_object().get_scope()["x"] a = mod["f"].get_object().get_scope()["a"] self.assertEqual((mod, 1), x.get_definition_location()) self.assertEqual((mod, 2), a.get_definition_location()) def test_syntax_errors_in_code(self): with self.assertRaises(exceptions.ModuleSyntaxError): libutils.get_string_module(self.project, "xyx print\n") def test_holding_error_location_information(self): try: libutils.get_string_module(self.project, "xyx print\n") except exceptions.ModuleSyntaxError as e: self.assertEqual(1, e.lineno) def test_no_exceptions_on_module_encoding_problems(self): mod = testutils.create_module(self.project, "mod") contents = b"\nsdsdsd\n\xa9\n" file = open(mod.real_path, "wb") file.write(contents) file.close() mod.read() def test_syntax_errors_when_cannot_decode_file2(self): mod = testutils.create_module(self.project, "mod") contents = b"\n\xa9\n" file = open(mod.real_path, "wb") file.write(contents) file.close() with self.assertRaises(exceptions.ModuleSyntaxError): self.pycore.resource_to_pyobject(mod) def test_syntax_errors_when_null_bytes(self): mod = testutils.create_module(self.project, "mod") contents = b"\n\x00\n" file = open(mod.real_path, "wb") file.write(contents) file.close() with self.assertRaises(exceptions.ModuleSyntaxError): self.pycore.resource_to_pyobject(mod) def test_syntax_errors_when_bad_strs(self): mod = testutils.create_module(self.project, "mod") contents = b'\n"\\x0"\n' file = open(mod.real_path, "wb") file.write(contents) file.close() with self.assertRaises(exceptions.ModuleSyntaxError): self.pycore.resource_to_pyobject(mod) def test_not_reaching_maximum_recursions_with_from_star_imports(self): mod1 = testutils.create_module(self.project, "mod1") mod2 = testutils.create_module(self.project, "mod2") mod1.write("from mod2 import *\n") mod2.write("from mod1 import *\n") pymod1 = self.pycore.resource_to_pyobject(mod1) pymod1.get_attributes() def test_not_reaching_maximum_recursions_when_importing_variables(self): mod1 = testutils.create_module(self.project, "mod1") mod2 = testutils.create_module(self.project, "mod2") mod1.write("from mod2 import myvar\n") mod2.write("from mod1 import myvar\n") pymod1 = self.pycore.resource_to_pyobject(mod1) pymod1["myvar"].get_object() def test_not_reaching_maximum_recursions_when_importing_variables2(self): mod1 = testutils.create_module(self.project, "mod1") mod1.write("from mod1 import myvar\n") pymod1 = self.pycore.resource_to_pyobject(mod1) pymod1["myvar"].get_object() def test_pyobject_equality_should_compare_types(self): mod1 = testutils.create_module(self.project, "mod1") mod1.write(dedent("""\ var1 = "" var2 = "" """)) pymod1 = self.pycore.resource_to_pyobject(mod1) self.assertEqual(pymod1["var1"].get_object(), pymod1["var2"].get_object()) class PyCoreInProjectsTest(unittest.TestCase): def setUp(self): super().setUp() self.project = testutils.sample_project() self.pycore = self.project.pycore samplemod = testutils.create_module(self.project, "samplemod") code = dedent("""\ class SampleClass(object): def sample_method(): pass def sample_func(): pass sample_var = 10 def _underlined_func(): pass """) samplemod.write(code) package = testutils.create_package(self.project, "package") testutils.create_module(self.project, "nestedmod", package) def tearDown(self): testutils.remove_project(self.project) super().tearDown() def test_simple_import(self): code = "import samplemod\n" mod = libutils.get_string_module(self.project, code) samplemod = mod["samplemod"].get_object() self.assertEqual(get_base_type("Module"), samplemod.get_type()) def test_from_import_class(self): code = "from samplemod import SampleClass\n" mod = libutils.get_string_module(self.project, code) result = mod["SampleClass"].get_object() self.assertEqual(get_base_type("Type"), result.get_type()) self.assertTrue("sample_func" not in mod.get_attributes()) def test_from_import_star(self): code = "from samplemod import *\n" mod = libutils.get_string_module(self.project, code) self.assertEqual( get_base_type("Type"), mod["SampleClass"].get_object().get_type() ) self.assertEqual( get_base_type("Function"), mod["sample_func"].get_object().get_type() ) self.assertTrue(mod["sample_var"] is not None) def test_from_import_star_overwriting(self): code = dedent("""\ from samplemod import * class SampleClass(object): pass """) mod = libutils.get_string_module(self.project, code) samplemod = self.project.get_module("samplemod") sample_class = samplemod["SampleClass"].get_object() self.assertNotEqual( sample_class, mod.get_attributes()["SampleClass"].get_object() ) def test_from_import_star_not_importing_underlined(self): code = "from samplemod import *" mod = libutils.get_string_module(self.project, code) self.assertTrue("_underlined_func" not in mod.get_attributes()) def test_from_import_star_imports_in_functions(self): code = dedent("""\ def f(): from os import * """) mod = libutils.get_string_module(self.project, code) mod["f"].get_object().get_scope().get_names() def test_from_package_import_mod(self): code = "from package import nestedmod\n" mod = libutils.get_string_module(self.project, code) self.assertEqual( get_base_type("Module"), mod["nestedmod"].get_object().get_type() ) # XXX: Deciding to import everything on import start from packages def xxx_test_from_package_import_star(self): code = "from package import *\n" mod = libutils.get_string_module(self.project, code) self.assertTrue("nestedmod" not in mod.get_attributes()) def test_unknown_when_module_cannot_be_found(self): code = "from doesnotexist import nestedmod\n" mod = libutils.get_string_module(self.project, code) self.assertTrue("nestedmod" in mod) def test_from_import_function(self): code = dedent("""\ def f(): from samplemod import SampleClass """) scope = libutils.get_string_scope(self.project, code) self.assertEqual( get_base_type("Type"), scope.get_scopes()[0]["SampleClass"].get_object().get_type(), ) def test_circular_imports(self): mod1 = testutils.create_module(self.project, "mod1") mod2 = testutils.create_module(self.project, "mod2") mod1.write("import mod2\n") mod2.write("import mod1\n") self.project.get_module("mod1") def test_circular_imports2(self): mod1 = testutils.create_module(self.project, "mod1") mod2 = testutils.create_module(self.project, "mod2") mod1.write(dedent("""\ from mod2 import Sample2 class Sample1(object): pass """)) mod2.write(dedent("""\ from mod1 import Sample1 class Sample2(object): pass """)) self.project.get_module("mod1").get_attributes() def test_multi_dot_imports(self): pkg = testutils.create_package(self.project, "pkg") pkg_mod = testutils.create_module(self.project, "mod", pkg) pkg_mod.write(dedent("""\ def sample_func(): pass """)) code = "import pkg.mod\n" mod = libutils.get_string_module(self.project, code) self.assertTrue("pkg" in mod) self.assertTrue("sample_func" in mod["pkg"].get_object()["mod"].get_object()) def test_multi_dot_imports2(self): pkg = testutils.create_package(self.project, "pkg") testutils.create_module(self.project, "mod1", pkg) testutils.create_module(self.project, "mod2", pkg) code = dedent("""\ import pkg.mod1 import pkg.mod2 """) mod = libutils.get_string_module(self.project, code) package = mod["pkg"].get_object() self.assertEqual(2, len(package.get_attributes())) self.assertTrue("mod1" in package and "mod2" in package) def test_multi_dot_imports3(self): pkg1 = testutils.create_package(self.project, "pkg1") pkg2 = testutils.create_package(self.project, "pkg2", pkg1) testutils.create_module(self.project, "mod1", pkg2) testutils.create_module(self.project, "mod2", pkg2) code = dedent("""\ import pkg1.pkg2.mod1 import pkg1.pkg2.mod2 """) mod = libutils.get_string_module(self.project, code) package1 = mod["pkg1"].get_object() package2 = package1["pkg2"].get_object() self.assertEqual(2, len(package2.get_attributes())) self.assertTrue("mod1" in package2 and "mod2" in package2) def test_multi_dot_imports_as(self): pkg = testutils.create_package(self.project, "pkg") mod1 = testutils.create_module(self.project, "mod1", pkg) mod1.write(dedent("""\ def f(): pass """)) code = "import pkg.mod1 as mod1\n" mod = libutils.get_string_module(self.project, code) module = mod["mod1"].get_object() self.assertTrue("f" in module) # TODO: not showing unimported names as attributes of packages def xxx_test_from_package_import_package(self): pkg1 = testutils.create_package(self.project, "pkg1") pkg2 = testutils.create_package(self.project, "pkg2", pkg1) testutils.create_module(self.project, "mod", pkg2) code = "from pkg1 import pkg2\n" mod = libutils.get_string_module(self.project, code) package = mod["pkg2"] self.assertEqual(0, len(package.get_attributes())) def test_invalidating_cache_after_resource_change(self): module = testutils.create_module(self.project, "mod") module.write("import sys\n") mod1 = self.project.get_module("mod") self.assertTrue("var" not in mod1.get_attributes()) module.write("var = 10\n") mod2 = self.project.get_module("mod") self.assertTrue("var" in mod2) def test_invalidating_cache_after_resource_change_for_init_dot_pys(self): pkg = testutils.create_package(self.project, "pkg") mod = testutils.create_module(self.project, "mod") init_dot_py = pkg.get_child("__init__.py") init_dot_py.write("a_var = 10\n") mod.write("import pkg\n") pymod = self.project.get_module("mod") self.assertTrue("a_var" in pymod["pkg"].get_object()) init_dot_py.write("new_var = 10\n") self.assertTrue("a_var" not in pymod["pkg"].get_object().get_attributes()) def test_invalidating_cache_after_rsrc_chng_for_nested_init_dot_pys(self): pkg1 = testutils.create_package(self.project, "pkg1") pkg2 = testutils.create_package(self.project, "pkg2", pkg1) mod = testutils.create_module(self.project, "mod") init_dot_py = pkg2.get_child("__init__.py") init_dot_py.write("a_var = 10\n") mod.write("import pkg1\n") pymod = self.project.get_module("mod") self.assertTrue("a_var" in pymod["pkg1"].get_object()["pkg2"].get_object()) init_dot_py.write("new_var = 10\n") self.assertTrue("a_var" not in pymod["pkg1"].get_object()["pkg2"].get_object()) def test_from_import_nonexistent_module(self): code = "from doesnotexistmod import DoesNotExistClass\n" mod = libutils.get_string_module(self.project, code) self.assertTrue("DoesNotExistClass" in mod) self.assertEqual( get_base_type("Unknown"), mod["DoesNotExistClass"].get_object().get_type() ) def test_from_import_nonexistent_name(self): code = "from samplemod import DoesNotExistClass\n" mod = libutils.get_string_module(self.project, code) self.assertTrue("DoesNotExistClass" in mod) self.assertEqual( get_base_type("Unknown"), mod["DoesNotExistClass"].get_object().get_type() ) def test_not_considering_imported_names_as_sub_scopes(self): code = "from samplemod import SampleClass\n" scope = libutils.get_string_scope(self.project, code) self.assertEqual(0, len(scope.get_scopes())) def test_not_considering_imported_modules_as_sub_scopes(self): code = "import samplemod\n" scope = libutils.get_string_scope(self.project, code) self.assertEqual(0, len(scope.get_scopes())) def test_inheriting_dotted_base_class(self): code = dedent("""\ import samplemod class Derived(samplemod.SampleClass): pass """) mod = libutils.get_string_module(self.project, code) derived = mod["Derived"].get_object() self.assertTrue("sample_method" in derived) def test_self_in_methods(self): code = dedent("""\ class Sample(object): def func(self): pass """) scope = libutils.get_string_scope(self.project, code) sample_class = scope["Sample"].get_object() func_scope = scope.get_scopes()[0].get_scopes()[0] self.assertEqual(sample_class, func_scope["self"].get_object().get_type()) self.assertTrue("func" in func_scope["self"].get_object()) def test_none_assignments_in_classes(self): code = dedent("""\ class C(object): var = "" def f(self): self.var += "".join([]) """) scope = libutils.get_string_scope(self.project, code) c_class = scope["C"].get_object() self.assertTrue("var" in c_class) def test_self_in_methods_with_decorators(self): code = dedent("""\ class Sample(object): @staticmethod def func(self): pass """) scope = libutils.get_string_scope(self.project, code) sample_class = scope["Sample"].get_object() func_scope = scope.get_scopes()[0].get_scopes()[0] self.assertNotEqual(sample_class, func_scope["self"].get_object().get_type()) def test_location_of_imports_when_importing(self): mod = testutils.create_module(self.project, "mod") mod.write("from samplemod import SampleClass\n") code = "from mod import SampleClass\n" scope = libutils.get_string_scope(self.project, code) sample_class = scope["SampleClass"] samplemod = self.project.get_module("samplemod") self.assertEqual((samplemod, 1), sample_class.get_definition_location()) def test_nested_modules(self): pkg = testutils.create_package(self.project, "pkg") testutils.create_module(self.project, "mod", pkg) imported_module = self.project.get_module("pkg.mod") code = "import pkg.mod\n" scope = libutils.get_string_scope(self.project, code) mod_pyobject = scope["pkg"].get_object()["mod"] self.assertEqual((imported_module, 1), mod_pyobject.get_definition_location()) def test_reading_init_dot_py(self): pkg = testutils.create_package(self.project, "pkg") init_dot_py = pkg.get_child("__init__.py") init_dot_py.write("a_var = 1\n") pkg_object = self.project.get_module("pkg") self.assertTrue("a_var" in pkg_object) def test_relative_imports(self): pkg = testutils.create_package(self.project, "pkg") mod1 = testutils.create_module(self.project, "mod1", pkg) mod2 = testutils.create_module(self.project, "mod2", pkg) mod2.write("import mod1\n") mod1_object = self.pycore.resource_to_pyobject(mod1) mod2_object = self.pycore.resource_to_pyobject(mod2) self.assertEqual(mod1_object, mod2_object.get_attributes()["mod1"].get_object()) def test_relative_froms(self): pkg = testutils.create_package(self.project, "pkg") mod1 = testutils.create_module(self.project, "mod1", pkg) mod2 = testutils.create_module(self.project, "mod2", pkg) mod1.write(dedent("""\ def a_func(): pass """)) mod2.write("from mod1 import a_func\n") mod1_object = self.pycore.resource_to_pyobject(mod1) mod2_object = self.pycore.resource_to_pyobject(mod2) self.assertEqual( mod1_object["a_func"].get_object(), mod2_object["a_func"].get_object() ) def test_relative_imports_for_string_modules(self): pkg = testutils.create_package(self.project, "pkg") mod1 = testutils.create_module(self.project, "mod1", pkg) mod2 = testutils.create_module(self.project, "mod2", pkg) mod2.write("import mod1\n") mod1_object = self.pycore.resource_to_pyobject(mod1) mod2_object = libutils.get_string_module(self.project, mod2.read(), mod2) self.assertEqual(mod1_object, mod2_object["mod1"].get_object()) def test_relative_imports_for_string_scopes(self): pkg = testutils.create_package(self.project, "pkg") mod1 = testutils.create_module(self.project, "mod1", pkg) mod2 = testutils.create_module(self.project, "mod2", pkg) mod2.write("import mod1\n") mod1_object = self.pycore.resource_to_pyobject(mod1) mod2_scope = libutils.get_string_scope(self.project, mod2.read(), mod2) self.assertEqual(mod1_object, mod2_scope["mod1"].get_object()) def test_new_style_relative_imports(self): pkg = testutils.create_package(self.project, "pkg") mod1 = testutils.create_module(self.project, "mod1", pkg) mod2 = testutils.create_module(self.project, "mod2", pkg) mod2.write("from . import mod1\n") mod1_object = self.pycore.resource_to_pyobject(mod1) mod2_object = self.pycore.resource_to_pyobject(mod2) self.assertEqual(mod1_object, mod2_object["mod1"].get_object()) def test_new_style_relative_imports2(self): pkg = testutils.create_package(self.project, "pkg") mod1 = testutils.create_module(self.project, "mod1") mod2 = testutils.create_module(self.project, "mod2", pkg) mod1.write(dedent("""\ def a_func(): pass """)) mod2.write("from ..mod1 import a_func\n") mod1_object = self.pycore.resource_to_pyobject(mod1) mod2_object = self.pycore.resource_to_pyobject(mod2) self.assertEqual( mod1_object["a_func"].get_object(), mod2_object["a_func"].get_object() ) def test_invalidating_cache_for_from_imports_after_resource_change(self): mod1 = testutils.create_module(self.project, "mod1") mod2 = testutils.create_module(self.project, "mod2") mod2.write(dedent("""\ def a_func(): print(1) """)) mod1.write(dedent("""\ from mod2 import a_func a_func() """)) pymod1 = self.project.get_module("mod1") pymod2 = self.project.get_module("mod2") self.assertEqual(pymod1["a_func"].get_object(), pymod2["a_func"].get_object()) mod2.write(mod2.read() + "\n") pymod2 = self.project.get_module("mod2") self.assertEqual(pymod1["a_func"].get_object(), pymod2["a_func"].get_object()) def test_invalidating_superclasses_after_change(self): mod1 = testutils.create_module(self.project, "mod1") mod2 = testutils.create_module(self.project, "mod2") mod1.write(dedent("""\ class A(object): def func1(self): pass """)) mod2.write(dedent("""\ import mod1 class B(mod1.A): pass """)) b_class = self.project.get_module("mod2")["B"].get_object() self.assertTrue("func1" in b_class) mod1.write(dedent("""\ class A(object): def func2(self): pass """)) self.assertTrue("func2" in b_class) def test_caching_pymodule_with_syntax_errors(self): self.project.prefs["ignore_syntax_errors"] = True self.project.prefs["automatic_soa"] = True self.project.pycore._init_automatic_soa() source = dedent("""\ import sys ab cd """) mod = testutils.create_module(self.project, "mod") mod.write(source) from rope.contrib import fixsyntax fixer = fixsyntax.FixSyntax(self.project, source, mod, 10) pymodule = fixer.get_pymodule() self.assertTrue(pymodule.source_code.startswith("import sys\npass\n")) class TextChangeDetectorTest(unittest.TestCase): def test_trivial_case(self): detector = _TextChangeDetector("\n", "\n") self.assertFalse(detector.is_changed(1, 1)) def test_one_line_change(self): detector = _TextChangeDetector("1\n2\n", "1\n3\n") self.assertFalse(detector.is_changed(1, 1)) self.assertTrue(detector.is_changed(2, 2)) def test_line_expansion(self): detector = _TextChangeDetector("1\n2\n", "1\n3\n4\n2\n") self.assertFalse(detector.is_changed(1, 1)) self.assertFalse(detector.is_changed(2, 2)) def test_line_removals(self): detector = _TextChangeDetector("1\n3\n4\n2\n", "1\n2\n") self.assertFalse(detector.is_changed(1, 1)) self.assertTrue(detector.is_changed(2, 3)) self.assertFalse(detector.is_changed(4, 4)) def test_multi_line_checks(self): detector = _TextChangeDetector("1\n2\n", "1\n3\n") self.assertTrue(detector.is_changed(1, 2)) def test_consume_change(self): detector = _TextChangeDetector("1\n2\n", "1\n3\n") self.assertTrue(detector.is_changed(1, 2)) self.assertTrue(detector.consume_changes(1, 2)) self.assertFalse(detector.is_changed(1, 2)) class PyCoreProjectConfigsTest(unittest.TestCase): def setUp(self): super().setUp() self.project = None def tearDown(self): if self.project: testutils.remove_project(self.project) super().tearDown() def test_python_files_config(self): self.project = testutils.sample_project(python_files=["myscript"]) myscript = self.project.root.create_file("myscript") self.assertTrue(self.project.pycore.is_python_file(myscript)) def test_ignore_bad_imports(self): self.project = testutils.sample_project(ignore_bad_imports=True) code = "import some_nonexistent_module\n" pymod = libutils.get_string_module(self.project, code) self.assertFalse("some_nonexistent_module" in pymod) def test_ignore_bad_imports_for_froms(self): self.project = testutils.sample_project(ignore_bad_imports=True) code = "from some_nonexistent_module import var\n" pymod = libutils.get_string_module(self.project, code) self.assertFalse("var" in pymod) def test_reporting_syntax_errors_with_force_errors(self): self.project = testutils.sample_project(ignore_syntax_errors=True) mod = testutils.create_module(self.project, "mod") mod.write("syntax error ...\n") with self.assertRaises(exceptions.ModuleSyntaxError): self.project.pycore.resource_to_pyobject(mod, force_errors=True) def test_reporting_syntax_errors_in_strings_with_force_errors(self): self.project = testutils.sample_project(ignore_syntax_errors=True) with self.assertRaises(exceptions.ModuleSyntaxError): libutils.get_string_module( self.project, "syntax error ...", force_errors=True ) def test_not_raising_errors_for_strings_with_ignore_errors(self): self.project = testutils.sample_project(ignore_syntax_errors=True) libutils.get_string_module(self.project, "syntax error ...") def test_reporting_syntax_errors_with_force_errors_for_packages(self): self.project = testutils.sample_project(ignore_syntax_errors=True) pkg = testutils.create_package(self.project, "pkg") pkg.get_child("__init__.py").write("syntax error ...\n") with self.assertRaises(exceptions.ModuleSyntaxError): self.project.pycore.resource_to_pyobject(pkg, force_errors=True) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/ropetest/pyscopestest.py0000664000175000017500000004567114512700666017502 0ustar00lieryanlieryanimport unittest from textwrap import dedent from rope.base import libutils from rope.base.pyobjects import get_base_type from ropetest import testutils class PyCoreScopesTest(unittest.TestCase): def setUp(self): super().setUp() self.project = testutils.sample_project() self.pycore = self.project.pycore def tearDown(self): testutils.remove_project(self.project) super().tearDown() def test_simple_scope(self): code = dedent("""\ def sample_func(): pass """) scope = libutils.get_string_scope(self.project, code) sample_func = scope["sample_func"].get_object() self.assertEqual(get_base_type("Function"), sample_func.get_type()) def test_simple_function_scope(self): code = dedent("""\ def sample_func(): a = 10 """) scope = libutils.get_string_scope(self.project, code) self.assertEqual(1, len(scope.get_scopes())) sample_func_scope = scope.get_scopes()[0] self.assertEqual(1, len(sample_func_scope.get_names())) self.assertEqual(0, len(sample_func_scope.get_scopes())) def test_classes_inside_function_scopes(self): code = dedent("""\ def sample_func(): class SampleClass(object): pass """) scope = libutils.get_string_scope(self.project, code) self.assertEqual(1, len(scope.get_scopes())) sample_func_scope = scope.get_scopes()[0] self.assertEqual( get_base_type("Type"), sample_func_scope["SampleClass"].get_object().get_type(), ) def test_list_comprehension_scope_inside_assignment(self): code = "a_var = [b_var + d_var for b_var, c_var in e_var]\n" scope = libutils.get_string_scope(self.project, code) self.assertEqual( list(sorted(scope.get_defined_names())), ["a_var"], ) self.assertEqual( list(sorted(scope.get_scopes()[0].get_defined_names())), ["b_var", "c_var"], ) def test_list_comprehension_scope(self): code = "[b_var + d_var for b_var, c_var in e_var]\n" scope = libutils.get_string_scope(self.project, code) self.assertEqual( list(sorted(scope.get_scopes()[0].get_defined_names())), ["b_var", "c_var"], ) def test_set_comprehension_scope(self): code = "{b_var + d_var for b_var, c_var in e_var}\n" scope = libutils.get_string_scope(self.project, code) self.assertEqual( list(sorted(scope.get_scopes()[0].get_defined_names())), ["b_var", "c_var"], ) def test_generator_comprehension_scope(self): code = "(b_var + d_var for b_var, c_var in e_var)\n" scope = libutils.get_string_scope(self.project, code) self.assertEqual( list(sorted(scope.get_scopes()[0].get_defined_names())), ["b_var", "c_var"], ) def test_dict_comprehension_scope(self): code = "{b_var: d_var for b_var, c_var in e_var}\n" scope = libutils.get_string_scope(self.project, code) self.assertEqual( list(sorted(scope.get_scopes()[0].get_defined_names())), ["b_var", "c_var"], ) @testutils.only_for_versions_higher("3.8") def test_inline_assignment(self): code = """values = (a_var := 2,)""" scope = libutils.get_string_scope(self.project, code) self.assertEqual( list(sorted(scope.get_defined_names())), ["a_var", "values"], ) @testutils.only_for_versions_higher("3.8") def test_inline_assignment_in_comprehensions(self): code = dedent("""\ [ (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) ] """) scope = libutils.get_string_scope(self.project, code) self.assertEqual( list(sorted(scope.get_scopes()[0].get_defined_names())), ["a_var", "b_var", "f_var"], ) self.assertEqual( list(sorted(scope.get_scopes()[0].get_scopes()[0].get_defined_names())), ["i_var", "j_var"], ) def test_nested_comprehension(self): code = dedent("""\ [ b_var + d_var for b_var, c_var in [ e_var for e_var in f_var ] ] """) scope = libutils.get_string_scope(self.project, code) self.assertEqual( list(sorted(scope.get_scopes()[0].get_defined_names())), ["b_var", "c_var"], ) self.assertEqual( list(sorted(scope.get_scopes()[0].get_scopes()[0].get_defined_names())), ["e_var"], ) def test_simple_class_scope(self): code = dedent("""\ class SampleClass(object): def f(self): var = 10 """) scope = libutils.get_string_scope(self.project, code) self.assertEqual(1, len(scope.get_scopes())) sample_class_scope = scope.get_scopes()[0] self.assertTrue("f" in sample_class_scope) self.assertEqual(1, len(sample_class_scope.get_scopes())) f_in_class = sample_class_scope.get_scopes()[0] self.assertTrue("var" in f_in_class) def test_get_lineno(self): code = dedent("""\ def sample_func(): a = 10 """) scope = libutils.get_string_scope(self.project, code) self.assertEqual(1, len(scope.get_scopes())) sample_func_scope = scope.get_scopes()[0] self.assertEqual(1, scope.get_start()) self.assertEqual(2, sample_func_scope.get_start()) def test_scope_kind(self): code = dedent("""\ class SampleClass(object): pass def sample_func(): pass """) scope = libutils.get_string_scope(self.project, code) sample_class_scope = scope.get_scopes()[0] sample_func_scope = scope.get_scopes()[1] self.assertEqual("Module", scope.get_kind()) self.assertEqual("Class", sample_class_scope.get_kind()) self.assertEqual("Function", sample_func_scope.get_kind()) def test_function_parameters_in_scope_names(self): code = dedent("""\ def sample_func(param): a = 10 """) scope = libutils.get_string_scope(self.project, code) sample_func_scope = scope.get_scopes()[0] self.assertTrue("param" in sample_func_scope) def test_get_names_contains_only_names_defined_in_a_scope(self): code = dedent("""\ var1 = 10 def sample_func(param): var2 = 20 """) scope = libutils.get_string_scope(self.project, code) sample_func_scope = scope.get_scopes()[0] self.assertTrue("var1" not in sample_func_scope) def test_scope_lookup(self): code = dedent("""\ var1 = 10 def sample_func(param): var2 = 20 """) scope = libutils.get_string_scope(self.project, code) self.assertTrue(scope.lookup("var2") is None) self.assertEqual( get_base_type("Function"), scope.lookup("sample_func").get_object().get_type(), ) sample_func_scope = scope.get_scopes()[0] self.assertTrue(sample_func_scope.lookup("var1") is not None) def test_function_scopes(self): code = dedent("""\ def func(): var = 10 """) scope = libutils.get_string_scope(self.project, code) func_scope = scope.get_scopes()[0] self.assertTrue("var" in func_scope) def test_function_scopes_classes(self): code = dedent("""\ def func(): class Sample(object): pass """) scope = libutils.get_string_scope(self.project, code) func_scope = scope.get_scopes()[0] self.assertTrue("Sample" in func_scope) def test_function_getting_scope(self): code = dedent("""\ def func(): var = 10 """) mod = libutils.get_string_module(self.project, code) func_scope = mod["func"].get_object().get_scope() self.assertTrue("var" in func_scope) def test_scopes_in_function_scopes(self): code = dedent("""\ def func(): def inner(): var = 10 """) scope = libutils.get_string_scope(self.project, code) func_scope = scope.get_scopes()[0] inner_scope = func_scope.get_scopes()[0] self.assertTrue("var" in inner_scope) def test_for_variables_in_scopes(self): code = dedent("""\ for a_var in range(10): pass """) scope = libutils.get_string_scope(self.project, code) self.assertTrue("a_var" in scope) def test_assists_inside_fors(self): code = dedent("""\ for i in range(10): a_var = i """) scope = libutils.get_string_scope(self.project, code) self.assertTrue("a_var" in scope) def test_first_parameter_of_a_method(self): code = dedent("""\ class AClass(object): def a_func(self, param): pass """) a_class = libutils.get_string_module(self.project, code)["AClass"].get_object() function_scope = a_class["a_func"].get_object().get_scope() self.assertEqual(a_class, function_scope["self"].get_object().get_type()) self.assertNotEqual(a_class, function_scope["param"].get_object().get_type()) def test_first_parameter_of_static_methods(self): code = dedent("""\ class AClass(object): @staticmethod def a_func(param): pass """) a_class = libutils.get_string_module(self.project, code)["AClass"].get_object() function_scope = a_class["a_func"].get_object().get_scope() self.assertNotEqual(a_class, function_scope["param"].get_object().get_type()) def test_first_parameter_of_class_methods(self): code = dedent("""\ class AClass(object): @classmethod def a_func(cls): pass """) a_class = libutils.get_string_module(self.project, code)["AClass"].get_object() function_scope = a_class["a_func"].get_object().get_scope() self.assertEqual(a_class, function_scope["cls"].get_object()) def test_first_parameter_with_self_as_name_and_unknown_decorator(self): code = dedent("""\ def my_decorator(func): return func class AClass(object): @my_decorator def a_func(self): pass """) a_class = libutils.get_string_module(self.project, code)["AClass"].get_object() function_scope = a_class["a_func"].get_object().get_scope() self.assertEqual(a_class, function_scope["self"].get_object().get_type()) def test_inside_class_scope_attribute_lookup(self): code = dedent("""\ class C(object): an_attr = 1 def a_func(self): pass""") scope = libutils.get_string_scope(self.project, code) self.assertEqual(1, len(scope.get_scopes())) c_scope = scope.get_scopes()[0] self.assertTrue("an_attr" in c_scope.get_names()) self.assertTrue(c_scope.lookup("an_attr") is not None) f_in_c = c_scope.get_scopes()[0] self.assertTrue(f_in_c.lookup("an_attr") is None) def test_inside_class_scope_attribute_lookup2(self): code = dedent("""\ class C(object): def __init__(self): self.an_attr = 1 def a_func(self): pass""") scope = libutils.get_string_scope(self.project, code) self.assertEqual(1, len(scope.get_scopes())) c_scope = scope.get_scopes()[0] f_in_c = c_scope.get_scopes()[0] self.assertTrue(f_in_c.lookup("an_attr") is None) def test_get_inner_scope_for_staticmethods(self): code = dedent("""\ class C(object): @staticmethod def a_func(self): pass """) scope = libutils.get_string_scope(self.project, code) c_scope = scope.get_scopes()[0] f_in_c = c_scope.get_scopes()[0] self.assertEqual(f_in_c, scope.get_inner_scope_for_line(4)) def test_get_scope_for_offset_for_comprehension(self): code = "a = [i for i in range(10)]\n" scope = libutils.get_string_scope(self.project, code) c_scope = scope.get_scopes()[0] self.assertEqual(c_scope, scope.get_inner_scope_for_offset(10)) self.assertEqual(scope, scope.get_inner_scope_for_offset(1)) def test_get_scope_for_offset_for_in_nested_comprehension(self): code = "[i for i in [j for j in k]]\n" scope = libutils.get_string_scope(self.project, code) c_scope = scope.get_scopes()[0] self.assertEqual(c_scope, scope.get_inner_scope_for_offset(5)) inner_scope = c_scope.get_scopes()[0] self.assertEqual(inner_scope, scope.get_inner_scope_for_offset(15)) def test_get_scope_for_offset_for_scope_with_indent(self): code = dedent("""\ def f(a): print(a) """) scope = libutils.get_string_scope(self.project, code) inner_scope = scope.get_scopes()[0] self.assertEqual(inner_scope, scope.get_inner_scope_for_offset(10)) def test_get_scope_for_offset_for_function_scope_and_async_with_statement(self): scope = libutils.get_string_scope( self.project, dedent("""\ async def func(): async with a_func() as var: print(var) """), ) inner_scope = scope.get_scopes()[0] self.assertEqual(inner_scope, scope.get_inner_scope_for_offset(27)) def test_getting_overwritten_scopes(self): code = dedent("""\ def f(): pass def f(): pass """) scope = libutils.get_string_scope(self.project, code) self.assertEqual(2, len(scope.get_scopes())) f1_scope = scope.get_scopes()[0] f2_scope = scope.get_scopes()[1] self.assertNotEqual(f1_scope, f2_scope) def test_assigning_builtin_names(self): code = "range = 1\n" mod = libutils.get_string_module(self.project, code) range = mod.get_scope().lookup("range") self.assertEqual((mod, 1), range.get_definition_location()) def test_get_inner_scope_and_logical_lines(self): code = dedent('''\ class C(object): def f(): s = """ 1 2 """ a = 1 ''') scope = libutils.get_string_scope(self.project, code) c_scope = scope.get_scopes()[0] f_in_c = c_scope.get_scopes()[0] self.assertEqual(f_in_c, scope.get_inner_scope_for_line(7)) def test_getting_defined_names_for_classes(self): code = dedent("""\ class A(object): def a(self): pass class B(A): def b(self): pass """) scope = libutils.get_string_scope(self.project, code) a_scope = scope["A"].get_object().get_scope() # noqa b_scope = scope["B"].get_object().get_scope() self.assertTrue("a" in b_scope.get_names()) self.assertTrue("b" in b_scope.get_names()) self.assertTrue("a" not in b_scope.get_defined_names()) self.assertTrue("b" in b_scope.get_defined_names()) def test_getting_defined_names_for_modules(self): code = dedent("""\ class A(object): pass """) scope = libutils.get_string_scope(self.project, code) self.assertTrue("open" in scope.get_names()) self.assertTrue("A" in scope.get_names()) self.assertTrue("open" not in scope.get_defined_names()) self.assertTrue("A" in scope.get_defined_names()) def test_get_inner_scope_for_list_comprhension_with_many_targets(self): code = "a = [(i, j) for i,j in enumerate(range(10))]\n" scope = libutils.get_string_scope(self.project, code) self.assertEqual(len(scope.get_scopes()), 1) self.assertNotIn("i", scope) self.assertNotIn("j", scope) self.assertIn("i", scope.get_scopes()[0]) self.assertIn("j", scope.get_scopes()[0]) def test_get_inner_scope_for_generator(self): code = "a = (i for i in range(10))\n" scope = libutils.get_string_scope(self.project, code) self.assertEqual(len(scope.get_scopes()), 1) self.assertNotIn("i", scope) self.assertIn("i", scope.get_scopes()[0]) def test_get_inner_scope_for_set_comprehension(self): code = "a = {i for i in range(10)}\n" scope = libutils.get_string_scope(self.project, code) self.assertEqual(len(scope.get_scopes()), 1) self.assertNotIn("i", scope) self.assertIn("i", scope.get_scopes()[0]) def test_get_inner_scope_for_dict_comprehension(self): code = "a = {i:i for i in range(10)}\n" scope = libutils.get_string_scope(self.project, code) self.assertEqual(len(scope.get_scopes()), 1) self.assertNotIn("i", scope) self.assertIn("i", scope.get_scopes()[0]) def test_get_inner_scope_for_nested_list_comprhension(self): code = "a = [[i + j for j in range(10)] for i in range(10)]\n" scope = libutils.get_string_scope(self.project, code) self.assertEqual(len(scope.get_scopes()), 1) self.assertNotIn("i", scope) self.assertNotIn("j", scope) self.assertIn("i", scope.get_scopes()[0]) self.assertEqual(len(scope.get_scopes()[0].get_scopes()), 1) self.assertIn("j", scope.get_scopes()[0].get_scopes()[0]) self.assertIn("i", scope.get_scopes()[0].get_scopes()[0]) def test_get_scope_region(self): scope = libutils.get_string_scope( self.project, dedent(""" def func1(ala): pass def func2(o): pass """), ) self.assertEqual(scope.get_region(), (0, 48)) self.assertEqual(scope.get_scopes()[0].get_region(), (1, 24)) self.assertEqual(scope.get_scopes()[1].get_region(), (26, 47)) def test_only_get_inner_scope_region(self): scope = libutils.get_string_scope( self.project, dedent(""" def func1(ala): pass def func2(o): pass """), ) self.assertEqual(scope.get_scopes()[1].get_region(), (26, 47)) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1705553002.7139843 rope-1.12.0/ropetest/refactor/0000775000175000017500000000000014552126153016150 5ustar00lieryanlieryan././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/ropetest/refactor/__init__.py0000664000175000017500000011327514512700666020275 0ustar00lieryanlieryanimport unittest from textwrap import dedent import rope.base.taskhandle import rope.refactor.introduce_parameter import ropetest.refactor.extracttest import ropetest.refactor.importutilstest import ropetest.refactor.inlinetest import ropetest.refactor.movetest import ropetest.refactor.multiprojecttest import ropetest.refactor.patchedasttest import ropetest.refactor.renametest import ropetest.refactor.restructuretest import ropetest.refactor.suitestest import ropetest.refactor.usefunctiontest from rope.base.exceptions import InterruptedTaskError, RefactoringError from rope.refactor.encapsulate_field import EncapsulateField from rope.refactor.introduce_factory import IntroduceFactory from rope.refactor.localtofield import LocalToField from rope.refactor.method_object import MethodObject from ropetest import testutils from ropetest.refactor import change_signature_test, similarfindertest class MethodObjectTest(unittest.TestCase): def setUp(self): super().setUp() self.project = testutils.sample_project() self.pycore = self.project.pycore self.mod = testutils.create_module(self.project, "mod") def tearDown(self): testutils.remove_project(self.project) super().tearDown() def test_empty_method(self): code = dedent("""\ def func(): pass """) self.mod.write(code) replacer = MethodObject(self.project, self.mod, code.index("func")) expected = dedent("""\ class _New(object): def __call__(self): pass """) self.assertEqual( expected, replacer.get_new_class("_New"), ) def test_trivial_return(self): code = dedent("""\ def func(): return 1 """) self.mod.write(code) replacer = MethodObject(self.project, self.mod, code.index("func")) expected = dedent("""\ class _New(object): def __call__(self): return 1 """) self.assertEqual( expected, replacer.get_new_class("_New"), ) def test_multi_line_header(self): code = dedent("""\ def func( ): return 1 """) self.mod.write(code) replacer = MethodObject(self.project, self.mod, code.index("func")) expected = dedent("""\ class _New(object): def __call__(self): return 1 """) self.assertEqual( expected, replacer.get_new_class("_New"), ) def test_a_single_parameter(self): code = dedent("""\ def func(param): return 1 """) self.mod.write(code) replacer = MethodObject(self.project, self.mod, code.index("func")) expected = dedent("""\ class _New(object): def __init__(self, param): self.param = param def __call__(self): return 1 """) self.assertEqual( expected, replacer.get_new_class("_New"), ) def test_self_parameter(self): code = dedent("""\ def func(self): return 1 """) self.mod.write(code) replacer = MethodObject(self.project, self.mod, code.index("func")) expected = dedent("""\ class _New(object): def __init__(self, host): self.self = host def __call__(self): return 1 """) self.assertEqual( expected, replacer.get_new_class("_New"), ) def test_simple_using_passed_parameters(self): code = dedent("""\ def func(param): return param """) self.mod.write(code) replacer = MethodObject(self.project, self.mod, code.index("func")) expected = dedent("""\ class _New(object): def __init__(self, param): self.param = param def __call__(self): return self.param """) self.assertEqual( expected, replacer.get_new_class("_New"), ) def test_self_keywords_and_args_parameters(self): code = dedent("""\ def func(arg, *args, **kwds): result = arg + args[0] + kwds[arg] return result """) self.mod.write(code) replacer = MethodObject(self.project, self.mod, code.index("func")) expected = dedent("""\ class _New(object): def __init__(self, arg, args, kwds): self.arg = arg self.args = args self.kwds = kwds def __call__(self): result = self.arg + self.args[0] + self.kwds[self.arg] return result """) self.assertEqual(expected, replacer.get_new_class("_New")) def test_performing_on_not_a_function(self): code = dedent("""\ my_var = 10 """) self.mod.write(code) with self.assertRaises(RefactoringError): MethodObject(self.project, self.mod, code.index("my_var")) def test_changing_the_module(self): code = dedent("""\ def func(): return 1 """) self.mod.write(code) replacer = MethodObject(self.project, self.mod, code.index("func")) self.project.do(replacer.get_changes("_New")) expected = dedent("""\ def func(): return _New()() class _New(object): def __call__(self): return 1 """) self.assertEqual(expected, self.mod.read()) def test_changing_the_module_and_class_methods(self): code = dedent("""\ class C(object): def a_func(self): return 1 def another_func(self): pass """) self.mod.write(code) replacer = MethodObject(self.project, self.mod, code.index("func")) self.project.do(replacer.get_changes("_New")) expected = dedent("""\ class C(object): def a_func(self): return _New(self)() def another_func(self): pass class _New(object): def __init__(self, host): self.self = host def __call__(self): return 1 """) self.assertEqual(expected, self.mod.read()) class IntroduceFactoryTest(unittest.TestCase): def setUp(self): super().setUp() self.project = testutils.sample_project() self.pycore = self.project.pycore def tearDown(self): testutils.remove_project(self.project) super().tearDown() def _introduce_factory(self, resource, offset, *args, **kwds): factory_introducer = IntroduceFactory(self.project, resource, offset) changes = factory_introducer.get_changes(*args, **kwds) self.project.do(changes) def test_adding_the_method(self): code = dedent("""\ class AClass(object): an_attr = 10 """) mod = testutils.create_module(self.project, "mod") mod.write(code) expected = dedent("""\ class AClass(object): an_attr = 10 @staticmethod def create(*args, **kwds): return AClass(*args, **kwds) """) self._introduce_factory(mod, mod.read().index("AClass") + 1, "create") self.assertEqual(expected, mod.read()) def test_changing_occurrences_in_the_main_module(self): code = dedent("""\ class AClass(object): an_attr = 10 a_var = AClass()""") mod = testutils.create_module(self.project, "mod") mod.write(code) expected = dedent("""\ class AClass(object): an_attr = 10 @staticmethod def create(*args, **kwds): return AClass(*args, **kwds) a_var = AClass.create()""") self._introduce_factory(mod, mod.read().index("AClass") + 1, "create") self.assertEqual(expected, mod.read()) def test_changing_occurrences_with_arguments(self): code = dedent("""\ class AClass(object): def __init__(self, arg): pass a_var = AClass(10) """) mod = testutils.create_module(self.project, "mod") mod.write(code) expected = dedent("""\ class AClass(object): def __init__(self, arg): pass @staticmethod def create(*args, **kwds): return AClass(*args, **kwds) a_var = AClass.create(10) """) self._introduce_factory(mod, mod.read().index("AClass") + 1, "create") self.assertEqual(expected, mod.read()) def test_changing_occurrences_in_other_modules(self): mod1 = testutils.create_module(self.project, "mod1") mod2 = testutils.create_module(self.project, "mod2") mod1.write("class AClass(object):\n an_attr = 10\n") mod2.write("import mod1\na_var = mod1.AClass()\n") self._introduce_factory(mod1, mod1.read().index("AClass") + 1, "create") expected1 = dedent("""\ class AClass(object): an_attr = 10 @staticmethod def create(*args, **kwds): return AClass(*args, **kwds) """) expected2 = dedent("""\ import mod1 a_var = mod1.AClass.create() """) self.assertEqual(expected1, mod1.read()) self.assertEqual(expected2, mod2.read()) def test_raising_exception_for_non_classes(self): mod = testutils.create_module(self.project, "mod") mod.write("def a_func():\n pass\n") with self.assertRaises(RefactoringError): self._introduce_factory(mod, mod.read().index("a_func") + 1, "create") def test_undoing_introduce_factory(self): mod1 = testutils.create_module(self.project, "mod1") mod2 = testutils.create_module(self.project, "mod2") code1 = dedent("""\ class AClass(object): an_attr = 10 """) mod1.write(code1) code2 = dedent("""\ from mod1 import AClass a_var = AClass() """) mod2.write(code2) self._introduce_factory(mod1, mod1.read().index("AClass") + 1, "create") self.project.history.undo() self.assertEqual(code1, mod1.read()) self.assertEqual(code2, mod2.read()) def test_using_on_an_occurrence_outside_the_main_module(self): mod1 = testutils.create_module(self.project, "mod1") mod2 = testutils.create_module(self.project, "mod2") mod1.write("class AClass(object):\n an_attr = 10\n") mod2.write("import mod1\na_var = mod1.AClass()\n") self._introduce_factory(mod2, mod2.read().index("AClass") + 1, "create") expected1 = dedent("""\ class AClass(object): an_attr = 10 @staticmethod def create(*args, **kwds): return AClass(*args, **kwds) """) expected2 = "import mod1\n" "a_var = mod1.AClass.create()\n" self.assertEqual(expected1, mod1.read()) self.assertEqual(expected2, mod2.read()) def test_introduce_factory_in_nested_scopes(self): code = dedent("""\ def create_var(): class AClass(object): an_attr = 10 return AClass() """) mod = testutils.create_module(self.project, "mod") mod.write(code) expected = dedent("""\ def create_var(): class AClass(object): an_attr = 10 @staticmethod def create(*args, **kwds): return AClass(*args, **kwds) return AClass.create() """) self._introduce_factory(mod, mod.read().index("AClass") + 1, "create") self.assertEqual(expected, mod.read()) def test_adding_factory_for_global_factories(self): code = dedent("""\ class AClass(object): an_attr = 10 """) mod = testutils.create_module(self.project, "mod") mod.write(code) expected = dedent("""\ class AClass(object): an_attr = 10 def create(*args, **kwds): return AClass(*args, **kwds) """) self._introduce_factory( mod, mod.read().index("AClass") + 1, "create", global_factory=True ) self.assertEqual(expected, mod.read()) def test_get_name_for_factories(self): code = dedent("""\ class C(object): pass """) mod = testutils.create_module(self.project, "mod") mod.write(code) factory = IntroduceFactory(self.project, mod, mod.read().index("C") + 1) self.assertEqual("C", factory.get_name()) def test_raising_exception_for_global_factory_for_nested_classes(self): code = dedent("""\ def create_var(): class AClass(object): an_attr = 10 return AClass() """) mod = testutils.create_module(self.project, "mod") mod.write(code) with self.assertRaises(RefactoringError): self._introduce_factory( mod, mod.read().index("AClass") + 1, "create", global_factory=True ) def test_changing_occurrences_in_the_main_module_for_global_factories(self): code = dedent("""\ class AClass(object): an_attr = 10 a_var = AClass()""") mod = testutils.create_module(self.project, "mod") mod.write(code) expected = dedent("""\ class AClass(object): an_attr = 10 def create(*args, **kwds): return AClass(*args, **kwds) a_var = create()""") self._introduce_factory( mod, mod.read().index("AClass") + 1, "create", global_factory=True ) self.assertEqual(expected, mod.read()) def test_changing_occurrences_in_other_modules_for_global_factories(self): mod1 = testutils.create_module(self.project, "mod1") mod2 = testutils.create_module(self.project, "mod2") mod1.write("class AClass(object):\n an_attr = 10\n") mod2.write("import mod1\na_var = mod1.AClass()\n") self._introduce_factory( mod1, mod1.read().index("AClass") + 1, "create", global_factory=True ) expected1 = dedent("""\ class AClass(object): an_attr = 10 def create(*args, **kwds): return AClass(*args, **kwds) """) expected2 = "import mod1\n" "a_var = mod1.create()\n" self.assertEqual(expected1, mod1.read()) self.assertEqual(expected2, mod2.read()) def test_import_if_necessary_in_other_mods_for_global_factories(self): mod1 = testutils.create_module(self.project, "mod1") mod2 = testutils.create_module(self.project, "mod2") mod1.write("class AClass(object):\n an_attr = 10\n") mod2.write("from mod1 import AClass\npair = AClass(), AClass\n") self._introduce_factory( mod1, mod1.read().index("AClass") + 1, "create", global_factory=True ) expected1 = dedent("""\ class AClass(object): an_attr = 10 def create(*args, **kwds): return AClass(*args, **kwds) """) expected2 = dedent("""\ from mod1 import AClass, create pair = create(), AClass """) self.assertEqual(expected1, mod1.read()) self.assertEqual(expected2, mod2.read()) def test_changing_occurrences_for_renamed_classes(self): code = dedent("""\ class AClass(object): an_attr = 10 a_class = AClass a_var = a_class()""") mod = testutils.create_module(self.project, "mod") mod.write(code) expected = dedent("""\ class AClass(object): an_attr = 10 @staticmethod def create(*args, **kwds): return AClass(*args, **kwds) a_class = AClass a_var = a_class()""") self._introduce_factory(mod, mod.read().index("a_class") + 1, "create") self.assertEqual(expected, mod.read()) def test_changing_occurs_in_the_same_module_with_conflict_ranges(self): mod = testutils.create_module(self.project, "mod") code = dedent("""\ class C(object): def create(self): return C() """) mod.write(code) self._introduce_factory(mod, mod.read().index("C"), "create_c", True) expected = dedent("""\ class C(object): def create(self): return create_c() """) self.assertTrue(mod.read().startswith(expected)) def _transform_module_to_package(self, resource): self.project.do( rope.refactor.ModuleToPackage(self.project, resource).get_changes() ) def test_transform_module_to_package(self): mod1 = testutils.create_module(self.project, "mod1") mod1.write("import mod2\nfrom mod2 import AClass\n") mod2 = testutils.create_module(self.project, "mod2") mod2.write("class AClass(object):\n pass\n") self._transform_module_to_package(mod2) mod2 = self.project.get_resource("mod2") root_folder = self.project.root self.assertFalse(root_folder.has_child("mod2.py")) self.assertEqual( "class AClass(object):\n pass\n", root_folder.get_child("mod2").get_child("__init__.py").read(), ) def test_transform_module_to_package_undoing(self): pkg = testutils.create_package(self.project, "pkg") mod = testutils.create_module(self.project, "mod", pkg) self._transform_module_to_package(mod) self.assertFalse(pkg.has_child("mod.py")) self.assertTrue(pkg.get_child("mod").has_child("__init__.py")) self.project.history.undo() self.assertTrue(pkg.has_child("mod.py")) self.assertFalse(pkg.has_child("mod")) def test_transform_module_to_package_with_relative_imports(self): pkg = testutils.create_package(self.project, "pkg") mod1 = testutils.create_module(self.project, "mod1", pkg) mod1.write("import mod2\nfrom mod2 import AClass\n") mod2 = testutils.create_module(self.project, "mod2", pkg) mod2.write("class AClass(object):\n pass\n") self._transform_module_to_package(mod1) new_init = self.project.get_resource("pkg/mod1/__init__.py") self.assertEqual( "import pkg.mod2\nfrom pkg.mod2 import AClass\n", new_init.read() ) def test_resources_parameter(self): code = dedent("""\ class A(object): an_attr = 10 """) code1 = dedent("""\ import mod a = mod.A() """) mod = testutils.create_module(self.project, "mod") mod1 = testutils.create_module(self.project, "mod1") mod.write(code) mod1.write(code1) expected = dedent("""\ class A(object): an_attr = 10 @staticmethod def create(*args, **kwds): return A(*args, **kwds) """) self._introduce_factory( mod, mod.read().index("A") + 1, "create", resources=[mod] ) self.assertEqual(expected, mod.read()) self.assertEqual(code1, mod1.read()) class EncapsulateFieldTest(unittest.TestCase): def setUp(self): super().setUp() self.project = testutils.sample_project() self.pycore = self.project.pycore self.mod = testutils.create_module(self.project, "mod") self.mod1 = testutils.create_module(self.project, "mod1") self.a_class = dedent("""\ class A(object): def __init__(self): self.attr = 1 """) self.added_methods = ( "\n" " def get_attr(self):\n" " return self.attr\n\n" " def set_attr(self, value):\n" " self.attr = value\n" ) self.encapsulated = self.a_class + self.added_methods def tearDown(self): testutils.remove_project(self.project) super().tearDown() def _encapsulate(self, resource, offset, **args): changes = EncapsulateField(self.project, resource, offset).get_changes(**args) self.project.do(changes) def test_adding_getters_and_setters(self): code = self.a_class self.mod.write(code) self._encapsulate(self.mod, code.index("attr") + 1) self.assertEqual(self.encapsulated, self.mod.read()) def test_changing_getters_in_other_modules(self): code = dedent("""\ import mod a_var = mod.A() range(a_var.attr) """) self.mod1.write(code) self.mod.write(self.a_class) self._encapsulate(self.mod, self.mod.read().index("attr") + 1) expected = dedent("""\ import mod a_var = mod.A() range(a_var.get_attr()) """) self.assertEqual(expected, self.mod1.read()) def test_changing_setters_in_other_modules(self): code = dedent("""\ import mod a_var = mod.A() a_var.attr = 1 """) self.mod1.write(code) self.mod.write(self.a_class) self._encapsulate(self.mod, self.mod.read().index("attr") + 1) expected = dedent("""\ import mod a_var = mod.A() a_var.set_attr(1) """) self.assertEqual(expected, self.mod1.read()) def test_changing_getters_in_setters(self): code = dedent("""\ import mod a_var = mod.A() a_var.attr = 1 + a_var.attr """) self.mod1.write(code) self.mod.write(self.a_class) self._encapsulate(self.mod, self.mod.read().index("attr") + 1) expected = dedent("""\ import mod a_var = mod.A() a_var.set_attr(1 + a_var.get_attr()) """) self.assertEqual(expected, self.mod1.read()) def test_appending_to_class_end(self): self.mod1.write(self.a_class + "a_var = A()\n") self._encapsulate(self.mod1, self.mod1.read().index("attr") + 1) self.assertEqual(self.encapsulated + "a_var = A()\n", self.mod1.read()) def test_performing_in_other_modules(self): code = dedent("""\ import mod a_var = mod.A() range(a_var.attr) """) self.mod1.write(code) self.mod.write(self.a_class) self._encapsulate(self.mod1, self.mod1.read().index("attr") + 1) self.assertEqual(self.encapsulated, self.mod.read()) expected = dedent("""\ import mod a_var = mod.A() range(a_var.get_attr()) """) self.assertEqual(expected, self.mod1.read()) def test_changing_main_module_occurrences(self): code = self.a_class + "a_var = A()\n" "a_var.attr = a_var.attr * 2\n" self.mod1.write(code) self._encapsulate(self.mod1, self.mod1.read().index("attr") + 1) expected = ( self.encapsulated + "a_var = A()\n" "a_var.set_attr(a_var.get_attr() * 2)\n" ) self.assertEqual(expected, self.mod1.read()) def test_raising_exception_when_performed_on_non_attributes(self): self.mod1.write("attr = 10") with self.assertRaises(RefactoringError): self._encapsulate(self.mod1, self.mod1.read().index("attr") + 1) def test_raising_exception_on_tuple_assignments(self): self.mod.write(self.a_class) code = dedent("""\ import mod a_var = mod.A() a_var.attr = 1 a_var.attr, b = 1, 2 """) self.mod1.write(code) with self.assertRaises(RefactoringError): self._encapsulate(self.mod1, self.mod1.read().index("attr") + 1) def test_raising_exception_on_tuple_assignments2(self): self.mod.write(self.a_class) code = dedent("""\ import mod a_var = mod.A() a_var.attr = 1 b, a_var.attr = 1, 2 """) self.mod1.write(code) with self.assertRaises(RefactoringError): self._encapsulate(self.mod1, self.mod1.read().index("attr") + 1) def test_tuple_assignments_and_function_calls(self): code = dedent("""\ import mod def func(a1=0, a2=0): pass a_var = mod.A() func(a_var.attr, a2=2) """) self.mod1.write(code) self.mod.write(self.a_class) self._encapsulate(self.mod, self.mod.read().index("attr") + 1) expected = dedent("""\ import mod def func(a1=0, a2=0): pass a_var = mod.A() func(a_var.get_attr(), a2=2) """) self.assertEqual(expected, self.mod1.read()) def test_tuple_assignments(self): code = dedent("""\ import mod a_var = mod.A() a, b = a_var.attr, 1 """) self.mod1.write(code) self.mod.write(self.a_class) self._encapsulate(self.mod, self.mod.read().index("attr") + 1) expected = dedent("""\ import mod a_var = mod.A() a, b = a_var.get_attr(), 1 """) self.assertEqual(expected, self.mod1.read()) def test_changing_augmented_assignments(self): code = "import mod\n" "a_var = mod.A()\n" "a_var.attr += 1\n" self.mod1.write(code) self.mod.write(self.a_class) self._encapsulate(self.mod, self.mod.read().index("attr") + 1) expected = dedent("""\ import mod a_var = mod.A() a_var.set_attr(a_var.get_attr() + 1) """) self.assertEqual(expected, self.mod1.read()) def test_changing_augmented_assignments2(self): code = dedent("""\ import mod a_var = mod.A() a_var.attr <<= 1 """) self.mod1.write(code) self.mod.write(self.a_class) self._encapsulate(self.mod, self.mod.read().index("attr") + 1) expected = dedent("""\ import mod a_var = mod.A() a_var.set_attr(a_var.get_attr() << 1) """) self.assertEqual(expected, self.mod1.read()) def test_changing_occurrences_inside_the_class(self): new_class = ( self.a_class + "\n" " def a_func(self):\n" " self.attr = 1\n" ) self.mod.write(new_class) self._encapsulate(self.mod, self.mod.read().index("attr") + 1) expected = ( self.a_class + "\n" " def a_func(self):\n" " self.set_attr(1)\n" + self.added_methods ) self.assertEqual(expected, self.mod.read()) def test_getter_and_setter_parameters(self): self.mod.write(self.a_class) self._encapsulate( self.mod, self.mod.read().index("attr") + 1, getter="getAttr", setter="setAttr", ) new_methods = self.added_methods.replace("get_attr", "getAttr").replace( "set_attr", "setAttr" ) expected = self.a_class + new_methods self.assertEqual(expected, self.mod.read()) def test_using_resources_parameter(self): self.mod1.write("import mod\na = mod.A()\nvar = a.attr\n") self.mod.write(self.a_class) self._encapsulate( self.mod, self.mod.read().index("attr") + 1, resources=[self.mod] ) self.assertEqual("import mod\na = mod.A()\nvar = a.attr\n", self.mod1.read()) expected = self.a_class + self.added_methods self.assertEqual(expected, self.mod.read()) class LocalToFieldTest(unittest.TestCase): def setUp(self): super().setUp() self.project = testutils.sample_project() self.pycore = self.project.pycore self.mod = testutils.create_module(self.project, "mod") def tearDown(self): testutils.remove_project(self.project) super().tearDown() def _perform_convert_local_variable_to_field(self, resource, offset): changes = LocalToField(self.project, resource, offset).get_changes() self.project.do(changes) def test_simple_local_to_field(self): code = dedent("""\ class A(object): def a_func(self): var = 10 """) self.mod.write(code) self._perform_convert_local_variable_to_field(self.mod, code.index("var") + 1) expected = dedent("""\ class A(object): def a_func(self): self.var = 10 """) self.assertEqual(expected, self.mod.read()) def test_raising_exception_when_performed_on_a_global_var(self): self.mod.write("var = 10\n") with self.assertRaises(RefactoringError): self._perform_convert_local_variable_to_field( self.mod, self.mod.read().index("var") + 1 ) def test_raising_exception_when_performed_on_field(self): code = dedent("""\ class A(object): def a_func(self): self.var = 10 """) self.mod.write(code) with self.assertRaises(RefactoringError): self._perform_convert_local_variable_to_field( self.mod, self.mod.read().index("var") + 1 ) def test_raising_exception_when_performed_on_a_parameter(self): code = dedent("""\ class A(object): def a_func(self, var): a = var """) self.mod.write(code) with self.assertRaises(RefactoringError): self._perform_convert_local_variable_to_field( self.mod, self.mod.read().index("var") + 1 ) # NOTE: This situation happens a lot and is normally not an error # @testutils.assert_raises(RefactoringError) def test_not_raise_exception_when_there_is_a_field_with_the_same_name(self): code = dedent("""\ class A(object): def __init__(self): self.var = 1 def a_func(self): var = 10 """) self.mod.write(code) self._perform_convert_local_variable_to_field( self.mod, self.mod.read().rindex("var") + 1 ) def test_local_to_field_with_self_renamed(self): code = dedent("""\ class A(object): def a_func(myself): var = 10 """) self.mod.write(code) self._perform_convert_local_variable_to_field(self.mod, code.index("var") + 1) expected = dedent("""\ class A(object): def a_func(myself): myself.var = 10 """) self.assertEqual(expected, self.mod.read()) class IntroduceParameterTest(unittest.TestCase): def setUp(self): super().setUp() self.project = testutils.sample_project() self.pycore = self.project.pycore self.mod = testutils.create_module(self.project, "mod") def tearDown(self): testutils.remove_project(self.project) super().tearDown() def _introduce_parameter(self, offset, name): rope.refactor.introduce_parameter.IntroduceParameter( self.project, self.mod, offset ).get_changes(name).do() def test_simple_case(self): code = dedent("""\ var = 1 def f(): b = var """) self.mod.write(code) offset = self.mod.read().rindex("var") self._introduce_parameter(offset, "var") expected = dedent("""\ var = 1 def f(var=var): b = var """) self.assertEqual(expected, self.mod.read()) def test_changing_function_body(self): code = dedent("""\ var = 1 def f(): b = var """) self.mod.write(code) offset = self.mod.read().rindex("var") self._introduce_parameter(offset, "p1") expected = dedent("""\ var = 1 def f(p1=var): b = p1 """) self.assertEqual(expected, self.mod.read()) def test_unknown_variables(self): self.mod.write("def f():\n b = var + c\n") offset = self.mod.read().rindex("var") with self.assertRaises(RefactoringError): self._introduce_parameter(offset, "p1") self.assertEqual("def f(p1=var):\n b = p1 + c\n", self.mod.read()) def test_failing_when_not_inside(self): self.mod.write("var = 10\nb = var\n") offset = self.mod.read().rindex("var") with self.assertRaises(RefactoringError): self._introduce_parameter(offset, "p1") def test_attribute_accesses(self): code = dedent("""\ class C(object): a = 10 c = C() def f(): b = c.a """) self.mod.write(code) offset = self.mod.read().rindex("a") self._introduce_parameter(offset, "p1") expected = dedent("""\ class C(object): a = 10 c = C() def f(p1=c.a): b = p1 """) self.assertEqual(expected, self.mod.read()) def test_introducing_parameters_for_methods(self): code = dedent("""\ var = 1 class C(object): def f(self): b = var """) self.mod.write(code) offset = self.mod.read().rindex("var") self._introduce_parameter(offset, "p1") expected = dedent("""\ var = 1 class C(object): def f(self, p1=var): b = p1 """) self.assertEqual(expected, self.mod.read()) class _MockTaskObserver: def __init__(self): self.called = 0 def __call__(self): self.called += 1 class TaskHandleTest(unittest.TestCase): def test_trivial_case(self): handle = rope.base.taskhandle.TaskHandle() self.assertFalse(handle.is_stopped()) def test_stopping(self): handle = rope.base.taskhandle.TaskHandle() handle.stop() self.assertTrue(handle.is_stopped()) def test_job_sets(self): handle = rope.base.taskhandle.TaskHandle() jobs = handle.create_jobset() self.assertEqual([jobs], handle.get_jobsets()) def test_starting_and_finishing_jobs(self): handle = rope.base.taskhandle.TaskHandle() jobs = handle.create_jobset(name="test job set", count=1) jobs.started_job("job1") jobs.finished_job() def test_test_checking_status(self): handle = rope.base.taskhandle.TaskHandle() jobs = handle.create_jobset() handle.stop() with self.assertRaises(InterruptedTaskError): jobs.check_status() def test_test_checking_status_when_starting(self): handle = rope.base.taskhandle.TaskHandle() jobs = handle.create_jobset() handle.stop() with self.assertRaises(InterruptedTaskError): jobs.started_job("job1") def test_calling_the_observer_after_stopping(self): handle = rope.base.taskhandle.TaskHandle() observer = _MockTaskObserver() handle.add_observer(observer) handle.stop() self.assertEqual(1, observer.called) def test_calling_the_observer_after_creating_job_sets(self): handle = rope.base.taskhandle.TaskHandle() observer = _MockTaskObserver() handle.add_observer(observer) jobs = handle.create_jobset() # noqa self.assertEqual(1, observer.called) def test_calling_the_observer_when_starting_and_finishing_jobs(self): handle = rope.base.taskhandle.TaskHandle() observer = _MockTaskObserver() handle.add_observer(observer) jobs = handle.create_jobset(name="test job set", count=1) jobs.started_job("job1") jobs.finished_job() self.assertEqual(3, observer.called) def test_job_set_get_percent_done(self): handle = rope.base.taskhandle.TaskHandle() jobs = handle.create_jobset(name="test job set", count=2) self.assertEqual(0, jobs.get_percent_done()) jobs.started_job("job1") jobs.finished_job() self.assertEqual(50, jobs.get_percent_done()) jobs.started_job("job2") jobs.finished_job() self.assertEqual(100, jobs.get_percent_done()) def test_getting_job_name(self): handle = rope.base.taskhandle.TaskHandle() jobs = handle.create_jobset(name="test job set", count=1) # recommended name/job_name attribute self.assertEqual("test job set", jobs.name) self.assertEqual(None, jobs.job_name) # deprecated getters self.assertEqual("test job set", jobs.get_name()) self.assertEqual(None, jobs.get_active_job_name()) jobs.started_job("job1") # recommended name/job_name attribute self.assertEqual("test job set", jobs.get_name()) # deprecated getters self.assertEqual("job1", jobs.get_active_job_name()) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/ropetest/refactor/change_signature_test.py0000664000175000017500000006475114512700666023107 0ustar00lieryanlieryanimport unittest from textwrap import dedent import rope.base.exceptions from rope.refactor import change_signature from ropetest import testutils class ChangeSignatureTest(unittest.TestCase): def setUp(self): super().setUp() self.project = testutils.sample_project() self.pycore = self.project.pycore self.mod = testutils.create_module(self.project, "mod") def tearDown(self): testutils.remove_project(self.project) super().tearDown() def test_normalizing_parameters_for_trivial_case(self): code = dedent("""\ def a_func(): pass a_func()""") self.mod.write(code) signature = change_signature.ChangeSignature( self.project, self.mod, self.mod.read().index("a_func") + 1 ) self.project.do(signature.get_changes([change_signature.ArgumentNormalizer()])) self.assertEqual(code, self.mod.read()) def test_normalizing_parameters_for_trivial_case2(self): code = dedent("""\ def a_func(param): pass a_func(2)""") self.mod.write(code) signature = change_signature.ChangeSignature( self.project, self.mod, self.mod.read().index("a_func") + 1 ) self.project.do(signature.get_changes([change_signature.ArgumentNormalizer()])) self.assertEqual(code, self.mod.read()) def test_normalizing_parameters_for_unneeded_keyword(self): self.mod.write(dedent("""\ def a_func(param): pass a_func(param=1)""")) signature = change_signature.ChangeSignature( self.project, self.mod, self.mod.read().index("a_func") + 1 ) self.project.do(signature.get_changes([change_signature.ArgumentNormalizer()])) self.assertEqual( dedent("""\ def a_func(param): pass a_func(1)"""), self.mod.read(), ) def test_normalizing_parameters_for_unneeded_keyword_for_methods(self): code = dedent("""\ class A(object): def a_func(self, param): pass a_var = A() a_var.a_func(param=1) """) self.mod.write(code) signature = change_signature.ChangeSignature( self.project, self.mod, self.mod.read().index("a_func") + 1 ) self.project.do(signature.get_changes([change_signature.ArgumentNormalizer()])) expected = dedent("""\ class A(object): def a_func(self, param): pass a_var = A() a_var.a_func(1) """) self.assertEqual(expected, self.mod.read()) def test_normalizing_parameters_for_unsorted_keyword(self): self.mod.write(dedent("""\ def a_func(p1, p2): pass a_func(p2=2, p1=1)""")) signature = change_signature.ChangeSignature( self.project, self.mod, self.mod.read().index("a_func") + 1 ) self.project.do(signature.get_changes([change_signature.ArgumentNormalizer()])) self.assertEqual( dedent("""\ def a_func(p1, p2): pass a_func(1, 2)"""), self.mod.read(), ) def test_raising_exceptions_for_non_functions(self): self.mod.write("a_var = 10") with self.assertRaises(rope.base.exceptions.RefactoringError): change_signature.ChangeSignature( self.project, self.mod, self.mod.read().index("a_var") + 1 ) def test_normalizing_parameters_for_args_parameter(self): self.mod.write(dedent("""\ def a_func(*arg): pass a_func(1, 2) """)) signature = change_signature.ChangeSignature( self.project, self.mod, self.mod.read().index("a_func") + 1 ) self.project.do(signature.get_changes([change_signature.ArgumentNormalizer()])) self.assertEqual( dedent("""\ def a_func(*arg): pass a_func(1, 2) """), self.mod.read(), ) def test_normalizing_parameters_for_args_parameter_and_keywords(self): self.mod.write(dedent("""\ def a_func(param, *args): pass a_func(*[1, 2, 3]) """)) signature = change_signature.ChangeSignature( self.project, self.mod, self.mod.read().index("a_func") + 1 ) self.project.do(signature.get_changes([change_signature.ArgumentNormalizer()])) self.assertEqual( dedent("""\ def a_func(param, *args): pass a_func(*[1, 2, 3]) """), self.mod.read(), ) def test_normalizing_functions_from_other_modules(self): mod1 = testutils.create_module(self.project, "mod1") mod1.write(dedent("""\ def a_func(param): pass """)) self.mod.write(dedent("""\ import mod1 mod1.a_func(param=1) """)) signature = change_signature.ChangeSignature( self.project, mod1, mod1.read().index("a_func") + 1 ) self.project.do(signature.get_changes([change_signature.ArgumentNormalizer()])) self.assertEqual( dedent("""\ import mod1 mod1.a_func(1) """), self.mod.read(), ) def test_normalizing_parameters_for_keyword_parameters(self): self.mod.write(dedent("""\ def a_func(p1, **kwds): pass a_func(p2=2, p1=1) """)) signature = change_signature.ChangeSignature( self.project, self.mod, self.mod.read().index("a_func") + 1 ) self.project.do(signature.get_changes([change_signature.ArgumentNormalizer()])) self.assertEqual( dedent("""\ def a_func(p1, **kwds): pass a_func(1, p2=2) """), self.mod.read(), ) def test_removing_arguments(self): self.mod.write(dedent("""\ def a_func(p1): pass a_func(1) """)) signature = change_signature.ChangeSignature( self.project, self.mod, self.mod.read().index("a_func") + 1 ) self.project.do(signature.get_changes([change_signature.ArgumentRemover(0)])) self.assertEqual( dedent("""\ def a_func(): pass a_func() """), self.mod.read(), ) def test_removing_arguments_with_multiple_args(self): self.mod.write(dedent("""\ def a_func(p1, p2): pass a_func(1, 2) """)) signature = change_signature.ChangeSignature( self.project, self.mod, self.mod.read().index("a_func") + 1 ) self.project.do(signature.get_changes([change_signature.ArgumentRemover(0)])) self.assertEqual( dedent("""\ def a_func(p2): pass a_func(2) """), self.mod.read(), ) def test_removing_arguments_passed_as_keywords(self): self.mod.write(dedent("""\ def a_func(p1): pass a_func(p1=1) """)) signature = change_signature.ChangeSignature( self.project, self.mod, self.mod.read().index("a_func") + 1 ) self.project.do(signature.get_changes([change_signature.ArgumentRemover(0)])) self.assertEqual( dedent("""\ def a_func(): pass a_func() """), self.mod.read(), ) def test_removing_arguments_with_defaults(self): self.mod.write(dedent("""\ def a_func(p1=1): pass a_func(1) """)) signature = change_signature.ChangeSignature( self.project, self.mod, self.mod.read().index("a_func") + 1 ) self.project.do(signature.get_changes([change_signature.ArgumentRemover(0)])) self.assertEqual( dedent("""\ def a_func(): pass a_func() """), self.mod.read(), ) def test_removing_arguments_star_args(self): self.mod.write(dedent("""\ def a_func(p1, *args): pass a_func(1) """)) signature = change_signature.ChangeSignature( self.project, self.mod, self.mod.read().index("a_func") + 1 ) self.project.do(signature.get_changes([change_signature.ArgumentRemover(1)])) self.assertEqual( dedent("""\ def a_func(p1): pass a_func(1) """), self.mod.read(), ) def test_removing_keyword_arg(self): self.mod.write(dedent("""\ def a_func(p1, **kwds): pass a_func(1) """)) signature = change_signature.ChangeSignature( self.project, self.mod, self.mod.read().index("a_func") + 1 ) self.project.do(signature.get_changes([change_signature.ArgumentRemover(1)])) self.assertEqual( dedent("""\ def a_func(p1): pass a_func(1) """), self.mod.read(), ) def test_removing_keyword_arg2(self): self.mod.write(dedent("""\ def a_func(p1, *args, **kwds): pass a_func(1) """)) signature = change_signature.ChangeSignature( self.project, self.mod, self.mod.read().index("a_func") + 1 ) self.project.do(signature.get_changes([change_signature.ArgumentRemover(2)])) self.assertEqual( dedent("""\ def a_func(p1, *args): pass a_func(1) """), self.mod.read(), ) # XXX: What to do here for star args? @unittest.skip("How to deal with start args?") def xxx_test_removing_arguments_star_args2(self): self.mod.write(dedent("""\ def a_func(p1, *args): pass a_func(2, 3, p1=1) """)) signature = change_signature.ChangeSignature( self.project, self.mod, self.mod.read().index("a_func") + 1 ) self.project.do(signature.get_changes([change_signature.ArgumentRemover(1)])) self.assertEqual( dedent("""\ def a_func(p1): pass a_func(p1=1) """), self.mod.read(), ) # XXX: What to do here for star args? def xxx_test_removing_arguments_star_args3(self): self.mod.write(dedent("""\ def a_func(p1, *args): pass a_func(*[1, 2, 3]) """)) signature = change_signature.ChangeSignature( self.project, self.mod, self.mod.read().index("a_func") + 1 ) self.project.do(signature.get_changes([change_signature.ArgumentRemover(1)])) self.assertEqual( dedent("""\ def a_func(p1): pass a_func(*[1, 2, 3]) """), self.mod.read(), ) def test_adding_arguments_for_normal_args_changing_definition(self): self.mod.write(dedent("""\ def a_func(): pass """)) signature = change_signature.ChangeSignature( self.project, self.mod, self.mod.read().index("a_func") + 1 ) self.project.do( signature.get_changes([change_signature.ArgumentAdder(0, "p1")]) ) self.assertEqual( dedent("""\ def a_func(p1): pass """), self.mod.read(), ) def test_adding_arguments_for_normal_args_with_defaults(self): self.mod.write(dedent("""\ def a_func(): pass a_func() """)) signature = change_signature.ChangeSignature( self.project, self.mod, self.mod.read().index("a_func") + 1 ) adder = change_signature.ArgumentAdder(0, "p1", "None") self.project.do(signature.get_changes([adder])) self.assertEqual( dedent("""\ def a_func(p1=None): pass a_func() """), self.mod.read(), ) def test_adding_arguments_for_normal_args_changing_calls(self): self.mod.write(dedent("""\ def a_func(): pass a_func() """)) signature = change_signature.ChangeSignature( self.project, self.mod, self.mod.read().index("a_func") + 1 ) adder = change_signature.ArgumentAdder(0, "p1", "None", "1") self.project.do(signature.get_changes([adder])) self.assertEqual( dedent("""\ def a_func(p1=None): pass a_func(1) """), self.mod.read(), ) def test_adding_arguments_for_norm_args_chang_calls_with_kwords(self): self.mod.write(dedent("""\ def a_func(p1=0): pass a_func() """)) signature = change_signature.ChangeSignature( self.project, self.mod, self.mod.read().index("a_func") + 1 ) adder = change_signature.ArgumentAdder(1, "p2", "0", "1") self.project.do(signature.get_changes([adder])) self.assertEqual( dedent("""\ def a_func(p1=0, p2=0): pass a_func(p2=1) """), self.mod.read(), ) def test_adding_arguments_for_norm_args_chang_calls_with_no_value(self): self.mod.write(dedent("""\ def a_func(p2=0): pass a_func(1) """)) signature = change_signature.ChangeSignature( self.project, self.mod, self.mod.read().index("a_func") + 1 ) adder = change_signature.ArgumentAdder(0, "p1", "0", None) self.project.do(signature.get_changes([adder])) self.assertEqual( dedent("""\ def a_func(p1=0, p2=0): pass a_func(p2=1) """), self.mod.read(), ) def test_adding_duplicate_parameter_and_raising_exceptions(self): self.mod.write(dedent("""\ def a_func(p1): pass """)) with self.assertRaises(rope.base.exceptions.RefactoringError): signature = change_signature.ChangeSignature( self.project, self.mod, self.mod.read().index("a_func") + 1 ) self.project.do( signature.get_changes([change_signature.ArgumentAdder(1, "p1")]) ) def test_inlining_default_arguments(self): self.mod.write(dedent("""\ def a_func(p1=0): pass a_func() """)) signature = change_signature.ChangeSignature( self.project, self.mod, self.mod.read().index("a_func") + 1 ) self.project.do( signature.get_changes([change_signature.ArgumentDefaultInliner(0)]) ) self.assertEqual( dedent("""\ def a_func(p1=0): pass a_func(0) """), self.mod.read(), ) def test_inlining_default_arguments2(self): self.mod.write(dedent("""\ def a_func(p1=0): pass a_func(1) """)) signature = change_signature.ChangeSignature( self.project, self.mod, self.mod.read().index("a_func") + 1 ) self.project.do( signature.get_changes([change_signature.ArgumentDefaultInliner(0)]) ) self.assertEqual( dedent("""\ def a_func(p1=0): pass a_func(1) """), self.mod.read(), ) def test_preserving_args_and_keywords_order(self): self.mod.write(dedent("""\ def a_func(*args, **kwds): pass a_func(3, 1, 2, a=1, c=3, b=2) """)) signature = change_signature.ChangeSignature( self.project, self.mod, self.mod.read().index("a_func") + 1 ) self.project.do(signature.get_changes([change_signature.ArgumentNormalizer()])) self.assertEqual( dedent("""\ def a_func(*args, **kwds): pass a_func(3, 1, 2, a=1, c=3, b=2) """), self.mod.read(), ) def test_change_order_for_only_one_parameter(self): self.mod.write(dedent("""\ def a_func(p1): pass a_func(1) """)) signature = change_signature.ChangeSignature( self.project, self.mod, self.mod.read().index("a_func") + 1 ) self.project.do( signature.get_changes([change_signature.ArgumentReorderer([0])]) ) self.assertEqual( dedent("""\ def a_func(p1): pass a_func(1) """), self.mod.read(), ) def test_change_order_for_two_parameter(self): self.mod.write(dedent("""\ def a_func(p1, p2): pass a_func(1, 2) """)) signature = change_signature.ChangeSignature( self.project, self.mod, self.mod.read().index("a_func") + 1 ) self.project.do( signature.get_changes([change_signature.ArgumentReorderer([1, 0])]) ) self.assertEqual( dedent("""\ def a_func(p2, p1): pass a_func(2, 1) """), self.mod.read(), ) def test_reordering_multi_line_function_headers(self): self.mod.write(dedent("""\ def a_func(p1, p2): pass a_func(1, 2) """)) signature = change_signature.ChangeSignature( self.project, self.mod, self.mod.read().index("a_func") + 1 ) self.project.do( signature.get_changes([change_signature.ArgumentReorderer([1, 0])]) ) self.assertEqual( dedent("""\ def a_func(p2, p1): pass a_func(2, 1) """), self.mod.read(), ) def test_changing_order_with_static_params(self): self.mod.write(dedent("""\ def a_func(p1, p2=0, p3=0): pass a_func(1, 2) """)) signature = change_signature.ChangeSignature( self.project, self.mod, self.mod.read().index("a_func") + 1 ) self.project.do( signature.get_changes([change_signature.ArgumentReorderer([0, 2, 1])]) ) self.assertEqual( dedent("""\ def a_func(p1, p3=0, p2=0): pass a_func(1, p2=2) """), self.mod.read(), ) def test_doing_multiple_changes(self): changers = [] self.mod.write(dedent("""\ def a_func(p1): pass a_func(1) """)) changers.append(change_signature.ArgumentRemover(0)) changers.append(change_signature.ArgumentAdder(0, "p2", None, None)) signature = change_signature.ChangeSignature( self.project, self.mod, self.mod.read().index("a_func") + 1 ) signature.get_changes(changers).do() self.assertEqual( dedent("""\ def a_func(p2): pass a_func() """), self.mod.read(), ) def test_doing_multiple_changes2(self): changers = [] self.mod.write(dedent("""\ def a_func(p1, p2): pass a_func(p2=2) """)) changers.append(change_signature.ArgumentAdder(2, "p3", None, "3")) changers.append(change_signature.ArgumentReorderer([1, 0, 2])) changers.append(change_signature.ArgumentRemover(1)) signature = change_signature.ChangeSignature( self.project, self.mod, self.mod.read().index("a_func") + 1 ) signature.get_changes(changers).do() self.assertEqual( dedent("""\ def a_func(p2, p3): pass a_func(2, 3) """), self.mod.read(), ) def test_changing_signature_in_subclasses(self): self.mod.write(dedent("""\ class A(object): def a_method(self): pass class B(A): def a_method(self): pass """)) signature = change_signature.ChangeSignature( self.project, self.mod, self.mod.read().index("a_method") + 1 ) signature.get_changes( [change_signature.ArgumentAdder(1, "p1")], in_hierarchy=True ).do() self.assertEqual( dedent("""\ class A(object): def a_method(self, p1): pass class B(A): def a_method(self, p1): pass """), self.mod.read(), ) def test_differentiating_class_accesses_from_instance_accesses(self): self.mod.write(dedent("""\ class A(object): def a_func(self, param): pass a_var = A() A.a_func(a_var, param=1)""")) signature = change_signature.ChangeSignature( self.project, self.mod, self.mod.read().index("a_func") + 1 ) self.project.do(signature.get_changes([change_signature.ArgumentRemover(1)])) self.assertEqual( dedent("""\ class A(object): def a_func(self): pass a_var = A() A.a_func(a_var)"""), self.mod.read(), ) def test_changing_signature_for_constructors(self): self.mod.write(dedent("""\ class C(object): def __init__(self, p): pass c = C(1) """)) signature = change_signature.ChangeSignature( self.project, self.mod, self.mod.read().index("C") + 1 ) signature.get_changes([change_signature.ArgumentRemover(1)]).do() self.assertEqual( dedent("""\ class C(object): def __init__(self): pass c = C() """), self.mod.read(), ) def test_changing_signature_for_constructors2(self): self.mod.write(dedent("""\ class C(object): def __init__(self, p): pass c = C(1) """)) signature = change_signature.ChangeSignature( self.project, self.mod, self.mod.read().index("__init__") + 1 ) signature.get_changes([change_signature.ArgumentRemover(1)]).do() self.assertEqual( dedent("""\ class C(object): def __init__(self): pass c = C() """), self.mod.read(), ) def test_changing_signature_for_constructors_when_using_super(self): self.mod.write(dedent("""\ class A(object): def __init__(self, p): pass class B(A): def __init__(self, p): super(B, self).__init__(p) """)) signature = change_signature.ChangeSignature( self.project, self.mod, self.mod.read().index("__init__") + 1 ) signature.get_changes([change_signature.ArgumentRemover(1)]).do() self.assertEqual( dedent("""\ class A(object): def __init__(self): pass class B(A): def __init__(self, p): super(B, self).__init__() """), self.mod.read(), ) def test_redordering_arguments_reported_by_mft(self): self.mod.write(dedent("""\ def f(a, b, c): pass f(1, 2, 3) """)) signature = change_signature.ChangeSignature( self.project, self.mod, self.mod.read().rindex("f") ) signature.get_changes([change_signature.ArgumentReorderer([1, 2, 0])]).do() self.assertEqual( dedent("""\ def f(b, c, a): pass f(2, 3, 1) """), self.mod.read(), ) def test_resources_parameter(self): mod1 = testutils.create_module(self.project, "mod1") mod1.write("def a_func(param):\n pass\n") self.mod.write(dedent("""\ import mod1 mod1.a_func(1) """)) signature = change_signature.ChangeSignature( self.project, mod1, mod1.read().index("a_func") + 1 ) signature.get_changes( [change_signature.ArgumentRemover(0)], resources=[mod1] ).do() self.assertEqual( dedent("""\ import mod1 mod1.a_func(1) """), self.mod.read(), ) self.assertEqual( dedent("""\ def a_func(): pass """), mod1.read(), ) def test_reordering_and_automatic_defaults(self): code = dedent("""\ def f(p1, p2=2): pass f(1, 2) """) self.mod.write(code) signature = change_signature.ChangeSignature( self.project, self.mod, code.index("f(") ) reorder = change_signature.ArgumentReorderer([1, 0], autodef="1") signature.get_changes([reorder]).do() expected = dedent("""\ def f(p2=2, p1=1): pass f(2, 1) """) self.assertEqual(expected, self.mod.read()) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1705551350.0 rope-1.12.0/ropetest/refactor/extracttest.py0000664000175000017500000032325414552122766021113 0ustar00lieryanlieryanimport unittest from textwrap import dedent import rope.base.codeanalyze import rope.base.exceptions from rope.refactor import extract from ropetest import testutils class ExtractMethodTest(unittest.TestCase): def setUp(self): super().setUp() self.project = testutils.sample_project() self.pycore = self.project.pycore def tearDown(self): testutils.remove_project(self.project) super().tearDown() def do_extract_method(self, source_code, start, end, extracted, **kwds): testmod = testutils.create_module(self.project, "testmod") testmod.write(source_code) extractor = extract.ExtractMethod(self.project, testmod, start, end) self.project.do(extractor.get_changes(extracted, **kwds)) return testmod.read() def do_extract_variable(self, source_code, start, end, extracted, **kwds): testmod = testutils.create_module(self.project, "testmod") testmod.write(source_code) extractor = extract.ExtractVariable(self.project, testmod, start, end) self.project.do(extractor.get_changes(extracted, **kwds)) return testmod.read() def _convert_line_range_to_offset(self, code, start, end): lines = rope.base.codeanalyze.SourceLinesAdapter(code) return lines.get_line_start(start), lines.get_line_end(end) def test_simple_extract_function(self): code = dedent("""\ def a_func(): print('one') print('two') """) start, end = self._convert_line_range_to_offset(code, 2, 2) refactored = self.do_extract_method(code, start, end, "extracted") expected = dedent("""\ def a_func(): extracted() print('two') def extracted(): print('one') """) self.assertEqual(expected, refactored) def test_simple_extract_function_one_line(self): code = dedent("""\ def a_func(): resp = 'one' print(resp) """) selected = "'one'" start, end = code.index(selected), code.index(selected) + len(selected) refactored = self.do_extract_method(code, start, end, "extracted") expected = dedent("""\ def a_func(): resp = extracted() print(resp) def extracted(): return 'one' """) self.assertEqual(expected, refactored) def test_extract_function_at_the_end_of_file(self): code = dedent("""\ def a_func(): print('one')""") start, end = self._convert_line_range_to_offset(code, 2, 2) refactored = self.do_extract_method(code, start, end, "extracted") expected = dedent("""\ def a_func(): extracted() def extracted(): print('one') """) self.assertEqual(expected, refactored) def test_extract_function_after_scope(self): code = dedent("""\ def a_func(): print('one') print('two') print('hey') """) start, end = self._convert_line_range_to_offset(code, 2, 2) refactored = self.do_extract_method(code, start, end, "extracted") expected = dedent("""\ def a_func(): extracted() print('two') def extracted(): print('one') print('hey') """) self.assertEqual(expected, refactored) 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 = dedent("""\ def a_func(): a_var = 10 print(a_var) """) start, end = self._convert_line_range_to_offset(code, 3, 3) refactored = self.do_extract_method(code, start, end, "new_func") expected = dedent("""\ def a_func(): a_var = 10 new_func(a_var) def new_func(a_var): print(a_var) """) self.assertEqual(expected, refactored) def test_not_unread_variables_as_parameter(self): code = dedent("""\ def a_func(): a_var = 10 print('hey') """) start, end = self._convert_line_range_to_offset(code, 3, 3) refactored = self.do_extract_method(code, start, end, "new_func") expected = dedent("""\ def a_func(): a_var = 10 new_func() def new_func(): print('hey') """) self.assertEqual(expected, refactored) def test_simple_extract_function_with_two_parameter(self): code = dedent("""\ def a_func(): a_var = 10 another_var = 20 third_var = a_var + another_var """) start, end = self._convert_line_range_to_offset(code, 4, 4) refactored = self.do_extract_method(code, start, end, "new_func") expected = dedent("""\ def a_func(): a_var = 10 another_var = 20 new_func(a_var, another_var) def new_func(a_var, another_var): third_var = a_var + another_var """) self.assertEqual(expected, refactored) def test_simple_extract_function_with_return_value(self): code = dedent("""\ def a_func(): a_var = 10 print(a_var) """) start, end = self._convert_line_range_to_offset(code, 2, 2) refactored = self.do_extract_method(code, start, end, "new_func") expected = dedent("""\ def a_func(): a_var = new_func() print(a_var) def new_func(): a_var = 10 return a_var """) self.assertEqual(expected, refactored) def test_extract_function_with_kwonlyargs(self): code = dedent("""\ def a_func(b, *, a_var): another_var = 20 third_var = a_var + another_var """) start, end = self._convert_line_range_to_offset(code, 3, 3) refactored = self.do_extract_method(code, start, end, "new_func") expected = dedent("""\ def a_func(b, *, a_var): another_var = 20 new_func(a_var, another_var) def new_func(a_var, another_var): third_var = a_var + another_var """) self.assertEqual(expected, refactored) @testutils.only_for_versions_higher("3.8") def test_extract_function_with_posonlyargs(self): code = dedent("""\ def a_func(a_var, /, b): another_var = 20 third_var = a_var + another_var """) start, end = self._convert_line_range_to_offset(code, 3, 3) refactored = self.do_extract_method(code, start, end, "new_func") expected = dedent("""\ def a_func(a_var, /, b): another_var = 20 new_func(a_var, another_var) def new_func(a_var, another_var): third_var = a_var + another_var """) self.assertEqual(expected, refactored) def test_extract_function_with_multiple_return_values(self): code = dedent("""\ def a_func(): a_var = 10 another_var = 20 third_var = a_var + another_var """) start, end = self._convert_line_range_to_offset(code, 2, 3) refactored = self.do_extract_method(code, start, end, "new_func") expected = dedent("""\ def a_func(): a_var, another_var = new_func() third_var = a_var + another_var def new_func(): a_var = 10 another_var = 20 return a_var, another_var """) self.assertEqual(expected, refactored) def test_simple_extract_method(self): code = dedent("""\ class AClass(object): def a_func(self): print(1) print(2) """) start, end = self._convert_line_range_to_offset(code, 4, 4) refactored = self.do_extract_method(code, start, end, "new_func") expected = dedent("""\ class AClass(object): def a_func(self): self.new_func() print(2) def new_func(self): print(1) """) self.assertEqual(expected, refactored) def test_extract_method_with_args_and_returns(self): code = dedent("""\ class AClass(object): def a_func(self): a_var = 10 another_var = a_var * 3 third_var = a_var + another_var """) start, end = self._convert_line_range_to_offset(code, 4, 4) refactored = self.do_extract_method(code, start, end, "new_func") expected = dedent("""\ class AClass(object): def a_func(self): a_var = 10 another_var = self.new_func(a_var) third_var = a_var + another_var def new_func(self, a_var): another_var = a_var * 3 return another_var """) self.assertEqual(expected, refactored) def test_extract_method_args_and_kwargs(self): code = dedent("""\ def a_func(a, *args, **kwargs): print(a, args, kwargs) """) start, end = self._convert_line_range_to_offset(code, 2, 2) refactored = self.do_extract_method(code, start, end, "new_func") expected = dedent("""\ def a_func(a, *args, **kwargs): new_func(a, args, kwargs) def new_func(a, args, kwargs): print(a, args, kwargs) """) self.assertEqual(expected, refactored) def test_extract_method_with_self_as_argument(self): code = dedent("""\ class AClass(object): def a_func(self): print(self) """) start, end = self._convert_line_range_to_offset(code, 3, 3) refactored = self.do_extract_method(code, start, end, "new_func") expected = dedent("""\ class AClass(object): def a_func(self): self.new_func() def new_func(self): print(self) """) self.assertEqual(expected, refactored) def test_extract_method_with_no_self_as_argument(self): code = dedent("""\ class AClass(object): def a_func(): print(1) """) start, end = self._convert_line_range_to_offset(code, 3, 3) with self.assertRaises(rope.base.exceptions.RefactoringError): self.do_extract_method(code, start, end, "new_func") def test_extract_method_with_multiple_methods(self): code = dedent("""\ class AClass(object): def a_func(self): print(self) def another_func(self): pass """) start, end = self._convert_line_range_to_offset(code, 3, 3) refactored = self.do_extract_method(code, start, end, "new_func") expected = dedent("""\ class AClass(object): def a_func(self): self.new_func() def new_func(self): print(self) def another_func(self): pass """) self.assertEqual(expected, refactored) def test_extract_function_with_function_returns(self): code = dedent("""\ def a_func(): def inner_func(): pass inner_func() """) start, end = self._convert_line_range_to_offset(code, 2, 3) refactored = self.do_extract_method(code, start, end, "new_func") expected = dedent("""\ def a_func(): inner_func = new_func() inner_func() def new_func(): def inner_func(): pass return inner_func """) self.assertEqual(expected, refactored) def test_simple_extract_global_function(self): code = dedent("""\ print('one') print('two') print('three') """) start, end = self._convert_line_range_to_offset(code, 2, 2) refactored = self.do_extract_method(code, start, end, "new_func") expected = dedent("""\ print('one') def new_func(): print('two') new_func() print('three') """) self.assertEqual(expected, refactored) def test_extract_global_function_inside_ifs(self): code = dedent("""\ if True: a = 10 """) start, end = self._convert_line_range_to_offset(code, 2, 2) refactored = self.do_extract_method(code, start, end, "new_func") expected = dedent("""\ def new_func(): a = 10 if True: new_func() """) self.assertEqual(expected, refactored) def test_extract_function_while_inner_function_reads(self): code = dedent("""\ def a_func(): a_var = 10 def inner_func(): print(a_var) return inner_func """) start, end = self._convert_line_range_to_offset(code, 3, 4) refactored = self.do_extract_method(code, start, end, "new_func") expected = dedent("""\ def a_func(): a_var = 10 inner_func = new_func(a_var) return inner_func def new_func(a_var): def inner_func(): print(a_var) return inner_func """) self.assertEqual(expected, refactored) def test_extract_method_bad_range(self): code = dedent("""\ def a_func(): pass a_var = 10 """) start, end = self._convert_line_range_to_offset(code, 2, 3) with self.assertRaises(rope.base.exceptions.RefactoringError): self.do_extract_method(code, start, end, "new_func") def test_extract_method_bad_range2(self): code = dedent("""\ class AClass(object): pass """) start, end = self._convert_line_range_to_offset(code, 1, 1) with self.assertRaises(rope.base.exceptions.RefactoringError): self.do_extract_method(code, start, end, "new_func") def test_extract_method_containing_return(self): code = dedent("""\ def a_func(arg): if arg: return arg * 2 return 1""") start, end = self._convert_line_range_to_offset(code, 2, 4) with self.assertRaises(rope.base.exceptions.RefactoringError): self.do_extract_method(code, start, end, "new_func") def test_extract_method_containing_yield(self): code = dedent("""\ def a_func(arg): yield arg * 2 """) start, end = self._convert_line_range_to_offset(code, 2, 2) with self.assertRaises(rope.base.exceptions.RefactoringError): self.do_extract_method(code, start, end, "new_func") def test_extract_method_containing_incomplete_lines(self): code = dedent("""\ a_var = 20 another_var = 30 """) start = code.index("20") end = code.index("30") + 2 with self.assertRaises(rope.base.exceptions.RefactoringError): self.do_extract_method(code, start, end, "new_func") def test_extract_method_containing_incomplete_lines2(self): code = dedent("""\ a_var = 20 another_var = 30 """) start = code.index("20") end = code.index("another") + 5 with self.assertRaises(rope.base.exceptions.RefactoringError): self.do_extract_method(code, start, end, "new_func") def test_extract_function_and_argument_as_parameter(self): code = dedent("""\ def a_func(arg): print(arg) """) start, end = self._convert_line_range_to_offset(code, 2, 2) refactored = self.do_extract_method(code, start, end, "new_func") expected = dedent("""\ def a_func(arg): new_func(arg) def new_func(arg): print(arg) """) self.assertEqual(expected, refactored) def test_extract_function_and_end_as_the_start_of_a_line(self): code = dedent("""\ print("hey") if True: pass """) start = 0 end = code.index("\n") + 1 refactored = self.do_extract_method(code, start, end, "new_func") expected = dedent("""\ def new_func(): print("hey") new_func() if True: pass """) self.assertEqual(expected, refactored) def test_extract_function_and_indented_blocks(self): code = dedent("""\ def a_func(arg): if True: if True: print(arg) """) start, end = self._convert_line_range_to_offset(code, 3, 4) refactored = self.do_extract_method(code, start, end, "new_func") expected = dedent("""\ def a_func(arg): if True: new_func(arg) def new_func(arg): if True: print(arg) """) self.assertEqual(expected, refactored) def test_extract_method_and_multi_line_headers(self): code = dedent("""\ def a_func( arg): print(arg) """) start, end = self._convert_line_range_to_offset(code, 3, 3) refactored = self.do_extract_method(code, start, end, "new_func") expected = dedent("""\ def a_func( arg): new_func(arg) def new_func(arg): print(arg) """) self.assertEqual(expected, refactored) def test_single_line_extract_function(self): code = dedent("""\ a_var = 10 + 20 """) start = code.index("10") end = code.index("20") + 2 refactored = self.do_extract_method(code, start, end, "new_func") expected = dedent("""\ def new_func(): return 10 + 20 a_var = new_func() """) self.assertEqual(expected, refactored) def test_single_line_extract_function2(self): code = dedent("""\ def a_func(): a = 10 b = a * 20 """) start = code.rindex("a") end = code.index("20") + 2 refactored = self.do_extract_method(code, start, end, "new_func") expected = dedent("""\ def a_func(): a = 10 b = new_func(a) def new_func(a): return a * 20 """) self.assertEqual(expected, refactored) def test_single_line_extract_method_and_logical_lines(self): code = dedent("""\ a_var = 10 +\\ 20 """) start = code.index("10") end = code.index("20") + 2 refactored = self.do_extract_method(code, start, end, "new_func") expected = dedent("""\ def new_func(): return 10 + 20 a_var = new_func() """) self.assertEqual(expected, refactored) def test_single_line_extract_method_and_logical_lines2(self): code = dedent("""\ a_var = (10,\\ 20) """) start = code.index("10") - 1 end = code.index("20") + 3 refactored = self.do_extract_method(code, start, end, "new_func") expected = dedent("""\ def new_func(): return (10, 20) a_var = new_func() """) self.assertEqual(expected, refactored) def test_single_line_extract_method_with_large_multiline_expression(self): code = dedent("""\ a_var = func( { "hello": 1, "world": 2, }, blah=foo, ) """) start = code.index("{") - 1 end = code.index("}") + 1 refactored = self.do_extract_method(code, start, end, "new_func") expected = dedent("""\ def new_func(): return { "hello": 1, "world": 2, } a_var = func( new_func(), blah=foo, ) """) self.assertEqual(expected, refactored) def test_single_line_extract_method(self): code = dedent("""\ class AClass(object): def a_func(self): a = 10 b = a * a """) start = code.rindex("=") + 2 end = code.rindex("a") + 1 refactored = self.do_extract_method(code, start, end, "new_func") expected = dedent("""\ class AClass(object): def a_func(self): a = 10 b = self.new_func(a) def new_func(self, a): return a * a """) self.assertEqual(expected, refactored) def test_single_line_extract_function_if_condition(self): code = dedent("""\ if True: pass """) start = code.index("True") end = code.index("True") + 4 refactored = self.do_extract_method(code, start, end, "new_func") expected = dedent("""\ def new_func(): return True if new_func(): pass """) self.assertEqual(expected, refactored) def test_unneeded_params(self): code = dedent("""\ class A(object): def a_func(self): a_var = 10 a_var += 2 """) start = code.rindex("2") end = code.rindex("2") + 1 refactored = self.do_extract_method(code, start, end, "new_func") expected = dedent("""\ class A(object): def a_func(self): a_var = 10 a_var += self.new_func() def new_func(self): return 2 """) self.assertEqual(expected, refactored) def test_breaks_and_continues_inside_loops(self): code = dedent("""\ def a_func(): for i in range(10): continue """) start = code.index("for") end = len(code) - 1 refactored = self.do_extract_method(code, start, end, "new_func") expected = dedent("""\ def a_func(): new_func() def new_func(): for i in range(10): continue """) self.assertEqual(expected, refactored) def test_breaks_and_continues_outside_loops(self): code = dedent("""\ def a_func(): for i in range(10): a = i continue """) start = code.index("a = i") end = len(code) - 1 with self.assertRaises(rope.base.exceptions.RefactoringError): self.do_extract_method(code, start, end, "new_func") def test_for_loop_variable_scope(self): code = dedent("""\ def my_func(): i = 0 for dummy in range(10): i += 1 print(i) """) start, end = self._convert_line_range_to_offset(code, 4, 5) refactored = self.do_extract_method(code, start, end, "new_func") expected = dedent("""\ def my_func(): i = 0 for dummy in range(10): i = new_func(i) def new_func(i): i += 1 print(i) return i """) self.assertEqual(expected, refactored) def test_for_loop_variable_scope_read_then_write(self): code = dedent("""\ def my_func(): i = 0 for dummy in range(10): a = i + 1 i = a + 1 """) start, end = self._convert_line_range_to_offset(code, 4, 5) refactored = self.do_extract_method(code, start, end, "new_func") expected = dedent("""\ def my_func(): i = 0 for dummy in range(10): i = new_func(i) def new_func(i): a = i + 1 i = a + 1 return i """) self.assertEqual(expected, refactored) def test_for_loop_variable_scope_write_then_read(self): code = dedent("""\ def my_func(): i = 0 for dummy in range(10): i = 'hello' print(i) """) start, end = self._convert_line_range_to_offset(code, 4, 5) refactored = self.do_extract_method(code, start, end, "new_func") expected = dedent("""\ def my_func(): i = 0 for dummy in range(10): new_func() def new_func(): i = 'hello' print(i) """) self.assertEqual(expected, refactored) def test_for_loop_variable_scope_write_only(self): code = dedent("""\ def my_func(): i = 0 for num in range(10): i = 'hello' + num print(i) """) start, end = self._convert_line_range_to_offset(code, 4, 4) refactored = self.do_extract_method(code, start, end, "new_func") expected = dedent("""\ def my_func(): i = 0 for num in range(10): i = new_func(num) print(i) def new_func(num): i = 'hello' + num return i """) self.assertEqual(expected, refactored) def test_variable_writes_followed_by_variable_reads_after_extraction(self): code = dedent("""\ def a_func(): a = 1 a = 2 b = a """) start = code.index("a = 1") end = code.index("a = 2") - 1 refactored = self.do_extract_method(code, start, end, "new_func") expected = dedent("""\ def a_func(): new_func() a = 2 b = a def new_func(): a = 1 """) self.assertEqual(expected, refactored) def test_var_writes_followed_by_var_reads_inside_extraction(self): code = dedent("""\ def a_func(): a = 1 a = 2 b = a """) start = code.index("a = 2") end = len(code) - 1 refactored = self.do_extract_method(code, start, end, "new_func") expected = dedent("""\ def a_func(): a = 1 new_func() def new_func(): a = 2 b = a """) self.assertEqual(expected, refactored) def test_extract_variable(self): code = dedent("""\ a_var = 10 + 20 """) start = code.index("10") end = code.index("20") + 2 refactored = self.do_extract_variable(code, start, end, "new_var") expected = dedent("""\ new_var = 10 + 20 a_var = new_var """) 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 = dedent("""\ a = 1 b = 2 """) start = code.index("1") end = code.index("1") + 1 refactored = self.do_extract_variable(code, start, end, "c") expected = dedent("""\ c = 1 a = c b = 2 """) self.assertEqual(expected, refactored) def test_extract_variable_in_the_middle_of_statements(self): code = dedent("""\ a = 1 + 2 """) start = code.index("1") end = code.index("1") + 1 refactored = self.do_extract_variable(code, start, end, "c") expected = dedent("""\ c = 1 a = c + 2 """) self.assertEqual(expected, refactored) def test_extract_variable_for_a_tuple(self): code = dedent("""\ a = 1, 2 """) start = code.index("1") end = code.index("2") + 1 refactored = self.do_extract_variable(code, start, end, "c") expected = dedent("""\ c = 1, 2 a = c """) self.assertEqual(expected, refactored) def test_extract_variable_for_a_string(self): code = dedent("""\ def a_func(): a = "hey!" """) start = code.index('"') end = code.rindex('"') + 1 refactored = self.do_extract_variable(code, start, end, "c") expected = dedent("""\ def a_func(): c = "hey!" a = c """) self.assertEqual(expected, refactored) def test_extract_variable_inside_ifs(self): code = dedent("""\ if True: a = 1 + 2 """) start = code.index("1") end = code.rindex("2") + 1 refactored = self.do_extract_variable(code, start, end, "b") expected = dedent("""\ if True: b = 1 + 2 a = b """) self.assertEqual(expected, refactored) def test_extract_variable_inside_ifs_and_logical_lines(self): code = dedent("""\ if True: a = (3 + (1 + 2)) """) start = code.index("1") end = code.index("2") + 1 refactored = self.do_extract_variable(code, start, end, "b") expected = dedent("""\ if True: b = 1 + 2 a = (3 + (b)) """) self.assertEqual(expected, refactored) # TODO: Handle when extracting a subexpression def xxx_test_extract_variable_for_a_subexpression(self): code = dedent("""\ a = 3 + 1 + 2 """) start = code.index("1") end = code.index("2") + 1 refactored = self.do_extract_variable(code, start, end, "b") expected = dedent("""\ b = 1 + 2 a = 3 + b """) self.assertEqual(expected, refactored) def test_extract_variable_starting_from_the_start_of_the_line(self): code = dedent("""\ a_dict = {1: 1} a_dict.values().count(1) """) start = code.rindex("a_dict") end = code.index("count") - 1 refactored = self.do_extract_variable(code, start, end, "values") expected = dedent("""\ a_dict = {1: 1} values = a_dict.values() values.count(1) """) self.assertEqual(expected, refactored) def test_extract_variable_on_the_last_line_of_a_function(self): code = dedent("""\ def f(): a_var = {} a_var.keys() """) start = code.rindex("a_var") end = code.index(".keys") refactored = self.do_extract_variable(code, start, end, "new_var") expected = dedent("""\ def f(): a_var = {} new_var = a_var new_var.keys() """) self.assertEqual(expected, refactored) def test_extract_variable_on_the_indented_function_statement(self): code = dedent("""\ def f(): if True: a_var = 1 + 2 """) start = code.index("1") end = code.index("2") + 1 refactored = self.do_extract_variable(code, start, end, "new_var") expected = dedent("""\ def f(): if True: new_var = 1 + 2 a_var = new_var """) self.assertEqual(expected, refactored) def test_extract_method_on_the_last_line_of_a_function(self): code = dedent("""\ def f(): a_var = {} a_var.keys() """) start = code.rindex("a_var") end = code.index(".keys") refactored = self.do_extract_method(code, start, end, "new_f") expected = dedent("""\ def f(): a_var = {} new_f(a_var).keys() def new_f(a_var): return a_var """) self.assertEqual(expected, refactored) def test_raising_exception_when_on_incomplete_variables(self): code = dedent("""\ a_var = 10 + 20 """) start = code.index("10") + 1 end = code.index("20") + 2 with self.assertRaises(rope.base.exceptions.RefactoringError): self.do_extract_method(code, start, end, "new_func") def test_raising_exception_when_on_incomplete_variables_on_end(self): code = dedent("""\ a_var = 10 + 20 """) start = code.index("10") end = code.index("20") + 1 with self.assertRaises(rope.base.exceptions.RefactoringError): self.do_extract_method(code, start, end, "new_func") def test_raising_exception_on_bad_parens(self): code = dedent("""\ a_var = (10 + 20) + 30 """) start = code.index("20") end = code.index("30") + 2 with self.assertRaises(rope.base.exceptions.RefactoringError): self.do_extract_method(code, start, end, "new_func") def test_raising_exception_on_bad_operators(self): code = dedent("""\ a_var = 10 + 20 + 30 """) start = code.index("10") end = code.rindex("+") + 1 with self.assertRaises(rope.base.exceptions.RefactoringError): self.do_extract_method(code, start, end, "new_func") # FIXME: Extract method should be more intelligent about bad ranges def xxx_test_raising_exception_on_function_parens(self): code = dedent("""\ a = range(10)""") start = code.index("(") end = code.rindex(")") + 1 with self.assertRaises(rope.base.exceptions.RefactoringError): self.do_extract_method(code, start, end, "new_func") def test_raising_exception_on_incomplete_block(self): code = dedent("""\ if True: a = 1 b = 2 """) start = code.index("if") end = code.index("1") + 1 with self.assertRaises(rope.base.exceptions.RefactoringError): self.do_extract_method(code, start, end, "new_func") def test_raising_exception_on_incomplete_block_2(self): code = dedent("""\ if True: a = 1 # b = 2 """) start = code.index("if") end = code.index("1") + 1 with self.assertRaises(rope.base.exceptions.RefactoringError): self.do_extract_method(code, start, end, "new_func") def test_raising_exception_on_incomplete_block_3(self): code = dedent("""\ if True: a = 1 b = 2 """) start = code.index("if") end = code.index("1") + 1 with self.assertRaises(rope.base.exceptions.RefactoringError): self.do_extract_method(code, start, end, "new_func") def test_raising_exception_on_incomplete_block_4(self): code = dedent("""\ # if True: a = 1 b = 2 """) start = code.index("#") end = code.index("1") + 1 with self.assertRaises(rope.base.exceptions.RefactoringError): self.do_extract_method(code, start, end, "new_func") def test_raising_exception_on_incomplete_block_5(self): code = dedent("""\ if True: if 0: a = 1 """) start = code.index("if") end = code.index("0:") + 2 with self.assertRaises(rope.base.exceptions.RefactoringError): self.do_extract_method(code, start, end, "new_func") def test_extract_method_and_extra_blank_lines(self): code = dedent("""\ print(1) """) refactored = self.do_extract_method(code, 0, len(code), "new_f") expected = dedent("""\ def new_f(): print(1) new_f() """) 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 = dedent("""\ a = 1 a = 1 + a """) start = code.index("\n") + 1 end = len(code) refactored = self.do_extract_method(code, start, end, "new_f", global_=True) expected = dedent("""\ a = 1 def new_f(a): a = 1 + a new_f(a) """) self.assertEqual(expected, refactored) def test_variable_writes_in_the_same_line_as_variable_read2(self): 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 = 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): code = dedent("""\ a = 1 b = 1 """) start = code.index("1") end = start + 1 refactored = self.do_extract_variable(code, start, end, "one", similar=True) expected = dedent("""\ one = 1 a = one b = one """) self.assertEqual(expected, refactored) def test_definition_should_appear_before_the_first_use(self): code = dedent("""\ a = 1 b = 1 """) start = code.rindex("1") end = start + 1 refactored = self.do_extract_variable(code, start, end, "one", similar=True) expected = dedent("""\ one = 1 a = one b = one """) self.assertEqual(expected, refactored) def test_extract_method_and_similar_expressions(self): code = dedent("""\ a = 1 b = 1 """) start = code.index("1") end = start + 1 refactored = self.do_extract_method(code, start, end, "one", similar=True) expected = dedent("""\ def one(): return 1 a = one() b = one() """) self.assertEqual(expected, refactored) def test_simple_extract_method_and_similar_statements(self): code = dedent("""\ class AClass(object): def func1(self): a = 1 + 2 b = a def func2(self): a = 1 + 2 b = a """) start, end = self._convert_line_range_to_offset(code, 4, 4) refactored = self.do_extract_method(code, start, end, "new_func", similar=True) expected = dedent("""\ class AClass(object): def func1(self): a = self.new_func() b = a def new_func(self): a = 1 + 2 return a def func2(self): a = self.new_func() b = a """) self.assertEqual(expected, refactored) def test_extract_method_and_similar_statements2(self): code = dedent("""\ class AClass(object): def func1(self, p1): a = p1 + 2 def func2(self, p2): a = p2 + 2 """) start = code.rindex("p1") end = code.index("2\n") + 1 refactored = self.do_extract_method(code, start, end, "new_func", similar=True) expected = dedent("""\ class AClass(object): def func1(self, p1): a = self.new_func(p1) def new_func(self, p1): return p1 + 2 def func2(self, p2): a = self.new_func(p2) """) self.assertEqual(expected, refactored) def test_extract_method_and_similar_sttemnts_return_is_different(self): code = dedent("""\ class AClass(object): def func1(self, p1): a = p1 + 2 def func2(self, p2): self.attr = p2 + 2 """) start = code.rindex("p1") end = code.index("2\n") + 1 refactored = self.do_extract_method(code, start, end, "new_func", similar=True) expected = dedent("""\ class AClass(object): def func1(self, p1): a = self.new_func(p1) def new_func(self, p1): return p1 + 2 def func2(self, p2): self.attr = self.new_func(p2) """) self.assertEqual(expected, refactored) def test_extract_method_and_similar_sttemnts_overlapping_regions(self): code = dedent("""\ def func(p): a = p b = a c = b d = c return d""") start = code.index("a") end = code.rindex("a") + 1 refactored = self.do_extract_method(code, start, end, "new_func", similar=True) expected = dedent("""\ def func(p): b = new_func(p) d = new_func(b) return d def new_func(p): a = p b = a return b """) self.assertEqual(expected, refactored) def test_definition_should_appear_where_it_is_visible(self): code = dedent("""\ if True: a = 1 else: b = 1 """) start = code.rindex("1") end = start + 1 refactored = self.do_extract_variable(code, start, end, "one", similar=True) expected = dedent("""\ one = 1 if True: a = one else: b = one """) self.assertEqual(expected, refactored) def test_extract_variable_and_similar_statements_in_classes(self): code = dedent("""\ class AClass(object): def func1(self): a = 1 def func2(self): b = 1 """) start = code.index(" 1") + 1 refactored = self.do_extract_variable( code, start, start + 1, "one", similar=True ) expected = dedent("""\ class AClass(object): def func1(self): one = 1 a = one def func2(self): b = 1 """) self.assertEqual(expected, refactored) def test_extract_method_in_staticmethods(self): code = dedent("""\ class AClass(object): @staticmethod def func2(): b = 1 """) start = code.index(" 1") + 1 refactored = self.do_extract_method(code, start, start + 1, "one", similar=True) expected = dedent("""\ class AClass(object): @staticmethod def func2(): b = AClass.one() @staticmethod def one(): return 1 """) self.assertEqual(expected, refactored) def test_extract_normal_method_with_staticmethods(self): code = dedent("""\ class AClass(object): @staticmethod def func1(): b = 1 def func2(self): b = 1 """) start = code.rindex(" 1") + 1 refactored = self.do_extract_method(code, start, start + 1, "one", similar=True) expected = dedent("""\ class AClass(object): @staticmethod def func1(): b = 1 def func2(self): b = self.one() def one(self): return 1 """) self.assertEqual(expected, refactored) def test_extract_variable_with_no_new_lines_at_the_end(self): code = "a_var = 10" start = code.index("10") end = start + 2 refactored = self.do_extract_variable(code, start, end, "new_var") expected = dedent("""\ new_var = 10 a_var = new_var""") self.assertEqual(expected, refactored) def test_extract_method_containing_return_in_functions(self): code = dedent("""\ def f(arg): return arg print(f(1)) """) start, end = self._convert_line_range_to_offset(code, 1, 3) refactored = self.do_extract_method(code, start, end, "a_func") expected = dedent("""\ def a_func(): def f(arg): return arg print(f(1)) a_func() """) self.assertEqual(expected, refactored) def test_extract_method_and_varying_first_parameter(self): code = dedent("""\ class C(object): def f1(self): print(str(self)) def f2(self): print(str(1)) """) start = code.index("print(") + 6 end = code.index("))\n") + 1 refactored = self.do_extract_method(code, start, end, "to_str", similar=True) expected = dedent("""\ class C(object): def f1(self): print(self.to_str()) def to_str(self): return str(self) def f2(self): print(str(1)) """) self.assertEqual(expected, refactored) def test_extract_method_when_an_attribute_exists_in_function_scope(self): code = dedent("""\ class A(object): def func(self): pass a = A() def f(): func = a.func() print(func) """) start, end = self._convert_line_range_to_offset(code, 6, 6) refactored = self.do_extract_method(code, start, end, "g") refactored = refactored[refactored.index("A()") + 4 :] expected = dedent("""\ def f(): func = g() print(func) def g(): func = a.func() return func """) self.assertEqual(expected, refactored) def test_global_option_for_extract_method(self): code = dedent("""\ def a_func(): print(1) """) start, end = self._convert_line_range_to_offset(code, 2, 2) refactored = self.do_extract_method(code, start, end, "extracted", global_=True) expected = dedent("""\ def a_func(): extracted() def extracted(): print(1) """) self.assertEqual(expected, refactored) def test_global_extract_method(self): code = dedent("""\ class AClass(object): def a_func(self): print(1) """) start, end = self._convert_line_range_to_offset(code, 4, 4) refactored = self.do_extract_method(code, start, end, "new_func", global_=True) expected = dedent("""\ class AClass(object): def a_func(self): new_func() def new_func(): print(1) """) self.assertEqual(expected, refactored) def test_global_extract_method_with_multiple_methods(self): code = dedent("""\ class AClass(object): def a_func(self): print(1) def another_func(self): pass """) start, end = self._convert_line_range_to_offset(code, 3, 3) refactored = self.do_extract_method(code, start, end, "new_func", global_=True) expected = dedent("""\ class AClass(object): def a_func(self): new_func() def another_func(self): pass def new_func(): print(1) """) self.assertEqual(expected, refactored) def test_where_to_search_when_extracting_global_names(self): code = dedent("""\ def a(): return 1 def b(): return 1 b = 1 """) start = code.index("1") end = start + 1 refactored = self.do_extract_variable( code, start, end, "one", similar=True, global_=True ) expected = dedent("""\ def a(): return one one = 1 def b(): return one b = one """) self.assertEqual(expected, refactored) def test_extracting_pieces_with_distinct_temp_names(self): code = dedent("""\ a = 1 print(a) b = 1 print(b) """) start = code.index("a") end = code.index("\nb") refactored = self.do_extract_method( code, start, end, "f", similar=True, global_=True ) expected = dedent("""\ def f(): a = 1 print(a) f() f() """) self.assertEqual(expected, refactored) def test_extract_methods_in_glob_funcs_should_be_glob(self): code = dedent("""\ def f(): a = 1 def g(): b = 1 """) start = code.rindex("1") refactored = self.do_extract_method( code, start, start + 1, "one", similar=True, global_=False ) expected = dedent("""\ def f(): a = one() def g(): b = one() def one(): return 1 """) self.assertEqual(expected, refactored) def test_extract_methods_in_glob_funcs_should_be_glob_2(self): code = dedent("""\ if 1: var = 2 """) start = code.rindex("2") refactored = self.do_extract_method( code, start, start + 1, "two", similar=True, global_=False ) expected = dedent("""\ def two(): return 2 if 1: var = two() """) self.assertEqual(expected, refactored) def test_extract_method_and_try_blocks(self): code = dedent("""\ def f(): try: pass except Exception: pass """) start, end = self._convert_line_range_to_offset(code, 2, 5) refactored = self.do_extract_method(code, start, end, "g") expected = dedent("""\ def f(): g() def g(): try: pass except Exception: pass """) self.assertEqual(expected, refactored) @testutils.only_for_versions_higher("3.11") def test_extract_method_and_try_except_star_block_1(self): code = dedent("""\ def f(): try: pass except* Exception: pass """) start, end = self._convert_line_range_to_offset(code, 2, 5) refactored = self.do_extract_method(code, start, end, "g") expected = dedent("""\ def f(): g() def g(): try: pass except* Exception: pass """) self.assertEqual(expected, refactored) def test_extract_method_and_augmented_assignment_nested_1(self): code = dedent("""\ def f(): my_var = [[0], [1], [2]] my_var[0][0] += 1 print(1) """) start, end = self._convert_line_range_to_offset(code, 4, 4) refactored = self.do_extract_method(code, start, end, "g") expected = dedent("""\ def f(): my_var = [[0], [1], [2]] my_var[0][0] += 1 g() def g(): print(1) """) self.assertEqual(expected, refactored) def test_extract_method_and_augmented_assignment_nested_2(self): code = dedent("""\ def f(): my_var = [[0], [1], [2]] my_var[0][0] += 1 print(my_var) """) start, end = self._convert_line_range_to_offset(code, 3, 3) refactored = self.do_extract_method(code, start, end, "g") expected = dedent("""\ def f(): my_var = [[0], [1], [2]] g(my_var) print(my_var) def g(my_var): my_var[0][0] += 1 """) self.assertEqual(expected, refactored) def test_extract_method_and_augmented_assignment_var_to_read_in_lhs(self): code = dedent("""\ def f(): var_to_read = 0 my_var = [0, 1, 2] my_var[var_to_read] += 1 print(my_var) """) start, end = self._convert_line_range_to_offset(code, 4, 4) refactored = self.do_extract_method(code, start, end, "g") expected = dedent("""\ def f(): var_to_read = 0 my_var = [0, 1, 2] g(my_var, var_to_read) print(my_var) def g(my_var, var_to_read): my_var[var_to_read] += 1 """) self.assertEqual(expected, refactored) def test_extract_method_and_augmented_assignment_in_try_block(self): code = dedent("""\ def f(): any_subscriptable = [0] try: any_subscriptable[0] += 1 except Exception: pass """) start, end = self._convert_line_range_to_offset(code, 2, 6) refactored = self.do_extract_method(code, start, end, "g") expected = dedent("""\ def f(): g() def g(): any_subscriptable = [0] try: any_subscriptable[0] += 1 except Exception: pass """) self.assertEqual(expected, refactored) def test_extract_and_not_passing_global_functions(self): code = dedent("""\ def next(p): return p + 1 var = next(1) """) start = code.rindex("next") refactored = self.do_extract_method(code, start, len(code) - 1, "two") expected = dedent("""\ def next(p): return p + 1 def two(): return next(1) var = two() """) self.assertEqual(expected, refactored) def test_extracting_with_only_one_return(self): code = dedent("""\ def f(): var = 1 return var """) start, end = self._convert_line_range_to_offset(code, 2, 3) refactored = self.do_extract_method(code, start, end, "g") expected = dedent("""\ def f(): return g() def g(): var = 1 return var """) self.assertEqual(expected, refactored) def test_extracting_variable_and_implicit_continuations(self): code = dedent("""\ s = ("1" "2") """) start = code.index('"') end = code.rindex('"') + 1 refactored = self.do_extract_variable(code, start, end, "s2") expected = dedent("""\ s2 = "1" "2" s = (s2) """) self.assertEqual(expected, refactored) def test_extracting_method_and_implicit_continuations(self): code = dedent("""\ s = ("1" "2") """) start = code.index('"') end = code.rindex('"') + 1 refactored = self.do_extract_method(code, start, end, "f") expected = dedent("""\ def f(): return "1" "2" s = (f()) """) self.assertEqual(expected, refactored) def test_passing_conditional_updated_vars_in_extracted(self): code = dedent("""\ def f(a): if 0: a = 1 print(a) """) start, end = self._convert_line_range_to_offset(code, 2, 4) refactored = self.do_extract_method(code, start, end, "g") expected = dedent("""\ def f(a): g(a) def g(a): if 0: a = 1 print(a) """) self.assertEqual(expected, refactored) def test_returning_conditional_updated_vars_in_extracted(self): code = dedent("""\ def f(a): if 0: a = 1 print(a) """) start, end = self._convert_line_range_to_offset(code, 2, 3) refactored = self.do_extract_method(code, start, end, "g") expected = dedent("""\ def f(a): a = g(a) print(a) def g(a): if 0: a = 1 return a """) self.assertEqual(expected, refactored) def test_extract_method_with_variables_possibly_written_to(self): code = dedent("""\ def a_func(b): if b > 0: a = 2 print(a) """) start, end = self._convert_line_range_to_offset(code, 2, 3) refactored = self.do_extract_method(code, start, end, "extracted") expected = dedent("""\ def a_func(b): a = extracted(b) print(a) def extracted(b): if b > 0: a = 2 return a """) self.assertEqual(expected, refactored) def test_extract_method_with_list_comprehension(self): code = dedent("""\ def foo(): x = [e for e in []] f = 23 for e, f in []: def bar(): e[42] = 1 """) start, end = self._convert_line_range_to_offset(code, 4, 7) refactored = self.do_extract_method(code, start, end, "baz") expected = dedent("""\ def foo(): x = [e for e in []] f = 23 baz() def baz(): for e, f in []: def bar(): e[42] = 1 """) self.assertEqual(expected, refactored) def test_extract_method_with_list_comprehension_in_class_method(self): code = dedent("""\ class SomeClass: def method(self): result = [i for i in range(1)] print(1) """) start, end = self._convert_line_range_to_offset(code, 4, 4) refactored = self.do_extract_method(code, start, end, "baz", similar=True) expected = dedent("""\ class SomeClass: def method(self): result = [i for i in range(1)] self.baz() def baz(self): print(1) """) self.assertEqual(expected, refactored) def test_extract_method_with_list_comprehension_and_iter(self): code = dedent("""\ def foo(): x = [e for e in []] f = 23 for x, f in x: def bar(): x[42] = 1 """) start, end = self._convert_line_range_to_offset(code, 4, 7) refactored = self.do_extract_method(code, start, end, "baz") expected = dedent("""\ def foo(): x = [e for e in []] f = 23 baz(x) def baz(x): for x, f in x: def bar(): x[42] = 1 """) self.assertEqual(expected, refactored) def test_extract_method_with_list_comprehension_and_orelse(self): code = dedent("""\ def foo(): x = [e for e in []] f = 23 for e, f in []: def bar(): e[42] = 1 """) start, end = self._convert_line_range_to_offset(code, 4, 7) refactored = self.do_extract_method(code, start, end, "baz") expected = dedent("""\ def foo(): x = [e for e in []] f = 23 baz() def baz(): for e, f in []: def bar(): e[42] = 1 """) self.assertEqual(expected, refactored) def test_extract_method_with_list_comprehension_multiple_targets(self): code = dedent("""\ def foo(): x = [(a, b) for a, b in []] f = 23 print("hello") """) start, end = self._convert_line_range_to_offset(code, 4, 4) refactored = self.do_extract_method(code, start, end, "baz") expected = dedent("""\ def foo(): x = [(a, b) for a, b in []] f = 23 baz() def baz(): print("hello") """) self.assertEqual(expected, refactored) def test_extract_function_with_for_else_statemant(self): code = dedent("""\ def a_func(): for i in range(10): a = i else: a = None """) start = code.index("for") end = len(code) - 1 refactored = self.do_extract_method(code, start, end, "new_func") expected = dedent("""\ def a_func(): new_func() def new_func(): for i in range(10): a = i else: a = None """) self.assertEqual(expected, refactored) def test_extract_function_with_for_else_statemant_more(self): """TODO: fixed code to test passed""" code = dedent("""\ def a_func(): for i in range(10): a = i else: for i in range(5): b = i else: b = None a = None """) start = code.index("for") end = len(code) - 1 refactored = self.do_extract_method(code, start, end, "new_func") expected = dedent("""\ def a_func(): new_func() def new_func(): for i in range(10): a = i else: for i in range(5): b = i else: b = None a = None """) self.assertEqual(expected, refactored) def test_extract_function_with_for_else_statemant_outside_loops(self): code = dedent("""\ def a_func(): for i in range(10): a = i else: a=None """) start = code.index("a = i") 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_statement_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) @testutils.only_for_versions_higher("3.8") def test_extract_function_expression_with_inline_assignment_in_condition(self): code = dedent("""\ def foo(a): if i := a == 5: i += 1 print(i) """) extract_target = "i := a == 5" start, end = code.index(extract_target), code.index(extract_target) + len( extract_target ) refactored = self.do_extract_method(code, start, end, "new_func") expected = dedent("""\ def foo(a): if i := new_func(a): i += 1 print(i) def new_func(a): return (i := a == 5) """) self.assertEqual(expected, refactored) @testutils.only_for_versions_higher("3.8") def test_extract_function_expression_with_inline_assignment_complex(self): code = dedent("""\ def foo(a): if i := a == (c := 5): i += 1 c += 1 print(i) """) extract_target = "i := a == (c := 5)" start, end = code.index(extract_target), code.index(extract_target) + len( extract_target ) refactored = self.do_extract_method(code, start, end, "new_func") expected = dedent("""\ def foo(a): if i, c := new_func(a): i += 1 c += 1 print(i) def new_func(a): return (i := a == (c := 5)) """) self.assertEqual(expected, refactored) @testutils.only_for_versions_higher("3.8") def test_extract_function_expression_with_inline_assignment_in_inner_expression( self, ): code = dedent("""\ def foo(a): if a == (c := 5): c += 1 print(i) """) extract_target = "a == (c := 5)" start, end = code.index(extract_target), code.index(extract_target) + len( extract_target ) with self.assertRaisesRegex( rope.base.exceptions.RefactoringError, "Extracted piece cannot contain named expression \\(:= operator\\).", ): self.do_extract_method(code, start, end, "new_func") def test_extract_exec(self): code = dedent("""\ exec("def f(): pass", {}) """) start, end = self._convert_line_range_to_offset(code, 1, 1) refactored = self.do_extract_method(code, start, end, "new_func") expected = dedent("""\ def new_func(): exec("def f(): pass", {}) new_func() """) self.assertEqual(expected, refactored) @testutils.only_for_versions_higher("3.5") def test_extract_async_function(self): code = dedent("""\ async def my_func(my_list): for x in my_list: var = x + 1 return var """) start, end = self._convert_line_range_to_offset(code, 3, 3) refactored = self.do_extract_method(code, start, end, "new_func") expected = dedent("""\ async def my_func(my_list): for x in my_list: var = new_func(x) return var def new_func(x): var = x + 1 return var """) self.assertEqual(expected, refactored) @testutils.only_for_versions_higher("3.5") def test_extract_inner_async_function(self): code = dedent("""\ def my_func(my_list): async def inner_func(my_list): for x in my_list: var = x + 1 return inner_func """) start, end = self._convert_line_range_to_offset(code, 2, 4) refactored = self.do_extract_method(code, start, end, "new_func") expected = dedent("""\ def my_func(my_list): inner_func = new_func(my_list) return inner_func def new_func(my_list): async def inner_func(my_list): for x in my_list: var = x + 1 return inner_func """) self.assertEqual(expected, refactored) @testutils.only_for_versions_higher("3.5") def test_extract_around_inner_async_function(self): code = dedent("""\ def my_func(lst): async def inner_func(obj): for x in obj: var = x + 1 return map(inner_func, lst) """) start, end = self._convert_line_range_to_offset(code, 5, 5) refactored = self.do_extract_method(code, start, end, "new_func") expected = dedent("""\ def my_func(lst): async def inner_func(obj): for x in obj: var = x + 1 return new_func(inner_func, lst) def new_func(inner_func, lst): return map(inner_func, lst) """) self.assertEqual(expected, refactored) @testutils.only_for_versions_higher("3.5") def test_extract_refactor_around_async_for_loop(self): code = dedent("""\ async def my_func(my_list): async for x in my_list: var = x + 1 return var """) start, end = self._convert_line_range_to_offset(code, 3, 3) refactored = self.do_extract_method(code, start, end, "new_func") expected = dedent("""\ async def my_func(my_list): async for x in my_list: var = new_func(x) return var def new_func(x): var = x + 1 return var """) self.assertEqual(expected, refactored) @testutils.only_for_versions_higher("3.5") @testutils.only_for_versions_lower("3.8") def test_extract_refactor_containing_async_for_loop_should_error_before_py38(self): """ Refactoring async/await syntaxes is only supported in Python 3.8 and higher because support for ast.PyCF_ALLOW_TOP_LEVEL_AWAIT was only added to the standard library in Python 3.8. """ code = dedent("""\ async def my_func(my_list): async for x in my_list: var = x + 1 return var """) start, end = self._convert_line_range_to_offset(code, 2, 3) with self.assertRaisesRegex( rope.base.exceptions.RefactoringError, "Extracted piece can only have async/await statements if Rope is running on Python 3.8 or higher", ): self.do_extract_method(code, start, end, "new_func") @testutils.only_for_versions_higher("3.8") def test_extract_refactor_containing_async_for_loop_is_supported_after_py38(self): code = dedent("""\ async def my_func(my_list): async for x in my_list: var = x + 1 return var """) start, end = self._convert_line_range_to_offset(code, 2, 3) refactored = self.do_extract_method(code, start, end, "new_func") expected = dedent("""\ async def my_func(my_list): var = new_func(my_list) return var def new_func(my_list): async for x in my_list: var = x + 1 return var """) self.assertEqual(expected, refactored) @testutils.only_for_versions_higher("3.5") def test_extract_await_expression(self): code = dedent("""\ async def my_func(my_list): for url in my_list: resp = await request(url) return resp """) selected = "request(url)" start, end = code.index(selected), code.index(selected) + len(selected) refactored = self.do_extract_method(code, start, end, "new_func") expected = dedent("""\ async def my_func(my_list): for url in my_list: resp = await new_func(url) return resp def new_func(url): return request(url) """) self.assertEqual(expected, refactored) def test_extract_to_staticmethod(self): code = dedent("""\ class A: def first_method(self): a_var = 1 b_var = a_var + 1 """) extract_target = "a_var + 1" start, end = code.index(extract_target), code.index(extract_target) + len( extract_target ) refactored = self.do_extract_method( code, start, end, "second_method", kind="staticmethod" ) expected = dedent("""\ class A: def first_method(self): a_var = 1 b_var = A.second_method(a_var) @staticmethod def second_method(a_var): return a_var + 1 """) self.assertEqual(expected, refactored) def test_extract_to_staticmethod_when_self_in_body(self): code = dedent("""\ class A: def first_method(self): a_var = 1 b_var = self.a_var + 1 """) extract_target = "self.a_var + 1" start, end = code.index(extract_target), code.index(extract_target) + len( extract_target ) refactored = self.do_extract_method( code, start, end, "second_method", kind="staticmethod" ) expected = dedent("""\ class A: def first_method(self): a_var = 1 b_var = A.second_method(self) @staticmethod def second_method(self): return self.a_var + 1 """) self.assertEqual(expected, refactored) def test_extract_from_function_to_staticmethod_raises_exception(self): code = dedent("""\ def first_method(): a_var = 1 b_var = a_var + 1 """) extract_target = "a_var + 1" start, end = code.index(extract_target), code.index(extract_target) + len( extract_target ) with self.assertRaisesRegex( rope.base.exceptions.RefactoringError, "Cannot extract to staticmethod/classmethod outside class", ): self.do_extract_method( code, start, end, "second_method", kind="staticmethod" ) def test_extract_method_in_classmethods(self): code = dedent("""\ class AClass(object): @classmethod def func2(cls): b = 1 """) start = code.index(" 1") + 1 refactored = self.do_extract_method(code, start, start + 1, "one", similar=True) expected = dedent("""\ class AClass(object): @classmethod def func2(cls): b = AClass.one() @classmethod def one(cls): return 1 """) self.assertEqual(expected, refactored) def test_extract_from_function_to_classmethod_raises_exception(self): code = dedent("""\ def first_method(): a_var = 1 b_var = a_var + 1 """) extract_target = "a_var + 1" start, end = code.index(extract_target), code.index(extract_target) + len( extract_target ) with self.assertRaisesRegex( rope.base.exceptions.RefactoringError, "Cannot extract to staticmethod/classmethod outside class", ): self.do_extract_method( code, start, end, "second_method", kind="classmethod" ) def test_extract_to_classmethod_when_self_in_body(self): code = dedent("""\ class A: def first_method(self): a_var = 1 b_var = self.a_var + 1 """) extract_target = "self.a_var + 1" start, end = code.index(extract_target), code.index(extract_target) + len( extract_target ) refactored = self.do_extract_method( code, start, end, "second_method", kind="classmethod" ) expected = dedent("""\ class A: def first_method(self): a_var = 1 b_var = A.second_method(self) @classmethod def second_method(cls, self): return self.a_var + 1 """) self.assertEqual(expected, refactored) def test_extract_to_classmethod(self): code = dedent("""\ class A: def first_method(self): a_var = 1 b_var = a_var + 1 """) extract_target = "a_var + 1" start, end = code.index(extract_target), code.index(extract_target) + len( extract_target ) refactored = self.do_extract_method( code, start, end, "second_method", kind="classmethod" ) expected = dedent("""\ class A: def first_method(self): a_var = 1 b_var = A.second_method(a_var) @classmethod def second_method(cls, a_var): return a_var + 1 """) self.assertEqual(expected, refactored) def test_extract_to_classmethod_when_name_starts_with_at_sign(self): code = dedent("""\ class A: def first_method(self): a_var = 1 b_var = a_var + 1 """) extract_target = "a_var + 1" start, end = code.index(extract_target), code.index(extract_target) + len( extract_target ) refactored = self.do_extract_method(code, start, end, "@second_method") expected = dedent("""\ class A: def first_method(self): a_var = 1 b_var = A.second_method(a_var) @classmethod def second_method(cls, a_var): return a_var + 1 """) self.assertEqual(expected, refactored) def test_extract_to_staticmethod_when_name_starts_with_dollar_sign(self): code = dedent("""\ class A: def first_method(self): a_var = 1 b_var = a_var + 1 """) extract_target = "a_var + 1" start, end = code.index(extract_target), code.index(extract_target) + len( extract_target ) refactored = self.do_extract_method(code, start, end, "$second_method") expected = dedent("""\ class A: def first_method(self): a_var = 1 b_var = A.second_method(a_var) @staticmethod def second_method(a_var): return a_var + 1 """) self.assertEqual(expected, refactored) def test_raises_exception_when_sign_in_name_and_kind_mismatch(self): with self.assertRaisesRegex( rope.base.exceptions.RefactoringError, "Kind and shortcut in name mismatch" ): self.do_extract_method("code", 0, 1, "$second_method", kind="classmethod") def test_extracting_from_static_with_function_arg(self): code = dedent("""\ class A: @staticmethod def first_method(someargs): b_var = someargs + 1 """) extract_target = "someargs + 1" start, end = code.index(extract_target), code.index(extract_target) + len( extract_target ) refactored = self.do_extract_method(code, start, end, "second_method") expected = dedent("""\ class A: @staticmethod def first_method(someargs): b_var = A.second_method(someargs) @staticmethod def second_method(someargs): return someargs + 1 """) self.assertEqual(expected, refactored) def test_extract_with_list_comprehension(self): code = dedent("""\ def f(): y = [1,2,3,4] a = sum([x for x in y]) b = sum([x for x in y]) print(a, b) f() """) extract_target = " a = sum([x for x in y])\n" start, end = code.index(extract_target), code.index(extract_target) + len( extract_target ) refactored = self.do_extract_method(code, start, end, "_a") expected = dedent("""\ def f(): y = [1,2,3,4] a = _a(y) b = sum([x for x in y]) print(a, b) def _a(y): a = sum([x for x in y]) return a f() """) self.assertEqual(expected, refactored) def test_extract_with_generator(self): code = dedent("""\ def f(): y = [1,2,3,4] a = sum(x for x in y) b = sum(x for x in y) print(a, b) f() """) extract_target = " a = sum(x for x in y)\n" start, end = code.index(extract_target), code.index(extract_target) + len( extract_target ) refactored = self.do_extract_method(code, start, end, "_a") expected = dedent("""\ def f(): y = [1,2,3,4] a = _a(y) b = sum(x for x in y) print(a, b) def _a(y): a = sum(x for x in y) return a f() """) self.assertEqual(expected, refactored) def test_extract_with_generator_2(self): code = dedent("""\ def f(): y = [1,2,3,4] a = sum(x for x in y) """) extract_target = "x for x in y" start, end = code.index(extract_target), code.index(extract_target) + len( extract_target ) refactored = self.do_extract_method(code, start, end, "_a") expected = dedent("""\ def f(): y = [1,2,3,4] a = sum(_a(y)) def _a(y): return (x for x in y) """) self.assertEqual(expected, refactored) def test_extract_with_set_comprehension(self): code = dedent("""\ def f(): y = [1,2,3,4] a = sum({x for x in y}) b = sum({x for x in y}) print(a, b) f() """) extract_target = " a = sum({x for x in y})\n" start, end = code.index(extract_target), code.index(extract_target) + len( extract_target ) refactored = self.do_extract_method(code, start, end, "_a") expected = dedent("""\ def f(): y = [1,2,3,4] a = _a(y) b = sum({x for x in y}) print(a, b) def _a(y): a = sum({x for x in y}) return a f() """) self.assertEqual(expected, refactored) def test_extract_with_dict_comprehension(self): code = dedent("""\ def f(): y = [1,2,3,4] a = sum({x: x for x in y}) b = sum({x: x for x in y}) print(a, b) f() """) extract_target = " a = sum({x: x for x in y})\n" start, end = code.index(extract_target), code.index(extract_target) + len( extract_target ) refactored = self.do_extract_method(code, start, end, "_a") expected = dedent("""\ def f(): y = [1,2,3,4] a = _a(y) b = sum({x: x for x in y}) print(a, b) def _a(y): a = sum({x: x for x in y}) return a f() """) self.assertEqual(expected, refactored) def test_extract_function_expression_with_assignment_to_attribute(self): code = dedent("""\ class A(object): def func(self): self.var_a = 1 var_bb = self.var_a """) extract_target = "= self.var_a" start, end = ( code.index(extract_target) + 2, code.index(extract_target) + 2 + len(extract_target) - 2, ) refactored = self.do_extract_method(code, start, end, "new_func", similar=True) expected = dedent("""\ class A(object): def func(self): self.var_a = 1 var_bb = self.new_func() def new_func(self): return self.var_a """) self.assertEqual(expected, refactored) def test_extract_function_expression_with_assignment_index(self): code = dedent("""\ class A(object): def func(self, val): self[val] = 1 var_bb = self[val] """) extract_target = "= self[val]" start, end = ( code.index(extract_target) + 2, code.index(extract_target) + 2 + len(extract_target) - 2, ) refactored = self.do_extract_method(code, start, end, "new_func", similar=True) expected = dedent("""\ class A(object): def func(self, val): self[val] = 1 var_bb = self.new_func(val) def new_func(self, val): return self[val] """) self.assertEqual(expected, refactored) def test_extraction_method_with_global_variable(self): code = dedent("""\ g = None def f(): global g g = 2 f() print(g) """) extract_target = "g = 2" start, end = code.index(extract_target), code.index(extract_target) + len( extract_target ) refactored = self.do_extract_method(code, start, end, "_g") expected = dedent("""\ g = None def f(): global g _g() def _g(): global g g = 2 f() print(g) """) self.assertEqual(expected, refactored) def test_extraction_method_with_global_variable_and_global_declaration(self): code = dedent("""\ g = None def f(): global g g = 2 f() print(g) """) start, end = 23, 42 refactored = self.do_extract_method(code, start, end, "_g") expected = dedent("""\ g = None def f(): _g() def _g(): global g g = 2 f() print(g) """) self.assertEqual(expected, refactored) def test_extraction_one_line_with_global_variable_read_only(self): code = dedent("""\ g = None def f(): global g a = g f() print(g) """) extract_target = "= g" start, end = code.index(extract_target) + 2, code.index(extract_target) + 3 refactored = self.do_extract_method(code, start, end, "_g") expected = dedent("""\ g = None def f(): global g a = _g() def _g(): return g f() print(g) """) self.assertEqual(expected, refactored) @testutils.only_for_versions_higher("3.8") def test_extraction_one_line_with_global_variable(self): code = dedent("""\ g = None def f(): global g while g := 4: pass f() print(g) """) extract_target = "g := 4" start, end = code.index(extract_target), code.index(extract_target) + len( extract_target ) refactored = self.do_extract_method(code, start, end, "_g") expected = dedent("""\ g = None def f(): global g while _g(): pass def _g(): global g return (g := 4) f() print(g) """) self.assertEqual(expected, refactored) @testutils.only_for_versions_higher("3.8") def test_extraction_one_line_with_global_variable_has_postread(self): code = dedent("""\ g = None def f(): global g while g := 4: print(g) f() print(g) """) extract_target = "g := 4" start, end = code.index(extract_target), code.index(extract_target) + len( extract_target ) refactored = self.do_extract_method(code, start, end, "_g") expected = dedent("""\ g = None def f(): global g while g := _g(): print(g) def _g(): global g return (g := 4) f() print(g) """) self.assertEqual(expected, refactored) def test_extraction_with_nonlocal_variable(self): code = dedent("""\ def outer(): a = 0 def inner(): nonlocal a a = 3 """) extract_target = "a = 3" start, end = code.index(extract_target), code.index(extract_target) + len( extract_target ) refactored = self.do_extract_method(code, start, end, "extracted") expected = dedent("""\ def outer(): a = 0 def inner(): nonlocal a extracted() def extracted(): nonlocal a a = 3 """) self.assertEqual(expected, refactored) def test_extraction_method_with_nonlocal_variable_and_nonlocal_declaration(self): code = dedent("""\ def outer(): g = None def inner(): nonlocal g g = 2 inner() print(g) """) start, end = 52, 78 refactored = self.do_extract_method(code, start, end, "_g") expected = dedent("""\ def outer(): g = None def inner(): _g() def _g(): nonlocal g g = 2 inner() print(g) """) self.assertEqual(expected, refactored) def test_extraction_one_line_with_nonlocal_variable_read_only(self): code = dedent("""\ def outer(): g = None def inner(): nonlocal g a = g inner() print(g) """) extract_target = "= g" start, end = code.index(extract_target) + 2, code.index(extract_target) + 3 refactored = self.do_extract_method(code, start, end, "_g") expected = dedent("""\ def outer(): g = None def inner(): nonlocal g a = _g() def _g(): return g inner() print(g) """) self.assertEqual(expected, refactored) @testutils.only_for_versions_higher("3.8") def test_extraction_one_line_with_nonlocal_variable(self): code = dedent("""\ def outer(): g = None def inner(): nonlocal g while g := 4: pass inner() print(g) """) extract_target = "g := 4" start, end = code.index(extract_target), code.index(extract_target) + len( extract_target ) refactored = self.do_extract_method(code, start, end, "_g") expected = dedent("""\ def outer(): g = None def inner(): nonlocal g while _g(): pass def _g(): nonlocal g return (g := 4) inner() print(g) """) self.assertEqual(expected, refactored) @testutils.only_for_versions_higher("3.8") def test_extraction_one_line_with_nonlocal_variable_has_postread(self): code = dedent("""\ def outer(): g = None def inner(): nonlocal g while g := 4: print(g) inner() print(g) """) extract_target = "g := 4" start, end = code.index(extract_target), code.index(extract_target) + len( extract_target ) refactored = self.do_extract_method(code, start, end, "_g") expected = dedent("""\ def outer(): g = None def inner(): nonlocal g while g := _g(): print(g) def _g(): nonlocal g return (g := 4) inner() print(g) """) self.assertEqual(expected, refactored) def test_extract_method_with_nested_double_with_as(self): code = dedent("""\ with open("test") as file1: with open("test") as file2: print(file1, file2) """) start, end = self._convert_line_range_to_offset(code, 3, 4) refactored = self.do_extract_method(code, start, end, "extracted", global_=True) expected = dedent("""\ def extracted(file1, file2): print(file1, file2) with open("test") as file1: with open("test") as file2: extracted(file1, file2) """) self.assertEqual(expected, refactored) def test_extract_method_with_double_with_as(self): code = dedent("""\ with open("test") as file1, open("test") as file2: print(file1, file2) """) start, end = self._convert_line_range_to_offset(code, 2, 3) refactored = self.do_extract_method(code, start, end, "extracted", global_=True) expected = dedent("""\ def extracted(file1, file2): print(file1, file2) with open("test") as file1, open("test") as file2: extracted(file1, file2) """) self.assertEqual(expected, refactored) def test_extract_method_with_nested_double_with_as_and_misleading_comment(self): code = dedent("""\ with open("test") as file1, open("test") as file2: # with in comment bar() """) start, end = self._convert_line_range_to_offset(code, 3, 3) refactored = self.do_extract_method(code, start, end, "extracted", global_=True) expected = dedent("""\ def extracted(): bar() with open("test") as file1, open("test") as file2: # with in comment extracted() """) self.assertEqual(expected, refactored) @testutils.only_for_versions_higher("3.8") def test_extract_method_async_with_simple(self): code = dedent("""\ async def afunc(): async with open("test") as file1: print(file1) """) start, end = self._convert_line_range_to_offset(code, 2, 3) refactored = self.do_extract_method(code, start, end, "extracted", global_=True) expected = dedent("""\ async def afunc(): extracted() def extracted(): async with open("test") as file1: print(file1) """) self.assertEqual(expected, refactored) @testutils.only_for_versions_higher("3.8") def test_extract_method_containing_async_with(self): code = dedent("""\ async def afunc(): async with open("test") as file1, open("test") as file2: print(file1, file2) """) start, end = self._convert_line_range_to_offset(code, 3, 3) refactored = self.do_extract_method(code, start, end, "extracted", global_=True) expected = dedent("""\ async def afunc(): async with open("test") as file1, open("test") as file2: extracted(file1, file2) def extracted(file1, file2): print(file1, file2) """) self.assertEqual(expected, refactored) @testutils.only_for_versions_higher("3.10") def test_extract_method_containing_structural_pattern_match(self): code = dedent("""\ match var: case Foo("xx"): print(x) case Foo(x): print(x) """) start, end = self._convert_line_range_to_offset(code, 5, 5) refactored = self.do_extract_method(code, start, end, "extracted") expected = dedent("""\ def extracted(): print(x) match var: case Foo("xx"): print(x) case Foo(x): extracted() """) self.assertEqual(expected, refactored) @testutils.only_for_versions_higher("3.10") def test_extract_method_containing_structural_pattern_match_2(self): code = dedent("""\ def foo(): match var: case Foo(x): print(x) """) start, end = self._convert_line_range_to_offset(code, 4, 4) refactored = self.do_extract_method(code, start, end, "extracted") expected = dedent("""\ def foo(): match var: case Foo(x): extracted(x) def extracted(x): print(x) """) self.assertEqual(expected, refactored) @testutils.only_for_versions_higher("3.10") def test_extract_method_containing_structural_pattern_match_3(self): code = dedent("""\ def foo(): match var: case {"hello": x} as y: print(x) """) start, end = self._convert_line_range_to_offset(code, 4, 4) refactored = self.do_extract_method(code, start, end, "extracted") expected = dedent("""\ def foo(): match var: case {"hello": x} as y: extracted(x) def extracted(x): print(x) """) self.assertEqual(expected, refactored) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/ropetest/refactor/importutilstest.py0000664000175000017500000020757414512700666022037 0ustar00lieryanlieryanimport unittest from textwrap import dedent from rope.refactor.importutils import ImportTools, add_import, importinfo from ropetest import testutils class ImportUtilsTest(unittest.TestCase): def setUp(self): super().setUp() self.project = testutils.sample_project() self.import_tools = ImportTools(self.project) self.mod = testutils.create_module(self.project, "mod") self.pkg1 = testutils.create_package(self.project, "pkg1") self.mod1 = testutils.create_module(self.project, "mod1", self.pkg1) self.pkg2 = testutils.create_package(self.project, "pkg2") self.mod2 = testutils.create_module(self.project, "mod2", self.pkg2) self.mod3 = testutils.create_module(self.project, "mod3", self.pkg2) p1 = testutils.create_package(self.project, "p1") p2 = testutils.create_package(self.project, "p2", p1) p3 = testutils.create_package(self.project, "p3", p2) m1 = testutils.create_module(self.project, "m1", p3) # noqa l = testutils.create_module(self.project, "l", p3) # noqa def tearDown(self): testutils.remove_project(self.project) super().tearDown() def test_get_import_for_module(self): mod = self.project.find_module("mod") import_statement = self.import_tools.get_import(mod) self.assertEqual("import mod", import_statement.get_import_statement()) def test_get_import_for_module_in_nested_modules(self): mod = self.project.find_module("pkg1.mod1") import_statement = self.import_tools.get_import(mod) self.assertEqual("import pkg1.mod1", import_statement.get_import_statement()) def test_get_import_for_module_in_init_dot_py(self): init_dot_py = self.pkg1.get_child("__init__.py") import_statement = self.import_tools.get_import(init_dot_py) self.assertEqual("import pkg1", import_statement.get_import_statement()) def test_get_from_import_for_module(self): mod = self.project.find_module("mod") import_statement = self.import_tools.get_from_import(mod, "a_func") self.assertEqual( "from mod import a_func", import_statement.get_import_statement() ) def test_get_from_import_for_module_in_nested_modules(self): mod = self.project.find_module("pkg1.mod1") import_statement = self.import_tools.get_from_import(mod, "a_func") self.assertEqual( "from pkg1.mod1 import a_func", import_statement.get_import_statement() ) def test_get_from_import_for_module_in_init_dot_py(self): init_dot_py = self.pkg1.get_child("__init__.py") import_statement = self.import_tools.get_from_import(init_dot_py, "a_func") self.assertEqual( "from pkg1 import a_func", import_statement.get_import_statement() ) def test_get_import_statements(self): self.mod.write("import pkg1\n") pymod = self.project.get_module("mod") module_with_imports = self.import_tools.module_imports(pymod) imports = module_with_imports.imports self.assertEqual("import pkg1", imports[0].import_info.get_import_statement()) def test_get_import_statements_with_alias(self): self.mod.write("import pkg1.mod1 as mod1\n") pymod = self.project.get_module("mod") module_with_imports = self.import_tools.module_imports(pymod) imports = module_with_imports.imports self.assertEqual( "import pkg1.mod1 as mod1", imports[0].import_info.get_import_statement() ) def test_get_import_statements_for_froms(self): self.mod.write("from pkg1 import mod1\n") pymod = self.project.get_module("mod") module_with_imports = self.import_tools.module_imports(pymod) imports = module_with_imports.imports self.assertEqual( "from pkg1 import mod1", imports[0].import_info.get_import_statement() ) def test_get_multi_line_import_statements_for_froms(self): self.mod.write("from pkg1 \\\n import mod1\n") pymod = self.project.get_module("mod") module_with_imports = self.import_tools.module_imports(pymod) imports = module_with_imports.imports self.assertEqual( "from pkg1 import mod1", imports[0].import_info.get_import_statement() ) def test_get_import_statements_for_from_star(self): self.mod.write("from pkg1 import *\n") pymod = self.project.get_module("mod") module_with_imports = self.import_tools.module_imports(pymod) imports = module_with_imports.imports self.assertEqual( "from pkg1 import *", imports[0].import_info.get_import_statement() ) def test_get_import_statements_for_new_relatives(self): self.mod2.write("from .mod3 import x\n") pymod = self.project.get_module("pkg2.mod2") module_with_imports = self.import_tools.module_imports(pymod) imports = module_with_imports.imports self.assertEqual( "from .mod3 import x", imports[0].import_info.get_import_statement() ) def test_ignoring_indented_imports(self): self.mod.write(dedent("""\ if True: import pkg1 """)) pymod = self.project.get_module("mod") module_with_imports = self.import_tools.module_imports(pymod) imports = module_with_imports.imports self.assertEqual(0, len(imports)) def test_import_get_names(self): self.mod.write("import pkg1 as pkg\n") pymod = self.project.get_module("mod") module_with_imports = self.import_tools.module_imports(pymod) imports = module_with_imports.imports context = importinfo.ImportContext(self.project, self.project.root) self.assertEqual(["pkg"], imports[0].import_info.get_imported_names(context)) def test_import_get_names_with_alias(self): self.mod.write("import pkg1.mod1\n") pymod = self.project.get_module("mod") module_with_imports = self.import_tools.module_imports(pymod) imports = module_with_imports.imports context = importinfo.ImportContext(self.project, self.project.root) self.assertEqual(["pkg1"], imports[0].import_info.get_imported_names(context)) def test_import_get_names_with_alias2(self): self.mod1.write(dedent("""\ def a_func(): pass """)) self.mod.write("from pkg1.mod1 import *\n") pymod = self.project.get_module("mod") module_with_imports = self.import_tools.module_imports(pymod) imports = module_with_imports.imports context = importinfo.ImportContext(self.project, self.project.root) self.assertEqual(["a_func"], imports[0].import_info.get_imported_names(context)) def test_empty_getting_used_imports(self): self.mod.write("") pymod = self.project.get_module("mod") module_with_imports = self.import_tools.module_imports(pymod) imports = module_with_imports.get_used_imports(pymod) self.assertEqual(0, len(imports)) def test_empty_getting_used_imports2(self): self.mod.write("import pkg\n") pymod = self.project.get_module("mod") module_with_imports = self.import_tools.module_imports(pymod) imports = module_with_imports.get_used_imports(pymod) self.assertEqual(0, len(imports)) def test_simple_getting_used_imports(self): self.mod.write("import pkg\nprint(pkg)\n") pymod = self.project.get_module("mod") module_with_imports = self.import_tools.module_imports(pymod) imports = module_with_imports.get_used_imports(pymod) self.assertEqual(1, len(imports)) self.assertEqual("import pkg", imports[0].get_import_statement()) def test_simple_getting_used_imports2(self): self.mod.write(dedent("""\ import pkg def a_func(): print(pkg) """)) pymod = self.project.get_module("mod") module_with_imports = self.import_tools.module_imports(pymod) imports = module_with_imports.get_used_imports(pymod) self.assertEqual(1, len(imports)) self.assertEqual("import pkg", imports[0].get_import_statement()) def test_getting_used_imports_for_nested_scopes(self): self.mod.write(dedent("""\ import pkg1 print(pkg1) def a_func(): pass print(pkg1) """)) pymod = self.project.get_module("mod") module_with_imports = self.import_tools.module_imports(pymod) imports = module_with_imports.get_used_imports(pymod["a_func"].get_object()) self.assertEqual(0, len(imports)) def test_getting_used_imports_for_nested_scopes2(self): self.mod.write(dedent("""\ from pkg1 import mod1 def a_func(): print(mod1) """)) pymod = self.project.get_module("mod") module_with_imports = self.import_tools.module_imports(pymod) imports = module_with_imports.get_used_imports(pymod["a_func"].get_object()) self.assertEqual(1, len(imports)) self.assertEqual("from pkg1 import mod1", imports[0].get_import_statement()) def test_empty_removing_unused_imports(self): self.mod.write("import pkg1\nprint(pkg1)\n") pymod = self.project.get_module("mod") module_with_imports = self.import_tools.module_imports(pymod) module_with_imports.remove_unused_imports() self.assertEqual( "import pkg1\nprint(pkg1)\n", module_with_imports.get_changed_source() ) def test_simple_removing_unused_imports(self): self.mod.write("import pkg1\n\n") pymod = self.project.get_module("mod") module_with_imports = self.import_tools.module_imports(pymod) module_with_imports.remove_unused_imports() self.assertEqual("", module_with_imports.get_changed_source()) def test_simple_removing_unused_imports_for_froms(self): self.mod1.write(dedent("""\ def a_func(): pass def another_func(): pass """)) self.mod.write(dedent("""\ from pkg1.mod1 import a_func, another_func a_func() """)) pymod = self.project.get_module("mod") module_with_imports = self.import_tools.module_imports(pymod) module_with_imports.remove_unused_imports() self.assertEqual( dedent("""\ from pkg1.mod1 import a_func a_func() """), module_with_imports.get_changed_source(), ) def test_simple_removing_unused_imports_for_from_stars(self): self.mod.write(dedent("""\ from pkg1.mod1 import * """)) pymod = self.project.get_module("mod") module_with_imports = self.import_tools.module_imports(pymod) module_with_imports.remove_unused_imports() self.assertEqual("", module_with_imports.get_changed_source()) def test_simple_removing_unused_imports_for_nested_modules(self): self.mod1.write(dedent("""\ def a_func(): pass """)) self.mod.write(dedent("""\ import pkg1.mod1 pkg1.mod1.a_func()""")) pymod = self.project.get_module("mod") module_with_imports = self.import_tools.module_imports(pymod) module_with_imports.remove_unused_imports() self.assertEqual( "import pkg1.mod1\npkg1.mod1.a_func()", module_with_imports.get_changed_source(), ) def test_removing_unused_imports_and_functions_of_the_same_name(self): self.mod.write(dedent("""\ def a_func(): pass def a_func(): pass """)) pymod = self.project.get_module("mod") module_with_imports = self.import_tools.module_imports(pymod) module_with_imports.remove_unused_imports() self.assertEqual( dedent("""\ def a_func(): pass def a_func(): pass """), module_with_imports.get_changed_source(), ) def test_removing_unused_imports_for_from_import_with_as(self): self.mod.write("a_var = 1\n") self.mod1.write(dedent("""\ from mod import a_var as myvar a_var = myvar """)) pymod = self.project.get_pymodule(self.mod1) module_with_imports = self.import_tools.module_imports(pymod) module_with_imports.remove_unused_imports() self.assertEqual( dedent("""\ from mod import a_var as myvar a_var = myvar """), module_with_imports.get_changed_source(), ) def test_not_removing_imports_that_conflict_with_class_names(self): code = dedent("""\ import pkg1 class A(object): pkg1 = 0 def f(self): a_var = pkg1 """) self.mod.write(code) pymod = self.project.get_module("mod") module_with_imports = self.import_tools.module_imports(pymod) module_with_imports.remove_unused_imports() self.assertEqual(code, module_with_imports.get_changed_source()) def test_adding_imports(self): self.mod.write("\n") pymod = self.project.get_module("mod") module_with_imports = self.import_tools.module_imports(pymod) new_import = self.import_tools.get_import(self.mod1) module_with_imports.add_import(new_import) self.assertEqual("import pkg1.mod1\n", module_with_imports.get_changed_source()) def test_adding_imports_no_pull_to_top(self): self.mod.write(dedent("""\ import pkg2.mod3 class A(object): pass import pkg2.mod2 """)) pymod = self.project.get_module("mod") self.project.prefs["pull_imports_to_top"] = False module_with_imports = self.import_tools.module_imports(pymod) new_import = self.import_tools.get_import(self.mod1) module_with_imports.add_import(new_import) self.assertEqual( dedent("""\ import pkg2.mod3 class A(object): pass import pkg2.mod2 import pkg1.mod1 """), module_with_imports.get_changed_source(), ) def test_adding_from_imports(self): self.mod1.write(dedent("""\ def a_func(): pass def another_func(): pass """)) self.mod.write("from pkg1.mod1 import a_func\n") pymod = self.project.get_module("mod") module_with_imports = self.import_tools.module_imports(pymod) new_import = self.import_tools.get_from_import(self.mod1, "another_func") module_with_imports.add_import(new_import) self.assertEqual( "from pkg1.mod1 import a_func, another_func\n", module_with_imports.get_changed_source(), ) def test_adding_to_star_imports(self): self.mod1.write(dedent("""\ def a_func(): pass def another_func(): pass """)) self.mod.write("from pkg1.mod1 import *\n") pymod = self.project.get_module("mod") module_with_imports = self.import_tools.module_imports(pymod) new_import = self.import_tools.get_from_import(self.mod1, "another_func") module_with_imports.add_import(new_import) self.assertEqual( "from pkg1.mod1 import *\n", module_with_imports.get_changed_source() ) def test_adding_star_imports(self): self.mod1.write(dedent("""\ def a_func(): pass def another_func(): pass """)) self.mod.write("from pkg1.mod1 import a_func\n") pymod = self.project.get_module("mod") module_with_imports = self.import_tools.module_imports(pymod) new_import = self.import_tools.get_from_import(self.mod1, "*") module_with_imports.add_import(new_import) self.assertEqual( "from pkg1.mod1 import *\n", module_with_imports.get_changed_source() ) def test_adding_imports_and_preserving_spaces_after_imports(self): self.mod.write(dedent("""\ import pkg1 print(pkg1) """)) pymod = self.project.get_module("mod") module_with_imports = self.import_tools.module_imports(pymod) new_import = self.import_tools.get_import(self.pkg2) module_with_imports.add_import(new_import) self.assertEqual( dedent("""\ import pkg1 import pkg2 print(pkg1) """), module_with_imports.get_changed_source(), ) def test_not_changing_the_format_of_unchanged_imports(self): self.mod1.write(dedent("""\ def a_func(): pass def another_func(): pass """)) self.mod.write(dedent("""\ from pkg1.mod1 import (a_func, another_func) """)) pymod = self.project.get_module("mod") module_with_imports = self.import_tools.module_imports(pymod) self.assertEqual( dedent("""\ from pkg1.mod1 import (a_func, another_func) """), module_with_imports.get_changed_source(), ) def test_not_changing_the_format_of_unchanged_imports2(self): self.mod1.write(dedent("""\ def a_func(): pass def another_func(): pass """)) self.mod.write(dedent("""\ from pkg1.mod1 import (a_func) a_func() """)) pymod = self.project.get_module("mod") module_with_imports = self.import_tools.module_imports(pymod) module_with_imports.remove_unused_imports() self.assertEqual( dedent("""\ from pkg1.mod1 import (a_func) a_func() """), module_with_imports.get_changed_source(), ) def test_removing_unused_imports_and_reoccuring_names(self): self.mod1.write(dedent("""\ def a_func(): pass def another_func(): pass """)) self.mod.write(dedent("""\ from pkg1.mod1 import * from pkg1.mod1 import a_func a_func() """)) pymod = self.project.get_module("mod") module_with_imports = self.import_tools.module_imports(pymod) module_with_imports.remove_unused_imports() self.assertEqual( dedent("""\ from pkg1.mod1 import * a_func() """), module_with_imports.get_changed_source(), ) def test_removing_unused_imports_and_reoccuring_names2(self): self.mod.write(dedent("""\ import pkg2.mod2 import pkg2.mod3 print(pkg2.mod2, pkg2.mod3)""")) pymod = self.project.get_module("mod") module_with_imports = self.import_tools.module_imports(pymod) module_with_imports.remove_unused_imports() self.assertEqual( dedent("""\ import pkg2.mod2 import pkg2.mod3 print(pkg2.mod2, pkg2.mod3)"""), module_with_imports.get_changed_source(), ) def test_removing_unused_imports_and_common_packages(self): self.mod.write(dedent("""\ import pkg1.mod1 import pkg1 print(pkg1, pkg1.mod1) """)) pymod = self.project.get_module("mod") module_with_imports = self.import_tools.module_imports(pymod) module_with_imports.remove_unused_imports() self.assertEqual( dedent("""\ import pkg1.mod1 print(pkg1, pkg1.mod1) """), module_with_imports.get_changed_source(), ) def test_removing_unused_imports_and_common_packages_reversed(self): self.mod.write(dedent("""\ import pkg1 import pkg1.mod1 print(pkg1, pkg1.mod1) """)) pymod = self.project.get_module("mod") module_with_imports = self.import_tools.module_imports(pymod) module_with_imports.remove_duplicates() self.assertEqual( dedent("""\ import pkg1.mod1 print(pkg1, pkg1.mod1) """), module_with_imports.get_changed_source(), ) def test_removing_unused_imports_and_common_packages2(self): self.mod.write(dedent("""\ import pkg1.mod1 import pkg1.mod2 print(pkg1) """)) pymod = self.project.get_module("mod") module_with_imports = self.import_tools.module_imports(pymod) module_with_imports.remove_unused_imports() self.assertEqual( dedent("""\ import pkg1.mod1 print(pkg1) """), module_with_imports.get_changed_source(), ) def test_removing_unused_imports_and_froms(self): self.mod1.write(dedent("""\ def func1(): pass """)) self.mod.write(dedent("""\ from pkg1.mod1 import func1 """)) pymod = self.project.get_module("mod") module_with_imports = self.import_tools.module_imports(pymod) module_with_imports.remove_unused_imports() self.assertEqual("", module_with_imports.get_changed_source()) def test_removing_unused_imports_and_froms2(self): self.mod1.write(dedent("""\ def func1(): pass """)) self.mod.write(dedent("""\ from pkg1.mod1 import func1 func1()""")) pymod = self.project.get_module("mod") module_with_imports = self.import_tools.module_imports(pymod) module_with_imports.remove_unused_imports() self.assertEqual( dedent("""\ from pkg1.mod1 import func1 func1()"""), module_with_imports.get_changed_source(), ) def test_removing_unused_imports_and_froms3(self): self.mod1.write(dedent("""\ def func1(): pass """)) self.mod.write(dedent("""\ from pkg1.mod1 import func1 def a_func(): func1() """)) pymod = self.project.get_module("mod") module_with_imports = self.import_tools.module_imports(pymod) module_with_imports.remove_unused_imports() self.assertEqual( dedent("""\ from pkg1.mod1 import func1 def a_func(): func1() """), module_with_imports.get_changed_source(), ) def test_removing_unused_imports_and_froms4(self): self.mod1.write(dedent("""\ def func1(): pass """)) self.mod.write(dedent("""\ from pkg1.mod1 import func1 class A(object): def a_func(self): func1() """)) pymod = self.project.get_module("mod") module_with_imports = self.import_tools.module_imports(pymod) module_with_imports.remove_unused_imports() self.assertEqual( dedent("""\ from pkg1.mod1 import func1 class A(object): def a_func(self): func1() """), module_with_imports.get_changed_source(), ) def test_removing_unused_imports_and_getting_attributes(self): self.mod1.write(dedent("""\ class A(object): def f(self): pass """)) self.mod.write(dedent("""\ from pkg1.mod1 import A var = A().f()""")) pymod = self.project.get_module("mod") module_with_imports = self.import_tools.module_imports(pymod) module_with_imports.remove_unused_imports() self.assertEqual( dedent("""\ from pkg1.mod1 import A var = A().f()"""), module_with_imports.get_changed_source(), ) def test_removing_unused_imports_function_parameters(self): self.mod1.write(dedent("""\ def func1(): pass """)) self.mod.write(dedent("""\ import pkg1 def a_func(pkg1): my_var = pkg1 """)) pymod = self.project.get_module("mod") module_with_imports = self.import_tools.module_imports(pymod) module_with_imports.remove_unused_imports() self.assertEqual( dedent("""\ def a_func(pkg1): my_var = pkg1 """), module_with_imports.get_changed_source(), ) def test_trivial_expanding_star_imports(self): self.mod1.write(dedent("""\ def a_func(): pass def another_func(): pass """)) self.mod.write("from pkg1.mod1 import *\n") pymod = self.project.get_module("mod") module_with_imports = self.import_tools.module_imports(pymod) module_with_imports.expand_stars() self.assertEqual("", module_with_imports.get_changed_source()) def test_expanding_star_imports(self): self.mod1.write(dedent("""\ def a_func(): pass def another_func(): pass """)) self.mod.write("from pkg1.mod1 import *\na_func()\n") pymod = self.project.get_module("mod") module_with_imports = self.import_tools.module_imports(pymod) module_with_imports.expand_stars() self.assertEqual( "from pkg1.mod1 import a_func\na_func()\n", module_with_imports.get_changed_source(), ) def test_removing_duplicate_imports(self): self.mod.write("import pkg1\nimport pkg1\n") pymod = self.project.get_module("mod") module_with_imports = self.import_tools.module_imports(pymod) module_with_imports.remove_duplicates() self.assertEqual("import pkg1\n", module_with_imports.get_changed_source()) def test_removing_duplicates_and_reoccuring_names(self): self.mod.write("import pkg2.mod2\nimport pkg2.mod3\n") pymod = self.project.get_module("mod") module_with_imports = self.import_tools.module_imports(pymod) module_with_imports.remove_duplicates() self.assertEqual( "import pkg2.mod2\nimport pkg2.mod3\n", module_with_imports.get_changed_source(), ) def test_removing_duplicate_imports_for_froms(self): self.mod1.write(dedent("""\ def a_func(): pass def another_func(): pass """)) self.mod.write(dedent("""\ from pkg1 import a_func from pkg1 import a_func, another_func """)) pymod = self.project.get_module("mod") module_with_imports = self.import_tools.module_imports(pymod) module_with_imports.remove_duplicates() self.assertEqual( "from pkg1 import a_func, another_func\n", module_with_imports.get_changed_source(), ) def test_transforming_froms_to_normal_changing_imports(self): self.mod1.write(dedent("""\ def a_func(): pass """)) self.mod.write(dedent("""\ from pkg1.mod1 import a_func print(a_func) """)) pymod = self.project.get_module("mod") changed_module = self.import_tools.froms_to_imports(pymod) self.assertEqual( dedent("""\ import pkg1.mod1 print(pkg1.mod1.a_func) """), changed_module, ) def test_transforming_froms_to_normal_changing_occurrences(self): self.mod1.write("def a_func():\n pass\n") self.mod.write("from pkg1.mod1 import a_func\na_func()") pymod = self.project.get_module("mod") changed_module = self.import_tools.froms_to_imports(pymod) self.assertEqual("import pkg1.mod1\npkg1.mod1.a_func()", changed_module) def test_transforming_froms_to_normal_for_multi_imports(self): self.mod1.write(dedent("""\ def a_func(): pass def another_func(): pass """)) self.mod.write(dedent("""\ from pkg1.mod1 import * a_func() another_func() """)) pymod = self.project.get_module("mod") changed_module = self.import_tools.froms_to_imports(pymod) self.assertEqual( dedent("""\ import pkg1.mod1 pkg1.mod1.a_func() pkg1.mod1.another_func() """), changed_module, ) def test_transform_froms_to_norm_for_multi_imports_inside_parens(self): self.mod1.write(dedent("""\ def a_func(): pass def another_func(): pass """)) self.mod.write(dedent("""\ from pkg1.mod1 import (a_func, another_func) a_func() another_func() """)) pymod = self.project.get_module("mod") changed_module = self.import_tools.froms_to_imports(pymod) self.assertEqual( dedent("""\ import pkg1.mod1 pkg1.mod1.a_func() pkg1.mod1.another_func() """), changed_module, ) def test_transforming_froms_to_normal_from_stars(self): self.mod1.write(dedent("""\ def a_func(): pass """)) self.mod.write(dedent("""\ from pkg1.mod1 import * a_func() """)) pymod = self.project.get_module("mod") changed_module = self.import_tools.froms_to_imports(pymod) self.assertEqual( dedent("""\ import pkg1.mod1 pkg1.mod1.a_func() """), changed_module, ) def test_transforming_froms_to_normal_from_stars2(self): self.mod1.write("a_var = 10") self.mod.write(dedent("""\ import pkg1.mod1 from pkg1.mod1 import a_var def a_func(): print(pkg1.mod1, a_var) """)) pymod = self.project.get_module("mod") changed_module = self.import_tools.froms_to_imports(pymod) self.assertEqual( dedent("""\ import pkg1.mod1 def a_func(): print(pkg1.mod1, pkg1.mod1.a_var) """), changed_module, ) def test_transforming_froms_to_normal_from_with_alias(self): self.mod1.write(dedent("""\ def a_func(): pass """)) self.mod.write(dedent("""\ from pkg1.mod1 import a_func as another_func another_func() """)) pymod = self.project.get_module("mod") changed_module = self.import_tools.froms_to_imports(pymod) self.assertEqual( dedent("""\ import pkg1.mod1 pkg1.mod1.a_func() """), changed_module, ) def test_transforming_froms_to_normal_for_relatives(self): self.mod2.write(dedent("""\ def a_func(): pass """)) self.mod3.write(dedent("""\ from mod2 import * a_func() """)) pymod = self.project.get_pymodule(self.mod3) changed_module = self.import_tools.froms_to_imports(pymod) self.assertEqual( dedent("""\ import pkg2.mod2 pkg2.mod2.a_func() """), changed_module, ) def test_transforming_froms_to_normal_for_os_path(self): self.mod.write("from os import path\npath.exists('.')\n") pymod = self.project.get_pymodule(self.mod) changed_module = self.import_tools.froms_to_imports(pymod) self.assertEqual("import os\nos.path.exists('.')\n", changed_module) def test_transforming_froms_to_normal_kwarg_with_same_name(self): self.mod.write("from os import path\nfoo(path=path.join('a', 'b'))\n") pymod = self.project.get_pymodule(self.mod) changed_module = self.import_tools.froms_to_imports(pymod) self.assertEqual("import os\nfoo(path=os.path.join('a', 'b'))\n", changed_module) def test_transform_relatives_imports_to_abs_imports_doing_nothing(self): self.mod2.write("from pkg1 import mod1\nimport mod1\n") pymod = self.project.get_pymodule(self.mod2) self.assertEqual( "from pkg1 import mod1\nimport mod1\n", self.import_tools.relatives_to_absolutes(pymod), ) def test_transform_relatives_to_absolute_imports_for_normal_imports(self): self.mod2.write("import mod3\n") pymod = self.project.get_pymodule(self.mod2) self.assertEqual( "import pkg2.mod3\n", self.import_tools.relatives_to_absolutes(pymod) ) def test_transform_relatives_imports_to_absolute_imports_for_froms(self): self.mod3.write(dedent("""\ def a_func(): pass """)) self.mod2.write("from mod3 import a_func\n") pymod = self.project.get_pymodule(self.mod2) self.assertEqual( "from pkg2.mod3 import a_func\n", self.import_tools.relatives_to_absolutes(pymod), ) def test_transform_rel_imports_to_abs_imports_for_new_relatives(self): self.mod3.write(dedent("""\ def a_func(): pass """)) self.mod2.write("from .mod3 import a_func\n") pymod = self.project.get_pymodule(self.mod2) self.assertEqual( "from pkg2.mod3 import a_func\n", self.import_tools.relatives_to_absolutes(pymod), ) def test_transform_relatives_to_absolute_imports_for_normal_imports2(self): self.mod2.write("import mod3\nprint(mod3)") pymod = self.project.get_pymodule(self.mod2) self.assertEqual( "import pkg2.mod3\nprint(pkg2.mod3)", self.import_tools.relatives_to_absolutes(pymod), ) def test_transform_relatives_to_absolute_imports_for_aliases(self): self.mod2.write("import mod3 as mod3\nprint(mod3)") pymod = self.project.get_pymodule(self.mod2) self.assertEqual( "import pkg2.mod3 as mod3\nprint(mod3)", self.import_tools.relatives_to_absolutes(pymod), ) def test_organizing_imports(self): self.mod1.write("import mod1\n") pymod = self.project.get_pymodule(self.mod1) self.assertEqual("", self.import_tools.organize_imports(pymod)) def test_organizing_imports_without_deduplication(self): contents = dedent("""\ from pkg2 import mod2 from pkg2 import mod3 """) self.mod.write(contents) pymod = self.project.get_pymodule(self.mod) self.project.prefs["split_imports"] = True self.assertEqual( contents, self.import_tools.organize_imports(pymod, unused=False) ) def test_splitting_imports(self): self.mod.write(dedent("""\ from pkg1 import mod1 from pkg2 import mod2, mod3 """)) pymod = self.project.get_pymodule(self.mod) self.project.prefs["split_imports"] = True self.assertEqual( dedent("""\ from pkg1 import mod1 from pkg2 import mod2 from pkg2 import mod3 """), self.import_tools.organize_imports(pymod, unused=False), ) def test_splitting_imports_no_pull_to_top(self): self.mod.write(dedent("""\ from pkg2 import mod3, mod4 from pkg1 import mod2 from pkg1 import mod1 """)) pymod = self.project.get_pymodule(self.mod) self.project.prefs["split_imports"] = True self.project.prefs["pull_imports_to_top"] = False self.assertEqual( dedent("""\ from pkg1 import mod2 from pkg1 import mod1 from pkg2 import mod3 from pkg2 import mod4 """), self.import_tools.organize_imports(pymod, sort=False, unused=False), ) def test_splitting_imports_with_filter(self): self.mod.write(dedent("""\ from pkg1 import mod1, mod2 from pkg2 import mod3, mod4 """)) pymod = self.project.get_pymodule(self.mod) self.project.prefs["split_imports"] = True def import_filter(stmt): return stmt.import_info.module_name == "pkg1" self.assertEqual( dedent("""\ from pkg1 import mod1 from pkg1 import mod2 from pkg2 import mod3, mod4 """), self.import_tools.organize_imports( pymod, unused=False, import_filter=import_filter ), ) def test_splitting_duplicate_imports(self): self.mod.write(dedent("""\ from pkg2 import mod1 from pkg2 import mod1, mod2 """)) pymod = self.project.get_pymodule(self.mod) self.project.prefs["split_imports"] = True self.assertEqual( dedent("""\ from pkg2 import mod1 from pkg2 import mod2 """), self.import_tools.organize_imports(pymod, unused=False), ) def test_splitting_duplicate_imports2(self): self.mod.write(dedent("""\ from pkg2 import mod1, mod3 from pkg2 import mod1, mod2 from pkg2 import mod2, mod3 """)) pymod = self.project.get_pymodule(self.mod) self.project.prefs["split_imports"] = True self.assertEqual( dedent("""\ from pkg2 import mod1 from pkg2 import mod2 from pkg2 import mod3 """), self.import_tools.organize_imports(pymod, unused=False), ) def test_removing_self_imports(self): self.mod.write(dedent("""\ import mod mod.a_var = 1 print(mod.a_var) """)) pymod = self.project.get_pymodule(self.mod) self.assertEqual( dedent("""\ a_var = 1 print(a_var) """), self.import_tools.organize_imports(pymod), ) def test_removing_self_imports2(self): self.mod1.write(dedent("""\ import pkg1.mod1 pkg1.mod1.a_var = 1 print(pkg1.mod1.a_var) """)) pymod = self.project.get_pymodule(self.mod1) self.assertEqual( dedent("""\ a_var = 1 print(a_var) """), self.import_tools.organize_imports(pymod), ) def test_removing_self_imports_with_as(self): self.mod.write(dedent("""\ import mod as mymod mymod.a_var = 1 print(mymod.a_var) """)) pymod = self.project.get_pymodule(self.mod) self.assertEqual( dedent("""\ a_var = 1 print(a_var) """), self.import_tools.organize_imports(pymod), ) def test_removing_self_imports_for_froms(self): self.mod1.write(dedent("""\ from pkg1 import mod1 mod1.a_var = 1 print(mod1.a_var) """)) pymod = self.project.get_pymodule(self.mod1) self.assertEqual( dedent("""\ a_var = 1 print(a_var) """), self.import_tools.organize_imports(pymod), ) def test_removing_self_imports_for_froms_with_as(self): self.mod1.write(dedent("""\ from pkg1 import mod1 as mymod mymod.a_var = 1 print(mymod.a_var) """)) pymod = self.project.get_pymodule(self.mod1) self.assertEqual( dedent("""\ a_var = 1 print(a_var) """), self.import_tools.organize_imports(pymod), ) def test_removing_self_imports_for_froms2(self): self.mod.write(dedent("""\ from mod import a_var a_var = 1 print(a_var) """)) pymod = self.project.get_pymodule(self.mod) self.assertEqual( dedent("""\ a_var = 1 print(a_var) """), self.import_tools.organize_imports(pymod), ) def test_removing_self_imports_for_froms3(self): self.mod.write(dedent("""\ from mod import a_var a_var = 1 print(a_var) """)) pymod = self.project.get_pymodule(self.mod) self.assertEqual( dedent("""\ a_var = 1 print(a_var) """), self.import_tools.organize_imports(pymod), ) def test_removing_self_imports_for_froms4(self): self.mod.write(dedent("""\ from mod import a_var as myvar a_var = 1 print(myvar) """)) pymod = self.project.get_pymodule(self.mod) self.assertEqual( dedent("""\ a_var = 1 print(a_var) """), self.import_tools.organize_imports(pymod), ) def test_removing_self_imports_with_no_dot_after_mod(self): self.mod.write(dedent("""\ import mod print(mod) """)) pymod = self.project.get_pymodule(self.mod) self.assertEqual( dedent("""\ import mod print(mod) """), self.import_tools.organize_imports(pymod), ) def test_removing_self_imports_with_no_dot_after_mod2(self): self.mod.write(dedent("""\ import mod a_var = 1 print(mod\\ \\ .var) """)) pymod = self.project.get_pymodule(self.mod) self.assertEqual( dedent("""\ a_var = 1 print(var) """), self.import_tools.organize_imports(pymod), ) def test_removing_self_imports_for_from_import_star(self): self.mod.write(dedent("""\ from mod import * a_var = 1 print(myvar) """)) pymod = self.project.get_pymodule(self.mod) self.assertEqual( dedent("""\ a_var = 1 print(myvar) """), self.import_tools.organize_imports(pymod), ) def test_not_removing_future_imports(self): self.mod.write("from __future__ import division\n") pymod = self.project.get_pymodule(self.mod) self.assertEqual( "from __future__ import division\n", self.import_tools.organize_imports(pymod), ) def test_sorting_empty_imports(self): self.mod.write("") pymod = self.project.get_pymodule(self.mod) self.assertEqual("", self.import_tools.sort_imports(pymod)) def test_sorting_one_import(self): self.mod.write("import pkg1.mod1\n") pymod = self.project.get_pymodule(self.mod) self.assertEqual("import pkg1.mod1\n", self.import_tools.sort_imports(pymod)) def test_sorting_imports_alphabetically(self): self.mod.write("import pkg2.mod2\nimport pkg1.mod1\n") pymod = self.project.get_pymodule(self.mod) self.assertEqual( "import pkg1.mod1\nimport pkg2.mod2\n", self.import_tools.sort_imports(pymod), ) def test_sorting_imports_purely_alphabetically(self): self.mod.write(dedent("""\ from pkg2 import mod3 as mod0 import pkg2.mod2 import pkg1.mod1 """)) pymod = self.project.get_pymodule(self.mod) self.project.prefs["sort_imports_alphabetically"] = True self.assertEqual( dedent("""\ import pkg1.mod1 import pkg2.mod2 from pkg2 import mod3 as mod0 """), self.import_tools.sort_imports(pymod), ) def test_sorting_imports_and_froms(self): self.mod.write("import pkg2.mod2\nfrom pkg1 import mod1\n") pymod = self.project.get_pymodule(self.mod) self.assertEqual( "import pkg2.mod2\nfrom pkg1 import mod1\n", self.import_tools.sort_imports(pymod), ) def test_sorting_imports_and_standard_modules(self): self.mod.write(dedent("""\ import pkg1 import sys """)) pymod = self.project.get_pymodule(self.mod) self.assertEqual( dedent("""\ import sys import pkg1 """), self.import_tools.sort_imports(pymod), ) def test_sorting_imports_and_standard_modules2(self): self.mod.write(dedent("""\ import sys import time """)) pymod = self.project.get_pymodule(self.mod) self.assertEqual( dedent("""\ import sys import time """), self.import_tools.sort_imports(pymod), ) def test_sorting_only_standard_modules(self): self.mod.write("import sys\n") pymod = self.project.get_pymodule(self.mod) self.assertEqual("import sys\n", self.import_tools.sort_imports(pymod)) def test_sorting_third_party(self): self.mod.write(dedent("""\ import pkg1 import a_third_party """)) pymod = self.project.get_pymodule(self.mod) self.assertEqual( dedent("""\ import a_third_party import pkg1 """), self.import_tools.sort_imports(pymod), ) def test_sorting_only_third_parties(self): self.mod.write(dedent("""\ import a_third_party a_var = 1 """)) pymod = self.project.get_pymodule(self.mod) self.assertEqual( dedent("""\ import a_third_party a_var = 1 """), self.import_tools.sort_imports(pymod), ) def test_simple_handling_long_imports(self): self.mod.write(dedent("""\ import pkg1.mod1 m = pkg1.mod1 """)) pymod = self.project.get_pymodule(self.mod) self.assertEqual( dedent("""\ import pkg1.mod1 m = pkg1.mod1 """), self.import_tools.handle_long_imports(pymod, maxdots=2), ) def test_handling_long_imports_for_many_dots(self): self.mod.write(dedent("""\ import p1.p2.p3.m1 m = p1.p2.p3.m1 """)) pymod = self.project.get_pymodule(self.mod) self.assertEqual( dedent("""\ from p1.p2.p3 import m1 m = m1 """), self.import_tools.handle_long_imports(pymod, maxdots=2), ) def test_handling_long_imports_for_their_length(self): self.mod.write(dedent("""\ import p1.p2.p3.m1 m = p1.p2.p3.m1 """)) pymod = self.project.get_pymodule(self.mod) self.assertEqual( dedent("""\ import p1.p2.p3.m1 m = p1.p2.p3.m1 """), self.import_tools.handle_long_imports(pymod, maxdots=3, maxlength=20), ) def test_handling_long_imports_for_many_dots2(self): self.mod.write(dedent("""\ import p1.p2.p3.m1 m = p1.p2.p3.m1 """)) pymod = self.project.get_pymodule(self.mod) self.assertEqual( dedent("""\ from p1.p2.p3 import m1 m = m1 """), self.import_tools.handle_long_imports(pymod, maxdots=3, maxlength=10), ) def test_handling_long_imports_with_one_letter_last(self): self.mod.write(dedent("""\ import p1.p2.p3.l m = p1.p2.p3.l """)) pymod = self.project.get_pymodule(self.mod) self.assertEqual( dedent("""\ from p1.p2.p3 import l m = l """), self.import_tools.handle_long_imports(pymod, maxdots=2), ) def test_empty_removing_unused_imports_and_eating_blank_lines(self): self.mod.write(dedent("""\ import pkg1 import pkg2 print(pkg1) """)) pymod = self.project.get_module("mod") module_with_imports = self.import_tools.module_imports(pymod) module_with_imports.remove_unused_imports() self.assertEqual( dedent("""\ import pkg1 print(pkg1) """), module_with_imports.get_changed_source(), ) def test_sorting_imports_moving_to_top(self): self.mod.write(dedent("""\ import mod def f(): print(mod, pkg1, pkg2) import pkg1 import pkg2 """)) pymod = self.project.get_module("mod") self.assertEqual( dedent("""\ import mod import pkg1 import pkg2 def f(): print(mod, pkg1, pkg2) """), self.import_tools.sort_imports(pymod), ) def test_sorting_imports_moving_to_top2(self): self.mod.write(dedent("""\ def f(): print(mod) import mod """)) pymod = self.project.get_module("mod") self.assertEqual( dedent("""\ import mod def f(): print(mod) """), self.import_tools.sort_imports(pymod), ) # Sort pulls imports to the top anyway def test_sorting_imports_no_pull_to_top(self): code = dedent("""\ import pkg2 def f(): print(mod, pkg1, pkg2) import pkg1 import mod """) self.mod.write(code) pymod = self.project.get_module("mod") self.project.prefs["pull_imports_to_top"] = False self.assertEqual( dedent("""\ import mod import pkg1 import pkg2 def f(): print(mod, pkg1, pkg2) """), self.import_tools.sort_imports(pymod), ) def test_sorting_imports_moving_to_top_and_module_docs(self): self.mod.write(dedent('''\ """ docs """ def f(): print(mod) import mod ''')) pymod = self.project.get_module("mod") self.assertEqual( dedent('''\ """ docs """ import mod def f(): print(mod) '''), self.import_tools.sort_imports(pymod), ) def test_sorting_imports_moving_to_top_and_module_docs2(self): self.mod.write(dedent('''\ """ docs """ import bbb import aaa def f(): print(mod) import mod ''')) pymod = self.project.get_module("mod") self.assertEqual( dedent('''\ """ docs """ import aaa import bbb import mod def f(): print(mod) '''), self.import_tools.sort_imports(pymod), ) def test_get_changed_source_preserves_blank_lines(self): self.mod.write(dedent("""\ __author__ = "author" import aaa import bbb def f(): print(mod) """)) pymod = self.project.get_module("mod") module_with_imports = self.import_tools.module_imports(pymod) self.assertEqual( dedent("""\ import aaa import bbb __author__ = "author" def f(): print(mod) """), module_with_imports.get_changed_source(), ) def test_sorting_future_imports(self): self.mod.write(dedent("""\ import os from __future__ import division """)) pymod = self.project.get_module("mod") self.assertEqual( dedent("""\ from __future__ import division import os """), self.import_tools.sort_imports(pymod), ) def test_organizing_imports_all_star(self): code = expected = dedent("""\ from package import some_name __all__ = ["some_name"] """) self.mod.write(code) pymod = self.project.get_pymodule(self.mod) self.assertEqual(expected, self.import_tools.organize_imports(pymod)) def test_organizing_imports_all_star_with_variables(self): code = expected = dedent("""\ from package import name_one, name_two if something(): foo = 'name_one' else: foo = 'name_two' __all__ = [foo] """) self.mod.write(code) pymod = self.project.get_pymodule(self.mod) self.assertEqual(expected, self.import_tools.organize_imports(pymod)) def test_organizing_imports_all_star_with_inline_if(self): code = expected = dedent("""\ from package import name_one, name_two __all__ = ['name_one' if something() else 'name_two'] """) self.mod.write(code) pymod = self.project.get_pymodule(self.mod) self.assertEqual(expected, self.import_tools.organize_imports(pymod)) @testutils.only_for_versions_higher("3") def test_organizing_imports_all_star_tolerates_non_list_of_str_1(self): code = expected = dedent("""\ from package import name_one, name_two foo = 'name_two' __all__ = [bar, *abc] + mylist __all__ = [foo, 'name_one', *abc] __all__ = [it for it in mylist] """) self.mod.write(code) pymod = self.project.get_pymodule(self.mod) self.assertEqual(expected, self.import_tools.organize_imports(pymod)) def test_organizing_imports_all_star_assigned_name_alias(self): code = expected = dedent("""\ from package import name_one, name_two foo = ['name_one', 'name_two'] __all__ = foo """) self.mod.write(code) pymod = self.project.get_pymodule(self.mod) self.assertEqual(expected, self.import_tools.organize_imports(pymod)) def test_organizing_imports_all_star_imported_name_alias(self): self.mod1.write("foo = ['name_one', 'name_two']") self.mod2.write("from pkg1.mod1 import foo") code = expected = dedent("""\ from package import name_one, name_two from pkg2.mod2 import foo __all__ = foo """) self.mod3.write(code) pymod = self.project.get_pymodule(self.mod3) self.assertEqual(expected, self.import_tools.organize_imports(pymod)) def test_organizing_imports_all_star_tolerates_non_list_of_str_2(self): code = expected = dedent("""\ from package import name_one, name_two foo = 'name_two' __all__ = [foo, 3, 'name_one'] __all__ = [it for it in mylist] """) self.mod.write(code) pymod = self.project.get_pymodule(self.mod) self.assertEqual(expected, self.import_tools.organize_imports(pymod)) def test_organizing_imports_all_star_plusjoin(self): code = expected = dedent("""\ from package import name_one, name_two foo = ['name_two'] __all__ = ['name_one'] + foo """) self.mod.write(code) pymod = self.project.get_pymodule(self.mod) self.assertEqual(expected, self.import_tools.organize_imports(pymod)) def test_organizing_imports_all_star_starjoin(self): code = expected = dedent("""\ from package import name_one, name_two foo = ['name_two'] __all__ = ['name_one', *foo] """) self.mod.write(code) pymod = self.project.get_pymodule(self.mod) self.assertEqual(expected, self.import_tools.organize_imports(pymod)) @testutils.time_limit(60) def test_organizing_imports_all_star_no_infinite_loop(self): code = expected = dedent("""\ from package import name_one, name_two foo = bar bar = foo __all__ = [foo, 'name_one', 'name_two'] """) self.mod.write(code) pymod = self.project.get_pymodule(self.mod) self.assertEqual(expected, self.import_tools.organize_imports(pymod)) def test_organizing_imports_all_star_resolve_imported_name(self): self.mod1.write("foo = 'name_one'") code = expected = dedent("""\ from package import name_one, name_two from pkg1.mod1 import foo __all__ = [foo, 'name_two'] """) self.mod.write(code) pymod = self.project.get_pymodule(self.mod) self.assertEqual(expected, self.import_tools.organize_imports(pymod)) def test_organizing_imports_undefined_variable(self): code = expected = dedent("""\ from foo import some_name __all__ = ['some_name', undefined_variable] """) self.mod1.write(code) pymod = self.project.get_pymodule(self.mod1) self.assertEqual(expected, self.import_tools.organize_imports(pymod)) def test_organizing_imports_undefined_variable_with_imported_name(self): self.mod1.write("") self.mod2.write("from pkg1.mod1 import undefined_variable") code = expected = dedent("""\ from pkg2.mod2 import undefined_variable __all__ = undefined_variable """) self.mod3.write(code) pymod = self.project.get_pymodule(self.mod3) self.assertEqual(expected, self.import_tools.organize_imports(pymod)) def test_organizing_indirect_all_star_import(self): self.mod1.write("some_name = 1") self.mod2.write(dedent("""\ __all__ = ['some_name', *imported_all] """)) code = expected = dedent("""\ from mod1 import some_name from mod2 import __all__ """) self.mod3.write(code) pymod = self.project.get_pymodule(self.mod3) self.assertEqual(expected, self.import_tools.organize_imports(pymod)) def test_customized_import_organization(self): self.mod.write(dedent("""\ import sys import sys """)) pymod = self.project.get_pymodule(self.mod) self.assertEqual( "import sys\n", self.import_tools.organize_imports(pymod, unused=False) ) def test_customized_import_organization2(self): self.mod.write("import sys\n") pymod = self.project.get_pymodule(self.mod) self.assertEqual( "import sys\n", self.import_tools.organize_imports(pymod, unused=False) ) def test_customized_import_organization3(self): self.mod.write(dedent("""\ import sys import mod var = 1 print(mod.var) """)) pymod = self.project.get_pymodule(self.mod) self.assertEqual( dedent("""\ import sys var = 1 print(var) """), self.import_tools.organize_imports(pymod, unused=False), ) def test_trivial_filtered_expand_stars(self): self.pkg1.get_child("__init__.py").write("var1 = 1\n") self.pkg2.get_child("__init__.py").write("var2 = 1\n") self.mod.write(dedent("""\ from pkg1 import * from pkg2 import * print(var1, var2) """)) pymod = self.project.get_pymodule(self.mod) self.assertEqual( dedent("""\ from pkg1 import * from pkg2 import * print(var1, var2) """), self.import_tools.expand_stars(pymod, lambda stmt: False), ) def _line_filter(self, lineno): def import_filter(import_stmt): return import_stmt.start_line <= lineno < import_stmt.end_line return import_filter def test_filtered_expand_stars(self): self.pkg1.get_child("__init__.py").write("var1 = 1\n") self.pkg2.get_child("__init__.py").write("var2 = 1\n") self.mod.write(dedent("""\ from pkg1 import * from pkg2 import * print(var1, var2) """)) pymod = self.project.get_pymodule(self.mod) self.assertEqual( dedent("""\ from pkg1 import * from pkg2 import var2 print(var1, var2) """), self.import_tools.expand_stars(pymod, self._line_filter(2)), ) def test_filtered_relative_to_absolute(self): self.mod3.write("var = 1") self.mod2.write("import mod3\n\nprint(mod3.var)\n") pymod = self.project.get_pymodule(self.mod2) self.assertEqual( dedent("""\ import mod3 print(mod3.var) """), self.import_tools.relatives_to_absolutes(pymod, lambda stmt: False), ) self.assertEqual( dedent("""\ import pkg2.mod3 print(pkg2.mod3.var) """), self.import_tools.relatives_to_absolutes(pymod, self._line_filter(1)), ) def test_filtered_froms_to_normals(self): self.pkg1.get_child("__init__.py").write("var1 = 1\n") self.pkg2.get_child("__init__.py").write("var2 = 1\n") self.mod.write(dedent("""\ from pkg1 import var1 from pkg2 import var2 print(var1, var2) """)) pymod = self.project.get_pymodule(self.mod) self.assertEqual( dedent("""\ from pkg1 import var1 from pkg2 import var2 print(var1, var2) """), self.import_tools.expand_stars(pymod, lambda stmt: False), ) self.assertEqual( dedent("""\ from pkg1 import var1 import pkg2 print(var1, pkg2.var2) """), self.import_tools.froms_to_imports(pymod, self._line_filter(2)), ) def test_filtered_froms_to_normals2(self): self.pkg1.get_child("__init__.py").write("var1 = 1\n") self.pkg2.get_child("__init__.py").write("var2 = 1\n") self.mod.write(dedent("""\ from pkg1 import * from pkg2 import * print(var1, var2) """)) pymod = self.project.get_pymodule(self.mod) self.assertEqual( dedent("""\ from pkg1 import * import pkg2 print(var1, pkg2.var2) """), self.import_tools.froms_to_imports(pymod, self._line_filter(2)), ) def test_filtered_handle_long_imports(self): self.mod.write(dedent("""\ import p1.p2.p3.m1 import pkg1.mod1 m = p1.p2.p3.m1, pkg1.mod1 """)) pymod = self.project.get_pymodule(self.mod) self.assertEqual( dedent("""\ import p1.p2.p3.m1 from pkg1 import mod1 m = p1.p2.p3.m1, mod1 """), self.import_tools.handle_long_imports( pymod, maxlength=5, import_filter=self._line_filter(2) ), ) def test_filtering_and_import_actions_with_more_than_one_phase(self): self.pkg1.get_child("__init__.py").write("var1 = 1\n") self.pkg2.get_child("__init__.py").write("var2 = 1\n") self.mod.write(dedent("""\ from pkg1 import * from pkg2 import * print(var2) """)) pymod = self.project.get_pymodule(self.mod) self.assertEqual( dedent("""\ from pkg2 import * print(var2) """), self.import_tools.expand_stars(pymod, self._line_filter(1)), ) def test_non_existent_module_and_used_imports(self): self.mod.write(dedent("""\ from does_not_exist import func func() """)) pymod = self.project.get_module("mod") module_with_imports = self.import_tools.module_imports(pymod) imports = module_with_imports.get_used_imports(pymod) self.assertEqual(1, len(imports)) class AddImportTest(unittest.TestCase): def setUp(self): super().setUp() self.project = testutils.sample_project() self.mod1 = testutils.create_module(self.project, "mod1") self.mod2 = testutils.create_module(self.project, "mod2") self.pkg = testutils.create_package(self.project, "pkg") self.mod3 = testutils.create_module(self.project, "mod3", self.pkg) def tearDown(self): testutils.remove_project(self.project) super().tearDown() def test_normal_imports(self): self.mod2.write("myvar = None\n") self.mod1.write("\n") pymod = self.project.get_module("mod1") result, name = add_import(self.project, pymod, "mod2", "myvar") self.assertEqual("import mod2\n", result) self.assertEqual("mod2.myvar", name) def test_not_reimporting_a_name(self): self.mod2.write("myvar = None\n") self.mod1.write("from mod2 import myvar\n") pymod = self.project.get_module("mod1") result, name = add_import(self.project, pymod, "mod2", "myvar") self.assertEqual("from mod2 import myvar\n", result) self.assertEqual("myvar", name) def test_adding_import_when_siblings_are_imported(self): self.mod2.write("var1 = None\nvar2 = None\n") self.mod1.write("from mod2 import var1\n") pymod = self.project.get_module("mod1") result, name = add_import(self.project, pymod, "mod2", "var2") self.assertEqual("from mod2 import var1, var2\n", result) self.assertEqual("var2", name) def test_adding_import_when_the_package_is_imported(self): self.pkg.get_child("__init__.py").write("var1 = None\n") self.mod3.write("var2 = None\n") self.mod1.write("from pkg import var1\n") pymod = self.project.get_module("mod1") result, name = add_import(self.project, pymod, "pkg.mod3", "var2") self.assertEqual("from pkg import var1, mod3\n", result) self.assertEqual("mod3.var2", name) def test_adding_import_for_modules_instead_of_names(self): self.pkg.get_child("__init__.py").write("var1 = None\n") self.mod3.write("\n") self.mod1.write("from pkg import var1\n") pymod = self.project.get_module("mod1") result, name = add_import(self.project, pymod, "pkg.mod3", None) self.assertEqual("from pkg import var1, mod3\n", result) self.assertEqual("mod3", name) def test_adding_import_for_modules_with_normal_duplicate_imports(self): self.pkg.get_child("__init__.py").write("var1 = None\n") self.mod3.write("\n") self.mod1.write("import pkg.mod3\n") pymod = self.project.get_module("mod1") result, name = add_import(self.project, pymod, "pkg.mod3", None) self.assertEqual("import pkg.mod3\n", result) self.assertEqual("pkg.mod3", name) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/ropetest/refactor/inlinetest.py0000664000175000017500000012346314512700666020714 0ustar00lieryanlieryanimport unittest from textwrap import dedent import rope.base.exceptions from rope.refactor import inline from ropetest import testutils class InlineTest(unittest.TestCase): def setUp(self): super().setUp() self.project = testutils.sample_project() self.pycore = self.project.pycore self.mod = testutils.create_module(self.project, "mod") self.mod2 = testutils.create_module(self.project, "mod2") def tearDown(self): testutils.remove_project(self.project) super().tearDown() def _inline(self, code, offset, **kwds): self.mod.write(code) self._inline2(self.mod, offset, **kwds) return self.mod.read() def _inline2(self, resource, offset, **kwds): inliner = inline.create_inline(self.project, resource, offset) changes = inliner.get_changes(**kwds) self.project.do(changes) return self.mod.read() def test_simple_case(self): code = dedent("""\ a_var = 10 another_var = a_var """) refactored = self._inline(code, code.index("a_var") + 1) self.assertEqual("another_var = 10\n", refactored) def test_empty_case(self): code = "a_var = 10\n" refactored = self._inline(code, code.index("a_var") + 1) self.assertEqual("", refactored) def test_long_definition(self): code = dedent("""\ a_var = 10 + (10 + 10) another_var = a_var """) refactored = self._inline(code, code.index("a_var") + 1) self.assertEqual("another_var = 10 + (10 + 10)\n", refactored) def test_explicit_continuation(self): code = dedent("""\ a_var = (10 + 10) another_var = a_var """) refactored = self._inline(code, code.index("a_var") + 1) self.assertEqual( dedent("""\ another_var = (10 + 10) """), refactored, ) def test_implicit_continuation(self): code = dedent("""\ a_var = 10 +\\ 10 another_var = a_var """) refactored = self._inline(code, code.index("a_var") + 1) self.assertEqual( dedent("""\ another_var = 10 +\\ 10 """), refactored, ) def test_inlining_at_the_end_of_input(self): code = dedent("""\ a = 1 b = a""") refactored = self._inline(code, code.index("a") + 1) self.assertEqual("b = 1", refactored) def test_on_classes(self): code = dedent("""\ class AClass(object): pass """) with self.assertRaises(rope.base.exceptions.RefactoringError): self._inline(code, code.index("AClass") + 1) def test_multiple_assignments(self): code = dedent("""\ a_var = 10 a_var = 20 """) with self.assertRaises(rope.base.exceptions.RefactoringError): self._inline(code, code.index("a_var") + 1) def test_tuple_assignments(self): code = "a_var, another_var = (20, 30)\n" with self.assertRaises(rope.base.exceptions.RefactoringError): self._inline(code, code.index("a_var") + 1) def test_on_unknown_vars(self): code = "a_var = another_var\n" with self.assertRaises(rope.base.exceptions.RefactoringError): self._inline(code, code.index("another_var") + 1) def test_attribute_inlining(self): code = dedent("""\ class A(object): def __init__(self): self.an_attr = 3 range(self.an_attr) """) refactored = self._inline(code, code.index("an_attr") + 1) expected = dedent("""\ class A(object): def __init__(self): range(3) """) self.assertEqual(expected, refactored) def test_attribute_inlining2(self): code = dedent("""\ class A(object): def __init__(self): self.an_attr = 3 range(self.an_attr) a = A() range(a.an_attr)""") refactored = self._inline(code, code.index("an_attr") + 1) expected = dedent("""\ class A(object): def __init__(self): range(3) a = A() range(3)""") self.assertEqual(expected, refactored) def test_a_function_with_no_occurrence(self): self.mod.write(dedent("""\ def a_func(): pass """)) self._inline2(self.mod, self.mod.read().index("a_func") + 1) self.assertEqual("", self.mod.read()) def test_a_function_with_no_occurrence2(self): self.mod.write(dedent("""\ a_var = 10 def a_func(): pass print(a_var) """)) self._inline2(self.mod, self.mod.read().index("a_func") + 1) self.assertEqual( dedent("""\ a_var = 10 print(a_var) """), self.mod.read(), ) def test_replacing_calls_with_function_definition_in_other_modules(self): self.mod.write(dedent("""\ def a_func(): print(1) """)) mod1 = testutils.create_module(self.project, "mod1") mod1.write(dedent("""\ import mod mod.a_func() """)) self._inline2(self.mod, self.mod.read().index("a_func") + 1) self.assertEqual( dedent("""\ import mod print(1) """), mod1.read(), ) def test_replacing_calls_with_function_definition_in_other_modules2(self): self.mod.write(dedent("""\ def a_func(): print(1) """)) mod1 = testutils.create_module(self.project, "mod1") mod1.write(dedent("""\ import mod if True: mod.a_func() """)) self._inline2(self.mod, self.mod.read().index("a_func") + 1) self.assertEqual( dedent("""\ import mod if True: print(1) """), mod1.read(), ) def test_replacing_calls_with_method_definition_in_other_modules(self): self.mod.write(dedent("""\ class A(object): var = 10 def a_func(self): print(1) """)) mod1 = testutils.create_module(self.project, "mod1") mod1.write(dedent("""\ import mod mod.A().a_func() """)) self._inline2(self.mod, self.mod.read().index("a_func") + 1) self.assertEqual( dedent("""\ import mod print(1) """), mod1.read(), ) self.assertEqual( dedent("""\ class A(object): var = 10 """), self.mod.read(), ) def test_replacing_calls_with_function_definition_in_defining_module(self): self.mod.write(dedent("""\ def a_func(): print(1) a_func() """)) self._inline2(self.mod, self.mod.read().index("a_func") + 1) self.assertEqual("print(1)\n", self.mod.read()) def test_replac_calls_with_function_definition_in_defining_module2(self): self.mod.write(dedent("""\ def a_func(): for i in range(10): print(1) a_func() """)) self._inline2(self.mod, self.mod.read().index("a_func") + 1) self.assertEqual( dedent("""\ for i in range(10): print(1) """), self.mod.read(), ) def test_replacing_calls_with_method_definition_in_defining_modules(self): self.mod.write(dedent("""\ class A(object): var = 10 def a_func(self): print(1) A().a_func()""")) self._inline2(self.mod, self.mod.read().index("a_func") + 1) self.assertEqual( dedent("""\ class A(object): var = 10 print(1) """), self.mod.read(), ) def test_parameters_with_the_same_name_as_passed(self): self.mod.write(dedent("""\ def a_func(var): print(var) var = 1 a_func(var) """)) self._inline2(self.mod, self.mod.read().index("a_func") + 1) self.assertEqual( dedent("""\ var = 1 print(var) """), self.mod.read(), ) def test_parameters_with_the_same_name_as_passed2(self): self.mod.write(dedent("""\ def a_func(var): print(var) var = 1 a_func(var=var) """)) self._inline2(self.mod, self.mod.read().index("a_func") + 1) self.assertEqual( dedent("""\ var = 1 print(var) """), self.mod.read(), ) def test_simple_parameters_renaming(self): self.mod.write(dedent("""\ def a_func(param): print(param) var = 1 a_func(var) """)) self._inline2(self.mod, self.mod.read().index("a_func") + 1) self.assertEqual( dedent("""\ var = 1 print(var) """), self.mod.read(), ) def test_simple_parameters_renaming_for_multiple_params(self): self.mod.write(dedent("""\ def a_func(param1, param2): p = param1 + param2 var1 = 1 var2 = 1 a_func(var1, var2) """)) self._inline2(self.mod, self.mod.read().index("a_func") + 1) self.assertEqual( dedent("""\ var1 = 1 var2 = 1 p = var1 + var2 """), self.mod.read(), ) def test_parameters_renaming_for_passed_constants(self): self.mod.write(dedent("""\ def a_func(param): print(param) a_func(1) """)) self._inline2(self.mod, self.mod.read().index("a_func") + 1) self.assertEqual("print(1)\n", self.mod.read()) def test_parameters_renaming_for_passed_statements(self): self.mod.write(dedent("""\ def a_func(param): print(param) a_func((1 + 2) / 3) """)) self._inline2(self.mod, self.mod.read().index("a_func") + 1) self.assertEqual( dedent("""\ print((1 + 2) / 3) """), self.mod.read(), ) def test_simple_parameters_renam_for_multiple_params_using_keywords(self): self.mod.write(dedent("""\ def a_func(param1, param2): p = param1 + param2 var1 = 1 var2 = 1 a_func(param2=var1, param1=var2) """)) self._inline2(self.mod, self.mod.read().index("a_func") + 1) self.assertEqual( dedent("""\ var1 = 1 var2 = 1 p = var2 + var1 """), self.mod.read(), ) def test_simple_params_renam_for_multi_params_using_mixed_keywords(self): self.mod.write(dedent("""\ def a_func(param1, param2): p = param1 + param2 var1 = 1 var2 = 1 a_func(var2, param2=var1) """)) self._inline2(self.mod, self.mod.read().index("a_func") + 1) self.assertEqual( dedent("""\ var1 = 1 var2 = 1 p = var2 + var1 """), self.mod.read(), ) def test_simple_putting_in_default_arguments(self): self.mod.write(dedent("""\ def a_func(param=None): print(param) a_func() """)) self._inline2(self.mod, self.mod.read().index("a_func") + 1) self.assertEqual("print(None)\n", self.mod.read()) def test_overriding_default_arguments(self): self.mod.write(dedent("""\ def a_func(param1=1, param2=2): print(param1, param2) a_func(param2=3) """)) self._inline2(self.mod, self.mod.read().index("a_func") + 1) self.assertEqual("print(1, 3)\n", self.mod.read()) def test_arguments_containing_comparisons(self): self.mod.write(dedent("""\ def a_func(param1, param2, param3): param2.name a_func(2 <= 1, item, True) """)) self._inline2(self.mod, self.mod.read().index("a_func") + 1) self.assertEqual("item.name\n", self.mod.read()) def test_badly_formatted_text(self): self.mod.write(dedent("""\ def a_func ( param1 = 1 ,param2 = 2 ) : print(param1, param2) a_func ( param2 = 3 ) """)) self._inline2(self.mod, self.mod.read().index("a_func") + 1) self.assertEqual("print(1, 3)\n", self.mod.read()) def test_passing_first_arguments_for_methods(self): a_class = dedent("""\ class A(object): def __init__(self): self.var = 1 self.a_func(self.var) def a_func(self, param): print(param) """) self.mod.write(a_class) self._inline2(self.mod, self.mod.read().index("a_func") + 1) expected = dedent("""\ class A(object): def __init__(self): self.var = 1 print(self.var) """) self.assertEqual(expected, self.mod.read()) def test_passing_first_arguments_for_methods2(self): a_class = dedent("""\ class A(object): def __init__(self): self.var = 1 def a_func(self, param): print(param, self.var) an_a = A() an_a.a_func(1) """) self.mod.write(a_class) self._inline2(self.mod, self.mod.read().index("a_func") + 1) expected = dedent("""\ class A(object): def __init__(self): self.var = 1 an_a = A() print(1, an_a.var) """) self.assertEqual(expected, self.mod.read()) def test_passing_first_arguments_for_methods3(self): a_class = dedent("""\ class A(object): def __init__(self): self.var = 1 def a_func(self, param): print(param, self.var) an_a = A() A.a_func(an_a, 1) """) self.mod.write(a_class) self._inline2(self.mod, self.mod.read().index("a_func") + 1) expected = dedent("""\ class A(object): def __init__(self): self.var = 1 an_a = A() print(1, an_a.var) """) self.assertEqual(expected, self.mod.read()) def test_inlining_staticmethods(self): a_class = dedent("""\ class A(object): @staticmethod def a_func(param): print(param) A.a_func(1) """) self.mod.write(a_class) self._inline2(self.mod, self.mod.read().index("a_func") + 1) expected = dedent("""\ class A(object): pass print(1) """) self.assertEqual(expected, self.mod.read()) def test_static_methods2(self): a_class = dedent("""\ class A(object): var = 10 @staticmethod def a_func(param): print(param) an_a = A() an_a.a_func(1) A.a_func(2) """) self.mod.write(a_class) self._inline2(self.mod, self.mod.read().index("a_func") + 1) expected = dedent("""\ class A(object): var = 10 an_a = A() print(1) print(2) """) self.assertEqual(expected, self.mod.read()) def test_inlining_classmethods(self): a_class = dedent("""\ class A(object): @classmethod def a_func(cls, param): print(param) A.a_func(1) """) self.mod.write(a_class) self._inline2(self.mod, self.mod.read().index("a_func") + 1) expected = dedent("""\ class A(object): pass print(1) """) self.assertEqual(expected, self.mod.read()) def test_inlining_classmethods2(self): a_class = dedent("""\ class A(object): @classmethod def a_func(cls, param): return cls print(A.a_func(1)) """) self.mod.write(a_class) self._inline2(self.mod, self.mod.read().index("a_func") + 1) expected = dedent("""\ class A(object): pass print(A) """) self.assertEqual(expected, self.mod.read()) def test_simple_return_values_and_inlining_functions(self): self.mod.write(dedent("""\ def a_func(): return 1 a = a_func() """)) self._inline2(self.mod, self.mod.read().index("a_func") + 1) self.assertEqual("a = 1\n", self.mod.read()) def test_simple_return_values_and_inlining_lonely_functions(self): self.mod.write(dedent("""\ def a_func(): return 1 a_func() """)) self._inline2(self.mod, self.mod.read().index("a_func") + 1) self.assertEqual("1\n", self.mod.read()) def test_empty_returns_and_inlining_lonely_functions(self): self.mod.write(dedent("""\ def a_func(): if True: return a_func() """)) self._inline2(self.mod, self.mod.read().index("a_func") + 1) self.assertEqual( dedent("""\ if True: pass """), self.mod.read(), ) def test_multiple_returns(self): self.mod.write(dedent("""\ def less_than_five(var): if var < 5: return True return False a = less_than_five(2) """)) with self.assertRaises(rope.base.exceptions.RefactoringError): self._inline2(self.mod, self.mod.read().index("less") + 1) def test_multiple_returns_and_not_using_the_value(self): self.mod.write(dedent("""\ def less_than_five(var): if var < 5: return True return False less_than_five(2) """)) self._inline2(self.mod, self.mod.read().index("less") + 1) self.assertEqual( dedent("""\ if 2 < 5: True False """), self.mod.read(), ) def test_raising_exception_for_list_arguments(self): self.mod.write(dedent("""\ def a_func(*args): print(args) a_func(1) """)) with self.assertRaises(rope.base.exceptions.RefactoringError): self._inline2(self.mod, self.mod.read().index("a_func") + 1) def test_raising_exception_for_list_keywods(self): self.mod.write(dedent("""\ def a_func(**kwds): print(kwds) a_func(n=1) """)) with self.assertRaises(rope.base.exceptions.RefactoringError): self._inline2(self.mod, self.mod.read().index("a_func") + 1) def test_function_parameters_and_returns_in_other_functions(self): code = dedent("""\ def a_func(param1, param2): return param1 + param2 range(a_func(20, param2=abs(10))) """) self.mod.write(code) self._inline2(self.mod, self.mod.read().index("a_func") + 1) self.assertEqual("range(20 + abs(10))\n", self.mod.read()) def test_function_references_other_than_call(self): self.mod.write(dedent("""\ def a_func(param): print(param) f = a_func """)) with self.assertRaises(rope.base.exceptions.RefactoringError): self._inline2(self.mod, self.mod.read().index("a_func") + 1) def test_function_referencing_itself(self): self.mod.write(dedent("""\ def a_func(var): func = a_func """)) with self.assertRaises(rope.base.exceptions.RefactoringError): self._inline2(self.mod, self.mod.read().index("a_func") + 1) def test_recursive_functions(self): self.mod.write(dedent("""\ def a_func(var): a_func(var) """)) with self.assertRaises(rope.base.exceptions.RefactoringError): self._inline2(self.mod, self.mod.read().index("a_func") + 1) # TODO: inlining on function parameters def xxx_test_inlining_function_default_parameters(self): self.mod.write(dedent("""\ def a_func(p1=1): pass a_func() """)) self._inline2(self.mod, self.mod.read().index("p1") + 1) self.assertEqual( dedent("""\ def a_func(p1=1): pass a_func() """), self.mod.read(), ) def test_simple_inlining_after_extra_indented_lines(self): self.mod.write(dedent("""\ def a_func(): for i in range(10): pass if True: pass a_func() """)) self._inline2(self.mod, self.mod.read().index("a_func") + 1) self.assertEqual( dedent("""\ if True: pass for i in range(10): pass """), self.mod.read(), ) def test_inlining_a_function_with_pydoc(self): self.mod.write(dedent('''\ def a_func(): """docs""" a = 1 a_func()''')) self._inline2(self.mod, self.mod.read().index("a_func") + 1) self.assertEqual("a = 1\n", self.mod.read()) def test_inlining_methods(self): self.mod.write(dedent("""\ class A(object): name = 'hey' def get_name(self): return self.name a = A() name = a.get_name() """)) self._inline2(self.mod, self.mod.read().rindex("get_name") + 1) self.assertEqual( dedent("""\ class A(object): name = 'hey' a = A() name = a.name """), self.mod.read(), ) def test_simple_returns_with_backslashes(self): self.mod.write(dedent("""\ def a_func(): return 1\\ + 2 a = a_func() """)) self._inline2(self.mod, self.mod.read().index("a_func") + 1) self.assertEqual( "a = 1\\\n + 2\n", dedent("""\ a = 1\\ + 2 """), self.mod.read(), ) def test_a_function_with_pass_body(self): self.mod.write(dedent("""\ def a_func(): print(1) a = a_func() """)) self._inline2(self.mod, self.mod.read().index("a_func") + 1) self.assertEqual( dedent("""\ print(1) a = None """), self.mod.read(), ) def test_inlining_the_last_method_of_a_class(self): self.mod.write(dedent("""\ class A(object): def a_func(self): pass """)) self._inline2(self.mod, self.mod.read().rindex("a_func") + 1) self.assertEqual( dedent("""\ class A(object): pass """), self.mod.read(), ) def test_adding_needed_imports_in_the_dest_module(self): self.mod.write(dedent("""\ import sys def ver(): print(sys.version) """)) self.mod2.write(dedent("""\ import mod mod.ver()""")) self._inline2(self.mod, self.mod.read().index("ver") + 1) self.assertEqual( dedent("""\ import mod import sys print(sys.version) """), self.mod2.read(), ) def test_adding_needed_imports_in_the_dest_module_removing_selfs(self): self.mod.write(dedent("""\ import mod2 def f(): print(mod2.var) """)) self.mod2.write(dedent("""\ import mod var = 1 mod.f() """)) self._inline2(self.mod, self.mod.read().index("f(") + 1) self.assertEqual( dedent("""\ import mod var = 1 print(var) """), self.mod2.read(), ) def test_handling_relative_imports_when_inlining(self): pkg = testutils.create_package(self.project, "pkg") mod3 = testutils.create_module(self.project, "mod3", pkg) mod4 = testutils.create_module(self.project, "mod4", pkg) mod4.write("var = 1\n") mod3.write(dedent("""\ from . import mod4 def f(): print(mod4.var) """)) self.mod.write(dedent("""\ import pkg.mod3 pkg.mod3.f() """)) self._inline2(self.mod, self.mod.read().index("f(") + 1) # Cannot determine the exact import self.assertTrue("\n\nprint(mod4.var)\n" in self.mod.read()) def test_adding_needed_imports_for_elements_in_source(self): self.mod.write(dedent("""\ def f1(): return f2() def f2(): return 1 """)) self.mod2.write(dedent("""\ import mod print(mod.f1()) """)) self._inline2(self.mod, self.mod.read().index("f1") + 1) self.assertEqual( dedent("""\ import mod from mod import f2 print(f2()) """), self.mod2.read(), ) def test_relative_imports_and_changing_inlining_body(self): pkg = testutils.create_package(self.project, "pkg") mod3 = testutils.create_module(self.project, "mod3", pkg) mod4 = testutils.create_module(self.project, "mod4", pkg) mod4.write("var = 1\n") mod3.write(dedent("""\ import mod4 def f(): print(mod4.var) """)) self.mod.write(dedent("""\ import pkg.mod3 pkg.mod3.f() """)) self._inline2(self.mod, self.mod.read().index("f(") + 1) self.assertEqual( dedent("""\ import pkg.mod3 import pkg.mod4 print(pkg.mod4.var) """), self.mod.read(), ) def test_inlining_with_different_returns(self): self.mod.write(dedent("""\ def f(p): return p print(f(1)) print(f(2)) print(f(1)) """)) self._inline2(self.mod, self.mod.read().index("f(") + 1) self.assertEqual( dedent("""\ print(1) print(2) print(1) """), self.mod.read(), ) def test_not_removing_definition_for_variables(self): code = dedent("""\ a_var = 10 another_var = a_var """) refactored = self._inline(code, code.index("a_var") + 1, remove=False) self.assertEqual( dedent("""\ a_var = 10 another_var = 10 """), refactored, ) def test_not_removing_definition_for_methods(self): code = dedent("""\ def func(): print(1) func() """) refactored = self._inline(code, code.index("func") + 1, remove=False) self.assertEqual( dedent("""\ def func(): print(1) print(1) """), refactored, ) def test_only_current_for_methods(self): code = dedent("""\ def func(): print(1) func() func() """) refactored = self._inline( code, code.rindex("func") + 1, remove=False, only_current=True ) self.assertEqual( dedent("""\ def func(): print(1) func() print(1) """), refactored, ) def test_only_current_for_variables(self): code = dedent("""\ one = 1 a = one b = one """) refactored = self._inline( code, code.rindex("one") + 1, remove=False, only_current=True ) self.assertEqual( dedent("""\ one = 1 a = one b = 1 """), refactored, ) def test_inlining_one_line_functions(self): code = dedent("""\ def f(): return 1 var = f() """) refactored = self._inline(code, code.rindex("f")) self.assertEqual("var = 1\n", refactored) def test_inlining_one_line_functions_with_breaks(self): code = dedent("""\ def f( p): return p var = f(1) """) refactored = self._inline(code, code.rindex("f")) self.assertEqual("var = 1\n", refactored) def test_inlining_one_line_functions_with_breaks2(self): code = dedent("""\ def f( ): return 1 var = f() """) refactored = self._inline(code, code.rindex("f")) self.assertEqual("var = 1\n", refactored) def test_resources_parameter(self): self.mod.write(dedent("""\ def a_func(): print(1) """)) mod1 = testutils.create_module(self.project, "mod1") mod1.write(dedent("""\ import mod mod.a_func() """)) self._inline2(self.mod, self.mod.read().index("a_func"), resources=[self.mod]) self.assertEqual("", self.mod.read()) self.assertEqual( dedent("""\ import mod mod.a_func() """), mod1.read(), ) def test_inlining_parameters(self): code = dedent("""\ def f(p=1): pass f() """) result = self._inline(code, code.index("p")) self.assertEqual( dedent("""\ def f(p=1): pass f(1) """), result, ) def test_inlining_function_with_line_breaks_in_args(self): code = dedent("""\ def f(p): return p var = f(1 + 1) """) refactored = self._inline(code, code.rindex("f")) self.assertEqual("var = 1 + 1\n", refactored) def test_inlining_variables_before_comparison(self): code = "start = 1\nprint(start <= 2)\n" refactored = self._inline(code, code.index("start")) self.assertEqual("print(1 <= 2)\n", refactored) def test_inlining_variables_in_other_modules(self): self.mod.write("myvar = 1\n") self.mod2.write(dedent("""\ import mod print(mod.myvar) """)) self._inline2(self.mod, 2) self.assertEqual( dedent("""\ import mod print(1) """), self.mod2.read(), ) def test_inlining_variables_and_back_importing(self): self.mod.write(dedent("""\ mainvar = 1 myvar = mainvar """)) self.mod2.write(dedent("""\ import mod print(mod.myvar) """)) self._inline2(self.mod, self.mod.read().index("myvar")) expected = dedent("""\ import mod from mod import mainvar print(mainvar) """) self.assertEqual(expected, self.mod2.read()) def test_inlining_variables_and_importing_used_imports(self): self.mod.write(dedent("""\ import sys myvar = sys.argv """)) self.mod2.write(dedent("""\ import mod print(mod.myvar) """)) self._inline2(self.mod, self.mod.read().index("myvar")) expected = dedent("""\ import mod import sys print(sys.argv) """) self.assertEqual(expected, self.mod2.read()) def test_inlining_variables_and_removing_old_froms(self): self.mod.write("var = 1\n") self.mod2.write(dedent("""\ from mod import var print(var) """)) self._inline2(self.mod2, self.mod2.read().rindex("var")) self.assertEqual("print(1)\n", self.mod2.read()) def test_inlining_method_and_removing_old_froms(self): self.mod.write(dedent("""\ def f(): return 1 """)) self.mod2.write(dedent("""\ from mod import f print(f()) """)) self._inline2(self.mod2, self.mod2.read().rindex("f")) self.assertEqual("print(1)\n", self.mod2.read()) def test_inlining_functions_in_other_modules_and_only_current(self): code1 = dedent("""\ def f(): return 1 print(f()) """) code2 = dedent("""\ import mod print(mod.f()) print(mod.f()) """) self.mod.write(code1) self.mod2.write(code2) self._inline2( self.mod2, self.mod2.read().rindex("f"), remove=False, only_current=True ) expected2 = dedent("""\ import mod print(mod.f()) print(1) """) self.assertEqual(code1, self.mod.read()) self.assertEqual(expected2, self.mod2.read()) def test_inlining_variables_in_other_modules_and_only_current(self): code1 = dedent("""\ var = 1 print(var) """) code2 = dedent("""\ import mod print(mod.var) print(mod.var) """) self.mod.write(code1) self.mod2.write(code2) self._inline2( self.mod2, self.mod2.read().rindex("var"), remove=False, only_current=True ) expected2 = "import mod\n" "print(mod.var)\n" "print(1)\n" self.assertEqual(code1, self.mod.read()) self.assertEqual(expected2, self.mod2.read()) def test_inlining_does_not_change_string_constants(self): code = dedent("""\ var = 1 print("var\\ ") """) expected = dedent("""\ var = 1 print("var\\ ") """) refactored = self._inline( code, code.rindex("var"), remove=False, only_current=True, docs=False ) self.assertEqual(expected, refactored) def test_inlining_does_change_string_constants_if_docs_is_set(self): code = dedent("""\ var = 1 print("var\\ ") """) expected = dedent("""\ var = 1 print("1\\ ") """) refactored = self._inline( code, code.rindex("var"), remove=False, only_current=True, docs=True ) self.assertEqual(expected, refactored) @testutils.only_for_versions_higher("3.6") def test_inlining_into_format_string(self): code = dedent("""\ var = 123 print(f"{var}") """) expected = dedent("""\ print(f"{123}") """) refactored = self._inline(code, code.rindex("var")) self.assertEqual(expected, refactored) @testutils.only_for_versions_higher("3.6") def test_inlining_into_format_string_containing_quotes(self): code = dedent('''\ var = 123 print(f" '{var}' ") print(f""" "{var}" """) print(f' "{var}" ') ''') expected = dedent('''\ print(f" '{123}' ") print(f""" "{123}" """) print(f' "{123}" ') ''') refactored = self._inline(code, code.rindex("var")) self.assertEqual(expected, refactored) def test_parameters_with_the_same_name_as_passed_with_type_hints(self): self.mod.write(dedent("""\ def a_func(var: int): print(var) var = 1 a_func(var) """)) self._inline2(self.mod, self.mod.read().index("a_func") + 1) self.assertEqual( dedent("""\ var = 1 print(var) """), self.mod.read(), ) def test_parameters_with_the_same_name_as_passed_as_kwargs_with_type_hints(self): self.mod.write(dedent("""\ def a_func(var: int): print(var) var = 1 a_func(var=var) """)) self._inline2(self.mod, self.mod.read().index("a_func") + 1) self.assertEqual( dedent("""\ var = 1 print(var) """), self.mod.read(), ) def test_simple_parameters_renaming_with_type_hints(self): self.mod.write(dedent("""\ def a_func(param: int): print(param) var = 1 a_func(var) """)) self._inline2(self.mod, self.mod.read().index("a_func") + 1) self.assertEqual( dedent("""\ var = 1 print(var) """), self.mod.read(), ) def test_simple_parameters_renaming_for_multiple_params_with_type_hints(self): self.mod.write(dedent("""\ def a_func(param1, param2: int): p = param1 + param2 var1 = 1 var2 = 1 a_func(var1, var2) """)) self._inline2(self.mod, self.mod.read().index("a_func") + 1) self.assertEqual( dedent("""\ var1 = 1 var2 = 1 p = var1 + var2 """), self.mod.read(), ) def test_parameters_renaming_for_passed_constants_with_type_hints(self): self.mod.write(dedent("""\ def a_func(param: int): print(param) a_func(1) """)) self._inline2(self.mod, self.mod.read().index("a_func") + 1) self.assertEqual("print(1)\n", self.mod.read()) def test_parameters_renaming_for_passed_statements_with_type_hints(self): self.mod.write(dedent("""\ def a_func(param: int): print(param) a_func((1 + 2) / 3) """)) self._inline2(self.mod, self.mod.read().index("a_func") + 1) self.assertEqual( dedent("""\ print((1 + 2) / 3) """), self.mod.read(), ) def test_simple_parameters_renaming_for_multiple_params_using_keywords_with_type_hints( self, ): self.mod.write(dedent("""\ def a_func(param1, param2: int): p = param1 + param2 var1 = 1 var2 = 1 a_func(param2=var1, param1=var2) """)) self._inline2(self.mod, self.mod.read().index("a_func") + 1) self.assertEqual( dedent("""\ var1 = 1 var2 = 1 p = var2 + var1 """), self.mod.read(), ) def test_simple_params_renaming_for_multi_params_using_mixed_keywords_with_type_hints( self, ): self.mod.write(dedent("""\ def a_func(param1, param2: int): p = param1 + param2 var1 = 1 var2 = 1 a_func(var2, param2=var1) """)) self._inline2(self.mod, self.mod.read().index("a_func") + 1) self.assertEqual( dedent("""\ var1 = 1 var2 = 1 p = var2 + var1 """), self.mod.read(), ) def test_simple_putting_in_default_arguments_with_type_hints(self): self.mod.write(dedent("""\ def a_func(param: Optional[int] = None): print(param) a_func() """)) self._inline2(self.mod, self.mod.read().index("a_func") + 1) self.assertEqual("print(None)\n", self.mod.read()) def test_overriding_default_arguments_with_type_hints(self): self.mod.write(dedent("""\ def a_func(param1=1, param2: int = 2): print(param1, param2) a_func(param2=3) """)) self._inline2(self.mod, self.mod.read().index("a_func") + 1) self.assertEqual("print(1, 3)\n", self.mod.read()) def test_dictionary_with_inline_comment(self): code = dedent("""\ myvar = { "key": "value", # noqa } print(myvar) """) refactored = self._inline(code, code.index("myvar") + 1) expected = dedent("""\ print({ "key": "value", # noqa }) """) self.assertEqual(expected, refactored) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/ropetest/refactor/movetest.py0000664000175000017500000012120314512700666020372 0ustar00lieryanlieryanimport unittest from textwrap import dedent from rope.base import exceptions from rope.refactor import move from ropetest import testutils class MoveRefactoringTest(unittest.TestCase): def setUp(self): super().setUp() self.project = testutils.sample_project() self.mod1 = testutils.create_module(self.project, "mod1") self.mod2 = testutils.create_module(self.project, "mod2") self.mod3 = testutils.create_module(self.project, "mod3") self.pkg = testutils.create_package(self.project, "pkg") self.mod4 = testutils.create_module(self.project, "mod4", self.pkg) self.mod5 = testutils.create_module(self.project, "mod5", self.pkg) def tearDown(self): testutils.remove_project(self.project) super().tearDown() def _move(self, resource, offset, dest_resource): changes = move.create_move(self.project, resource, offset).get_changes( dest_resource ) self.project.do(changes) def test_move_constant(self): self.mod1.write("foo = 123\n") self._move(self.mod1, self.mod1.read().index("foo") + 1, self.mod2) self.assertEqual("", self.mod1.read()) self.assertEqual("foo = 123\n", self.mod2.read()) def test_move_constant_2(self): self.mod1.write("bar = 321\nfoo = 123\n") self._move(self.mod1, self.mod1.read().index("foo") + 1, self.mod2) self.assertEqual("bar = 321\n", self.mod1.read()) self.assertEqual("foo = 123\n", self.mod2.read()) def test_move_target_is_module_name(self): self.mod1.write("foo = 123\n") self._move(self.mod1, self.mod1.read().index("foo") + 1, "mod2") self.assertEqual("", self.mod1.read()) self.assertEqual("foo = 123\n", self.mod2.read()) def test_move_target_is_package_name(self): self.mod1.write("foo = 123\n") self._move(self.mod1, self.mod1.read().index("foo") + 1, "pkg.mod4") self.assertEqual("", self.mod1.read()) self.assertEqual("foo = 123\n", self.mod4.read()) def test_move_constant_multiline(self): self.mod1.write(dedent("""\ foo = ( 123 ) """)) self._move(self.mod1, self.mod1.read().index("foo") + 1, self.mod2) self.assertEqual("", self.mod1.read()) self.assertEqual( dedent("""\ foo = ( 123 ) """), self.mod2.read(), ) def test_move_constant_multiple_statements(self): self.mod1.write(dedent("""\ foo = 123 foo += 3 foo = 4 """)) self._move(self.mod1, self.mod1.read().index("foo") + 1, self.mod2) self.assertEqual( dedent("""\ import mod2 mod2.foo += 3 mod2.foo = 4 """), self.mod1.read(), ) self.assertEqual("foo = 123\n", self.mod2.read()) def test_simple_moving(self): self.mod1.write(dedent("""\ class AClass(object): pass """)) self._move(self.mod1, self.mod1.read().index("AClass") + 1, self.mod2) self.assertEqual("", self.mod1.read()) self.assertEqual( dedent("""\ class AClass(object): pass """), self.mod2.read(), ) def test_moving_with_comment_prefix(self): self.mod1.write(dedent("""\ a = 1 # 1 # 2 class AClass(object): pass """)) self._move(self.mod1, self.mod1.read().index("AClass") + 1, self.mod2) self.assertEqual("a = 1\n", self.mod1.read()) self.assertEqual( dedent("""\ # 1 # 2 class AClass(object): pass """), self.mod2.read(), ) def test_moving_with_comment_prefix_imports(self): self.mod1.write(dedent("""\ import foo a = 1 # 1 # 2 class AClass(foo.FooClass): pass """)) self._move(self.mod1, self.mod1.read().index("AClass") + 1, self.mod2) self.assertEqual("a = 1\n", self.mod1.read()) self.assertEqual( dedent("""\ import foo # 1 # 2 class AClass(foo.FooClass): pass """), self.mod2.read(), ) def test_changing_other_modules_replacing_normal_imports(self): self.mod1.write("class AClass(object):\n pass\n") self.mod3.write("import mod1\na_var = mod1.AClass()\n") self._move(self.mod1, self.mod1.read().index("AClass") + 1, self.mod2) self.assertEqual( dedent("""\ import mod2 a_var = mod2.AClass() """), self.mod3.read(), ) def test_changing_other_modules_adding_normal_imports(self): self.mod1.write(dedent("""\ class AClass(object): pass def a_function(): pass """)) self.mod3.write(dedent("""\ import mod1 a_var = mod1.AClass() mod1.a_function()""")) self._move(self.mod1, self.mod1.read().index("AClass") + 1, self.mod2) self.assertEqual( dedent("""\ import mod1 import mod2 a_var = mod2.AClass() mod1.a_function()"""), self.mod3.read(), ) def test_adding_imports_prefer_from_module(self): self.project.prefs["prefer_module_from_imports"] = True self.mod1.write(dedent("""\ class AClass(object): pass def a_function(): pass """)) self.mod3.write(dedent("""\ import mod1 a_var = mod1.AClass() mod1.a_function()""")) # Move to mod4 which is in a different package self._move(self.mod1, self.mod1.read().index("AClass") + 1, self.mod4) self.assertEqual( dedent("""\ import mod1 from pkg import mod4 a_var = mod4.AClass() mod1.a_function()"""), self.mod3.read(), ) def test_adding_imports_noprefer_from_module(self): self.project.prefs["prefer_module_from_imports"] = False self.mod1.write(dedent("""\ class AClass(object): pass def a_function(): pass """)) self.mod3.write(dedent("""\ import mod1 a_var = mod1.AClass() mod1.a_function()""")) # Move to mod4 which is in a different package self._move(self.mod1, self.mod1.read().index("AClass") + 1, self.mod4) self.assertEqual( dedent("""\ import mod1 import pkg.mod4 a_var = pkg.mod4.AClass() mod1.a_function()"""), self.mod3.read(), ) def test_adding_imports_prefer_from_module_top_level_module(self): self.project.prefs["prefer_module_from_imports"] = True self.mod1.write(dedent("""\ class AClass(object): pass def a_function(): pass """)) self.mod3.write(dedent("""\ import mod1 a_var = mod1.AClass() mod1.a_function()""")) self._move(self.mod1, self.mod1.read().index("AClass") + 1, self.mod2) self.assertEqual( dedent("""\ import mod1 import mod2 a_var = mod2.AClass() mod1.a_function()"""), self.mod3.read(), ) def test_changing_other_modules_removing_from_imports(self): self.mod1.write(dedent("""\ class AClass(object): pass """)) self.mod3.write(dedent("""\ from mod1 import AClass a_var = AClass() """)) self._move(self.mod1, self.mod1.read().index("AClass") + 1, self.mod2) self.assertEqual( dedent("""\ import mod2 a_var = mod2.AClass() """), self.mod3.read(), ) def test_changing_source_module(self): self.mod1.write(dedent("""\ class AClass(object): pass a_var = AClass() """)) self._move(self.mod1, self.mod1.read().index("AClass") + 1, self.mod2) self.assertEqual( dedent("""\ import mod2 a_var = mod2.AClass() """), self.mod1.read(), ) def test_changing_destination_module(self): self.mod1.write(dedent("""\ class AClass(object): pass """)) self.mod2.write(dedent("""\ from mod1 import AClass a_var = AClass() """)) self._move(self.mod1, self.mod1.read().index("AClass") + 1, self.mod2) self.assertEqual( dedent("""\ class AClass(object): pass a_var = AClass() """), self.mod2.read(), ) def test_folder_destination(self): folder = self.project.root.create_folder("folder") self.mod1.write(dedent("""\ class AClass(object): pass """)) with self.assertRaises(exceptions.RefactoringError): self._move(self.mod1, self.mod1.read().index("AClass") + 1, folder) def test_raising_exception_for_moving_non_global_elements(self): self.mod1.write(dedent("""\ def a_func(): class AClass(object): pass """)) with self.assertRaises(exceptions.RefactoringError): self._move(self.mod1, self.mod1.read().index("AClass") + 1, self.mod2) def test_raising_an_exception_for_moving_non_global_variable(self): code = dedent("""\ class TestClass: CONSTANT = 5 """) self.mod1.write(code) with self.assertRaises(exceptions.RefactoringError): mover = move.create_move( self.project, self.mod1, code.index("CONSTANT") + 1 ) def test_raising_exception_for_moving_glob_elements_to_the_same_module(self): self.mod1.write("def a_func():\n pass\n") with self.assertRaises(exceptions.RefactoringError): self._move(self.mod1, self.mod1.read().index("a_func"), self.mod1) def test_moving_used_imports_to_destination_module(self): self.mod3.write("a_var = 10") code = dedent("""\ import mod3 from mod3 import a_var def a_func(): print(mod3, a_var) """) self.mod1.write(code) self._move(self.mod1, code.index("a_func") + 1, self.mod2) expected = dedent("""\ import mod3 from mod3 import a_var def a_func(): print(mod3, a_var) """) self.assertEqual(expected, self.mod2.read()) def test_moving_used_names_to_destination_module2(self): code = dedent("""\ a_var = 10 def a_func(): print(a_var) """) self.mod1.write(code) self._move(self.mod1, code.index("a_func") + 1, self.mod2) self.assertEqual( dedent("""\ a_var = 10 """), self.mod1.read(), ) expected = dedent("""\ from mod1 import a_var def a_func(): print(a_var) """) self.assertEqual(expected, self.mod2.read()) def test_moving_used_underlined_names_to_destination_module(self): code = dedent("""\ _var = 10 def a_func(): print(_var) """) self.mod1.write(code) self._move(self.mod1, code.index("a_func") + 1, self.mod2) expected = dedent("""\ from mod1 import _var def a_func(): print(_var) """) self.assertEqual(expected, self.mod2.read()) def test_moving_and_used_relative_imports(self): code = dedent("""\ import mod5 def a_func(): print(mod5) """) self.mod4.write(code) self._move(self.mod4, code.index("a_func") + 1, self.mod1) expected = dedent("""\ import pkg.mod5 def a_func(): print(pkg.mod5) """) self.assertEqual(expected, self.mod1.read()) self.assertEqual("", self.mod4.read()) def test_moving_modules(self): code = "import mod1\nprint(mod1)" self.mod2.write(code) self._move(self.mod2, code.index("mod1") + 1, self.pkg) expected = "import pkg.mod1\nprint(pkg.mod1)" self.assertEqual(expected, self.mod2.read()) self.assertTrue( not self.mod1.exists() and self.project.find_module("pkg.mod1") is not None ) def test_moving_modules_and_removing_out_of_date_imports(self): code = "import pkg.mod4\nprint(pkg.mod4)" self.mod2.write(code) self._move(self.mod2, code.index("mod4") + 1, self.project.root) expected = "import mod4\nprint(mod4)" self.assertEqual(expected, self.mod2.read()) self.assertTrue(self.project.find_module("mod4") is not None) def test_moving_modules_and_removing_out_of_date_froms(self): code = "from pkg import mod4\nprint(mod4)" self.mod2.write(code) self._move(self.mod2, code.index("mod4") + 1, self.project.root) self.assertEqual("import mod4\nprint(mod4)", self.mod2.read()) def test_moving_modules_and_removing_out_of_date_froms2(self): self.mod4.write("a_var = 10") code = "from pkg.mod4 import a_var\nprint(a_var)\n" self.mod2.write(code) self._move(self.mod2, code.index("mod4") + 1, self.project.root) expected = "from mod4 import a_var\nprint(a_var)\n" self.assertEqual(expected, self.mod2.read()) def test_moving_modules_and_relative_import(self): self.mod4.write("import mod5\nprint(mod5)\n") code = "import pkg.mod4\nprint(pkg.mod4)" self.mod2.write(code) self._move(self.mod2, code.index("mod4") + 1, self.project.root) moved = self.project.find_module("mod4") expected = "import pkg.mod5\nprint(pkg.mod5)\n" self.assertEqual(expected, moved.read()) def test_moving_module_kwarg_same_name_as_old(self): self.mod1.write("def foo(mod1=0):\n pass") code = "import mod1\nmod1.foo(mod1=1)" self.mod2.write(code) self._move(self.mod1, None, self.pkg) moved = self.project.find_module("mod2") expected = "import pkg.mod1\npkg.mod1.foo(mod1=1)" self.assertEqual(expected, moved.read()) def test_moving_packages(self): pkg2 = testutils.create_package(self.project, "pkg2") code = "import pkg.mod4\nprint(pkg.mod4)" self.mod1.write(code) self._move(self.mod1, code.index("pkg") + 1, pkg2) self.assertFalse(self.pkg.exists()) self.assertTrue(self.project.find_module("pkg2.pkg.mod4") is not None) self.assertTrue(self.project.find_module("pkg2.pkg.mod4") is not None) self.assertTrue(self.project.find_module("pkg2.pkg.mod5") is not None) expected = "import pkg2.pkg.mod4\nprint(pkg2.pkg.mod4)" self.assertEqual(expected, self.mod1.read()) def test_moving_modules_with_self_imports(self): self.mod1.write("import mod1\nprint(mod1)\n") self.mod2.write("import mod1\n") self._move(self.mod2, self.mod2.read().index("mod1") + 1, self.pkg) moved = self.project.find_module("pkg.mod1") self.assertEqual( dedent("""\ import pkg.mod1 print(pkg.mod1) """), moved.read(), ) def test_moving_modules_with_from_imports(self): pkg2 = testutils.create_package(self.project, "pkg2") code = dedent("""\ from pkg import mod4 print(mod4)""") self.mod1.write(code) self._move(self.mod1, code.index("pkg") + 1, pkg2) self.assertFalse(self.pkg.exists()) self.assertTrue(self.project.find_module("pkg2.pkg.mod4") is not None) self.assertTrue(self.project.find_module("pkg2.pkg.mod5") is not None) expected = dedent("""\ from pkg2.pkg import mod4 print(mod4)""") self.assertEqual(expected, self.mod1.read()) def test_moving_modules_with_from_import(self): pkg2 = testutils.create_package(self.project, "pkg2") pkg3 = testutils.create_package(self.project, "pkg3", pkg2) pkg4 = testutils.create_package(self.project, "pkg4", pkg3) code = dedent("""\ from pkg import mod4 print(mod4)""") self.mod1.write(code) self._move(self.mod4, None, pkg4) self.assertTrue(self.project.find_module("pkg2.pkg3.pkg4.mod4") is not None) expected = dedent("""\ from pkg2.pkg3.pkg4 import mod4 print(mod4)""") self.assertEqual(expected, self.mod1.read()) def test_moving_modules_with_multi_from_imports(self): pkg2 = testutils.create_package(self.project, "pkg2") pkg3 = testutils.create_package(self.project, "pkg3", pkg2) pkg4 = testutils.create_package(self.project, "pkg4", pkg3) code = dedent("""\ from pkg import mod4, mod5 print(mod4)""") self.mod1.write(code) self._move(self.mod4, None, pkg4) self.assertTrue(self.project.find_module("pkg2.pkg3.pkg4.mod4") is not None) expected = dedent("""\ from pkg import mod5 from pkg2.pkg3.pkg4 import mod4 print(mod4)""") self.assertEqual(expected, self.mod1.read()) def test_moving_modules_with_from_and_normal_imports(self): pkg2 = testutils.create_package(self.project, "pkg2") pkg3 = testutils.create_package(self.project, "pkg3", pkg2) pkg4 = testutils.create_package(self.project, "pkg4", pkg3) code = dedent("""\ from pkg import mod4 import pkg.mod4 print(mod4) print(pkg.mod4)""") self.mod1.write(code) self._move(self.mod4, None, pkg4) self.assertTrue(self.project.find_module("pkg2.pkg3.pkg4.mod4") is not None) expected = dedent("""\ import pkg2.pkg3.pkg4.mod4 from pkg2.pkg3.pkg4 import mod4 print(mod4) print(pkg2.pkg3.pkg4.mod4)""") self.assertEqual(expected, self.mod1.read()) def test_moving_modules_with_normal_and_from_imports(self): pkg2 = testutils.create_package(self.project, "pkg2") pkg3 = testutils.create_package(self.project, "pkg3", pkg2) pkg4 = testutils.create_package(self.project, "pkg4", pkg3) code = dedent("""\ import pkg.mod4 from pkg import mod4 print(mod4) print(pkg.mod4)""") self.mod1.write(code) self._move(self.mod4, None, pkg4) self.assertTrue(self.project.find_module("pkg2.pkg3.pkg4.mod4") is not None) expected = dedent("""\ import pkg2.pkg3.pkg4.mod4 from pkg2.pkg3.pkg4 import mod4 print(mod4) print(pkg2.pkg3.pkg4.mod4)""") self.assertEqual(expected, self.mod1.read()) def test_moving_modules_from_import_variable(self): pkg2 = testutils.create_package(self.project, "pkg2") pkg3 = testutils.create_package(self.project, "pkg3", pkg2) pkg4 = testutils.create_package(self.project, "pkg4", pkg3) code = dedent("""\ from pkg.mod4 import foo print(foo)""") self.mod1.write(code) self._move(self.mod4, None, pkg4) self.assertTrue(self.project.find_module("pkg2.pkg3.pkg4.mod4") is not None) expected = dedent("""\ from pkg2.pkg3.pkg4.mod4 import foo print(foo)""") self.assertEqual(expected, self.mod1.read()) def test_moving_modules_normal_import(self): pkg2 = testutils.create_package(self.project, "pkg2") pkg3 = testutils.create_package(self.project, "pkg3", pkg2) pkg4 = testutils.create_package(self.project, "pkg4", pkg3) code = dedent("""\ import pkg.mod4 print(pkg.mod4)""") self.mod1.write(code) self._move(self.mod4, None, pkg4) self.assertTrue(self.project.find_module("pkg2.pkg3.pkg4.mod4") is not None) expected = dedent("""\ import pkg2.pkg3.pkg4.mod4 print(pkg2.pkg3.pkg4.mod4)""") self.assertEqual(expected, self.mod1.read()) def test_moving_package_with_from_and_normal_imports(self): pkg2 = testutils.create_package(self.project, "pkg2") code = dedent("""\ from pkg import mod4 import pkg.mod4 print(pkg.mod4) print(mod4)""") self.mod1.write(code) self._move(self.mod1, code.index("pkg") + 1, pkg2) self.assertFalse(self.pkg.exists()) self.assertTrue(self.project.find_module("pkg2.pkg.mod4") is not None) self.assertTrue(self.project.find_module("pkg2.pkg.mod5") is not None) expected = dedent("""\ from pkg2.pkg import mod4 import pkg2.pkg.mod4 print(pkg2.pkg.mod4) print(mod4)""") self.assertEqual(expected, self.mod1.read()) def test_moving_package_with_from_and_normal_imports2(self): pkg2 = testutils.create_package(self.project, "pkg2") code = dedent("""\ import pkg.mod4 from pkg import mod4 print(pkg.mod4) print(mod4)""") self.mod1.write(code) self._move(self.mod1, code.index("pkg") + 1, pkg2) self.assertFalse(self.pkg.exists()) self.assertTrue(self.project.find_module("pkg2.pkg.mod4") is not None) self.assertTrue(self.project.find_module("pkg2.pkg.mod5") is not None) expected = dedent("""\ import pkg2.pkg.mod4 from pkg2.pkg import mod4 print(pkg2.pkg.mod4) print(mod4)""") self.assertEqual(expected, self.mod1.read()) def test_moving_package_and_retaining_blank_lines(self): pkg2 = testutils.create_package(self.project, "pkg2", self.pkg) code = dedent('''\ """Docstring followed by blank lines.""" import pkg.mod4 from pkg import mod4 from x import y from y import z from a import b from b import c print(pkg.mod4) print(mod4)''') self.mod1.write(code) self._move(self.mod4, None, pkg2) expected = dedent('''\ """Docstring followed by blank lines.""" import pkg.pkg2.mod4 from x import y from y import z from a import b from b import c from pkg.pkg2 import mod4 print(pkg.pkg2.mod4) print(mod4)''') self.assertEqual(expected, self.mod1.read()) def test_moving_functions_to_imported_module(self): code = dedent("""\ import mod1 def a_func(): var = mod1.a_var """) self.mod1.write("a_var = 1\n") self.mod2.write(code) self._move(self.mod2, code.index("a_func") + 1, self.mod1) expected = dedent("""\ def a_func(): var = a_var a_var = 1 """) self.assertEqual(expected, self.mod1.read()) def test_moving_resources_using_move_module_refactoring(self): self.mod1.write("a_var = 1") self.mod2.write(dedent("""\ import mod1 my_var = mod1.a_var """)) mover = move.create_move(self.project, self.mod1) mover.get_changes(self.pkg).do() expected = dedent("""\ import pkg.mod1 my_var = pkg.mod1.a_var """) self.assertEqual(expected, self.mod2.read()) self.assertTrue(self.pkg.get_child("mod1.py") is not None) def test_moving_resources_using_move_module_for_packages(self): self.mod1.write(dedent("""\ import pkg my_pkg = pkg""")) pkg2 = testutils.create_package(self.project, "pkg2") mover = move.create_move(self.project, self.pkg) mover.get_changes(pkg2).do() expected = dedent("""\ import pkg2.pkg my_pkg = pkg2.pkg""") self.assertEqual(expected, self.mod1.read()) self.assertTrue(pkg2.get_child("pkg") is not None) def test_moving_resources_using_move_module_for_init_dot_py(self): self.mod1.write(dedent("""\ import pkg my_pkg = pkg""")) pkg2 = testutils.create_package(self.project, "pkg2") init = self.pkg.get_child("__init__.py") mover = move.create_move(self.project, init) mover.get_changes(pkg2).do() self.assertEqual( dedent("""\ import pkg2.pkg my_pkg = pkg2.pkg"""), self.mod1.read(), ) self.assertTrue(pkg2.get_child("pkg") is not None) def test_moving_module_and_star_imports(self): self.mod1.write("a_var = 1") self.mod2.write(dedent("""\ from mod1 import * a = a_var """)) mover = move.create_move(self.project, self.mod1) mover.get_changes(self.pkg).do() self.assertEqual( dedent("""\ from pkg.mod1 import * a = a_var """), self.mod2.read(), ) def test_moving_module_and_not_removing_blanks_after_imports(self): self.mod4.write("a_var = 1") self.mod2.write(dedent("""\ from pkg import mod4 import os print(mod4.a_var) """)) mover = move.create_move(self.project, self.mod4) mover.get_changes(self.project.root).do() self.assertEqual( dedent("""\ import os import mod4 print(mod4.a_var) """), self.mod2.read(), ) def test_moving_module_refactoring_and_nonexistent_destinations(self): self.mod4.write("a_var = 1") self.mod2.write(dedent("""\ from pkg import mod4 import os print(mod4.a_var) """)) with self.assertRaises(exceptions.RefactoringError): mover = move.create_move(self.project, self.mod4) mover.get_changes(None).do() def test_moving_methods_choosing_the_correct_class(self): code = dedent("""\ class A(object): def a_method(self): pass """) self.mod1.write(code) mover = move.create_move(self.project, self.mod1, code.index("a_method")) self.assertTrue(isinstance(mover, move.MoveMethod)) def test_moving_methods_getting_new_method_for_empty_methods(self): code = dedent("""\ class A(object): def a_method(self): pass """) self.mod1.write(code) mover = move.create_move(self.project, self.mod1, code.index("a_method")) self.assertEqual( dedent("""\ def new_method(self): pass """), mover.get_new_method("new_method"), ) def test_moving_methods_getting_new_method_for_constant_methods(self): code = dedent("""\ class A(object): def a_method(self): return 1 """) self.mod1.write(code) mover = move.create_move(self.project, self.mod1, code.index("a_method")) self.assertEqual( dedent("""\ def new_method(self): return 1 """), mover.get_new_method("new_method"), ) def test_moving_methods_getting_new_method_passing_simple_parameters(self): code = dedent("""\ class A(object): def a_method(self, p): return p """) self.mod1.write(code) mover = move.create_move(self.project, self.mod1, code.index("a_method")) self.assertEqual( dedent("""\ def new_method(self, p): return p """), mover.get_new_method("new_method"), ) def test_moving_methods_getting_new_method_using_main_object(self): code = dedent("""\ class A(object): attr = 1 def a_method(host): return host.attr """) self.mod1.write(code) mover = move.create_move(self.project, self.mod1, code.index("a_method")) self.assertEqual( dedent("""\ def new_method(self, host): return host.attr """), mover.get_new_method("new_method"), ) def test_moving_methods_getting_new_method_renaming_main_object(self): code = dedent("""\ class A(object): attr = 1 def a_method(self): return self.attr """) self.mod1.write(code) mover = move.create_move(self.project, self.mod1, code.index("a_method")) self.assertEqual( dedent("""\ def new_method(self, host): return host.attr """), mover.get_new_method("new_method"), ) def test_moving_methods_gettin_new_method_with_keyword_arguments(self): code = dedent("""\ class A(object): attr = 1 def a_method(self, p=None): return p """) self.mod1.write(code) mover = move.create_move(self.project, self.mod1, code.index("a_method")) self.assertEqual( dedent("""\ def new_method(self, p=None): return p """), mover.get_new_method("new_method"), ) def test_moving_methods_gettin_new_method_with_many_kinds_arguments(self): code = dedent("""\ class A(object): attr = 1 def a_method(self, p1, *args, **kwds): return self.attr """) self.mod1.write(code) mover = move.create_move(self.project, self.mod1, code.index("a_method")) expected = dedent("""\ def new_method(self, host, p1, *args, **kwds): return host.attr """) self.assertEqual(expected, mover.get_new_method("new_method")) def test_moving_methods_getting_new_method_for_multi_line_methods(self): code = dedent("""\ class A(object): def a_method(self): a = 2 return a """) self.mod1.write(code) mover = move.create_move(self.project, self.mod1, code.index("a_method")) self.assertEqual( dedent("""\ def new_method(self): a = 2 return a """), mover.get_new_method("new_method"), ) def test_moving_methods_getting_old_method_for_constant_methods(self): self.mod2.write("class B(object):\n pass\n") code = dedent("""\ import mod2 class A(object): attr = mod2.B() def a_method(self): return 1 """) self.mod1.write(code) mover = move.create_move(self.project, self.mod1, code.index("a_method")) mover.get_changes("attr", "new_method").do() expected = dedent("""\ import mod2 class A(object): attr = mod2.B() def a_method(self): return self.attr.new_method() """) self.assertEqual(expected, self.mod1.read()) def test_moving_methods_getting_getting_changes_for_goal_class(self): self.mod2.write("class B(object):\n var = 1\n") code = dedent("""\ import mod2 class A(object): attr = mod2.B() def a_method(self): return 1 """) self.mod1.write(code) mover = move.create_move(self.project, self.mod1, code.index("a_method")) mover.get_changes("attr", "new_method").do() expected = dedent("""\ class B(object): var = 1 def new_method(self): return 1 """) self.assertEqual(expected, self.mod2.read()) def test_moving_methods_getting_getting_changes_for_goal_class2(self): code = dedent("""\ class B(object): var = 1 class A(object): attr = B() def a_method(self): return 1 """) self.mod1.write(code) mover = move.create_move(self.project, self.mod1, code.index("a_method")) mover.get_changes("attr", "new_method").do() self.assertEqual( dedent("""\ class B(object): var = 1 def new_method(self): return 1 class A(object): attr = B() def a_method(self): return self.attr.new_method() """), self.mod1.read(), ) def test_moving_methods_and_nonexistent_attributes(self): code = dedent("""\ class A(object): def a_method(self): return 1 """) self.mod1.write(code) with self.assertRaises(exceptions.RefactoringError): mover = move.create_move(self.project, self.mod1, code.index("a_method")) mover.get_changes("x", "new_method") def test_unknown_attribute_type(self): code = dedent("""\ class A(object): attr = 1 def a_method(self): return 1 """) self.mod1.write(code) with self.assertRaises(exceptions.RefactoringError): mover = move.create_move(self.project, self.mod1, code.index("a_method")) mover.get_changes("attr", "new_method") def test_moving_methods_and_moving_used_imports(self): self.mod2.write("class B(object):\n var = 1\n") code = dedent("""\ import sys import mod2 class A(object): attr = mod2.B() def a_method(self): return sys.version """) self.mod1.write(code) mover = move.create_move(self.project, self.mod1, code.index("a_method")) mover.get_changes("attr", "new_method").do() code = dedent("""\ import sys class B(object): var = 1 def new_method(self): return sys.version """) self.assertEqual(code, self.mod2.read()) def test_moving_methods_getting_getting_changes_for_goal_class3(self): self.mod2.write(dedent("""\ class B(object): pass """)) code = dedent("""\ import mod2 class A(object): attr = mod2.B() def a_method(self): return 1 """) self.mod1.write(code) mover = move.create_move(self.project, self.mod1, code.index("a_method")) mover.get_changes("attr", "new_method").do() expected = dedent("""\ class B(object): def new_method(self): return 1 """) self.assertEqual(expected, self.mod2.read()) def test_moving_methods_and_source_class_with_parameters(self): self.mod2.write("class B(object):\n pass\n") code = dedent("""\ import mod2 class A(object): attr = mod2.B() def a_method(self, p): return p """) self.mod1.write(code) mover = move.create_move(self.project, self.mod1, code.index("a_method")) mover.get_changes("attr", "new_method").do() expected1 = dedent("""\ import mod2 class A(object): attr = mod2.B() def a_method(self, p): return self.attr.new_method(p) """) self.assertEqual(expected1, self.mod1.read()) expected2 = dedent("""\ class B(object): def new_method(self, p): return p """) self.assertEqual(expected2, self.mod2.read()) def test_moving_globals_to_a_module_with_only_docstrings(self): self.mod1.write(dedent("""\ import sys def f(): print(sys.version) """)) self.mod2.write(dedent('''\ """doc More docs ... """ ''')) mover = move.create_move( self.project, self.mod1, self.mod1.read().index("f()") + 1 ) self.project.do(mover.get_changes(self.mod2)) self.assertEqual( dedent('''\ """doc More docs ... """ import sys def f(): print(sys.version) '''), self.mod2.read(), ) def test_moving_globals_to_a_module_with_only_docstrings2(self): code = dedent("""\ import os import sys def f(): print(sys.version, os.path) """) self.mod1.write(code) self.mod2.write('"""doc\n\nMore docs ...\n\n"""\n') mover = move.create_move( self.project, self.mod1, self.mod1.read().index("f()") + 1 ) self.project.do(mover.get_changes(self.mod2)) expected = dedent('''\ """doc More docs ... """ import os import sys def f(): print(sys.version, os.path) ''') self.assertEqual(expected, self.mod2.read()) def test_moving_a_global_when_it_is_used_after_a_multiline_str(self): code = dedent('''\ def f(): pass s = """\\ """ r = f() ''') self.mod1.write(code) mover = move.create_move(self.project, self.mod1, code.index("f()") + 1) self.project.do(mover.get_changes(self.mod2)) expected = dedent('''\ import mod2 s = """\\ """ r = mod2.f() ''') self.assertEqual(expected, self.mod1.read()) def test_raising_an_exception_when_moving_non_package_folders(self): dir = self.project.root.create_folder("dir") with self.assertRaises(exceptions.RefactoringError): move.create_move(self.project, dir) def test_moving_to_a_module_with_encoding_cookie(self): code1 = "# -*- coding: utf-8 -*-" self.mod1.write(code1) code2 = dedent("""\ def f(): pass """) self.mod2.write(code2) mover = move.create_move(self.project, self.mod2, code2.index("f()") + 1) self.project.do(mover.get_changes(self.mod1)) expected = f"{code1}\n{code2}" self.assertEqual(expected, self.mod1.read()) def test_moving_decorated_function(self): self.mod1.write(dedent("""\ def hello(func): return func @hello def foo(): pass """)) self._move(self.mod1, self.mod1.read().index("foo") + 1, self.mod2) self.assertEqual("def hello(func):\n return func\n", self.mod1.read()) self.assertEqual( dedent("""\ from mod1 import hello @hello def foo(): pass """), self.mod2.read(), ) def test_moving_decorated_class(self): self.mod1.write(dedent("""\ from dataclasses import dataclass @dataclass class AClass: pass """)) self._move(self.mod1, self.mod1.read().index("AClass") + 1, self.mod2) self.assertEqual("", self.mod1.read()) self.assertEqual( dedent("""\ from dataclasses import dataclass @dataclass class AClass: pass """), self.mod2.read(), ) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/ropetest/refactor/multiprojecttest.py0000664000175000017500000000655214512700666022156 0ustar00lieryanlieryanimport unittest from textwrap import dedent from rope.refactor import move, multiproject, rename from ropetest import testutils class MultiProjectRefactoringTest(unittest.TestCase): def setUp(self): super().setUp() self.project1 = testutils.sample_project(foldername="testproject1") self.project2 = testutils.sample_project(foldername="testproject2") self.mod1 = self.project1.root.create_file("mod1.py") self.other = self.project1.root.create_file("other.py") self.mod2 = self.project2.root.create_file("mod2.py") def tearDown(self): testutils.remove_project(self.project1) testutils.remove_project(self.project2) super().tearDown() def test_trivial_rename(self): self.mod1.write("var = 1\n") refactoring = multiproject.MultiProjectRefactoring(rename.Rename, []) renamer = refactoring(self.project1, self.mod1, 1) multiproject.perform(renamer.get_all_changes("newvar")) self.assertEqual("newvar = 1\n", self.mod1.read()) def test_rename(self): self.mod1.write("var = 1\n") self.mod2.write(dedent("""\ import mod1 myvar = mod1.var """)) refactoring = multiproject.MultiProjectRefactoring( rename.Rename, [self.project2] ) renamer = refactoring(self.project1, self.mod1, 1) multiproject.perform(renamer.get_all_changes("newvar")) self.assertEqual("newvar = 1\n", self.mod1.read()) self.assertEqual( dedent("""\ import mod1 myvar = mod1.newvar """), self.mod2.read(), ) def test_move(self): self.mod1.write(dedent("""\ def a_func(): pass """)) self.mod2.write(dedent("""\ import mod1 myvar = mod1.a_func() """)) refactoring = multiproject.MultiProjectRefactoring( move.create_move, [self.project2] ) renamer = refactoring(self.project1, self.mod1, self.mod1.read().index("_func")) multiproject.perform(renamer.get_all_changes(self.other)) self.assertEqual("", self.mod1.read()) self.assertEqual( dedent("""\ def a_func(): pass """), self.other.read(), ) self.assertEqual( dedent("""\ import other myvar = other.a_func() """), self.mod2.read(), ) def test_rename_from_the_project_not_containing_the_change(self): self.project2.get_prefs().add("python_path", self.project1.address) self.mod1.write("var = 1\n") self.mod2.write(dedent("""\ import mod1 myvar = mod1.var """)) refactoring = multiproject.MultiProjectRefactoring( rename.Rename, [self.project1] ) renamer = refactoring(self.project2, self.mod2, self.mod2.read().rindex("var")) multiproject.perform(renamer.get_all_changes("newvar")) self.assertEqual( dedent("""\ newvar = 1 """), self.mod1.read(), ) self.assertEqual( dedent("""\ import mod1 myvar = mod1.newvar """), self.mod2.read(), ) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1699196919.0 rope-1.12.0/ropetest/refactor/patchedasttest.py0000664000175000017500000016633414521727767021575 0ustar00lieryanlieryanimport itertools import sys import unittest from textwrap import dedent from rope.base import ast from rope.refactor import patchedast from ropetest import testutils NameConstant = "Name" if sys.version_info <= (3, 8) else "NameConstant" class PatchedASTTest(unittest.TestCase): def setUp(self): super().setUp() def tearDown(self): super().tearDown() def assert_single_case_match_block(self, checker, match_type): checker.check_children("Match", [ "match", " ", "Name", "", ":", "\n ", "match_case", ]) checker.check_children("match_case", [ "case", " ", match_type, "", ":", "\n ", "Expr", ]) def test_operator_support_completeness(self): ast_ops = { n.__name__ for n in itertools.chain( ast.boolop.__subclasses__(), ast.cmpop.__subclasses__(), ast.operator.__subclasses__(), ast.unaryop.__subclasses__(), ) } supported_ops = set(patchedast._PatchingASTWalker._operators) assert ast_ops == supported_ops def test_bytes_string(self): source = '1 + b"("\n' ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) str_fragment = 'b"("' start = source.index(str_fragment) checker.check_region("Bytes", start, start + len(str_fragment)) checker.check_children("Bytes", [str_fragment]) def test_integer_literals_and_region(self): source = "a = 10\n" ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) start = source.index("10") checker.check_region("Num", start, start + 2) def test_negative_integer_literals_and_region(self): source = "a = -10\n" ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) start = source.index("-10") + 1 end = start + 2 # Python 3 parses as UnaryOp(op=USub(), operand=Num(n=10)) checker.check_region("Num", start, end) def test_scientific_integer_literals_and_region(self): source = "a = -1.0e-3\n" ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) start = source.index("-1.0e-3") + 1 end = start + 6 # Python 3 parses as UnaryOp(op=USub(), operand=Num(n=10)) checker.check_region("Num", start, end) def test_hex_integer_literals_and_region(self): source = "a = 0x1\n" ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) start = source.index("0x1") checker.check_region("Num", start, start + 3) def test_octal_integer_literals_and_region(self): source = "a = -0o1251\n" ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) start = source.index("-0o1251") + 1 end = start + 6 # Python 3 parses as UnaryOp(op=USub(), operand=Num(n=10)) checker.check_region("Num", start, end) checker.check_children("Num", ["0o1251"]) def test_integer_literals_and_sorted_children(self): source = "a = 10\n" ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_children("Num", ["10"]) def test_ellipsis(self): source = "a[...]\n" ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) start = source.index("...") checker.check_region("Ellipsis", start, start + len("...")) def test_ass_name_node(self): source = "a = 10\n" ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) start = source.index("a") checker.check_region("Name", start, start + 1) checker.check_children("Name", ["a"]) def test_assign_node(self): source = "a = 10\n" ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_region("Assign", 0, len(source) - 1) 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) 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) 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) checker = _ResultChecker(self, ast_frag) checker.check_region("BinOp", 0, len(source) - 1) checker.check_children("BinOp", ["Num", " ", "+", " ", "Num"]) def test_lshift_node(self): source = "1 << 2\n" ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_region("BinOp", 0, len(source) - 1) checker.check_children("BinOp", ["Num", " ", "<<", " ", "Num"]) def test_and_node(self): source = "True and True\n" ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_region("BoolOp", 0, len(source) - 1) checker.check_children("BoolOp", [NameConstant, " ", "and", " ", NameConstant]) def test_matmult_node(self): source = "a @ b\n" ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_children("BinOp", ["Name", " ", "@", " ", "Name"]) def test_basic_closing_parens(self): source = "1 + (2)\n" ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_region("BinOp", 0, len(source) - 1) checker.check_children("BinOp", ["Num", " ", "+", " (", "Num", ")"]) def test_basic_opening_parens(self): source = "(1) + 2\n" ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_region("BinOp", 0, len(source) - 1) checker.check_children("BinOp", ["(", "Num", ") ", "+", " ", "Num"]) def test_basic_opening_biway(self): source = "(1) + (2)\n" ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_region("BinOp", 0, len(source) - 1) checker.check_children("BinOp", ["(", "Num", ") ", "+", " (", "Num", ")"]) def test_basic_opening_double(self): source = "1 + ((2))\n" ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_region("BinOp", 0, len(source) - 1) checker.check_children("BinOp", ["Num", " ", "+", " ((", "Num", "))"]) def test_handling_comments(self): source = "(1 + #(\n2)\n" ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_children("BinOp", ["Num", " ", "+", " #(\n", "Num"]) def test_handling_parens_with_spaces(self): source = "1 + (2\n )\n" ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_children("BinOp", ["Num", " ", "+", " (", "Num", "\n )"]) def test_handling_strings(self): source = '1 + "("\n' ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_children("BinOp", ["Num", " ", "+", " ", "Str"]) def test_handling_fstrings(self): source = '1 + f"("\n' ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_children("BinOp", ["Num", " ", "+", " ", "JoinedStr"]) def test_handling_implicit_string_concatenation(self): source = "a = '1''2'" ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_children("Assign", ["Name", " ", "=", " ", "Str"]) checker.check_children("Str", ["'1''2'"]) def test_handling_implicit_string_concatenation_line_breaks(self): source = dedent("""\ a = '1' \\ '2'""") ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_children("Assign", ["Name", " ", "=", " ", "Str"]) checker.check_children("Str", ["'1' \\\n'2'"]) def test_handling_explicit_string_concatenation_line_breaks(self): source = "a = ('1' \n'2')" ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_children("Assign", ["Name", " ", "=", " (", "Str", ")"]) checker.check_children("Str", ["'1' \n'2'"]) def test_not_concatenating_strings_on_separate_lines(self): source = dedent("""\ '1' '2' """) ast_frag = patchedast.get_patched_ast(source, True) 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", "", "}"]) def test_complex_number_literals(self): source = "1.0e2j + a" ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_children("BinOp", ["Num", " ", "+", " ", "Name"]) checker.check_children("Num", ["1.0e2j"]) def test_ass_attr_node(self): source = "a.b = 1\n" ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_region("Attribute", 0, source.index("=") - 1) checker.check_children("Attribute", ["Name", "", ".", "", "b"]) def test_ass_list_node(self): source = "[a, b] = 1, 2\n" ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_region("List", 0, source.index("]") + 1) checker.check_children("List", ["[", "", "Name", "", ",", " ", "Name", "", "]"]) def test_ass_tuple(self): source = "a, b = range(2)\n" ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_region("Tuple", 0, source.index("=") - 1) checker.check_children("Tuple", ["Name", "", ",", " ", "Name"]) def test_ass_tuple2(self): source = "(a, b) = range(2)\n" ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_region("Tuple", 0, source.index("=") - 1) checker.check_children( "Tuple", ["(", "", "Name", "", ",", " ", "Name", "", ")"] ) def test_assert(self): source = "assert True\n" ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_region("Assert", 0, len(source) - 1) checker.check_children("Assert", ["assert", " ", NameConstant]) def test_assert2(self): source = 'assert True, "error"\n' ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_region("Assert", 0, len(source) - 1) checker.check_children( "Assert", ["assert", " ", NameConstant, "", ",", " ", "Str"] ) def test_aug_assign_node(self): source = "a += 1\n" ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_region("AugAssign", 0, len(source) - 1) checker.check_children("AugAssign", ["Name", " ", "+", "", "=", " ", "Num"]) def test_bitand(self): source = "1 & 2\n" ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_region("BinOp", 0, len(source) - 1) checker.check_children("BinOp", ["Num", " ", "&", " ", "Num"]) def test_bitor(self): source = "1 | 2\n" ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_children("BinOp", ["Num", " ", "|", " ", "Num"]) def test_call_func(self): source = "f(1, 2)\n" ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_region("Call", 0, len(source) - 1) checker.check_children( "Call", ["Name", "", "(", "", "Num", "", ",", " ", "Num", "", ")"] ) def test_call_func_and_keywords(self): source = "f(1, p=2)\n" ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_children( "Call", ["Name", "", "(", "", "Num", "", ",", " ", "keyword", "", ")"] ) @testutils.only_for_versions_lower("3.5") def test_call_func_and_star_args(self): source = "f(1, *args)\n" ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_children( "Call", ["Name", "", "(", "", "Num", "", ",", " ", "*", "", "Name", "", ")"] ) def test_call_func_and_star_argspython35(self): source = "f(1, *args)\n" ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_children( "Call", ["Name", "", "(", "", "Num", "", ",", " *", "Starred", "", ")"] ) @testutils.only_for_versions_lower("3.5") def test_call_func_and_only_dstar_args(self): source = "f(**kwds)\n" ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_children("Call", ["Name", "", "(", "", "**", "", "Name", "", ")"]) def test_call_func_and_only_dstar_args_python35(self): source = "f(**kwds)\n" ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_children("Call", ["Name", "", "(", "**", "keyword", "", ")"]) @testutils.only_for_versions_lower("3.5") def test_call_func_and_both_varargs_and_kwargs(self): source = "f(*args, **kwds)\n" ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_children( "Call", ["Name", "", "(", "", "*", "", "Name", "", ",", " ", "**", "", "Name", "", ")"], ) def test_call_func_and_both_varargs_and_kwargs_python35(self): source = "f(*args, **kwds)\n" ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_children( "Call", ["Name", "", "(", "*", "Starred", "", ",", " **", "keyword", "", ")"], ) def test_class_node(self): source = dedent('''\ class A(object): """class docs""" pass ''') ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_region("Class", 0, len(source) - 1) checker.check_children( "Class", ["class", " ", "A", "", "(", "", "Name", "", ")", "", ":", "\n ", "Expr", "\n ", "Pass"], ) def test_class_with_no_bases(self): source = dedent("""\ class A: pass """) ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_region("Class", 0, len(source) - 1) checker.check_children("Class", ["class", " ", "A", "", ":", "\n ", "Pass"]) def test_simple_compare(self): source = "1 < 2\n" ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_region("Compare", 0, len(source) - 1) checker.check_children("Compare", ["Num", " ", "<", " ", "Num"]) def test_multiple_compare(self): source = "1 < 2 <= 3\n" ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_region("Compare", 0, len(source) - 1) checker.check_children( "Compare", ["Num", " ", "<", " ", "Num", " ", "<=", " ", "Num"] ) def test_decorators_node(self): source = dedent("""\ @d def f(): pass """) ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_region("FunctionDef", 0, len(source) - 1) checker.check_children( "FunctionDef", ["@", "", "Name", "\n", "def", " ", "f", "", "(", "", "arguments", "", ")", "", ":", "\n ", "Pass"], ) def test_decorators_for_classes(self): source = dedent("""\ @d class C(object): pass """) ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_region("ClassDef", 0, len(source) - 1) checker.check_children( "ClassDef", ["@", "", "Name", "\n", "class", " ", "C", "", "(", "", "Name", "", ")", "", ":", "\n ", "Pass"], ) def test_both_varargs_and_kwargs(self): source = dedent("""\ def f(*args, **kwds): pass """) ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_children( "arguments", ["*", "", "args", "", ",", " ", "**", "", "kwds"] ) def test_function_node(self): source = dedent("""\ def f(): pass """) ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_region("Function", 0, len(source) - 1) checker.check_children( "Function", ["def", " ", "f", "", "(", "", "arguments", "", ")", "", ":", "\n ", "Pass"], ) @testutils.only_for_versions_higher("3.5") def test_async_function_node(self): source = dedent("""\ async def f(): pass """) ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_region("AsyncFunction", 0, len(source) - 1) checker.check_children( "AsyncFunction", ["async", " ", "def", " ", "f", "", "(", "", "arguments", "", ")", "", ":", "\n ", "Pass"], ) def test_function_node2(self): source = dedent('''\ def f(p1, **p2): """docs""" pass ''') ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_region("Function", 0, len(source) - 1) checker.check_children( "Function", ["def", " ", "f", "", "(", "", "arguments", "", ")", "", ":", "\n ", "Expr", "\n ", "Pass"], ) expected_child = ast.arg.__name__ checker.check_children( "arguments", [expected_child, "", ",", " ", "**", "", "p2"] ) def test_dict_node(self): source = "{1: 2, 3: 4}\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", ["{", "", "Num", "", ":", " ", "Num", "", ",", " ", "Num", "", ":", " ", "Num", "", "}"], ) 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) checker = _ResultChecker(self, ast_frag) checker.check_region("BinOp", 0, len(source) - 1) checker.check_children("BinOp", ["Num", " ", "/", " ", "Num"]) def test_for_node(self): source = dedent("""\ for i in range(1): pass else: pass """) ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_region("For", 0, len(source) - 1) checker.check_children( "For", ["for", " ", "Name", " ", "in", " ", "Call", "", ":", "\n ", "Pass", "\n", "else", "", ":", "\n ", "Pass"], ) @testutils.only_for_versions_higher("3.5") def test_async_for_node(self): source = dedent("""\ async def foo(): async for i in range(1): pass else: pass """) ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_region("AsyncFor", source.index("async for"), len(source) - 1) checker.check_children( "AsyncFor", ["async", " ", "for", " ", "Name", " ", "in", " ", "Call", "", ":", "\n ", "Pass", "\n ", "else", "", ":", "\n ", "Pass"], ) @testutils.only_for_versions_higher("3.8") def test_named_expr_node(self): source = dedent("""\ if a := 10 == 10: pass """) 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) checker = _ResultChecker(self, ast_frag) checker.check_region("ImportFrom", 0, len(source) - 1) checker.check_children( "ImportFrom", ["from", " ", "x", " ", "import", " ", "alias"] ) checker.check_children("alias", ["y"]) def test_from_node(self): source = "from ..x import y as z\n" ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_region("ImportFrom", 0, len(source) - 1) checker.check_children( "ImportFrom", ["from", " ", ".", "", ".", "", "x", " ", "import", " ", "alias"] ) checker.check_children("alias", ["y", " ", "as", " ", "z"]) def test_from_node_relative_import(self): source = "from . import y as z\n" ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_region("ImportFrom", 0, len(source) - 1) checker.check_children( "ImportFrom", ["from", " ", ".", " ", "import", " ", "alias"] ) checker.check_children("alias", ["y", " ", "as", " ", "z"]) def test_from_node_whitespace_around_dots_1(self): source = "from . . . import y as z\n" ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_region("ImportFrom", 0, len(source) - 1) checker.check_children( "ImportFrom", ["from", " ", ".", " ", ".", " ", ".", " ", "import", " ", "alias"] ) checker.check_children("alias", ["y", " ", "as", " ", "z"]) def test_from_node_whitespace_around_dots_2(self): source = "from . a . b import y as z\n" ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_region("ImportFrom", 0, len(source) - 1) checker.check_children( "ImportFrom", ["from", " ", ".", " ", "a", " . ", "b", " ", "import", " ", "alias"] ) checker.check_children("alias", ["y", " ", "as", " ", "z"]) def test_simple_gen_expr_node(self): source = "zip(i for i in x)\n" ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_region("GeneratorExp", 4, len(source) - 2) checker.check_children("GeneratorExp", ["Name", " ", "comprehension"]) checker.check_children( "comprehension", ["for", " ", "Name", " ", "in", " ", "Name"] ) def test_gen_expr_node_handling_surrounding_parens(self): source = "(i for i in x)\n" ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_region("GeneratorExp", 0, len(source) - 1) checker.check_children( "GeneratorExp", ["(", "", "Name", " ", "comprehension", "", ")"] ) def test_gen_expr_node2(self): source = "zip(i for i in range(1) if i == 1)\n" ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_children( "comprehension", ["for", " ", "Name", " ", "in", " ", "Call", " ", "if", " ", "Compare"], ) def test_get_attr_node(self): source = "a.b\n" ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_region("Attribute", 0, len(source) - 1) checker.check_children("Attribute", ["Name", "", ".", "", "b"]) def test_global_node(self): source = "global a, b\n" ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_region("Global", 0, len(source) - 1) checker.check_children("Global", ["global", " ", "a", "", ",", " ", "b"]) def test_nonlocal_node(self): source = "nonlocal a, b\n" ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_region("Nonlocal", 0, len(source) - 1) checker.check_children("Nonlocal", ["nonlocal", " ", "a", "", ",", " ", "b"]) def test_if_node(self): source = "if True:\n pass\nelse:\n pass\n" ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_region("If", 0, len(source) - 1) checker.check_children( "If", ["if", " ", NameConstant, "", ":", "\n ", "Pass", "\n", "else", "", ":", "\n ", "Pass"], ) def test_if_node2(self): source = dedent("""\ if True: pass elif False: pass """) ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_region("If", 0, len(source) - 1) checker.check_children( "If", ["if", " ", NameConstant, "", ":", "\n ", "Pass", "\n", "If"] ) def test_if_node3(self): source = dedent("""\ if True: pass else: if True: pass """) ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_region("If", 0, len(source) - 1) checker.check_children( "If", ["if", " ", NameConstant, "", ":", "\n ", "Pass", "\n", "else", "", ":", "\n ", "If"], ) def test_import_node(self): source = "import a, b as c\n" ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_region("Import", 0, len(source) - 1) checker.check_children( "Import", ["import", " ", "alias", "", ",", " ", "alias"] ) def test_import_node_whitespace_around_dots(self): source = "import a . b, b as c\n" ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_region("Import", 0, len(source) - 1) checker.check_children( "Import", ["import", " ", "alias", "", ",", " ", "alias"] ) def test_lambda_node(self): source = "lambda a, b=1, *z: None\n" ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_region("Lambda", 0, len(source) - 1) checker.check_children( "Lambda", ["lambda", " ", "arguments", "", ":", " ", NameConstant] ) expected_child = ast.arg.__name__ checker.check_children( "arguments", [expected_child, "", ",", " ", expected_child, "", "=", "", "Num", "", ",", " ", "*", "", "z"], ) def test_list_node(self): source = "[1, 2]\n" ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_region("List", 0, len(source) - 1) checker.check_children("List", ["[", "", "Num", "", ",", " ", "Num", "", "]"]) def test_list_comp_node(self): source = "[i for i in range(1) if True]\n" ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_region("ListComp", 0, len(source) - 1) checker.check_children( "ListComp", ["[", "", "Name", " ", "comprehension", "", "]"] ) checker.check_children( "comprehension", ["for", " ", "Name", " ", "in", " ", "Call", " ", "if", " ", NameConstant], ) def test_list_comp_node_with_multiple_comprehensions(self): source = "[i for i in range(1) for j in range(1) if True]\n" ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_region("ListComp", 0, len(source) - 1) checker.check_children( "ListComp", ["[", "", "Name", " ", "comprehension", " ", "comprehension", "", "]"], ) checker.check_children( "comprehension", ["for", " ", "Name", " ", "in", " ", "Call", " ", "if", " ", NameConstant], ) def test_set_node(self): source = "{1, 2}\n" ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_region("Set", 0, len(source) - 1) checker.check_children("Set", ["{", "", "Num", "", ",", " ", "Num", "", "}"]) def test_set_comp_node(self): source = "{i for i in range(1) if True}\n" ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_region("SetComp", 0, len(source) - 1) checker.check_children( "SetComp", ["{", "", "Name", " ", "comprehension", "", "}"] ) checker.check_children( "comprehension", ["for", " ", "Name", " ", "in", " ", "Call", " ", "if", " ", NameConstant], ) def test_dict_comp_node(self): source = "{i:i for i in range(1) if True}\n" ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_region("DictComp", 0, len(source) - 1) checker.check_children( "DictComp", ["{", "", "Name", "", ":", "", "Name", " ", "comprehension", "", "}"], ) checker.check_children( "comprehension", ["for", " ", "Name", " ", "in", " ", "Call", " ", "if", " ", NameConstant], ) def test_ext_slice_node(self): source = "x = xs[0,:]\n" ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) 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" ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_region("Module", 0, len(source)) checker.check_children("Module", ["", "Pass", "\n"]) def test_module_node(self): source = dedent('''\ """docs""" pass ''') ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_region("Module", 0, len(source)) checker.check_children("Module", ["", "Expr", "\n", "Pass", "\n"]) checker.check_children("Str", ['"""docs"""']) def test_not_and_or_nodes(self): source = "not True or False\n" ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_children("Expr", ["BoolOp"]) checker.check_children("BoolOp", ["UnaryOp", " ", "or", " ", NameConstant]) def test_raise_node_bare(self): source = "raise\n" ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_region("Raise", 0, len(source) - 1) checker.check_children("Raise", ["raise"]) def test_raise_node_for_python3(self): source = "raise x(y)\n" ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_region("Raise", 0, len(source) - 1) checker.check_children("Raise", ["raise", " ", "Call"]) def test_raise_node_for_python3_with_cause(self): source = "raise x(y) from e\n" ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_region("Raise", 0, len(source) - 1) checker.check_children("Raise", ["raise", " ", "Call", " ", "from", " ", "Name"]) def test_return_node(self): source = dedent("""\ def f(): return None """) ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_children("Return", ["return", " ", NameConstant]) def test_empty_return_node(self): source = dedent("""\ def f(): return """) ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_children("Return", ["return"]) def test_simple_slice_node(self): source = "a[1:2]\n" ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_children("Subscript", ["Name", "", "[", "", "Slice", "", "]"]) checker.check_children("Slice", ["Num", "", ":", "", "Num"]) def test_slice_node2(self): source = "a[:]\n" ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_children("Subscript", ["Name", "", "[", "", "Slice", "", "]"]) checker.check_children("Slice", [":"]) def test_simple_subscript(self): source = "a[1]\n" ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) 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" ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_children("Tuple", ["(", "", "Num", "", ",", " ", "Num", "", ")"]) def test_tuple_node2(self): source = "#(\n1, 2\n" ast_frag = patchedast.get_patched_ast(source, True) 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) checker = _ResultChecker(self, ast_frag) checker.check_children("Tuple", ["(", "", "Num", ",", ")"]) def test_empty_tuple_node(self): source = "()\n" ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) 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("""\ def f(): yield None """) ast_frag = patchedast.get_patched_ast(source, True) 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: pass else: pass """) ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_children( "While", ["while", " ", NameConstant, "", ":", "\n ", "Pass", "\n", "else", "", ":", "\n ", "Pass"], ) def test_with_node(self): source = dedent("""\ from __future__ import with_statement with a as b: pass """) ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_children( "With", ["with", " ", "Name", " ", "as", " ", "Name", "", ":", "\n ", "Pass"], ) def test_async_with_node(self): source = dedent("""\ async def afunc(): async with a as b: pass\n """) ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_children( "AsyncWith", ["async", " ", "with", " ", "Name", " ", "as", " ", "Name", "", ":", "\n ", "Pass"], ) def test_try_finally_node(self): source = dedent("""\ try: pass finally: pass """) ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) node_to_test = "Try" expected_children = ["try", "", ":", "\n ", "Pass", "\n", "finally", "", ":", "\n ", "Pass"] checker.check_children(node_to_test, expected_children) def test_try_except_node(self): source = dedent("""\ try: pass except Exception as e: pass """) ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) node_to_test = "Try" checker.check_children( node_to_test, ["try", "", ":", "\n ", "Pass", "\n", ("excepthandler", "ExceptHandler")], ) expected_child = "e" checker.check_children( ("excepthandler", "ExceptHandler"), ["except", " ", "Name", " ", "as", " ", expected_child, "", ":", "\n ", "Pass"], ) def test_try_except_and_finally_node(self): source = dedent("""\ try: pass except: pass finally: pass """) ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) node_to_test = "Try" expected_children = ["try", "", ":", "\n ", "Pass", "\n", "ExceptHandler", "\n", "finally", "", ":", "\n ", "Pass"] checker.check_children(node_to_test, expected_children) @testutils.only_for_versions_higher("3.11") def test_try_except_group_node(self): source = dedent("""\ try: pass except* (ValueError, IOError) as e: pass except* ZeroDivisionError as e: pass """) ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_children( "TryStar", ["try", "", ":", "\n ", "Pass", "\n", "ExceptHandler", "\n", "ExceptHandler"], ) expected_child = "e" checker.check_children( "ExceptHandler", ["except", "* ", "Name", " ", "as", " ", expected_child, "", ":", "\n ", "Pass"], ) def test_ignoring_comments(self): source = "#1\n1\n" ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) start = source.rindex("1") checker.check_region("Num", start, start + 1) def test_simple_sliceobj(self): source = "a[1::3]\n" ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_children("Slice", ["Num", "", ":", "", ":", "", "Num"]) def test_ignoring_strings_that_start_with_a_char(self): source = 'r"""("""\n1\n' ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_children("Module", ["", "Expr", "\n", "Expr", "\n"]) def test_semicolon(self): source = "1;\n" patchedast.get_patched_ast(source, True) def test_if_exp_node(self): source = "1 if True else 2\n" ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_children( "IfExp", ["Num", " ", "if", " ", NameConstant, " ", "else", " ", "Num"] ) def test_delete_node(self): source = "del a, b\n" ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_children("Delete", ["del", " ", "Name", "", ",", " ", "Name"]) @testutils.only_for_versions_lower("3.5") def test_starargs_before_keywords_legacy(self): source = "foo(*args, a=1)\n" ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_children( "Call", ["Name", "", "(", "", "*", "", "Name", "", ",", " ", "keyword", "", ")"], ) @testutils.only_for_versions_lower("3.5") def test_starargs_in_keywords_legacy(self): source = "foo(a=1, *args, b=2)\n" ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_children( "Call", ["Name", "", "(", "", "keyword", "", ",", " ", "*", "", "Name", "", ",", " ", "keyword", "", ")"], ) @testutils.only_for_versions_lower("3.5") def test_starargs_after_keywords_legacy(self): source = "foo(a=1, *args)\n" ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_children( "Call", ["Name", "", "(", "", "keyword", "", ",", " ", "*", "", "Name", "", ")"], ) def test_starargs_before_keywords(self): source = "foo(*args, a=1)\n" ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_children( "Call", ["Name", "", "(", "*", "Starred", "", ",", " ", "keyword", "", ")"] ) def test_starargs_in_keywords(self): source = "foo(a=1, *args, b=2)\n" ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_children( "Call", ["Name", "", "(", "", "keyword", "", ",", " *", "Starred", "", ",", " ", "keyword", "", ")"], ) def test_starargs_in_positional(self): source = "foo(a, *b, c)\n" ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_children( "Call", ["Name", "", "(", "", "Name", "", ",", " *", "Starred", "", ",", " ", "Name", "", ")"], ) def test_starargs_after_keywords(self): source = "foo(a=1, *args)\n" ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_children( "Call", ["Name", "", "(", "", "keyword", "", ",", " *", "Starred", "", ")"] ) @testutils.only_for_versions_higher("3.5") def test_await_node(self): source = dedent("""\ async def f(): await sleep() """) ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_children("Await", ["await", " ", "Call"]) @testutils.only_for_versions_higher("3.10") def test_match_node_with_constant_match_value(self): source = dedent("""\ match x: case 1: print(x) """) ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) self.assert_single_case_match_block(checker, "MatchValue") checker.check_children("MatchValue", [ "Num" ]) @testutils.only_for_versions_higher("3.10") def test_match_node_match_case_with_guard(self): source = dedent("""\ match x: case int(n) if x < 10: print(n) """) ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) checker.check_children("Match", [ "match", " ", "Name", "", ":", "\n ", "match_case", ]) checker.check_children("match_case", [ "case", " ", "MatchClass", " ", "if", " ", "Compare", "", ":", "\n ", "Expr", ]) @testutils.only_for_versions_higher("3.10") def test_match_node_with_match_class(self): source = dedent("""\ match x: case Foo(1): print(x) """) ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) self.assert_single_case_match_block(checker, "MatchClass") checker.check_children("MatchClass", [ "Name", "", "(", "", "MatchValue", "", ")", ]) checker.check_children("MatchValue", [ "Num" ]) @testutils.only_for_versions_higher("3.10") def test_match_node_with_wildcard(self): source = dedent("""\ match x: case _: print(x) """) ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) self.assert_single_case_match_block(checker, "MatchAs") checker.check_children("MatchAs", [ "_" ]) @testutils.only_for_versions_higher("3.10") def test_match_node_with_match_as_capture_pattern(self): source = dedent("""\ match x: case myval: print(myval) """) ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) self.assert_single_case_match_block(checker, "MatchAs") checker.check_children("MatchAs", [ "myval" ]) @testutils.only_for_versions_higher("3.10") def test_match_node_with_match_as_capture_pattern_with_explicit_name(self): source = dedent("""\ match x: case "foo" as myval: print(myval) """) ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) self.assert_single_case_match_block(checker, "MatchAs") checker.check_children("MatchAs", [ "MatchValue", " ", "as", " ", "myval", ]) @testutils.only_for_versions_higher("3.10") def test_match_node_with_match_class_simple_match_as_capture_pattern(self): source = dedent("""\ match x: case Foo(x): print(x) """) ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) self.assert_single_case_match_block(checker, "MatchClass") checker.check_children("MatchClass", [ "Name", "", "(", "", "MatchAs", "", ")", ]) @testutils.only_for_versions_higher("3.10") def test_match_node_with_match_class_named_argument(self): source = dedent("""\ match x: case Foo(x=10, y="20"): print(x) """) ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) self.assert_single_case_match_block(checker, "MatchClass") checker.check_children("MatchClass", [ "Name", "", "(", "", "x", "", "=", "", "MatchValue", "", ",", " ", "y", "", "=", "", "MatchValue", "", ")", ]) @testutils.only_for_versions_higher("3.10") def test_match_node_with_match_class_match_as_capture_pattern_with_explicit_name(self): source = dedent("""\ match x: case Foo(x) as b: print(x) """) ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) self.assert_single_case_match_block(checker, "MatchAs") checker.check_children("MatchAs", [ "MatchClass", " ", "as", " ", "b", ]) checker.check_children("MatchClass", [ "Name", "", "(", "", "MatchAs", "", ")", ]) @testutils.only_for_versions_higher("3.10") def test_match_node_with_match_mapping_match_as(self): source = dedent("""\ match x: case {"a": b} as c: print(x) """) ast_frag = patchedast.get_patched_ast(source, True) checker = _ResultChecker(self, ast_frag) self.assert_single_case_match_block(checker, "MatchAs") checker.check_children("MatchAs", [ "MatchMapping", " ", "as", " ", "c", ]) checker.check_children("MatchMapping", [ "{", "", "Str", "", ":", " ", "MatchAs", "", "}", ]) class _ResultChecker: def __init__(self, test_case, ast): self.test_case = test_case self.ast = ast def check_region(self, text, start, end): node = self._find_node(text) if node is None: self.test_case.fail("Node <%s> cannot be found" % text) self.test_case.assertEqual((start, end), node.region) def _find_node(self, text): """ Find the node in `self.ast` whose type is named in `text`. :param text: ast node name Generally, the test should only have a single matching node, as it make the test much harder to understand when there may be multiple matches. If `self.ast` contains more than one nodes that matches `text`, then the **outer-most last match** takes precedence. For example, given that we are looking for `ast.Call` node: checker._find_node("Call") and given that `self.ast` is the AST for this code: func_a(1, func_b(2, 3)) + func_c(4, func_d(5, 6)) the outer-most last match would be the ast node representing this bit: func_c(4, func_d(5, 6)) Note that the order of traversal is based on the order of ast nodes, which usually, but not always, match textual order. """ goal = text if not isinstance(text, (tuple, list)): goal = [text] class Search: result = None def __call__(self, node): for text in goal: if str(node).startswith(text): self.result = node break if ast.get_node_type_name(node).startswith(text): self.result = node break return self.result is not None search = Search() ast.call_for_nodes(self.ast, search) return search.result def check_children(self, text, children): node = self._find_node(text) if node is None: self.test_case.fail("Node <%s> cannot be found" % text) result = list(node.sorted_children) self.test_case.assertEqual(len(children), len(result)) for expected, child in zip(children, result): goals = expected if not isinstance(expected, (tuple, list)): goals = [expected] for goal in goals: if goal == "" or isinstance(child, (str, bytes)): self.test_case.assertEqual(goal, child) break else: self.test_case.assertNotEqual("", text, "probably ignoring some node") self.test_case.assertTrue( ast.get_node_type_name(child).startswith(expected), msg="Expected <%s> but was <%s>" % (expected, ast.get_node_type_name(child)), ) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/ropetest/refactor/renametest.py0000664000175000017500000014217714512700666020710 0ustar00lieryanlieryanimport sys import unittest from textwrap import dedent import rope.base.codeanalyze import rope.refactor.occurrences from rope.refactor import rename from rope.refactor.rename import Rename from ropetest import testutils class RenameRefactoringTest(unittest.TestCase): def setUp(self): super().setUp() self.project = testutils.sample_project() def tearDown(self): testutils.remove_project(self.project) super().tearDown() def _local_rename(self, source_code, offset, new_name): testmod = testutils.create_module(self.project, "testmod") testmod.write(source_code) changes = Rename(self.project, testmod, offset).get_changes( new_name, resources=[testmod] ) self.project.do(changes) return testmod.read() def _rename(self, resource, offset, new_name, **kwds): changes = Rename(self.project, resource, offset).get_changes(new_name, **kwds) self.project.do(changes) def test_local_variable_but_not_parameter(self): code = dedent("""\ a = 10 foo = dict(a=a) """) refactored = self._local_rename(code, 1, "new_a") self.assertEqual( dedent("""\ new_a = 10 foo = dict(a=new_a) """), refactored, ) def test_simple_global_variable_renaming(self): refactored = self._local_rename("a_var = 20\n", 2, "new_var") self.assertEqual("new_var = 20\n", refactored) def test_variable_renaming_only_in_its_scope(self): refactored = self._local_rename( dedent("""\ a_var = 20 def a_func(): a_var = 10 """), 32, "new_var", ) self.assertEqual( dedent("""\ a_var = 20 def a_func(): new_var = 10 """), refactored, ) def test_not_renaming_dot_name(self): refactored = self._local_rename( dedent("""\ replace = True 'aaa'.replace('a', 'b') """), 1, "new_var", ) self.assertEqual( dedent("""\ new_var = True 'aaa'.replace('a', 'b') """), refactored, ) def test_renaming_multiple_names_in_the_same_line(self): refactored = self._local_rename( dedent("""\ a_var = 10 a_var = 10 + a_var / 2 """), 2, "new_var", ) self.assertEqual( dedent("""\ new_var = 10 new_var = 10 + new_var / 2 """), refactored, ) def test_renaming_names_when_getting_some_attribute(self): refactored = self._local_rename( dedent("""\ a_var = 'a b c' a_var.split('\\n') """), 2, "new_var", ) self.assertEqual( dedent("""\ new_var = 'a b c' new_var.split('\\n') """), refactored, ) def test_renaming_names_when_getting_some_attribute2(self): refactored = self._local_rename( dedent("""\ a_var = 'a b c' a_var.split('\\n') """), 20, "new_var", ) self.assertEqual( dedent("""\ new_var = 'a b c' new_var.split('\\n') """), refactored, ) def test_renaming_function_parameters1(self): refactored = self._local_rename( dedent("""\ def f(a_param): print(a_param) """), 8, "new_param", ) self.assertEqual( dedent("""\ def f(new_param): print(new_param) """), refactored, ) def test_renaming_function_parameters2(self): refactored = self._local_rename( dedent("""\ def f(a_param): print(a_param) """), 30, "new_param", ) self.assertEqual( dedent("""\ def f(new_param): print(new_param) """), refactored, ) def test_renaming_occurrences_inside_functions(self): code = dedent("""\ def a_func(p1): a = p1 a_func(1) """) refactored = self._local_rename(code, code.index("p1") + 1, "new_param") self.assertEqual( dedent("""\ def a_func(new_param): a = new_param a_func(1) """), 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, ) def test_renaming_comprehension_loop_variables_scope(self): 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( dedent("""\ [new_var for new_var, c_var in d_var if new_var == c_var] b_var = 10 """), 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 = dedent("""\ def a_func(p1=None, p2=None): pass a_func(p2=1) """) refactored = self._local_rename(code, code.index("p2") + 1, "p3") self.assertEqual( dedent("""\ def a_func(p1=None, p3=None): pass a_func(p3=1) """), refactored, ) def test_renaming_function_parameters_of_class_init(self): code = dedent("""\ class A(object): def __init__(self, a_param): pass a_var = A(a_param=1) """) refactored = self._local_rename(code, code.index("a_param") + 1, "new_param") expected = dedent("""\ class A(object): def __init__(self, new_param): pass a_var = A(new_param=1) """) self.assertEqual(expected, refactored) def test_rename_functions_parameters_and_occurrences_in_other_modules(self): mod1 = testutils.create_module(self.project, "mod1") mod2 = testutils.create_module(self.project, "mod2") mod1.write(dedent("""\ def a_func(a_param): print(a_param) """)) mod2.write(dedent("""\ from mod1 import a_func a_func(a_param=10) """)) self._rename(mod1, mod1.read().index("a_param") + 1, "new_param") self.assertEqual( dedent("""\ def a_func(new_param): print(new_param) """), mod1.read(), ) self.assertEqual( dedent("""\ from mod1 import a_func a_func(new_param=10) """), mod2.read(), ) def test_renaming_with_backslash_continued_names(self): refactored = self._local_rename( "replace = True\n'ali'.\\\nreplace\n", 2, "is_replace" ) self.assertEqual("is_replace = True\n'ali'.\\\nreplace\n", refactored) def test_renaming_occurrence_in_f_string(self): code = dedent("""\ a_var = 20 a_string=f'value: {a_var}' """) expected = dedent("""\ new_var = 20 a_string=f'value: {new_var}' """) refactored = self._local_rename(code, 2, "new_var") self.assertEqual(expected, refactored) def test_renaming_occurrence_in_nested_f_string(self): code = dedent("""\ a_var = 20 a_string=f'{f"{a_var}"}' """) expected = dedent("""\ new_var = 20 a_string=f'{f"{new_var}"}' """) refactored = self._local_rename(code, 2, "new_var") self.assertEqual(expected, refactored) def test_not_renaming_string_contents_in_f_string(self): refactored = self._local_rename( "a_var = 20\na_string=f'{\"a_var\"}'\n", 2, "new_var" ) self.assertEqual( dedent("""\ new_var = 20 a_string=f'{"a_var"}' """), refactored, ) def test_not_renaming_string_contents(self): refactored = self._local_rename("a_var = 20\na_string='a_var'\n", 2, "new_var") self.assertEqual( dedent("""\ new_var = 20 a_string='a_var' """), refactored, ) def test_not_renaming_comment_contents(self): refactored = self._local_rename("a_var = 20\n# a_var\n", 2, "new_var") self.assertEqual( dedent("""\ new_var = 20 # a_var """), refactored, ) def test_renaming_all_occurrences_in_containing_scope(self): code = dedent("""\ if True: a_var = 1 else: a_var = 20 """) refactored = self._local_rename(code, 16, "new_var") self.assertEqual( dedent("""\ if True: new_var = 1 else: new_var = 20 """), refactored, ) def test_renaming_a_variable_with_arguement_name(self): code = dedent("""\ a_var = 10 def a_func(a_var): print(a_var) """) refactored = self._local_rename(code, 1, "new_var") self.assertEqual( dedent("""\ new_var = 10 def a_func(a_var): print(a_var) """), refactored, ) def test_renaming_an_arguement_with_variable_name(self): code = dedent("""\ a_var = 10 def a_func(a_var): print(a_var) """) refactored = self._local_rename(code, len(code) - 3, "new_var") self.assertEqual( dedent("""\ a_var = 10 def a_func(new_var): print(new_var) """), refactored, ) def test_renaming_function_with_local_variable_name(self): code = dedent("""\ def a_func(): a_func=20 a_func()""") refactored = self._local_rename(code, len(code) - 3, "new_func") self.assertEqual( dedent("""\ def new_func(): a_func=20 new_func()"""), refactored, ) def test_renaming_functions(self): code = dedent("""\ def a_func(): pass a_func() """) refactored = self._local_rename(code, len(code) - 5, "new_func") self.assertEqual( dedent("""\ def new_func(): pass new_func() """), refactored, ) def test_renaming_async_function(self): code = dedent("""\ async def a_func(): pass a_func()""") refactored = self._local_rename(code, len(code) - 5, "new_func") self.assertEqual( dedent("""\ async def new_func(): pass new_func()"""), refactored, ) def test_renaming_await(self): code = dedent("""\ async def b_func(): pass async def a_func(): await b_func()""") refactored = self._local_rename(code, len(code) - 5, "new_func") self.assertEqual( dedent("""\ async def new_func(): pass async def a_func(): await new_func()"""), refactored, ) def test_renaming_functions_across_modules(self): mod1 = testutils.create_module(self.project, "mod1") mod1.write(dedent("""\ def a_func(): pass a_func() """)) mod2 = testutils.create_module(self.project, "mod2") mod2.write(dedent("""\ import mod1 mod1.a_func() """)) self._rename(mod1, len(mod1.read()) - 5, "new_func") self.assertEqual( dedent("""\ def new_func(): pass new_func() """), mod1.read(), ) self.assertEqual( dedent("""\ import mod1 mod1.new_func() """), mod2.read(), ) def test_renaming_functions_across_modules_from_import(self): mod1 = testutils.create_module(self.project, "mod1") mod1.write(dedent("""\ def a_func(): pass a_func() """)) mod2 = testutils.create_module(self.project, "mod2") mod2.write(dedent("""\ from mod1 import a_func a_func() """)) self._rename(mod1, len(mod1.read()) - 5, "new_func") self.assertEqual( dedent("""\ def new_func(): pass new_func() """), mod1.read(), ) self.assertEqual( dedent("""\ from mod1 import new_func new_func() """), mod2.read(), ) def test_renaming_functions_from_another_module(self): mod1 = testutils.create_module(self.project, "mod1") mod1.write(dedent("""\ def a_func(): pass a_func() """)) mod2 = testutils.create_module(self.project, "mod2") mod2.write(dedent("""\ import mod1 mod1.a_func() """)) self._rename(mod2, len(mod2.read()) - 5, "new_func") self.assertEqual( dedent("""\ def new_func(): pass new_func() """), mod1.read(), ) self.assertEqual( dedent("""\ import mod1 mod1.new_func() """), mod2.read(), ) def test_applying_all_changes_together(self): mod1 = testutils.create_module(self.project, "mod1") mod1.write(dedent("""\ import mod2 mod2.a_func() """)) mod2 = testutils.create_module(self.project, "mod2") mod2.write(dedent("""\ def a_func(): pass a_func() """)) self._rename(mod2, len(mod2.read()) - 5, "new_func") self.assertEqual( dedent("""\ import mod2 mod2.new_func() """), mod1.read(), ) self.assertEqual( dedent("""\ def new_func(): pass new_func() """), mod2.read(), ) def test_renaming_modules(self): mod1 = testutils.create_module(self.project, "mod1") mod1.write(dedent("""\ def a_func(): pass """)) mod2 = testutils.create_module(self.project, "mod2") mod2.write("from mod1 import a_func\n") self._rename(mod2, mod2.read().index("mod1") + 1, "newmod") self.assertTrue( not mod1.exists() and self.project.find_module("newmod") is not None ) self.assertEqual("from newmod import a_func\n", mod2.read()) def test_renaming_modules_aliased(self): mod1 = testutils.create_module(self.project, "mod1") mod1.write(dedent("""\ def a_func(): pass """)) mod2 = testutils.create_module(self.project, "mod2") mod2.write(dedent("""\ import mod1 as m m.a_func() """)) self._rename(mod1, None, "newmod") self.assertTrue( not mod1.exists() and self.project.find_module("newmod") is not None ) self.assertEqual("import newmod as m\nm.a_func()\n", mod2.read()) def test_renaming_packages(self): pkg = testutils.create_package(self.project, "pkg") mod1 = testutils.create_module(self.project, "mod1", pkg) mod1.write(dedent("""\ def a_func(): pass """)) mod2 = testutils.create_module(self.project, "mod2", pkg) mod2.write("from pkg.mod1 import a_func\n") self._rename(mod2, 6, "newpkg") self.assertTrue(self.project.find_module("newpkg.mod1") is not None) new_mod2 = self.project.find_module("newpkg.mod2") self.assertEqual("from newpkg.mod1 import a_func\n", new_mod2.read()) def test_module_dependencies(self): mod1 = testutils.create_module(self.project, "mod1") mod1.write(dedent("""\ class AClass(object): pass """)) mod2 = testutils.create_module(self.project, "mod2") mod2.write(dedent("""\ import mod1 a_var = mod1.AClass() """)) self.project.get_pymodule(mod2).get_attributes()["mod1"] mod1.write(dedent("""\ def AClass(): return 0 """)) self._rename(mod2, len(mod2.read()) - 3, "a_func") self.assertEqual( dedent("""\ def a_func(): return 0 """), mod1.read(), ) self.assertEqual( dedent("""\ import mod1 a_var = mod1.a_func() """), mod2.read(), ) def test_renaming_class_attributes(self): mod1 = testutils.create_module(self.project, "mod1") mod1.write(dedent("""\ class AClass(object): def __init__(self): self.an_attr = 10 """)) mod2 = testutils.create_module(self.project, "mod2") mod2.write(dedent("""\ import mod1 a_var = mod1.AClass() another_var = a_var.an_attr""")) self._rename(mod1, mod1.read().index("an_attr"), "attr") self.assertEqual( dedent("""\ class AClass(object): def __init__(self): self.attr = 10 """), mod1.read(), ) self.assertEqual( dedent("""\ import mod1 a_var = mod1.AClass() another_var = a_var.attr"""), mod2.read(), ) def test_renaming_class_attributes2(self): mod1 = testutils.create_module(self.project, "mod1") mod1.write(dedent("""\ class AClass(object): def __init__(self): an_attr = 10 self.an_attr = 10 """)) mod2 = testutils.create_module(self.project, "mod2") mod2.write(dedent("""\ import mod1 a_var = mod1.AClass() another_var = a_var.an_attr""")) self._rename(mod1, mod1.read().rindex("an_attr"), "attr") self.assertEqual( dedent("""\ class AClass(object): def __init__(self): an_attr = 10 self.attr = 10 """), mod1.read(), ) self.assertEqual( dedent("""\ import mod1 a_var = mod1.AClass() another_var = a_var.attr"""), mod2.read(), ) def test_renaming_methods_in_subclasses(self): mod = testutils.create_module(self.project, "mod1") mod.write(dedent("""\ class A(object): def a_method(self): pass class B(A): def a_method(self): pass """)) self._rename( mod, mod.read().rindex("a_method") + 1, "new_method", in_hierarchy=True ) self.assertEqual( dedent("""\ class A(object): def new_method(self): pass class B(A): def new_method(self): pass """), mod.read(), ) def test_renaming_methods_in_sibling_classes(self): mod = testutils.create_module(self.project, "mod1") mod.write(dedent("""\ class A(object): def a_method(self): pass class B(A): def a_method(self): pass class C(A): def a_method(self): pass """)) self._rename( mod, mod.read().rindex("a_method") + 1, "new_method", in_hierarchy=True ) self.assertEqual( dedent("""\ class A(object): def new_method(self): pass class B(A): def new_method(self): pass class C(A): def new_method(self): pass """), mod.read(), ) def test_not_renaming_methods_in_hierarchies(self): mod = testutils.create_module(self.project, "mod1") mod.write(dedent("""\ class A(object): def a_method(self): pass class B(A): def a_method(self): pass """)) self._rename( mod, mod.read().rindex("a_method") + 1, "new_method", in_hierarchy=False ) self.assertEqual( dedent("""\ class A(object): def a_method(self): pass class B(A): def new_method(self): pass """), mod.read(), ) def test_undoing_refactorings(self): mod1 = testutils.create_module(self.project, "mod1") mod1.write(dedent("""\ def a_func(): pass a_func() """)) self._rename(mod1, len(mod1.read()) - 5, "new_func") self.project.history.undo() self.assertEqual( dedent("""\ def a_func(): pass a_func() """), mod1.read(), ) def test_undoing_renaming_modules(self): mod1 = testutils.create_module(self.project, "mod1") mod1.write(dedent("""\ def a_func(): pass """)) mod2 = testutils.create_module(self.project, "mod2") mod2.write("from mod1 import a_func\n") self._rename(mod2, 6, "newmod") self.project.history.undo() self.assertEqual("mod1.py", mod1.path) self.assertEqual("from mod1 import a_func\n", mod2.read()) def test_rename_in_module_renaming_one_letter_names_for_expressions(self): mod1 = testutils.create_module(self.project, "mod1") mod1.write("a = 10\nprint(1+a)\n") pymod = self.project.get_module("mod1") old_pyname = pymod["a"] finder = rope.refactor.occurrences.create_finder(self.project, "a", old_pyname) refactored = rename.rename_in_module( finder, "new_var", pymodule=pymod, replace_primary=True ) self.assertEqual( dedent("""\ new_var = 10 print(1+new_var) """), refactored, ) def test_renaming_for_loop_variable(self): code = dedent("""\ for var in range(10): print(var) """) refactored = self._local_rename(code, code.find("var") + 1, "new_var") self.assertEqual( dedent("""\ for new_var in range(10): print(new_var) """), refactored, ) def test_renaming_async_for_loop_variable(self): code = dedent("""\ async def func(): async for var in range(10): print(var) """) refactored = self._local_rename(code, code.find("var") + 1, "new_var") self.assertEqual( dedent("""\ async def func(): async for new_var in range(10): print(new_var) """), refactored, ) def test_renaming_async_with_context_manager(self): code = dedent("""\ def a_cm(): pass async def a_func(): async with a_cm() as x: pass""") refactored = self._local_rename(code, code.find("a_cm") + 1, "another_cm") expected = dedent("""\ def another_cm(): pass async def a_func(): async with another_cm() as x: pass""") self.assertEqual(refactored, expected) def test_renaming_async_with_as_variable(self): code = dedent("""\ async def func(): async with a_func() as var: print(var) """) refactored = self._local_rename(code, code.find("var") + 1, "new_var") self.assertEqual( dedent("""\ async def func(): async with a_func() as new_var: print(new_var) """), refactored, ) def test_renaming_parameters(self): code = dedent("""\ def a_func(param): print(param) a_func(param=hey) """) refactored = self._local_rename(code, code.find("param") + 1, "new_param") self.assertEqual( dedent("""\ def a_func(new_param): print(new_param) a_func(new_param=hey) """), refactored, ) def test_renaming_assigned_parameters(self): code = dedent("""\ def f(p): p = p + 1 return p f(p=1) """) refactored = self._local_rename(code, code.find("p"), "arg") self.assertEqual( dedent("""\ def f(arg): arg = arg + 1 return arg f(arg=1) """), refactored, ) def test_renaming_parameters_not_renaming_others(self): code = dedent("""\ def a_func(param): print(param) param=10 a_func(param) """) refactored = self._local_rename(code, code.find("param") + 1, "new_param") self.assertEqual( dedent("""\ def a_func(new_param): print(new_param) param=10 a_func(param) """), refactored, ) def test_renaming_parameters_not_renaming_others2(self): code = dedent("""\ def a_func(param): print(param) param=10 a_func(param=param)""") refactored = self._local_rename(code, code.find("param") + 1, "new_param") self.assertEqual( dedent("""\ def a_func(new_param): print(new_param) param=10 a_func(new_param=param)"""), refactored, ) def test_renaming_parameters_with_multiple_params(self): code = dedent("""\ def a_func(param1, param2): print(param1) a_func(param1=1, param2=2) """) refactored = self._local_rename(code, code.find("param1") + 1, "new_param") self.assertEqual( dedent("""\ def a_func(new_param, param2): print(new_param) a_func(new_param=1, param2=2) """), refactored, ) def test_renaming_parameters_with_multiple_params2(self): code = dedent("""\ def a_func(param1, param2): print(param1) a_func(param1=1, param2=2) """) refactored = self._local_rename(code, code.rfind("param2") + 1, "new_param") self.assertEqual( dedent("""\ def a_func(param1, new_param): print(param1) a_func(param1=1, new_param=2) """), refactored, ) def test_renaming_parameters_on_calls(self): code = dedent("""\ def a_func(param): print(param) a_func(param = hey) """) refactored = self._local_rename(code, code.rfind("param") + 1, "new_param") self.assertEqual( dedent("""\ def a_func(new_param): print(new_param) a_func(new_param = hey) """), refactored, ) def test_renaming_parameters_spaces_before_call(self): code = dedent("""\ def a_func(param): print(param) a_func (param=hey) """) refactored = self._local_rename(code, code.rfind("param") + 1, "new_param") self.assertEqual( dedent("""\ def a_func(new_param): print(new_param) a_func (new_param=hey) """), refactored, ) def test_renaming_parameter_like_objects_after_keywords(self): code = dedent("""\ def a_func(param): print(param) dict(param=hey) """) refactored = self._local_rename(code, code.find("param") + 1, "new_param") self.assertEqual( dedent("""\ def a_func(new_param): print(new_param) dict(param=hey) """), refactored, ) def test_renaming_variables_in_init_dot_pys(self): pkg = testutils.create_package(self.project, "pkg") init_dot_py = pkg.get_child("__init__.py") init_dot_py.write("a_var = 10\n") mod = testutils.create_module(self.project, "mod") mod.write("import pkg\nprint(pkg.a_var)\n") self._rename(mod, mod.read().index("a_var") + 1, "new_var") self.assertEqual("new_var = 10\n", init_dot_py.read()) self.assertEqual("import pkg\nprint(pkg.new_var)\n", mod.read()) def test_renaming_variables_in_init_dot_pys2(self): pkg = testutils.create_package(self.project, "pkg") init_dot_py = pkg.get_child("__init__.py") init_dot_py.write("a_var = 10\n") mod = testutils.create_module(self.project, "mod") mod.write("import pkg\nprint(pkg.a_var)\n") self._rename(init_dot_py, init_dot_py.read().index("a_var") + 1, "new_var") self.assertEqual("new_var = 10\n", init_dot_py.read()) self.assertEqual("import pkg\nprint(pkg.new_var)\n", mod.read()) def test_renaming_variables_in_init_dot_pys3(self): pkg = testutils.create_package(self.project, "pkg") init_dot_py = pkg.get_child("__init__.py") init_dot_py.write("a_var = 10\n") mod = testutils.create_module(self.project, "mod") mod.write("import pkg\nprint(pkg.a_var)\n") self._rename(mod, mod.read().index("a_var") + 1, "new_var") self.assertEqual("new_var = 10\n", init_dot_py.read()) self.assertEqual("import pkg\nprint(pkg.new_var)\n", mod.read()) def test_renaming_resources_using_rename_module_refactoring(self): mod1 = testutils.create_module(self.project, "mod1") mod2 = testutils.create_module(self.project, "mod2") mod1.write("a_var = 1") mod2.write("import mod1\nmy_var = mod1.a_var\n") renamer = rename.Rename(self.project, mod1) renamer.get_changes("newmod").do() self.assertEqual("import newmod\nmy_var = newmod.a_var\n", mod2.read()) def test_renam_resources_using_rename_module_refactor_for_packages(self): mod1 = testutils.create_module(self.project, "mod1") pkg = testutils.create_package(self.project, "pkg") mod1.write("import pkg\nmy_pkg = pkg") renamer = rename.Rename(self.project, pkg) renamer.get_changes("newpkg").do() self.assertEqual("import newpkg\nmy_pkg = newpkg", mod1.read()) def test_renam_resources_use_rename_module_refactor_for_init_dot_py(self): mod1 = testutils.create_module(self.project, "mod1") pkg = testutils.create_package(self.project, "pkg") mod1.write("import pkg\nmy_pkg = pkg") renamer = rename.Rename(self.project, pkg.get_child("__init__.py")) renamer.get_changes("newpkg").do() self.assertEqual("import newpkg\nmy_pkg = newpkg", mod1.read()) def test_renaming_global_variables(self): code = dedent("""\ a_var = 1 def a_func(): global a_var var = a_var """) refactored = self._local_rename(code, code.index("a_var"), "new_var") self.assertEqual( dedent("""\ new_var = 1 def a_func(): global new_var var = new_var """), refactored, ) def test_renaming_global_variables2(self): code = dedent("""\ a_var = 1 def a_func(): global a_var var = a_var """) refactored = self._local_rename(code, code.rindex("a_var"), "new_var") self.assertEqual( dedent("""\ new_var = 1 def a_func(): global new_var var = new_var """), refactored, ) def test_renaming_when_unsure(self): code = dedent("""\ class C(object): def a_func(self): pass def f(arg): arg.a_func() """) mod1 = testutils.create_module(self.project, "mod1") mod1.write(code) self._rename(mod1, code.index("a_func"), "new_func", unsure=self._true) self.assertEqual( dedent("""\ class C(object): def new_func(self): pass def f(arg): arg.new_func() """), mod1.read(), ) def _true(self, *args): return True def test_renaming_when_unsure_with_confirmation(self): def confirm(occurrence): return False code = dedent("""\ class C(object): def a_func(self): pass def f(arg): arg.a_func() """) mod1 = testutils.create_module(self.project, "mod1") mod1.write(code) self._rename(mod1, code.index("a_func"), "new_func", unsure=confirm) self.assertEqual( dedent("""\ class C(object): def new_func(self): pass def f(arg): arg.a_func() """), mod1.read(), ) def test_renaming_when_unsure_not_renaming_knowns(self): code = dedent("""\ class C1(object): def a_func(self): pass class C2(object): def a_func(self): pass c1 = C1() c1.a_func() c2 = C2() c2.a_func() """) mod1 = testutils.create_module(self.project, "mod1") mod1.write(code) self._rename(mod1, code.index("a_func"), "new_func", unsure=self._true) self.assertEqual( dedent("""\ class C1(object): def new_func(self): pass class C2(object): def a_func(self): pass c1 = C1() c1.new_func() c2 = C2() c2.a_func() """), mod1.read(), ) def test_renaming_in_strings_and_comments(self): code = dedent("""\ a_var = 1 # a_var """) mod1 = testutils.create_module(self.project, "mod1") mod1.write(code) self._rename(mod1, code.index("a_var"), "new_var", docs=True) self.assertEqual( dedent("""\ new_var = 1 # new_var """), mod1.read(), ) def test_not_renaming_in_strings_and_comments_where_not_visible(self): code = dedent("""\ def f(): a_var = 1 # a_var """) mod1 = testutils.create_module(self.project, "mod1") mod1.write(code) self._rename(mod1, code.index("a_var"), "new_var", docs=True) self.assertEqual( dedent("""\ def f(): new_var = 1 # a_var """), mod1.read(), ) def test_not_renaming_all_text_occurrences_in_strings_and_comments(self): code = dedent("""\ a_var = 1 # a_vard _a_var """) mod1 = testutils.create_module(self.project, "mod1") mod1.write(code) self._rename(mod1, code.index("a_var"), "new_var", docs=True) self.assertEqual( dedent("""\ new_var = 1 # a_vard _a_var """), mod1.read(), ) def test_renaming_occurrences_in_overwritten_scopes(self): refactored = self._local_rename( dedent("""\ a_var = 20 def f(): print(a_var) def f(): print(a_var) """), 2, "new_var", ) self.assertEqual( dedent("""\ new_var = 20 def f(): print(new_var) def f(): print(new_var) """), refactored, ) def test_renaming_occurrences_in_overwritten_scopes2(self): code = dedent("""\ def f(): a_var = 1 print(a_var) def f(): a_var = 1 print(a_var) """) refactored = self._local_rename(code, code.index("a_var") + 1, "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") refactored = self._local_rename(code, offset, "b") self.assertEqual( "\nb = 1\n\nprint(2 + b + 2)\n", refactored.replace("\r\n", "\n") ) def test_multi_byte_strs_and_renaming(self): s = "{LATIN SMALL LETTER I WITH DIAERESIS}" * 4 code = "# -*- coding: utf-8 -*-\n# " + s + "\na = 1\nprint(2 + a + 2)\n" refactored = self._local_rename(code, code.rindex("a"), "b") self.assertEqual( "# -*- coding: utf-8 -*-\n# " + s + "\nb = 1\nprint(2 + b + 2)\n", refactored, ) def test_resources_parameter(self): mod1 = testutils.create_module(self.project, "mod1") mod2 = testutils.create_module(self.project, "mod2") mod1.write(dedent("""\ def f(): pass """)) mod2.write(dedent("""\ import mod1 mod1.f() """)) self._rename(mod1, mod1.read().rindex("f"), "g", resources=[mod1]) self.assertEqual( dedent("""\ def g(): pass """), mod1.read(), ) self.assertEqual( dedent("""\ import mod1 mod1.f() """), mod2.read(), ) def test_resources_parameter_not_changing_defining_module(self): mod1 = testutils.create_module(self.project, "mod1") mod2 = testutils.create_module(self.project, "mod2") mod1.write(dedent("""\ def f(): pass """)) mod2.write(dedent("""\ import mod1 mod1.f() """)) self._rename(mod1, mod1.read().rindex("f"), "g", resources=[mod2]) self.assertEqual( dedent("""\ def f(): pass """), mod1.read(), ) self.assertEqual( dedent("""\ import mod1 mod1.g() """), mod2.read(), ) # XXX: with variables should not leak def xxx_test_with_statement_variables_should_not_leak(self): code = dedent("""\ f = 1 with open("1.txt") as f: print(f) """) if sys.version_info < (2, 6, 0): code = "from __future__ import with_statement\n" + code mod1 = testutils.create_module(self.project, "mod1") mod1.write(code) self._rename(mod1, code.rindex("f"), "file") expected = dedent("""\ f = 1 with open("1.txt") as file: print(file) """) self.assertEqual(expected, mod1.read()) def test_rename_in_list_comprehension(self): code = dedent("""\ some_var = 1 compr = [some_var for some_var in range(10)] """) offset = code.index("some_var") refactored = self._local_rename(code, offset, "new_var") expected = dedent("""\ new_var = 1 compr = [some_var for some_var in range(10)] """) 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): self.project = testutils.sample_project() self.mod = testutils.create_module(self.project, "mod") def tearDown(self): testutils.remove_project(self.project) super().tearDown() def test_simple_case(self): self.mod.write(dedent("""\ a_var = 1 print(a_var) """)) changer = rename.ChangeOccurrences( self.project, self.mod, self.mod.read().index("a_var") ) changer.get_changes("new_var").do() self.assertEqual( dedent("""\ new_var = 1 print(new_var) """), self.mod.read(), ) def test_only_performing_inside_scopes(self): self.mod.write(dedent("""\ a_var = 1 new_var = 2 def f(): print(a_var) """)) changer = rename.ChangeOccurrences( self.project, self.mod, self.mod.read().rindex("a_var") ) changer.get_changes("new_var").do() self.assertEqual( dedent("""\ a_var = 1 new_var = 2 def f(): print(new_var) """), self.mod.read(), ) def test_only_performing_on_calls(self): self.mod.write(dedent("""\ def f1(): pass def f2(): pass g = f1 a = f1() """)) changer = rename.ChangeOccurrences( self.project, self.mod, self.mod.read().rindex("f1") ) changer.get_changes("f2", only_calls=True).do() self.assertEqual( dedent("""\ def f1(): pass def f2(): pass g = f1 a = f2() """), self.mod.read(), ) def test_only_performing_on_reads(self): self.mod.write(dedent("""\ a = 1 b = 2 print(a) """)) changer = rename.ChangeOccurrences( self.project, self.mod, self.mod.read().rindex("a") ) changer.get_changes("b", writes=False).do() self.assertEqual( dedent("""\ a = 1 b = 2 print(b) """), self.mod.read(), ) class ImplicitInterfacesTest(unittest.TestCase): def setUp(self): super().setUp() self.project = testutils.sample_project(validate_objectdb=True) self.pycore = self.project.pycore self.mod1 = testutils.create_module(self.project, "mod1") self.mod2 = testutils.create_module(self.project, "mod2") def tearDown(self): testutils.remove_project(self.project) super().tearDown() def _rename(self, resource, offset, new_name, **kwds): changes = Rename(self.project, resource, offset).get_changes(new_name, **kwds) self.project.do(changes) def test_performing_rename_on_parameters(self): self.mod1.write("def f(arg):\n arg.run()\n") self.mod2.write(dedent("""\ import mod1 class A(object): def run(self): pass class B(object): def run(self): pass mod1.f(A()) mod1.f(B()) """)) self.pycore.analyze_module(self.mod2) self._rename(self.mod1, self.mod1.read().index("run"), "newrun") self.assertEqual("def f(arg):\n arg.newrun()\n", self.mod1.read()) self.assertEqual( dedent("""\ import mod1 class A(object): def newrun(self): pass class B(object): def newrun(self): pass mod1.f(A()) mod1.f(B()) """), self.mod2.read(), ) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/ropetest/refactor/restructuretest.py0000664000175000017500000002271014512700666022016 0ustar00lieryanlieryanimport unittest from textwrap import dedent from rope.refactor import restructure from ropetest import testutils class RestructureTest(unittest.TestCase): def setUp(self): super().setUp() self.project = testutils.sample_project() self.pycore = self.project.pycore self.mod = testutils.create_module(self.project, "mod") def tearDown(self): testutils.remove_project(self.project) super().tearDown() def test_trivial_case(self): refactoring = restructure.Restructure(self.project, "a = 1", "a = 0") self.mod.write("b = 1\n") self.project.do(refactoring.get_changes()) self.assertEqual("b = 1\n", self.mod.read()) def test_replacing_simple_patterns(self): refactoring = restructure.Restructure(self.project, "a = 1", "a = int(1)") self.mod.write("a = 1\nb = 1\n") self.project.do(refactoring.get_changes()) self.assertEqual("a = int(1)\nb = 1\n", self.mod.read()) def test_replacing_patterns_with_normal_names(self): refactoring = restructure.Restructure( self.project, "${a} = 1", "${a} = int(1)", args={"a": "exact"} ) self.mod.write("a = 1\nb = 1\n") self.project.do(refactoring.get_changes()) self.assertEqual("a = int(1)\nb = 1\n", self.mod.read()) def test_replacing_patterns_with_any_names(self): refactoring = restructure.Restructure(self.project, "${a} = 1", "${a} = int(1)") self.mod.write("a = 1\nb = 1\n") self.project.do(refactoring.get_changes()) self.assertEqual("a = int(1)\nb = int(1)\n", self.mod.read()) def test_replacing_patterns_with_any_names2(self): refactoring = restructure.Restructure(self.project, "${x} + ${x}", "${x} * 2") self.mod.write("a = 1 + 1\n") self.project.do(refactoring.get_changes()) self.assertEqual("a = 1 * 2\n", self.mod.read()) def test_replacing_patterns_with_checks(self): self.mod.write(dedent("""\ def f(p=1): return p g = f g() """)) refactoring = restructure.Restructure( self.project, "${f}()", "${f}(2)", args={"f": "object=mod.f"} ) self.project.do(refactoring.get_changes()) self.assertEqual( dedent("""\ def f(p=1): return p g = f g(2) """), self.mod.read(), ) def test_replacing_assignments_with_sets(self): refactoring = restructure.Restructure( self.project, "${a} = ${b}", "${a}.set(${b})" ) self.mod.write("a = 1\nb = 1\n") self.project.do(refactoring.get_changes()) self.assertEqual("a.set(1)\nb.set(1)\n", self.mod.read()) def test_replacing_sets_with_assignments(self): refactoring = restructure.Restructure( self.project, "${a}.set(${b})", "${a} = ${b}" ) self.mod.write("a.set(1)\nb.set(1)\n") self.project.do(refactoring.get_changes()) self.assertEqual( dedent("""\ a = 1 b = 1 """), self.mod.read(), ) def test_using_make_checks(self): self.mod.write(dedent("""\ def f(p=1): return p g = f g() """)) refactoring = restructure.Restructure( self.project, "${f}()", "${f}(2)", args={"f": "object=mod.f"} ) self.project.do(refactoring.get_changes()) self.assertEqual( dedent("""\ def f(p=1): return p g = f g(2) """), self.mod.read(), ) def test_using_make_checking_builtin_types(self): self.mod.write("a = 1 + 1\n") refactoring = restructure.Restructure( self.project, "${i} + ${i}", "${i} * 2", args={"i": "type=__builtin__.int"} ) self.project.do(refactoring.get_changes()) self.assertEqual("a = 1 * 2\n", self.mod.read()) def test_auto_indentation_when_no_indentation(self): self.mod.write("a = 2\n") refactoring = restructure.Restructure( self.project, "${a} = 2", "${a} = 1\n${a} += 1" ) self.project.do(refactoring.get_changes()) self.assertEqual( dedent("""\ a = 1 a += 1 """), self.mod.read(), ) def test_auto_indentation(self): self.mod.write(dedent("""\ def f(): a = 2 """)) refactoring = restructure.Restructure( self.project, "${a} = 2", "${a} = 1\n${a} += 1" ) self.project.do(refactoring.get_changes()) self.assertEqual( dedent("""\ def f(): a = 1 a += 1 """), self.mod.read(), ) def test_auto_indentation_and_not_indenting_blanks(self): self.mod.write("def f():\n a = 2\n") refactoring = restructure.Restructure( self.project, "${a} = 2", "${a} = 1\n\n${a} += 1" ) self.project.do(refactoring.get_changes()) self.assertEqual("def f():\n a = 1\n\n a += 1\n", self.mod.read()) def test_importing_names(self): self.mod.write("a = 2\n") refactoring = restructure.Restructure( self.project, "${a} = 2", "${a} = myconsts.two", imports=["import myconsts"] ) self.project.do(refactoring.get_changes()) self.assertEqual( dedent("""\ import myconsts a = myconsts.two """), self.mod.read(), ) def test_not_importing_names_when_there_are_no_changes(self): self.mod.write("a = True\n") refactoring = restructure.Restructure( self.project, "${a} = 2", "${a} = myconsts.two", imports=["import myconsts"] ) self.project.do(refactoring.get_changes()) self.assertEqual("a = True\n", self.mod.read()) def test_handling_containing_matches(self): self.mod.write("a = 1 / 2 / 3\n") refactoring = restructure.Restructure( self.project, "${a} / ${b}", "${a} // ${b}" ) self.project.do(refactoring.get_changes()) self.assertEqual("a = 1 // 2 // 3\n", self.mod.read()) def test_handling_overlapping_matches(self): self.mod.write("a = 1\na = 1\na = 1\n") refactoring = restructure.Restructure(self.project, "a = 1\na = 1\n", "b = 1") self.project.do(refactoring.get_changes()) self.assertEqual("b = 1\na = 1\n", self.mod.read()) def test_preventing_stack_overflow_when_matching(self): self.mod.write("1\n") refactoring = restructure.Restructure(self.project, "${a}", "${a}") self.project.do(refactoring.get_changes()) self.assertEqual("1\n", self.mod.read()) def test_performing_a_restructuring_to_all_modules(self): mod2 = testutils.create_module(self.project, "mod2") self.mod.write("a = 1\n") mod2.write("b = 1\n") refactoring = restructure.Restructure(self.project, "1", "2 / 1") self.project.do(refactoring.get_changes()) self.assertEqual("a = 2 / 1\n", self.mod.read()) self.assertEqual("b = 2 / 1\n", mod2.read()) def test_performing_a_restructuring_to_selected_modules(self): mod2 = testutils.create_module(self.project, "mod2") self.mod.write("a = 1\n") mod2.write("b = 1\n") refactoring = restructure.Restructure(self.project, "1", "2 / 1") self.project.do(refactoring.get_changes(resources=[mod2])) self.assertEqual("a = 1\n", self.mod.read()) self.assertEqual("b = 2 / 1\n", mod2.read()) def test_unsure_argument_of_default_wildcard(self): self.mod.write(dedent("""\ def f(p): return p * 2 x = "" * 2 i = 1 * 2 """)) refactoring = restructure.Restructure( self.project, "${s} * 2", "dup(${s})", args={"s": {"type": "__builtins__.str", "unsure": True}}, ) self.project.do(refactoring.get_changes()) self.assertEqual( dedent("""\ def f(p): return dup(p) x = dup("") i = 1 * 2 """), self.mod.read(), ) def test_statement_after_string_and_column(self): mod_text = dedent("""\ def f(x): if a == "a": raise Exception("test") """) self.mod.write(mod_text) 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(), ) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1699196919.0 rope-1.12.0/ropetest/refactor/similarfindertest.py0000664000175000017500000002744514521727767022304 0ustar00lieryanlieryanimport unittest from textwrap import dedent from rope.refactor import similarfinder from ropetest import testutils class SimilarFinderTest(unittest.TestCase): def setUp(self): super().setUp() self.project = testutils.sample_project() self.mod = testutils.create_module(self.project, "mod") def tearDown(self): testutils.remove_project(self.project) super().tearDown() def _create_finder(self, source, **kwds): self.mod.write(source) pymodule = self.project.get_pymodule(self.mod) return similarfinder.SimilarFinder(pymodule, **kwds) def test_trivial_case(self): finder = self._create_finder("") self.assertEqual([], list(finder.get_match_regions("10"))) def test_constant_integer(self): source = "a = 10\n" finder = self._create_finder(source) result = [(source.index("10"), source.index("10") + 2)] self.assertEqual(result, list(finder.get_match_regions("10"))) def test_bool_is_not_similar_to_integer(self): source = dedent("""\ a = False b = 0""") finder = self._create_finder(source) result = [(source.index("False"), source.index("False") + len("False"))] self.assertEqual(result, list(finder.get_match_regions("False"))) def test_simple_addition(self): source = "a = 1 + 2\n" finder = self._create_finder(source) result = [(source.index("1"), source.index("2") + 1)] self.assertEqual(result, list(finder.get_match_regions("1 + 2"))) def test_simple_addition2(self): source = "a = 1 +2\n" finder = self._create_finder(source) result = [(source.index("1"), source.index("2") + 1)] self.assertEqual(result, list(finder.get_match_regions("1 + 2"))) def test_simple_assign_statements(self): source = "a = 1 + 2\n" finder = self._create_finder(source) self.assertEqual( [(0, len(source) - 1)], list(finder.get_match_regions("a = 1 + 2")) ) def test_simple_multiline_statements(self): source = dedent("""\ a = 1 b = 2 """) finder = self._create_finder(source) self.assertEqual( [(0, len(source) - 1)], list(finder.get_match_regions("a = 1\nb = 2")) ) def test_multiple_matches(self): source = "a = 1 + 1\n" finder = self._create_finder(source) result = list(finder.get_match_regions("1")) self.assertEqual(2, len(result)) start1 = source.index("1") self.assertEqual((start1, start1 + 1), result[0]) start2 = source.rindex("1") self.assertEqual((start2, start2 + 1), result[1]) def test_multiple_matches2(self): source = dedent("""\ a = 1 b = 2 a = 1 b = 2 """) finder = self._create_finder(source) self.assertEqual(2, len(list(finder.get_match_regions("a = 1\nb = 2")))) def test_restricting_the_region_to_search(self): source = "1\n\n1\n" finder = self._create_finder(source) result = list(finder.get_match_regions("1", start=2)) start = source.rfind("1") self.assertEqual([(start, start + 1)], result) def test_matching_basic_patterns(self): source = "b = a\n" finder = self._create_finder(source) result = list(finder.get_match_regions("${a}", args={"a": "exact"})) start = source.rfind("a") self.assertEqual([(start, start + 1)], result) def test_match_get_ast(self): source = "b = a\n" finder = self._create_finder(source) result = list(finder.get_matches("${a}", args={"a": "exact"})) self.assertEqual("a", result[0].get_ast("a").id) def test_match_get_ast_for_statements(self): source = "b = a\n" finder = self._create_finder(source) result = list(finder.get_matches("b = ${a}")) self.assertEqual("a", result[0].get_ast("a").id) def test_matching_multiple_patterns(self): source = "c = a + b\n" finder = self._create_finder(source) result = list(finder.get_matches("${a} + ${b}")) self.assertEqual("a", result[0].get_ast("a").id) self.assertEqual("b", result[0].get_ast("b").id) def test_matching_any_patterns(self): source = "b = a\n" finder = self._create_finder(source) result = list(finder.get_matches("b = ${x}")) self.assertEqual("a", result[0].get_ast("x").id) def test_matching_any_patterns_repeating(self): source = "b = 1 + 1\n" finder = self._create_finder(source) result = list(finder.get_matches("b = ${x} + ${x}")) self.assertEqual(1, result[0].get_ast("x").value) def test_matching_any_patterns_not_matching_different_nodes(self): source = "b = 1 + 2\n" finder = self._create_finder(source) result = list(finder.get_matches("b = ${x} + ${x}")) self.assertEqual(0, len(result)) def test_matching_normal_names_and_assname(self): source = "a = 1\n" finder = self._create_finder(source) result = list(finder.get_matches("${a} = 1")) self.assertEqual("a", result[0].get_ast("a").id) def test_matching_normal_names_and_assname2(self): source = "a = 1\n" finder = self._create_finder(source) result = list(finder.get_matches("${a}", args={"a": "exact"})) self.assertEqual(1, len(result)) def test_matching_normal_names_and_attributes(self): source = "x.a = 1\n" finder = self._create_finder(source) result = list(finder.get_matches("${a} = 1", args={"a": "exact"})) self.assertEqual(0, len(result)) def test_functions_not_matching_when_only_first_parameters(self): source = "f(1, 2)\n" finder = self._create_finder(source) self.assertEqual(0, len(list(finder.get_matches("f(1)")))) def test_matching_nested_try_finally(self): source = dedent("""\ if 1: try: pass except: pass """) pattern = dedent("""\ try: pass except: pass """) finder = self._create_finder(source) self.assertEqual(1, len(list(finder.get_matches(pattern)))) def test_matching_dicts_inside_functions(self): source = dedent("""\ def f(p): d = {1: p.x} """) pattern = "{1: ${a}.x}" finder = self._create_finder(source) self.assertEqual(1, len(list(finder.get_matches(pattern)))) class CheckingFinderTest(unittest.TestCase): def setUp(self): super().setUp() self.project = testutils.sample_project() self.mod1 = testutils.create_module(self.project, "mod1") def tearDown(self): testutils.remove_project(self.project) super().tearDown() def test_trivial_case(self): self.mod1.write("") pymodule = self.project.get_pymodule(self.mod1) finder = similarfinder.SimilarFinder(pymodule) self.assertEqual([], list(finder.get_matches("10", {}))) def test_simple_finding(self): self.mod1.write(dedent("""\ class A(object): pass a = A() """)) pymodule = self.project.get_pymodule(self.mod1) finder = similarfinder.SimilarFinder(pymodule) result = list(finder.get_matches("${anything} = ${A}()", {})) self.assertEqual(1, len(result)) def test_not_matching_when_the_name_does_not_match(self): self.mod1.write(dedent("""\ class A(object): pass a = list() """)) pymodule = self.project.get_pymodule(self.mod1) finder = similarfinder.SimilarFinder(pymodule) result = list(finder.get_matches("${anything} = ${C}()", {"C": "name=mod1.A"})) self.assertEqual(0, len(result)) def test_not_matching_unknowns_finding(self): self.mod1.write(dedent("""\ class A(object): pass a = unknown() """)) pymodule = self.project.get_pymodule(self.mod1) finder = similarfinder.SimilarFinder(pymodule) result = list(finder.get_matches("${anything} = ${C}()", {"C": "name=mod1.A"})) self.assertEqual(0, len(result)) def test_finding_and_matching_pyobjects(self): source = dedent("""\ class A(object): pass NewA = A a = NewA() """) self.mod1.write(source) pymodule = self.project.get_pymodule(self.mod1) finder = similarfinder.SimilarFinder(pymodule) result = list( finder.get_matches("${anything} = ${A}()", {"A": "object=mod1.A"}) ) self.assertEqual(1, len(result)) start = source.rindex("a =") self.assertEqual((start, len(source) - 1), result[0].get_region()) def test_finding_and_matching_types(self): source = dedent("""\ class A(object): def f(self): pass a = A() b = a.f() """) self.mod1.write(source) pymodule = self.project.get_pymodule(self.mod1) finder = similarfinder.SimilarFinder(pymodule) result = list( finder.get_matches("${anything} = ${inst}.f()", {"inst": "type=mod1.A"}) ) self.assertEqual(1, len(result)) start = source.rindex("b") self.assertEqual((start, len(source) - 1), result[0].get_region()) def test_checking_the_type_of_an_ass_name_node(self): self.mod1.write(dedent("""\ class A(object): pass an_a = A() """)) pymodule = self.project.get_pymodule(self.mod1) finder = similarfinder.SimilarFinder(pymodule) result = list(finder.get_matches("${a} = ${assigned}", {"a": "type=mod1.A"})) self.assertEqual(1, len(result)) def test_checking_instance_of_an_ass_name_node(self): self.mod1.write(dedent("""\ class A(object): pass class B(A): pass b = B() """)) pymodule = self.project.get_pymodule(self.mod1) finder = similarfinder.SimilarFinder(pymodule) result = list( finder.get_matches("${a} = ${assigned}", {"a": "instance=mod1.A"}) ) self.assertEqual(1, len(result)) def test_checking_equality_of_imported_pynames(self): mod2 = testutils.create_module(self.project, "mod2") mod2.write(dedent("""\ class A(object): pass """)) self.mod1.write(dedent("""\ from mod2 import A an_a = A() """)) pymod1 = self.project.get_pymodule(self.mod1) finder = similarfinder.SimilarFinder(pymod1) result = list(finder.get_matches("${a_class}()", {"a_class": "name=mod2.A"})) self.assertEqual(1, len(result)) class TemplateTest(unittest.TestCase): def test_simple_templates(self): template = similarfinder.CodeTemplate("${a}\n") self.assertEqual({"a"}, set(template.get_names())) def test_ignoring_matches_in_comments(self): template = similarfinder.CodeTemplate("#${a}\n") self.assertEqual({}.keys(), template.get_names()) def test_ignoring_matches_in_strings(self): template = similarfinder.CodeTemplate("'${a}'\n") self.assertEqual({}.keys(), template.get_names()) def test_simple_substitution(self): template = similarfinder.CodeTemplate("${a}\n") self.assertEqual("b\n", template.substitute({"a": "b"})) def test_substituting_multiple_names(self): template = similarfinder.CodeTemplate("${a}, ${b}\n") self.assertEqual("1, 2\n", template.substitute({"a": "1", "b": "2"})) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/ropetest/refactor/suitestest.py0000664000175000017500000001515614512700666020751 0ustar00lieryanlieryanimport unittest from textwrap import dedent from rope.base import ast from rope.refactor import suites from ropetest import testutils class SuiteTest(unittest.TestCase): def setUp(self): super().setUp() def tearDown(self): super().tearDown() def test_trivial_case(self): root = source_suite_tree("") self.assertEqual(1, root.get_start()) self.assertEqual(0, len(root.get_children())) def test_simple_ifs(self): root = source_suite_tree(dedent("""\ if True: pass""")) self.assertEqual(1, len(root.get_children())) def test_simple_else(self): root = source_suite_tree(dedent("""\ if True: pass else: pass """)) self.assertEqual(2, len(root.get_children())) self.assertEqual(1, root.get_children()[1].get_start()) def test_for(self): root = source_suite_tree(dedent("""\ for i in range(10): pass else: pass """)) self.assertEqual(2, len(root.get_children())) self.assertEqual(2, root.get_children()[1].get_start()) def test_while(self): root = source_suite_tree(dedent("""\ while True: pass """)) self.assertEqual(1, len(root.get_children())) self.assertEqual(1, root.get_children()[0].get_start()) def test_with(self): root = source_suite_tree(dedent("""\ from __future__ import with_statement with file(x): pass """)) self.assertEqual(1, len(root.get_children())) self.assertEqual(2, root.get_children()[0].get_start()) def test_try_finally(self): root = source_suite_tree(dedent("""\ try: pass finally: pass """)) self.assertEqual(2, len(root.get_children())) self.assertEqual(1, root.get_children()[0].get_start()) def test_try_except(self): root = source_suite_tree(dedent("""\ try: pass except: pass else: pass """)) self.assertEqual(3, len(root.get_children())) self.assertEqual(1, root.get_children()[2].get_start()) def test_try_except_finally(self): root = source_suite_tree(dedent("""\ try: pass except: pass finally: pass """)) self.assertEqual(3, len(root.get_children())) self.assertEqual(1, root.get_children()[2].get_start()) def test_local_start_and_end(self): root = source_suite_tree(dedent("""\ if True: pass else: pass """)) self.assertEqual(1, root.local_start()) self.assertEqual(4, root.local_end()) if_suite = root.get_children()[0] self.assertEqual(2, if_suite.local_start()) self.assertEqual(2, if_suite.local_end()) else_suite = root.get_children()[1] self.assertEqual(4, else_suite.local_start()) self.assertEqual(4, else_suite.local_end()) def test_find_suite(self): root = source_suite_tree("\n") self.assertEqual(root, root.find_suite(1)) def test_find_suite_for_ifs(self): root = source_suite_tree(dedent("""\ if True: pass """)) if_suite = root.get_children()[0] self.assertEqual(if_suite, root.find_suite(2)) def test_find_suite_for_between_suites(self): root = source_suite_tree(dedent("""\ if True: pass print(1) if True: pass """)) if_suite1 = root.get_children()[0] if_suite2 = root.get_children()[1] self.assertEqual(if_suite1, root.find_suite(2)) self.assertEqual(if_suite2, root.find_suite(5)) self.assertEqual(root, root.find_suite(3)) def test_simple_find_visible(self): root = source_suite_tree("a = 1\n") self.assertEqual(1, suites.find_visible_for_suite(root, [1])) def test_simple_find_visible_ifs(self): root = source_suite_tree(dedent("""\ if True: a = 1 b = 2 """)) self.assertEqual(root.find_suite(3), root.find_suite(4)) self.assertEqual(3, suites.find_visible_for_suite(root, [3, 4])) def test_simple_find_visible_for_else(self): root = source_suite_tree(dedent("""\ if True: pass else: pass """)) self.assertEqual(2, suites.find_visible_for_suite(root, [2, 4])) def test_simple_find_visible_for_different_suites(self): root = source_suite_tree(dedent("""\ if True: pass a = 1 if False: pass """)) self.assertEqual(1, suites.find_visible_for_suite(root, [2, 3])) self.assertEqual(5, suites.find_visible_for_suite(root, [5])) self.assertEqual(1, suites.find_visible_for_suite(root, [2, 5])) def test_not_always_selecting_scope_start(self): root = source_suite_tree(dedent("""\ if True: a = 1 if True: pass else: pass """)) self.assertEqual(3, suites.find_visible_for_suite(root, [4, 6])) self.assertEqual(3, suites.find_visible_for_suite(root, [3, 5])) self.assertEqual(3, suites.find_visible_for_suite(root, [4, 5])) def test_ignoring_functions(self): root = source_suite_tree(dedent("""\ def f(): pass a = 1 """)) self.assertEqual(3, suites.find_visible_for_suite(root, [2, 3])) def test_ignoring_classes(self): root = source_suite_tree(dedent("""\ a = 1 class C(): pass """)) self.assertEqual(1, suites.find_visible_for_suite(root, [1, 3])) @testutils.only_for_versions_higher("3.10") def test_match_case(self): root = source_suite_tree(dedent("""\ a = 1 match var: case Foo("xx"): print(x) case Foo(x): print(x) """)) self.assertEqual(root.find_suite(4), root.find_suite(6)) self.assertEqual(root.find_suite(3), root.find_suite(6)) self.assertEqual(2, suites.find_visible_for_suite(root, [2, 4])) def source_suite_tree(source): return suites.ast_suite_tree(ast.parse(source)) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/ropetest/refactor/usefunctiontest.py0000664000175000017500000001440014512700666021766 0ustar00lieryanlieryanimport unittest from textwrap import dedent from rope.base import exceptions from rope.refactor.usefunction import UseFunction from ropetest import testutils class UseFunctionTest(unittest.TestCase): def setUp(self): super().setUp() self.project = testutils.sample_project() self.mod1 = testutils.create_module(self.project, "mod1") self.mod2 = testutils.create_module(self.project, "mod2") def tearDown(self): testutils.remove_project(self.project) super().tearDown() def test_simple_case(self): code = dedent("""\ def f(): pass """) self.mod1.write(code) user = UseFunction(self.project, self.mod1, code.rindex("f")) self.project.do(user.get_changes()) self.assertEqual(code, self.mod1.read()) def test_simple_function(self): code = dedent("""\ def f(p): print(p) print(1) """) self.mod1.write(code) user = UseFunction(self.project, self.mod1, code.rindex("f")) self.project.do(user.get_changes()) self.assertEqual( dedent("""\ def f(p): print(p) f(1) """), self.mod1.read(), ) def test_simple_function2(self): code = dedent("""\ def f(p): print(p + 1) print(1 + 1) """) self.mod1.write(code) user = UseFunction(self.project, self.mod1, code.rindex("f")) self.project.do(user.get_changes()) self.assertEqual( dedent("""\ def f(p): print(p + 1) f(1) """), self.mod1.read(), ) def test_functions_with_multiple_statements(self): code = dedent("""\ def f(p): r = p + 1 print(r) r = 2 + 1 print(r) """) self.mod1.write(code) user = UseFunction(self.project, self.mod1, code.rindex("f")) self.project.do(user.get_changes()) self.assertEqual( dedent("""\ def f(p): r = p + 1 print(r) f(2) """), self.mod1.read(), ) def test_returning(self): code = dedent("""\ def f(p): return p + 1 r = 2 + 1 print(r) """) self.mod1.write(code) user = UseFunction(self.project, self.mod1, code.rindex("f")) self.project.do(user.get_changes()) self.assertEqual( dedent("""\ def f(p): return p + 1 r = f(2) print(r) """), self.mod1.read(), ) def test_returning_a_single_expression(self): code = dedent("""\ def f(p): return p + 1 print(2 + 1) """) self.mod1.write(code) user = UseFunction(self.project, self.mod1, code.rindex("f")) self.project.do(user.get_changes()) self.assertEqual( dedent("""\ def f(p): return p + 1 print(f(2)) """), self.mod1.read(), ) def test_occurrences_in_other_modules(self): code = dedent("""\ def f(p): return p + 1 """) self.mod1.write(code) user = UseFunction(self.project, self.mod1, code.rindex("f")) self.mod2.write("print(2 + 1)\n") self.project.do(user.get_changes()) self.assertEqual( dedent("""\ import mod1 print(mod1.f(2)) """), self.mod2.read(), ) def test_when_performing_on_non_functions(self): code = "var = 1\n" self.mod1.write(code) with self.assertRaises(exceptions.RefactoringError): UseFunction(self.project, self.mod1, code.rindex("var")) def test_differing_in_the_inner_temp_names(self): code = dedent("""\ def f(p): a = p + 1 print(a) b = 2 + 1 print(b) """) self.mod1.write(code) user = UseFunction(self.project, self.mod1, code.rindex("f")) self.project.do(user.get_changes()) self.assertEqual( dedent("""\ def f(p): a = p + 1 print(a) f(2) """), self.mod1.read(), ) # TODO: probably new options should be added to restructure def xxx_test_being_a_bit_more_intelligent_when_returning_assigneds(self): code = dedent("""\ def f(p): a = p + 1 return a var = 2 + 1 print(var) """) self.mod1.write(code) user = UseFunction(self.project, self.mod1, code.rindex("f")) self.project.do(user.get_changes()) self.assertEqual( dedent("""\ def f(p): a = p + 1 return a var = f(p) print(var) """), self.mod1.read(), ) def test_exception_when_performing_a_function_with_yield(self): code = dedent("""\ def func(): yield 1 """) self.mod1.write(code) with self.assertRaises(exceptions.RefactoringError): UseFunction(self.project, self.mod1, code.index("func")) def test_exception_when_performing_a_function_two_returns(self): code = dedent("""\ def func(): return 1 return 2 """) self.mod1.write(code) with self.assertRaises(exceptions.RefactoringError): UseFunction(self.project, self.mod1, code.index("func")) def test_exception_when_returns_is_not_the_last_statement(self): code = dedent("""\ def func(): return 2 a = 1 """) self.mod1.write(code) with self.assertRaises(exceptions.RefactoringError): UseFunction(self.project, self.mod1, code.index("func")) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/ropetest/reprtest.py0000664000175000017500000001302514512700666016571 0ustar00lieryanlieryanimport pathlib import tempfile from textwrap import dedent from unittest.mock import MagicMock import pytest from rope.base import libutils, pyobjectsdef, resources from rope.base.project import Project from rope.contrib import findit from rope.contrib.autoimport import models from rope.refactor import occurrences from ropetest import testutils @pytest.fixture def project(): proj = testutils.sample_project() yield proj testutils.remove_project(proj) @pytest.fixture def mod1(project): testutils.create_package(project, "pkg1") return testutils.create_module(project, "pkg1.mod1") def test_repr_project(): with tempfile.TemporaryDirectory() as folder: folder = pathlib.Path(folder).resolve() obj = testutils.sample_project(folder) assert isinstance(obj, Project) assert repr(obj) == f'' def test_repr_file(project): obj = project.get_file("test/file.py") assert isinstance(obj, resources.File) assert repr(obj).startswith('" at 0x' ) def test_repr_pyobjectsdef_pycomprehension_without_associated_resource(project): code = """[a for a in b]""" mod = libutils.get_string_module(project, code) mod._create_structural_attributes() assert len(mod.defineds) == 1 obj = mod.defineds[0] assert isinstance(obj, pyobjectsdef.PyComprehension) assert repr(obj).startswith( '" at 0x' ) def test_repr_findit_location(project, mod1): code = dedent("""\ a = 10 b = 20 c = 30 """) mod1.write(code) occurrence = MagicMock( occurrences.Occurrence, resource=project.get_resource("pkg1/mod1.py"), lineno=2, ) occurrence.get_word_range.return_value = (11, 13) occurrence.is_unsure.return_value = True obj = findit.Location(occurrence=occurrence) assert repr(obj).startswith( ' parse_version(version), f"This test requires version of Python lower than {version}", ) def only_for_versions_higher(version): """Should be used as a decorator for a unittest.TestCase test method""" return unittest.skipIf( sys.version_info < parse_version(version), f"This test requires version of Python higher than {version}", ) def skipNotPOSIX(): return unittest.skipIf(os.name != "posix", "This test works only on POSIX") def time_limit(timeout): if not any(procname in sys.argv[0] for procname in {"pytest", "py.test"}): # no-op when running tests without pytest return lambda *args, **kwargs: lambda func: func # do a local import so we don't import pytest when running without pytest import pytest # this prevents infinite loop/recursion from taking forever in CI return pytest.mark.time_limit(timeout) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/ropetest/type_hinting_test.py0000664000175000017500000003770614512700666020475 0ustar00lieryanlieryanimport unittest from textwrap import dedent, indent import pytest from rope.base.oi.type_hinting import evaluate from rope.contrib.codeassist import code_assist from ropetest import testutils class AbstractHintingTest(unittest.TestCase): def setUp(self): super().setUp() self.project = testutils.sample_project() def tearDown(self): testutils.remove_project(self.project) super().tearDown() def _assist(self, code, offset=None, resource=None, **kwds): if offset is None: offset = len(code) return code_assist(self.project, code, offset, resource, **kwds) def assert_completion_in_result(self, name, scope, result): for proposal in result: if proposal.name == name and proposal.scope == scope: return self.fail( "completion <%s> in scope %r not proposed, available names: %r" % (name, scope, [(i.name, i.scope) for i in result]) ) def assert_completion_not_in_result(self, name, scope, result): for proposal in result: if proposal.name == name and proposal.scope == scope: self.fail("completion <%s> was proposed" % name) def run(self, result=None): if self.__class__.__name__.startswith("Abstract"): return super().run(result) class DocstringParamHintingTest(AbstractHintingTest): def test_hint_param(self): code = dedent('''\ class Sample(object): def a_method(self, a_arg): """:type a_arg: threading.Thread""" a_arg.is_a''') result = self._assist(code) self.assert_completion_in_result("is_alive", "attribute", result) def test_hierarchical_hint_param(self): code = dedent('''\ class ISample(object): def a_method(self, a_arg): """:type a_arg: threading.Thread""" class Sample(ISample): def a_method(self, a_arg): a_arg.is_a''') result = self._assist(code) self.assert_completion_in_result("is_alive", "attribute", result) class DocstringReturnHintingTest(AbstractHintingTest): def test_hierarchical_hint_rtype(self): code = dedent('''\ class ISample(object): def b_method(self): """:rtype: threading.Thread""" class Sample(ISample): def b_method(self): pass def a_method(self): self.b_method().is_a''') result = self._assist(code) self.assert_completion_in_result("is_alive", "attribute", result) def fix_indents(hint): return indent(hint, " " * 12).strip() class AbstractAssignmentHintingTest(AbstractHintingTest): def _make_class_hint(self, type_str): raise NotImplementedError def _make_constructor_hint(self, type_str): raise NotImplementedError def test_hint_attr(self): code = dedent(f"""\ class Sample(object): {fix_indents(self._make_class_hint("threading.Thread"))} def a_method(self): self.a_attr.is_a""") result = self._assist(code) self.assert_completion_in_result("is_alive", "attribute", result) def test_hierarchical_hint_attr(self): code = dedent(f"""\ class ISample(object): {fix_indents(self._make_class_hint("threading.Thread"))} class Sample(ISample): a_attr = None def a_method(self): self.a_attr.is_a""") result = self._assist(code) self.assert_completion_in_result("is_alive", "attribute", result) def test_hint_defined_by_constructor(self): code = dedent(f"""\ class Sample(object): def __init__(self, arg): {fix_indents(self._make_constructor_hint("threading.Thread"))} def a_method(self): self.a_attr.is_a""") result = self._assist(code) self.assert_completion_in_result("is_alive", "attribute", result) def test_hint_attr_redefined_by_constructor(self): code = dedent(f"""\ class Sample(object): {fix_indents(self._make_class_hint("threading.Thread"))} def __init__(self): self.a_attr = None def a_method(self): self.a_attr.is_a""") result = self._assist(code) self.assert_completion_in_result("is_alive", "attribute", result) def test_hierarchical_hint_attr_redefined_by_constructor(self): code = dedent(f"""\ class ISample(object): {fix_indents(self._make_class_hint("threading.Thread"))} class Sample(ISample): def __init__(self): self.a_attr = None def a_method(self): self.a_attr.is_a""") result = self._assist(code) self.assert_completion_in_result("is_alive", "attribute", result) def test_hint_attr_for_pre_defined_type(self): code = dedent(f"""\ class Other(object): def is_alive(self): pass class Sample(object): {fix_indents(self._make_class_hint("Other"))} def a_method(self): self.a_attr.is_a""") result = self._assist(code) self.assert_completion_in_result("is_alive", "attribute", result) def test_hint_attr_for_post_defined_type(self): code = dedent(f"""\ class Sample(object): {fix_indents(self._make_class_hint("Other"))} def a_method(self): self.a_attr.is_a""") offset = len(code) # Note: the leading blank lines are required. code += dedent("""\ class Other(object): def is_alive(self): pass """) result = self._assist(code, offset) self.assert_completion_in_result("is_alive", "attribute", result) def test_hint_parametrized_list(self): code = dedent(f"""\ class Sample(object): {fix_indents(self._make_class_hint("list[threading.Thread]"))} def a_method(self): for i in self.a_attr: i.is_a""") result = self._assist(code) self.assert_completion_in_result("is_alive", "attribute", result) def test_hint_parametrized_tuple(self): code = dedent(f"""\ class Sample(object): {fix_indents(self._make_class_hint("tuple[threading.Thread]"))} def a_method(self): for i in self.a_attr: i.is_a""") result = self._assist(code) self.assert_completion_in_result("is_alive", "attribute", result) def test_hint_parametrized_set(self): code = dedent(f"""\ class Sample(object): {fix_indents(self._make_class_hint("set[threading.Thread]"))} def a_method(self): for i in self.a_attr: i.is_a""") result = self._assist(code) self.assert_completion_in_result("is_alive", "attribute", result) def test_hint_parametrized_iterable(self): code = dedent(f"""\ class Sample(object): {fix_indents(self._make_class_hint("collections.Iterable[threading.Thread]"))} def a_method(self): for i in self.a_attr: i.is_a""") result = self._assist(code) self.assert_completion_in_result("is_alive", "attribute", result) def test_hint_parametrized_iterator(self): code = dedent(f"""\ class Sample(object): {fix_indents(self._make_class_hint("collections.Iterator[threading.Thread]"))} def a_method(self): for i in self.a_attr: i.is_a""") result = self._assist(code) self.assert_completion_in_result("is_alive", "attribute", result) def test_hint_parametrized_dict_key(self): code = dedent(f"""\ class Sample(object): {fix_indents(self._make_class_hint("dict[str, threading.Thread]"))} def a_method(self): for i in self.a_attr.keys(): i.sta""") result = self._assist(code) self.assert_completion_in_result("startswith", "builtin", result) def test_hint_parametrized_dict_value(self): code = dedent(f"""\ class Sample(object): {fix_indents(self._make_class_hint("dict[str, threading.Thread]"))} def a_method(self): for i in self.a_attr.values(): i.is_a""") result = self._assist(code) self.assert_completion_in_result("is_alive", "attribute", result) def test_hint_parametrized_nested_tuple_list(self): code = dedent(f"""\ class Sample(object): {fix_indents(self._make_class_hint("tuple[list[threading.Thread]]"))} def a_method(self): for j in self.a_attr: for i in j: i.is_a""") result = self._assist(code) self.assert_completion_in_result("is_alive", "attribute", result) def test_hint_or(self): code = dedent(f"""\ class Sample(object): {fix_indents(self._make_class_hint("str | threading.Thread"))} def a_method(self): for i in self.a_attr.values(): i.is_a""") result = self._assist(code) try: # Be sure, there isn't errors currently self.assert_completion_in_result('is_alive', 'attribute', result) except AssertionError as e: pytest.xfail("failing configuration (but should work)") def test_hint_nonexistent(self): code = dedent(f"""\ class Sample(object): {fix_indents(self._make_class_hint("sdfdsf.asdfasdf.sdfasdf.Dffg"))} def a_method(self): for i in self.a_attr.values(): i.is_a""") result = self._assist(code) self.assertEqual(result, []) def test_hint_invalid_syntax(self): code = dedent(f"""\ class Sample(object): {fix_indents(self._make_class_hint("sdf | & # &*"))} def a_method(self): for i in self.a_attr.values(): i.is_a""") result = self._assist(code) self.assertEqual(result, []) class DocstringNoneAssignmentHintingTest(AbstractAssignmentHintingTest): def _make_class_hint(self, type_str): hint = dedent(f'''\ """:type a_attr: {type_str}""" a_attr = None ''') return indent(hint, " " * 4) def _make_constructor_hint(self, type_str): hint = dedent(f'''\ """:type arg: {type_str}""" self.a_attr = arg ''') return indent(hint, " " * 8) class DocstringNotImplementedAssignmentHintingTest(AbstractAssignmentHintingTest): def _make_class_hint(self, type_str): hint = dedent(f'''\ """:type a_attr: {type_str}""" a_attr = NotImplemented ''') return indent(hint, " " * 4) def _make_constructor_hint(self, type_str): hint = dedent(f'''\ """:type arg: {type_str}""" self.a_attr = arg ''') return indent(hint, " " * 8) class PEP0484CommentNoneAssignmentHintingTest(AbstractAssignmentHintingTest): def _make_class_hint(self, type_str): hint = dedent(f"""\ a_attr = None # type: {type_str} """) return indent(hint, " " * 4) def _make_constructor_hint(self, type_str): hint = dedent(f"""\ self.a_attr = None # type: {type_str} """) return indent(hint, " " * 8) class PEP0484CommentNotImplementedAssignmentHintingTest(AbstractAssignmentHintingTest): def _make_class_hint(self, type_str): hint = dedent(f"""\ a_attr = NotImplemented # type: {type_str} """) return indent(hint, " " * 4) def _make_constructor_hint(self, type_str): hint = dedent(f"""\ self.a_attr = NotImplemented # type: {type_str} """) return indent(hint, " " * 8) class EvaluateTest(unittest.TestCase): def test_parser(self): tests = [ ("Foo", "(name Foo)"), ("mod1.Foo", "(name mod1.Foo)"), ("mod1.mod2.Foo", "(name mod1.mod2.Foo)"), ("Foo[Bar]", "('[' (name Foo) [(name Bar)])"), ( "Foo[Bar1, Bar2, Bar3]", "('[' (name Foo) [(name Bar1), (name Bar2), (name Bar3)])", ), ("Foo[Bar[Baz]]", "('[' (name Foo) [('[' (name Bar) [(name Baz)])])"), ( "Foo[Bar1[Baz1], Bar2[Baz2]]", "('[' (name Foo) [('[' (name Bar1) [(name Baz1)]), ('[' (name Bar2) [(name Baz2)])])", ), ("mod1.mod2.Foo[Bar]", "('[' (name mod1.mod2.Foo) [(name Bar)])"), ( "mod1.mod2.Foo[mod1.mod2.Bar]", "('[' (name mod1.mod2.Foo) [(name mod1.mod2.Bar)])", ), ( "mod1.mod2.Foo[Bar1, Bar2, Bar3]", "('[' (name mod1.mod2.Foo) [(name Bar1), (name Bar2), (name Bar3)])", ), ( "mod1.mod2.Foo[mod1.mod2.Bar[mod1.mod2.Baz]]", "('[' (name mod1.mod2.Foo) [('[' (name mod1.mod2.Bar) [(name mod1.mod2.Baz)])])", ), ( "mod1.mod2.Foo[mod1.mod2.Bar1[mod1.mod2.Baz1], mod1.mod2.Bar2[mod1.mod2.Baz2]]", "('[' (name mod1.mod2.Foo) [('[' (name mod1.mod2.Bar1) [(name mod1.mod2.Baz1)]), ('[' (name mod1.mod2.Bar2) [(name mod1.mod2.Baz2)])])", ), ("(Foo, Bar) -> Baz", "('(' [(name Foo), (name Bar)] (name Baz))"), ( "(mod1.mod2.Foo[mod1.mod2.Bar1[mod1.mod2.Baz1], mod1.mod2.Bar2[mod1.mod2.Baz2]], mod1.mod2.Bar[mod1.mod2.Bar1[mod1.mod2.Baz1], mod1.mod2.Bar2[mod1.mod2.Baz2]]) -> mod1.mod2.Baz[mod1.mod2.Bar1[mod1.mod2.Baz1], mod1.mod2.Bar2[mod1.mod2.Baz2]]", "('(' [('[' (name mod1.mod2.Foo) [('[' (name mod1.mod2.Bar1) [(name mod1.mod2.Baz1)]), ('[' (name mod1.mod2.Bar2) [(name mod1.mod2.Baz2)])]), ('[' (name mod1.mod2.Bar) [('[' (name mod1.mod2.Bar1) [(name mod1.mod2.Baz1)]), ('[' (name mod1.mod2.Bar2) [(name mod1.mod2.Baz2)])])] ('[' (name mod1.mod2.Baz) [('[' (name mod1.mod2.Bar1) [(name mod1.mod2.Baz1)]), ('[' (name mod1.mod2.Bar2) [(name mod1.mod2.Baz2)])]))", ), ( "(Foo, Bar) -> Baz | Foo[Bar[Baz]]", "('|' ('(' [(name Foo), (name Bar)] (name Baz)) ('[' (name Foo) [('[' (name Bar) [(name Baz)])]))", ), ( "Foo[Bar[Baz | (Foo, Bar) -> Baz]]", "('[' (name Foo) [('[' (name Bar) [('|' (name Baz) ('(' [(name Foo), (name Bar)] (name Baz)))])])", ), ] for t, expected in tests: result = repr(evaluate.compile(t)) self.assertEqual(expected, result) class RegressionHintingTest(AbstractHintingTest): def test_hierarchical_hint_for_mutable_attr_type(self): """Test for #157, AttributeError: 'PyObject' object has no attribute 'get_doc'""" code = dedent("""\ class SuperClass(object): def __init__(self): self.foo = None class SubClass(SuperClass): def __init__(self): super(SubClass, self).__init__() self.bar = 3 def foo(self): return self.bar""") result = self._assist(code) self.assert_completion_in_result("bar", "attribute", result) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/ropetest/versioningtest.py0000664000175000017500000000265014512700666020006 0ustar00lieryanlieryanimport secrets from unittest.mock import patch from rope.base import versioning def test_calculate_version_hash(project): version_hash = versioning.calculate_version_hash(project) assert isinstance(version_hash, str) def test_version_hash_is_constant(project): version_hash_1 = versioning.calculate_version_hash(project) version_hash_2 = versioning.calculate_version_hash(project) assert version_hash_1 == version_hash_2 def test_version_hash_varies_on_rope_version(project): actual_version_hash = versioning.calculate_version_hash(project) with patch("rope.VERSION", "1.0.0"): patched_version_hash = versioning.calculate_version_hash(project) assert actual_version_hash != patched_version_hash def test_version_hash_varies_on_user_preferences(project): actual_version_hash = versioning.calculate_version_hash(project) assert project.prefs.get("automatic_soa") is False project.prefs.set("automatic_soa", True) patched_version_hash = versioning.calculate_version_hash(project) assert actual_version_hash != patched_version_hash def test_version_hash_varies_on_get_file_content(project): actual_version_hash = versioning.calculate_version_hash(project) with patch("rope.base.versioning._get_file_content", return_value=secrets.token_hex()): patched_version_hash = versioning.calculate_version_hash(project) assert actual_version_hash != patched_version_hash ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1705553002.7179844 rope-1.12.0/setup.cfg0000664000175000017500000000023214552126153014314 0ustar00lieryanlieryan[flake8] extend-ignore = F841 E203 B007 B011 max-line-length = 110 [pycodestyle] max-line-length = 110 [egg_info] tag_build = tag_date = 0 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1697350070.0 rope-1.12.0/setup.py0000664000175000017500000000004614512700666014213 0ustar00lieryanlieryanfrom setuptools import setup setup()