Skip to content

Commit 905eb1c

Browse files
authored
Add new quick-fix/clean-up to replace deprecated fields if possible (#2254)
* Add new quick-fix/clean-up to replace deprecated fields if possible - add new ReplaceDeprecatedFieldCleanUpCore and ReplaceDeprecatedFieldFixCore classes - add new method QuickAssistProcessorUtil.getDeprecatedFieldReplacement - add new logic to QuickFixProcessor and QuickAssistProcessor - add new option to SourceFixingTabPage to perform replace fields clean-up - add new tests to CleanUpTest1d8 - fixes #2242
1 parent 00da872 commit 905eb1c

File tree

18 files changed

+883
-9
lines changed

18 files changed

+883
-9
lines changed

org.eclipse.jdt.core.manipulation/common/org/eclipse/jdt/internal/ui/fix/MultiFixMessages.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ private MultiFixMessages() {
159159
public static String RedundantModifiersCleanup_description;
160160
public static String SubstringCleanUp_description;
161161
public static String InlineDeprecatedMethodCleanUp_description;
162+
public static String ReplaceDeprecatedFieldsCleanUp_description;
162163
public static String JoinCleanup_description;
163164
public static String ArraysFillCleanUp_description;
164165
public static String EvaluateNullableCleanUp_description;

org.eclipse.jdt.core.manipulation/common/org/eclipse/jdt/internal/ui/fix/MultiFixMessages.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ HashCleanup_description=Use Objects.hash()
140140
RedundantModifiersCleanup_description=Remove redundant modifiers
141141
SubstringCleanUp_description=Remove redundant String.substring() parameter
142142
InlineDeprecatedMethodCleanUp_description=Replace deprecated calls with inlined content where possible
143+
ReplaceDeprecatedFieldsCleanUp_description=Replace deprecated fields where possible
143144
JoinCleanup_description=Use String.join()
144145
ArraysFillCleanUp_description=Use Arrays.fill() when possible
145146
EvaluateNullableCleanUp_description=Evaluate without null check
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2025 Red Hat Inc. and others.
3+
*
4+
* This program and the accompanying materials
5+
* are made available under the terms of the Eclipse Public License 2.0
6+
* which accompanies this distribution, and is available at
7+
* https://www.eclipse.org/legal/epl-2.0/
8+
*
9+
* SPDX-License-Identifier: EPL-2.0
10+
*
11+
* Contributors:
12+
* Red Hat Inc. - initial API and implementation
13+
*******************************************************************************/
14+
package org.eclipse.jdt.internal.ui.fix;
15+
16+
import java.util.ArrayList;
17+
import java.util.Collections;
18+
import java.util.List;
19+
import java.util.Map;
20+
21+
import org.eclipse.core.runtime.CoreException;
22+
23+
import org.eclipse.jdt.core.ICompilationUnit;
24+
import org.eclipse.jdt.core.dom.ASTNode;
25+
import org.eclipse.jdt.core.dom.ASTVisitor;
26+
import org.eclipse.jdt.core.dom.CompilationUnit;
27+
import org.eclipse.jdt.core.dom.FieldAccess;
28+
import org.eclipse.jdt.core.dom.QualifiedName;
29+
import org.eclipse.jdt.core.dom.SimpleName;
30+
import org.eclipse.jdt.core.dom.SuperFieldAccess;
31+
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
32+
33+
import org.eclipse.jdt.internal.corext.fix.CleanUpConstants;
34+
import org.eclipse.jdt.internal.corext.fix.CompilationUnitRewriteOperationsFixCore.CompilationUnitRewriteOperation;
35+
import org.eclipse.jdt.internal.corext.fix.ReplaceDeprecatedFieldFixCore;
36+
37+
import org.eclipse.jdt.ui.cleanup.CleanUpRequirements;
38+
import org.eclipse.jdt.ui.cleanup.ICleanUpFix;
39+
import org.eclipse.jdt.ui.text.java.IProblemLocation;
40+
41+
import org.eclipse.jdt.internal.ui.text.correction.QuickAssistProcessorUtil;
42+
43+
public class ReplaceDeprecatedFieldCleanUpCore extends AbstractMultiFix {
44+
45+
public ReplaceDeprecatedFieldCleanUpCore() {
46+
this(Collections.emptyMap());
47+
}
48+
49+
public ReplaceDeprecatedFieldCleanUpCore(final Map<String, String> options) {
50+
super(options);
51+
}
52+
53+
@Override
54+
public CleanUpRequirements getRequirements() {
55+
boolean requireAST= isEnabled(CleanUpConstants.REPLACE_DEPRECATED_FIELDS);
56+
return new CleanUpRequirements(requireAST, false, false, null);
57+
}
58+
59+
@Override
60+
public String[] getStepDescriptions() {
61+
if (isEnabled(CleanUpConstants.REPLACE_DEPRECATED_FIELDS)) {
62+
return new String[] { MultiFixMessages.ReplaceDeprecatedFieldsCleanUp_description };
63+
}
64+
return new String[0];
65+
}
66+
67+
@Override
68+
public String getPreview() {
69+
StringBuilder bld= new StringBuilder();
70+
bld.append("Class E {\n"); //$NON-NLS-1$
71+
bld.append(" /**\n"); //$NON-NLS-1$
72+
bld.append(" * @deprecated use {@link K#field2} instead\n"); //$NON-NLS-1$
73+
bld.append(" */\n"); //$NON-NLS-1$
74+
bld.append(" @Deprecated\n"); //$NON-NLS-1$
75+
bld.append(" public int field1;\n"); //$NON-NLS-1$
76+
bld.append("}\n\n"); //$NON-NLS-1$
77+
if (isEnabled(CleanUpConstants.REPLACE_DEPRECATED_FIELDS)) {
78+
bld.append("return K.field2;\n"); //$NON-NLS-1$
79+
} else {
80+
bld.append("return E.field1;\n"); //$NON-NLS-1$
81+
}
82+
return bld.toString();
83+
}
84+
85+
@Override
86+
public boolean canFix(ICompilationUnit compilationUnit, IProblemLocation problem) {
87+
return false;
88+
}
89+
90+
@Override
91+
protected ICleanUpFix createFix(CompilationUnit compilationUnit) throws CoreException {
92+
if (!isEnabled(CleanUpConstants.REPLACE_DEPRECATED_FIELDS)) {
93+
return null;
94+
}
95+
if (compilationUnit == null)
96+
return null;
97+
98+
final List<ReplaceDeprecatedFieldFixCore.ReplaceDeprecatedFieldProposalOperation> deprecatedFields= new ArrayList<>();
99+
ASTVisitor visitor= new ASTVisitor() {
100+
@Override
101+
public boolean visit(QualifiedName node) {
102+
String replacement= QuickAssistProcessorUtil.getDeprecatedFieldReplacement(node);
103+
if (replacement != null) {
104+
deprecatedFields.add(new ReplaceDeprecatedFieldFixCore.ReplaceDeprecatedFieldProposalOperation(node, replacement));
105+
}
106+
return false;
107+
}
108+
@Override
109+
public boolean visit(SimpleName node) {
110+
if (node.getLocationInParent() == VariableDeclarationFragment.NAME_PROPERTY) {
111+
return true;
112+
}
113+
String replacement= QuickAssistProcessorUtil.getDeprecatedFieldReplacement(node);
114+
if (replacement != null) {
115+
deprecatedFields.add(new ReplaceDeprecatedFieldFixCore.ReplaceDeprecatedFieldProposalOperation(node, replacement));
116+
}
117+
return false;
118+
}
119+
@Override
120+
public boolean visit(FieldAccess node) {
121+
String replacement= QuickAssistProcessorUtil.getDeprecatedFieldReplacement(node);
122+
if (replacement != null) {
123+
deprecatedFields.add(new ReplaceDeprecatedFieldFixCore.ReplaceDeprecatedFieldProposalOperation(node, replacement));
124+
}
125+
return false;
126+
}
127+
@Override
128+
public boolean visit(SuperFieldAccess node) {
129+
String replacement= QuickAssistProcessorUtil.getDeprecatedFieldReplacement(node);
130+
if (replacement != null) {
131+
deprecatedFields.add(new ReplaceDeprecatedFieldFixCore.ReplaceDeprecatedFieldProposalOperation(node, replacement));
132+
}
133+
return false;
134+
}
135+
};
136+
compilationUnit.accept(visitor);
137+
if (deprecatedFields.isEmpty()) {
138+
return null;
139+
}
140+
return new ReplaceDeprecatedFieldFixCore(getPreview(), compilationUnit, deprecatedFields.toArray(new CompilationUnitRewriteOperation[0]));
141+
}
142+
143+
@Override
144+
public int computeNumberOfFixes(CompilationUnit compilationUnit) {
145+
if (!isEnabled(CleanUpConstants.REPLACE_DEPRECATED_FIELDS)) {
146+
return 0;
147+
}
148+
if (compilationUnit == null)
149+
return 0;
150+
151+
final List<ASTNode> deprecatedFields= new ArrayList<>();
152+
ASTVisitor visitor= new ASTVisitor() {
153+
@Override
154+
public boolean visit(QualifiedName node) {
155+
if (QuickAssistProcessorUtil.getDeprecatedFieldReplacement(node) != null) {
156+
deprecatedFields.add(node);
157+
}
158+
return true;
159+
}
160+
@Override
161+
public boolean visit(FieldAccess node) {
162+
if (QuickAssistProcessorUtil.getDeprecatedFieldReplacement(node) != null) {
163+
deprecatedFields.add(node);
164+
}
165+
return true;
166+
}
167+
@Override
168+
public boolean visit(SuperFieldAccess node) {
169+
if (QuickAssistProcessorUtil.getDeprecatedFieldReplacement(node) != null) {
170+
deprecatedFields.add(node);
171+
}
172+
return true;
173+
}
174+
};
175+
compilationUnit.accept(visitor);
176+
return deprecatedFields.size();
177+
}
178+
179+
@Override
180+
protected ICleanUpFix createFix(CompilationUnit unit, IProblemLocation[] problems) throws CoreException {
181+
return null;
182+
}
183+
184+
}

org.eclipse.jdt.core.manipulation/common/org/eclipse/jdt/internal/ui/text/correction/IProposalRelevance.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*******************************************************************************
2-
* Copyright (c) 2012, 2024 IBM Corporation and others.
2+
* Copyright (c) 2012, 2025 IBM Corporation and others.
33
*
44
* This program and the accompanying materials
55
* are made available under the terms of the Eclipse Public License 2.0
@@ -58,6 +58,7 @@ public interface IProposalRelevance {
5858
int CREATE_OPTIONAL= 9;
5959
int CREATE_OPTIONAL_OF_NULLABLE= 9;
6060
int INLINE_DEPRECATED_METHOD= 9;
61+
int REPLACE_DEPRECATED_FIELD= 9;
6162

6263
int ADD_ABSTRACT_MODIFIER= 8;
6364
int ADD_STATIC_MODIFIER= 8;

org.eclipse.jdt.core.manipulation/common/org/eclipse/jdt/internal/ui/text/correction/QuickAssistProcessorUtil.java

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727

2828
import org.eclipse.jdt.core.IBuffer;
2929
import org.eclipse.jdt.core.ICompilationUnit;
30+
import org.eclipse.jdt.core.IField;
3031
import org.eclipse.jdt.core.IMethod;
3132
import org.eclipse.jdt.core.JavaModelException;
3233
import org.eclipse.jdt.core.dom.AST;
@@ -41,6 +42,8 @@
4142
import org.eclipse.jdt.core.dom.Expression;
4243
import org.eclipse.jdt.core.dom.ExpressionMethodReference;
4344
import org.eclipse.jdt.core.dom.ExpressionStatement;
45+
import org.eclipse.jdt.core.dom.FieldAccess;
46+
import org.eclipse.jdt.core.dom.FieldDeclaration;
4447
import org.eclipse.jdt.core.dom.IAnnotationBinding;
4548
import org.eclipse.jdt.core.dom.IBinding;
4649
import org.eclipse.jdt.core.dom.IDocElement;
@@ -49,16 +52,19 @@
4952
import org.eclipse.jdt.core.dom.IVariableBinding;
5053
import org.eclipse.jdt.core.dom.Javadoc;
5154
import org.eclipse.jdt.core.dom.LambdaExpression;
55+
import org.eclipse.jdt.core.dom.MemberRef;
5256
import org.eclipse.jdt.core.dom.MethodDeclaration;
5357
import org.eclipse.jdt.core.dom.MethodInvocation;
5458
import org.eclipse.jdt.core.dom.MethodRef;
5559
import org.eclipse.jdt.core.dom.MethodReference;
5660
import org.eclipse.jdt.core.dom.Modifier;
5761
import org.eclipse.jdt.core.dom.Name;
5862
import org.eclipse.jdt.core.dom.ParameterizedType;
63+
import org.eclipse.jdt.core.dom.QualifiedName;
5964
import org.eclipse.jdt.core.dom.ReturnStatement;
6065
import org.eclipse.jdt.core.dom.SimpleName;
6166
import org.eclipse.jdt.core.dom.Statement;
67+
import org.eclipse.jdt.core.dom.SuperFieldAccess;
6268
import org.eclipse.jdt.core.dom.SuperMethodInvocation;
6369
import org.eclipse.jdt.core.dom.SuperMethodReference;
6470
import org.eclipse.jdt.core.dom.TagElement;
@@ -743,6 +749,108 @@ public static CompilationUnit findCUForMethod(CompilationUnit compilationUnit, I
743749
return compilationUnit;
744750
}
745751

752+
public static String getDeprecatedFieldReplacement(ASTNode node) {
753+
IBinding binding= null;
754+
switch (node) {
755+
case QualifiedName q:
756+
binding= q.resolveBinding();
757+
break;
758+
case SimpleName s:
759+
if (s.getLocationInParent() == QualifiedName.NAME_PROPERTY) {
760+
node= s.getParent();
761+
binding= ((QualifiedName)node).resolveBinding();
762+
} else {
763+
binding= s.resolveBinding();
764+
}
765+
break;
766+
case FieldAccess f:
767+
// if (f.getExpression() instanceof MethodInvocation) {
768+
// return null;
769+
// }
770+
binding= f.resolveFieldBinding();
771+
break;
772+
case SuperFieldAccess sf:
773+
binding= sf.resolveFieldBinding();
774+
break;
775+
default:
776+
return null;
777+
}
778+
if (binding instanceof IVariableBinding varBinding && varBinding.isField()) {
779+
IField field= (IField)varBinding.getJavaElement();
780+
if (field == null) {
781+
return null;
782+
}
783+
IAnnotationBinding[] annotations= varBinding.getAnnotations();
784+
for (IAnnotationBinding annotation : annotations) {
785+
if (annotation.getAnnotationType().getQualifiedName().equals("java.lang.Deprecated")) { //$NON-NLS-1$
786+
CompilationUnit sourceCu= (CompilationUnit)node.getRoot();
787+
CompilationUnit cu= findCUForField(sourceCu, (ICompilationUnit)sourceCu.getJavaElement(), varBinding);
788+
if (cu == null) {
789+
return null;
790+
}
791+
try {
792+
FieldDeclaration fieldDeclaration= ASTNodeSearchUtil.getFieldDeclarationNode(field, cu);
793+
Javadoc javadoc= fieldDeclaration.getJavadoc();
794+
if (javadoc == null) {
795+
return null;
796+
}
797+
List<TagElement> tags= javadoc.tags();
798+
for (TagElement tag : tags) {
799+
if ("@deprecated".equals(tag.getTagName())) { //$NON-NLS-1$
800+
List<IDocElement> fragments= tag.fragments();
801+
if (fragments.size() < 2) {
802+
return null;
803+
}
804+
if (fragments.get(0) instanceof TextElement textElement) {
805+
String text= textElement.getText().toLowerCase().trim();
806+
if (text.endsWith("use") || text.endsWith("replace by")) { //$NON-NLS-1$ //$NON-NLS-2$
807+
if (fragments.get(1) instanceof TagElement tagElement) {
808+
if ("@link".equals(tagElement.getTagName())) { //$NON-NLS-1$
809+
List<IDocElement> linkFragments= tagElement.fragments();
810+
if (linkFragments.size() == 1) {
811+
IDocElement linkFragment= linkFragments.get(0);
812+
if (linkFragment instanceof MemberRef methodRef) {
813+
IBinding refBinding= methodRef.resolveBinding();
814+
if (refBinding instanceof IVariableBinding replaceBinding && replaceBinding.isField()) {
815+
return replaceBinding.getDeclaringClass().getQualifiedName() + "." + replaceBinding.getName(); //$NON-NLS-1$
816+
}
817+
}
818+
}
819+
}
820+
}
821+
}
822+
}
823+
}
824+
}
825+
} catch (JavaModelException e) {
826+
// ignore
827+
}
828+
}
829+
}
830+
}
831+
return null;
832+
}
833+
834+
public static CompilationUnit findCUForField(CompilationUnit compilationUnit, ICompilationUnit cu, IVariableBinding fieldBinding) {
835+
ASTNode fieldDecl= compilationUnit.findDeclaringNode(fieldBinding.getVariableDeclaration());
836+
if (fieldDecl == null) {
837+
// is field defined in another CU?
838+
ITypeBinding declaringTypeDecl= fieldBinding.getDeclaringClass().getTypeDeclaration();
839+
if (declaringTypeDecl.isFromSource()) {
840+
ICompilationUnit targetCU= null;
841+
try {
842+
targetCU= ASTResolving.findCompilationUnitForBinding(cu, compilationUnit, declaringTypeDecl);
843+
} catch (JavaModelException e) { /* can't do better */
844+
}
845+
if (targetCU != null) {
846+
return ASTResolving.createQuickFixAST(targetCU, null);
847+
}
848+
}
849+
return null;
850+
}
851+
return compilationUnit;
852+
}
853+
746854
public static ASTNode getCopyOfInner(ASTRewrite rewrite, ASTNode statement, boolean toControlStatementBody) {
747855
if (statement.getNodeType() == ASTNode.BLOCK) {
748856
Block block= (Block) statement;

org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/fix/CleanUpConstants.java

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*******************************************************************************
2-
* Copyright (c) 2000, 2023 IBM Corporation and others.
2+
* Copyright (c) 2000, 2025 IBM Corporation and others.
33
*
44
* This program and the accompanying materials
55
* are made available under the terms of the Eclipse Public License 2.0
@@ -1301,10 +1301,21 @@ public class CleanUpConstants {
13011301
*
13021302
* @see CleanUpOptions#TRUE
13031303
* @see CleanUpOptions#FALSE
1304-
* @since 4.30
1304+
* @since 4.37
13051305
*/
13061306
public static final String REPLACE_DEPRECATED_CALLS= "cleanup.replace_deprecated_calls"; //$NON-NLS-1$
13071307

1308+
/**
1309+
* Replace deprecated fields with specified replacement fields if possible.
1310+
* <p>
1311+
* Possible values: {TRUE, FALSE}
1312+
*
1313+
* @see CleanUpOptions#TRUE
1314+
* @see CleanUpOptions#FALSE
1315+
* @since 4.30
1316+
*/
1317+
public static final String REPLACE_DEPRECATED_FIELDS= "cleanup.replace_deprecated_fields"; //$NON-NLS-1$
1318+
13081319
/**
13091320
* Replaces {@code String.replaceAll()} by {@code String.replace()}.
13101321
* <p>

org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/fix/FixMessages.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,7 @@ private FixMessages() {
186186
public static String StringConcatToTextBlockFix_convert_msg;
187187
public static String LambdaExpressionAndMethodRefFix_clean_up_expression_msg;
188188
public static String InlineDeprecatedMethod_msg;
189+
public static String ReplaceDeprecatedField_msg;
189190

190191
static {
191192
// initialize resource bundle

0 commit comments

Comments
 (0)