mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-01-10 18:09:38 +07:00
PY-44858 Move and other refactorings don't introduce __init__.py inside namespace packages
Namely, Move Module Members, Extract Superclass and Make Local Function Top-Level were all affected by this. Now we check if the refactoring origin is inside a namespace package to decide whether __init__.py should be generated for target directories. Co-authored-by: Kamalia <alishevakamalia@gmail.com> Co-authored-by: Maksim.Levitskii <maksim.levitskii@jetbrains.com> GitOrigin-RevId: b0b3420c5ec8d1f7d3000d8834211631690a0c42
This commit is contained in:
committed by
intellij-monorepo-bot
parent
128ac0ba66
commit
90bbc21f3f
@@ -38,6 +38,12 @@ fun isNamespacePackage(element: PsiElement): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
fun isInNamespacePackage(element: PsiElement): Boolean {
|
||||
val myFile = element.containingFile ?: return false
|
||||
val parentDirectory = myFile.containingDirectory
|
||||
return parentDirectory != null && isNamespacePackage(parentDirectory)
|
||||
}
|
||||
|
||||
private val TOKENS_TO_SKIP = TokenSet.create(PyTokenTypes.DOCSTRING,
|
||||
PyTokenTypes.END_OF_LINE_COMMENT,
|
||||
PyTokenTypes.LINE_BREAK,
|
||||
|
||||
@@ -5,10 +5,13 @@ import com.intellij.ide.fileTemplates.FileTemplate;
|
||||
import com.intellij.ide.fileTemplates.FileTemplateManager;
|
||||
import com.intellij.lang.injection.InjectedLanguageManager;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.editor.Document;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.roots.ProjectRootManager;
|
||||
import com.intellij.openapi.util.Key;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
import com.intellij.openapi.util.io.FileUtil;
|
||||
import com.intellij.openapi.util.io.FileUtilRt;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.openapi.vfs.LocalFileSystem;
|
||||
@@ -28,7 +31,6 @@ import com.jetbrains.python.psi.*;
|
||||
import com.jetbrains.python.psi.impl.PyImportedModule;
|
||||
import com.jetbrains.python.psi.impl.PyPsiUtils;
|
||||
import com.jetbrains.python.refactoring.PyPsiRefactoringUtil;
|
||||
import com.jetbrains.python.refactoring.classes.extractSuperclass.PyExtractSuperclassHelper;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -518,8 +520,103 @@ public final class PyClassRefactoringUtil {
|
||||
PyImportOptimizer.onlyRemoveUnused().processFile(file).run();
|
||||
}
|
||||
|
||||
|
||||
public static PsiFile placeFile(@NotNull Project project, @NotNull String path, @NotNull String filename, boolean isNamespace) throws IOException {
|
||||
return placeFile(project, path, filename, null, isNamespace);
|
||||
}
|
||||
|
||||
/**
|
||||
* Places a file at the end of given path, creating intermediate dirs and inits.
|
||||
*
|
||||
* @return the placed file
|
||||
*/
|
||||
public static PsiFile placeFile(@NotNull Project project, @NotNull String path, @NotNull String filename, @Nullable String content, boolean isNamespace) throws IOException {
|
||||
PsiDirectory psiDir = createDirectories(project, path, isNamespace);
|
||||
LOG.assertTrue(psiDir != null);
|
||||
PsiFile psiFile = psiDir.findFile(filename);
|
||||
if (psiFile == null) {
|
||||
psiFile = psiDir.createFile(filename);
|
||||
if (content != null) {
|
||||
PsiDocumentManager manager = PsiDocumentManager.getInstance(project);
|
||||
Document document = manager.getDocument(psiFile);
|
||||
if (document != null) {
|
||||
document.setText(content);
|
||||
manager.commitDocument(document);
|
||||
}
|
||||
}
|
||||
}
|
||||
return psiFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create all intermediate dirs with inits from one of roots up to target dir.
|
||||
*
|
||||
* @param target a full path to target dir
|
||||
* @return deepest child directory, or null if target is not in roots or process fails at some point.
|
||||
*/
|
||||
@Nullable
|
||||
private static PsiDirectory createDirectories(@NotNull Project project, @NotNull String target, boolean isNamespace) throws IOException {
|
||||
String relativePath = null;
|
||||
VirtualFile closestRoot = null;
|
||||
|
||||
// NOTE: we don't canonicalize target; must be ok in reasonable cases, and is far easier in unit test mode
|
||||
target = FileUtil.toSystemIndependentName(target);
|
||||
final ProjectRootManager projectRootManager = ProjectRootManager.getInstance(project);
|
||||
final List<VirtualFile> allRoots = new ArrayList<>();
|
||||
ContainerUtil.addAll(allRoots, projectRootManager.getContentRoots());
|
||||
ContainerUtil.addAll(allRoots, projectRootManager.getContentSourceRoots());
|
||||
// Check deepest roots first
|
||||
allRoots.sort(Comparator.comparingInt((VirtualFile vf) -> vf.getPath().length()).reversed());
|
||||
for (VirtualFile file : allRoots) {
|
||||
final String rootPath = file.getPath();
|
||||
if (target.startsWith(rootPath)) {
|
||||
relativePath = target.substring(rootPath.length());
|
||||
closestRoot = file;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (closestRoot == null) {
|
||||
throw new IOException("Can't find '" + target + "' among roots");
|
||||
}
|
||||
final LocalFileSystem lfs = LocalFileSystem.getInstance();
|
||||
final PsiManager psiManager = PsiManager.getInstance(project);
|
||||
final String[] dirs = relativePath.split("/");
|
||||
int i = 0;
|
||||
if (dirs[0].isEmpty()) i = 1;
|
||||
VirtualFile resultDir = closestRoot;
|
||||
while (i < dirs.length) {
|
||||
VirtualFile subdir = resultDir.findChild(dirs[i]);
|
||||
if (subdir != null) {
|
||||
if (!subdir.isDirectory()) {
|
||||
throw new IOException("Expected resultDir, but got non-resultDir: " + subdir.getPath());
|
||||
}
|
||||
}
|
||||
else {
|
||||
subdir = resultDir.createChildDirectory(lfs, dirs[i]);
|
||||
}
|
||||
if (subdir.findChild(PyNames.INIT_DOT_PY) == null && !isNamespace) {
|
||||
subdir.createChildData(lfs, PyNames.INIT_DOT_PY);
|
||||
}
|
||||
/*
|
||||
// here we could add an __all__ clause to the __init__.py.
|
||||
// * there's no point to do so; we import the class directly;
|
||||
// * we can't do this consistently since __init__.py may already exist and be nontrivial.
|
||||
if (i == dirs.length - 1) {
|
||||
PsiFile init_file = psiManager.findFile(initVFile);
|
||||
LOG.assertTrue(init_file != null);
|
||||
final PyElementGenerator gen = PyElementGenerator.getInstance(project);
|
||||
final PyStatement statement = gen.createFromText(LanguageLevel.getDefault(), PyStatement.class, PyNames.ALL + " = [\"" + lastName + "\"]");
|
||||
init_file.add(statement);
|
||||
}
|
||||
*/
|
||||
resultDir = subdir;
|
||||
i += 1;
|
||||
}
|
||||
return psiManager.findDirectory(resultDir);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static PyFile getOrCreateFile(String path, Project project) {
|
||||
public static PyFile getOrCreateFile(@NotNull String path, @NotNull Project project, boolean isNamespace) {
|
||||
final VirtualFile vfile = LocalFileSystem.getInstance().findFileByIoFile(new File(path));
|
||||
final PsiFile psi;
|
||||
if (vfile == null) {
|
||||
@@ -531,14 +628,14 @@ public final class PyClassRefactoringUtil {
|
||||
final Properties properties = fileTemplateManager.getDefaultProperties();
|
||||
properties.setProperty("NAME", FileUtilRt.getNameWithoutExtension(file.getName()));
|
||||
final String content = template.getText(properties);
|
||||
psi = PyExtractSuperclassHelper.placeFile(project,
|
||||
psi = PyClassRefactoringUtil.placeFile(project,
|
||||
StringUtil.notNullize(
|
||||
file.getParent(),
|
||||
baseDir != null ? baseDir
|
||||
.getPath() : "."
|
||||
),
|
||||
file.getName(),
|
||||
content
|
||||
content, isNamespace
|
||||
);
|
||||
}
|
||||
catch (IOException e) {
|
||||
|
||||
@@ -5,7 +5,6 @@ import com.google.common.base.Predicate;
|
||||
import com.intellij.notebook.editor.BackedVirtualFile;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.editor.Document;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.roots.ProjectRootManager;
|
||||
import com.intellij.openapi.util.Comparing;
|
||||
@@ -39,6 +38,9 @@ import org.jetbrains.annotations.Nullable;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
|
||||
import static com.jetbrains.python.psi.resolve.PyNamespacePackageUtil.isInNamespacePackage;
|
||||
import static com.jetbrains.python.refactoring.classes.PyClassRefactoringUtil.placeFile;
|
||||
|
||||
/**
|
||||
* @author Dennis.Ushakov
|
||||
*/
|
||||
@@ -87,8 +89,6 @@ public final class PyExtractSuperclassHelper {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
final String text = "class " + superBaseName + ":\n pass" + "\n";
|
||||
PyClass newClass = PyElementGenerator.getInstance(project).createFromText(LanguageLevel.getDefault(), PyClass.class, text);
|
||||
|
||||
@@ -118,14 +118,14 @@ public final class PyExtractSuperclassHelper {
|
||||
Comparing.equal(((BackedVirtualFile)file).getOriginFile(), targetFile);
|
||||
}
|
||||
|
||||
private static PyClass placeNewClass(final Project project, PyClass newClass, @NotNull final PyClass clazz, final String targetFile) {
|
||||
private static PyClass placeNewClass(@NotNull final Project project, @NotNull PyClass newClass, @NotNull final PyClass clazz, @NotNull final String targetFile) {
|
||||
VirtualFile file = VirtualFileManager.getInstance()
|
||||
.findFileByUrl(ApplicationManager.getApplication().isUnitTestMode() ? targetFile : VfsUtilCore.pathToUrl(targetFile));
|
||||
// file is the same as the source
|
||||
if (Comparing.equal(file, clazz.getContainingFile().getVirtualFile()) || isRefactoredClassInBackedFile(file, clazz)) {
|
||||
return (PyClass)clazz.getParent().addBefore(newClass, clazz);
|
||||
}
|
||||
|
||||
boolean isNamespace = isInNamespacePackage(clazz);
|
||||
PsiFile psiFile = null;
|
||||
try {
|
||||
if (file == null) {
|
||||
@@ -140,10 +140,10 @@ public final class PyExtractSuperclassHelper {
|
||||
path = targetFile;
|
||||
filename = PyNames.INIT_DOT_PY; // user requested putting the class into this package directly
|
||||
}
|
||||
psiFile = placeFile(project, path, filename);
|
||||
psiFile = placeFile(project, path, filename, isNamespace);
|
||||
}
|
||||
else if (file.isDirectory()) { // existing directory
|
||||
psiFile = placeFile(project, file.getPath(), PyNames.INIT_DOT_PY);
|
||||
psiFile = placeFile(project, file.getPath(), PyNames.INIT_DOT_PY, isNamespace);
|
||||
}
|
||||
else { // existing file
|
||||
psiFile = PsiManager.getInstance(project).findFile(file);
|
||||
@@ -163,102 +163,12 @@ public final class PyExtractSuperclassHelper {
|
||||
return newClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Places a file at the end of given path, creating intermediate dirs and inits.
|
||||
*
|
||||
* @return the placed file
|
||||
*/
|
||||
public static PsiFile placeFile(Project project, String path, String filename) throws IOException {
|
||||
return placeFile(project, path, filename, null);
|
||||
}
|
||||
|
||||
//TODO: Mover to the other class? That is not good to dependent PyUtils on this class
|
||||
public static PsiFile placeFile(Project project, String path, String filename, @Nullable String content) throws IOException {
|
||||
PsiDirectory psiDir = createDirectories(project, path);
|
||||
LOG.assertTrue(psiDir != null);
|
||||
PsiFile psiFile = psiDir.findFile(filename);
|
||||
if (psiFile == null) {
|
||||
psiFile = psiDir.createFile(filename);
|
||||
if (content != null) {
|
||||
final PsiDocumentManager manager = PsiDocumentManager.getInstance(project);
|
||||
final Document document = manager.getDocument(psiFile);
|
||||
if (document != null) {
|
||||
document.setText(content);
|
||||
manager.commitDocument(document);
|
||||
}
|
||||
}
|
||||
}
|
||||
return psiFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create all intermediate dirs with inits from one of roots up to target dir.
|
||||
*
|
||||
* @param target a full path to target dir
|
||||
* @return deepest child directory, or null if target is not in roots or process fails at some point.
|
||||
*/
|
||||
@Nullable
|
||||
private static PsiDirectory createDirectories(Project project, String target) throws IOException {
|
||||
String relativePath = null;
|
||||
VirtualFile closestRoot = null;
|
||||
|
||||
// NOTE: we don't canonicalize target; must be ok in reasonable cases, and is far easier in unit test mode
|
||||
target = FileUtil.toSystemIndependentName(target);
|
||||
final ProjectRootManager projectRootManager = ProjectRootManager.getInstance(project);
|
||||
final List<VirtualFile> allRoots = new ArrayList<>();
|
||||
ContainerUtil.addAll(allRoots, projectRootManager.getContentRoots());
|
||||
ContainerUtil.addAll(allRoots, projectRootManager.getContentSourceRoots());
|
||||
// Check deepest roots first
|
||||
allRoots.sort(Comparator.comparingInt((VirtualFile vf) -> vf.getPath().length()).reversed());
|
||||
for (VirtualFile file : allRoots) {
|
||||
final String rootPath = file.getPath();
|
||||
if (target.startsWith(rootPath)) {
|
||||
relativePath = target.substring(rootPath.length());
|
||||
closestRoot = file;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (closestRoot == null) {
|
||||
throw new IOException("Can't find '" + target + "' among roots");
|
||||
}
|
||||
final LocalFileSystem lfs = LocalFileSystem.getInstance();
|
||||
final PsiManager psiManager = PsiManager.getInstance(project);
|
||||
final String[] dirs = relativePath.split("/");
|
||||
int i = 0;
|
||||
if (dirs[0].isEmpty()) i = 1;
|
||||
VirtualFile resultDir = closestRoot;
|
||||
while (i < dirs.length) {
|
||||
VirtualFile subdir = resultDir.findChild(dirs[i]);
|
||||
if (subdir != null) {
|
||||
if (!subdir.isDirectory()) {
|
||||
throw new IOException("Expected resultDir, but got non-resultDir: " + subdir.getPath());
|
||||
}
|
||||
}
|
||||
else {
|
||||
subdir = resultDir.createChildDirectory(lfs, dirs[i]);
|
||||
}
|
||||
if (subdir.findChild(PyNames.INIT_DOT_PY) == null) {
|
||||
subdir.createChildData(lfs, PyNames.INIT_DOT_PY);
|
||||
}
|
||||
/*
|
||||
// here we could add an __all__ clause to the __init__.py.
|
||||
// * there's no point to do so; we import the class directly;
|
||||
// * we can't do this consistently since __init__.py may already exist and be nontrivial.
|
||||
if (i == dirs.length - 1) {
|
||||
PsiFile init_file = psiManager.findFile(initVFile);
|
||||
LOG.assertTrue(init_file != null);
|
||||
final PyElementGenerator gen = PyElementGenerator.getInstance(project);
|
||||
final PyStatement statement = gen.createFromText(LanguageLevel.getDefault(), PyStatement.class, PyNames.ALL + " = [\"" + lastName + "\"]");
|
||||
init_file.add(statement);
|
||||
}
|
||||
*/
|
||||
resultDir = subdir;
|
||||
i += 1;
|
||||
}
|
||||
return psiManager.findDirectory(resultDir);
|
||||
}
|
||||
|
||||
public static String getRefactoringId() {
|
||||
return "refactoring.python.extract.superclass";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user