From f8fff41fc45ec6d33b67c89896bc91a4848ff5be Mon Sep 17 00:00:00 2001 From: sciwhiz12 Date: Sat, 24 Oct 2026 22:46:12 +0800 Subject: [PATCH 1/3] Properly number anonymous classes within anonymous classes The numbering of anonymous classes is done independently by traversing the containing class in a post-order depth-first traversal, collecting any anonymous (nonqualified) classes, and numbering them in encounter order. This poses a problem when there are anonymous classes nested within the top-level anonymous classes (where top-level is in reference to the root containing class.) The traversal also encounters those classes, so it includes them in the numbering. This is contrary to the actual numbering scheme, which only accounts for the 'immediate' children of the containing class. In the added test, this would show up as `example.C$1` being numbered as `example.C$2`, because the actual `example.C$1$1` would be erroneously numbered as `example.C$1`. Fix this by skipping traversal of the children of nested classes (but not the nested classes themselves). --- .../java/net/neoforged/jst/api/PsiHelper.java | 10 ++++++- .../anonymous_classes/accesstransformer.cfg | 4 +++ .../anonymous_classes/expected/example/C.java | 28 +++++++++++++++++-- .../anonymous_classes/source/example/C.java | 28 +++++++++++++++++-- 4 files changed, 65 insertions(+), 5 deletions(-) diff --git a/api/src/main/java/net/neoforged/jst/api/PsiHelper.java b/api/src/main/java/net/neoforged/jst/api/PsiHelper.java index 95e917e..d96de30 100644 --- a/api/src/main/java/net/neoforged/jst/api/PsiHelper.java +++ b/api/src/main/java/net/neoforged/jst/api/PsiHelper.java @@ -163,7 +163,15 @@ private static int getNonQualifiedClassIdx(@NotNull final PsiClass psiClass, @No CachedValuesManager.getCachedValue(containingClass, () -> { var map = new ObjectIntHashMap(); int index = 0; - for (var aClass : SyntaxTraverser.psiTraverser().withRoot(containingClass).postOrderDfsTraversal().filter(PsiClass.class)) { + var traverser = SyntaxTraverser.psiTraverser() + .withRoot(containingClass) + // We only care about looking at the direct class children of the containing class + // Only traverse children of either the containing class as root note (which itself is a PsiClass), or anything that isn't + // a PsiClass; this skips traversal of the children of any nested PsiClass + .expand(c -> c == containingClass || !(c instanceof PsiClass)) + .postOrderDfsTraversal() + .filter(PsiClass.class); + for (var aClass : traverser) { // We're only interested in actual classes without qualified names (type parameters are an instance of PsiClass) if (!(aClass instanceof PsiTypeParameter) && aClass.getQualifiedName() == null) { map.put(aClass, ++index); diff --git a/tests/data/accesstransformer/anonymous_classes/accesstransformer.cfg b/tests/data/accesstransformer/anonymous_classes/accesstransformer.cfg index 677b740..56567ea 100644 --- a/tests/data/accesstransformer/anonymous_classes/accesstransformer.cfg +++ b/tests/data/accesstransformer/anonymous_classes/accesstransformer.cfg @@ -1,2 +1,6 @@ public example.C$1 method()V +public example.C$1$1 method()V public example.C$2 *() +public example.C$2$1 *() +public example.C$2$1$1 *() +public example.C$2$1$2 *() diff --git a/tests/data/accesstransformer/anonymous_classes/expected/example/C.java b/tests/data/accesstransformer/anonymous_classes/expected/example/C.java index 32e829a..43d2498 100644 --- a/tests/data/accesstransformer/anonymous_classes/expected/example/C.java +++ b/tests/data/accesstransformer/anonymous_classes/expected/example/C.java @@ -1,15 +1,39 @@ package example; public class C { - static C REGULAR = new C() { + static C REGULAR = new C() { // $1 @Override public void method() { + new C() { // $1$1 + @Override + public void method() { // $1$1 method()V + } + }; } }; - static C WILDCARD = new C() { + static C WILDCARD = new C() { // $2 @Override public void method() { + new C() { // $2$1 + @Override + public void method() { + new C() { // $2$1$1 + @Override + public void method() { + } + }; + } + + @Override + public void method2() { + new C() { // $2$1$2 + @Override + public void method2() { + } + }; + } + }; } @Override diff --git a/tests/data/accesstransformer/anonymous_classes/source/example/C.java b/tests/data/accesstransformer/anonymous_classes/source/example/C.java index 244ddcc..e0330d3 100644 --- a/tests/data/accesstransformer/anonymous_classes/source/example/C.java +++ b/tests/data/accesstransformer/anonymous_classes/source/example/C.java @@ -1,15 +1,39 @@ package example; public class C { - static C REGULAR = new C() { + static C REGULAR = new C() { // $1 @Override protected void method() { + new C() { // $1$1 + @Override + protected void method() { // $1$1 method()V + } + }; } }; - static C WILDCARD = new C() { + static C WILDCARD = new C() { // $2 @Override protected void method() { + new C() { // $2$1 + @Override + protected void method() { + new C() { // $2$1$1 + @Override + protected void method() { + } + }; + } + + @Override + protected void method2() { + new C() { // $2$1$2 + @Override + protected void method2() { + } + }; + } + }; } @Override From f46c378f00b6e10ffd7a7293457d2938cd334e32 Mon Sep 17 00:00:00 2001 From: sciwhiz12 Date: Sat, 24 Oct 2026 22:47:39 +0800 Subject: [PATCH 2/3] Skip numbering of the root node Because of post-order traversal, the root node would get added to the end of the map if the root node itself was an anonmyous/nonqualified class. --- api/src/main/java/net/neoforged/jst/api/PsiHelper.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/api/src/main/java/net/neoforged/jst/api/PsiHelper.java b/api/src/main/java/net/neoforged/jst/api/PsiHelper.java index d96de30..49d9d2b 100644 --- a/api/src/main/java/net/neoforged/jst/api/PsiHelper.java +++ b/api/src/main/java/net/neoforged/jst/api/PsiHelper.java @@ -173,7 +173,8 @@ private static int getNonQualifiedClassIdx(@NotNull final PsiClass psiClass, @No .filter(PsiClass.class); for (var aClass : traverser) { // We're only interested in actual classes without qualified names (type parameters are an instance of PsiClass) - if (!(aClass instanceof PsiTypeParameter) && aClass.getQualifiedName() == null) { + // Avoid traversing the root node itself + if (aClass != containingClass && !(aClass instanceof PsiTypeParameter) && aClass.getQualifiedName() == null) { map.put(aClass, ++index); } } From a3cdcab9a3ddb9c79a4b1eebae9f899938e81d7b Mon Sep 17 00:00:00 2001 From: sciwhiz12 Date: Sat, 24 Oct 2026 23:06:32 +0800 Subject: [PATCH 3/3] Fix typo --- api/src/main/java/net/neoforged/jst/api/PsiHelper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/main/java/net/neoforged/jst/api/PsiHelper.java b/api/src/main/java/net/neoforged/jst/api/PsiHelper.java index 49d9d2b..b347a4a 100644 --- a/api/src/main/java/net/neoforged/jst/api/PsiHelper.java +++ b/api/src/main/java/net/neoforged/jst/api/PsiHelper.java @@ -166,7 +166,7 @@ private static int getNonQualifiedClassIdx(@NotNull final PsiClass psiClass, @No var traverser = SyntaxTraverser.psiTraverser() .withRoot(containingClass) // We only care about looking at the direct class children of the containing class - // Only traverse children of either the containing class as root note (which itself is a PsiClass), or anything that isn't + // Only traverse children of either the containing class as root node (which itself is a PsiClass), or anything that isn't // a PsiClass; this skips traversal of the children of any nested PsiClass .expand(c -> c == containingClass || !(c instanceof PsiClass)) .postOrderDfsTraversal()