mirror of
https://github.com/davidhalter/jedi.git
synced 2025-12-06 22:14:27 +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
|
# Compiled modules should not return a module path even if they
|
||||||
# have one.
|
# have one.
|
||||||
path: Optional[Path] = self._get_module_context().py__file__()
|
path: Optional[Path] = self._get_module_context().py__file__()
|
||||||
if path is not None:
|
return path
|
||||||
return path
|
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ from typing import Dict, Iterable, Tuple
|
|||||||
from parso import split_lines
|
from parso import split_lines
|
||||||
|
|
||||||
from jedi.api.exceptions import RefactoringError
|
from jedi.api.exceptions import RefactoringError
|
||||||
|
from jedi.inference.value.namespace import ImplicitNSName
|
||||||
|
|
||||||
EXPRESSION_PARTS = (
|
EXPRESSION_PARTS = (
|
||||||
'or_test and_test not_test comparison '
|
'or_test and_test not_test comparison '
|
||||||
@@ -102,7 +103,12 @@ class Refactoring:
|
|||||||
to_path=calculate_to_path(path),
|
to_path=calculate_to_path(path),
|
||||||
module_node=next(iter(map_)).get_root_node(),
|
module_node=next(iter(map_)).get_root_node(),
|
||||||
node_to_str_map=map_
|
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]]:
|
def get_renames(self) -> Iterable[Tuple[Path, Path]]:
|
||||||
@@ -116,7 +122,7 @@ class Refactoring:
|
|||||||
project_path = self._inference_state.project.path
|
project_path = self._inference_state.project.path
|
||||||
for from_, to in self.get_renames():
|
for from_, to in self.get_renames():
|
||||||
text += 'rename from %s\nrename to %s\n' \
|
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())
|
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")
|
raise RefactoringError("There is no name under the cursor")
|
||||||
|
|
||||||
for d in definitions:
|
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
|
tree_name = d._name.tree_name
|
||||||
if d.type == 'module' and tree_name is None:
|
if d.type == 'module' and tree_name is None and d.module_path is not None:
|
||||||
p = None if d.module_path is None else Path(d.module_path)
|
p = Path(d.module_path)
|
||||||
file_renames.add(_calculate_rename(p, new_name))
|
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:
|
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:
|
if tree_name is not None:
|
||||||
fmap = file_tree_name_map.setdefault(d.module_path, {})
|
fmap = file_tree_name_map.setdefault(d.module_path, {})
|
||||||
fmap[tree_name] = tree_name.prefix + new_name
|
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".
|
Removes the last indentation of a prefix, e.g. " \n \n " becomes " \n \n".
|
||||||
"""
|
"""
|
||||||
return ''.join(split_lines(prefix, keepends=True)[:-1])
|
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']
|
#? 22 ['mod1', 'base']
|
||||||
from import_tree.pkg. import mod1
|
from import_tree.pkg. import mod1
|
||||||
#? 17 ['mod1', 'mod2', 'random', 'pkg', 'references', 'rename1', 'rename2', 'classes', 'globals', 'recurse_class1', 'recurse_class2', 'invisible_pkg', 'flow_import']
|
#? 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']
|
#? 18 ['pkg']
|
||||||
from import_tree.p import pkg
|
from import_tree.p import pkg
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import os
|
import os
|
||||||
|
import shutil
|
||||||
from textwrap import dedent
|
from textwrap import dedent
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import platform
|
import platform
|
||||||
@@ -6,6 +7,7 @@ import platform
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
import jedi
|
import jedi
|
||||||
|
from test.helpers import get_example_dir
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture()
|
@pytest.fixture()
|
||||||
@@ -52,6 +54,46 @@ def test_rename_mod(Script, dir_with_content):
|
|||||||
''').format(dir=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):
|
def test_rename_none_path(Script):
|
||||||
refactoring = Script('foo', path=None).rename(new_name='bar')
|
refactoring = Script('foo', path=None).rename(new_name='bar')
|
||||||
with pytest.raises(jedi.RefactoringError, match='on a Script with path=None'):
|
with pytest.raises(jedi.RefactoringError, match='on a Script with path=None'):
|
||||||
|
|||||||
Reference in New Issue
Block a user