Skip to content

Commit 0609823

Browse files
committed
增强类级别 final 修饰符支持
1 parent f542746 commit 0609823

File tree

2 files changed

+278
-17
lines changed

2 files changed

+278
-17
lines changed

src/main/java/lwm/plugin/intention/AddFinalIntention.java

Lines changed: 151 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,11 @@
55
import com.intellij.codeInspection.util.IntentionName;
66
import com.intellij.openapi.editor.Editor;
77
import com.intellij.openapi.project.Project;
8+
import com.intellij.psi.PsiAssignmentExpression;
9+
import com.intellij.psi.PsiClass;
810
import com.intellij.psi.PsiCodeBlock;
911
import com.intellij.psi.PsiElement;
12+
import com.intellij.psi.PsiExpression;
1013
import com.intellij.psi.PsiField;
1114
import com.intellij.psi.PsiFile;
1215
import com.intellij.psi.PsiJavaFile;
@@ -17,6 +20,7 @@
1720
import com.intellij.psi.PsiModifierListOwner;
1821
import com.intellij.psi.PsiParameter;
1922
import com.intellij.psi.PsiParameterList;
23+
import com.intellij.psi.PsiReferenceExpression;
2024
import com.intellij.psi.PsiVariable;
2125
import com.intellij.psi.util.PsiTreeUtil;
2226
import com.intellij.psi.util.PsiUtil;
@@ -48,13 +52,20 @@ public boolean isAvailable(@NotNull final Project project, final Editor editor,
4852
if (element == null) {
4953
return false;
5054
}
55+
5156
final PsiField field = PsiTreeUtil.getParentOfType(element, PsiField.class, false);
5257
if (field != null) {
5358
return true;
5459
}
5560
if (element instanceof PsiVariable) {
5661
return true;
5762
}
63+
64+
// 检查是否在类名标识符上
65+
final PsiClass psiClass = PsiTreeUtil.getParentOfType(element, PsiClass.class, false);
66+
if (psiClass != null && isCaretOnClassIdentifier(element, psiClass)) {
67+
return true;
68+
}
5869

5970
return PsiTreeUtil.getParentOfType(element, PsiMethod.class) != null;
6071
}
@@ -66,40 +77,167 @@ public void invoke(@NotNull final Project project, final Editor editor, final Ps
6677
return;
6778
}
6879

80+
// 优先检查是否直接在变量上
6981
final PsiVariable specificVariable = PsiTreeUtil.getParentOfType(element, PsiVariable.class, false);
7082
if (specificVariable != null) {
7183
addFinalModifierIfNotPresent(specificVariable);
7284
return;
7385
}
7486

87+
// 检查是否在类名标识符上(而不是在类体内的其他地方)
88+
final PsiClass psiClass = PsiTreeUtil.getParentOfType(element, PsiClass.class, false);
89+
if (psiClass != null && isCaretOnClassIdentifier(element, psiClass)) {
90+
processClass(psiClass);
91+
return;
92+
}
93+
7594
final PsiMethod containingMethod = PsiTreeUtil.getParentOfType(element, PsiMethod.class);
7695
if (containingMethod != null) {
77-
// Add final to parameters
78-
final PsiParameterList parameterList = containingMethod.getParameterList();
79-
for (final PsiParameter parameter : parameterList.getParameters()) {
80-
addFinalModifierIfNotPresent(parameter);
81-
}
96+
processMethod(containingMethod);
97+
}
98+
}
8299

