Skip to content

Commit bb696b9

Browse files
committed
Organize Members hint: don't rearrange record components
if the formatter is configured to sort members alphabetically, the hint should not rearrange record components.
1 parent e80ec88 commit bb696b9

File tree

3 files changed

+171
-36
lines changed

3 files changed

+171
-36
lines changed

java/java.hints/src/org/netbeans/modules/java/hints/OrganizeMembers.java

Lines changed: 30 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
import org.netbeans.api.java.source.*;
4646
import org.netbeans.api.java.source.JavaSource.Phase;
4747
import org.netbeans.api.java.source.ModificationResult.Difference;
48-
import org.netbeans.api.progress.ProgressUtils;
48+
import org.netbeans.api.progress.BaseProgressUtils;
4949
import org.netbeans.editor.BaseAction;
5050
import org.netbeans.editor.BaseDocument;
5151
import org.netbeans.editor.GuardedDocument;
@@ -73,7 +73,7 @@
7373
@Hint(displayName = "#DN_org.netbeans.modules.java.hints.OrganizeMembers", description = "#DESC_org.netbeans.modules.java.hints.OrganizeMembers", category = "class_structure", enabled = false)
7474
public class OrganizeMembers {
7575

76-
@TriggerTreeKind(Kind.CLASS)
76+
@TriggerTreeKind({Kind.CLASS, Kind.RECORD, Kind.ENUM})
7777
public static ErrorDescription checkMembers(final HintContext context) {
7878
for (Diagnostic<?> d : context.getInfo().getDiagnostics()) {
7979
if (Hacks.isSyntaxError(d)) {
@@ -122,49 +122,44 @@ private static void doOrganizeMembers(WorkingCopy copy, TreePath path) {
122122
ClassTree clazz = (ClassTree) path.getLeaf();
123123
clazz = gu.importComments(clazz, copy.getCompilationUnit());
124124
TreeMaker maker = copy.getTreeMaker();
125+
125126
ClassTree nue = maker.Class(clazz.getModifiers(), clazz.getSimpleName(), clazz.getTypeParameters(), clazz.getExtendsClause(), clazz.getImplementsClause(), clazz.getPermitsClause(), Collections.<Tree>emptyList());
126-
List<Tree> members = new ArrayList<>(clazz.getMembers().size());
127-
Map<Tree, Tree> memberMap = new HashMap<>(clazz.getMembers().size());
127+
List<Tree> members = new ArrayList<>();
128+
Map<Tree, Tree> memberMap = new HashMap<>();
128129

129-
List<Tree> enumValues = new ArrayList<>();
130+
List<Tree> fixedMembers = new ArrayList<>();
130131
for (Tree tree : clazz.getMembers()) {
131-
if (copy.getTreeUtilities().isSynthetic(new TreePath(path, tree))) {
132+
// isSynthetic does not consider record components to be synthetic
133+
if (copy.getTreeUtilities().isSynthetic(new TreePath(path, tree))
134+
&& !(tree.getKind() == Kind.VARIABLE && copy.getTreeUtilities().isRecordComponent((VariableTree)tree))) {
132135
continue;
133136
}
134-
Tree member;
135-
switch (tree.getKind()) {
136-
case CLASS:
137-
case INTERFACE:
138-
case ENUM:
139-
case ANNOTATION_TYPE:
140-
member = maker.setLabel(tree, ((ClassTree)tree).getSimpleName());
141-
break;
142-
case VARIABLE:
143-
member = maker.setLabel(tree, ((VariableTree)tree).getName());
144-
if (copy.getTreeUtilities().isEnumConstant((VariableTree)tree)) {
145-
enumValues.add(member);
137+
Tree member = switch (tree.getKind()) {
138+
case CLASS, INTERFACE, ENUM, RECORD, ANNOTATION_TYPE ->
139+
maker.setLabel(tree, ((ClassTree)tree).getSimpleName());
140+
case VARIABLE -> {
141+
VariableTree vt = (VariableTree)tree;
142+
Tree mem = maker.setLabel(tree, vt.getName());
143+
if (copy.getTreeUtilities().isEnumConstant(vt) || copy.getTreeUtilities().isRecordComponent(vt)) {
144+
fixedMembers.add(mem);
146145
}
147-
break;
148-
case METHOD:
149-
member = maker.setLabel(tree, ((MethodTree)tree).getName());
150-
break;
151-
case BLOCK:
152-
member = maker.asReplacementOf(maker.Block(((BlockTree)tree).getStatements(), ((BlockTree)tree).isStatic()), tree, true);
153-
break;
154-
default:
155-
member = tree;
156-
}
146+
yield mem;
147+
}
148+
case METHOD -> maker.setLabel(tree, ((MethodTree)tree).getName());
149+
case BLOCK -> maker.asReplacementOf(maker.Block(((BlockTree)tree).getStatements(), ((BlockTree)tree).isStatic()), tree, true);
150+
default -> tree;
151+
};
157152
members.add(member);
158153
memberMap.put(member, tree);
159154
}
160155
// fool the generator utilities with cloned members, so it does not take positions into account
161-
if (enumValues.isEmpty()) {
156+
if (fixedMembers.isEmpty()) {
162157
nue = GeneratorUtilities.get(copy).insertClassMembers(nue, members);
163158
} else {
164-
members.removeAll(enumValues);
159+
members.removeAll(fixedMembers);
165160
int max = nue.getMembers().size();
166-
// insert the enum values in the original order
167-
for (Tree t : enumValues) {
161+
// insert the enum values or record components in the original order
162+
for (Tree t : fixedMembers) {
168163
nue = maker.insertClassMember(nue, max++, t);
169164
}
170165
nue = GeneratorUtilities.get(copy).insertClassMembers(nue, members);
@@ -181,8 +176,8 @@ private static void doOrganizeMembers(WorkingCopy copy, TreePath path) {
181176
}
182177

183178
private static boolean checkGuarded(Document doc, List<? extends Difference> diffs) {
184-
if (doc instanceof GuardedDocument) {
185-
MarkBlockChain chain = ((GuardedDocument) doc).getGuardedBlockChain();
179+
if (doc instanceof GuardedDocument guardedDocument) {
180+
MarkBlockChain chain = guardedDocument.getGuardedBlockChain();
186181
for (Difference diff : diffs) {
187182
if ((chain.compareBlock(diff.getStartPosition().getOffset(), diff.getEndPosition().getOffset()) & MarkBlock.OVERLAP) != 0) {
188183
return true;
@@ -226,7 +221,7 @@ public void actionPerformed(final ActionEvent evt, final JTextComponent componen
226221
final Source source = Source.create(doc);
227222
if (source != null) {
228223
final AtomicBoolean cancel = new AtomicBoolean();
229-
ProgressUtils.runOffEventDispatchThread(new Runnable() {
224+
BaseProgressUtils.runOffEventDispatchThread(new Runnable() {
230225
@Override
231226
public void run() {
232227
try {
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.netbeans.modules.java.hints;
20+
21+
import java.util.prefs.Preferences;
22+
import org.netbeans.api.editor.mimelookup.MimeLookup;
23+
import org.netbeans.junit.NbTestCase;
24+
import org.netbeans.modules.editor.java.JavaKit;
25+
import org.netbeans.modules.java.hints.test.api.HintTest;
26+
import org.netbeans.modules.java.ui.FmtOptions;
27+
import org.openide.util.NbBundle;
28+
29+
/**
30+
* @author mbien
31+
*/
32+
public class OrganizeMembersTest extends NbTestCase {
33+
34+
public OrganizeMembersTest(String name) {
35+
super(name);
36+
}
37+
38+
@Override
39+
protected void setUp() throws Exception {
40+
super.setUp();
41+
HintTest.create(); // initializes lookup (stolen from bugs.TinyTest)
42+
MimeLookup.getLookup(JavaKit.JAVA_MIME_TYPE)
43+
.lookup(Preferences.class)
44+
.putBoolean(FmtOptions.sortMembersInGroups, true);
45+
}
46+
47+
@Override
48+
protected boolean runInEQ() {
49+
// without it, hint markers are sometimes not found
50+
return true;
51+
}
52+
53+
public void testSimpleMemberSort() throws Exception {
54+
HintTest.create()
55+
.sourceLevel(11)
56+
.input(
57+
"""
58+
package test;
59+
public class Test {
60+
public static int b;
61+
public static int a;
62+
}
63+
"""
64+
)
65+
.run(OrganizeMembers.class)
66+
.findWarning("2:4-2:24:verifier:" + NbBundle.getMessage(OrganizeMembers.class, "MSG_OragnizeMembers"))
67+
.applyFix()
68+
.assertOutput(
69+
"""
70+
package test;
71+
public class Test {
72+
public static int a;
73+
public static int b;
74+
}
75+
"""
76+
);
77+
}
78+
79+
public void testRecordMemberSort() throws Exception {
80+
HintTest.create()
81+
.sourceLevel(17)
82+
.input(
83+
"""
84+
package test;
85+
public record Test(int d, int c) {
86+
public static int b;
87+
public static int a;
88+
}
89+
"""
90+
)
91+
.run(OrganizeMembers.class)
92+
.findWarning("2:4-2:24:verifier:" + NbBundle.getMessage(OrganizeMembers.class, "MSG_OragnizeMembers"))
93+
.applyFix()
94+
.assertOutput(
95+
"""
96+
package test;
97+
public record Test(int d, int c) {
98+
public static int a;
99+
public static int b;
100+
}
101+
"""
102+
);
103+
}
104+
105+
public void testEnumMemberSort() throws Exception {
106+
HintTest.create()
107+
.sourceLevel(11)
108+
.input(
109+
"""
110+
package test;
111+
public enum Test {
112+
B, A;
113+
public static int b;
114+
public static int a;
115+
}
116+
"""
117+
)
118+
.run(OrganizeMembers.class)
119+
.findWarning("3:4-3:24:verifier:" + NbBundle.getMessage(OrganizeMembers.class, "MSG_OragnizeMembers"))
120+
.applyFix()
121+
.assertOutput(
122+
"""
123+
package test;
124+
public enum Test {
125+
B, A;
126+
public static int a;
127+
public static int b;
128+
}
129+
"""
130+
);
131+
}
132+
133+
}

java/java.source.base/src/org/netbeans/api/java/source/TreeUtilities.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,14 @@ public boolean isEnum(ClassTree tree) {
151151
public boolean isEnumConstant(VariableTree tree) {
152152
return (((JCTree.JCModifiers) tree.getModifiers()).flags & Flags.ENUM) != 0;
153153
}
154-
154+
155+
/**
156+
* Checks whether given variable tree represents a record component.
157+
*/
158+
public boolean isRecordComponent(VariableTree tree) {
159+
return (((JCTree.JCModifiers) tree.getModifiers()).flags & Flags.RECORD) != 0;
160+
}
161+
155162
/**Checks whether the given tree represents an annotation.
156163
* @deprecated since 0.67, <code>Tree.getKind() == Kind.ANNOTATION_TYPE</code> should be used instead.
157164
*/

0 commit comments

Comments
 (0)