mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[java-inspections] IDEA-341641 Intention to convert implicit class to explicit and vice versa
- new inspection to convert ordinary classes into implicitly declared classes GitOrigin-RevId: 4a1756d52b672edafcb43a5f9f725a64bb19fa6d
This commit is contained in:
committed by
intellij-monorepo-bot
parent
164ad4178b
commit
36bab0a817
+165
@@ -0,0 +1,165 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.codeInspection;
|
||||
|
||||
import com.intellij.codeInsight.daemon.impl.analysis.HighlightingFeature;
|
||||
import com.intellij.codeInsight.daemon.impl.analysis.JavaModuleGraphUtil;
|
||||
import com.intellij.java.JavaBundle;
|
||||
import com.intellij.modcommand.ModPsiUpdater;
|
||||
import com.intellij.modcommand.PsiUpdateModCommandQuickFix;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.search.PackageScope;
|
||||
import com.intellij.psi.search.PsiSearchHelper;
|
||||
import com.intellij.psi.search.searches.ReferencesSearch;
|
||||
import com.intellij.psi.util.PsiMethodUtil;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.siyeh.ig.psiutils.CommentTracker;
|
||||
import org.jetbrains.annotations.Nls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public final class ExplicitToImplicitClassMigrationInspection extends AbstractBaseJavaLocalInspectionTool {
|
||||
|
||||
private static final String JAVA_SUFFIX = ".java";
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) {
|
||||
if (!HighlightingFeature.IMPLICIT_CLASSES.isAvailable(holder.getFile())) return PsiElementVisitor.EMPTY_VISITOR;
|
||||
return new JavaElementVisitor() {
|
||||
@Override
|
||||
public void visitClass(@NotNull PsiClass aClass) {
|
||||
if (aClass.isInterface() || aClass.isRecord() || aClass.isEnum()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (aClass.getContainingClass() != null) {
|
||||
return;
|
||||
}
|
||||
PsiJavaFile file = (PsiJavaFile)aClass.getContainingFile();
|
||||
if (file.getPackageStatement() != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (file.getClasses().length != 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
PsiJavaModule javaModule = JavaModuleGraphUtil.findDescriptorByElement(aClass);
|
||||
if (javaModule != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
String fileName = file.getName();
|
||||
if (!fileName.endsWith(JAVA_SUFFIX)) {
|
||||
return;
|
||||
}
|
||||
|
||||
String className = aClass.getName();
|
||||
if (className == null || !className.equals(fileName.substring(0, fileName.length() - JAVA_SUFFIX.length()))) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (aClass.hasTypeParameters()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!PsiMethodUtil.hasMainMethod(aClass)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (aClass.getExtendsListTypes().length != 0 || aClass.getImplementsListTypes().length != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (aClass.hasModifierProperty(PsiModifier.SEALED) || aClass.hasModifierProperty(PsiModifier.ABSTRACT)) {
|
||||
return;
|
||||
}
|
||||
|
||||
PsiMethod[] constructors = aClass.getConstructors();
|
||||
if (constructors.length > 0) {
|
||||
if (constructors.length > 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
PsiMethod constructor = constructors[0];
|
||||
if (constructor.hasParameters() ||
|
||||
constructor.hasModifierProperty(PsiModifier.PRIVATE) ||
|
||||
(constructor.getBody() != null && constructor.getBody().getStatements().length > 0)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Project project = aClass.getProject();
|
||||
PsiPackage aPackage = JavaPsiFacade.getInstance(project).findPackage(file.getPackageName());
|
||||
if (aPackage == null) {
|
||||
return;
|
||||
}
|
||||
PsiIdentifier classIdentifier = aClass.getNameIdentifier();
|
||||
if (classIdentifier == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
PackageScope scope = new PackageScope(aPackage, false, false);
|
||||
if (isOnTheFly) {
|
||||
final PsiSearchHelper searchHelper = PsiSearchHelper.getInstance(project);
|
||||
final PsiSearchHelper.SearchCostResult cost =
|
||||
searchHelper.isCheapEnoughToSearch(className, scope, null, null);
|
||||
if (cost == PsiSearchHelper.SearchCostResult.TOO_MANY_OCCURRENCES) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
PsiReference first = ReferencesSearch.search(aClass, scope).findFirst();
|
||||
if (first != null) {
|
||||
return;
|
||||
}
|
||||
PsiElement lBrace = aClass.getLBrace();
|
||||
PsiElement rBrace = aClass.getRBrace();
|
||||
if (lBrace == null || rBrace == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (PsiTreeUtil.hasErrorElements(aClass)) {
|
||||
return;
|
||||
}
|
||||
|
||||
holder.registerProblem(classIdentifier, JavaBundle.message("inspection.explicit.to.implicit.class.migration.name"),
|
||||
new ReplaceWithImplicitClassFix());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
private static class ReplaceWithImplicitClassFix extends PsiUpdateModCommandQuickFix {
|
||||
|
||||
@Nls(capitalization = Nls.Capitalization.Sentence)
|
||||
@NotNull
|
||||
@Override
|
||||
public String getFamilyName() {
|
||||
return JavaBundle.message("inspection.explicit.to.implicit.class.migration.fix.name");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void applyFix(@NotNull Project project, @NotNull PsiElement element, @NotNull ModPsiUpdater updater) {
|
||||
PsiClass psiClass = PsiTreeUtil.getParentOfType(element, PsiClass.class);
|
||||
if (psiClass == null) {
|
||||
return;
|
||||
}
|
||||
StringBuilder builder = new StringBuilder();
|
||||
PsiElement lBrace = psiClass.getLBrace();
|
||||
PsiElement rBrace = psiClass.getRBrace();
|
||||
if (lBrace == null || rBrace == null) {
|
||||
return;
|
||||
}
|
||||
PsiElement psiElement = lBrace.getNextSibling();
|
||||
CommentTracker tracker = new CommentTracker();
|
||||
while (psiElement != null && psiElement != rBrace) {
|
||||
builder.append(tracker.text(psiElement));
|
||||
psiElement = psiElement.getNextSibling();
|
||||
}
|
||||
PsiImplicitClass newClass = PsiElementFactory.getInstance(project).createImplicitClassFromText(builder.toString(), psiClass);
|
||||
PsiElement replaced = tracker.replace(psiClass, newClass);
|
||||
tracker.insertCommentsBefore(replaced);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1534,6 +1534,13 @@
|
||||
implementationClass="com.intellij.codeInspection.ImplicitToExplicitClassBackwardMigrationInspection"
|
||||
bundle="messages.JavaBundle"
|
||||
key="inspection.implicit.to.explicit.class.backward.migration.name"/>
|
||||
<localInspection groupPathKey="group.path.names.java.language.level.specific.issues.and.migration.aids" language="JAVA"
|
||||
groupBundle="messages.InspectionsBundle"
|
||||
groupKey="group.names.language.level.specific.issues.and.migration.aids21" enabledByDefault="true" level="WARNING"
|
||||
implementationClass="com.intellij.codeInspection.ExplicitToImplicitClassMigrationInspection"
|
||||
bundle="messages.JavaBundle"
|
||||
editorAttributes="NOT_USED_ELEMENT_ATTRIBUTES"
|
||||
key="inspection.explicit.to.implicit.class.migration.name"/>
|
||||
<localInspection groupPathKey="group.path.names.java.language.level.specific.issues.and.migration.aids" language="JAVA" shortName="TextBlockBackwardMigration"
|
||||
groupBundle="messages.InspectionsBundle"
|
||||
groupKey="group.names.language.level.specific.issues.and.migration.aids15" enabledByDefault="true" level="INFORMATION"
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
<html>
|
||||
<body>
|
||||
Reports ordinary classes, which can be converted into implicitly declared classes
|
||||
<p><b>Example:</b></p>
|
||||
<pre><code>
|
||||
public class Sample {
|
||||
public static void main(String[] args) {
|
||||
System.out.println("Hello, world!");
|
||||
}
|
||||
}
|
||||
</code></pre>
|
||||
<p>After the quick-fix is applied:</p>
|
||||
<pre><code>
|
||||
public static void main(String[] args) {
|
||||
System.out.println("Hello, world!");
|
||||
}
|
||||
</code></pre>
|
||||
<!-- tooltip end -->
|
||||
<p>
|
||||
Implicitly declared classes appeared in Java 21 (Preview).
|
||||
</p>
|
||||
<p><small>New in 2024.1</small></p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -90,7 +90,7 @@ public interface PsiClass
|
||||
/**
|
||||
* Returns the array of class types for the classes that this class or interface extends.
|
||||
*
|
||||
* @return the array of extended class types, or an empty list for anonymous classes and unnamed classes.
|
||||
* @return the array of extended class types, or an empty list for anonymous classes and implicitly declared classes.
|
||||
*/
|
||||
PsiClassType @NotNull [] getExtendsListTypes();
|
||||
|
||||
@@ -98,7 +98,7 @@ public interface PsiClass
|
||||
* Returns the array of class types for the interfaces that this class implements.
|
||||
*
|
||||
* @return the array of extended class types, or an empty list for anonymous classes,
|
||||
* enums, annotation types and unnamed classes
|
||||
* enums, annotation types and implicitly declared classes
|
||||
*/
|
||||
PsiClassType @NotNull [] getImplementsListTypes();
|
||||
|
||||
|
||||
@@ -98,6 +98,18 @@ public interface PsiJavaParserFacade {
|
||||
@NotNull
|
||||
PsiParameter createParameterFromText(@NotNull @NonNls String text, @Nullable PsiElement context) throws IncorrectOperationException;
|
||||
|
||||
/**
|
||||
* Creates an implicit class from the specified body text (the text between the braces).
|
||||
*
|
||||
* @param body the body text of the class to create.
|
||||
* @param context the PSI element used as context for resolving references which cannot be resolved
|
||||
* within the class.
|
||||
* @return created class instance.
|
||||
* @throws IncorrectOperationException if the text is not a valid class body.
|
||||
*/
|
||||
@NotNull
|
||||
PsiImplicitClass createImplicitClassFromText(@NotNull String body, @Nullable PsiElement context) throws IncorrectOperationException;
|
||||
|
||||
/**
|
||||
* Creates a Java record header from the specified text (excluding parentheses).
|
||||
*
|
||||
|
||||
@@ -139,6 +139,20 @@ public class PsiJavaParserFacadeImpl implements PsiJavaParserFacade {
|
||||
return classes[0];
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public PsiImplicitClass createImplicitClassFromText(@NotNull String body, @Nullable PsiElement context) throws IncorrectOperationException {
|
||||
PsiJavaFile aFile = createDummyJavaFile(body);
|
||||
PsiClass[] classes = aFile.getClasses();
|
||||
if (classes.length != 1) {
|
||||
throw new IncorrectOperationException("Incorrect class '" + body + "'");
|
||||
}
|
||||
if (classes[0] instanceof PsiImplicitClass) {
|
||||
return (PsiImplicitClass)classes[0];
|
||||
}
|
||||
throw new IncorrectOperationException("Incorrect implicit class '" + body + "'");
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public PsiClass createRecord(@NotNull String name) throws IncorrectOperationException {
|
||||
return createRecordFromText("public record " + name + "() { }");
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
// "Convert into implicitly declared class" "true-preview"
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.out.println("Hello, world!");
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
// "Convert into implicitly declared class" "true-preview"
|
||||
|
||||
/**
|
||||
* comments
|
||||
*/ /*comments2*/ public static void main(String[] args) {
|
||||
System.out.println("Hello, world!");
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
// "Convert into implicitly declared class" "false"
|
||||
public class AnotherFil<caret>eName {
|
||||
public static void main(String[] args) {
|
||||
System.out.println("Hello, world!");
|
||||
}
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
// "Convert into implicitly declared class" "false"
|
||||
|
||||
public interface beforeInterfa<caret>ce {
|
||||
public static void main(String[] args) {
|
||||
System.out.println("Hello, world!");
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
// "Convert into implicitly declared class" "false"
|
||||
|
||||
public class beforeSeveralSi<caret>mple {
|
||||
public static void main(String[] args) {
|
||||
System.out.println("Hello, world!");
|
||||
}
|
||||
}
|
||||
|
||||
class SecondClass{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// "Convert into implicitly declared class" "true-preview"
|
||||
|
||||
public class beforeSi<caret>mple {
|
||||
public static void main(String[] args) {
|
||||
System.out.println("Hello, world!");
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
// "Convert into implicitly declared class" "true-preview"
|
||||
|
||||
/**
|
||||
* comments
|
||||
*/
|
||||
|
||||
public /*comments2*/ class beforeSi<caret>mpleWithComments {
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.out.println("Hello, world!");
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
// "Convert into implicitly declared class" "false"
|
||||
public class beforeWithConstructo<caret>r {
|
||||
|
||||
public beforeWithConstructor(String t) {
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.out.println("Hello, world!");
|
||||
}
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
// "Convert into implicitly declared class" "false"
|
||||
public class beforeWithExtendLis<caret>t extends Something {
|
||||
public static void main(String[] args) {
|
||||
System.out.println("Hello, world!");
|
||||
}
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
// "Convert into implicitly declared class" "false"
|
||||
public class be<caret>foreWithGeneric<T> {
|
||||
public static void main(String[] args) {
|
||||
System.out.println("Hello, world!");
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
// "Convert into implicitly declared class" "false"
|
||||
package a;
|
||||
|
||||
public class beforeWithPackag<caret>e {
|
||||
public static void main(String[] args) {
|
||||
System.out.println("Hello, world!");
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
// "Convert into implicitly declared class" "false"
|
||||
public class beforeWi<caret>thSyntaxError {
|
||||
|
||||
public static void main(String[] args) {
|
||||
error error error;
|
||||
System.out.println("Hello, world!");
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
// "Convert into implicitly declared class" "false"
|
||||
public class beforeWithUsage<caret>s {
|
||||
|
||||
public static void main(String[] args) {
|
||||
new beforeWithUsages();
|
||||
System.out.println("Hello, world!");
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
// Copyright 2000-2023 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.
|
||||
package com.intellij.java.codeInspection;
|
||||
|
||||
import com.intellij.codeInsight.daemon.quickFix.LightQuickFixParameterizedTestCase;
|
||||
import com.intellij.codeInspection.ExplicitToImplicitClassMigrationInspection;
|
||||
import com.intellij.codeInspection.LocalInspectionTool;
|
||||
import com.intellij.pom.java.LanguageLevel;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class ExplicitToImplicitClassMigrationInspectionInspectionTest extends LightQuickFixParameterizedTestCase {
|
||||
@Override
|
||||
protected LocalInspectionTool @NotNull [] configureLocalInspectionTools() {
|
||||
return new LocalInspectionTool[]{new ExplicitToImplicitClassMigrationInspection()};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getBasePath() {
|
||||
return "/inspection/explicitToImplicitClassMigration/";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected LanguageLevel getLanguageLevel() {
|
||||
return LanguageLevel.JDK_22_PREVIEW;
|
||||
}
|
||||
}
|
||||
@@ -748,6 +748,8 @@ inspection.string.template.migration.concatenation.message=Concatenation can be
|
||||
inspection.string.template.migration.name=String template can be used
|
||||
inspection.implicit.to.explicit.class.backward.migration.name=Implicitly declared class can be replaced with ordinary class
|
||||
inspection.implicit.to.explicit.class.backward.migration.fix.name=Convert implicitly declared class into regular class
|
||||
inspection.explicit.to.implicit.class.migration.name=Explicit class declaration can be converted into implicitly declared class
|
||||
inspection.explicit.to.implicit.class.migration.fix.name=Convert into implicitly declared class
|
||||
inspection.inconsistent.text.block.indent.name=Inconsistent whitespace indentation in text block
|
||||
inspection.inconsistent.text.block.indent.message=Text block indent consists of tabs and spaces
|
||||
inspection.inconsistent.text.block.indent.spaces.to.tabs.one.to.one.fix=Replace spaces with tabs (1 space = 1 tab)
|
||||
|
||||
Reference in New Issue
Block a user