83-
// Add final to local variables
84-
final PsiCodeBlock methodBody = containingMethod.getBody();
85-
if (methodBody != null) {
86-
final Collection<PsiLocalVariable> localVariables = PsiTreeUtil.collectElementsOfType(methodBody, PsiLocalVariable.class);
87-
for (final PsiLocalVariable localVariable : localVariables) {
88-
addFinalModifierIfNotPresent(localVariable);
89-
}
100+
/**
101+
* 处理整个类
102+
*/
103+
private void processClass(final PsiClass psiClass) {
104+
// 处理类字段
105+
for (final PsiField field : psiClass.getFields()) {
106+
addFinalModifierIfNotPresent(field);
107+
}
108+
109+
// 处理类中所有方法
110+
for (final PsiMethod method : psiClass.getMethods()) {
111+
processMethod(method);
112+
}
113+
}
114+
115+
/**
116+
* 处理单个方法
117+
*/
118+
private void processMethod(final PsiMethod method) {
119+
// 给方法参数添加 final
120+
final PsiParameterList parameterList = method.getParameterList();
121+
for (final PsiParameter parameter : parameterList.getParameters()) {
122+
addFinalModifierIfNotPresent(parameter);
123+
}
124+
125+
// 给方法内局部变量添加 final
126+
final PsiCodeBlock methodBody = method.getBody();
127+
if (methodBody != null) {
128+
final Collection<PsiLocalVariable> localVariables = PsiTreeUtil.collectElementsOfType(methodBody, PsiLocalVariable.class);
129+
for (final PsiLocalVariable localVariable : localVariables) {
130+
addFinalModifierIfNotPresent(localVariable);
90131
}
91132
}
92133
}
93134

94135
private void addFinalModifierIfNotPresent(final PsiModifierListOwner element) {
95136
if (element != null) {
96137
final PsiModifierList modifierList = element.getModifierList();
97-
if (modifierList != null && !modifierList.hasExplicitModifier(PsiModifier.FINAL)) {
138+
if (modifierList != null && !modifierList.hasExplicitModifier(PsiModifier.FINAL) && canAddFinal(element)) {
98139
PsiUtil.setModifierProperty(element, PsiModifier.FINAL, true);
99140
}
100141
}
101142
}
102143

144+
/**
145+
* 判断是否可以添加 final 修饰符
146+
*/
147+
private boolean canAddFinal(final PsiModifierListOwner element) {
148+
final PsiModifierList modifierList = element.getModifierList();
149+
if (modifierList == null) {
150+
return false;
151+
}
152+
153+
// 对于字段,需要更严格的检查
154+
if (element instanceof PsiField) {
155+
final PsiField field = (PsiField) element;
156+
// 如果字段没有初始化器,需要检查是否在构造器中被初始化
157+
if (field.getInitializer() == null) {
158+
// 如果是静态字段但没有初始化器,不能添加 final
159+
if (modifierList.hasExplicitModifier(PsiModifier.STATIC)) {
160+
return false;
161+
}
162+
// 对于实例字段,检查是否在构造器中被初始化
163+
if (!isFieldInitializedInConstructor(field)) {
164+
return false;
165+
}
166+
}
167+
}
168+
169+
// 抽象方法的参数不能添加 final(虽然抽象方法本身没有方法体,但为了安全起见)
170+
if (element instanceof PsiParameter) {
171+
final PsiParameter parameter = (PsiParameter) element;
172+
final PsiElement parent = parameter.getParent();
173+
if (parent instanceof PsiParameterList) {
174+
final PsiElement grandParent = parent.getParent();
175+
if (grandParent instanceof PsiMethod) {
176+
final PsiMethod method = (PsiMethod) grandParent;
177+
return !method.hasModifierProperty(PsiModifier.ABSTRACT);
178+
}
179+
}
180+
}
181+
182+
return true;
183+
}
184+
185+
/**
186+
* 检查字段是否在构造器中被初始化
187+
*/
188+
private boolean isFieldInitializedInConstructor(final PsiField field) {
189+
final PsiClass containingClass = field.getContainingClass();
190+
if (containingClass == null) {
191+
return false;
192+
}
193+
194+
final PsiMethod[] constructors = containingClass.getConstructors();
195+
if (constructors.length == 0) {
196+
// 没有显式构造器,Java会提供默认构造器,字段不会被初始化
197+
return false;
198+
}
199+
200+
// 检查所有构造器是否都初始化了该字段
201+
for (final PsiMethod constructor : constructors) {
202+
if (!isFieldInitializedInMethod(field, constructor)) {
203+
return false;
204+
}
205+
}
206+
207+
return true;
208+
}
209+
210+
/**
211+
* 检查字段是否在指定方法中被初始化
212+
*/
213+
private boolean isFieldInitializedInMethod(final PsiField field, final PsiMethod method) {
214+
final PsiCodeBlock body = method.getBody();
215+
if (body == null) {
216+
return false;
217+
}
218+
219+
final Collection<PsiAssignmentExpression> assignments = PsiTreeUtil.collectElementsOfType(body, PsiAssignmentExpression.class);
220+
for (final PsiAssignmentExpression assignment : assignments) {
221+
final PsiExpression lhs = assignment.getLExpression();
222+
if (lhs instanceof PsiReferenceExpression) {
223+
final PsiReferenceExpression ref = (PsiReferenceExpression) lhs;
224+
if (field.equals(ref.resolve())) {
225+
return true;
226+
}
227+
}
228+
}
229+
230+
return false;
231+
}
232+
233+
/**
234+
* 检查光标是否在类名标识符上
235+
*/
236+
private boolean isCaretOnClassIdentifier(final PsiElement element, final PsiClass psiClass) {
237+
// 检查光标所在元素是否是类名标识符
238+
return element.getParent() == psiClass.getNameIdentifier() || element == psiClass.getNameIdentifier();
239+
}
240+
103241
@Override
104242
public boolean startInWriteAction() {
105243
return true;

src/test/java/lwm/plugin/intention/AddFinalIntentionTest.java

Lines changed: 127 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -105,11 +105,22 @@ public void testNotAvailableOnAlreadyFinalLocalVariable() {
105105
doTestNoChange(content);
106106
}
107107

108-
public void testNotAvailableOnClassDeclaration() {
109-
final String content = "class Te<caret>st {\n" +
110-
" void method(String param1) {}\n" +
108+
public void testAvailableOnClassDeclaration() {
109+
final String before = "class Te<caret>st {\n" +
110+
" String field1 = \"initialized\";\n" +
111+
" String field2;\n" +
112+
" void method(String param1) {\n" +
113+
" String local1 = \"hello\";\n" +
114+
" }\n" +
111115
"}";
112-
doTestNotAvailable(content);
116+
final String after = "class Test {\n" +
117+
" final String field1 = \"initialized\";\n" +
118+
" String field2;\n" +
119+
" void method(final String param1) {\n" +
120+
" final String local1 = \"hello\";\n" +
121+
" }\n" +
122+
"}";
123+
doTest(before, after);
113124
}
114125

115126
public void testNotAvailableInImportStatement() {
@@ -140,6 +151,118 @@ public void testNotAvailableOnAlreadyFinalFieldName() {
140151
doTestNoChange(content);
141152
}
142153

154+
// 测试类属性添加 final 的各种场景
155+
156+
public void testFieldWithInitializer() {
157+
final String before = "class Test {\n" +
158+
" String fi<caret>eld = \"initialized\";\n" +
159+
"}";
160+
final String after = "class Test {\n" +
161+
" final String field = \"initialized\";\n" +
162+
"}";
163+
doTest(before, after);
164+
}
165+
166+
public void testFieldInitializedInConstructor() {
167+
final String before = "class Test {\n" +
168+
" String fi<caret>eld;\n" +
169+
" Test() {\n" +
170+
" field = \"initialized\";\n" +
171+
" }\n" +
172+
"}";
173+
final String after = "class Test {\n" +
174+
" final String field;\n" +
175+
" Test() {\n" +
176+
" field = \"initialized\";\n" +
177+
" }\n" +
178+
"}";
179+
doTest(before, after);
180+
}
181+
182+
public void testFieldInitializedInMultipleConstructors() {
183+
final String before = "class Test {\n" +
184+
" String fi<caret>eld;\n" +
185+
" Test() {\n" +
186+
" field = \"default\";\n" +
187+
" }\n" +
188+
" Test(String value) {\n" +
189+
" field = value;\n" +
190+
" }\n" +
191+
"}";
192+
final String after = "class Test {\n" +
193+
" final String field;\n" +
194+
" Test() {\n" +
195+
" field = \"default\";\n" +
196+
" }\n" +
197+
" Test(String value) {\n" +
198+
" field = value;\n" +
199+
" }\n" +
200+
"}";
201+
doTest(before, after);
202+
}
203+
204+
public void testFieldNotInitializedInAllConstructors() {
205+
final String content = "class Test {\n" +
206+
" String fi<caret>eld;\n" +
207+
" Test() {\n" +
208+
" field = \"initialized\";\n" +
209+
" }\n" +
210+
" Test(String value) {\n" +
211+
" // 这个构造器没有初始化 field\n" +
212+
" }\n" +
213+
"}";
214+
// 不应该添加 final,因为并非所有构造器都初始化了字段
215+
doTestNoChange(content);
216+
}
217+
218+
public void testFieldWithoutInitializerAndNoConstructor() {
219+
final String content = "class Test {\n" +
220+
" String fi<caret>eld;\n" +
221+
"}";
222+
// 没有显式构造器且字段没有初始化器,不应该添加 final
223+
doTestNoChange(content);
224+
}
225+
226+
public void testStaticFieldWithInitializer() {
227+
final String before = "class Test {\n" +
228+
" static String fi<caret>eld = \"initialized\";\n" +
229+
"}";
230+
final String after = "class Test {\n" +
231+
" static final String field = \"initialized\";\n" +
232+
"}";
233+
doTest(before, after);
234+
}
235+
236+
public void testStaticFieldWithoutInitializer() {
237+
final String content = "class Test {\n" +
238+
" static String fi<caret>eld;\n" +
239+
"}";
240+
// 静态字段没有初始化器,不应该添加 final
241+
doTestNoChange(content);
242+
}
243+
244+
public void testClassWithMixedFields() {
245+
final String before = "class Te<caret>st {\n" +
246+
" String field1 = \"initialized\";\n" +
247+
" String field2;\n" +
248+
" static String field3 = \"static\";\n" +
249+
" static String field4;\n" +
250+
" Test() {\n" +
251+
" field2 = \"constructor\";\n" +
252+
" }\n" +
253+
"}";
254+
final String after = "class Test {\n" +
255+
" final String field1 = \"initialized\";\n" +
256+
" final String field2;\n" +
257+
" static final String field3 = \"static\";\n" +
258+
" static String field4;\n" +
259+
" Test() {\n" +
260+
" field2 = \"constructor\";\n" +
261+
" }\n" +
262+
"}";
263+
doTest(before, after);
264+
}
265+
143266

144267
@Override
145268
protected String getTestDataPath() {

0 commit comments

Comments
 (0)