mirror of
https://github.com/davidhalter/jedi.git
synced 2025-12-06 14:04:26 +08:00
Fix renaming of namespace packages, fixes #1779
This commit is contained in:
@@ -105,8 +105,7 @@ class BaseName:
|
||||
# Compiled modules should not return a module path even if they
|
||||
# have one.
|
||||
path: Optional[Path] = self._get_module_context().py__file__()
|
||||
if path is not None:
|
||||
return path
|
||||
return path
|
||||
|
||||
return None
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ from typing import Dict, Iterable, Tuple
|
||||
from parso import split_lines
|
||||
|
||||
from jedi.api.exceptions import RefactoringError
|
||||
from jedi.inference.value.namespace import ImplicitNSName
|
||||
|
||||
EXPRESSION_PARTS = (
|
||||
'or_test and_test not_test comparison '
|
||||
@@ -102,7 +103,12 @@ class Refactoring:
|
||||
to_path=calculate_to_path(path),
|
||||
module_node=next(iter(map_)).get_root_node(),
|
||||
node_to_str_map=map_
|
||||
) for path, map_ in sorted(self._file_to_node_changes.items())
|
||||
)
|
||||
# We need to use `or`, because the path can be None
|
||||
for path, map_ in sorted(
|
||||
self._file_to_node_changes.items(),
|
||||
key=lambda x: x[0] or Path("")
|
||||
)
|
||||
}
|
||||
|
||||
def get_renames(self) -> Iterable[Tuple[Path, Path]]:
|
||||
@@ -116,7 +122,7 @@ class Refactoring:
|
||||
project_path = self._inference_state.project.path
|
||||
for from_, to in self.get_renames():
|
||||
text += 'rename from %s\nrename to %s\n' \
|
||||
% (from_.relative_to(project_path), to.relative_to(project_path))
|
||||
% (_try_relative_to(from_, project_path), _try_relative_to(to, project_path))
|
||||
|
||||
return text + ''.join(f.get_diff() for f in self.get_changed_files().values())
|
||||
|
||||
@@ -146,13 +152,17 @@ def rename(inference_state, definitions, new_name):
|
||||
raise RefactoringError("There is no name under the cursor")
|
||||
|
||||
for d in definitions:
|
||||
# This private access is ok in a way. It's not public to
|
||||
# protect Jedi users from seeing it.
|
||||
tree_name = d._name.tree_name
|
||||
if d.type == 'module' and tree_name is None:
|
||||
p = None if d.module_path is None else Path(d.module_path)
|
||||
if d.type == 'module' and tree_name is None and d.module_path is not None:
|
||||
p = Path(d.module_path)
|
||||
file_renames.add(_calculate_rename(p, new_name))
|
||||
elif isinstance(d._name, ImplicitNSName):
|
||||
#file_renames.add(_calculate_rename(p, new_name))
|
||||
for p in d._name._value.py__path__():
|
||||
file_renames.add(_calculate_rename(Path(p), new_name))
|
||||
else:
|
||||
# This private access is ok in a way. It's not public to
|
||||
# protect Jedi users from seeing it.
|
||||
if tree_name is not None:
|
||||
fmap = file_tree_name_map.setdefault(d.module_path, {})
|
||||
fmap[tree_name] = tree_name.prefix + new_name
|
||||
@@ -246,3 +256,9 @@ def _remove_indent_of_prefix(prefix):
|
||||
Removes the last indentation of a prefix, e.g. " \n \n " becomes " \n \n".
|
||||
"""
|
||||
return ''.join(split_lines(prefix, keepends=True)[:-1])
|
||||
|
||||
def _try_relative_to(path: Path, base: Path) -> Path:
|
||||
try:
|
||||
return path.relative_to(base)
|
||||
except ValueError:
|
||||
return path
|
||||
|
||||
@@ -76,7 +76,7 @@ from import_tree.pkg.mod1 import not_existant,
|
||||
#? 22 ['mod1', 'base']
|
||||
from import_tree.pkg. import mod1
|
||||
#? 17 ['mod1', 'mod2', 'random', 'pkg', 'references', 'rename1', 'rename2', 'classes', 'globals', 'recurse_class1', 'recurse_class2', 'invisible_pkg', 'flow_import']
|
||||
from import_tree. import pkg
|
||||
from import_tree. import new_pkg
|
||||
|
||||
#? 18 ['pkg']
|
||||
from import_tree.p import pkg
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import os
|
||||
import shutil
|
||||
from textwrap import dedent
|
||||
from pathlib import Path
|
||||
import platform
|
||||
@@ -6,6 +7,7 @@ import platform
|
||||
import pytest
|
||||
|
||||
import jedi
|
||||
from test.helpers import get_example_dir
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
@@ -52,6 +54,46 @@ def test_rename_mod(Script, dir_with_content):
|
||||
''').format(dir=dir_with_content)
|
||||
|
||||
|
||||
def test_namespace_package(Script, tmpdir, skip_pre_python38):
|
||||
origin = get_example_dir('implicit_namespace_package')
|
||||
shutil.copytree(origin, tmpdir.strpath, dirs_exist_ok=True)
|
||||
sys_path = [
|
||||
os.path.join(tmpdir.strpath, 'ns1'),
|
||||
os.path.join(tmpdir.strpath, 'ns2')
|
||||
]
|
||||
script_path = os.path.join(tmpdir.strpath, 'script.py')
|
||||
script = Script(
|
||||
'import pkg\n',
|
||||
path=script_path,
|
||||
project=jedi.Project(os.path.join(tmpdir.strpath, 'does-not-exist'), sys_path=sys_path),
|
||||
)
|
||||
refactoring = script.rename(line=1, new_name='new_pkg')
|
||||
refactoring.apply()
|
||||
old1 = os.path.join(sys_path[0], "pkg")
|
||||
new1 = os.path.join(sys_path[0], "new_pkg")
|
||||
old2 = os.path.join(sys_path[1], "pkg")
|
||||
new2 = os.path.join(sys_path[1], "new_pkg")
|
||||
assert not os.path.exists(old1)
|
||||
assert os.path.exists(new1)
|
||||
assert not os.path.exists(old2)
|
||||
assert os.path.exists(new2)
|
||||
|
||||
changed, = iter(refactoring.get_changed_files().values())
|
||||
assert changed.get_new_code() == "import new_pkg\n"
|
||||
|
||||
assert refactoring.get_diff() == dedent(f'''\
|
||||
rename from {old1}
|
||||
rename to {new1}
|
||||
rename from {old2}
|
||||
rename to {new2}
|
||||
--- {script_path}
|
||||
+++ {script_path}
|
||||
@@ -1,2 +1,2 @@
|
||||
-import pkg
|
||||
+import new_pkg
|
||||
''').format(dir=dir_with_content)
|
||||
|
||||
|
||||
def test_rename_none_path(Script):
|
||||
refactoring = Script('foo', path=None).rename(new_name='bar')
|
||||
with pytest.raises(jedi.RefactoringError, match='on a Script with path=None'):
|
||||
|
||||
Reference in New Issue
Block a user