[mod-command] Support file deletion right in PSI; fix committing new files; tests for PackageDotHtmlMayBePackageInfo fix

GitOrigin-RevId: 6736011022179d86709da329d57230f76abe64dd
This commit is contained in:
Tagir Valeev
2023-06-25 19:05:46 +00:00
committed by intellij-monorepo-bot
parent 9dde90660e
commit 80fe6724fe
10 changed files with 86 additions and 46 deletions
@@ -4,8 +4,14 @@ package com.intellij.modcommand;
import com.intellij.openapi.vfs.VirtualFile;
import org.jetbrains.annotations.NotNull;
import java.util.Set;
/**
* @param file file to delete
*/
public record ModDeleteFile(@NotNull VirtualFile file) implements ModCommand {
@Override
public @NotNull Set<@NotNull VirtualFile> modifiedFiles() {
return Set.of(file);
}
}
@@ -7,7 +7,6 @@ import com.intellij.openapi.util.NlsContexts;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiDirectory;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiNameIdentifierOwner;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Contract;
@@ -33,13 +32,6 @@ public interface ModPsiUpdater {
@Contract(value = "null -> null; !null -> !null")
<E extends PsiElement> E getWritable(E e);
/**
* Records a command to delete the particular file. Note that calling {@link PsiFile#delete()} may not
* work, even if the file was obtained from {@link #getWritable(PsiElement)}.
* @param file file to delete (can be physical or returned earlier by {@link #getWritable(PsiElement)}
*/
void deleteFile(@NotNull PsiFile file);
/**
* Selects given element
*
@@ -1,4 +1,4 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.psi.impl.file;
@@ -7,6 +7,7 @@ import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.fileTypes.FileTypeRegistry;
import com.intellij.openapi.fileTypes.UnknownFileType;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiFile;
import com.intellij.psi.impl.PsiManagerImpl;
@@ -15,8 +16,11 @@ import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
import java.util.function.Consumer;
public final class PsiFileImplUtil {
private static final Key<Consumer<PsiFile>> NON_PHYSICAL_FILE_DELETE_HANDLER = Key.create("NonPhysicalFileDeleteHandler");
private PsiFileImplUtil() {
}
@@ -33,6 +37,17 @@ public final class PsiFileImplUtil {
}
}
@ApiStatus.Experimental
public static void setNonPhysicalFileDeleteHandler(@NotNull PsiFile file, @NotNull Consumer<@NotNull PsiFile> handler) {
if (file.isPhysical()) {
throw new IllegalArgumentException();
}
file.putUserData(NON_PHYSICAL_FILE_DELETE_HANDLER, handler);
}
public static boolean canDeleteNonPhysicalFile(@NotNull PsiFile file) {
return !file.isPhysical() && file.getUserData(NON_PHYSICAL_FILE_DELETE_HANDLER) != null;
}
public static PsiFile setName(@NotNull PsiFile file, @NotNull String newName) throws IncorrectOperationException {
VirtualFile vFile = file.getViewProvider().getVirtualFile();
@@ -61,6 +76,15 @@ public final class PsiFileImplUtil {
}
public static void doDelete(@NotNull PsiFile file) throws IncorrectOperationException {
if (!file.isPhysical()) {
Consumer<PsiFile> handler = file.getUserData(NON_PHYSICAL_FILE_DELETE_HANDLER);
if (handler == null) {
throw new IncorrectOperationException();
}
handler.accept(file);
return;
}
PsiManagerImpl manager = (PsiManagerImpl)file.getManager();
VirtualFile vFile = file.getVirtualFile();
@@ -451,6 +451,7 @@ public abstract class PsiFileImpl extends ElementBase implements PsiFileEx, PsiF
@Override
public void checkDelete() throws IncorrectOperationException {
if (!getViewProvider().isEventSystemEnabled()) {
if (PsiFileImplUtil.canDeleteNonPhysicalFile(this)) return;
throw new IncorrectOperationException();
}
CheckUtil.checkWritable(this);
@@ -157,7 +157,9 @@ public class ModCommandServiceImpl implements ModCommandService {
VirtualFile newFile = parent.createChildData(this, file.getName());
PsiFile psiFile = PsiManager.getInstance(project).findFile(newFile);
if (psiFile == null) return false;
psiFile.getViewProvider().getDocument().setText(create.text());
Document document = psiFile.getViewProvider().getDocument();
document.setText(create.text());
PsiDocumentManager.getInstance(project).commitDocument(document);
return true;
});
}
@@ -19,6 +19,7 @@ import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.impl.file.PsiDirectoryFactory;
import com.intellij.psi.impl.file.PsiFileImplUtil;
import com.intellij.psi.impl.source.PostprocessReformattingAspect;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.testFramework.LightVirtualFile;
@@ -71,10 +72,12 @@ final class PsiUpdateImpl {
private final @NotNull PsiFile myOrigFile;
private final @NotNull PsiFile myCopyFile;
private final @NotNull PsiDocumentManager myManager;
private boolean myDeleted;
FileTracker(@NotNull PsiFile origFile) {
myProject = origFile.getProject();
myCopyFile = copyFile(myProject, origFile);
PsiFileImplUtil.setNonPhysicalFileDeleteHandler(myCopyFile, f -> myDeleted = true);
myDocument = myCopyFile.getViewProvider().getDocument();
InjectedLanguageManager injectionManager = InjectedLanguageManager.getInstance(myProject);
boolean injected = injectionManager.isInjectedFragment(origFile);
@@ -108,10 +111,13 @@ final class PsiUpdateImpl {
}
ModCommand getUpdateCommand() {
myManager.commitDocument(myDocument);
unblock();
VirtualFile origVirtualFile = myOrigFile.getOriginalFile().getVirtualFile();
if (origVirtualFile == null) return new ModNothing();
if (myDeleted) {
return new ModDeleteFile(origVirtualFile);
}
myManager.commitDocument(myDocument);
unblock();
String newText = myTargetFile.getText();
return myOrigText.equals(newText) ? new ModNothing() :
new ModUpdateFileText(origVirtualFile, myOrigText, newText, myFragments);
@@ -201,7 +207,6 @@ final class PsiUpdateImpl {
private final @NotNull FileTracker myTracker;
private final @NotNull Map<PsiFile, FileTracker> myChangedFiles = new LinkedHashMap<>();
private final @NotNull Map<VirtualFile, ModPsiUpdaterImpl.ChangedDirectoryInfo> myChangedDirectories = new LinkedHashMap<>();
private final @NotNull Set<VirtualFile> myDeletedFiles = new LinkedHashSet<>();
private @NotNull VirtualFile myNavigationFile;
private int myCaretOffset;
private @NotNull TextRange mySelection;
@@ -220,10 +225,14 @@ final class PsiUpdateImpl {
@NotNull Stream<ModCommand> createFileCommands(@NotNull Project project) {
PsiManager manager = PsiManager.getInstance(project);
PsiDocumentManager documentManager = PsiDocumentManager.getInstance(project);
return directory().getAddedChildren().values().stream()
.map(vf -> {
PsiFile psiFile = manager.findFile(vf);
if (psiFile == null) return nop();
Document document = psiFile.getViewProvider().getDocument();
documentManager.commitDocument(document);
documentManager.doPostponedOperationsAndUnblockDocument(document);
return new ModCreateFile(new FutureVirtualFile(directory().getOriginalFile(),
vf.getName(), vf.getFileType()), psiFile.getText());
});
@@ -271,17 +280,6 @@ final class PsiUpdateImpl {
return tracker(file).getCopy(e);
}
@Override
public void deleteFile(@NotNull PsiFile file) {
PsiFile originalFile = file.getOriginalFile();
VirtualFile virtualFile = originalFile.getViewProvider().getVirtualFile();
if (virtualFile instanceof ChangedVirtualDirectory.AddedVirtualFile avf) {
avf.delete(null);
} else {
myDeletedFiles.add(virtualFile);
}
}
@Override
public void dispose() {
}
@@ -447,7 +445,6 @@ final class PsiUpdateImpl {
.andThen(myChangedDirectories.values().stream()
.flatMap(info -> info.createFileCommands(myTracker.myProject))
.reduce(nop(), ModCommand::andThen))
.andThen(myDeletedFiles.stream().<ModCommand>map(df -> new ModDeleteFile(df)).reduce(nop(), ModCommand::andThen))
.andThen(getNavigateCommand()).andThen(getHighlightCommand()).andThen(myRenameSymbol == null ? nop() : myRenameSymbol);
}
@@ -33,6 +33,7 @@ public class PackageDotHtmlMayBePackageInfoInspection extends BaseInspection {
return new PackageDotHtmlMayBePackageInfoFix(aPackage);
}
@SuppressWarnings("DialogTitleCapitalization")
@NotNull
@Override
protected String buildErrorString(Object... infos) {
@@ -58,7 +59,7 @@ public class PackageDotHtmlMayBePackageInfoInspection extends BaseInspection {
@Override
protected void applyFix(@NotNull Project project, @NotNull PsiElement element, @NotNull ModPsiUpdater updater) {
if (element instanceof XmlFile xmlFile) {
updater.deleteFile(xmlFile);
xmlFile.delete();
}
}
}
@@ -104,7 +105,7 @@ public class PackageDotHtmlMayBePackageInfoInspection extends BaseInspection {
packageInfoFile.add(comment);
}
CodeStyleManager.getInstance(project).reformat(packageInfoFile);
updater.deleteFile(xmlFile);
xmlFile.delete();
updater.moveTo(packageInfoFile);
}
@@ -0,0 +1,13 @@
<warning descr="'package.html' may be converted to 'package-info.java'"><!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
Hello from documentation!
<p>
This is a package.
</p>
</body>
</html></warning>
@@ -1,3 +0,0 @@
<warning descr="'package.html' may be converted to 'package-info.java'"><html>
this is a test file
</html></warning>
@@ -1,29 +1,36 @@
/*
* Copyright 2000-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.siyeh.ig.javadoc;
import com.intellij.codeInsight.intention.IntentionAction;
import com.intellij.codeInspection.InspectionProfileEntry;
import com.intellij.openapi.application.impl.NonBlockingReadActionImpl;
import com.intellij.psi.PsiDirectory;
import com.intellij.psi.PsiFile;
import com.siyeh.ig.LightJavaInspectionTestCase;
import org.jetbrains.annotations.Nullable;
public class PackageDotHtmlMayBePackageInfoInspectionTest extends LightJavaInspectionTestCase {
public void testSimple() {
myFixture.configureByFile("package.html");
myFixture.configureByFile("com/example/package.html");
myFixture.testHighlighting(true, false, false);
IntentionAction action = myFixture.findSingleIntention("Convert to 'package-info.java'");
PsiFile psiFile = myFixture.getFile();
PsiDirectory directory = psiFile.getContainingDirectory();
assertTrue(psiFile.isValid());
myFixture.launchAction(action);
NonBlockingReadActionImpl.waitForAsyncTaskCompletion();
assertFalse(psiFile.isValid());
PsiFile packageInfo = directory.findFile("package-info.java");
assertNotNull(packageInfo);
assertEquals("""
/**
* Hello from documentation!
* <p>
* This is a package.
* </p>
*/
package com.example;""", packageInfo.getText());
}
@Nullable