Java: correct find usages on default constructor (IDEA-67203)

GitOrigin-RevId: 5ce84cf2a8444fbf666f762af14090d000195c35
This commit is contained in:
Bas Leijdekkers
2025-10-29 23:18:10 +00:00
committed by intellij-monorepo-bot
parent b5ea94c4a7
commit 91b5aadb07
12 changed files with 82 additions and 25 deletions
@@ -26,6 +26,7 @@ import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.codeStyle.JavaCodeStyleSettings;
import com.intellij.psi.codeStyle.VariableKind;
import com.intellij.psi.impl.FindSuperElementsHelper;
import com.intellij.psi.impl.light.LightDefaultConstructor;
import com.intellij.psi.impl.light.LightRecordMethod;
import com.intellij.psi.impl.source.javadoc.PsiDocParamRef;
import com.intellij.psi.javadoc.PsiDocTag;
@@ -131,6 +132,7 @@ public class JavaSafeDeleteProcessor extends SafeDeleteProcessorDelegateBase {
@Nullable Module module,
@NotNull Collection<? extends PsiElement> allElementsToDelete) {
Project project = element.getProject();
if (element instanceof SyntheticElement && !(element instanceof LightDefaultConstructor)) return null;
if (element instanceof PsiPackage aPackage && module != null) {
PsiDirectory[] directories = aPackage.getDirectories(module.getModuleScope());
if (directories.length == 0) return null;
@@ -145,6 +147,9 @@ public class JavaSafeDeleteProcessor extends SafeDeleteProcessorDelegateBase {
if (ApplicationManager.getApplication().isUnitTestMode()) {
return Collections.singletonList(element);
}
if (method.isDefaultConstructor()) {
return Collections.singletonList(method.getContainingClass());
}
PsiMethod[] methods = SuperMethodWarningUtil.checkSuperMethods(method, allElementsToDelete);
if (methods.length == 0) return null;
ArrayList<PsiMethod> psiMethods = new ArrayList<>(Arrays.asList(methods));
@@ -6,7 +6,6 @@ import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.util.Computable;
import com.intellij.psi.*;
import com.intellij.psi.augment.PsiExtensionMethod;
import com.intellij.psi.impl.light.LightDefaultConstructor;
import com.intellij.psi.impl.light.LightRecordCanonicalConstructor;
import com.intellij.psi.impl.light.LightRecordMember;
import com.intellij.psi.javadoc.PsiDocTag;
@@ -162,9 +161,6 @@ public class JavaTargetElementEvaluator extends TargetElementEvaluatorEx2 implem
if (refElement instanceof LightRecordCanonicalConstructor) {
return ((LightRecordCanonicalConstructor)refElement).getContainingClass();
}
if (refElement instanceof LightDefaultConstructor def) {
return def.getContainingClass();
}
}
return super.adjustReferenceOrReferencedElement(file, editor, offset, flags, refElement);
}
@@ -5,7 +5,6 @@ import com.intellij.codeInsight.completion.command.CompletionCommand
import com.intellij.codeInsight.completion.command.commands.AbstractSafeDeleteCompletionCommandProvider
import com.intellij.codeInsight.completion.command.commands.DirectInspectionFixCompletionCommand
import com.intellij.codeInsight.completion.command.getCommandContext
import com.intellij.codeInsight.completion.command.getTargetContext
import com.intellij.openapi.editor.Editor
import com.intellij.psi.*
import com.intellij.psi.util.PsiTreeUtil
@@ -33,8 +32,6 @@ private class JavaSafeDeleteCompletionCommandProvider : AbstractSafeDeleteComple
return targetElement
}
}
val targetContext = getTargetContext(offset, editor)
if (targetContext?.isWritable != true) return null
return element
}
@@ -0,0 +1,10 @@
public class Animal {
public static void main(String[] args) {
Animal animal = new <caret>Animal();
}
}
class OneLeggedDog extends Animal {
OneLeggedDog() {
}
}
@@ -0,0 +1,6 @@
public class DefaultConstructor {
public static void main(String[] args) {
DefaultConstructor constructor = new <caret>DefaultConstructor();
}
}
@@ -0,0 +1,6 @@
public class HeadlessHorseman {
public static void main(String[] args) {
HeadlessHorseman constructor = new HeadlessHorseman();
}
}
@@ -1,4 +1,4 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.java.psi.search;
import com.intellij.openapi.actionSystem.CommonDataKeys;
@@ -47,6 +47,18 @@ public class FindUsagesTargetTest extends LightJavaCodeInsightFixtureTestCase {
assertTrue(((PsiMethod)element).isConstructor());
assertEquals("Xyz", ((PsiMethod)element).getName());
}
public void testDefaultConstructor() {
myFixture.configureByText("OneLeggedDog.java", """
public class OneLeggedDog {
static void main(){
OneLeggedDog dog = new <caret>OneLeggedDog();
}
}
""");
PsiElement element = getTargetElement();
assertTrue(element instanceof PsiMethod m && m.isConstructor() && "OneLeggedDog".equals(m.getName()));
}
private PsiElement getTargetElement() {
DataContext dataContext = ((EditorEx)myFixture.getEditor()).getDataContext();
@@ -55,7 +67,7 @@ public class FindUsagesTargetTest extends LightJavaCodeInsightFixtureTestCase {
PsiElement psiElement = CommonDataKeys.PSI_ELEMENT.getData(dataContext);
UsageTarget[] targets = UsageTargetUtil.findUsageTargets(editor, psiFile, psiElement);
assertTrue(targets.length > 0);
assertTrue("<caret> is ", targets.length > 0);
assertTrue(targets[0] instanceof PsiElementUsageTarget);
return ((PsiElementUsageTarget)targets[0]).getElement();
}
@@ -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-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.java.psi.search;
import com.intellij.JavaTestUtil;
@@ -40,6 +40,7 @@ import com.intellij.util.ProcessingContext;
import com.intellij.util.Processor;
import com.intellij.util.TimeoutUtil;
import com.intellij.util.ui.UIUtil;
import com.siyeh.ig.psiutils.PsiElementOrderComparator;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
import org.jetbrains.annotations.NotNull;
@@ -119,6 +120,20 @@ public class FindUsagesTest extends JavaPsiTestCase {
((PsiCompiledElement)myJavaFacade.findClass("javax.swing.JLabel", GlobalSearchScope.allScope(myProject))).getMirror();
assertEquals(2, ReferencesSearch.search(decompiled, GlobalSearchScope.projectScope(myProject)).findAll().size());
}
public void testDefaultConstructor() {
PsiClass aClass = myJavaFacade.findClass("Animal", GlobalSearchScope.allScope(myProject));
PsiMethod main = aClass.findMethodsByName("main", false)[0];
PsiLocalVariable variable = (PsiLocalVariable)main.getBody().getStatements()[0].getFirstChild();
PsiNewExpression newExpression = (PsiNewExpression)variable.getInitializer();
PsiMethod defaultConstructor = newExpression.resolveMethod();
Collection<PsiReference> references = ReferencesSearch.search(defaultConstructor, GlobalSearchScope.projectScope(myProject)).findAll();
List<@NotNull PsiElement> result =
references.stream().map(r -> r.getElement()).sorted(PsiElementOrderComparator.getInstance()).toList();
assertEquals(2, references.size());
assertTrue(result.get(0).getParent() instanceof PsiNewExpression);
assertTrue(result.get(1) instanceof PsiMethod m && m.isConstructor() && "OneLeggedDog".equals(m.getName()));
}
public void testImplicitConstructorUsage() {
PsiMethod[] ctrs = myJavaFacade.findClass("Foo", GlobalSearchScope.allScope(myProject)).getConstructors();
@@ -62,6 +62,10 @@ public class RenameMembersInplaceTest extends LightJavaCodeInsightTestCase {
public void testSuperMethodAnonymousInheritor() {
doTestInplaceRename("xxx");
}
public void testDefaultConstructor() {
doTestInplaceRename("HeadlessHorseman");
}
public void testMultipleConstructors() {
doTestInplaceRename("Bar");
@@ -1,4 +1,4 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.refactoring.actions;
import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer;
@@ -227,18 +227,18 @@ public abstract class BaseRefactoringAction extends AnAction {
@ApiStatus.Internal
public static PsiElement findRefactoringTargetInEditor(@NotNull DataContext dataContext,
@NotNull Predicate<? super Language> elementLanguagePredicate) {
Editor editor = dataContext.getData(CommonDataKeys.EDITOR);
PsiFile file = dataContext.getData(CommonDataKeys.PSI_FILE);
PsiElement element = dataContext.getData(CommonDataKeys.PSI_ELEMENT);
Language[] languages = dataContext.getData(LangDataKeys.CONTEXT_LANGUAGES);
if (element == null || element instanceof SyntheticElement || !elementLanguagePredicate.test(element.getLanguage())) {
if (element == null || !elementLanguagePredicate.test(element.getLanguage())) {
Editor editor = dataContext.getData(CommonDataKeys.EDITOR);
PsiFile file = dataContext.getData(CommonDataKeys.PSI_FILE);
if (file == null || editor == null) {
return null;
}
element = getElementAtCaret(editor, file);
}
if (element == null || element instanceof SyntheticElement || languages == null) {
Language[] languages = dataContext.getData(LangDataKeys.CONTEXT_LANGUAGES);
if (element == null || languages == null) {
return null;
}
@@ -1,4 +1,4 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.refactoring.rename.inplace;
import com.intellij.codeInsight.TargetElementUtil;
@@ -228,9 +228,8 @@ public class MemberInplaceRenamer extends VariableInplaceRenamer {
private void appendAdditionalElement(Collection<? super Pair<PsiElement, TextRange>> stringUsages,
PsiNamedElement variable,
PsiElement element) {
if (element != variable && element instanceof PsiNameIdentifierOwner &&
!notSameFile(null, element.getContainingFile())) {
final PsiElement identifier = ((PsiNameIdentifierOwner)element).getNameIdentifier();
if (element != variable && element instanceof PsiNameIdentifierOwner owner && !notSameFile(null, element.getContainingFile())) {
final PsiElement identifier = owner.getNameIdentifier();
if (identifier != null) {
stringUsages.add(Pair.create(identifier, new TextRange(0, identifier.getTextLength())));
}
@@ -242,7 +241,14 @@ public class MemberInplaceRenamer extends VariableInplaceRenamer {
try {
startDumbIfPossible();
tryRollback();
final PsiNamedElement variable = getVariable();
PsiNamedElement variable = getVariable();
if (variable == null) {
PsiFile psiFile = PsiDocumentManager.getInstance(myProject).getPsiFile(myEditor.getDocument());
if (psiFile != null) {
variable = PsiTreeUtil.findElementOfClassAtRange(psiFile, mySubstitutedRange.getStartOffset(), mySubstitutedRange.getEndOffset(),
PsiNameIdentifierOwner.class);
}
}
if (variable != null && !newName.equals(myOldName)) {
if (isIdentifier(newName, variable.getLanguage())) {
final PsiElement substituted = getSubstituted();
@@ -250,6 +256,8 @@ public class MemberInplaceRenamer extends VariableInplaceRenamer {
return;
}
String type = UsageViewUtil.getType(variable);
String name = DescriptiveNameUtil.getDescriptiveName(variable);
Runnable performRunnable = () -> {
try (var ignored = SlowOperations.startSection(SlowOperations.ACTION_PERFORM)) {
if (DumbService.isDumb(myProject)) {
@@ -259,9 +267,8 @@ public class MemberInplaceRenamer extends VariableInplaceRenamer {
return;
}
final String commandName = RefactoringBundle.message("renaming.0.1.to.2",
UsageViewUtil.getType(variable),
DescriptiveNameUtil.getDescriptiveName(variable), newName);
final String commandName = RefactoringBundle.message("renaming.0.1.to.2", type, name, newName);
CommandProcessor.getInstance().executeCommand(myProject, () -> {
performRenameInner(substituted, newName);
PsiDocumentManager.getInstance(myProject).commitAllDocuments();
@@ -456,7 +456,6 @@ public final class SafeDeleteProcessor extends BaseRefactoringProcessor {
public static boolean validElement(@NotNull PsiElement element) {
if (element instanceof PsiFile) return true;
if (!element.isPhysical()) return false;
RefactoringSupportProvider provider = LanguageRefactoringSupport.getInstance().forContext(element);
return provider != null && provider.isSafeDeleteAvailable(element);
}