From 80fe6724fedda8b9a2dbc0bf81765b435301fdee Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Fri, 23 Jun 2023 13:59:46 +0200 Subject: [PATCH] [mod-command] Support file deletion right in PSI; fix committing new files; tests for PackageDotHtmlMayBePackageInfo fix GitOrigin-RevId: 6736011022179d86709da329d57230f76abe64dd --- .../intellij/modcommand/ModDeleteFile.java | 6 +++ .../intellij/modcommand/ModPsiUpdater.java | 8 ---- .../psi/impl/file/PsiFileImplUtil.java | 26 ++++++++++++- .../intellij/psi/impl/source/PsiFileImpl.java | 1 + .../modcommand/ModCommandServiceImpl.java | 4 +- .../lang/impl/modcommand/PsiUpdateImpl.java | 27 ++++++------- ...kageDotHtmlMayBePackageInfoInspection.java | 5 ++- .../com/example/package.html | 13 +++++++ .../package.html | 3 -- ...DotHtmlMayBePackageInfoInspectionTest.java | 39 +++++++++++-------- 10 files changed, 86 insertions(+), 46 deletions(-) create mode 100644 plugins/InspectionGadgets/test/com/siyeh/igtest/javadoc/package_dot_html_may_be_package_info/com/example/package.html delete mode 100644 plugins/InspectionGadgets/test/com/siyeh/igtest/javadoc/package_dot_html_may_be_package_info/package.html diff --git a/platform/analysis-api/src/com/intellij/modcommand/ModDeleteFile.java b/platform/analysis-api/src/com/intellij/modcommand/ModDeleteFile.java index 03c22a3dad1f..30c1402a9c77 100644 --- a/platform/analysis-api/src/com/intellij/modcommand/ModDeleteFile.java +++ b/platform/analysis-api/src/com/intellij/modcommand/ModDeleteFile.java @@ -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); + } } diff --git a/platform/analysis-api/src/com/intellij/modcommand/ModPsiUpdater.java b/platform/analysis-api/src/com/intellij/modcommand/ModPsiUpdater.java index 14c2079c7d1d..9693818c0165 100644 --- a/platform/analysis-api/src/com/intellij/modcommand/ModPsiUpdater.java +++ b/platform/analysis-api/src/com/intellij/modcommand/ModPsiUpdater.java @@ -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 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 * diff --git a/platform/core-impl/src/com/intellij/psi/impl/file/PsiFileImplUtil.java b/platform/core-impl/src/com/intellij/psi/impl/file/PsiFileImplUtil.java index b97265bbbb9b..b1d585daa765 100644 --- a/platform/core-impl/src/com/intellij/psi/impl/file/PsiFileImplUtil.java +++ b/platform/core-impl/src/com/intellij/psi/impl/file/PsiFileImplUtil.java @@ -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> 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 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(); diff --git a/platform/core-impl/src/com/intellij/psi/impl/source/PsiFileImpl.java b/platform/core-impl/src/com/intellij/psi/impl/source/PsiFileImpl.java index a6e6da29c887..4aabd12e12db 100644 --- a/platform/core-impl/src/com/intellij/psi/impl/source/PsiFileImpl.java +++ b/platform/core-impl/src/com/intellij/psi/impl/source/PsiFileImpl.java @@ -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); diff --git a/platform/lang-impl/src/com/intellij/lang/impl/modcommand/ModCommandServiceImpl.java b/platform/lang-impl/src/com/intellij/lang/impl/modcommand/ModCommandServiceImpl.java index d29deb58b595..c34d2811c4b8 100644 --- a/platform/lang-impl/src/com/intellij/lang/impl/modcommand/ModCommandServiceImpl.java +++ b/platform/lang-impl/src/com/intellij/lang/impl/modcommand/ModCommandServiceImpl.java @@ -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; }); } diff --git a/platform/lang-impl/src/com/intellij/lang/impl/modcommand/PsiUpdateImpl.java b/platform/lang-impl/src/com/intellij/lang/impl/modcommand/PsiUpdateImpl.java index 40682902e476..0184e34bbc38 100644 --- a/platform/lang-impl/src/com/intellij/lang/impl/modcommand/PsiUpdateImpl.java +++ b/platform/lang-impl/src/com/intellij/lang/impl/modcommand/PsiUpdateImpl.java @@ -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 myChangedFiles = new LinkedHashMap<>(); private final @NotNull Map myChangedDirectories = new LinkedHashMap<>(); - private final @NotNull Set myDeletedFiles = new LinkedHashSet<>(); private @NotNull VirtualFile myNavigationFile; private int myCaretOffset; private @NotNull TextRange mySelection; @@ -220,10 +225,14 @@ final class PsiUpdateImpl { @NotNull Stream 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().map(df -> new ModDeleteFile(df)).reduce(nop(), ModCommand::andThen)) .andThen(getNavigateCommand()).andThen(getHighlightCommand()).andThen(myRenameSymbol == null ? nop() : myRenameSymbol); } diff --git a/plugins/InspectionGadgets/src/com/siyeh/ig/javadoc/PackageDotHtmlMayBePackageInfoInspection.java b/plugins/InspectionGadgets/src/com/siyeh/ig/javadoc/PackageDotHtmlMayBePackageInfoInspection.java index 51274ea85daa..4998c7f13f63 100644 --- a/plugins/InspectionGadgets/src/com/siyeh/ig/javadoc/PackageDotHtmlMayBePackageInfoInspection.java +++ b/plugins/InspectionGadgets/src/com/siyeh/ig/javadoc/PackageDotHtmlMayBePackageInfoInspection.java @@ -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); } diff --git a/plugins/InspectionGadgets/test/com/siyeh/igtest/javadoc/package_dot_html_may_be_package_info/com/example/package.html b/plugins/InspectionGadgets/test/com/siyeh/igtest/javadoc/package_dot_html_may_be_package_info/com/example/package.html new file mode 100644 index 000000000000..6778b2fcacf1 --- /dev/null +++ b/plugins/InspectionGadgets/test/com/siyeh/igtest/javadoc/package_dot_html_may_be_package_info/com/example/package.html @@ -0,0 +1,13 @@ + + + + + Title + + + Hello from documentation! +

+ This is a package. +

+ +
\ No newline at end of file diff --git a/plugins/InspectionGadgets/test/com/siyeh/igtest/javadoc/package_dot_html_may_be_package_info/package.html b/plugins/InspectionGadgets/test/com/siyeh/igtest/javadoc/package_dot_html_may_be_package_info/package.html deleted file mode 100644 index 2d408d629217..000000000000 --- a/plugins/InspectionGadgets/test/com/siyeh/igtest/javadoc/package_dot_html_may_be_package_info/package.html +++ /dev/null @@ -1,3 +0,0 @@ - -this is a test file - \ No newline at end of file diff --git a/plugins/InspectionGadgets/testsrc/com/siyeh/ig/javadoc/PackageDotHtmlMayBePackageInfoInspectionTest.java b/plugins/InspectionGadgets/testsrc/com/siyeh/ig/javadoc/PackageDotHtmlMayBePackageInfoInspectionTest.java index b99fe1288714..99e1696e4a47 100644 --- a/plugins/InspectionGadgets/testsrc/com/siyeh/ig/javadoc/PackageDotHtmlMayBePackageInfoInspectionTest.java +++ b/plugins/InspectionGadgets/testsrc/com/siyeh/ig/javadoc/PackageDotHtmlMayBePackageInfoInspectionTest.java @@ -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! + *

+ * This is a package. + *

+ */ + package com.example;""", packageInfo.getText()); } @Nullable