From ad50f0d0e6a7bd879a25f1308fbd6f52964cde1a Mon Sep 17 00:00:00 2001 From: Sougandh S Date: Sun, 26 Jul 2026 19:04:04 +0530 Subject: [PATCH] Infer record components for "Create record" quickfix Previously the quick fix always generated an empty record header. Now it infers component types and names from the constructor call's arguments when one is present Fixes : https://github.com/eclipse-jdtls/eclipse.jdt.ls/issues/3856 --- .../corrections/proposals/NewCUProposal.java | 52 ++++++++++++++++++- .../UnresolvedElementsSubProcessor.java | 37 +++++++++++-- .../UnresolvedTypesQuickFixTest.java | 32 +++++++++++- 3 files changed, 116 insertions(+), 5 deletions(-) diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/corrections/proposals/NewCUProposal.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/corrections/proposals/NewCUProposal.java index bcb8472411..5ffaf573a5 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/corrections/proposals/NewCUProposal.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/corrections/proposals/NewCUProposal.java @@ -20,6 +20,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.Iterator; +import java.util.List; import java.util.Objects; import org.eclipse.core.runtime.CoreException; @@ -35,6 +36,8 @@ import org.eclipse.jdt.core.dom.ASTParser; import org.eclipse.jdt.core.dom.AbstractTypeDeclaration; import org.eclipse.jdt.core.dom.CompilationUnit; +import org.eclipse.jdt.core.dom.Expression; +import org.eclipse.jdt.core.dom.ITypeBinding; import org.eclipse.jdt.core.dom.Modifier; import org.eclipse.jdt.core.dom.Name; import org.eclipse.jdt.core.dom.ParameterizedType; @@ -82,6 +85,8 @@ public class NewCUProposal extends ChangeCorrectionProposalCore { private int fTypeKind; private IJavaElement fTypeContainer; // IType or IPackageFragment private String fTypeNameWithParameters; + private List fArguments; + /** * Construct a new compilation unit proposal. * @@ -111,6 +116,36 @@ public NewCUProposal(ICompilationUnit cu, Name node, int typeKind, IJavaElement setDisplayName(); } + /** + * Construct a new compilation unit proposal. + * + * @param cu + * current compilation unit. + * @param node + * {@link Name} corresponding to the compilation unit to be created. + * @param typeKind + * possible values: { K_CLASS, K_INTERFACE, K_ENUM, K_ANNOTATION } + * @param typeContainer + * enclosing {@link IJavaElement} of the target compilation unit, can + * be {@link IType} or {@link IPackageFragment}. + * @param relevance + * the relevance of this proposal + * @param arguments + * the list of {@link Expression} nodes + */ + public NewCUProposal(ICompilationUnit cu, Name node, int typeKind, IJavaElement typeContainer, int relevance, List arguments) { + super("", null, relevance); //$NON-NLS-1$ + fCompilationUnit = cu; + fNode = node; + fTypeKind = typeKind; + fTypeContainer = typeContainer; + if (fNode != null) { + fTypeNameWithParameters = getTypeName(typeKind, node); + } + fArguments = arguments; + setDisplayName(); + } + private void setDisplayName() { String containerName; if (fNode != null) { @@ -508,7 +543,22 @@ private String constructTypeStub(ICompilationUnit parentCU, String name, int mod buf.append(name); if (fTypeKind == K_RECORD) { - buf.append("()"); + buf.append("("); + if (fArguments != null && !fArguments.isEmpty()) { + for (int i = 0; i < fArguments.size(); i++) { + if (i > 0) { + buf.append(", "); //$NON-NLS-1$ + } + Expression arg = fArguments.get(i); + ITypeBinding typeBinding = arg.resolveTypeBinding(); + String typeName = typeBinding != null && !typeBinding.isRecovered() + ? typeBinding.getName() + : "Object"; //$NON-NLS-1$ + String argName = "arg" + (i + 1); //$NON-NLS-1$ + buf.append(typeName).append(' ').append(argName); + } + } + buf.append(")"); } if (isPermitted) { diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/corrections/proposals/UnresolvedElementsSubProcessor.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/corrections/proposals/UnresolvedElementsSubProcessor.java index cb27e11d08..13ffcfa924 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/corrections/proposals/UnresolvedElementsSubProcessor.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/corrections/proposals/UnresolvedElementsSubProcessor.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2024 IBM Corporation and others. + * Copyright (c) 2000, 2026 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 * which accompanies this distribution, and is available at @@ -24,12 +24,17 @@ import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.compiler.IProblem; import org.eclipse.jdt.core.dom.ASTNode; +import org.eclipse.jdt.core.dom.ClassInstanceCreation; import org.eclipse.jdt.core.dom.CompilationUnit; +import org.eclipse.jdt.core.dom.Expression; import org.eclipse.jdt.core.dom.ITypeBinding; import org.eclipse.jdt.core.dom.IVariableBinding; import org.eclipse.jdt.core.dom.Name; import org.eclipse.jdt.core.dom.NodeFinder; +import org.eclipse.jdt.core.dom.SimpleType; import org.eclipse.jdt.core.dom.Type; +import org.eclipse.jdt.core.dom.VariableDeclarationFragment; +import org.eclipse.jdt.core.dom.VariableDeclarationStatement; import org.eclipse.jdt.core.dom.rewrite.ASTRewrite; import org.eclipse.jdt.core.dom.rewrite.ImportRewrite; import org.eclipse.jdt.core.dom.rewrite.ImportRewrite.ImportRewriteContext; @@ -40,7 +45,6 @@ import org.eclipse.jdt.internal.core.manipulation.JavaElementLabelsCore; import org.eclipse.jdt.internal.corext.codemanipulation.ContextSensitiveImportRewriteContext; import org.eclipse.jdt.internal.corext.util.Messages; -import org.eclipse.jdt.ls.core.internal.corrections.CorrectionMessages; import org.eclipse.jdt.internal.ui.text.correction.ReorgCorrectionsBaseSubProcessor; import org.eclipse.jdt.internal.ui.text.correction.TypeMismatchBaseSubProcessor; import org.eclipse.jdt.internal.ui.text.correction.UnresolvedElementsBaseSubProcessor; @@ -58,6 +62,7 @@ import org.eclipse.jdt.internal.ui.text.correction.proposals.ReplaceCorrectionProposalCore; import org.eclipse.jdt.ls.core.internal.JDTUtils; import org.eclipse.jdt.ls.core.internal.JavaLanguageServerPlugin; +import org.eclipse.jdt.ls.core.internal.corrections.CorrectionMessages; import org.eclipse.jdt.ls.core.internal.corrections.ProposalKindWrapper; import org.eclipse.jdt.ls.core.internal.handlers.CodeActionHandler; import org.eclipse.jdt.ui.text.java.IInvocationContext; @@ -111,13 +116,39 @@ public static void getAnnotationMemberProposals(IInvocationContext context, IPro /* (non-Javadoc) * @see org.eclipse.jdt.internal.ui.text.correction.UnresolvedElementsBaseSubProcessor#addNewTypeProposalsInteractiveInnerLoop(org.eclipse.jdt.core.ICompilationUnit, org.eclipse.jdt.core.dom.Name, org.eclipse.jdt.core.IJavaElement, int, int, org.eclipse.jdt.core.dom.Name, java.util.Collection) */ + @SuppressWarnings("unchecked") @Override protected void addNewTypeProposalsInteractiveInnerLoop(ICompilationUnit cu, Name node, IJavaElement enclosing, int rel, int kind, Name refNode, Collection proposals) throws CoreException { if ((kind & TypeKinds.CLASSES) != 0) { NewCUProposal proposal = new NewCUProposal(cu, node, NewCUProposal.K_CLASS, enclosing, rel + 3); proposals.add(CodeActionHandler.wrap(proposal, CodeActionKind.QuickFix)); if (canUseRecord(cu.getJavaProject(), refNode)) { - proposal = new NewCUProposal(cu, node, NewCUProposal.K_RECORD, enclosing, rel + 3); + + List arguments = null; + ASTNode parent = node.getParent(); + if (parent instanceof SimpleType) { + parent = parent.getParent(); + } + if (parent instanceof VariableDeclarationStatement stmt) { + for (Object o : stmt.fragments()) { + if (o instanceof VariableDeclarationFragment frag) { + Expression init = frag.getInitializer(); + if (init instanceof ClassInstanceCreation creation) { + arguments = creation.arguments(); + } + } + + } + } + if (parent instanceof ClassInstanceCreation creation) { + arguments = creation.arguments(); + } + + if (arguments != null && arguments.isEmpty()) { + proposal = new NewCUProposal(cu, node, NewCUProposal.K_RECORD, enclosing, rel + 3); + } else { + proposal = new NewCUProposal(cu, node, NewCUProposal.K_RECORD, enclosing, rel + 3, arguments); + } proposals.add(CodeActionHandler.wrap(proposal, CodeActionKind.QuickFix)); } } diff --git a/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/correction/UnresolvedTypesQuickFixTest.java b/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/correction/UnresolvedTypesQuickFixTest.java index 8564adaf47..827af4fd4f 100644 --- a/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/correction/UnresolvedTypesQuickFixTest.java +++ b/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/correction/UnresolvedTypesQuickFixTest.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2013 IBM Corporation and others. + * Copyright (c) 2000, 2026 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 * which accompanies this distribution, and is available at @@ -1632,4 +1632,34 @@ void foo() { assertCodeActions(cu, e1); } + @Test + public void testTypeInStatementCreateRecordWithComponents() throws Exception { + Map options17 = new HashMap<>(); + JavaModelUtil.setComplianceOptions(options17, JavaCore.VERSION_17); + fJProject1.setOptions(options17); + + IPackageFragment pack1 = fSourceFolder.createPackageFragment("test1", false, null); + StringBuilder buf = new StringBuilder(); + buf.append("package test1;\n"); + buf.append("public class E {\n"); + buf.append(" void foo() {\n"); + buf.append(" Re1 obj = new Re1(1, \"hello\");\n"); + buf.append(" }\n"); + buf.append("}\n"); + ICompilationUnit cu = pack1.createCompilationUnit("E.java", buf.toString(), false, null); + + buf = new StringBuilder(); + buf.append("package test1;\n"); + buf.append("\n"); + buf.append("/**\n"); + buf.append(" * Re1\n"); + buf.append(" */\n"); + buf.append("public record Re1(int arg1, String arg2) {\n"); + buf.append("\n"); + buf.append("}\n"); + Expected e1 = new Expected("Create record 'Re1'", buf.toString()); + + assertCodeActionExists(cu, e1); + } + } \ No newline at end of file