[java-highlighting] Provide quick-fix for the "Package annotations should be in file package-info.java" error

IDEA-216254

GitOrigin-RevId: 9d8c4074babff861b5702caa09dd320de5d63d72
This commit is contained in:
Andrey Cherkasov
2022-12-22 15:59:18 +00:00
committed by intellij-monorepo-bot
parent 71ed47f845
commit 7bf9474dac
7 changed files with 200 additions and 21 deletions
@@ -615,6 +615,7 @@ inspection.unsatisfied.range.display.name=Return value is outside of declared ra
inspection.unsatisfied.range.message=Return value range ''{0}'' is outside of declared range ''{1}''
inspection.unsatisfied.range.message.value=Return value ''{0}'' is outside of declared range ''{1}''
intention.family.name.move.annotation.to.array=Move annotation to array
move.annotations.to.package.info.file.family.name=Move annotations to 'package-info.java'
inspection.redundant.requires.statement.message.transitive.dependencies.on.can.be.used.directly=Transitive dependencies on ''{0}'' can be used directly.
inspection.redundant.requires.statement.message.module.unused=No usages of module packages are found.
@@ -626,7 +626,13 @@ public final class AnnotationsHighlightUtil {
PsiModifierList annotationList = statement.getAnnotationList();
if (annotationList != null && !PsiPackage.PACKAGE_INFO_FILE.equals(file.getName())) {
String message = JavaErrorBundle.message("invalid.package.annotation.containing.file");
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(annotationList).descriptionAndTooltip(message);
IntentionAction deleteFix =
QUICK_FIX_FACTORY.createDeleteFix(annotationList, JavaAnalysisBundle.message("intention.text.remove.annotation"));
HighlightInfo.Builder builder =
HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(annotationList).descriptionAndTooltip(message);
IntentionAction moveAnnotationToPackageInfoFileFix = new MoveAnnotationToPackageInfoFileFix(statement);
return builder.registerFix(deleteFix, null, null, null, null)
.registerFix(moveAnnotationToPackageInfoFileFix, null, null, null, null);
}
return null;
}
@@ -0,0 +1,152 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.daemon.impl.analysis;
import com.intellij.codeInsight.intention.preview.IntentionPreviewInfo;
import com.intellij.codeInspection.LocalQuickFixAndIntentionActionOnPsiElement;
import com.intellij.ide.highlighter.JavaFileType;
import com.intellij.java.analysis.JavaAnalysisBundle;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.ObjectUtils;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class MoveAnnotationToPackageInfoFileFix extends LocalQuickFixAndIntentionActionOnPsiElement {
protected MoveAnnotationToPackageInfoFileFix(@NotNull PsiPackageStatement pkgStatement) {
super(pkgStatement);
}
@Override
public boolean isAvailable(@NotNull Project project,
@NotNull PsiFile file,
@NotNull PsiElement startElement,
@NotNull PsiElement endElement) {
PsiPackageStatement packageStatement = (PsiPackageStatement)startElement;
PsiPackage aPackage = getPackage(packageStatement);
if (aPackage == null) return false;
PsiFile packageInfoFile = getPackageInfoFile(aPackage);
if (packageInfoFile == null) return true;
if (!PsiPackage.PACKAGE_INFO_FILE.equals(packageInfoFile.getName())) return false;
PsiPackageStatement packageStatementInPackageInfoFile = PsiTreeUtil.findChildOfType(packageInfoFile, PsiPackageStatement.class);
if (packageStatementInPackageInfoFile == null) return false;
PsiModifierList missingAnnotations = findMissingAnnotations(packageStatement, packageStatementInPackageInfoFile);
return missingAnnotations != null && missingAnnotations.getAnnotations().length != 0;
}
@Override
public void invoke(@NotNull Project project,
@NotNull PsiFile file,
@Nullable Editor editor,
@NotNull PsiElement startElement,
@NotNull PsiElement endElement) {
PsiPackageStatement packageStatement = (PsiPackageStatement)startElement;
PsiPackage aPackage = getPackage(packageStatement);
if (aPackage == null) return;
PsiFile packageInfoFile = getPackageInfoFile(aPackage);
if (packageInfoFile == null) {
PsiFile createdFile = file.getContainingDirectory().createFile(PsiPackage.PACKAGE_INFO_FILE);
createdFile.add(packageStatement);
createdFile.navigate(true);
}
else if (PsiPackage.PACKAGE_INFO_FILE.equals(packageInfoFile.getName())) {
PsiFile modifiedPackageInfoFile = moveAnnotationsAndGetFile(packageStatement, packageInfoFile);
if (modifiedPackageInfoFile != null) {
modifiedPackageInfoFile.navigate(true);
}
}
}
private static @Nullable PsiPackage getPackage(@NotNull PsiPackageStatement packageStatement) {
PsiJavaCodeReferenceElement packageReference = packageStatement.getPackageReference();
return ObjectUtils.tryCast(packageReference.resolve(), PsiPackage.class);
}
private static @Nullable PsiFile moveAnnotationsAndGetFile(@NotNull PsiPackageStatement packageStatement,
@NotNull PsiFile packageInfoFile) {
PsiPackageStatement packageStatementInPackageInfoFile = PsiTreeUtil.findChildOfType(packageInfoFile, PsiPackageStatement.class);
if (packageStatementInPackageInfoFile == null) return null;
PsiModifierList missingAnnotations = findMissingAnnotations(packageStatement, packageStatementInPackageInfoFile);
if (missingAnnotations == null) return null;
PsiModifierList annotationList = packageStatementInPackageInfoFile.getAnnotationList();
if (annotationList != null) {
StreamEx.of(missingAnnotations.getAnnotations()).forEach(annotationList::add);
}
else {
packageStatementInPackageInfoFile.addBefore(missingAnnotations, packageStatementInPackageInfoFile.getFirstChild());
}
return packageInfoFile;
}
private static @Nullable PsiModifierList findMissingAnnotations(@NotNull PsiPackageStatement packageStatement,
@NotNull PsiPackageStatement packageStatementInPackageInfoFile) {
PsiModifierList annotationList = packageStatementInPackageInfoFile.getAnnotationList();
if (annotationList == null) return packageStatement.getAnnotationList();
PsiPackageStatement copy = (PsiPackageStatement)packageStatement.copy();
StreamEx.of(copy.getAnnotationList().getChildren())
.select(PsiAnnotation.class)
.filter(annotation -> {
String qualifiedName = annotation.getQualifiedName();
return qualifiedName != null && annotationList.hasAnnotation(qualifiedName);
})
.forEach(PsiElement::delete);
return copy.getAnnotationList();
}
@Override
public @NotNull IntentionPreviewInfo generatePreview(@NotNull Project project, @NotNull Editor editor, @NotNull PsiFile file) {
PsiPackageStatement packageStatement = (PsiPackageStatement)myStartElement.getElement();
assert packageStatement != null;
PsiPackage aPackage = getPackage(packageStatement);
if (aPackage == null) return IntentionPreviewInfo.EMPTY;
PsiFile packageInfoFile = getPackageInfoFile(aPackage);
if (packageInfoFile == null) {
return new IntentionPreviewInfo.CustomDiff(JavaFileType.INSTANCE, PsiPackage.PACKAGE_INFO_FILE, "", packageStatement.getText());
}
else if (PsiPackage.PACKAGE_INFO_FILE.equals(packageInfoFile.getName())) {
PsiFile modifiedPackageInfoFile = moveAnnotationsAndGetFile(packageStatement, (PsiFile)packageInfoFile.copy());
if (modifiedPackageInfoFile == null) return IntentionPreviewInfo.EMPTY;
return new IntentionPreviewInfo.CustomDiff(JavaFileType.INSTANCE, PsiPackage.PACKAGE_INFO_FILE, packageInfoFile.getText(),
modifiedPackageInfoFile.getText());
}
return IntentionPreviewInfo.EMPTY;
}
@Override
public @NotNull String getText() {
return getFamilyName();
}
@Override
public @NotNull String getFamilyName() {
return JavaAnalysisBundle.message("move.annotations.to.package.info.file.family.name");
}
/**
* Returns either a `package-info.java` or `package.html` file for the specified package.
*
* @param aPackage the package
* @return a `package-info.java` or `package.html` file or {@code null}
* if the package does not contain such files.
*/
@Contract("null -> null")
public static @Nullable PsiFile getPackageInfoFile(@Nullable PsiPackage aPackage) {
if (aPackage == null) {
return null;
}
final PsiDirectory[] directories = aPackage.getDirectories();
for (PsiDirectory directory : directories) {
final PsiFile packageInfoJava = directory.findFile(PsiPackage.PACKAGE_INFO_FILE);
if (packageInfoJava != null) return packageInfoJava;
final PsiFile packageHtml = directory.findFile("package.html");
if (packageHtml != null) {
return packageHtml;
}
}
return null;
}
}
@@ -0,0 +1,10 @@
// "Remove" "true-preview"
package com.example;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
@Target(ElementType.PACKAGE)
@interface Anno {
int value();
}
@@ -0,0 +1,11 @@
// "Remove" "true-preview"
@Anno(value=42)<caret>
package com.example;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
@Target(ElementType.PACKAGE)
@interface Anno {
int value();
}
@@ -0,0 +1,14 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.daemon.impl.quickfix;
import com.intellij.codeInsight.daemon.quickFix.LightQuickFixParameterizedTestCase;
public class RemovePackageAnnotationFixTest extends LightQuickFixParameterizedTestCase {
@Override
protected String getBasePath() {
return "/codeInsight/daemonCodeAnalyzer/quickFix/removePackageAnnotation";
}
}
@@ -1,7 +1,8 @@
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
// Copyright 2000-2022 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.analysis.AnalysisScope;
import com.intellij.codeInsight.daemon.impl.analysis.MoveAnnotationToPackageInfoFileFix;
import com.intellij.codeInsight.intention.preview.IntentionPreviewInfo;
import com.intellij.codeInspection.*;
import com.intellij.codeInspection.reference.RefPackage;
@@ -21,7 +22,6 @@ import com.siyeh.ig.BaseInspectionVisitor;
import com.siyeh.ig.BaseSharedLocalInspection;
import com.siyeh.ig.InspectionGadgetsFix;
import com.siyeh.ig.PackageGlobalInspection;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -46,7 +46,8 @@ public class MissingPackageInfoInspection extends PackageGlobalInspection {
final String packageName = refPackage.getQualifiedName();
final Project project = globalInspectionContext.getProject();
final PsiPackage aPackage = ReadAction.compute(() -> JavaPsiFacade.getInstance(project).findPackage(packageName));
boolean needsPackageInfo = ReadAction.compute(() -> !hasPackageInfoFile(aPackage) && aPackage.getClasses().length > 0);
boolean needsPackageInfo =
ReadAction.compute(() -> MoveAnnotationToPackageInfoFileFix.getPackageInfoFile(aPackage) == null && aPackage.getClasses().length > 0);
if (!needsPackageInfo) {
return null;
}
@@ -60,22 +61,6 @@ public class MissingPackageInfoInspection extends PackageGlobalInspection {
}
}
@Contract("null -> true")
static boolean hasPackageInfoFile(PsiPackage aPackage) {
if (aPackage == null) {
return true;
}
final PsiDirectory[] directories = aPackage.getDirectories();
for (PsiDirectory directory : directories) {
final boolean packageInfoFound = directory.findFile(PsiPackage.PACKAGE_INFO_FILE) != null;
final boolean packageDotHtmlFound = directory.findFile("package.html") != null;
if (packageInfoFound || packageDotHtmlFound) {
return true;
}
}
return false;
}
private static class LocalMissingPackageInfoInspection extends BaseSharedLocalInspection<MissingPackageInfoInspection> {
LocalMissingPackageInfoInspection(MissingPackageInfoInspection settingsDelegate) {
@@ -143,7 +128,7 @@ public class MissingPackageInfoInspection extends PackageGlobalInspection {
return;
}
final PsiPackage aPackage = (PsiPackage)target;
if (hasPackageInfoFile(aPackage)) {
if (MoveAnnotationToPackageInfoFileFix.getPackageInfoFile(aPackage) != null) {
return;
}
registerError(packageReference, packageStatement);